diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index 990429cf68..9b2e770879 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -70,11 +70,13 @@ MULTISIG_INPUT_SCRIPT_TYPES = ( InputScriptType.SPENDMULTISIG, InputScriptType.SPENDP2SHWITNESS, InputScriptType.SPENDWITNESS, + InputScriptType.SPENDTAPROOT, ) MULTISIG_OUTPUT_SCRIPT_TYPES = ( OutputScriptType.PAYTOMULTISIG, OutputScriptType.PAYTOP2SHWITNESS, OutputScriptType.PAYTOWITNESS, + OutputScriptType.PAYTOTAPROOT, ) CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES: dict[OutputScriptType, InputScriptType] = { @@ -117,9 +119,11 @@ def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes: return sigder -def bip340_sign(node: bip32.HDNode, digest: bytes) -> bytes: +def bip340_sign(node: bip32.HDNode, digest: bytes, tweak: bool = True) -> bytes: internal_private_key = node.private_key() - output_private_key = bip340.tweak_secret_key(internal_private_key) + output_private_key = ( + bip340.tweak_secret_key(internal_private_key) if tweak else internal_private_key + ) return bip340.sign(output_private_key, digest) diff --git a/core/src/apps/bitcoin/scripts.py b/core/src/apps/bitcoin/scripts.py index d6b76bbda4..ba97108968 100644 --- a/core/src/apps/bitcoin/scripts.py +++ b/core/src/apps/bitcoin/scripts.py @@ -14,10 +14,13 @@ from .common import ( OP_CHECKSIG, OP_CHECKSIGADD, OP_NUMEQUAL, + LEAF_VERSION, + p2tr_multisig_tweaked_pubkey, ) from .multisig import ( multisig_get_pubkeys, multisig_pubkey_index, + multisig_get_dummy_pubkey, ) from .readers import read_memoryview_prefixed, read_op_push from .writers import ( @@ -603,6 +606,47 @@ def parse_output_script_multisig(script: bytes) -> tuple[list[memoryview], int]: # === +def write_witness_multisig_taproot( + w: Writer, + multisig: MultisigRedeemScriptType, + signature: bytes, + signature_index: int, + sighash_type: SigHashType, +) -> None: + from .multisig import multisig_get_pubkey_count + + # 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 DataError("Invalid multisig parameters") + signatures[signature_index] = signature + + # signatures + redeem script + control block + num_of_witness_items = len(signatures) + 1 + 1 + write_compact_size(w, num_of_witness_items) + + for s in reversed(signatures): + if s: + write_signature_prefixed(w, s, sighash_type) # size of the witness included + else: + w.append(0x00) + + # redeem script + pubkeys = multisig_get_pubkeys(multisig) + write_output_script_multisig_taproot(w, pubkeys, multisig.m) + + # control block + dummy_pubkey = multisig_get_dummy_pubkey(multisig) + write_compact_size(w, len(dummy_pubkey[1:]) + 1) + parity, _ = p2tr_multisig_tweaked_pubkey(pubkeys, dummy_pubkey, multisig.m) + w.append(LEAF_VERSION + parity) + w.extend(dummy_pubkey[1:]) + + def write_output_script_multisig_taproot( w: Writer, pubkeys: Sequence[bytes | memoryview], diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index 08c3aea391..cc1e9d3946 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -10,7 +10,7 @@ from trezor.wire import DataError, ProcessError from apps.common.writers import write_compact_size from .. import addresses, common, multisig, scripts, writers -from ..common import SigHashType, ecdsa_sign, input_is_external +from ..common import SigHashType, ecdsa_sign, input_is_external, p2tr_multisig_leaf_hash from ..ownership import verify_nonownership from ..verification import SignatureVerifier from . import helpers @@ -642,14 +642,22 @@ class Bitcoin: def sign_taproot_input(self, i: int, txi: TxInput) -> bytes: from ..common import bip340_sign + if txi.multisig: + public_keys = multisig.multisig_get_pubkeys(txi.multisig) + threshold = txi.multisig.m + leaf_hash = p2tr_multisig_leaf_hash(public_keys, threshold) + else: + leaf_hash = None + sigmsg_digest = self.tx_info.sig_hasher.hash341( - i, - self.tx_info.tx, - self.get_sighash_type(txi), + i, self.tx_info.tx, self.get_sighash_type(txi), leaf_hash ) node = self.keychain.derive(txi.address_n) - return bip340_sign(node, sigmsg_digest) + public_key = node.public_key() + signature = bip340_sign(node, sigmsg_digest, not txi.multisig) + + return public_key, signature async def sign_segwit_input(self, i: int) -> None: # STAGE_REQUEST_SEGWIT_WITNESS in legacy @@ -660,11 +668,24 @@ class Bitcoin: raise ProcessError("Transaction has changed during signing") if txi.script_type == InputScriptType.SPENDTAPROOT: - signature = self.sign_taproot_input(i, txi) + public_key, signature = self.sign_taproot_input(i, txi) if self.serialize: - scripts.write_witness_p2tr( - self.serialized_tx, signature, self.get_sighash_type(txi) - ) + if txi.multisig: + # find out place of our signature based on the pubkey + signature_index = multisig.multisig_pubkey_index( + txi.multisig, public_key + ) + scripts.write_witness_multisig_taproot( + self.serialized_tx, + txi.multisig, + signature, + signature_index, + self.get_sighash_type(txi), + ) + else: + scripts.write_witness_p2tr( + self.serialized_tx, signature, self.get_sighash_type(txi) + ) else: public_key, signature = self.sign_bip143_input(i, txi) if self.serialize: diff --git a/core/src/apps/bitcoin/sign_tx/sig_hasher.py b/core/src/apps/bitcoin/sign_tx/sig_hasher.py index 83ee92d163..27c5b3bcc8 100644 --- a/core/src/apps/bitcoin/sign_tx/sig_hasher.py +++ b/core/src/apps/bitcoin/sign_tx/sig_hasher.py @@ -37,6 +37,7 @@ if TYPE_CHECKING: i: int, tx: SignTx | PrevTx, sighash_type: SigHashType, + leaf_hash: bytes | None, ) -> bytes: ... def hash_zip244( @@ -132,9 +133,10 @@ class BitcoinSigHasher: i: int, tx: SignTx | PrevTx, sighash_type: SigHashType, + leaf_hash: bytes | None, ) -> bytes: from ..common import tagged_hashwriter - from ..writers import write_uint8 + from ..writers import write_uint8, write_uint32 h_sigmsg = tagged_hashwriter(b"TapSighash") @@ -165,12 +167,18 @@ class BitcoinSigHasher: # sha_outputs write_bytes_fixed(h_sigmsg, self.h_outputs.get_digest(), TX_HASH_SIZE) - # spend_type 0 (no tapscript message extension, no annex) - write_uint8(h_sigmsg, 0) + # spend_type, no annex support for now + spend_type = 0 if leaf_hash is None else 2 + write_uint8(h_sigmsg, spend_type) # input_index write_uint32(h_sigmsg, i) + if leaf_hash is not None: + write_bytes_fixed(h_sigmsg, leaf_hash, TX_HASH_SIZE) + write_uint8(h_sigmsg, 0) + write_uint32(h_sigmsg, 0xFFFFFFFF) + return h_sigmsg.get_digest() def hash_zip244( diff --git a/core/src/apps/bitcoin/sign_tx/tx_weight.py b/core/src/apps/bitcoin/sign_tx/tx_weight.py index a1b44dbd6d..c25d1c23c4 100644 --- a/core/src/apps/bitcoin/sign_tx/tx_weight.py +++ b/core/src/apps/bitcoin/sign_tx/tx_weight.py @@ -35,6 +35,8 @@ _TXSIZE_DER_SIGNATURE = const(72) _TXSIZE_SCHNORR_SIGNATURE = const(64) # size of a multiscript without pubkey (1 M, 1 N, 1 checksig) _TXSIZE_MULTISIGSCRIPT = const(3) +# size of a taproot multiscript without pubkey (1 M, 1 numequal, 1 + 33 control block) +_TXSIZE_MULTISIGSCRIPT_TAPROOT = const(36) # size of a p2wpkh script (1 version, 1 push, 20 hash) _TXSIZE_WITNESSPKHASH = const(22) # size of a p2wsh script (1 version, 1 push, 32 hash) @@ -72,10 +74,18 @@ class TxWeightCalculator: pass if multisig: - if script_type == IST.SPENDTAPROOT: - raise wire.ProcessError("Multisig not supported for taproot") - n = len(multisig.nodes) if multisig.nodes else len(multisig.pubkeys) + if script_type == IST.SPENDTAPROOT: + multisig_script_size = _TXSIZE_MULTISIGSCRIPT_TAPROOT + n * ( + 1 + 1 + _TXSIZE_PUBKEY + ) + multisig_script_size += cls.compact_size_len(multisig_script_size) + return ( + multisig_script_size + + multisig.m * (1 + _TXSIZE_SCHNORR_SIGNATURE) + + (n - multisig.m) + ) + multisig_script_size = _TXSIZE_MULTISIGSCRIPT + n * (1 + _TXSIZE_PUBKEY) if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: multisig_script_size += cls.compact_size_len(multisig_script_size) diff --git a/core/src/apps/bitcoin/sign_tx/zcash_v4.py b/core/src/apps/bitcoin/sign_tx/zcash_v4.py index 38a6ecc100..72d3190c4e 100644 --- a/core/src/apps/bitcoin/sign_tx/zcash_v4.py +++ b/core/src/apps/bitcoin/sign_tx/zcash_v4.py @@ -107,6 +107,7 @@ class Zip243SigHasher: i: int, tx: SignTx | PrevTx, sighash_type: SigHashType, + leaf_hash: bytes | None, ) -> bytes: raise NotImplementedError diff --git a/core/src/apps/zcash/hasher.py b/core/src/apps/zcash/hasher.py index 350681f0e7..5ac4db61a7 100644 --- a/core/src/apps/zcash/hasher.py +++ b/core/src/apps/zcash/hasher.py @@ -118,6 +118,7 @@ class ZcashHasher: i: int, tx: SignTx | PrevTx, sighash_type: SigHashType, + leaf_hash: bytes | None, ) -> bytes: raise NotImplementedError