mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-11 08:58:08 +00:00
feat(BTC): add taproot multisig input signing
This commit is contained in:
parent
28f51e6358
commit
b6e0bb9d1d
@ -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)
|
||||
|
||||
|
||||
|
@ -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],
|
||||
|
@ -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,8 +668,21 @@ 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:
|
||||
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)
|
||||
)
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -107,6 +107,7 @@ class Zip243SigHasher:
|
||||
i: int,
|
||||
tx: SignTx | PrevTx,
|
||||
sighash_type: SigHashType,
|
||||
leaf_hash: bytes | None,
|
||||
) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -118,6 +118,7 @@ class ZcashHasher:
|
||||
i: int,
|
||||
tx: SignTx | PrevTx,
|
||||
sighash_type: SigHashType,
|
||||
leaf_hash: bytes | None,
|
||||
) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user