diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index 955878b10..de7501fc5 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -14,6 +14,10 @@ if False: BITCOIN_NAMES = ("Bitcoin", "Regtest", "Testnet") +# Signature hash type with the same semantics as the SIGHASH_ALL, but instead +# of having to include the byte in the signature, it is implied. +SIGHASH_ALL_TAPROOT = const(0x00) + # Default signature hash type in Bitcoin which signs all inputs and all outputs of the transaction. SIGHASH_ALL = const(0x01) diff --git a/core/src/apps/bitcoin/scripts.py b/core/src/apps/bitcoin/scripts.py index d353d0538..17209cbe8 100644 --- a/core/src/apps/bitcoin/scripts.py +++ b/core/src/apps/bitcoin/scripts.py @@ -240,6 +240,28 @@ def output_script_native_segwit(witver: int, witprog: bytes) -> bytearray: return w +def parse_output_script_p2tr(script_pubkey: bytes) -> memoryview: + # 51 20 <32-byte-taproot-output-key> + try: + r = utils.BufferReader(script_pubkey) + + if r.get() != common.OP_1: + # P2TR should be SegWit version 1 + raise ValueError + + if r.get() != 32: + # taproot output key should be 32 bytes + raise ValueError + + pubkey = r.read_memoryview(32) + if r.remaining_count(): + raise ValueError + except (ValueError, EOFError): + raise wire.DataError("Invalid scriptPubKey.") + + return pubkey + + # SegWit: P2WPKH nested in P2SH # === # https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#witness-program @@ -381,6 +403,39 @@ def parse_witness_multisig( return script, signatures +# Taproot: Witness getters +# === + + +def write_witness_p2tr(w: Writer, signature: bytes, hash_type: int) -> None: + # Taproot key path spending without annex. + write_bitcoin_varint(w, 0x01) # num of segwit items + write_signature_prefixed(w, signature, hash_type) + + +def parse_witness_p2tr(witness: bytes) -> tuple[memoryview, int]: + try: + r = utils.BufferReader(witness) + + if r.get() != 1: # Number of stack items. + # Only Taproot key path spending without annex is supported. + raise ValueError + + n = read_bitcoin_varint(r) + if n not in (64, 65): + raise ValueError + + signature = r.read_memoryview(64) + hash_type = r.get() if n == 65 else common.SIGHASH_ALL_TAPROOT + + if r.remaining_count(): + raise ValueError + except (ValueError, EOFError): + raise wire.DataError("Invalid witness.") + + return signature, hash_type + + # Multisig # === # @@ -573,9 +628,14 @@ def read_bip322_signature_proof(r: utils.BufferReader) -> tuple[memoryview, memo def write_signature_prefixed(w: Writer, signature: bytes, hash_type: int) -> None: - write_bitcoin_varint(w, len(signature) + 1) + length = len(signature) + if hash_type != common.SIGHASH_ALL_TAPROOT: + length += 1 + + write_bitcoin_varint(w, length) write_bytes_unchecked(w, signature) - w.append(hash_type) + if hash_type != common.SIGHASH_ALL_TAPROOT: + w.append(hash_type) def append_signature(w: Writer, signature: bytes, hash_type: int) -> None: