mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-03 08:46:05 +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;
|
||||
}
|
||||
|
||||
@ -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.
|
||||
if (!tx_info_add_input(&info, txinput)) {
|
||||
return false;
|
||||
@ -1230,8 +1249,6 @@ static bool signing_check_input(const TxInputType *txinput) {
|
||||
tx_serialize_input_hash(&ti, txinput);
|
||||
}
|
||||
#endif
|
||||
// hash all input data to check it later (relevant for fee computation)
|
||||
tx_input_check_hash(&hasher_check, txinput);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1413,17 +1430,29 @@ static bool save_signature(TxInputType *txinput) {
|
||||
}
|
||||
|
||||
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.
|
||||
// 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
|
||||
// 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 ||
|
||||
memcmp(orig_input->prev_hash.bytes, input.prev_hash.bytes,
|
||||
input.prev_hash.size) != 0 ||
|
||||
orig_input->prev_index != input.prev_index ||
|
||||
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,
|
||||
_("Original input does not match current input."));
|
||||
signing_abort();
|
||||
|
@ -216,14 +216,88 @@ bool compute_address(const CoinInfo *coin, InputScriptType script_type,
|
||||
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,
|
||||
const HDNode *root, TxOutputType *in, TxOutputBinType *out,
|
||||
bool needs_confirm) {
|
||||
memzero(out, sizeof(TxOutputBinType));
|
||||
out->amount = in->amount;
|
||||
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) {
|
||||
// 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
|
||||
}
|
||||
|
||||
addr_raw_len = base58_decode_check(in->address, coin->curve->hasher_base58,
|
||||
addr_raw, MAX_ADDR_RAW_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 {
|
||||
if (!address_to_script_pubkey(coin, in->address, out->script_pubkey.bytes,
|
||||
&out->script_pubkey.size)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -368,6 +384,24 @@ int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
|
||||
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,
|
||||
uint8_t *out) {
|
||||
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,
|
||||
sizeof(input->sequence));
|
||||
hasher_Update(hasher, (const uint8_t *)&input->amount, sizeof(input->amount));
|
||||
tx_script_hash(hasher, input->script_pubkey.size, input->script_pubkey.bytes);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,8 @@ uint32_t serialize_script_multisig(const CoinInfo *coin,
|
||||
int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
|
||||
const HDNode *root, TxOutputType *in, TxOutputBinType *out,
|
||||
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);
|
||||
uint32_t tx_prevout_hash(Hasher *hasher, const TxInputType *input);
|
||||
|
@ -224,9 +224,14 @@ class TestMultisig:
|
||||
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)
|
||||
|
||||
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):
|
||||
"""
|
||||
In Phases 1 and 2 the attacker replaces a non-multisig input
|
||||
|
Loading…
Reference in New Issue
Block a user