Skip to content

Commit

Permalink
Merge pull request #294 from LedgerHQ/musig2
Browse files Browse the repository at this point in the history
MuSig2 support
bigspider authored Dec 10, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents bc81ed0 + 2d22ebe commit 24bcdae
Showing 776 changed files with 6,095 additions and 950 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build_and_functional_tests.yml
Original file line number Diff line number Diff line change
@@ -31,4 +31,4 @@ jobs:
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1
with:
download_app_binaries_artifact: "compiled_app_binaries"

container_image: "ghcr.io/ledgerhq/app-bitcoin-new/speculos-bitcoin-musig2:latest"
8 changes: 4 additions & 4 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
@@ -136,7 +136,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: ghcr.io/ledgerhq/app-bitcoin-new/speculos-bitcoin:latest
image: ghcr.io/ledgerhq/speculos:latest
ports:
- 1234:1234
- 9999:9999
@@ -168,7 +168,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: ghcr.io/ledgerhq/app-bitcoin-new/speculos-bitcoin:latest
image: ghcr.io/ledgerhq/speculos:latest
ports:
- 1234:1234
- 9999:9999
@@ -195,7 +195,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: ghcr.io/ledgerhq/app-bitcoin-new/speculos-bitcoin:latest
image: ghcr.io/ledgerhq/speculos:latest
ports:
- 1234:1234
- 9999:9999
@@ -232,7 +232,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: ghcr.io/ledgerhq/app-bitcoin-new/speculos-bitcoin:latest
image: ghcr.io/ledgerhq/speculos:latest
ports:
- 1234:1234
- 9999:9999
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

Dates are in `dd-mm-yyyy` format.

## [2.4.0] - TBD

### Added

- Support for `musig()` key expressions in taproot wallet policies.

### Changed

- For wallet policies with multiple internal spending paths, the app will only sign for key expressions for which the corresponding `PSBT_IN_BIP32_DERIVATION` or `PSBT_IN_TAP_BIP32_DERIVATION` is present in the PSBT. This improves performance when signing for certain spending paths is not desired.

## [2.3.0] - 26-08-2024

### Added
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -46,9 +46,9 @@ PATH_SLIP21_APP_LOAD_PARAMS = "LEDGER-Wallet policy"

# Application version
APPVERSION_M = 2
APPVERSION_N = 3
APPVERSION_N = 4
APPVERSION_P = 0
APPVERSION_SUFFIX = # if not empty, appended at the end. Do not add a dash.
APPVERSION_SUFFIX = rc # if not empty, appended at the end. Do not add a dash.

ifeq ($(APPVERSION_SUFFIX),)
APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)"
5 changes: 4 additions & 1 deletion bitcoin_client/ledger_bitcoin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

"""Ledger Nano Bitcoin app client"""

from .client_base import Client, TransportClient, PartialSignature
from .client_base import Client, TransportClient, PartialSignature, MusigPubNonce, MusigPartialSignature, SignPsbtYieldedObject
from .client import createClient
from .common import Chain

@@ -13,6 +13,9 @@
"Client",
"TransportClient",
"PartialSignature",
"MusigPubNonce",
"MusigPartialSignature",
"SignPsbtYieldedObject",
"createClient",
"Chain",
"AddressType",
177 changes: 177 additions & 0 deletions bitcoin_client/ledger_bitcoin/bip0327.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# extracted from the BIP327 reference implementation: https://github.com/bitcoin/bips/blob/b3701faef2bdb98a0d7ace4eedbeefa2da4c89ed/bip-0327/reference.py

# Only contains the key aggregation part of the library

# The code in this source file is distributed under the BSD-3-Clause.

# autopep8: off

from typing import List, Optional, Tuple, NewType, NamedTuple
import hashlib

#
# The following helper functions were copied from the BIP-340 reference implementation:
# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
#

p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

# Points are tuples of X and Y coordinates and the point at infinity is
# represented by the None keyword.
G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)

Point = Tuple[int, int]

# This implementation can be sped up by storing the midstate after hashing
# tag_hash instead of rehashing it all the time.
def tagged_hash(tag: str, msg: bytes) -> bytes:
tag_hash = hashlib.sha256(tag.encode()).digest()
return hashlib.sha256(tag_hash + tag_hash + msg).digest()

def is_infinite(P: Optional[Point]) -> bool:
return P is None

def x(P: Point) -> int:
assert not is_infinite(P)
return P[0]

def y(P: Point) -> int:
assert not is_infinite(P)
return P[1]

def point_add(P1: Optional[Point], P2: Optional[Point]) -> Optional[Point]:
if P1 is None:
return P2
if P2 is None:
return P1
if (x(P1) == x(P2)) and (y(P1) != y(P2)):
return None
if P1 == P2:
lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p
else:
lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p
x3 = (lam * lam - x(P1) - x(P2)) % p
return (x3, (lam * (x(P1) - x3) - y(P1)) % p)

def point_mul(P: Optional[Point], n: int) -> Optional[Point]:
R = None
for i in range(256):
if (n >> i) & 1:
R = point_add(R, P)
P = point_add(P, P)
return R

def bytes_from_int(x: int) -> bytes:
return x.to_bytes(32, byteorder="big")

def lift_x(b: bytes) -> Optional[Point]:
x = int_from_bytes(b)
if x >= p:
return None
y_sq = (pow(x, 3, p) + 7) % p
y = pow(y_sq, (p + 1) // 4, p)
if pow(y, 2, p) != y_sq:
return None
return (x, y if y & 1 == 0 else p-y)

def int_from_bytes(b: bytes) -> int:
return int.from_bytes(b, byteorder="big")

def has_even_y(P: Point) -> bool:
assert not is_infinite(P)
return y(P) % 2 == 0

#
# End of helper functions copied from BIP-340 reference implementation.
#

PlainPk = NewType('PlainPk', bytes)
XonlyPk = NewType('XonlyPk', bytes)

# There are two types of exceptions that can be raised by this implementation:
# - ValueError for indicating that an input doesn't conform to some function
# precondition (e.g. an input array is the wrong length, a serialized
# representation doesn't have the correct format).
# - InvalidContributionError for indicating that a signer (or the
# aggregator) is misbehaving in the protocol.
#
# Assertions are used to (1) satisfy the type-checking system, and (2) check for
# inconvenient events that can't happen except with negligible probability (e.g.
# output of a hash function is 0) and can't be manually triggered by any
# signer.

# This exception is raised if a party (signer or nonce aggregator) sends invalid
# values. Actual implementations should not crash when receiving invalid
# contributions. Instead, they should hold the offending party accountable.
class InvalidContributionError(Exception):
def __init__(self, signer, contrib):
self.signer = signer
# contrib is one of "pubkey", "pubnonce", "aggnonce", or "psig".
self.contrib = contrib

infinity = None

def xbytes(P: Point) -> bytes:
return bytes_from_int(x(P))

def cbytes(P: Point) -> bytes:
a = b'\x02' if has_even_y(P) else b'\x03'
return a + xbytes(P)

def point_negate(P: Optional[Point]) -> Optional[Point]:
if P is None:
return P
return (x(P), p - y(P))

def cpoint(x: bytes) -> Point:
if len(x) != 33:
raise ValueError('x is not a valid compressed point.')
P = lift_x(x[1:33])
if P is None:
raise ValueError('x is not a valid compressed point.')
if x[0] == 2:
return P
elif x[0] == 3:
P = point_negate(P)
assert P is not None
return P
else:
raise ValueError('x is not a valid compressed point.')

KeyAggContext = NamedTuple('KeyAggContext', [('Q', Point),
('gacc', int),
('tacc', int)])

def key_agg(pubkeys: List[PlainPk]) -> KeyAggContext:
pk2 = get_second_key(pubkeys)
u = len(pubkeys)
Q = infinity
for i in range(u):
try:
P_i = cpoint(pubkeys[i])
except ValueError:
raise InvalidContributionError(i, "pubkey")
a_i = key_agg_coeff_internal(pubkeys, pubkeys[i], pk2)
Q = point_add(Q, point_mul(P_i, a_i))
# Q is not the point at infinity except with negligible probability.
assert(Q is not None)
gacc = 1
tacc = 0
return KeyAggContext(Q, gacc, tacc)

def hash_keys(pubkeys: List[PlainPk]) -> bytes:
return tagged_hash('KeyAgg list', b''.join(pubkeys))

def get_second_key(pubkeys: List[PlainPk]) -> PlainPk:
u = len(pubkeys)
for j in range(1, u):
if pubkeys[j] != pubkeys[0]:
return pubkeys[j]
return PlainPk(b'\x00'*33)

def key_agg_coeff_internal(pubkeys: List[PlainPk], pk_: PlainPk, pk2: PlainPk) -> int:
L = hash_keys(pubkeys)
if pk_ == pk2:
return 1
return int_from_bytes(tagged_hash('KeyAgg coefficient', L + pk_)) % n
130 changes: 116 additions & 14 deletions bitcoin_client/ledger_bitcoin/client.py
Original file line number Diff line number Diff line change
@@ -3,23 +3,25 @@
import base64
from io import BytesIO, BufferedReader

from .embit import base58
from .embit.base import EmbitError
from .embit.descriptor import Descriptor
from .embit.networks import NETWORKS

from .command_builder import BitcoinCommandBuilder, BitcoinInsType
from .common import Chain, read_uint, read_varint
from .client_command import ClientCommandInterpreter
from .client_base import Client, TransportClient, PartialSignature
from .client_command import ClientCommandInterpreter, CCMD_YIELD_MUSIG_PARTIALSIGNATURE_TAG, CCMD_YIELD_MUSIG_PUBNONCE_TAG
from .client_base import Client, MusigPartialSignature, MusigPubNonce, SignPsbtYieldedObject, TransportClient, PartialSignature
from .client_legacy import LegacyClient
from .exception import DeviceException
from .errors import UnknownDeviceError
from .merkle import get_merkleized_map_commitment
from .wallet import WalletPolicy, WalletType
from .psbt import PSBT, normalize_psbt
from . import segwit_addr
from ._serialize import deser_string

from .bip0327 import key_agg, cbytes


def parse_stream_to_map(f: BufferedReader) -> Mapping[bytes, bytes]:
result = {}
@@ -39,6 +41,54 @@ def parse_stream_to_map(f: BufferedReader) -> Mapping[bytes, bytes]:
return result


def aggr_xpub(pubkeys: List[bytes], chain: Chain) -> str:
BIP_328_CHAINCODE = bytes.fromhex(
"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965")
# sort the pubkeys prior to aggregation
ctx = key_agg(list(sorted(pubkeys)))
compressed_pubkey = cbytes(ctx.Q)

# Serialize according to BIP-32
if chain == Chain.MAIN:
version = 0x0488B21E
else:
version = 0x043587CF

return base58.encode_check(b''.join([
version.to_bytes(4, byteorder='big'),
b'\x00', # depth
b'\x00\x00\x00\x00', # parent fingerprint
b'\x00\x00\x00\x00', # child number
BIP_328_CHAINCODE,
compressed_pubkey
]))


# Given a valid descriptor, replaces each musig() (if any) with the
# corresponding synthetic xpub/tpub.
def replace_musigs(desc: str, chain: Chain) -> str:
while True:
musig_start = desc.find("musig(")
if musig_start == -1:
break
musig_end = desc.find(")", musig_start)
if musig_end == -1:
raise ValueError("Invalid descriptor template")

key_and_origs = desc[musig_start+6:musig_end].split(",")
pubkeys = []
for key_orig in key_and_origs:
orig_end = key_orig.find("]")
xpub = key_orig if orig_end == -1 else key_orig[orig_end+1:]
pubkeys.append(base58.decode_check(xpub)[-33:])

# replace with the aggregate xpub
desc = desc[:musig_start] + \
aggr_xpub(pubkeys, chain) + desc[musig_end+1:]

return desc


def _make_partial_signature(pubkey_augm: bytes, signature: bytes) -> PartialSignature:
if len(pubkey_augm) == 64:
# tapscript spend: pubkey_augm is the concatenation of:
@@ -56,6 +106,60 @@ def _make_partial_signature(pubkey_augm: bytes, signature: bytes) -> PartialSign
return PartialSignature(signature=signature, pubkey=pubkey_augm)


def _decode_signpsbt_yielded_value(res: bytes) -> Tuple[int, SignPsbtYieldedObject]:
res_buffer = BytesIO(res)
input_index_or_tag = read_varint(res_buffer)
if input_index_or_tag == CCMD_YIELD_MUSIG_PUBNONCE_TAG:
input_index = read_varint(res_buffer)
pubnonce = res_buffer.read(66)
participant_pk = res_buffer.read(33)
aggregate_pubkey = res_buffer.read(33)
tapleaf_hash = res_buffer.read()
if len(tapleaf_hash) == 0:
tapleaf_hash = None

return (
input_index,
MusigPubNonce(
participant_pubkey=participant_pk,
aggregate_pubkey=aggregate_pubkey,
tapleaf_hash=tapleaf_hash,
pubnonce=pubnonce
)
)
elif input_index_or_tag == CCMD_YIELD_MUSIG_PARTIALSIGNATURE_TAG:
input_index = read_varint(res_buffer)
partial_signature = res_buffer.read(32)
participant_pk = res_buffer.read(33)
aggregate_pubkey = res_buffer.read(33)
tapleaf_hash = res_buffer.read()
if len(tapleaf_hash) == 0:
tapleaf_hash = None

return (
input_index,
MusigPartialSignature(
participant_pubkey=participant_pk,
aggregate_pubkey=aggregate_pubkey,
tapleaf_hash=tapleaf_hash,
partial_signature=partial_signature
)
)
else:
# other values follow an encoding without an explicit tag, where the
# first element is the input index. All the signature types are implemented
# by the PartialSignature type (not to be confused with the musig Partial Signature).
input_index = input_index_or_tag

pubkey_augm_len = read_uint(res_buffer, 8)
pubkey_augm = res_buffer.read(pubkey_augm_len)

signature = res_buffer.read()

return((input_index, _make_partial_signature(pubkey_augm, signature)))



class NewClient(Client):
# internal use for testing: if set to True, sign_psbt will not clone the psbt before converting to psbt version 2
_no_clone_psbt: bool = False
@@ -162,7 +266,7 @@ def get_wallet_address(

return result

def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_hmac: Optional[bytes]) -> List[Tuple[int, PartialSignature]]:
def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_hmac: Optional[bytes]) -> List[Tuple[int, SignPsbtYieldedObject]]:

psbt = normalize_psbt(psbt)

@@ -231,17 +335,10 @@ def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_
if any(len(x) <= 1 for x in results):
raise RuntimeError("Invalid response")

results_list: List[Tuple[int, PartialSignature]] = []
results_list: List[Tuple[int, SignPsbtYieldedObject]] = []
for res in results:
res_buffer = BytesIO(res)
input_index = read_varint(res_buffer)

pubkey_augm_len = read_uint(res_buffer, 8)
pubkey_augm = res_buffer.read(pubkey_augm_len)

signature = res_buffer.read()

results_list.append((input_index, _make_partial_signature(pubkey_augm, signature)))
input_index, obj = _decode_signpsbt_yielded_value(res)
results_list.append((input_index, obj))

return results_list

@@ -273,6 +370,11 @@ def sign_message(self, message: Union[str, bytes], bip32_path: str) -> str:

def _derive_address_for_policy(self, wallet: WalletPolicy, change: bool, address_index: int) -> Optional[str]:
desc_str = wallet.get_descriptor(change)

# Since embit does not support musig() in descriptors, we replace each
# occurrence with the corresponding aggregated xpub
desc_str = replace_musigs(desc_str, self.chain)

try:
desc = Descriptor.from_string(desc_str)

55 changes: 50 additions & 5 deletions bitcoin_client/ledger_bitcoin/client_base.py
Original file line number Diff line number Diff line change
@@ -28,7 +28,8 @@ def __init__(self, sw: int, data: bytes) -> None:

class TransportClient:
def __init__(self, interface: Literal['hid', 'tcp'] = "tcp", *, server: str = "127.0.0.1", port: int = 9999, path: Optional[str] = None, hid: Optional[HID] = None, debug: bool = False):
self.transport = Transport('hid', path=path, hid=hid, debug=debug) if interface == 'hid' else Transport(interface, server=server, port=port, debug=debug)
self.transport = Transport('hid', path=path, hid=hid, debug=debug) if interface == 'hid' else Transport(
interface, server=server, port=port, debug=debug)

def apdu_exchange(
self, cla: int, ins: int, data: bytes = b"", p1: int = 0, p2: int = 0
@@ -67,18 +68,62 @@ def print_response(sw: int, data: bytes) -> None:

@dataclass(frozen=True)
class PartialSignature:
"""Represents a partial signature returned by sign_psbt.
"""Represents a partial signature returned by sign_psbt. Such objects can be added to the PSBT.
It always contains a pubkey and a signature.
The pubkey
The pubkey is a compressed 33-byte for legacy and segwit Scripts, or 32-byte x-only key for taproot.
The signature is in the format it would be pushed on the scriptSig or the witness stack, therefore of
variable length, and possibly concatenated with the SIGHASH flag byte if appropriate.
The tapleaf_hash is also filled if signing a for a tapscript.
The tapleaf_hash is also filled if signing for a tapscript.
Note: not to be confused with 'partial signature' of protocols like MuSig2;
"""
pubkey: bytes
signature: bytes
tapleaf_hash: Optional[bytes] = None


@dataclass(frozen=True)
class MusigPubNonce:
"""Represents a pubnonce returned by sign_psbt during the first round of a Musig2 signing session.
It always contains
- the participant_pubkey, a 33-byte compressed pubkey;
- aggregate_pubkey, the 33-byte compressed pubkey key that is the aggregate of all the participant
pubkeys, with the necessary tweaks; its x-only version is the key present in the Script;
- the 66-byte pubnonce.
The tapleaf_hash is also filled if signing for a tapscript; `None` otherwise.
"""
participant_pubkey: bytes
aggregate_pubkey: bytes
tapleaf_hash: Optional[bytes]
pubnonce: bytes


@dataclass(frozen=True)
class MusigPartialSignature:
"""Represents a partial signature returned by sign_psbt during the second round of a Musig2 signing session.
It always contains
- the participant_pubkey, a 33-byte compressed pubkey;
- aggregate_pubkey, the 33-byte compressed pubkey key that is the aggregate of all the participant
pubkeys, with the necessary tweaks; its x-only version is the key present in the Script;
- the partial_signature, the 32-byte partial signature for this participant.
The tapleaf_hash is also filled if signing for a tapscript; `None` otherwise
"""
participant_pubkey: bytes
aggregate_pubkey: bytes
tapleaf_hash: Optional[bytes]
partial_signature: bytes


SignPsbtYieldedObject = Union[PartialSignature,
MusigPubNonce, MusigPartialSignature]


class Client:
def __init__(self, transport_client: TransportClient, chain: Chain = Chain.MAIN, debug: bool = False) -> None:
self.transport_client = transport_client
@@ -218,7 +263,7 @@ def get_wallet_address(

raise NotImplementedError

def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_hmac: Optional[bytes]) -> List[Tuple[int, PartialSignature]]:
def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_hmac: Optional[bytes]) -> List[Tuple[int, SignPsbtYieldedObject]]:
"""Signs a PSBT using a registered wallet (or a standard wallet that does not need registration).
Signature requires explicit approval from the user.
4 changes: 4 additions & 0 deletions bitcoin_client/ledger_bitcoin/client_command.py
Original file line number Diff line number Diff line change
@@ -15,6 +15,10 @@ class ClientCommandCode(IntEnum):
GET_MORE_ELEMENTS = 0xA0


CCMD_YIELD_MUSIG_PUBNONCE_TAG = 0xFFFFFFFF
CCMD_YIELD_MUSIG_PARTIALSIGNATURE_TAG = 0xFFFFFFFE


class ClientCommand:
def execute(self, request: bytes) -> bytes:
raise NotImplementedError("Subclasses should implement this method.")
92 changes: 92 additions & 0 deletions bitcoin_client/ledger_bitcoin/psbt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Original version: https://github.com/bitcoin-core/HWI/blob/3fe369d0379212fae1c72729a179d133b0adc872/hwilib/key.py
# Distributed under the MIT License.

# fmt: off

"""
PSBT Classes and Utilities
**************************
@@ -107,6 +109,9 @@ class PartiallySignedInput:
PSBT_IN_TAP_BIP32_DERIVATION = 0x16
PSBT_IN_TAP_INTERNAL_KEY = 0x17
PSBT_IN_TAP_MERKLE_ROOT = 0x18
PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a
PSBT_IN_MUSIG2_PUB_NONCE = 0x1b
PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c

def __init__(self, version: int) -> None:
self.non_witness_utxo: Optional[CTransaction] = None
@@ -129,6 +134,9 @@ def __init__(self, version: int) -> None:
self.tap_bip32_paths: Dict[bytes, Tuple[Set[bytes], KeyOriginInfo]] = {}
self.tap_internal_key = b""
self.tap_merkle_root = b""
self.musig2_participant_pubkeys: Dict[bytes, List[bytes]] = {}
self.musig2_pub_nonces: Dict[Tuple[bytes, bytes, Optional[bytes]], bytes] = {}
self.musig2_partial_sigs: Dict[Tuple[bytes, bytes, Optional[bytes]], bytes] = {}
self.unknown: Dict[bytes, bytes] = {}

self.version: int = version
@@ -355,6 +363,51 @@ def deserialize(self, f: Readable) -> None:
self.tap_merkle_root = deser_string(f)
if len(self.tap_merkle_root) != 32:
raise PSBTSerializationError("Input Taproot merkle root is not 32 bytes")
elif key_type == PartiallySignedInput.PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS:
if key in key_lookup:
raise PSBTSerializationError("Duplicate key, input Musig2 participant pubkeys already provided")
elif len(key) != 1 + 33:
raise PSBTSerializationError("Input Musig2 aggregate compressed pubkey is not 33 bytes")

pubkeys_cat = deser_string(f)
if len(pubkeys_cat) == 0:
raise PSBTSerializationError("The list of compressed pubkeys for Musig2 cannot be empty")
if (len(pubkeys_cat) % 33) != 0:
raise PSBTSerializationError("The compressed pubkeys for Musig2 must be exactly 33 bytes long")
pubkeys = []
for i in range(0, len(pubkeys_cat), 33):
pubkeys.append(pubkeys_cat[i: i + 33])

self.musig2_participant_pubkeys[key[1:]] = pubkeys
elif key_type == PartiallySignedInput.PSBT_IN_MUSIG2_PUB_NONCE:
if key in key_lookup:
raise PSBTSerializationError("Duplicate key, Musig2 public nonce already provided")
elif len(key) not in [1 + 33 + 33, 1 + 33 + 33 + 32]:
raise PSBTSerializationError("Invalid key length for Musig2 public nonce")

providing_pubkey = key[1:1+33]
aggregate_pubkey = key[1+33:1+33+33]
tapleaf_hash = None if len(key) == 1 + 33 + 33 else key[1+33+33:]

public_nonces = deser_string(f)
if len(public_nonces) != 66:
raise PSBTSerializationError("The length of the public nonces in Musig2 must be exactly 66 bytes")

self.musig2_pub_nonces[(providing_pubkey, aggregate_pubkey, tapleaf_hash)] = public_nonces
elif key_type == PartiallySignedInput.PSBT_IN_MUSIG2_PARTIAL_SIG:
if key in key_lookup:
raise PSBTSerializationError("Duplicate key, Musig2 partial signature already provided")
elif len(key) not in [1 + 33 + 33, 1 + 33 + 33 + 32]:
raise PSBTSerializationError("Invalid key length for Musig2 partial signature")

providing_pubkey = key[1:1+33]
aggregate_pubkey = key[1+33:1+33+33]
tapleaf_hash = None if len(key) == 1 + 33 + 33 else key[1+33+33:]

partial_sig = deser_string(f)
if len(partial_sig) != 32:
raise PSBTSerializationError("The length of the partial signature in Musig2 must be exactly 32 bytes")
self.musig2_partial_sigs[(providing_pubkey, aggregate_pubkey, tapleaf_hash)] = partial_sig
else:
if key in self.unknown:
raise PSBTSerializationError("Duplicate key, key for unknown value already provided")
@@ -466,6 +519,20 @@ def serialize(self) -> bytes:
r += ser_string(ser_compact_size(PartiallySignedInput.PSBT_IN_REQUIRED_HEIGHT_LOCKTIME))
r += ser_string(struct.pack("<I", self.height_locktime))

for pk, pubkeys in self.musig2_participant_pubkeys.items():
r += ser_string(ser_compact_size(PartiallySignedInput.PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS) + pk)
r += ser_string(b''.join(pubkeys))

for (pk, aggpk, hash), pubnonce in self.musig2_pub_nonces.items():
key_value = pk + aggpk + (hash or b'')
r += ser_string(ser_compact_size(PartiallySignedInput.PSBT_IN_MUSIG2_PUB_NONCE) + key_value)
r += ser_string(pubnonce)

for (pk, aggpk, hash), partial_sig in self.musig2_partial_sigs.items():
key_value = pk + aggpk + (hash or b'')
r += ser_string(ser_compact_size(PartiallySignedInput.PSBT_IN_MUSIG2_PARTIAL_SIG) + key_value)
r += ser_string(partial_sig)

for key, value in sorted(self.unknown.items()):
r += ser_string(key)
r += ser_string(value)
@@ -487,6 +554,7 @@ class PartiallySignedOutput:
PSBT_OUT_TAP_INTERNAL_KEY = 0x05
PSBT_OUT_TAP_TREE = 0x06
PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08

def __init__(self, version: int) -> None:
self.redeem_script = b""
@@ -497,6 +565,9 @@ def __init__(self, version: int) -> None:
self.tap_internal_key = b""
self.tap_tree = b""
self.tap_bip32_paths: Dict[bytes, Tuple[Set[bytes], KeyOriginInfo]] = {}

self.musig2_participant_pubkeys: Dict[bytes, List[bytes]] = {}

self.unknown: Dict[bytes, bytes] = {}

self.version: int = version
@@ -593,6 +664,22 @@ def deserialize(self, f: Readable) -> None:
for i in range(0, num_hashes):
leaf_hashes.add(vs.read(32))
self.tap_bip32_paths[xonly] = (leaf_hashes, KeyOriginInfo.deserialize(vs.read()))
elif key_type == PartiallySignedOutput.PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS:
if key in key_lookup:
raise PSBTSerializationError("Duplicate key, output Musig2 participant pubkeys already provided")
elif len(key) != 1 + 33:
raise PSBTSerializationError("Output Musig2 aggregate compressed pubkey is not 33 bytes")

pubkeys_cat = deser_string(f)
if len(pubkeys_cat) == 0:
raise PSBTSerializationError("The list of compressed pubkeys for Musig2 cannot be empty")
if (len(pubkeys_cat) % 33) != 0:
raise PSBTSerializationError("The compressed pubkeys for Musig2 must be exactly 33 bytes long")
pubkeys = []
for i in range(0, len(pubkeys_cat), 33):
pubkeys.append(pubkeys_cat[i: i + 33])

self.musig2_participant_pubkeys[key[1:]] = pubkeys
else:
if key in self.unknown:
raise PSBTSerializationError("Duplicate key, key for unknown value already provided")
@@ -650,6 +737,11 @@ def serialize(self) -> bytes:
value += origin.serialize()
r += ser_string(value)

for pk, pubkeys in self.musig2_participant_pubkeys.items():
r += ser_string(ser_compact_size(
PartiallySignedOutput.PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS) + pk)
r += ser_string(b''.join(pubkeys))

for key, value in sorted(self.unknown.items()):
r += ser_string(key)
r += ser_string(value)
35 changes: 27 additions & 8 deletions bitcoin_client/ledger_bitcoin/wallet.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import re

from enum import IntEnum
from typing import List
from typing import List, Union

from hashlib import sha256

from .common import serialize_str, AddressType, write_varint
from .merkle import MerkleTree, element_hash


class WalletType(IntEnum):
WALLET_POLICY_V1 = 1
WALLET_POLICY_V2 = 2
@@ -58,34 +59,52 @@ def n_keys(self) -> int:
return len(self.keys_info)

def serialize(self) -> bytes:
keys_info_hashes = map(lambda k: element_hash(k.encode()), self.keys_info)
keys_info_hashes = map(
lambda k: element_hash(k.encode()), self.keys_info)

descriptor_template_sha256 = sha256(self.descriptor_template.encode()).digest()
descriptor_template_sha256 = sha256(
self.descriptor_template.encode()).digest()

return b"".join([
super().serialize(),
write_varint(len(self.descriptor_template.encode())),
self.descriptor_template.encode() if self.version == WalletType.WALLET_POLICY_V1 else descriptor_template_sha256,
self.descriptor_template.encode(
) if self.version == WalletType.WALLET_POLICY_V1 else descriptor_template_sha256,
write_varint(len(self.keys_info)),
MerkleTree(keys_info_hashes).root
])

def get_descriptor(self, change: bool) -> str:
def get_descriptor(self, change: Union[bool, None]) -> str:
"""
Generates a descriptor string based on the wallet's descriptor template and keys.
Args:
change (bool | None): Indicates whether the descriptor is for a change address.
- If None, returns the BIP-389 multipath address for both the receive and change address.
- If True, the descriptor is for a change address.
- If False, the descriptor is for a non-change address.
Returns:
str: The generated descriptor.
"""

desc = self.descriptor_template
for i in reversed(range(self.n_keys)):
key = self.keys_info[i]
desc = desc.replace(f"@{i}", key)

# in V1, /** is part of the key; in V2, it's part of the policy map. This handles either
desc = desc.replace("/**", f"/{1 if change else 0}/*")
if change is not None:
desc = desc.replace("/**", f"/{1 if change else 0}/*")
else:
desc = desc.replace("/**", f"/<0;1>/*")

if self.version == WalletType.WALLET_POLICY_V2:
# V2, the /<M;N> syntax is supported. Replace with M if not change, or with N if change
regex = r"/<(\d+);(\d+)>"
desc = re.sub(regex, "/\\2" if change else "/\\1", desc)
if change is not None:
desc = re.sub(r"/<(\d+);(\d+)>", "/\\2" if change else "/\\1", desc)

return desc


class MultisigWallet(WalletPolicy):
def __init__(self, name: str, address_type: AddressType, threshold: int, keys_info: List[str], sorted: bool = True, version: WalletType = WalletType.WALLET_POLICY_V2) -> None:
n_keys = len(keys_info)
9 changes: 5 additions & 4 deletions bitcoin_client/tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
pytest>=6.1.1,<7.0.0
pytest-timeout>=2.1.0,<3.0.0
bip32>=3.4,<4.0
ledgercomm>=1.1.0,<1.2.0
ecdsa>=0.16.1,<0.17.0
typing-extensions>=3.7,<4.0
embit>=0.7.0,<0.8.0
mnemonic==0.20
bip32>=3.4,<4.0
pytest>=6.1.1,<7.0.0
pytest-timeout>=2.1.0,<3.0.0
speculos>=0.12.0,<0.13.0
typing-extensions>=3.7,<4.0
26 changes: 21 additions & 5 deletions doc/bitcoin.md
Original file line number Diff line number Diff line change
@@ -229,17 +229,33 @@ No output data; the signature are returned using the YIELD client command.

Using the information in the PSBT and the wallet description, this command verifies what inputs are internal and what outputs match the pattern for a change address. After validating all the external outputs and the transaction fee with the user, it signs each of the internal inputs; each signature is sent to the client using the YIELD command, in the format described below. If multiple key placeholders of the wallet policy are internal, the process is repeated for each of them.

For a registered wallet, the hmac must be correct.

For a default wallet, `hmac` must be equal to 32 bytes `0`.

The data yielded begins with `<tag_or_input_index>`, represented as a bitcoin-style varint; the rest of the message depends on the value of `<tag_or_input_index>`, as specified in the following subsections.

##### If `<tag_or_input_index>` is at most 65535

If `<tag_or_input index>` is at most 65535, then it is an `<input_index>` and a partial_signature is being yielded.

The results yielded via the YIELD command respect the following format: `<input_index> <pubkey_augm_len> <pubkey_augm> <signature>`, where:
- `input_index` is a Bitcoin style varint, the index input of the input being signed (starting from 0);
- `pubkey_augm_len` is an unsigned byte equal to the length of `pubkey_augm`;
- `pubkey_augm` is the `pubkey` used for signing for legacy, segwit or taproot script path spends (a compressed pubkey if non-taproot, a 32-byte x-only pubkey if taproot); for taproot script path spends, it is the concatenation of the `x-only` pubkey and the 32-byte *tapleaf hash* as defined in [BIP-0341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki);
- `pubkey_augm` is the `pubkey` used for signing for legacy, segwit or taproot keypath spends (a compressed pubkey if non-taproot, a 32-byte x-only pubkey if taproot); for taproot script path spends, it is the concatenation of the `x-only` pubkey and the 32-byte *tapleaf hash* as defined in [BIP-0341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki);
- `signature` is the returned signature, possibly concatenated with the sighash byte (as it would be pushed on the stack).

If `P2` is `0` (version `0` of the protocol), `pubkey_augm_len` and `pubkey_augm` are omitted in the YIELD messages.
##### If `<tag_or_input_index>` is more than 65535

For a registered wallet, the hmac must be correct.
In this case `<tag_or_input_index>` is a `<tag>` that specifies what the rest of the data contains.

- If `<tag>` equals `0xFFFFFFFF`: a pubnonce is being yielded during Round 1 of the MuSig2 protocol. The format is: `<tag> <input_index: varint)> <pubnonce: 66 bytes> <participant_pk: 33 bytes> <aggregate_pubkey: 33 bytes> <tapleaf_hash: 0 or 32 bytes>`.
- If `<tag>` equals `0xFFFFFFFE`: a partial signature is being yielded, completing Round 2 of the MuSig2 protocol. The format is: `<tag> <input_index: varint)> <partial_signature: 32 bytes> <participant_pk: 33 bytes> <aggregate_pubkey: 33 bytes> <tapleaf_hash: 0 or 32 bytes>`

In both cases, the `tapleaf_hash` is only present for a Script path spend.

Other values of the `<tag>` are undefined and reserved for future use.

For a default wallet, `hmac` must be equal to 32 bytes `0`.

#### Client commands

@@ -249,7 +265,7 @@ The client must respond to the `GET_PREIMAGE`, `GET_MERKLE_LEAF_PROOF` and `GET_

The `GET_MORE_ELEMENTS` command must be handled.

The `YIELD` command must be processed in order to receive the signatures.
The `YIELD` command must be processed in order to receive the signatures, or the MuSig2 partial nonces or partial signatures.

### GET_MASTER_FINGERPRINT

87 changes: 87 additions & 0 deletions doc/musig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# MuSig2

The Ledger Bitcoin app supports wallet policies with `musig()` key expressions.

MuSig2 is a 2-round multi-signature scheme compatible with the public keys and signatures used in taproot transactions. The implementation is compliant with [BIP-0327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki).

## Specs

`musig()` key expressions are supported for all taproot policies, including taproot keypaths and miniscript.

- At most 16 keys are allowed in the musig expression; performance limitations, however, might apply in practice.
- At most 8 parallel MuSig signing sessions are supported, due to the need to persist state in the device's memory.
- Only `musig(...)/**` or `musig(...)/<M;N>/*` key expressions are supported; the public keys must be xpubs aggregated without any further derivation. Schemes where each pubkey is derived prior to aggregation (for example descriptors similar to `musig(xpub1/<0;1>/*,xpub2/<0;1>/*,...)`) are not supported.

## State minimization

This section describes implementation details that allow to minimize the amount of statefor each MuSig2 signing session, allowing secure support for multiple parallel MuSig2 on embedded device with limited storage.

### Introduction

BIP-0327 discusses at length the necessity to keep some state during a signing session. However, a "signing session" in BIP-0327 only refers to the production of a single signature.

In the typical signing flow of a wallet, it's more logical to consider a _session_ at the level of an entire transaction. All transaction inputs are likely obtained from the same [descriptor containing musig()](https://github.com/bitcoin/bips/pull/1540), with the signer producing the pubnonce/signature for all the inputs at once.

Therefore, in the flow of BIP-0327, you would expect at least _one MuSig2 signing session per input_ to be active at the same time. In the context of hardware signing device support, that's somewhat problematic: it would require to persist state for an unbounded number of signing sessions, for example for a wallet that received a large number of small UTXOs. Persistent storage is often a scarce resource in embedded signing devices, and a naive approach would likely impose a maximum limit on the number of inputs of the transactions, depending on the hardware limitations.

This document describes an approach that is compatible with and builds on top of BIP-0327 to define a _psbt-level session_ with only a small amount of state persisted on the device. Each psbt-level session allows to manage in parallel all the MuSig2 sessions involved in signing a transaction (typically, at least one for each input). Each psbt-level session only requires 64 bytes of storage for the entire transaction, regardless of the amount of inputs.

### Signing flow with synthetic randomness

#### Synthetic generation of BIP-0327 state

This section presents the core idea, while the next section makes it more precise in the context of signing devices.

In BIP-0327, the internal state that is kept by the signing device is essentially the *secnonce*, which in turn is computed from a random number _rand'_, and optionally from other parameters of _NonceGen_ which depend on the transaction being signed.

The core idea for state minimization is to compute a global random `rand_root`; then, for the *i*-th input and for the *j*-th `musig()` key that the device is signing for in the [wallet policy](https://github.com/bitcoin/bips/pull/1389), one defines the *rand'* in _NonceGen_ as:

$\qquad rand_{i,j} = SHA256(rand\_root || i || j)$

In the concatenation, a fixed-length encoding of $i$ and $j$ is used in order to avoid collisions. That is used as the *rand'* value in the *NonceGen* algorithm for that input/KEY pair.

The *j* parameter allows to handle wallet policies that contain more than one `musig()` key expression involving the signing device.

#### Signing flow in detail

This section describes the handling of the psbt-level sessions, plugging on top of the default signing flow of BIP-0327.

We assume that the signing device handles a single psbt-level session; this can be generalized to multiple parallel psbt-level sessions, where each session computes and stores a different `rand_root`.

In the following, a _session_ always refers to the psbt-level signing session; it contains `rand_root`, and possibly any other auxiliary data that the device wishes to save while signing is in progress.

The term *persistent memory* refers to secure storage that is not wiped out when the device is turned off. The term *volatile memory* refers to the working memory available while the device is involved in the signing process. In Ledger signing devices, the persistent storage is flash memory, and the volatile memory is the RAM of the app. Both are contained in the Secure Element.

**Phase 1: pubnonce generation:** A PSBT is sent to the signing device, and it does not contain any pubnonce.
- If a session already exists, it is deleted from the persistent memory.
- A new session is created in volatile memory.
- The device produces a fresh random number $rand\_root$, and saves it in the current session.
- The device generates the randomness for the $i$-th input and for the $j$-th key as: $rand_{i,j} = SHA256(rand\_root || i || j)$.
- Compute each *(secnonce, pubnonce)* as per the `NonceGen` algorithm.
- At completion (after all the pubnonces are returned), the session secret $rand\_root$ is copied into the persistent memory.

**Phase 2: partial signature generation:** A PSBT containing all the pubnonces is sent to the device.
- *A copy of the session is stored in the volatile memory, and the session is deleted from the persistent memory*.
- For each input/musig-key pair $(i, j)$:
- Recompute the pubnonce/secnonce pair using `NonceGen` with the synthetic randomness $rand_{i,j}$ as above.
- Verify that the pubnonce contained in the PSBT matches the one synthetically recomputed.
- Continue the signing flow as per BIP-0327, generating the partial signature.

### Security considerations
#### State reuse avoidance
Storing the session in persistent memory only at the end of Phase 1, and deleting it before beginning Phase 2 simplifies auditing and making sure that there is no reuse of state across signing sessions.

#### Security of synthetic randomness

Generating $rand_{i, j}$ synthetically is not a problem, since the $rand\_root$ value is kept secret and never leaves the device. This ensures that all the values produced for different $i$ and $j$ are not predictable for an attacker.

#### Malleability of the PSBT
If the optional parameters are passed to the _NonceGen_ function, they will depend on the transaction data present in the PSBT. Therefore, there is no guarantee that they will be unchanged the next time the PSBT is provided.

However, that does not constitute a security risk, as those parameters are only used as additional sources of entropy in _NonceGen_. A malicious software wallet can't affect the _secnonce_/_pubnonce_ pairs in any predictable way. Changing any of the parameters used in _NonceGen_ would cause a failure during Phase 2, as the recomputed _pubnonce_ would not match the one in the psbt.

### Generalization to multiple PSBT signing sessions

The approach described above assumes that no attempt to sign a PSBT for a wallet policy containing `musig()` keys is initiated while a session is already in progress.

In order to generalize this to an arbitrary number of parallel signing sessions, one can identify each signing session with a `psbt_session_id`. Such `psbt_session_id` should deterministically depend on the transaction being signed (ignoring all the other PSBT fields), and the wallet policy being signed. In praticular, the computed `psbt_session_id` should be identical between Round 1 and Round 2 of the protocol. Note that malicious collisions of the `psbt_session_id` (for example by tampering with some details of the PSBT, like the SIGHASH flags) _are_ possible, but they do not constitute a security risk.
4 changes: 4 additions & 0 deletions src/commands.h
Original file line number Diff line number Diff line change
@@ -11,3 +11,7 @@ typedef enum {
GET_MASTER_FINGERPRINT = 0x05,
SIGN_MESSAGE = 0x10,
} command_e;

// Tags used when yielding different objects with the YIELD client command.
#define CCMD_YIELD_MUSIG_PUBNONCE_TAG 0xffffffff
#define CCMD_YIELD_MUSIG_PARTIALSIGNATURE_TAG 0xfffffffe
95 changes: 49 additions & 46 deletions src/common/psbt.h
Original file line number Diff line number Diff line change
@@ -3,55 +3,58 @@
// clang-format off

enum PsbtGlobalType {
PSBT_GLOBAL_UNSIGNED_TX = 0x00,
PSBT_GLOBAL_XPUB = 0x01,
PSBT_GLOBAL_TX_VERSION = 0x02,
PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03,
PSBT_GLOBAL_INPUT_COUNT = 0x04,
PSBT_GLOBAL_OUTPUT_COUNT = 0x05,
PSBT_GLOBAL_TX_MODIFIABLE = 0x06,
PSBT_GLOBAL_SIGHASH_SINGLE_INPUTS = 0x07,
PSBT_GLOBAL_VERSION = 0xFB,
PSBT_GLOBAL_PROPRIETARY = 0xFC
PSBT_GLOBAL_UNSIGNED_TX = 0x00,
PSBT_GLOBAL_XPUB = 0x01,
PSBT_GLOBAL_TX_VERSION = 0x02,
PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03,
PSBT_GLOBAL_INPUT_COUNT = 0x04,
PSBT_GLOBAL_OUTPUT_COUNT = 0x05,
PSBT_GLOBAL_TX_MODIFIABLE = 0x06,
PSBT_GLOBAL_VERSION = 0xFB,
PSBT_GLOBAL_PROPRIETARY = 0xFC
};

enum PsbtInputType {
PSBT_IN_NON_WITNESS_UTXO = 0x00,
PSBT_IN_WITNESS_UTXO = 0x01,
PSBT_IN_PARTIAL_SIG = 0x02,
PSBT_IN_SIGHASH_TYPE = 0x03,
PSBT_IN_REDEEM_SCRIPT = 0x04,
PSBT_IN_WITNESS_SCRIPT = 0x05,
PSBT_IN_BIP32_DERIVATION = 0x06,
PSBT_IN_FINAL_SCRIPTSIG = 0x07,
PSBT_IN_FINAL_SCRIPTWITNESS = 0x08,
PSBT_IN_POR_COMMITMENT = 0x09,
PSBT_IN_RIPEMD160 = 0x0A,
PSBT_IN_SHA256 = 0x0B,
PSBT_IN_HASH160 = 0x0C,
PSBT_IN_HASH256 = 0x0D,
PSBT_IN_PREVIOUS_TXID = 0x0E,
PSBT_IN_OUTPUT_INDEX = 0x0F,
PSBT_IN_SEQUENCE = 0x10,
PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11,
PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12,
PSBT_IN_TAP_KEY_SIG = 0x13,
PSBT_IN_TAP_SCRIPT_SIG = 0x14,
PSBT_IN_TAP_LEAF_SCRIPT = 0x15,
PSBT_IN_TAP_BIP32_DERIVATION = 0x16,
PSBT_IN_TAP_INTERNAL_KEY = 0x17,
PSBT_IN_TAP_MERKLE_ROOT = 0x18,
PSBT_IN_PROPRIETARY = 0xFC
PSBT_IN_NON_WITNESS_UTXO = 0x00,
PSBT_IN_WITNESS_UTXO = 0x01,
PSBT_IN_PARTIAL_SIG = 0x02,
PSBT_IN_SIGHASH_TYPE = 0x03,
PSBT_IN_REDEEM_SCRIPT = 0x04,
PSBT_IN_WITNESS_SCRIPT = 0x05,
PSBT_IN_BIP32_DERIVATION = 0x06,
PSBT_IN_FINAL_SCRIPTSIG = 0x07,
PSBT_IN_FINAL_SCRIPTWITNESS = 0x08,
PSBT_IN_POR_COMMITMENT = 0x09,
PSBT_IN_RIPEMD160 = 0x0A,
PSBT_IN_SHA256 = 0x0B,
PSBT_IN_HASH160 = 0x0C,
PSBT_IN_HASH256 = 0x0D,
PSBT_IN_PREVIOUS_TXID = 0x0E,
PSBT_IN_OUTPUT_INDEX = 0x0F,
PSBT_IN_SEQUENCE = 0x10,
PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11,
PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12,
PSBT_IN_TAP_KEY_SIG = 0x13,
PSBT_IN_TAP_SCRIPT_SIG = 0x14,
PSBT_IN_TAP_LEAF_SCRIPT = 0x15,
PSBT_IN_TAP_BIP32_DERIVATION = 0x16,
PSBT_IN_TAP_INTERNAL_KEY = 0x17,
PSBT_IN_TAP_MERKLE_ROOT = 0x18,
PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1A,
PSBT_IN_MUSIG2_PUB_NONCE = 0x1B,
PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1C,
PSBT_IN_PROPRIETARY = 0xFC
};

enum PsbtOutputType {
PSBT_OUT_REDEEM_SCRIPT = 0x00,
PSBT_OUT_WITNESS_SCRIPT = 0x01,
PSBT_OUT_BIP32_DERIVATION = 0x02,
PSBT_OUT_AMOUNT = 0x03,
PSBT_OUT_SCRIPT = 0x04,
PSBT_OUT_TAP_INTERNAL_KEY = 0x05,
PSBT_OUT_TAP_TREE = 0x06,
PSBT_OUT_TAP_BIP32_DERIVATION = 0x07,
PSBT_OUT_PROPRIETARY = 0xFC
};
PSBT_OUT_REDEEM_SCRIPT = 0x00,
PSBT_OUT_WITNESS_SCRIPT = 0x01,
PSBT_OUT_BIP32_DERIVATION = 0x02,
PSBT_OUT_AMOUNT = 0x03,
PSBT_OUT_SCRIPT = 0x04,
PSBT_OUT_TAP_INTERNAL_KEY = 0x05,
PSBT_OUT_TAP_TREE = 0x06,
PSBT_OUT_TAP_BIP32_DERIVATION = 0x07,
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08,
PSBT_OUT_PROPRIETARY = 0xFC
};
204 changes: 156 additions & 48 deletions src/common/wallet.c
Original file line number Diff line number Diff line change
@@ -424,23 +424,120 @@ int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out, int
return 0;
}

// parses a placeholder from in_buf, storing it in out. On success, the pointed placeholder_index is
// stored in out->placeholder_index, and then it's incremented.
static int parse_placeholder(buffer_t *in_buf,
int version,
policy_node_key_placeholder_t *out,
uint16_t *placeholder_index) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcomment"
// The compiler doesn't like /** inside a block comment, so we disable this warning temporarily.

/**
* Parses a key expression, in one of the following forms:
* - Single key index:
* - @IDX/**
* - @IDX/<M;N>/*
* - MuSig2 aggregate key (only if is_taproot is true):
* - musig(@IDX,@IDX,...,@IDX)/**
* - musig(@IDX,@IDX,...,@IDX)/<M;N>/*
* where IDX is a key index.
*/
#pragma GCC diagnostic pop
static int parse_keyexpr(buffer_t *in_buf,
int version,
policy_node_keyexpr_t *out,
bool is_taproot,
buffer_t *out_buf,
uint16_t *keyexpr_index) {
char c;
if (!buffer_read_u8(in_buf, (uint8_t *) &c) || c != '@') {
return WITH_ERROR(-1, "Expected key placeholder starting with '@'");
if (!buffer_read_u8(in_buf, (uint8_t *) &c)) {
return WITH_ERROR(-1, "Expected key expression");
}

uint32_t k;
if (parse_unsigned_decimal(in_buf, &k) == -1 || k > INT16_MAX) {
return WITH_ERROR(-1, "The key index in a placeholder must be at most 32767");
}
if (c == '@') {
out->type = KEY_EXPRESSION_NORMAL;

uint32_t k;
if (parse_unsigned_decimal(in_buf, &k) == -1 || k > INT16_MAX) {
return WITH_ERROR(-1, "The key index in a placeholder must be at most 32767");
}

out->k.key_index = (int16_t) k;
} else if (c == 'm') {
// parse a musig(key1,...,keyn) expression, where each key is a key expression
if (!consume_characters(in_buf, "usig(", 5)) {
return WITH_ERROR(-1, "Expected musig key expression");
}

if (!is_taproot) {
return WITH_ERROR(-1, "musig is only allowed in taproot");
}

out->type = KEY_EXPRESSION_MUSIG;

if (version != WALLET_POLICY_VERSION_V2) {
return WITH_ERROR(-1, "musig key expressions are only supported with version number 2");
}

uint16_t keys[MAX_PUBKEYS_PER_MUSIG];
int n_musig_keys = 0;

// parse comma-separated list of @NUM
while (true) {
if (!buffer_read_u8(in_buf, (uint8_t *) &c) || c != '@') {
return WITH_ERROR(-1, "Expected key placeholder starting with '@'");
}

uint32_t k;
if (parse_unsigned_decimal(in_buf, &k) == -1 || k > INT16_MAX) {
return WITH_ERROR(-1, "The key index in a placeholder must be at most 32767");
}

out->key_index = (int16_t) k;
if (n_musig_keys >= MAX_PUBKEYS_PER_MUSIG) {
return WITH_ERROR(-1, "Too many keys in musig");
}

keys[n_musig_keys] = (uint16_t) k;
++n_musig_keys;

// the next character must be "," if there are more keys, or ')' otherwise
if (!buffer_read_u8(in_buf, (uint8_t *) &c)) {
return WITH_ERROR(-1, "Expression terminated prematurely");
}

if (c == ')') {
break;
} else if (c != ',') {
return WITH_ERROR(-1, "Invalid character in musig; expected ',' or ')'");
}
}

if (n_musig_keys < 2) {
return WITH_ERROR(-1, "musig must have at least 2 key indexes");
}

// sanity check; the loop above should never exit with too many keys
LEDGER_ASSERT(n_musig_keys <= MAX_PUBKEYS_PER_MUSIG, "Too many keys in musig");

// allocate musig structures

musig_aggr_key_info_t *musig_info =
(musig_aggr_key_info_t *) buffer_alloc(out_buf, sizeof(musig_info), true);

if (musig_info == NULL) {
return WITH_ERROR(-1, "Out of memory");
}

uint16_t *key_indexes =
(uint16_t *) buffer_alloc(out_buf, sizeof(uint16_t) * n_musig_keys, true);
if (key_indexes == NULL) {
return WITH_ERROR(-1, "Out of memory");
}
memcpy(key_indexes, keys, sizeof(uint16_t) * n_musig_keys);

musig_info->n = n_musig_keys;
i_uint16(&musig_info->key_indexes, key_indexes);

i_musig_aggr_key_info(&out->m.musig_info, musig_info);
} else {
return WITH_ERROR(-1, "Expected key expression starting with '@', or musig");
}

if (version == WALLET_POLICY_VERSION_V1) {
// default values for compatibility with the new code
@@ -453,12 +550,12 @@ static int parse_placeholder(buffer_t *in_buf,
|| !buffer_peek(in_buf, &next_character) // we must be able to read the next character
|| !(next_character == '*' || next_character == '<') // and it must be '*' or '<'
) {
return WITH_ERROR(-1, "Expected /** or /<M;N>/* in key placeholder");
return WITH_ERROR(-1, "Expected /** or /<M;N>/* in key expression");
}

if (next_character == '*') {
if (!consume_characters(in_buf, "**", 2)) {
return WITH_ERROR(-1, "Expected /** or /<M;N>/* in key placeholder");
return WITH_ERROR(-1, "Expected /** or /<M;N>/* in key expression");
}
out->num_first = 0;
out->num_second = 1;
@@ -468,34 +565,34 @@ static int parse_placeholder(buffer_t *in_buf,
out->num_first > 0x80000000u) {
return WITH_ERROR(
-1,
"Expected /** or /<M;N>/* in key placeholder, with unhardened M and N");
"Expected /** or /<M;N>/* in key expression, with unhardened M and N");
}

if (!consume_character(in_buf, ';')) {
return WITH_ERROR(-1, "Expected /** or /<M;N>/* in key placeholder");
return WITH_ERROR(-1, "Expected /** or /<M;N>/* in key expression");
}

if (parse_unsigned_decimal(in_buf, &out->num_second) == -1 ||
out->num_second > 0x80000000u) {
return WITH_ERROR(
-1,
"Expected /** or /<M;N>/* in key placeholder, with unhardened M and N");
"Expected /** or /<M;N>/* in key expression, with unhardened M and N");
}

if (out->num_first == out->num_second) {
return WITH_ERROR(-1, "M and N must be different in <M;N>/*");
}

if (!consume_characters(in_buf, ">/*", 3)) {
return WITH_ERROR(-1, "Expected /** or /<M;N>/* in key placeholder");
return WITH_ERROR(-1, "Expected /** or /<M;N>/* in key expression");
}
}
} else {
return WITH_ERROR(-1, "Invalid version number");
}

out->placeholder_index = *placeholder_index;
++(*placeholder_index);
out->keyexpr_index = *keyexpr_index;
++(*keyexpr_index);
return 0;
}

@@ -551,13 +648,13 @@ static int parse_script(buffer_t *in_buf,
unsigned int context_flags) {
int n_wrappers = 0;

// Keep track of how many key placeholders have been created while parsing
// Keep track of how many key expressions have been created while parsing
// This allows to know the counter even in recursive calls
static uint16_t key_placeholder_count = 0;
static uint16_t key_expression_count = 0;

if (depth == 0) {
// reset the counter on function entry, but not in recursive calls
key_placeholder_count = 0;
key_expression_count = 0;
}

policy_node_t *outermost_node = (policy_node_t *) buffer_get_cur(out_buf);
@@ -1394,13 +1491,13 @@ static int parse_script(buffer_t *in_buf,
return WITH_ERROR(-1, "Out of memory");
}

policy_node_key_placeholder_t *key_placeholder =
buffer_alloc(out_buf, sizeof(policy_node_key_placeholder_t), true);
policy_node_keyexpr_t *key_expr =
buffer_alloc(out_buf, sizeof(policy_node_keyexpr_t), true);

if (key_placeholder == NULL) {
if (key_expr == NULL) {
return WITH_ERROR(-1, "Out of memory");
}
i_policy_node_key_placeholder(&node->key_placeholder, key_placeholder);
i_policy_node_keyexpr(&node->key, key_expr);

if (token == TOKEN_WPKH) {
if (depth > 0 && ((context_flags & CONTEXT_WITHIN_SH) == 0)) {
@@ -1412,8 +1509,14 @@ static int parse_script(buffer_t *in_buf,

node->base.type = token;

if (0 > parse_placeholder(in_buf, version, key_placeholder, &key_placeholder_count)) {
return WITH_ERROR(-1, "Couldn't parse key placeholder");
bool is_taproot = (context_flags & CONTEXT_WITHIN_TR) != 0;
if (0 > parse_keyexpr(in_buf,
version,
key_expr,
is_taproot,
out_buf,
&key_expression_count)) {
return WITH_ERROR(-1, "Couldn't parse key expression");
}

if (token == TOKEN_WPKH) {
@@ -1475,15 +1578,16 @@ static int parse_script(buffer_t *in_buf,
return WITH_ERROR(-1, "Out of memory");
}

policy_node_key_placeholder_t *key_placeholder =
buffer_alloc(out_buf, sizeof(policy_node_key_placeholder_t), true);
if (key_placeholder == NULL) {
policy_node_keyexpr_t *key_expr =
buffer_alloc(out_buf, sizeof(policy_node_keyexpr_t), true);
if (key_expr == NULL) {
return WITH_ERROR(-1, "Out of memory");
}
i_policy_node_key_placeholder(&node->key_placeholder, key_placeholder);
i_policy_node_keyexpr(&node->key, key_expr);

if (0 > parse_placeholder(in_buf, version, key_placeholder, &key_placeholder_count)) {
return WITH_ERROR(-1, "Couldn't parse key placeholder");
if (0 >
parse_keyexpr(in_buf, version, key_expr, true, out_buf, &key_expression_count)) {
return WITH_ERROR(-1, "Couldn't parse key expression");
}

uint8_t c;
@@ -1559,7 +1663,8 @@ static int parse_script(buffer_t *in_buf,
return WITH_ERROR(-1, "Out of memory");
}

if ((context_flags & CONTEXT_WITHIN_TR) != 0) {
bool is_taproot = (context_flags & CONTEXT_WITHIN_TR) != 0;
if (is_taproot) {
if (token != TOKEN_MULTI_A && token != TOKEN_SORTEDMULTI_A) {
return WITH_ERROR(
-1,
@@ -1597,7 +1702,7 @@ static int parse_script(buffer_t *in_buf,
// We allocate the array of key indices at the current position in the output buffer
// (on success)
buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer
i_policy_node_key_placeholder(&node->key_placeholders, buffer_get_cur(out_buf));
i_policy_node_keyexpr(&node->keys, buffer_get_cur(out_buf));

node->n = 0;
while (true) {
@@ -1612,19 +1717,22 @@ static int parse_script(buffer_t *in_buf,
return WITH_ERROR(-1, "Expected ','");
}

policy_node_key_placeholder_t *key_placeholder =
(policy_node_key_placeholder_t *) buffer_alloc(
out_buf,
sizeof(policy_node_key_placeholder_t),
true); // we align this pointer, as there's padding in an array of
// structures
if (key_placeholder == NULL) {
policy_node_keyexpr_t *key_expr = (policy_node_keyexpr_t *) buffer_alloc(
out_buf,
sizeof(policy_node_keyexpr_t),
true); // we align this pointer, as there's padding in an array of
// structures
if (key_expr == NULL) {
return WITH_ERROR(-1, "Out of memory");
}

if (0 >
parse_placeholder(in_buf, version, key_placeholder, &key_placeholder_count)) {
return WITH_ERROR(-1, "Error parsing key placeholder");
if (0 > parse_keyexpr(in_buf,
version,
key_expr,
is_taproot,
out_buf,
&key_expression_count)) {
return WITH_ERROR(-1, "Error parsing key expression");
}

++node->n;
67 changes: 48 additions & 19 deletions src/common/wallet.h
Original file line number Diff line number Diff line change
@@ -19,6 +19,13 @@
// bitcoin-core supports up to 20, but we limit to 16 as bigger pushes require special handling.
#define MAX_PUBKEYS_PER_MULTISIG 16

// The maximum number of keys supported in a musig() key expression
// It is basically unlimited in theory, but we need to set a practical limit.
// The implementation of MuSig2 requires quite a few large arrays (for example, the pubnonces are
// 66 bytes each, and there is one for each cosigner), therefore we keep this quite small.
// Increasing this might require optimizing the memory management in the MuSig2 implementation.
#define MAX_PUBKEYS_PER_MUSIG 5

#define WALLET_POLICY_VERSION_V1 1 // the legacy version of the first release
#define WALLET_POLICY_VERSION_V2 2 // the current full version

@@ -280,31 +287,54 @@ typedef struct policy_node_ext_info_s {
unsigned int x : 1; // the last opcode is not EQUAL, CHECKSIG, or CHECKMULTISIG
} policy_node_ext_info_t;

DEFINE_REL_PTR(uint16, uint16_t)

typedef struct {
int16_t n; // number of key indexes
rptr_uint16_t key_indexes; // pointer to an array of exactly n key indexes
} musig_aggr_key_info_t;

DEFINE_REL_PTR(musig_aggr_key_info, musig_aggr_key_info_t)

typedef enum {
KEY_EXPRESSION_NORMAL = 0, // a key expression with a single key expression
KEY_EXPRESSION_MUSIG = 1 // a key expression containing a musig()
} KeyExpressionType;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcomment"
// The compiler doesn't like /** inside a block comment, so we disable this warning temporarily.

/** Structure representing a key placeholder.
* In V1, it's the index of a key expression in the key informations array, which includes the final
* / ** step.
* In V2, it's the index of a key expression in the key informations array, plus the two
* numbers a, b in the /<NUM_a;NUM_b>/* derivation steps; here, the xpubs in the key informations
* array don't have extra derivation steps.
/** Structure representing a key expression.
* In V1, it's the index of a key in the key informations array, which includes the final /** step.
* In V2, it's the index of a key in the key informations array, plus the two numbers a, b in the
* /<NUM_a;NUM_b>/* derivation steps; here, the xpubs in the key informations array don't have extra
* derivation steps.
*/
#pragma GCC diagnostic pop
// 12 bytes

// 16 bytes
typedef struct {
// the following fields are only used in V2
uint32_t num_first; // NUM_a of /<NUM_a,NUM_b>/*
uint32_t num_second; // NUM_b of /<NUM_a,NUM_b>/*

// common between V1 and V2
int16_t key_index; // index of the key
KeyExpressionType type;
union {
// type == 0
struct {
int16_t key_index; // index of the key (common between V1 and V2)
} k;
// type == 1
struct {
rptr_musig_aggr_key_info_t musig_info; // only used in V2
} m;
};
int16_t
placeholder_index; // index of the placeholder in the descriptor template, in parsing order
} policy_node_key_placeholder_t;
keyexpr_index; // index of the key expression in the descriptor template, in parsing order
} policy_node_keyexpr_t;

DEFINE_REL_PTR(policy_node_key_placeholder, policy_node_key_placeholder_t)
DEFINE_REL_PTR(policy_node_keyexpr, policy_node_keyexpr_t)

// 4 bytes
typedef struct {
@@ -335,7 +365,7 @@ typedef policy_node_with_script3_t policy_node_with_scripts_t;
// 4 bytes
typedef struct {
struct policy_node_s base;
rptr_policy_node_key_placeholder_t key_placeholder;
rptr_policy_node_keyexpr_t key;
} policy_node_with_key_t;

// 8 bytes
@@ -346,11 +376,10 @@ typedef struct {

// 12 bytes
typedef struct {
struct policy_node_s base; // type is TOKEN_MULTI or TOKEN_SORTEDMULTI
int16_t k; // threshold
int16_t n; // number of keys
rptr_policy_node_key_placeholder_t
key_placeholders; // pointer to array of exactly n key placeholders
struct policy_node_s base; // type is TOKEN_MULTI or TOKEN_SORTEDMULTI
int16_t k; // threshold
int16_t n; // number of keys
rptr_policy_node_keyexpr_t keys; // pointer to array of exactly n key expressions
} policy_node_multisig_t;

// 8 bytes
@@ -400,7 +429,7 @@ typedef struct policy_node_tree_s {

typedef struct {
struct policy_node_s base;
rptr_policy_node_key_placeholder_t key_placeholder;
rptr_policy_node_keyexpr_t key;
rptr_policy_node_tree_t tree; // NULL if tr(KP)
} policy_node_tr_t;

7 changes: 7 additions & 0 deletions src/constants.h
Original file line number Diff line number Diff line change
@@ -51,6 +51,13 @@
*/
#define MAX_N_OUTPUTS_CAN_SIGN 512

/**
* Maximum supported number of internal key expressions in a wallet policy.
* A key expression is internal if we can sign for it (either as an individual key,
* or as part of a MuSig key expression).
*/
#define MAX_INTERNAL_KEY_EXPRESSIONS 8

// SIGHASH flags
#define SIGHASH_DEFAULT 0x00000000
#define SIGHASH_ALL 0x00000001
63 changes: 18 additions & 45 deletions src/crypto.c
Original file line number Diff line number Diff line change
@@ -39,44 +39,12 @@

#include "crypto.h"

/**
* Generator for secp256k1, value 'g' defined in "Standards for Efficient Cryptography"
* (SEC2) 2.7.1.
*/
// clang-format off
static const uint8_t secp256k1_generator[] = {
0x04,
0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07,
0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98,
0x48, 0x3A, 0xDA, 0x77, 0x26, 0xA3, 0xC4, 0x65, 0x5D, 0xA4, 0xFB, 0xFC, 0x0E, 0x11, 0x08, 0xA8,
0xFD, 0x17, 0xB4, 0x48, 0xA6, 0x85, 0x54, 0x19, 0x9C, 0x47, 0xD0, 0x8F, 0xFB, 0x10, 0xD4, 0xB8};
// clang-format on

/**
* Modulo for secp256k1
*/
static const uint8_t secp256k1_p[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2f};

/**
* Curve order for secp256k1
*/
static const uint8_t secp256k1_n[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41};

/**
* (p + 1)/4, used to calculate square roots in secp256k1
*/
static const uint8_t secp256k1_sqr_exponent[] = {
0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0x0c};
#include "secp256k1.h"

/* BIP0341 tags for computing the tagged hashes when tweaking public keys */
static const uint8_t BIP0341_taptweak_tag[] = {'T', 'a', 'p', 'T', 'w', 'e', 'a', 'k'};
static const uint8_t BIP0341_tapbranch_tag[] = {'T', 'a', 'p', 'B', 'r', 'a', 'n', 'c', 'h'};
static const uint8_t BIP0341_tapleaf_tag[] = {'T', 'a', 'p', 'L', 'e', 'a', 'f'};
const uint8_t BIP0341_taptweak_tag[] = {'T', 'a', 'p', 'T', 'w', 'e', 'a', 'k'};
const uint8_t BIP0341_tapbranch_tag[] = {'T', 'a', 'p', 'B', 'r', 'a', 'n', 'c', 'h'};
const uint8_t BIP0341_tapleaf_tag[] = {'T', 'a', 'p', 'L', 'e', 'a', 'f'};

// Copy of cx_ecfp_scalar_mult_no_throw, but without using randomization for the scalar
// multiplication. Therefore, it is faster, but not safe to use on private data, as it is vulnerable
@@ -122,7 +90,8 @@ static int secp256k1_point_unsafe(const uint8_t k[static 32], uint8_t out[static

int bip32_CKDpub(const serialized_extended_pubkey_t *parent,
uint32_t index,
serialized_extended_pubkey_t *child) {
serialized_extended_pubkey_t *child,
uint8_t *tweak) {
PRINT_STACK_POINTER();

if (index >= BIP32_FIRST_HARDENED_CHILD) {
@@ -147,6 +116,10 @@ int bip32_CKDpub(const serialized_extended_pubkey_t *parent,
uint8_t *I_L = &I[0];
uint8_t *I_R = &I[32];

if (tweak != NULL) {
memcpy(tweak, I_L, 32);
}

// fail if I_L is not smaller than the group order n, but the probability is < 1/2^128
int diff;
if (CX_OK != cx_math_cmp_no_throw(I_L, secp256k1_n, 32, &diff) || diff >= 0) {
@@ -466,7 +439,7 @@ void crypto_tr_tapleaf_hash_init(cx_sha256_t *hash_context) {
crypto_tr_tagged_hash_init(hash_context, BIP0341_tapleaf_tag, sizeof(BIP0341_tapleaf_tag));
}

static int crypto_tr_lift_x(const uint8_t x[static 32], uint8_t out[static 65]) {
int crypto_tr_lift_x(const uint8_t x[static 32], uint8_t out[static 65]) {
// save memory by reusing output buffer for intermediate results
uint8_t *y = out + 1 + 32;
// we use the memory for the x-coordinate of the output as a temporary variable
@@ -505,13 +478,13 @@ static int crypto_tr_lift_x(const uint8_t x[static 32], uint8_t out[static 65])

// Computes a tagged hash according to BIP-340.
// If data2_len > 0, then data2 must be non-NULL and the `data` and `data2` arrays are concatenated.
static void crypto_tr_tagged_hash(const uint8_t *tag,
uint16_t tag_len,
const uint8_t *data,
uint16_t data_len,
const uint8_t *data2,
uint16_t data2_len,
uint8_t out[static CX_SHA256_SIZE]) {
void crypto_tr_tagged_hash(const uint8_t *tag,
uint16_t tag_len,
const uint8_t *data,
uint16_t data_len,
const uint8_t *data2,
uint16_t data2_len,
uint8_t out[static CX_SHA256_SIZE]) {
// First compute hashtag, reuse out buffer for that
cx_sha256_hash(tag, tag_len, out);

50 changes: 48 additions & 2 deletions src/crypto.h
Original file line number Diff line number Diff line change
@@ -36,18 +36,22 @@ typedef struct {
*
* @param[in] parent
* Pointer to the extended serialized pubkey of the parent.
* @param[out] index
* @param[in] index
* Index of the child to derive. It MUST be not hardened, that is, strictly less than 0x80000000.
* @param[out] child
* Pointer to the output struct for the child's serialized pubkey. It can equal parent, which in
* that case is overwritten.
* @param[out] tweak
* If not NULL, pointer to a 32-byte array that will receive the 32-byte tweak used during the
* child key derivation.
*
* @return 0 if success, a negative number on failure.
*
*/
int bip32_CKDpub(const serialized_extended_pubkey_t *parent,
uint32_t index,
serialized_extended_pubkey_t *child);
serialized_extended_pubkey_t *child,
uint8_t *tweak);

/**
* Convenience wrapper for cx_hash_no_throw to add some data to an initialized hash context.
@@ -331,6 +335,11 @@ int crypto_ecdsa_sign_sha256_hash_with_key(const uint32_t bip32_path[],
uint8_t out[static MAX_DER_SIG_LEN],
uint32_t *info);

// Constants defined in BIP-0341
extern const uint8_t BIP0341_taptweak_tag[8];
extern const uint8_t BIP0341_tapbranch_tag[9];
extern const uint8_t BIP0341_tapleaf_tag[7];

/**
* Initializes the "tagged" SHA256 hash with the given tag, as defined by BIP-0340.
*
@@ -343,6 +352,43 @@ int crypto_ecdsa_sign_sha256_hash_with_key(const uint32_t bip32_path[],
*/
void crypto_tr_tagged_hash_init(cx_sha256_t *hash_context, const uint8_t *tag, uint16_t tag_len);

/**
* Implementation of the lift_x procedure as defined by BIP-0340.
*
* @param[in] x
* Pointer to a 32-byte array.
* @param[out] out
* Pointer to an array that will received the output as an uncompressed 65-bytes pubkey.
*/
int crypto_tr_lift_x(const uint8_t x[static 32], uint8_t out[static 65]);

/**
* A tagged hash as defined in BIP-0340.
*
* @param[in] tag
* Pointer to an array containing the tag of the tagged hash.
* @param[in] tag_len
* Length of the tag.
* @param[in] data
* Pointer to an array of data.
* @param[in] data_len
* Length of the array pointed by `data`.
* @param[in] data2
* If NULL, ignored. If not null, a pointer to an array of data; the tagged hash for the
* concatenation of `data` and `data2` is computed.
* @param[in] data2_len
* If `data2` is NULL, ignored. Otherwise, the length the array pointed by `data2`.
* @param[out] out
* Pointer to a 32-byte array that will receive the result.
*/
void crypto_tr_tagged_hash(const uint8_t *tag,
uint16_t tag_len,
const uint8_t *data,
uint16_t data_len,
const uint8_t *data2,
uint16_t data2_len,
uint8_t out[static CX_SHA256_SIZE]);

/**
* Initializes the "tagged" SHA256 hash with tag "TapLeaf", used for tapscript leaves.
*
3 changes: 3 additions & 0 deletions src/error_codes.h
Original file line number Diff line number Diff line change
@@ -56,6 +56,9 @@
// The redeem Script in the PSBT is incorrect.
#define EC_SIGN_PSBT_MISMATCHING_REDEEM_SCRIPT 0x000b

// The wallet policy has too many internal keys.
#define EC_SIGN_PSBT_WALLET_POLICY_TOO_MANY_INTERNAL_KEYS 0x000c

/**
* Swap
*/
405 changes: 262 additions & 143 deletions src/handler/lib/policy.c

Large diffs are not rendered by default.

50 changes: 34 additions & 16 deletions src/handler/lib/policy.h
Original file line number Diff line number Diff line change
@@ -46,13 +46,31 @@ typedef struct {
// WALLET_POLICY_VERSION_V2
const uint8_t
*keys_merkle_root; // The Merkle root of the tree of key informations in the policy
uint32_t n_keys; // The number of key information placeholders in the policy
uint32_t n_keys; // The number of key information elements in the policy
size_t address_index; // The address index to use in the derivation
bool change; // whether a change address or a receive address is derived
sign_psbt_cache_t
*sign_psbt_cache; // If not NULL, the cache for key derivations used during signing
} wallet_derivation_info_t;

/**
* Requests and parses the serialized extended public key from the client.
*
* @param[in] dispatcher_context Pointer to the dispatcher content
* @param[in] wdi Pointer to a `wallet_derivation_info_t` struct with the details of the
* necessary details of the wallet policy. The change/addr_index pairs are not
* @param[in] key_index Index of the pubkey in the vector of keys of the wallet policy.
* @param[out] out Pointer to a `serialized_extended_pubkey_t` that will contain the requested
* extended pubkey.
*
* @return -1 on error, 0 if the returned key info has no wildcard (**), 1 if it has the wildcard.
*/
__attribute__((warn_unused_result)) int get_extended_pubkey_from_client(
dispatcher_context_t *dispatcher_context,
const wallet_derivation_info_t *wdi,
int key_index,
serialized_extended_pubkey_t *out);

/**
* Computes the hash of a taptree, to be used as tweak for the internal key per BIP-0341;
* The returned hash is the second value in the tuple returned by taproot_tree_helper in
@@ -179,31 +197,31 @@ bool compute_wallet_hmac(const uint8_t wallet_id[static 32], uint8_t wallet_hmac
bool check_wallet_hmac(const uint8_t wallet_id[static 32], const uint8_t wallet_hmac[static 32]);

/**
* Copies the i-th placeholder (indexing from 0) of the given policy into `out_placeholder` (if not
* Copies the i-th key expression (indexing from 0) of the given policy into `out_keyexpr` (if not
* null).
*
* @param[in] policy
* Pointer to the root node of the policy
* @param[in] i
* Index of the wanted placeholder. Ignored if out_placeholder is NULL.
* Index of the wanted key expression. Ignored if out_keyexpr is NULL.
* @param[out] out_tapleaf_ptr
* If not NULL, and if the i-th placeholder is in a tapleaf of the policy, receives the pointer to
* the tapleaf's script.
* @param[out] out_placeholder
* If not NULL, it is a pointer that will receive the i-th placeholder of the policy.
* @return the number of placeholders in the policy on success; -1 in case of error.
* If not NULL, and if the i-th key expression is in a tapleaf of the policy, receives the pointer
* to the tapleaf's script.
* @param[out] out_keyexpr
* If not NULL, it is a pointer that will receive a pointer to the i-th key expression of the
* policy.
* @return the number of key expressions in the policy on success; -1 in case of error.
*/
__attribute__((warn_unused_result)) int get_key_placeholder_by_index(
const policy_node_t *policy,
unsigned int i,
const policy_node_t **out_tapleaf_ptr,
policy_node_key_placeholder_t *out_placeholder);
__attribute__((warn_unused_result)) int get_keyexpr_by_index(const policy_node_t *policy,
unsigned int i,
const policy_node_t **out_tapleaf_ptr,
policy_node_keyexpr_t **out_keyexpr);

/**
* Determines the expected number of unique keys in the provided policy's key information.
* The function calculates this by finding the maximum key index from placeholders and increments it
* by 1. For instance, if the maximum key index found in the placeholders is `n`, then the result
* would be `n + 1`.
* The function calculates this by finding the maximum key index from key expressions and increments
* it by 1. For instance, if the maximum key index found in the key expressions is `n`, then the
* result would be `n + 1`.
*
* @param[in] policy
* Pointer to the root node of the policy
1,017 changes: 574 additions & 443 deletions src/handler/sign_psbt.c

Large diffs are not rendered by default.

152 changes: 152 additions & 0 deletions src/handler/sign_psbt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#pragma once

#include "../musig/musig_sessions.h"
#include "../common/merkle.h"
#include "../ui/display.h"

// common info that applies to either the current input or the current output
typedef struct {
merkleized_map_commitment_t map;

bool unexpected_pubkey_error; // Set to true if the pubkey in the keydata of
// PSBT_{IN,OUT}_BIP32_DERIVATION or
// PSBT_{IN,OUT}_TAP_BIP32_DERIVATION is not the correct length.

bool key_expression_found; // Set to true if the input/output info in the psbt was correctly
// matched with the current key expression in the signing flow

bool is_change;
int address_index;

// For an output, its scriptPubKey
// for an input, the prevout's scriptPubKey (either from the non-witness-utxo, or from the
// witness-utxo)

uint8_t scriptPubKey[MAX_OUTPUT_SCRIPTPUBKEY_LEN];
size_t scriptPubKey_len;
} in_out_info_t;

typedef struct {
in_out_info_t in_out;
bool has_witnessUtxo;
bool has_nonWitnessUtxo;
bool has_redeemScript;
bool has_sighash_type;

uint64_t prevout_amount; // the value of the prevout of the current input

// we no longer need the script when we compute the taptree hash right before a taproot key-path
// spending; therefore, we reuse the same memory
union {
// the script used when signing, either from the witness utxo or the redeem script
uint8_t script[MAX_PREVOUT_SCRIPTPUBKEY_LEN];
uint8_t taptree_hash[32];
};

size_t script_len;

uint32_t sighash_type;
} input_info_t;

typedef struct {
in_out_info_t in_out;
uint64_t value;
} output_info_t;

typedef struct {
policy_node_keyexpr_t *key_expression_ptr;
// index of this key expression in the descriptor template, in parsing order
int index;
uint32_t fingerprint;

// we only sign for keys expressions for which we find a matching key derivation in the PSBT,
// at least for one of the inputs
bool to_sign;

// info about the internal key of this key expression
// used at signing time to derive the correct key
uint32_t key_derivation[MAX_BIP32_PATH_STEPS];
uint8_t key_derivation_length;

// same as key_derivation_length for internal key
// expressions; 0 for musig, as the key derivation in
// the PSBT use the aggregate key as the root
// used to identify the correct change/address_index from the psbt
uint8_t psbt_root_key_derivation_length;

// the root pubkey of this key expression
serialized_extended_pubkey_t pubkey;
// the pubkey of the internal key of this key expression.
// same as `pubkey` for simple key expressions, but it's the actual
// internal key for musig key expressions
serialized_extended_pubkey_t internal_pubkey;

bool is_tapscript; // true if signing with a BIP342 tapleaf script path spend
// only used for tapscripts
const policy_node_t *tapleaf_ptr;
uint8_t tapleaf_hash[32];
} keyexpr_info_t;

// Cache for partial hashes during signing (avoid quadratic hashing for segwit transactions)
typedef struct tx_hashes_s {
uint8_t sha_prevouts[32];
uint8_t sha_amounts[32];
uint8_t sha_scriptpubkeys[32];
uint8_t sha_sequences[32];
uint8_t sha_outputs[32];
} tx_hashes_t;

// the signing state for the current transaction; it does not contain any per-input state
typedef struct signing_state_s {
tx_hashes_t tx_hashes;
musig_signing_state_t musig;
} signing_state_t;

// We cache the first 2 external outputs; that's needed for the swap checks
// Moreover, this helps the code for the simplified UX for transactions that
// have a single external output.
#define N_CACHED_EXTERNAL_OUTPUTS 2

typedef struct {
uint32_t master_key_fingerprint;
uint32_t tx_version;
uint32_t locktime;

unsigned int n_internal_key_expressions;
keyexpr_info_t internal_key_expressions[MAX_INTERNAL_KEY_EXPRESSIONS];

unsigned int n_inputs;
uint8_t inputs_root[32]; // merkle root of the vector of input maps commitments
unsigned int n_outputs;
uint8_t outputs_root[32]; // merkle root of the vector of output maps commitments

uint64_t inputs_total_amount;

policy_map_wallet_header_t wallet_header;

unsigned int n_external_inputs;
unsigned int n_external_outputs;

// set to true if at least a PSBT_IN_MUSIG2_PUB_NONCE field is present in the PSBT
bool has_musig2_pub_nonces;

// aggregate info on outputs
struct {
uint64_t total_amount; // amount of all the outputs (external + change)
uint64_t change_total_amount; // total amount of all change outputs
int n_change; // count of outputs compatible with change outputs
size_t output_script_lengths[N_CACHED_EXTERNAL_OUTPUTS];
uint8_t output_scripts[N_CACHED_EXTERNAL_OUTPUTS][MAX_OUTPUT_SCRIPTPUBKEY_LEN];
uint64_t output_amounts[N_CACHED_EXTERNAL_OUTPUTS];
} outputs;

bool is_wallet_default;

uint8_t protocol_version;

__attribute__((aligned(4))) uint8_t wallet_policy_map_bytes[MAX_WALLET_POLICY_BYTES];
policy_node_t *wallet_policy_map;

tx_ux_warning_t warnings;

} sign_psbt_state_t;
480 changes: 480 additions & 0 deletions src/handler/sign_psbt/musig_signing.c

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions src/handler/sign_psbt/musig_signing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include <stdint.h>
#include "../common/wallet.h"
#include "../musig/musig.h"
#include "../boilerplate/dispatcher.h"
#include "../sign_psbt.h"

// Struct to hold the info computed for a given input in either of the two rounds
typedef struct {
plain_pk_t keys[MAX_PUBKEYS_PER_MUSIG];
serialized_extended_pubkey_t agg_key_tweaked;
uint8_t psbt_session_id[32];
uint8_t tweaks[3][32]; // 2 or three tweaks
size_t n_tweaks; // always 2 or 3 for supported BIP-388 wallet policies
bool is_xonly[3]; // 2 or 3 elements
} musig_per_input_info_t;

/**
* Computes the MuSig2 per-input, per-key-expression information.
*
* This function calculates the necessary information for each input in the MuSig protocol.
* It is the shared logic that is common between both rounds of the MuSig2 protocol.
*
* Returns true if the computation is successful, false otherwise. In case of failure, it already
* sends an error status word.
*/
bool compute_musig_per_input_info(dispatcher_context_t *dc,
sign_psbt_state_t *st,
signing_state_t *signing_state,
const input_info_t *input,
const keyexpr_info_t *keyexpr_info,
musig_per_input_info_t *out);

/**
* Computes and yields the pubnonce for the current input and placeholder, during Round 1 of the
* MuSig2 protocol.
*
* Returns true if the computation is successful, false otherwise. In case of failure, it already
* sends an error status word.
*/
bool produce_and_yield_pubnonce(dispatcher_context_t *dc,
sign_psbt_state_t *st,
signing_state_t *signing_state,
const keyexpr_info_t *keyexpr_info,
const input_info_t *input,
unsigned int cur_input_index);

/**
* Computes and yields the partial signature for a certain sighash, during Round 2 of the MuSig2
* protocol.
*
* Returns true if the computation is successful, false otherwise. In case of failure, it already
* sends an error status word.
*/
bool sign_sighash_musig_and_yield(dispatcher_context_t *dc,
sign_psbt_state_t *st,
signing_state_t *signing_state,
const keyexpr_info_t *keyexpr_info,
const input_info_t *input,
unsigned int cur_input_index,
uint8_t sighash[static 32]);
24 changes: 11 additions & 13 deletions src/handler/sign_psbt/sign_psbt_cache.c
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
#include "sign_psbt_cache.h"

int derive_first_step_for_pubkey(const serialized_extended_pubkey_t *base_key,
const policy_node_key_placeholder_t *placeholder,
const policy_node_keyexpr_t *keyexpr,
sign_psbt_cache_t *cache,
bool is_change,
serialized_extended_pubkey_t *out_pubkey) {
uint32_t change_step = is_change ? placeholder->num_second : placeholder->num_first;
uint32_t change_step = is_change ? keyexpr->num_second : keyexpr->num_first;

// make sure a cache was provided, and the index is less than the size of the cache
if (placeholder->placeholder_index >= MAX_CACHED_KEY_EXPRESSIONS || !cache) {
if (keyexpr->keyexpr_index >= MAX_CACHED_KEY_EXPRESSIONS || !cache) {
// do not use the cache, derive the key directly
return bip32_CKDpub(base_key, change_step, out_pubkey);
return bip32_CKDpub(base_key, change_step, out_pubkey, NULL);
}

if (!cache->derived_child[placeholder->placeholder_index]
.is_child_pubkey_initialized[is_change]) {
if (!cache->derived_child[keyexpr->keyexpr_index].is_child_pubkey_initialized[is_change]) {
// key not in cache; compute it and store it in the cache
if (0 > bip32_CKDpub(
base_key,
change_step,
&cache->derived_child[placeholder->placeholder_index].child_pubkeys[is_change]))
if (0 > bip32_CKDpub(base_key,
change_step,
&cache->derived_child[keyexpr->keyexpr_index].child_pubkeys[is_change],
NULL))
return -1;

cache->derived_child[placeholder->placeholder_index]
.is_child_pubkey_initialized[is_change] = true;
cache->derived_child[keyexpr->keyexpr_index].is_child_pubkey_initialized[is_change] = true;
}

// now that we are guaranteed that the key is in cache, we just copy it
memcpy(out_pubkey,
&cache->derived_child[placeholder->placeholder_index].child_pubkeys[is_change],
&cache->derived_child[keyexpr->keyexpr_index].child_pubkeys[is_change],
sizeof(serialized_extended_pubkey_t));

return 0;
10 changes: 5 additions & 5 deletions src/handler/sign_psbt/sign_psbt_cache.h
Original file line number Diff line number Diff line change
@@ -39,12 +39,12 @@ number of BIP-32 derivations is cut almost by half when using the cache.
*/

/**
* Derives the first step for a public key in a placeholder, using a precomputed value from the
* Derives the first step for a public key in a key expression, using a precomputed value from the
* cache if available. If the key is not in the cache, it is computed and stored in the cache,
* unless the index is placeholder index is too large.
* unless the key expression index is too large.
*
* @param[in] base_key Pointer to the base serialized extended public key.
* @param[in] placeholder Pointer to the policy node key placeholder, which contains derivation
* @param[in] keyexpr Pointer to the policy node key expression, which contains derivation
* information.
* @param[in] cache Pointer to the cache structure used to store derived child keys.
* @param[in] is_change true if deriving the change address, false otherwise.
@@ -53,7 +53,7 @@ number of BIP-32 derivations is cut almost by half when using the cache.
* @return 0 on success, -1 on failure.
*/
int derive_first_step_for_pubkey(const serialized_extended_pubkey_t *base_key,
const policy_node_key_placeholder_t *placeholder,
const policy_node_keyexpr_t *keyexpr,
sign_psbt_cache_t *cache,
bool is_change,
serialized_extended_pubkey_t *out_pubkey);
serialized_extended_pubkey_t *out_pubkey);
599 changes: 599 additions & 0 deletions src/musig/musig.c

Large diffs are not rendered by default.

134 changes: 134 additions & 0 deletions src/musig/musig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#pragma once

#include <stdint.h>
#include <stddef.h>

#define MUSIG_PUBNONCE_SIZE 66

static uint8_t BIP_328_CHAINCODE[32] = {
0x86, 0x80, 0x87, 0xCA, 0x02, 0xA6, 0xF9, 0x74, 0xC4, 0x59, 0x89, 0x24, 0xC3, 0x6B, 0x57, 0x76,
0x2D, 0x32, 0xCB, 0x45, 0x71, 0x71, 0x67, 0xE3, 0x00, 0x62, 0x2C, 0x71, 0x67, 0xE3, 0x89, 0x65};

typedef uint8_t plain_pk_t[33];
typedef uint8_t xonly_pk_t[32];

// An uncompressed pubkey, encoded as 04||x||y, where x and y are 32-byte big-endian coordinates.
// If the first byte (prefix) is 0, encodes the point at infinity.
typedef struct {
union {
uint8_t raw[65];
struct {
uint8_t prefix; // 0 for the point at infinity, otherwise 4.
uint8_t x[32];
uint8_t y[32];
};
};
} point_t;

typedef struct musig_keyagg_context_s {
point_t Q;
uint8_t gacc[32];
uint8_t tacc[32];
} musig_keyagg_context_t;

typedef struct musig_secnonce_s {
uint8_t k_1[32];
uint8_t k_2[32];
uint8_t pk[33];
} musig_secnonce_t;

typedef struct musig_pubnonce_s {
union {
struct {
uint8_t R_s1[33];
uint8_t R_s2[33];
};
uint8_t raw[66];
};
} musig_pubnonce_t;

typedef struct musig_session_context_s {
musig_pubnonce_t *aggnonce;
size_t n_keys;
plain_pk_t *pubkeys;
size_t n_tweaks;
uint8_t **tweaks;
bool *is_xonly;
uint8_t *msg;
size_t msg_len;
} musig_session_context_t;

// Comparator for 33-byte compressed public key, in order to sort according to the KeySort
// algorithm of BIP-327.
static inline int compare_plain_pk(const void *a, const void *b) {
return memcmp(a, b, sizeof(plain_pk_t));
}

/**
* Computes the KeyAgg Context per BIP-0327.
*
* @param[in] pubkeys
* Pointer to a list of pubkeys.
* @param[in] n_keys
* Number of pubkeys.
* @param[out] musig_keyagg_context_t
* Pointer to receive the musig KeyAgg Context.
*
* @return 0 on success, a negative number in case of error.
*/
int musig_key_agg(const plain_pk_t pubkeys[], size_t n_keys, musig_keyagg_context_t *ctx);

/**
* Generates secret and public nonces (round 1 of MuSig per BIP-0327).
*
* @param[in] rand
* The randomness to use.
* @param[in] pk
* The 33-byte public key of the signer.
* @param[in] aggpk
* The 32-byte x-only aggregate public key.
* @param[out] secnonce
* Pointer to receive the secret nonce.
* @param[out] pubnonce
* Pointer to receive the public nonce.
*
* @return 0 on success, a negative number in case of error.
*/
int musig_nonce_gen(const uint8_t rand[32],
const plain_pk_t pk,
const xonly_pk_t aggpk,
musig_secnonce_t *secnonce,
musig_pubnonce_t *pubnonce);

/**
* Generates the aggregate nonce (nonce_agg in the reference implementation).
*
* @param[in] rand
* A list of musig_pubnonce_t, the pubnonces of all the participants.
* @param[in] n_keys
* Number of pubkeys.
* @param[out] out
* Pointer to receive the aggregate nonce.
*
* @return 0 on success, a negative number in case of error. On error, `-i - 1` is returned if the
* nonce provided by the cosigner with index `i` is invalid, in order to allow blaming for a
* disruptive signer.
*/
int musig_nonce_agg(const musig_pubnonce_t pubnonces[], size_t n_keys, musig_pubnonce_t *out);

/**
* Computes the partial signature (round 2 of MuSig per BIP-0327).
*
* @param[in] secnonce
* The secret nonce.
* @param[in] session_ctx
* The session context.
* @param[out] psig
* Pointer to receive the partial signature.
*
* @return 0 on success, a negative number in case of error.
*/
int musig_sign(musig_secnonce_t *secnonce,
const uint8_t sk[static 32],
const musig_session_context_t *session_ctx,
uint8_t psig[static 32]);
144 changes: 144 additions & 0 deletions src/musig/musig_sessions.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include <string.h>

#include "cx.h"

#include "musig_sessions.h"
#include "../crypto.h"

typedef struct {
// Aligning by 4 is necessary due to platform limitations.
// Aligning by 64 further guarantees that each session occupies exactly
// a single NVRAM page, minimizing the number of writes.
__attribute__((aligned(64))) musig_psbt_session_t sessions[MAX_N_MUSIG_SESSIONS];
} musig_persistent_storage_t;

const musig_persistent_storage_t N_musig_storage_real;
#define N_musig_storage (*(const volatile musig_persistent_storage_t *) PIC(&N_musig_storage_real))

static bool is_all_zeros(const uint8_t *array, size_t size) {
for (size_t i = 0; i < size; ++i) {
if (array[i] != 0) {
return false;
}
}
return true;
}

static bool musigsession_pop(const uint8_t psbt_session_id[static 32], musig_psbt_session_t *out) {
for (int i = 0; i < MAX_N_MUSIG_SESSIONS; i++) {
if (memcmp(psbt_session_id, (const void *) N_musig_storage.sessions[i]._id, 32) == 0) {
if (out != NULL) {
memcpy(out,
(const void *) &N_musig_storage.sessions[i],
sizeof(musig_psbt_session_t));
}
uint8_t zeros[sizeof(musig_psbt_session_t)] = {0};
nvm_write((void *) &N_musig_storage.sessions[i],
(void *) zeros,
sizeof(musig_psbt_session_t));

return true;
}
}
return false;
}

static void musigsession_init_randomness(musig_psbt_session_t *session) {
// it is extremely important that the randomness is initialized with a cryptographically strong
// random number generator
cx_get_random_bytes(session->_rand_root, 32);
}

static void musigsession_store(const uint8_t psbt_session_id[static 32],
const musig_psbt_session_t *session) {
// make sure that no session with the same id exists; delete it otherwise
musigsession_pop(psbt_session_id, NULL);

int i;
for (i = 0; i < MAX_N_MUSIG_SESSIONS; i++) {
if (is_all_zeros((uint8_t *) &N_musig_storage.sessions[i], sizeof(musig_psbt_session_t))) {
break;
}
}
if (i >= MAX_N_MUSIG_SESSIONS) {
// no free slot found, delete the first by default
// TODO: should we use a LIFO structure? Could add a counter to musig_psbt_session_t
i = 0;
}
// replace the chosen slot
nvm_write((void *) &N_musig_storage.sessions[i],
(void *) session,
sizeof(musig_psbt_session_t));
}

void compute_rand_i_j(const musig_psbt_session_t *psbt_session,
int i,
int j,
uint8_t out[static 32]) {
// It is extremely important that different choices of the root of randomness, i and j always
// produce a different result in out.
// Failure would be catastrophic as it would cause nonce reuse, which in MuSig2 allows attackers
// to recover the private key.

cx_sha256_t hash_context;
cx_sha256_init(&hash_context);
crypto_hash_update(&hash_context.header, psbt_session->_rand_root, CX_SHA256_SIZE);
crypto_hash_update_u32(&hash_context.header, (uint32_t) i);
crypto_hash_update_u32(&hash_context.header, (uint32_t) j);
crypto_hash_digest(&hash_context.header, out, 32);
}

void musigsession_initialize_signing_state(musig_signing_state_t *musig_signing_state) {
memset(musig_signing_state, 0, sizeof(musig_signing_state_t));
}

const musig_psbt_session_t *musigsession_round1_initialize(
uint8_t psbt_session_id[static 32],
musig_signing_state_t *musig_signing_state) {
// if an existing session for psbt_session_id exists, delete it
if (musigsession_pop(psbt_session_id, NULL)) {
// We wouldn't expect this: probably the client sent the same psbt for
// round 1 twice, without adding the pubnonces to the psbt after the first round.
// We delete the old session and start a fresh one, but we print a
// warning if in debug mode.
PRINTF("Session with the same id already existing\n");
}

if (memcmp(musig_signing_state->_round1._id, psbt_session_id, 32) != 0) {
// first input/placeholder pair using this session: initialize the session
memcpy(musig_signing_state->_round1._id, psbt_session_id, 32);
musigsession_init_randomness(&musig_signing_state->_round1);
}

return &musig_signing_state->_round1;
}

const musig_psbt_session_t *musigsession_round2_initialize(
uint8_t psbt_session_id[static 32],
musig_signing_state_t *musig_signing_state) {
if (memcmp(musig_signing_state->_round2._id, psbt_session_id, 32) != 0) {
// get and delete the musig session from permanent storage
if (!musigsession_pop(psbt_session_id, &musig_signing_state->_round2)) {
// The PSBT contains a partial nonce, but we do not have the corresponding psbt
// session in storage. Either it was deleted, or the pubnonces were not real. Either
// way, we cannot continue.
PRINTF("Missing MuSig2 session\n");
return NULL;
}
}

return &musig_signing_state->_round2;
}

void musigsession_commit(musig_signing_state_t *musig_signing_state) {
uint8_t acc = 0;
for (size_t i = 0; i < sizeof(musig_signing_state->_round1); i++) {
acc |= musig_signing_state->_round1._id[i];
}
// If round 1 was not executed, then there is nothing to store.
// This assumes that musigsession_initialize_signing_state, therefore the field is zeroed out
// if it wasn't used.
if (acc != 0) {
musigsession_store(musig_signing_state->_round1._id, &musig_signing_state->_round1);
}
}
100 changes: 100 additions & 0 deletions src/musig/musig_sessions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#pragma once

#include <stdbool.h>
#include "musig.h"

/**
* This module encapsulates the logic to manage the psbt-level MuSig2 sessions. See the
* documentation in docs/musig.md for more information.
*/

// the maximum number of musig sessions that are stored in permanent memory
#define MAX_N_MUSIG_SESSIONS 8

// state of a musig_psbt_session. Members are private and must not be accessed directly by any
// code outside of musig_sessions.c.
typedef struct musig_psbt_session_s {
uint8_t _id[32];
uint8_t _rand_root[32];
} musig_psbt_session_t;

// volatile state for musig signing. Members are private and must not be accessed directly by any
// code outside of musig_sessions.c.
typedef struct musig_signing_state_s {
// a session created during round 1; if signing completes (and in no other case), it is moved to
// the persistent storage
musig_psbt_session_t _round1;
// a session that was removed from the persistent storage before any partial signature is
// returned during round 2. It is deleted at the end of signing, and must _never_ be used again.
musig_psbt_session_t _round2;
} musig_signing_state_t;

/**
* Given a musig psbt session, computes the synthetic randomness for a given
* (input_index, placeholder_index) pair.
*/
void compute_rand_i_j(const musig_psbt_session_t *psbt_session,
int input_index,
int placeholder_index,
uint8_t out[static 32]);

/**
* Make sure that the musig signing state is initialized correctly.
*
* This method must be called before musigsession_round1_initialize or
* musigsession_round2_initialize are called in the code.
*
* This allows the calling code to not make any assumption about how
* the inialization of the musig signing state is done.
*
* @param[in] musig_signing_state
* Pointer to the musig signing state.
*/
void musigsession_initialize_signing_state(musig_signing_state_t *musig_signing_state);

/**
* Handles the creation of a new musig psbt session into the volatile memory, or its retrieval (if
* the session already exists).
* It must be called when starting MuSig2 round 1 for a fixed input/placeholder pair, during the
* signing process.
*
* @param[in] psbt_session_id
* Pointer to the musig psbt session id.
* @param[in] musig_signing_state
* Pointer to the musig signing state.
*
* @return a musig_psbt_session_t on success, NULL on failure.
*/
__attribute__((warn_unused_result)) const musig_psbt_session_t *musigsession_round1_initialize(
uint8_t psbt_session_id[static 32],
musig_signing_state_t *musig_signing_state);

/**
* Handles the retrieval of a musig psbt session from volatile memory (if it exists already) or its
* retrieval from the persistent memory otherwise. The session is guaranteed to be deleted from the
* persistent memory prior to returning.
* It must be called when starting MuSig2 round 2 for a fixed input/placeholder pair, during the
* signing process.
*
* @param[in] psbt_session_id
* Pointer to the musig psbt session id.
* @param[in] musig_signing_state
* Pointer to the musig signing state.
*
* @return a musig_psbt_session_t on success, NULL on failure.
*/
__attribute__((warn_unused_result)) const musig_psbt_session_t *musigsession_round2_initialize(
uint8_t psbt_session_id[static 32],
musig_signing_state_t *musig_signing_state);

/**
* If a session produced in round 1 is active in volatile memory, it is stored in the persistent
* memory.
* This must be called at the end of a successful signing flow, after all the public nonces have
* been returned to the client. It must _not_ be called if any error occurs, or if the signing
* process is aborted for any reason.
*
* @param[in] musig_signing_state
* Pointer to the musig signing state.
*/
void musigsession_commit(musig_signing_state_t *musig_signing_state);
23 changes: 23 additions & 0 deletions src/secp256k1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "secp256k1.h"

// clang-format off
const uint8_t secp256k1_generator[65] = {
0x04,
0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07,
0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98,
0x48, 0x3A, 0xDA, 0x77, 0x26, 0xA3, 0xC4, 0x65, 0x5D, 0xA4, 0xFB, 0xFC, 0x0E, 0x11, 0x08, 0xA8,
0xFD, 0x17, 0xB4, 0x48, 0xA6, 0x85, 0x54, 0x19, 0x9C, 0x47, 0xD0, 0x8F, 0xFB, 0x10, 0xD4, 0xB8};

const uint8_t secp256k1_p[32] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2f};

const uint8_t secp256k1_n[32] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41};

const uint8_t secp256k1_sqr_exponent[32] = {
0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0x0c};

// clang-format on
24 changes: 24 additions & 0 deletions src/secp256k1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include <stdint.h>

/**
* Generator for secp256k1, value 'g' defined in "Standards for Efficient Cryptography"
* (SEC2) 2.7.1.
*/
extern const uint8_t secp256k1_generator[65];

/**
* Modulo for secp256k1
*/
extern const uint8_t secp256k1_p[32];

/**
* Curve order for secp256k1
*/
extern const uint8_t secp256k1_n[32];

/**
* (p + 1)/4, used to calculate square roots in secp256k1
*/
extern const uint8_t secp256k1_sqr_exponent[32];
40 changes: 33 additions & 7 deletions test_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import re

import hashlib
from typing import Literal, Union

from mnemonic import Mnemonic
from bip32 import BIP32

from bitcoin_client.ledger_bitcoin.wallet import WalletPolicy
from bitcoin_client.ledger_bitcoin.wallet import WalletPolicy, WalletType

from .slip21 import Slip21Node

@@ -89,14 +91,16 @@ def get_internal_xpub(seed: str, path: str) -> str:
return bip32.get_xpub_from_path(f"m/{path}") if path else bip32.get_xpub_from_path("m")


def count_internal_keys(seed: str, network: Union[Literal['main'], Literal['test']], wallet_policy: WalletPolicy) -> int:
"""Count how many of the keys in wallet_policy are indeed internal"""
def count_internal_key_placeholders(seed: str, network: Union[Literal['main'], Literal['test']], wallet_policy: WalletPolicy, *, only_musig=False) -> int:
"""Count how many of the key placeholders in wallet_policy are indeed internal.
musig() placeholders are counted as many times as there are internal keys in them."""

bip32 = BIP32.from_seed(seed, network)
master_key_fingerprint = hash160(bip32.pubkey)[0:4]

count = 0
is_key_internal = []
for key_index, key_info in enumerate(wallet_policy.keys_info):
is_this_key_internal = False
if "]" in key_info:
key_orig_end_pos = key_info.index("]")
fpr = key_info[1:9]
@@ -110,8 +114,30 @@ def count_internal_keys(seed: str, network: Union[Literal['main'], Literal['test
if fpr == master_key_fingerprint.hex():
computed_xpub = get_internal_xpub(seed, path)
if computed_xpub == xpub:
# there could be multiple placeholders using the same key; we must count all of them
count += wallet_policy.descriptor_template.count(
f"@{key_index}/")
is_this_key_internal = True
is_key_internal.append(is_this_key_internal)

# enumerate all the key placeholders
# for simplicity, we look for all the following patterns using regular expressions:
# - Simple keys: @<key_index>/ (always with additional derivations, hence the final '/')
# - Musig expressions: musig(@k1, @k2, ...)

count = 0

if not only_musig:
simple_key_placeholders = re.findall(
r'@(\d+)/', wallet_policy.descriptor_template)
# for each match, count it if the corresponding key is internal
for key_index in simple_key_placeholders:
if is_key_internal[int(key_index)]:
count += 1

if wallet_policy.version != WalletType.WALLET_POLICY_V1: # no musig in V1 policies
musig_key_placeholders = re.findall(
r'musig\(([^)]*)\)', wallet_policy.descriptor_template)
for musig_expr in musig_key_placeholders:
musig_keys_indices = [int(k[1:]) for k in musig_expr.split(",")]
# We count each musig placeholder as many times are there are internal keys in it
count += sum(int(is_key_internal[k]) for k in musig_keys_indices)

return count
Loading

0 comments on commit 24bcdae

Please sign in to comment.