async def write_prev_tx_footer(self, w: writers.Writer, tx: TransactionType, prev_hash: bytes) -> None: await super().write_prev_tx_footer(w, tx, prev_hash) if self.coin.extra_data: ofs = 0 while ofs < tx.extra_data_len: size = min(1024, tx.extra_data_len - ofs) data = await helpers.request_tx_extra_data( self.tx_req, ofs, size, prev_hash) writers.write_bytes_unchecked(w, data) ofs += len(data)
def append_pubkey(w: Writer, pubkey: bytes) -> None: write_op_push(w, len(pubkey)) write_bytes_unchecked(w, pubkey)
def append_signature(w: Writer, signature: bytes, sighash: int) -> None: write_op_push(w, len(signature) + 1) write_bytes_unchecked(w, signature) w.append(sighash)
async def get_prevtx_output_value( coin: coininfo.CoinInfo, tx_req: TxRequest, prev_hash: bytes, prev_index: int ) -> int: total_out = 0 # sum of output amounts # STAGE_REQUEST_2_PREV_META tx = await helpers.request_tx_meta(tx_req, coin, prev_hash) if tx.outputs_cnt <= prev_index: raise SigningError( FailureType.ProcessError, "Not enough outputs in previous transaction." ) if not utils.BITCOIN_ONLY and coin.decred: txh = utils.HashWriter(blake256()) else: txh = utils.HashWriter(sha256()) if not utils.BITCOIN_ONLY and coin.overwintered: writers.write_uint32( txh, tx.version | zcash.OVERWINTERED ) # nVersion | fOverwintered writers.write_uint32(txh, tx.version_group_id) # nVersionGroupId elif not utils.BITCOIN_ONLY and coin.decred: writers.write_uint32(txh, tx.version | decred.DECRED_SERIALIZE_NO_WITNESS) else: writers.write_uint32(txh, tx.version) # nVersion if not utils.BITCOIN_ONLY and coin.timestamp: writers.write_uint32(txh, tx.timestamp) writers.write_varint(txh, tx.inputs_cnt) for i in range(tx.inputs_cnt): # STAGE_REQUEST_2_PREV_INPUT txi = await helpers.request_tx_input(tx_req, i, coin, prev_hash) if not utils.BITCOIN_ONLY and coin.decred: writers.write_tx_input_decred(txh, txi) else: writers.write_tx_input(txh, txi) writers.write_varint(txh, tx.outputs_cnt) for o in range(tx.outputs_cnt): # STAGE_REQUEST_2_PREV_OUTPUT txo_bin = await helpers.request_tx_output(tx_req, o, coin, prev_hash) writers.write_tx_output(txh, txo_bin) if o == prev_index: total_out += txo_bin.amount if ( not utils.BITCOIN_ONLY and coin.decred and txo_bin.decred_script_version is not None and txo_bin.decred_script_version != 0 ): raise SigningError( FailureType.ProcessError, "Cannot use utxo that has script_version != 0", ) writers.write_uint32(txh, tx.lock_time) if not utils.BITCOIN_ONLY and (coin.overwintered or coin.decred): writers.write_uint32(txh, tx.expiry) if not utils.BITCOIN_ONLY and coin.extra_data: ofs = 0 while ofs < tx.extra_data_len: size = min(1024, tx.extra_data_len - ofs) data = await helpers.request_tx_extra_data(tx_req, ofs, size, prev_hash) writers.write_bytes_unchecked(txh, data) ofs += len(data) if ( writers.get_tx_hash(txh, double=coin.sign_hash_double, reverse=True) != prev_hash ): raise SigningError(FailureType.ProcessError, "Encountered invalid prev_hash") return total_out
async def sign_tx(tx: SignTx, keychain: seed.Keychain): coin_name = tx.coin_name if tx.coin_name is not None else "Bitcoin" coin = coins.by_name(coin_name) tx = helpers.sanitize_sign_tx(tx, coin) progress.init(tx.inputs_count, tx.outputs_count) # Phase 1 ( h_first, hash143, segwit, authorized_bip143_in, wallet_path, multisig_fp, ) = await check_tx_fee(tx, keychain, coin) # Phase 2 # - sign inputs # - check that nothing changed any_segwit = True in segwit.values() tx_ser = TxRequestSerializedType() txo_bin = TxOutputBinType() tx_req = TxRequest() tx_req.details = TxRequestDetailsType() tx_req.serialized = None if not utils.BITCOIN_ONLY and coin.decred: prefix_hash = hash143.prefix_hash() for i_sign in range(tx.inputs_count): progress.advance() txi_sign = None key_sign = None key_sign_pub = None if segwit[i_sign]: # STAGE_REQUEST_SEGWIT_INPUT txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin) if not input_is_segwit(txi_sign): raise SigningError( FailureType.ProcessError, "Transaction has changed during signing" ) input_check_wallet_path(txi_sign, wallet_path) # NOTE: No need to check the multisig fingerprint, because we won't be signing # the script here. Signatures are produced in STAGE_REQUEST_SEGWIT_WITNESS. key_sign = keychain.derive(txi_sign.address_n, coin.curve_name) key_sign_pub = key_sign.public_key() txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub) w_txi = writers.empty_bytearray( 7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4 ) if i_sign == 0: # serializing first input => prepend headers write_tx_header(w_txi, coin, tx, True) writers.write_tx_input(w_txi, txi_sign) tx_ser.serialized_tx = w_txi tx_ser.signature_index = None tx_ser.signature = None tx_req.serialized = tx_ser elif not utils.BITCOIN_ONLY and (coin.force_bip143 or coin.overwintered): # STAGE_REQUEST_SEGWIT_INPUT txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin) input_check_wallet_path(txi_sign, wallet_path) input_check_multisig_fingerprint(txi_sign, multisig_fp) is_bip143 = ( txi_sign.script_type == InputScriptType.SPENDADDRESS or txi_sign.script_type == InputScriptType.SPENDMULTISIG ) if not is_bip143 or txi_sign.amount > authorized_bip143_in: raise SigningError( FailureType.ProcessError, "Transaction has changed during signing" ) authorized_bip143_in -= txi_sign.amount key_sign = keychain.derive(txi_sign.address_n, coin.curve_name) key_sign_pub = key_sign.public_key() hash143_hash = hash143.preimage_hash( coin, tx, txi_sign, addresses.ecdsa_hash_pubkey(key_sign_pub, coin), get_hash_type(coin), ) # if multisig, check if signing with a key that is included in multisig if txi_sign.multisig: multisig.multisig_pubkey_index(txi_sign.multisig, key_sign_pub) signature = ecdsa_sign(key_sign, hash143_hash) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature gc.collect() txi_sign.script_sig = input_derive_script( coin, txi_sign, key_sign_pub, signature ) w_txi_sign = writers.empty_bytearray( 5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4 ) if i_sign == 0: # serializing first input => prepend headers write_tx_header(w_txi_sign, coin, tx, any_segwit) writers.write_tx_input(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser elif not utils.BITCOIN_ONLY and coin.decred: txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin) input_check_wallet_path(txi_sign, wallet_path) input_check_multisig_fingerprint(txi_sign, multisig_fp) key_sign = keychain.derive(txi_sign.address_n, coin.curve_name) key_sign_pub = key_sign.public_key() if txi_sign.script_type == InputScriptType.SPENDMULTISIG: prev_pkscript = scripts.output_script_multisig( multisig.multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m, ) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: prev_pkscript = scripts.output_script_p2pkh( addresses.ecdsa_hash_pubkey(key_sign_pub, coin) ) else: raise SigningError("Unsupported input script type") h_witness = utils.HashWriter(blake256()) writers.write_uint32( h_witness, tx.version | decred.DECRED_SERIALIZE_WITNESS_SIGNING ) writers.write_varint(h_witness, tx.inputs_count) for ii in range(tx.inputs_count): if ii == i_sign: writers.write_bytes_prefixed(h_witness, prev_pkscript) else: writers.write_varint(h_witness, 0) witness_hash = writers.get_tx_hash( h_witness, double=coin.sign_hash_double, reverse=False ) h_sign = utils.HashWriter(blake256()) writers.write_uint32(h_sign, decred.DECRED_SIGHASHALL) writers.write_bytes_fixed(h_sign, prefix_hash, writers.TX_HASH_SIZE) writers.write_bytes_fixed(h_sign, witness_hash, writers.TX_HASH_SIZE) sig_hash = writers.get_tx_hash(h_sign, double=coin.sign_hash_double) signature = ecdsa_sign(key_sign, sig_hash) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature gc.collect() txi_sign.script_sig = input_derive_script( coin, txi_sign, key_sign_pub, signature ) w_txi_sign = writers.empty_bytearray( 8 + 4 + len(hash143.get_last_output_bytes()) if i_sign == 0 else 0 + 16 + 4 + len(txi_sign.script_sig) ) if i_sign == 0: writers.write_bytes_unchecked( w_txi_sign, hash143.get_last_output_bytes() ) writers.write_uint32(w_txi_sign, tx.lock_time) writers.write_uint32(w_txi_sign, tx.expiry) writers.write_varint(w_txi_sign, tx.inputs_count) writers.write_tx_input_decred_witness(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser else: # hash of what we are signing with this input h_sign = utils.HashWriter(sha256()) # same as h_first, checked before signing the digest h_second = utils.HashWriter(sha256()) writers.write_uint32(h_sign, tx.version) # nVersion if not utils.BITCOIN_ONLY and coin.timestamp: writers.write_uint32(h_sign, tx.timestamp) writers.write_varint(h_sign, tx.inputs_count) for i in range(tx.inputs_count): # STAGE_REQUEST_4_INPUT txi = await helpers.request_tx_input(tx_req, i, coin) input_check_wallet_path(txi, wallet_path) writers.write_tx_input_check(h_second, txi) if i == i_sign: txi_sign = txi input_check_multisig_fingerprint(txi_sign, multisig_fp) key_sign = keychain.derive(txi.address_n, coin.curve_name) key_sign_pub = key_sign.public_key() # for the signing process the script_sig is equal # to the previous tx's scriptPubKey (P2PKH) or a redeem script (P2SH) if txi_sign.script_type == InputScriptType.SPENDMULTISIG: txi_sign.script_sig = scripts.output_script_multisig( multisig.multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m, ) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: txi_sign.script_sig = scripts.output_script_p2pkh( addresses.ecdsa_hash_pubkey(key_sign_pub, coin) ) else: raise SigningError( FailureType.ProcessError, "Unknown transaction type" ) else: txi.script_sig = bytes() writers.write_tx_input(h_sign, txi) writers.write_varint(h_sign, tx.outputs_count) for o in range(tx.outputs_count): # STAGE_REQUEST_4_OUTPUT txo = await helpers.request_tx_output(tx_req, o, coin) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, keychain) writers.write_tx_output(h_second, txo_bin) writers.write_tx_output(h_sign, txo_bin) writers.write_uint32(h_sign, tx.lock_time) writers.write_uint32(h_sign, get_hash_type(coin)) # check the control digests if writers.get_tx_hash(h_first, False) != writers.get_tx_hash(h_second): raise SigningError( FailureType.ProcessError, "Transaction has changed during signing" ) # if multisig, check if signing with a key that is included in multisig if txi_sign.multisig: multisig.multisig_pubkey_index(txi_sign.multisig, key_sign_pub) # compute the signature from the tx digest signature = ecdsa_sign( key_sign, writers.get_tx_hash(h_sign, double=coin.sign_hash_double) ) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature gc.collect() txi_sign.script_sig = input_derive_script( coin, txi_sign, key_sign_pub, signature ) w_txi_sign = writers.empty_bytearray( 5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4 ) if i_sign == 0: # serializing first input => prepend headers write_tx_header(w_txi_sign, coin, tx, any_segwit) writers.write_tx_input(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser if not utils.BITCOIN_ONLY and coin.decred: return await helpers.request_tx_finish(tx_req) for o in range(tx.outputs_count): progress.advance() # STAGE_REQUEST_5_OUTPUT txo = await helpers.request_tx_output(tx_req, o, coin) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, keychain) # serialize output w_txo_bin = writers.empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4) 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.signature_index = None tx_ser.signature = None tx_ser.serialized_tx = w_txo_bin tx_req.serialized = tx_ser for i in range(tx.inputs_count): progress.advance() if segwit[i]: # STAGE_REQUEST_SEGWIT_WITNESS txi = await helpers.request_tx_input(tx_req, i, coin) input_check_wallet_path(txi, wallet_path) input_check_multisig_fingerprint(txi, multisig_fp) if not input_is_segwit(txi) or txi.amount > authorized_bip143_in: raise SigningError( FailureType.ProcessError, "Transaction has changed during signing" ) authorized_bip143_in -= txi.amount key_sign = keychain.derive(txi.address_n, coin.curve_name) key_sign_pub = key_sign.public_key() hash143_hash = hash143.preimage_hash( coin, tx, txi, addresses.ecdsa_hash_pubkey(key_sign_pub, coin), get_hash_type(coin), ) signature = ecdsa_sign(key_sign, hash143_hash) if txi.multisig: # find out place of our signature based on the pubkey signature_index = multisig.multisig_pubkey_index( txi.multisig, key_sign_pub ) witness = scripts.witness_p2wsh( txi.multisig, signature, signature_index, get_hash_type(coin) ) else: witness = scripts.witness_p2wpkh( signature, key_sign_pub, get_hash_type(coin) ) tx_ser.serialized_tx = witness tx_ser.signature_index = i tx_ser.signature = signature elif any_segwit: tx_ser.serialized_tx += bytearray(1) # empty witness for non-segwit inputs tx_ser.signature_index = None tx_ser.signature = None tx_req.serialized = tx_ser writers.write_uint32(tx_ser.serialized_tx, tx.lock_time) if not utils.BITCOIN_ONLY and coin.overwintered: if tx.version == 3: writers.write_uint32(tx_ser.serialized_tx, tx.expiry) # expiryHeight writers.write_varint(tx_ser.serialized_tx, 0) # nJoinSplit elif tx.version == 4: writers.write_uint32(tx_ser.serialized_tx, tx.expiry) # expiryHeight writers.write_uint64(tx_ser.serialized_tx, 0) # valueBalance writers.write_varint(tx_ser.serialized_tx, 0) # nShieldedSpend writers.write_varint(tx_ser.serialized_tx, 0) # nShieldedOutput writers.write_varint(tx_ser.serialized_tx, 0) # nJoinSplit else: raise SigningError( FailureType.DataError, "Unsupported version for overwintered transaction", ) await helpers.request_tx_finish(tx_req)
def append_pubkey(w: bytearray, pubkey: bytes) -> bytearray: write_op_push(w, len(pubkey)) write_bytes_unchecked(w, pubkey) return w
def append_signature(w: bytearray, signature: bytes, sighash: int) -> bytearray: write_op_push(w, len(signature) + 1) write_bytes_unchecked(w, signature) w.append(sighash) return w