def _check_sign_msg(self, msg): addr_path = "m/44h/1h/0h/0/0" sign_res = self.do_command(self.dev_args + ['signmessage', msg, addr_path]) self.assertNotIn("error", sign_res) self.assertNotIn("code", sign_res) self.assertIn("signature", sign_res) sig = sign_res["signature"] addr = self.do_command(self.dev_args + ['displayaddress', "--addr-type", "legacy", '--path', addr_path])["address"] addr = to_address(decode(addr)[1:-4], b"\x6F") self.assertTrue(self.rpc.verifymessage(addr, sig, msg))
def sign_tx(self, tx: PSBT) -> PSBT: """ Sign a transaction with the Trezor. There are some limitations to what transactions can be signed. - Multisig inputs are limited to at most n-of-15 multisigs. This is a firmware limitation. - Transactions with arbitrary input scripts (scriptPubKey, redeemScript, or witnessScript) and arbitrary output scripts cannot be signed. This is a firmware limitation. - Send-to-self transactions will result in no prompt for outputs as all outputs will be detected as change. """ self._check_unlocked() # Get this devices master key fingerprint master_key = btc.get_public_node(self.client, [0x80000000], coin_name="Bitcoin") master_fp = get_xpub_fingerprint(master_key.xpub) # Do multiple passes for multisig passes = 1 p = 0 while p < passes: # Prepare inputs inputs = [] to_ignore = ( [] ) # Note down which inputs whose signatures we're going to ignore for input_num, (psbt_in, txin) in py_enumerate( list(zip(tx.inputs, tx.tx.vin)) ): txinputtype = messages.TxInputType( prev_hash=ser_uint256(txin.prevout.hash)[::-1], prev_index=txin.prevout.n, sequence=txin.nSequence, ) # Detrermine spend type scriptcode = b"" utxo = None if psbt_in.witness_utxo: utxo = psbt_in.witness_utxo if psbt_in.non_witness_utxo: if txin.prevout.hash != psbt_in.non_witness_utxo.sha256: raise BadArgumentError( "Input {} has a non_witness_utxo with the wrong hash".format( input_num ) ) utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n] if utxo is None: continue scriptcode = utxo.scriptPubKey # Check if P2SH p2sh = False if is_p2sh(scriptcode): # Look up redeemscript if len(psbt_in.redeem_script) == 0: continue scriptcode = psbt_in.redeem_script p2sh = True # Check segwit is_wit, _, _ = is_witness(scriptcode) if is_wit: if p2sh: txinputtype.script_type = ( messages.InputScriptType.SPENDP2SHWITNESS ) else: txinputtype.script_type = messages.InputScriptType.SPENDWITNESS else: txinputtype.script_type = messages.InputScriptType.SPENDADDRESS txinputtype.amount = utxo.nValue # Check if P2WSH p2wsh = False if is_p2wsh(scriptcode): # Look up witnessscript if len(psbt_in.witness_script) == 0: continue scriptcode = psbt_in.witness_script p2wsh = True def ignore_input() -> None: txinputtype.address_n = [ 0x80000000 | 84, 0x80000000 | (0 if self.chain == Chain.MAIN else 1), 0x80000000, 0, 0, ] txinputtype.multisig = None txinputtype.script_type = messages.InputScriptType.SPENDWITNESS inputs.append(txinputtype) to_ignore.append(input_num) # Check for multisig is_ms, multisig = parse_multisig(scriptcode, tx.xpub, psbt_in) if is_ms: # Add to txinputtype txinputtype.multisig = multisig if not is_wit: if utxo.is_p2sh: txinputtype.script_type = ( messages.InputScriptType.SPENDMULTISIG ) else: # Cannot sign bare multisig, ignore it ignore_input() continue elif not is_ms and not is_wit and not is_p2pkh(scriptcode): # Cannot sign unknown spk, ignore it ignore_input() continue elif not is_ms and is_wit and p2wsh: # Cannot sign unknown witness script, ignore it ignore_input() continue # Find key to sign with found = False # Whether we have found a key to sign with found_in_sigs = ( False # Whether we have found one of our keys in the signatures ) our_keys = 0 for key in psbt_in.hd_keypaths.keys(): keypath = psbt_in.hd_keypaths[key] if keypath.fingerprint == master_fp: if ( key in psbt_in.partial_sigs ): # This key already has a signature found_in_sigs = True continue if ( not found ): # This key does not have a signature and we don't have a key to sign with yet txinputtype.address_n = keypath.path found = True our_keys += 1 # Determine if we need to do more passes to sign everything if our_keys > passes: passes = our_keys if ( not found and not found_in_sigs ): # None of our keys were in hd_keypaths or in partial_sigs # This input is not one of ours ignore_input() continue elif ( not found and found_in_sigs ): # All of our keys are in partial_sigs, ignore whatever signature is produced for this input ignore_input() continue # append to inputs inputs.append(txinputtype) # address version byte if self.chain != Chain.MAIN: p2pkh_version = b"\x6f" p2sh_version = b"\xc4" bech32_hrp = "tb" else: p2pkh_version = b"\x00" p2sh_version = b"\x05" bech32_hrp = "bc" # prepare outputs outputs = [] for i, out in py_enumerate(tx.tx.vout): txoutput = messages.TxOutputType(amount=out.nValue) txoutput.script_type = messages.OutputScriptType.PAYTOADDRESS if out.is_p2pkh(): txoutput.address = to_address(out.scriptPubKey[3:23], p2pkh_version) elif out.is_p2sh(): txoutput.address = to_address(out.scriptPubKey[2:22], p2sh_version) elif out.is_opreturn(): txoutput.script_type = messages.OutputScriptType.PAYTOOPRETURN txoutput.op_return_data = out.scriptPubKey[2:] else: wit, ver, prog = out.is_witness() if wit: txoutput.address = bech32.encode(bech32_hrp, ver, prog) else: raise BadArgumentError("Output is not an address") # Add the derivation path for change psbt_out = tx.outputs[i] for _, keypath in psbt_out.hd_keypaths.items(): if keypath.fingerprint != master_fp: continue wit, ver, prog = out.is_witness() if out.is_p2pkh(): txoutput.address_n = keypath.path txoutput.address = None elif wit: txoutput.script_type = messages.OutputScriptType.PAYTOWITNESS txoutput.address_n = keypath.path txoutput.address = None elif out.is_p2sh() and psbt_out.redeem_script: wit, ver, prog = CTxOut(0, psbt_out.redeem_script).is_witness() if wit and len(prog) in [20, 32]: txoutput.script_type = ( messages.OutputScriptType.PAYTOP2SHWITNESS ) txoutput.address_n = keypath.path txoutput.address = None # add multisig info is_ms, multisig = parse_multisig( psbt_out.witness_script or psbt_out.redeem_script, tx.xpub, psbt_out ) if is_ms: txoutput.multisig = multisig # append to outputs outputs.append(txoutput) # Prepare prev txs prevtxs = {} for psbt_in in tx.inputs: if psbt_in.non_witness_utxo: prev = psbt_in.non_witness_utxo t = messages.TransactionType() t.version = prev.nVersion t.lock_time = prev.nLockTime for vin in prev.vin: i = messages.TxInputType( prev_hash=ser_uint256(vin.prevout.hash)[::-1], prev_index=vin.prevout.n, script_sig=vin.scriptSig, sequence=vin.nSequence, ) t.inputs.append(i) for vout in prev.vout: o = messages.TxOutputBinType( amount=vout.nValue, script_pubkey=vout.scriptPubKey, ) t.bin_outputs.append(o) logging.debug(psbt_in.non_witness_utxo.hash) assert psbt_in.non_witness_utxo.sha256 is not None prevtxs[ser_uint256(psbt_in.non_witness_utxo.sha256)[::-1]] = t # Sign the transaction signed_tx = btc.sign_tx( client=self.client, coin_name=self.coin_name, inputs=inputs, outputs=outputs, prev_txes=prevtxs, version=tx.tx.nVersion, lock_time=tx.tx.nLockTime, ) # Each input has one signature for input_num, (psbt_in, sig) in py_enumerate( list(zip(tx.inputs, signed_tx[0])) ): if input_num in to_ignore: continue for pubkey in psbt_in.hd_keypaths.keys(): fp = psbt_in.hd_keypaths[pubkey].fingerprint if fp == master_fp and pubkey not in psbt_in.partial_sigs: psbt_in.partial_sigs[pubkey] = sig + b"\x01" break p += 1 return tx