1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-03 04:58:25 +00:00

feat(BTC): add taproot multisig input signing

This commit is contained in:
Andrew Toth 2024-09-05 13:49:19 -04:00
parent 28f51e6358
commit b6e0bb9d1d
No known key found for this signature in database
GPG Key ID: 60007AFC8938B018
7 changed files with 106 additions and 17 deletions

View File

@ -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)

View File

@ -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],

View File

@ -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:

View File

@ -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(

View File

@ -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)

View File

@ -107,6 +107,7 @@ class Zip243SigHasher:
i: int,
tx: SignTx | PrevTx,
sighash_type: SigHashType,
leaf_hash: bytes | None,
) -> bytes:
raise NotImplementedError

View File

@ -118,6 +118,7 @@ class ZcashHasher:
i: int,
tx: SignTx | PrevTx,
sighash_type: SigHashType,
leaf_hash: bytes | None,
) -> bytes:
raise NotImplementedError