def __init__(self, tx: SignTx, keychain: seed.Keychain, coin: coininfo.CoinInfo) -> None: ensure(coin.decred) super().__init__(tx, keychain, coin) self.write_tx_header(self.serialized_tx, self.tx, witness_marker=True) write_bitcoin_varint(self.serialized_tx, self.tx.inputs_count)
def witness_p2wpkh(signature: bytes, pubkey: bytes, sighash: int) -> bytearray: w = empty_bytearray(1 + 5 + len(signature) + 1 + 5 + len(pubkey)) write_bitcoin_varint(w, 0x02) # num of segwit items, in P2WPKH it's always 2 append_signature(w, signature, sighash) append_pubkey(w, pubkey) return w
def write_witness_multisig( w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, hash_type: int, ) -> 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_bitcoin_varint(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_bitcoin_varint(w, 0) for s in signatures: if s: write_signature_prefixed(w, s, hash_type) # size of the witness included # redeem script pubkeys = multisig_get_pubkeys(multisig) write_output_script_multisig(w, pubkeys, multisig.m, prefixed=True)
async def get_legacy_tx_digest( self, index: int, script_pubkey: Optional[bytes] = None ) -> Tuple[bytes, TxInputType, Optional[HDNode]]: # the transaction digest which gets signed for this input h_sign = self.create_hash_writer() # should come out the same as h_confirmed, checked before signing the digest h_check = self.create_hash_writer() self.write_tx_header(h_sign, self.tx, witness_marker=False) write_bitcoin_varint(h_sign, self.tx.inputs_count) for i in range(self.tx.inputs_count): # STAGE_REQUEST_4_INPUT in legacy txi = await helpers.request_tx_input(self.tx_req, i, self.coin) 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 node = None if not script_pubkey: self.wallet_path.check_input(txi) self.multisig_fingerprint.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: 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()) write_bitcoin_varint(h_sign, self.tx.outputs_count) for i in range(self.tx.outputs_count): # STAGE_REQUEST_4_OUTPUT in legacy txo = await helpers.request_tx_output(self.tx_req, i, self.coin) 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, self.tx.lock_time) writers.write_uint32(h_sign, self.get_sighash_type(txi_sign)) # check that the inputs were the same as those streamed for confirmation if self.h_confirmed.get_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
def generate_proof( node: bip32.HDNode, script_type: EnumTypeInputScriptType, multisig: Optional[MultisigRedeemScriptType], coin: CoinInfo, user_confirmed: bool, ownership_ids: List[bytes], script_pubkey: bytes, commitment_data: bytes, ) -> Tuple[bytes, bytes]: flags = 0 if user_confirmed: flags |= _FLAG_USER_CONFIRMED proof = empty_bytearray(4 + 1 + 1 + len(ownership_ids) * _OWNERSHIP_ID_LEN) write_bytes_fixed(proof, _VERSION_MAGIC, 4) write_uint8(proof, flags) write_bitcoin_varint(proof, len(ownership_ids)) for ownership_id in ownership_ids: write_bytes_fixed(proof, ownership_id, _OWNERSHIP_ID_LEN) sighash = hashlib.sha256(proof) sighash.update(script_pubkey) sighash.update(commitment_data) signature = common.ecdsa_sign(node, sighash.digest()) public_key = node.public_key() write_bip322_signature_proof(proof, script_type, multisig, coin, public_key, signature) return proof, signature
def message_digest(message): h = HashWriter(sha256()) signed_message_header = "Lisk Signed Message:\n" write_bitcoin_varint(h, len(signed_message_header)) h.extend(signed_message_header) write_bitcoin_varint(h, len(message)) h.extend(message) return sha256(h.get_digest()).digest()
def witness_p2wpkh(signature: bytes, pubkey: bytes, hash_type: int) -> bytearray: w = empty_bytearray(1 + 5 + len(signature) + 1 + 5 + len(pubkey)) write_bitcoin_varint(w, 0x02) # num of segwit items, in P2WPKH it's always 2 write_signature_prefixed(w, signature, hash_type) write_bytes_prefixed(w, pubkey) return w
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_bitcoin_varint(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: Union[SignTx, PrevTx], witness_marker: bool, ) -> None: writers.write_uint32(w, tx.version) # nVersion if witness_marker: write_bitcoin_varint(w, 0x00) # segwit witness marker write_bitcoin_varint(w, 0x01) # segwit witness flag
async def step4_serialize_inputs(self) -> None: self.write_tx_header(self.serialized_tx, self.tx, bool(self.segwit)) write_bitcoin_varint(self.serialized_tx, self.tx.inputs_count) for i in range(self.tx.inputs_count): progress.advance() if i in self.segwit: await self.serialize_segwit_input(i) else: await self.sign_nonsegwit_input(i)
def write_output_script_p2pkh(w: Writer, pubkeyhash: bytes, prefixed: bool = False) -> None: if prefixed: write_bitcoin_varint(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 step2_approve_outputs(self) -> None: write_bitcoin_varint(self.serialized_tx, self.tx_info.tx.outputs_count) write_bitcoin_varint(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_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_bitcoin_varint(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_bitcoin_varint(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 __init__( self, tx: SignTx, keychain: Keychain, coin: CoinInfo, approver: approvers.Approver, ) -> None: ensure(coin.decred) super().__init__(tx, keychain, coin, approver) self.write_tx_header(self.serialized_tx, self.tx, witness_marker=True) write_bitcoin_varint(self.serialized_tx, self.tx.inputs_count)
def write_tx_header( self, w: writers.Writer, tx: Union[SignTx, TransactionType], witness_marker: bool, ) -> None: writers.write_uint32(w, tx.version) # nVersion if self.coin.timestamp: writers.write_uint32(w, tx.timestamp) if witness_marker: write_bitcoin_varint(w, 0x00) # segwit witness marker write_bitcoin_varint(w, 0x01) # segwit witness flag
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_bitcoin_varint(w, 0x00) # segwit witness marker write_bitcoin_varint(w, 0x01) # segwit witness flag
def message_digest(coin: CoinType, message: bytes) -> bytes: if not utils.BITCOIN_ONLY and coin.decred: h = utils.HashWriter(blake256()) else: h = utils.HashWriter(sha256()) write_bitcoin_varint(h, len(coin.signed_message_header)) h.extend(coin.signed_message_header) write_bitcoin_varint(h, len(message)) h.extend(message) ret = h.get_digest() if coin.sign_hash_double: ret = sha256(ret).digest() return ret
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_bitcoin_varint(h, len(coin.signed_message_header)) h.extend(coin.signed_message_header.encode()) write_bitcoin_varint(h, len(message)) h.extend(message) ret = h.get_digest() if coin.sign_hash_double: ret = sha256(ret).digest() return ret
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_bitcoin_varint(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_bitcoin_varint(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 __init__( self, tx: SignTx, keychain: Keychain, coin: CoinInfo, approver: approvers.Approver, ) -> None: ensure(coin.decred) self.h_prefix = HashWriter(blake256()) super().__init__(tx, keychain, coin, approver) self.write_tx_header(self.serialized_tx, self.tx_info.tx, witness_marker=True) write_bitcoin_varint(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_bitcoin_varint(self.h_prefix, self.tx_info.tx.inputs_count)
def generate_proof( node: bip32.HDNode, script_type: InputScriptType, multisig: MultisigRedeemScriptType | None, coin: CoinInfo, user_confirmed: bool, ownership_ids: list[bytes], script_pubkey: bytes, commitment_data: bytes, ) -> tuple[bytes, bytes]: flags = 0 if user_confirmed: flags |= _FLAG_USER_CONFIRMED proof = utils.empty_bytearray(4 + 1 + 1 + len(ownership_ids) * _OWNERSHIP_ID_LEN) write_bytes_fixed(proof, _VERSION_MAGIC, 4) write_uint8(proof, flags) write_bitcoin_varint(proof, len(ownership_ids)) for ownership_id in ownership_ids: write_bytes_fixed(proof, ownership_id, _OWNERSHIP_ID_LEN) sighash = hashlib.sha256(proof) sighash.update(script_pubkey) sighash.update(commitment_data) if script_type in ( InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG, InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS, ): signature = common.ecdsa_sign(node, sighash.digest()) elif script_type == InputScriptType.SPENDTAPROOT: signature = common.bip340_sign(node, sighash.digest()) else: raise wire.DataError("Unsupported script type.") public_key = node.public_key() write_bip322_signature_proof(proof, script_type, multisig, coin, public_key, signature) return proof, signature
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_bitcoin_varint(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
async def step8_finish(self) -> None: self.write_tx_footer(self.serialized_tx, self.tx) if self.tx.version == 3: write_bitcoin_varint(self.serialized_tx, 0) # nJoinSplit elif self.tx.version == 4: write_uint64(self.serialized_tx, 0) # valueBalance write_bitcoin_varint(self.serialized_tx, 0) # nShieldedSpend write_bitcoin_varint(self.serialized_tx, 0) # nShieldedOutput write_bitcoin_varint(self.serialized_tx, 0) # nJoinSplit else: raise wire.DataError("Unsupported version for overwintered transaction") await helpers.request_tx_finish(self.tx_req)
async def step4_serialize_inputs(self) -> None: write_bitcoin_varint(self.serialized_tx, self.tx.inputs_count) prefix_hash = self.h_prefix.get_digest() for i_sign in range(self.tx.inputs_count): progress.advance() txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, self.coin) self.wallet_path.check_input(txi_sign) self.multisig_fingerprint.check_input(txi_sign) key_sign = self.keychain.derive(txi_sign.address_n) key_sign_pub = key_sign.public_key() if txi_sign.script_type == InputScriptType.SPENDMULTISIG: assert txi_sign.multisig is not None 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( ecdsa_hash_pubkey(key_sign_pub, self.coin)) else: raise wire.DataError("Unsupported input script type") h_witness = self.create_hash_writer() writers.write_uint32( h_witness, self.tx.version | DECRED_SERIALIZE_WITNESS_SIGNING) write_bitcoin_varint(h_witness, self.tx.inputs_count) for ii in range(self.tx.inputs_count): if ii == i_sign: writers.write_bytes_prefixed(h_witness, prev_pkscript) else: write_bitcoin_varint(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, DECRED_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 script_sig = self.input_derive_script(txi_sign, key_sign_pub, signature) self.write_tx_input_witness(self.serialized_tx, txi_sign, script_sig) self.set_serialized_signature(i_sign, signature)
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_bitcoin_varint(self.serialized_tx, 0) # nShieldedSpend write_bitcoin_varint(self.serialized_tx, 0) # nShieldedOutput write_bitcoin_varint(self.serialized_tx, 0) # nJoinSplit await helpers.request_tx_finish(self.tx_req)
def write_input_script_multisig_prefixed( w: Writer, multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, hash_type: int, 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, hash_type total_length += op_push_length(redeem_script_length) + redeem_script_length write_bitcoin_varint(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, hash_type) # redeem script write_op_push(w, redeem_script_length) write_output_script_multisig(w, pubkeys, multisig.m)
def witness_multisig( multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, hash_type: int, ) -> bytearray: # 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 # filter empty signatures = [s for s in signatures if s] # witness program + signatures + redeem script num_of_witness_items = 1 + len(signatures) + 1 # 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 + 1 # number of items, OP_FALSE for s in signatures: total_length += 1 + len(s) + 1 # length, signature, hash_type total_length += 1 + redeem_script_length # length, script w = empty_bytearray(total_length) write_bitcoin_varint(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_bitcoin_varint(w, 0) for s in signatures: write_signature_prefixed(w, s, hash_type) # size of the witness included # redeem script write_bitcoin_varint(w, redeem_script_length) write_output_script_multisig(w, pubkeys, multisig.m) return w
def write_bytes_prefixed(w: Writer, b: bytes) -> None: write_bitcoin_varint(w, len(b)) write_bytes_unchecked(w, b)
async def step5_serialize_outputs(self) -> None: write_bitcoin_varint(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)