def input_script_p2wsh_in_p2sh(script_hash: bytes) -> bytearray: # 22 00 20 <redeem script hash> # Signature is moved to the witness. if len(script_hash) != 32: raise wire.DataError("Redeem script hash should be 32 bytes long") w = utils.empty_bytearray(3 + len(script_hash)) 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) return w
def output_script_sstxchange(addr: str) -> bytearray: try: raw_address = base58.decode_check(addr, blake256d_32) except ValueError: raise wire.DataError("Invalid address") w = utils.empty_bytearray(26) w.append(0xBD) # OP_SSTXCHANGE w.append(0x76) # OP_DUP w.append(0xA9) # OP_HASH160 w.append(0x14) # OP_DATA_20 write_bytes_fixed(w, raw_address[2:], 20) w.append(0x88) # OP_EQUALVERIFY w.append(0xAC) # OP_CHECKSIG return w
def output_script_native_segwit(witver: int, witprog: bytes) -> bytearray: # Either: # 00 14 <20-byte-key-hash> # 00 20 <32-byte-script-hash> # 51 20 <32-byte-taproot-output-key> length = len(witprog) utils.ensure((length == 20 and witver == 0) or length == 32) w = utils.empty_bytearray(2 + length) w.append(witver + 0x50 if witver else 0) # witness version byte (OP_witver) w.append( length ) # witness program length is 20 (P2WPKH) or 32 (P2WSH, P2TR) bytes write_bytes_fixed(w, witprog, length) return w
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 = utils.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 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 = HashWriter(sha256(proof)) write_bytes_prefixed(sighash, script_pubkey) write_bytes_prefixed(sighash, commitment_data) if script_type in ( InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG, InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS, ): signature = common.ecdsa_sign(node, sighash.get_digest()) elif script_type == InputScriptType.SPENDTAPROOT: signature = common.bip340_sign(node, sighash.get_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 __init__( self, tx: SignTx, keychain: Keychain, coin: CoinInfo, approver: approvers.Approver | None, ) -> None: self.tx_info = TxInfo(self, helpers.sanitize_sign_tx(tx, coin)) self.keychain = keychain self.coin = coin if approver is not None: self.approver = approver else: self.approver = approvers.BasicApprover(tx, coin) # set of indices of inputs which are segwit self.segwit: set[int] = set() # set of indices of inputs which are external self.external: set[int] = set() # transaction and signature serialization self.serialized_tx = empty_bytearray(_MAX_SERIALIZED_CHUNK_SIZE) self.tx_req = TxRequest() self.tx_req.details = TxRequestDetailsType() self.tx_req.serialized = TxRequestSerializedType() self.tx_req.serialized.serialized_tx = self.serialized_tx # List of original transactions which are being replaced by the current transaction. # Note: A List is better than a Dict of TXID -> OriginalTxInfo. Dict ordering is # undefined so we would need to convert to a sorted list in several places to ensure # stable device tests. self.orig_txs: list[OriginalTxInfo] = [] # h_inputs is a digest of the inputs streamed for approval in Step 1, which # is used to ensure that the inputs streamed for verification in Step 3 are # the same as those in Step 1. self.h_inputs: bytes | None = None progress.init(tx.inputs_count, tx.outputs_count)
def input_script_multisig( multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, hash_type: int, coin: CoinInfo, ) -> bytearray: 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: total_length += 1 + len(s) + 1 # length, signature, hash_type total_length += 1 + redeem_script_length # length, script w = utils.empty_bytearray(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 len(s): append_signature(w, s, hash_type) # redeem script write_op_push(w, redeem_script_length) write_output_script_multisig(w, pubkeys, multisig.m) return w
def __init__( self, script_pubkey: bytes, script_sig: bytes | None, witness: bytes | None, coin: CoinInfo, ): self.threshold = 1 self.public_keys: list[memoryview] = [] self.signatures: list[tuple[memoryview, SigHashType]] = [] self.is_taproot = False if not script_sig: if not witness: raise wire.DataError("Signature data not provided") if len(script_pubkey) == 22: # P2WPKH public_key, signature, hash_type = parse_witness_p2wpkh(witness) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if output_script_native_segwit(0, pubkey_hash) != script_pubkey: raise wire.DataError("Invalid public key hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_pubkey) == 34 and script_pubkey[0] == OP_0: # P2WSH script, self.signatures = parse_witness_multisig(witness) script_hash = sha256(script).digest() if output_script_native_segwit(0, script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig(script) elif len(script_pubkey) == 34 and script_pubkey[0] == OP_1: # P2TR self.is_taproot = True self.public_keys = [parse_output_script_p2tr(script_pubkey)] self.signatures = [parse_witness_p2tr(witness)] else: raise wire.DataError("Unsupported signature script") elif witness and witness != b"\x00": if len(script_sig) == 23: # P2WPKH nested in BIP16 P2SH public_key, signature, hash_type = parse_witness_p2wpkh(witness) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) w = utils.empty_bytearray(23) write_input_script_p2wpkh_in_p2sh(w, pubkey_hash) if w != script_sig: raise wire.DataError("Invalid public key hash") script_hash = coin.script_hash(script_sig[1:]).digest() if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_sig) == 35: # P2WSH nested in BIP16 P2SH script, self.signatures = parse_witness_multisig(witness) script_hash = sha256(script).digest() w = utils.empty_bytearray(35) write_input_script_p2wsh_in_p2sh(w, script_hash) if w != script_sig: raise wire.DataError("Invalid script hash") script_hash = coin.script_hash(script_sig[1:]).digest() if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig(script) else: raise wire.DataError("Unsupported signature script") else: if len(script_pubkey) == 25: # P2PKH public_key, signature, hash_type = parse_input_script_p2pkh(script_sig) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if output_script_p2pkh(pubkey_hash) != script_pubkey: raise wire.DataError("Invalid public key hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_pubkey) == 23: # P2SH script, self.signatures = parse_input_script_multisig(script_sig) script_hash = coin.script_hash(script).digest() if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig(script) else: raise wire.DataError("Unsupported signature script") if self.threshold != len(self.signatures): raise wire.DataError("Invalid signature")
def sstxcommitment_pkh(pkh: bytes, amount: int) -> bytes: w = utils.empty_bytearray(30) write_bytes_fixed(w, pkh, 20) write_uint64_le(w, amount) write_bytes_fixed(w, b"\x00\x58", 2) # standard fee limits return w
def output_script_paytoopreturn(data: bytes) -> bytearray: w = utils.empty_bytearray(1 + 5 + len(data)) w.append(0x6A) # OP_RETURN write_op_push(w, len(data)) w.extend(data) return w
def output_script_multisig(pubkeys: list[bytes], m: int) -> bytearray: w = utils.empty_bytearray(output_script_multisig_length(pubkeys, m)) write_output_script_multisig(w, pubkeys, m) return w
def input_script_p2pkh_or_p2sh(pubkey: bytes, signature: bytes, hash_type: int) -> bytearray: w = utils.empty_bytearray(5 + len(signature) + 1 + 5 + len(pubkey)) append_signature(w, signature, hash_type) append_pubkey(w, pubkey) return w
PrevInput, PrevOutput, PrevTx, SignTx, TxInput, TxOutput, ) from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain from .hash143 import Hash143 # the number of bytes to preallocate for serialized transaction chunks _MAX_SERIALIZED_CHUNK_SIZE = const(2048) _SERIALIZED_TX_BUFFER = empty_bytearray(_MAX_SERIALIZED_CHUNK_SIZE) class Bitcoin: async def signer(self) -> None: # Add inputs to hash143 and h_tx_check and compute the sum of input amounts. await self.step1_process_inputs() # Approve the original TXIDs in case of a replacement transaction. await self.approver.approve_orig_txids(self.tx_info, self.orig_txs) # Add outputs to hash143 and h_tx_check, approve outputs and compute # sum of output amounts. await self.step2_approve_outputs() # Check fee, approve lock_time and total.
def output_script_p2pkh(pubkeyhash: bytes) -> bytearray: s = utils.empty_bytearray(25) write_output_script_p2pkh(s, pubkeyhash) return s