def write_witness_multisig( w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, sighash_type: SigHashType, ) -> None: # get other signatures, stretch with empty bytes to the number of the pubkeys signatures = multisig.signatures + [b""] * ( multisig_get_pubkey_count(multisig) - len(multisig.signatures)) # fill in our signature if signatures[signature_index]: raise wire.DataError("Invalid multisig parameters") signatures[signature_index] = signature # witness program + signatures + redeem script num_of_witness_items = 1 + sum(1 for s in signatures if s) + 1 write_compact_size(w, num_of_witness_items) # Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which # consumes one additional item on the stack: # https://bitcoin.org/en/developer-guide#standard-transactions write_compact_size(w, 0) for s in signatures: if s: write_signature_prefixed( w, s, sighash_type) # size of the witness included # redeem script pubkeys = multisig_get_pubkeys(multisig) write_output_script_multisig(w, pubkeys, multisig.m, prefixed=True)
def write_signature_prefixed(w: Writer, signature: bytes, sighash_type: SigHashType) -> None: length = len(signature) if sighash_type != SigHashType.SIGHASH_ALL_TAPROOT: length += 1 write_compact_size(w, length) write_bytes_unchecked(w, signature) if sighash_type != SigHashType.SIGHASH_ALL_TAPROOT: w.append(sighash_type)
def write_tx_header( self, w: writers.Writer, tx: SignTx | PrevTx, witness_marker: bool, ) -> None: writers.write_uint32(w, tx.version) # nVersion if witness_marker: write_compact_size(w, 0x00) # segwit witness marker write_compact_size(w, 0x01) # segwit witness flag
async def step2_approve_outputs(self) -> None: write_compact_size(self.serialized_tx, self.tx_info.tx.outputs_count) write_compact_size(self.h_prefix, self.tx_info.tx.outputs_count) if self.tx_info.tx.decred_staking_ticket: await self.approve_staking_ticket() else: await super().step2_approve_outputs() self.write_tx_footer(self.serialized_tx, self.tx_info.tx) self.write_tx_footer(self.h_prefix, self.tx_info.tx)
def write_output_script_p2pkh(w: Writer, pubkeyhash: bytes, prefixed: bool = False) -> None: if prefixed: write_compact_size(w, 25) w.append(0x76) # OP_DUP w.append(0xA9) # OP_HASH160 w.append(0x14) # OP_DATA_20 write_bytes_fixed(w, pubkeyhash, 20) w.append(0x88) # OP_EQUALVERIFY w.append(0xAC) # OP_CHECKSIG
async def step4_serialize_inputs(self) -> None: self.write_tx_header(self.serialized_tx, self.tx_info.tx, bool(self.segwit)) write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count) for i in range(self.tx_info.tx.inputs_count): progress.advance() if i in self.external: await self.serialize_external_input(i) elif i in self.segwit: await self.serialize_segwit_input(i) else: await self.sign_nonsegwit_input(i)
def write_input_script_p2wsh_in_p2sh(w: Writer, script_hash: bytes, prefixed: bool = False) -> None: # 22 00 20 <redeem script hash> # Signature is moved to the witness. if prefixed: write_compact_size(w, 35) w.append(0x22) # length of the data w.append(0x00) # witness version byte w.append(0x20) # P2WSH witness program (redeem script hash length) write_bytes_fixed(w, script_hash, 32)
def write_input_script_p2wpkh_in_p2sh(w: Writer, pubkeyhash: bytes, prefixed: bool = False) -> None: # 16 00 14 <pubkeyhash> # Signature is moved to the witness. if prefixed: write_compact_size(w, 23) w.append(0x16) # length of the data w.append(0x00) # witness version byte w.append(0x14) # P2WPKH witness program (pub key hash length) write_bytes_fixed(w, pubkeyhash, 20) # pub key hash
def write_tx_header( self, w: writers.Writer, tx: SignTx | PrevTx, witness_marker: bool, ) -> None: writers.write_uint32(w, tx.version) # nVersion if self.coin.timestamp: assert tx.timestamp is not None # checked in sanitize_* writers.write_uint32(w, tx.timestamp) if witness_marker: write_compact_size(w, 0x00) # segwit witness marker write_compact_size(w, 0x01) # segwit witness flag
async def get_prevtx_output( self, prev_hash: bytes, prev_index: int ) -> tuple[int, bytes]: amount_out = 0 # output amount # STAGE_REQUEST_3_PREV_META in legacy tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash) if tx.outputs_count <= prev_index: raise wire.ProcessError("Not enough outputs in previous transaction.") txh = self.create_hash_writer() # witnesses are not included in txid hash self.write_tx_header(txh, tx, witness_marker=False) write_compact_size(txh, tx.inputs_count) for i in range(tx.inputs_count): # STAGE_REQUEST_3_PREV_INPUT in legacy txi = await helpers.request_tx_prev_input( self.tx_req, i, self.coin, prev_hash ) self.write_tx_input(txh, txi, txi.script_sig) write_compact_size(txh, tx.outputs_count) script_pubkey: bytes | None = None for i in range(tx.outputs_count): # STAGE_REQUEST_3_PREV_OUTPUT in legacy txo_bin = await helpers.request_tx_prev_output( self.tx_req, i, self.coin, prev_hash ) self.write_tx_output(txh, txo_bin, txo_bin.script_pubkey) if i == prev_index: amount_out = txo_bin.amount script_pubkey = txo_bin.script_pubkey self.check_prevtx_output(txo_bin) assert script_pubkey is not None # prev_index < tx.outputs_count await self.write_prev_tx_footer(txh, tx, prev_hash) if ( writers.get_tx_hash(txh, double=self.coin.sign_hash_double, reverse=True) != prev_hash ): raise wire.ProcessError("Encountered invalid prev_hash") return amount_out, script_pubkey
def message_digest(coin: CoinInfo, message: bytes) -> bytes: if not utils.BITCOIN_ONLY and coin.decred: h = utils.HashWriter(blake256()) else: h = utils.HashWriter(sha256()) if not coin.signed_message_header: raise wire.DataError("Empty message header not allowed.") write_compact_size(h, len(coin.signed_message_header)) h.extend(coin.signed_message_header.encode()) write_compact_size(h, len(message)) h.extend(message) ret = h.get_digest() if coin.sign_hash_double: ret = sha256(ret).digest() return ret
def write_output_script_multisig( w: Writer, pubkeys: Sequence[bytes | memoryview], m: int, prefixed: bool = False, ) -> None: n = len(pubkeys) if n < 1 or n > 15 or m < 1 or m > 15 or m > n: raise wire.DataError("Invalid multisig parameters") for pubkey in pubkeys: if len(pubkey) != 33: raise wire.DataError("Invalid multisig parameters") if prefixed: write_compact_size(w, output_script_multisig_length(pubkeys, m)) w.append(0x50 + m) # numbers 1 to 16 are pushed as 0x50 + value for p in pubkeys: append_pubkey(w, p) w.append(0x50 + n) w.append(0xAE) # OP_CHECKMULTISIG
def __init__( self, tx: SignTx, keychain: Keychain, coin: CoinInfo, approver: approvers.Approver | None, ) -> None: ensure(coin.decred) self.h_prefix = HashWriter(blake256()) ensure(approver is None) approver = DecredApprover(tx, coin) super().__init__(tx, keychain, coin, approver) self.write_tx_header(self.serialized_tx, self.tx_info.tx, witness_marker=True) write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count) writers.write_uint32( self.h_prefix, self.tx_info.tx.version | DECRED_SERIALIZE_NO_WITNESS ) write_compact_size(self.h_prefix, self.tx_info.tx.inputs_count)
def write_input_script_multisig_prefixed( w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, sighash_type: SigHashType, coin: CoinInfo, ) -> None: signatures = multisig.signatures # other signatures if len(signatures[signature_index]) > 0: raise wire.DataError("Invalid multisig parameters") signatures[signature_index] = signature # our signature # length of the redeem script pubkeys = multisig_get_pubkeys(multisig) redeem_script_length = output_script_multisig_length(pubkeys, multisig.m) # length of the result total_length = 1 # OP_FALSE for s in signatures: if s: total_length += 1 + len(s) + 1 # length, signature, sighash_type total_length += op_push_length(redeem_script_length) + redeem_script_length write_compact_size(w, total_length) # Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which # consumes one additional item on the stack: # https://bitcoin.org/en/developer-guide#standard-transactions w.append(0x00) for s in signatures: if s: append_signature(w, s, sighash_type) # redeem script write_op_push(w, redeem_script_length) write_output_script_multisig(w, pubkeys, multisig.m)
async def step7_finish(self) -> None: self.write_tx_footer(self.serialized_tx, self.tx_info.tx) write_uint64(self.serialized_tx, 0) # valueBalance write_compact_size(self.serialized_tx, 0) # nShieldedSpend write_compact_size(self.serialized_tx, 0) # nShieldedOutput write_compact_size(self.serialized_tx, 0) # nJoinSplit await helpers.request_tx_finish(self.tx_req)
def write_witness_p2wpkh(w: Writer, signature: bytes, pubkey: bytes, sighash_type: SigHashType) -> None: write_compact_size(w, 0x02) # num of segwit items, in P2WPKH it's always 2 write_signature_prefixed(w, signature, sighash_type) write_bytes_prefixed(w, pubkey)
def write_witness_p2tr(w: Writer, signature: bytes, sighash_type: SigHashType) -> None: # Taproot key path spending without annex. write_compact_size(w, 0x01) # num of segwit items write_signature_prefixed(w, signature, sighash_type)
async def get_legacy_tx_digest( self, index: int, tx_info: TxInfo | OriginalTxInfo, script_pubkey: bytes | None = None, ) -> tuple[bytes, TxInput, bip32.HDNode | None]: tx_hash = tx_info.orig_hash if isinstance(tx_info, OriginalTxInfo) else None # the transaction digest which gets signed for this input h_sign = self.create_hash_writer() # should come out the same as h_tx_check, checked before signing the digest h_check = HashWriter(sha256()) self.write_tx_header(h_sign, tx_info.tx, witness_marker=False) write_compact_size(h_sign, tx_info.tx.inputs_count) txi_sign = None node = None for i in range(tx_info.tx.inputs_count): # STAGE_REQUEST_4_INPUT in legacy txi = await helpers.request_tx_input(self.tx_req, i, self.coin, tx_hash) writers.write_tx_input_check(h_check, txi) # Only the previous UTXO's scriptPubKey is included in h_sign. if i == index: txi_sign = txi if not script_pubkey: self.tx_info.check_input(txi) node = self.keychain.derive(txi.address_n) key_sign_pub = node.public_key() if txi.multisig: # Sanity check to ensure we are signing with a key that is included in the multisig. multisig.multisig_pubkey_index(txi.multisig, key_sign_pub) if txi.script_type == InputScriptType.SPENDMULTISIG: assert txi.multisig is not None # checked in sanitize_tx_input script_pubkey = scripts.output_script_multisig( multisig.multisig_get_pubkeys(txi.multisig), txi.multisig.m, ) elif txi.script_type == InputScriptType.SPENDADDRESS: script_pubkey = scripts.output_script_p2pkh( addresses.ecdsa_hash_pubkey(key_sign_pub, self.coin) ) else: raise wire.ProcessError("Unknown transaction type") self.write_tx_input(h_sign, txi, script_pubkey) else: self.write_tx_input(h_sign, txi, bytes()) if txi_sign is None: raise RuntimeError # index >= tx_info.tx.inputs_count write_compact_size(h_sign, tx_info.tx.outputs_count) for i in range(tx_info.tx.outputs_count): # STAGE_REQUEST_4_OUTPUT in legacy txo = await helpers.request_tx_output(self.tx_req, i, self.coin, tx_hash) script_pubkey = self.output_derive_script(txo) self.write_tx_output(h_check, txo, script_pubkey) self.write_tx_output(h_sign, txo, script_pubkey) writers.write_uint32(h_sign, tx_info.tx.lock_time) writers.write_uint32(h_sign, self.get_hash_type(txi_sign)) # check that the inputs were the same as those streamed for approval if tx_info.get_tx_check_digest() != h_check.get_digest(): raise wire.ProcessError("Transaction has changed during signing") tx_digest = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double) return tx_digest, txi_sign, node
async def step5_serialize_outputs(self) -> None: write_compact_size(self.serialized_tx, self.tx_info.tx.outputs_count) for i in range(self.tx_info.tx.outputs_count): progress.advance() await self.serialize_output(i)
def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None: # serialize Sapling bundle write_compact_size(w, 0) # nSpendsSapling write_compact_size(w, 0) # nOutputsSapling # serialize Orchard bundle write_compact_size(w, 0) # nActionsOrchard
def write_bytes_prefixed(w: Writer, b: bytes) -> None: write_compact_size(w, len(b)) write_bytes_unchecked(w, b)
async def step4_serialize_inputs(self) -> None: write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count) prefix_hash = self.h_prefix.get_digest() for i_sign in range(self.tx_info.tx.inputs_count): progress.advance() txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, self.coin) self.tx_info.check_input(txi_sign) key_sign = self.keychain.derive(txi_sign.address_n) key_sign_pub = key_sign.public_key() h_witness = self.create_hash_writer() writers.write_uint32( h_witness, self.tx_info.tx.version | DECRED_SERIALIZE_WITNESS_SIGNING ) write_compact_size(h_witness, self.tx_info.tx.inputs_count) for ii in range(self.tx_info.tx.inputs_count): if ii == i_sign: if txi_sign.decred_staking_spend == DecredStakingSpendType.SSRTX: scripts_decred.write_output_script_ssrtx_prefixed( h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin) ) elif txi_sign.decred_staking_spend == DecredStakingSpendType.SSGen: scripts_decred.write_output_script_ssgen_prefixed( h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin) ) elif txi_sign.script_type == InputScriptType.SPENDMULTISIG: assert txi_sign.multisig is not None scripts_decred.write_output_script_multisig( h_witness, multisig.multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m, prefixed=True, ) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: scripts_decred.write_output_script_p2pkh( h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin), prefixed=True, ) else: raise wire.DataError("Unsupported input script type") else: write_compact_size(h_witness, 0) witness_hash = writers.get_tx_hash( h_witness, double=self.coin.sign_hash_double, reverse=False ) h_sign = self.create_hash_writer() writers.write_uint32(h_sign, SigHashType.SIGHASH_ALL) 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=self.coin.sign_hash_double) signature = ecdsa_sign(key_sign, sig_hash) # serialize input with correct signature self.write_tx_input_witness( self.serialized_tx, txi_sign, key_sign_pub, signature ) self.set_serialized_signature(i_sign, signature)
def write_input_script_p2pkh_or_p2sh_prefixed( w: Writer, pubkey: bytes, signature: bytes, sighash_type: SigHashType) -> None: write_compact_size(w, 1 + len(signature) + 1 + 1 + len(pubkey)) append_signature(w, signature, sighash_type) append_pubkey(w, pubkey)