def test_paths_bch(self): incorrect_derivation_paths = [ ([44 | HARDENED], InputScriptType.SPENDADDRESS), # invalid length ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0 | HARDENED], InputScriptType.SPENDADDRESS), # too many HARDENED ([49 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDP2SHWITNESS), # bch is not segwit coin so 49' is not allowed ([84 | HARDENED, 145 | HARDENED, 1 | HARDENED, 0, 1], InputScriptType.SPENDWITNESS), # and neither is 84' ([44 | HARDENED, 145 | HARDENED], InputScriptType.SPENDADDRESS), # invalid length ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 0, 0, 0], InputScriptType.SPENDADDRESS), # invalid length ([44 | HARDENED, 123 | HARDENED, 0 | HARDENED, 0, 0, 0], InputScriptType.SPENDADDRESS), # invalid slip44 ([44 | HARDENED, 145 | HARDENED, 1000 | HARDENED, 0, 0], InputScriptType.SPENDADDRESS), # account too high ([44 | HARDENED, 145 | HARDENED, 1 | HARDENED, 2, 0], InputScriptType.SPENDADDRESS), # invalid y ([44 | HARDENED, 145 | HARDENED, 1 | HARDENED, 0, 10000000], InputScriptType.SPENDADDRESS), # address index too high ([84 | HARDENED, 145 | HARDENED, 1 | HARDENED, 0, 10000000], InputScriptType.SPENDWITNESS), # address index too high ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDWITNESS), # input type mismatch ] correct_derivation_paths = [ ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDADDRESS), ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 1, 0], InputScriptType.SPENDADDRESS), ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 1123], InputScriptType.SPENDADDRESS), ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 1, 44444], InputScriptType.SPENDADDRESS), ([44 | HARDENED, 145 | HARDENED, 5 | HARDENED, 0, 0], InputScriptType.SPENDADDRESS), ([48 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDMULTISIG), ([48 | HARDENED, 145 | HARDENED, 5 | HARDENED, 0, 0], InputScriptType.SPENDMULTISIG), ([48 | HARDENED, 145 | HARDENED, 5 | HARDENED, 0, 10], InputScriptType.SPENDMULTISIG), ] coin = coins.by_name('Bcash') # segwit is disabled for path, input_type in incorrect_derivation_paths: self.assertFalse(validate_full_path(path, coin, input_type)) for path, input_type in correct_derivation_paths: self.assertTrue(validate_full_path(path, coin, input_type))
def test_paths_other(self): incorrect_derivation_paths = [ ([44 | HARDENED, 3 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDMULTISIG), # input type mismatch ] correct_derivation_paths = [ ([44 | HARDENED, 3 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDADDRESS), ([44 | HARDENED, 3 | HARDENED, 0 | HARDENED, 1, 0], InputScriptType.SPENDADDRESS), ([44 | HARDENED, 3 | HARDENED, 0 | HARDENED, 0, 1123], InputScriptType.SPENDADDRESS), ([44 | HARDENED, 3 | HARDENED, 0 | HARDENED, 1, 44444], InputScriptType.SPENDADDRESS), ] coin = coins.by_name('Dogecoin') # segwit is disabled for path, input_type in correct_derivation_paths: self.assertTrue(validate_full_path(path, coin, input_type)) for path, input_type in incorrect_derivation_paths: self.assertFalse(validate_full_path(path, coin, input_type))
def test_paths_btc(self): incorrect_derivation_paths = [ ([49 | HARDENED], InputScriptType.SPENDP2SHWITNESS), # invalid length ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0 | HARDENED], InputScriptType.SPENDP2SHWITNESS), # too many HARDENED ([49 | HARDENED, 0 | HARDENED], InputScriptType.SPENDP2SHWITNESS), # invalid length ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0, 0, 0], InputScriptType.SPENDP2SHWITNESS), # invalid length ([49 | HARDENED, 123 | HARDENED, 0 | HARDENED, 0, 0, 0], InputScriptType.SPENDP2SHWITNESS), # invalid slip44 ([49 | HARDENED, 0 | HARDENED, 1000 | HARDENED, 0, 0], InputScriptType.SPENDP2SHWITNESS), # account too high ([49 | HARDENED, 0 | HARDENED, 1 | HARDENED, 2, 0], InputScriptType.SPENDP2SHWITNESS), # invalid y ([49 | HARDENED, 0 | HARDENED, 1 | HARDENED, 0, 10000000], InputScriptType.SPENDP2SHWITNESS), # address index too high ([84 | HARDENED, 0 | HARDENED, 1 | HARDENED, 0, 10000000], InputScriptType.SPENDWITNESS), # address index too high ([49 | HARDENED, 0 | HARDENED, 1 | HARDENED, 0, 0], InputScriptType.SPENDWITNESS), # invalid input type ([84 | HARDENED, 0 | HARDENED, 1 | HARDENED, 0, 0], InputScriptType.SPENDP2SHWITNESS), # invalid input type ([49 | HARDENED, 0 | HARDENED, 5 | HARDENED, 0, 10], InputScriptType.SPENDMULTISIG), # invalid input type ] correct_derivation_paths = [ ([44 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDADDRESS), # btc is segwit coin, but non-segwit paths are allowed as well ([44 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 1], InputScriptType.SPENDADDRESS), ([44 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 0], InputScriptType.SPENDADDRESS), ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDP2SHWITNESS), ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 0], InputScriptType.SPENDP2SHWITNESS), ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 1123], InputScriptType.SPENDP2SHWITNESS), ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 44444], InputScriptType.SPENDP2SHWITNESS), ([49 | HARDENED, 0 | HARDENED, 5 | HARDENED, 0, 0], InputScriptType.SPENDP2SHWITNESS), ([84 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDWITNESS), ([84 | HARDENED, 0 | HARDENED, 5 | HARDENED, 0, 0], InputScriptType.SPENDWITNESS), ([84 | HARDENED, 0 | HARDENED, 5 | HARDENED, 0, 10], InputScriptType.SPENDWITNESS), ([48 | HARDENED, 0 | HARDENED, 5 | HARDENED, 0, 10], InputScriptType.SPENDMULTISIG), ] coin = coins.by_name('Bitcoin') for path, input_type in incorrect_derivation_paths: self.assertFalse(validate_full_path(path, coin, input_type)) for path, input_type in correct_derivation_paths: self.assertTrue(validate_full_path(path, coin, input_type)) self.assertTrue(validate_full_path([44 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0], coin, InputScriptType.SPENDADDRESS)) self.assertFalse(validate_full_path([44 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0], coin, InputScriptType.SPENDWITNESS)) self.assertTrue(validate_full_path([44 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0], coin, InputScriptType.SPENDWITNESS, validate_script_type=False))
async def process_input(self, txi: TxInputType) -> None: self.wallet_path.add_input(txi) self.multisig_fingerprint.add_input(txi) writers.write_tx_input_check(self.h_confirmed, txi) self.hash143_add_input(txi) # all inputs are included (non-segwit as well) if not addresses.validate_full_path(txi.address_n, self.coin, txi.script_type): await helpers.confirm_foreign_address(txi.address_n) if input_is_segwit(txi): await self.process_segwit_input(txi) elif input_is_nonsegwit(txi): await self.process_nonsegwit_input(txi) else: raise SigningError(FailureType.DataError, "Wrong input script type")
async def check_tx_fee(tx: SignTx, keychain: seed.Keychain): coin = coins.by_name(tx.coin_name) # h_first is used to make sure the inputs and outputs streamed in Phase 1 # are the same as in Phase 2. it is thus not required to fully hash the # tx, as the SignTx info is streamed only once h_first = utils.HashWriter(sha256()) # not a real tx hash if coin.decred: hash143 = decred.DecredPrefixHasher( tx) # pseudo BIP-0143 prefix hashing tx_ser = TxRequestSerializedType() elif tx.overwintered: if tx.version == 3: hash143 = zcash.Zip143() # ZIP-0143 transaction hashing elif tx.version == 4: hash143 = zcash.Zip243() # ZIP-0243 transaction hashing else: raise SigningError( FailureType.DataError, "Unsupported version for overwintered transaction", ) else: hash143 = segwit_bip143.Bip143() # BIP-0143 transaction hashing multifp = multisig.MultisigFingerprint( ) # control checksum of multisig inputs weight = tx_weight.TxWeightCalculator(tx.inputs_count, tx.outputs_count) total_in = 0 # sum of input amounts segwit_in = 0 # sum of segwit input amounts total_out = 0 # sum of output amounts change_out = 0 # change output amount wallet_path = [] # common prefix of input paths segwit = {} # dict of booleans stating if input is segwit # output structures txo_bin = TxOutputBinType() tx_req = TxRequest() tx_req.details = TxRequestDetailsType() for i in range(tx.inputs_count): progress.advance() # STAGE_REQUEST_1_INPUT txi = await helpers.request_tx_input(tx_req, i) wallet_path = input_extract_wallet_path(txi, wallet_path) writers.write_tx_input_check(h_first, txi) weight.add_input(txi) hash143.add_prevouts( txi) # all inputs are included (non-segwit as well) hash143.add_sequence(txi) if not addresses.validate_full_path(txi.address_n, coin, txi.script_type): await helpers.confirm_foreign_address(txi.address_n) if txi.multisig: multifp.add(txi.multisig) if txi.script_type in ( InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS, ): if not coin.segwit: raise SigningError(FailureType.DataError, "Segwit not enabled on this coin") if not txi.amount: raise SigningError(FailureType.DataError, "Segwit input without amount") segwit[i] = True segwit_in += txi.amount total_in += txi.amount elif txi.script_type in ( InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG, ): if coin.force_bip143 or tx.overwintered: if not txi.amount: raise SigningError(FailureType.DataError, "Expected input with amount") segwit[i] = False segwit_in += txi.amount total_in += txi.amount else: segwit[i] = False total_in += await get_prevtx_output_value( coin, tx_req, txi.prev_hash, txi.prev_index) else: raise SigningError(FailureType.DataError, "Wrong input script type") if coin.decred: w_txi = writers.empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash)) if i == 0: # serializing first input => prepend headers writers.write_bytes(w_txi, get_tx_header(coin, tx)) writers.write_tx_input_decred(w_txi, txi) tx_ser.serialized_tx = w_txi tx_req.serialized = tx_ser if coin.decred: hash143.add_output_count(tx) for o in range(tx.outputs_count): # STAGE_REQUEST_3_OUTPUT txo = await helpers.request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, keychain) weight.add_output(txo_bin.script_pubkey) if change_out == 0 and output_is_change(txo, wallet_path, segwit_in, multifp): # output is change and does not need confirmation change_out = txo.amount elif not await helpers.confirm_output(txo, coin): raise SigningError(FailureType.ActionCancelled, "Output cancelled") if coin.decred: if txo.decred_script_version is not None and txo.decred_script_version != 0: raise SigningError( FailureType.ActionCancelled, "Cannot send to output with script version != 0", ) txo_bin.decred_script_version = txo.decred_script_version w_txo_bin = writers.empty_bytearray(4 + 8 + 2 + 4 + len(txo_bin.script_pubkey)) if o == 0: # serializing first output => prepend outputs count writers.write_varint(w_txo_bin, tx.outputs_count) writers.write_tx_output(w_txo_bin, txo_bin) tx_ser.serialized_tx = w_txo_bin tx_req.serialized = tx_ser hash143.set_last_output_bytes(w_txo_bin) writers.write_tx_output(h_first, txo_bin) hash143.add_output(txo_bin) total_out += txo_bin.amount fee = total_in - total_out if fee < 0: raise SigningError(FailureType.NotEnoughFunds, "Not enough funds") # fee > (coin.maxfee per byte * tx size) if fee > (coin.maxfee_kb / 1000) * (weight.get_total() / 4): if not await helpers.confirm_feeoverthreshold(fee, coin): raise SigningError(FailureType.ActionCancelled, "Signing cancelled") if not await helpers.confirm_total(total_in - change_out, fee, coin): raise SigningError(FailureType.ActionCancelled, "Total cancelled") if coin.decred: hash143.add_locktime_expiry(tx) return h_first, hash143, segwit, total_in, wallet_path