mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-03 16:56:07 +00:00
chore(legacy): Support script_pubkey parameter for Bitcoin inputs.
This commit is contained in:
parent
aed79eec65
commit
9a051df127
@ -1034,6 +1034,15 @@ static bool signing_validate_input(const TxInputType *txinput) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (txinput->has_script_pubkey) {
|
||||||
|
// scriptPubKey should only be provided for external inputs, which are not
|
||||||
|
// supported in this firmware
|
||||||
|
fsm_sendFailure(FailureType_Failure_DataError,
|
||||||
|
_("Input's script_pubkey provided but not expected."));
|
||||||
|
signing_abort();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1212,7 +1221,17 @@ static void tx_info_finish(TxInfo *tx_info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool signing_check_input(const TxInputType *txinput) {
|
static bool signing_check_input(TxInputType *txinput) {
|
||||||
|
// hash all input data to check it later (relevant for fee computation)
|
||||||
|
tx_input_check_hash(&hasher_check, txinput);
|
||||||
|
|
||||||
|
if (!fill_input_script_pubkey(coin, &root, txinput)) {
|
||||||
|
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||||
|
_("Failed to derive scriptPubKey"));
|
||||||
|
signing_abort();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Add input to BIP-143/BIP-341 computation.
|
// Add input to BIP-143/BIP-341 computation.
|
||||||
if (!tx_info_add_input(&info, txinput)) {
|
if (!tx_info_add_input(&info, txinput)) {
|
||||||
return false;
|
return false;
|
||||||
@ -1230,8 +1249,6 @@ static bool signing_check_input(const TxInputType *txinput) {
|
|||||||
tx_serialize_input_hash(&ti, txinput);
|
tx_serialize_input_hash(&ti, txinput);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// hash all input data to check it later (relevant for fee computation)
|
|
||||||
tx_input_check_hash(&hasher_check, txinput);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1413,17 +1430,29 @@ static bool save_signature(TxInputType *txinput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool signing_check_orig_input(TxInputType *orig_input) {
|
static bool signing_check_orig_input(TxInputType *orig_input) {
|
||||||
|
if (!fill_input_script_pubkey(coin, &root, orig_input)) {
|
||||||
|
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||||
|
_("Failed to derive scriptPubKey"));
|
||||||
|
signing_abort();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the original input matches the current input.
|
// Verify that the original input matches the current input.
|
||||||
// An input is characterized by its prev_hash and prev_index. We also
|
// An input is characterized by its prev_hash and prev_index. We also
|
||||||
// check that the amounts match, so that we don't have to stream the
|
// check that the amounts match, so that we don't have to stream the
|
||||||
// prevtx twice for the same prevtx output. Verifying that script_type
|
// prevtx twice for the same prevtx output. Verifying that script_type
|
||||||
// matches is just a sanity check.
|
// matches is just a sanity check. When all inputs are taproot, we don't
|
||||||
|
// check the prevtxs, so we have to ensure that the claims about the
|
||||||
|
// script_pubkey values and amounts remain consistent throughout.
|
||||||
if (orig_input->prev_hash.size != input.prev_hash.size ||
|
if (orig_input->prev_hash.size != input.prev_hash.size ||
|
||||||
memcmp(orig_input->prev_hash.bytes, input.prev_hash.bytes,
|
memcmp(orig_input->prev_hash.bytes, input.prev_hash.bytes,
|
||||||
input.prev_hash.size) != 0 ||
|
input.prev_hash.size) != 0 ||
|
||||||
orig_input->prev_index != input.prev_index ||
|
orig_input->prev_index != input.prev_index ||
|
||||||
orig_input->amount != input.amount ||
|
orig_input->amount != input.amount ||
|
||||||
orig_input->script_type != input.script_type) {
|
orig_input->script_type != input.script_type ||
|
||||||
|
orig_input->script_pubkey.size != input.script_pubkey.size ||
|
||||||
|
memcmp(orig_input->script_pubkey.bytes, input.script_pubkey.bytes,
|
||||||
|
input.script_pubkey.size) != 0) {
|
||||||
fsm_sendFailure(FailureType_Failure_ProcessError,
|
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||||
_("Original input does not match current input."));
|
_("Original input does not match current input."));
|
||||||
signing_abort();
|
signing_abort();
|
||||||
|
@ -216,14 +216,88 @@ bool compute_address(const CoinInfo *coin, InputScriptType script_type,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int address_to_script_pubkey(const CoinInfo *coin, const char *address,
|
||||||
|
uint8_t *script_pubkey, pb_size_t *size) {
|
||||||
|
uint8_t addr_raw[MAX_ADDR_RAW_SIZE] = {0};
|
||||||
|
size_t addr_raw_len = base58_decode_check(address, coin->curve->hasher_base58,
|
||||||
|
addr_raw, MAX_ADDR_RAW_SIZE);
|
||||||
|
|
||||||
|
// P2PKH
|
||||||
|
size_t prefix_len = address_prefix_bytes_len(coin->address_type);
|
||||||
|
if (addr_raw_len == 20 + prefix_len &&
|
||||||
|
address_check_prefix(addr_raw, coin->address_type)) {
|
||||||
|
script_pubkey[0] = 0x76; // OP_DUP
|
||||||
|
script_pubkey[1] = 0xA9; // OP_HASH_160
|
||||||
|
script_pubkey[2] = 0x14; // pushing 20 bytes
|
||||||
|
memcpy(script_pubkey + 3, addr_raw + prefix_len, 20);
|
||||||
|
script_pubkey[23] = 0x88; // OP_EQUALVERIFY
|
||||||
|
script_pubkey[24] = 0xAC; // OP_CHECKSIG
|
||||||
|
*size = 25;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// P2SH
|
||||||
|
prefix_len = address_prefix_bytes_len(coin->address_type_p2sh);
|
||||||
|
if (addr_raw_len == 20 + prefix_len &&
|
||||||
|
address_check_prefix(addr_raw, coin->address_type_p2sh)) {
|
||||||
|
script_pubkey[0] = 0xA9; // OP_HASH_160
|
||||||
|
script_pubkey[1] = 0x14; // pushing 20 bytes
|
||||||
|
memcpy(script_pubkey + 2, addr_raw + prefix_len, 20);
|
||||||
|
script_pubkey[22] = 0x87; // OP_EQUAL
|
||||||
|
*size = 23;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coin->cashaddr_prefix &&
|
||||||
|
cash_addr_decode(addr_raw, &addr_raw_len, coin->cashaddr_prefix,
|
||||||
|
address)) {
|
||||||
|
if (addr_raw_len == 21 && addr_raw[0] == (CASHADDR_P2KH | CASHADDR_160)) {
|
||||||
|
script_pubkey[0] = 0x76; // OP_DUP
|
||||||
|
script_pubkey[1] = 0xA9; // OP_HASH_160
|
||||||
|
script_pubkey[2] = 0x14; // pushing 20 bytes
|
||||||
|
memcpy(script_pubkey + 3, addr_raw + 1, 20);
|
||||||
|
script_pubkey[23] = 0x88; // OP_EQUALVERIFY
|
||||||
|
script_pubkey[24] = 0xAC; // OP_CHECKSIG
|
||||||
|
*size = 25;
|
||||||
|
return 1;
|
||||||
|
} else if (addr_raw_len == 21 &&
|
||||||
|
addr_raw[0] == (CASHADDR_P2SH | CASHADDR_160)) {
|
||||||
|
script_pubkey[0] = 0xA9; // OP_HASH_160
|
||||||
|
script_pubkey[1] = 0x14; // pushing 20 bytes
|
||||||
|
memcpy(script_pubkey + 2, addr_raw + 1, 20);
|
||||||
|
script_pubkey[22] = 0x87; // OP_EQUAL
|
||||||
|
*size = 23;
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SegWit
|
||||||
|
if (coin->bech32_prefix) {
|
||||||
|
int witver = 0;
|
||||||
|
if (!segwit_addr_decode(&witver, addr_raw, &addr_raw_len,
|
||||||
|
coin->bech32_prefix, address)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// push 1 byte version id (opcode OP_0 = 0, OP_i = 80+i)
|
||||||
|
// push addr_raw (segwit_addr_decode makes sure addr_raw_len is at most 40)
|
||||||
|
script_pubkey[0] = witver == 0 ? 0 : 80 + witver;
|
||||||
|
script_pubkey[1] = addr_raw_len;
|
||||||
|
memcpy(script_pubkey + 2, addr_raw, addr_raw_len);
|
||||||
|
*size = addr_raw_len + 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
|
int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
|
||||||
const HDNode *root, TxOutputType *in, TxOutputBinType *out,
|
const HDNode *root, TxOutputType *in, TxOutputBinType *out,
|
||||||
bool needs_confirm) {
|
bool needs_confirm) {
|
||||||
memzero(out, sizeof(TxOutputBinType));
|
memzero(out, sizeof(TxOutputBinType));
|
||||||
out->amount = in->amount;
|
out->amount = in->amount;
|
||||||
out->decred_script_version = DECRED_SCRIPT_VERSION;
|
out->decred_script_version = DECRED_SCRIPT_VERSION;
|
||||||
uint8_t addr_raw[MAX_ADDR_RAW_SIZE] = {0};
|
|
||||||
size_t addr_raw_len = 0;
|
|
||||||
|
|
||||||
if (in->script_type == OutputScriptType_PAYTOOPRETURN) {
|
if (in->script_type == OutputScriptType_PAYTOOPRETURN) {
|
||||||
// only 0 satoshi allowed for OP_RETURN
|
// only 0 satoshi allowed for OP_RETURN
|
||||||
@ -295,66 +369,8 @@ int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
|
|||||||
return 0; // failed to compile output
|
return 0; // failed to compile output
|
||||||
}
|
}
|
||||||
|
|
||||||
addr_raw_len = base58_decode_check(in->address, coin->curve->hasher_base58,
|
if (!address_to_script_pubkey(coin, in->address, out->script_pubkey.bytes,
|
||||||
addr_raw, MAX_ADDR_RAW_SIZE);
|
&out->script_pubkey.size)) {
|
||||||
size_t prefix_len = 0;
|
|
||||||
// p2pkh
|
|
||||||
if (addr_raw_len ==
|
|
||||||
20 + (prefix_len = address_prefix_bytes_len(coin->address_type)) &&
|
|
||||||
address_check_prefix(addr_raw, coin->address_type)) {
|
|
||||||
out->script_pubkey.bytes[0] = 0x76; // OP_DUP
|
|
||||||
out->script_pubkey.bytes[1] = 0xA9; // OP_HASH_160
|
|
||||||
out->script_pubkey.bytes[2] = 0x14; // pushing 20 bytes
|
|
||||||
memcpy(out->script_pubkey.bytes + 3, addr_raw + prefix_len, 20);
|
|
||||||
out->script_pubkey.bytes[23] = 0x88; // OP_EQUALVERIFY
|
|
||||||
out->script_pubkey.bytes[24] = 0xAC; // OP_CHECKSIG
|
|
||||||
out->script_pubkey.size = 25;
|
|
||||||
} else
|
|
||||||
// p2sh
|
|
||||||
if (addr_raw_len == 20 + (prefix_len = address_prefix_bytes_len(
|
|
||||||
coin->address_type_p2sh)) &&
|
|
||||||
address_check_prefix(addr_raw, coin->address_type_p2sh)) {
|
|
||||||
out->script_pubkey.bytes[0] = 0xA9; // OP_HASH_160
|
|
||||||
out->script_pubkey.bytes[1] = 0x14; // pushing 20 bytes
|
|
||||||
memcpy(out->script_pubkey.bytes + 2, addr_raw + prefix_len, 20);
|
|
||||||
out->script_pubkey.bytes[22] = 0x87; // OP_EQUAL
|
|
||||||
out->script_pubkey.size = 23;
|
|
||||||
} else if (coin->cashaddr_prefix &&
|
|
||||||
cash_addr_decode(addr_raw, &addr_raw_len, coin->cashaddr_prefix,
|
|
||||||
in->address)) {
|
|
||||||
if (addr_raw_len == 21 && addr_raw[0] == (CASHADDR_P2KH | CASHADDR_160)) {
|
|
||||||
out->script_pubkey.bytes[0] = 0x76; // OP_DUP
|
|
||||||
out->script_pubkey.bytes[1] = 0xA9; // OP_HASH_160
|
|
||||||
out->script_pubkey.bytes[2] = 0x14; // pushing 20 bytes
|
|
||||||
memcpy(out->script_pubkey.bytes + 3, addr_raw + 1, 20);
|
|
||||||
out->script_pubkey.bytes[23] = 0x88; // OP_EQUALVERIFY
|
|
||||||
out->script_pubkey.bytes[24] = 0xAC; // OP_CHECKSIG
|
|
||||||
out->script_pubkey.size = 25;
|
|
||||||
|
|
||||||
} else if (addr_raw_len == 21 &&
|
|
||||||
addr_raw[0] == (CASHADDR_P2SH | CASHADDR_160)) {
|
|
||||||
out->script_pubkey.bytes[0] = 0xA9; // OP_HASH_160
|
|
||||||
out->script_pubkey.bytes[1] = 0x14; // pushing 20 bytes
|
|
||||||
memcpy(out->script_pubkey.bytes + 2, addr_raw + 1, 20);
|
|
||||||
out->script_pubkey.bytes[22] = 0x87; // OP_EQUAL
|
|
||||||
out->script_pubkey.size = 23;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else if (coin->bech32_prefix) {
|
|
||||||
int witver = 0;
|
|
||||||
if (!segwit_addr_decode(&witver, addr_raw, &addr_raw_len,
|
|
||||||
coin->bech32_prefix, in->address)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// segwit:
|
|
||||||
// push 1 byte version id (opcode OP_0 = 0, OP_i = 80+i)
|
|
||||||
// push addr_raw (segwit_addr_decode makes sure addr_raw_len is at most 40)
|
|
||||||
out->script_pubkey.bytes[0] = witver == 0 ? 0 : 80 + witver;
|
|
||||||
out->script_pubkey.bytes[1] = addr_raw_len;
|
|
||||||
memcpy(out->script_pubkey.bytes + 2, addr_raw, addr_raw_len);
|
|
||||||
out->script_pubkey.size = addr_raw_len + 2;
|
|
||||||
} else {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,6 +384,24 @@ int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
|
|||||||
return out->script_pubkey.size;
|
return out->script_pubkey.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int fill_input_script_pubkey(const CoinInfo *coin, const HDNode *root,
|
||||||
|
TxInputType *in) {
|
||||||
|
static CONFIDENTIAL HDNode node;
|
||||||
|
memcpy(&node, root, sizeof(HDNode));
|
||||||
|
char address[MAX_ADDR_SIZE] = {0};
|
||||||
|
bool res = true;
|
||||||
|
res = res && hdnode_private_ckd_cached(&node, in->address_n,
|
||||||
|
in->address_n_count, NULL);
|
||||||
|
res = res && (hdnode_fill_public_key(&node) == 0);
|
||||||
|
res = res && compute_address(coin, in->script_type, &node, in->has_multisig,
|
||||||
|
&in->multisig, address);
|
||||||
|
memzero(&node, sizeof(node));
|
||||||
|
res = res && address_to_script_pubkey(coin, address, in->script_pubkey.bytes,
|
||||||
|
&in->script_pubkey.size);
|
||||||
|
in->has_script_pubkey = res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t compile_script_sig(uint32_t address_type, const uint8_t *pubkeyhash,
|
uint32_t compile_script_sig(uint32_t address_type, const uint8_t *pubkeyhash,
|
||||||
uint8_t *out) {
|
uint8_t *out) {
|
||||||
if (coinByAddressType(address_type)) { // valid coin type
|
if (coinByAddressType(address_type)) { // valid coin type
|
||||||
@ -506,6 +540,7 @@ void tx_input_check_hash(Hasher *hasher, const TxInputType *input) {
|
|||||||
hasher_Update(hasher, (const uint8_t *)&input->sequence,
|
hasher_Update(hasher, (const uint8_t *)&input->sequence,
|
||||||
sizeof(input->sequence));
|
sizeof(input->sequence));
|
||||||
hasher_Update(hasher, (const uint8_t *)&input->amount, sizeof(input->amount));
|
hasher_Update(hasher, (const uint8_t *)&input->amount, sizeof(input->amount));
|
||||||
|
tx_script_hash(hasher, input->script_pubkey.size, input->script_pubkey.bytes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,8 @@ uint32_t serialize_script_multisig(const CoinInfo *coin,
|
|||||||
int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
|
int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
|
||||||
const HDNode *root, TxOutputType *in, TxOutputBinType *out,
|
const HDNode *root, TxOutputType *in, TxOutputBinType *out,
|
||||||
bool needs_confirm);
|
bool needs_confirm);
|
||||||
|
int fill_input_script_pubkey(const CoinInfo *coin, const HDNode *root,
|
||||||
|
TxInputType *in);
|
||||||
|
|
||||||
void tx_input_check_hash(Hasher *hasher, const TxInputType *input);
|
void tx_input_check_hash(Hasher *hasher, const TxInputType *input);
|
||||||
uint32_t tx_prevout_hash(Hasher *hasher, const TxInputType *input);
|
uint32_t tx_prevout_hash(Hasher *hasher, const TxInputType *input);
|
||||||
|
@ -224,9 +224,14 @@ class TestMultisig:
|
|||||||
script_type=proto.OutputScriptType.PAYTOADDRESS,
|
script_type=proto.OutputScriptType.PAYTOADDRESS,
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(TrezorFailure, match="Pubkey not found in multisig script"):
|
with pytest.raises(TrezorFailure) as exc:
|
||||||
btc.sign_tx(client, "Bitcoin", [inp1], [out1], prev_txes=TX_API)
|
btc.sign_tx(client, "Bitcoin", [inp1], [out1], prev_txes=TX_API)
|
||||||
|
|
||||||
|
if client.features.model == "1":
|
||||||
|
assert exc.value.message.endswith("Failed to derive scriptPubKey")
|
||||||
|
else:
|
||||||
|
assert exc.value.message.endswith("Pubkey not found in multisig script")
|
||||||
|
|
||||||
def test_attack_change_input(self, client):
|
def test_attack_change_input(self, client):
|
||||||
"""
|
"""
|
||||||
In Phases 1 and 2 the attacker replaces a non-multisig input
|
In Phases 1 and 2 the attacker replaces a non-multisig input
|
||||||
|
Loading…
Reference in New Issue
Block a user