diff --git a/CHANGES.md b/CHANGES.md index 35de08639..33a21b6f4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,37 @@ # Changes +## Version 1.5.5 + +### Added +- descriptor: Add taproot (tapscript) script-path support: tr() taptrees, + multi_a/sortedmulti_a fragments, BIP-341 leaf/merkle hashing, and the + tree/leaf/control-block accessor APIs. +- descriptor: Add a Script-to-miniscript decoder (BIP-379). +- miniscript: Add a witness satisfier producing non-malleable, minimum-weight witnesses. +- psbt: Add BIP-371 taproot PSBT fields (internal key, leaf scripts, merkle root, + tap bip32 derivation), script-path signing, and the taproot finalizers. +- musig: Add MuSig2 (BIP-327/328/373/390) support: + - New `wally_musig.h` API with six opaque types: `wally_musig_keyagg_cache`, + `wally_musig_secnonce`, `wally_musig_pubnonce`, `wally_musig_aggnonce`, + `wally_musig_session`, `wally_musig_partial_sig` + - Key aggregation: `wally_musig_pubkey_agg`, `wally_musig_pubkey_get`, + `wally_musig_pubkey_ec_tweak_add`, `wally_musig_pubkey_xonly_tweak_add` + - BIP-328 synthetic xpub: `wally_musig_pubkey_to_xpub` enabling BIP-32 + derivation from aggregate keys; `wally_musig_pubkeys_agg_then_derive`, + `wally_musig_pubkeys_derive_then_agg` for combined aggregate-and-derive flows + - Nonce generation: `wally_musig_nonce_gen`, `wally_musig_nonce_gen_counter`, + `wally_musig_nonce_agg` + - Signing: `wally_musig_nonce_process`, `wally_musig_partial_sign`, + `wally_musig_partial_sig_verify`, `wally_musig_partial_sig_agg` + - Serialization helpers for all six opaque types + - BIP-390 `musig()` key expression in `wally_descriptor_parse()`: + parse and evaluate `tr(musig(xpub1,xpub2)/<0;1>/*)` descriptors + - BIP-373 PSBT MuSig2 fields: participant pubkeys, pubnonces, partial sigs; + high-level helpers `wally_psbt_musig2_add_nonce`, `wally_psbt_musig2_sign`, + `wally_psbt_musig2_finalize_input` + - Python, Java/JNI, and JavaScript/WASM bindings for all MuSig2 functions + - Requires secp256k1-zkp with MuSig2 module (guarded by `BUILD_STANDARD_SECP`) + ## Version 1.5.4 ### Added diff --git a/configure.ac b/configure.ac index 9dd4fa54e..57d43c5c3 100644 --- a/configure.ac +++ b/configure.ac @@ -484,7 +484,7 @@ export LD export LDFLAGS AM_COND_IF([LINK_SYSTEM_SECP256K1], [], [ - AX_SUBDIRS_CONFIGURE([src/secp256k1], [[--disable-shared], [--enable-static], [--with-pic], [--enable-experimental], [--enable-module-ecdh], [--enable-module-recovery], [--enable-module-extrakeys], [--enable-module-schnorrsig], [--enable-module-generator], [--enable-module-rangeproof], [--enable-module-surjectionproof], [--enable-module-whitelist], [--enable-module-ecdsa-s2c], [$secp256k1_test_opt], [--enable-exhaustive-tests=no], [--enable-benchmark=no], [--disable-dependency-tracking], [$secp_asm]]) + AX_SUBDIRS_CONFIGURE([src/secp256k1], [[--disable-shared], [--enable-static], [--with-pic], [--enable-experimental], [--enable-module-ecdh], [--enable-module-recovery], [--enable-module-extrakeys], [--enable-module-schnorrsig], [--enable-module-musig], [--enable-module-generator], [--enable-module-rangeproof], [--enable-module-surjectionproof], [--enable-module-whitelist], [--enable-module-ecdsa-s2c], [$secp256k1_test_opt], [--enable-exhaustive-tests=no], [--enable-benchmark=no], [--disable-dependency-tracking], [$secp_asm]]) ]) AC_OUTPUT diff --git a/contrib/musig2_psbt_2of2.py b/contrib/musig2_psbt_2of2.py new file mode 100644 index 000000000..aa145de5c --- /dev/null +++ b/contrib/musig2_psbt_2of2.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +2-of-2 MuSig2 PSBT signing example (BIP-327/373) + +Demonstrates the full two-round MuSig2 signing workflow: + 1. Key aggregation (signer 1 and signer 2 combine pubkeys) + 2. PSBT creation with P2TR output locked to the aggregate key + 3. Round 1: nonce generation and injection into the PSBT + 4. Round 2: partial signing by each participant + 5. Finalization: partial sigs aggregated into a 64-byte Schnorr sig + 6. Final PSBT verification + +Run with: + LD_LIBRARY_PATH=src/.libs PYTHONPATH=src/swig_python python3 contrib/musig2_psbt_2of2.py +""" +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src', 'swig_python')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src', 'test')) + +from ctypes import * +from util import * + +# ── Constants ──────────────────────────────────────────────────────────────── + +EC_PUBLIC_KEY_LEN = 33 +EC_XONLY_PUBLIC_KEY_LEN = 32 +EC_SIGNATURE_LEN = 64 +EC_FLAG_SCHNORR = 0x2 +WALLY_SIGHASH_DEFAULT = 0x00 +BIP32_VER_MAIN_PUBLIC = 0x0488B21E + +# Two participant secret keys (for example use only — never hardcode in production!) +SECKEY1 = bytes([0x01] * 32) +SECKEY2 = bytes([0x02] * 32) + + +def derive_pubkey(seckey): + """Derive the compressed 33-byte pubkey from a 32-byte secret key.""" + pub, pub_len = make_cbuffer('00' * EC_PUBLIC_KEY_LEN) + ret = wally_ec_public_key_from_private_key(seckey, len(seckey), pub, pub_len) + assert ret == WALLY_OK, 'derive_pubkey failed' + return bytes(pub) + + +def main(): + # ── Step 1: Key Aggregation ─────────────────────────────────────────────── + pk1 = derive_pubkey(SECKEY1) + pk2 = derive_pubkey(SECKEY2) + pub_keys_flat = pk1 + pk2 # concatenated compressed pubkeys + + agg_pk_xonly, _ = make_cbuffer('00' * EC_XONLY_PUBLIC_KEY_LEN) + cache = c_void_p() + ret = wally_musig_pubkey_agg(pub_keys_flat, len(pub_keys_flat), + agg_pk_xonly, EC_XONLY_PUBLIC_KEY_LEN, cache) + assert ret == WALLY_OK, 'key aggregation failed' + assert cache.value is not None + print(f'Aggregate x-only pubkey: {bytes(agg_pk_xonly).hex()}') + + # The PSBT stores participant keys under the compressed (33-byte) agg pubkey + agg_pubkey = bytes([0x02]) + bytes(agg_pk_xonly) + agg_pubkey_buf, _ = make_cbuffer(agg_pubkey.hex()) + + # ── Step 2: Build PSBT with a P2TR input ───────────────────────────────── + # Build the P2TR scriptpubkey. Passing the 33-byte COMPRESSED aggregate + # (internal) key makes wally apply the BIP-341 key-path output tweak, so the + # coin is locked to the standard taproot output key Q = P + H_TapTweak(P)*G + # (NOT the raw aggregate key P). The PSBT musig signing flow re-applies the + # same tweak internally so the aggregated signature is valid under Q. + p2tr_buf, _ = make_cbuffer('00' * 34) + ret, p2tr_written = wally_scriptpubkey_p2tr_from_bytes( + agg_pubkey, EC_PUBLIC_KEY_LEN, 0, p2tr_buf, 34) + assert ret == WALLY_OK, 'P2TR scriptpubkey creation failed' + p2tr_bytes = bytes(p2tr_buf[:p2tr_written]) + + # Create PSBT v2 with 1 input and 1 output + psbt = pointer(wally_psbt()) + assert wally_psbt_init_alloc(2, 1, 1, 0, 0, psbt) == WALLY_OK + + # Add a dummy input (txid=0..0, vout=0) + tx_in = pointer(wally_tx_input()) + assert wally_psbt_add_tx_input_at(psbt, 0, 0, tx_in) == WALLY_OK + + # Add a dummy P2WPKH output (recipient) + tx_output = pointer(wally_tx_output()) + assert wally_tx_output_init_alloc( + 1000, b'\x00\x14' + b'\xab' * 20, 22, tx_output) == WALLY_OK + assert wally_psbt_add_tx_output_at(psbt, 0, 0, tx_output) == WALLY_OK + + # Set the UTXO being spent (P2TR output, 200,000 sat) + utxo = pointer(wally_tx_output()) + assert wally_tx_output_init_alloc( + 200000, p2tr_bytes, len(p2tr_bytes), utxo) == WALLY_OK + assert wally_psbt_set_input_witness_utxo(psbt, 0, utxo) == WALLY_OK + assert wally_psbt_set_input_amount(psbt, 0, 200000) == WALLY_OK + + # Record the taproot internal key (x-only) so sighash knows the script tree + assert wally_psbt_set_input_taproot_internal_key( + psbt, 0, agg_pk_xonly, EC_XONLY_PUBLIC_KEY_LEN) == WALLY_OK + + # Register both participant pubkeys in the PSBT under the aggregate key. + # This is the BIP-373 MUSIG2_PARTICIPANT_PUBKEYS field. + participants_flat = pk1 + pk2 + ret = wally_psbt_input_add_musig2_participant_pubkeys( + psbt.contents.inputs, + agg_pubkey_buf, EC_PUBLIC_KEY_LEN, + participants_flat, len(participants_flat)) + assert ret == WALLY_OK, 'registering participant pubkeys failed' + + print('PSBT created with P2TR input') + + # ── Step 3: Round 1 — Nonce Generation ─────────────────────────────────── + # Each signer independently generates a (secnonce, pubnonce) pair using a + # unique session random value. wally_psbt_musig2_add_nonce stores the + # pubnonce in the PSBT and returns the secnonce to the caller. + # Each signer MUST use unique, cryptographically secure randomness here and + # MUST NOT reuse it across signing sessions (MuSig2 nonce reuse leaks the key). + secrand1, _ = make_cbuffer(os.urandom(32).hex()) + secrand2, _ = make_cbuffer(os.urandom(32).hex()) + sn1 = c_void_p() + sn2 = c_void_p() + + ret = wally_psbt_musig2_add_nonce( + psbt, 0, + secrand1, 32, # unique session random (32 bytes) + None, 0, # optional seckey for binding + pk1, EC_PUBLIC_KEY_LEN, + agg_pubkey_buf, EC_PUBLIC_KEY_LEN, + None, 0, # no tapscript leaf hash (key-path spend) + None, 0, # no external keyagg_cache + byref(sn1)) + assert ret == WALLY_OK, 'participant 1 nonce generation failed' + + ret = wally_psbt_musig2_add_nonce( + psbt, 0, + secrand2, 32, + None, 0, + pk2, EC_PUBLIC_KEY_LEN, + agg_pubkey_buf, EC_PUBLIC_KEY_LEN, + None, 0, None, 0, + byref(sn2)) + assert ret == WALLY_OK, 'participant 2 nonce generation failed' + + print('Round 1 complete: both pubnonces stored in PSBT') + + # ── Step 4: Round 2 — Partial Signing ──────────────────────────────────── + # Each signer produces a partial signature using their secnonce + seckey. + # The keyagg_cache (from step 1) must be the same object for both signers. + seckey1, _ = make_cbuffer(SECKEY1.hex()) + seckey2, _ = make_cbuffer(SECKEY2.hex()) + + ret = wally_psbt_musig2_sign( + psbt, 0, + sn1.value, # secnonce is consumed (zeroed) after this call + seckey1, 32, + pk1, EC_PUBLIC_KEY_LEN, + agg_pubkey_buf, EC_PUBLIC_KEY_LEN, + None, 0, # no tapscript leaf hash + cache.value, 0, # keyagg_cache from step 1 + None) # partial_sig_out (stored in PSBT internally) + assert ret == WALLY_OK, 'participant 1 partial sign failed' + + ret = wally_psbt_musig2_sign( + psbt, 0, + sn2.value, + seckey2, 32, + pk2, EC_PUBLIC_KEY_LEN, + agg_pubkey_buf, EC_PUBLIC_KEY_LEN, + None, 0, + cache.value, 0, + None) + assert ret == WALLY_OK, 'participant 2 partial sign failed' + + print('Round 2 complete: both partial signatures stored in PSBT') + + # ── Step 5: Finalization ────────────────────────────────────────────────── + # wally_psbt_musig2_finalize_input aggregates the two partial signatures + # into a single 64-byte BIP-340 Schnorr signature and writes it as the + # PSBT TAP_KEY_SIG field. The pubnonce and partial sig entries are then + # cleared from the PSBT. + ret = wally_psbt_musig2_finalize_input( + psbt, 0, + agg_pubkey_buf, EC_PUBLIC_KEY_LEN, + None, 0, # no tapscript leaf hash + cache.value, 0) + assert ret == WALLY_OK, 'finalization failed' + + # Read back the aggregated Schnorr signature + sig_buf, _ = make_cbuffer('00' * EC_SIGNATURE_LEN) + ret, sig_written = wally_psbt_get_input_taproot_signature( + psbt, 0, sig_buf, EC_SIGNATURE_LEN) + assert ret == WALLY_OK and sig_written == EC_SIGNATURE_LEN + print(f'Final Schnorr signature ({sig_written} bytes): {bytes(sig_buf).hex()}') + + # ── Step 6: Cryptographic Verification ─────────────────────────────────── + # Verify the signature against the P2TR output key. + # The P2TR scriptpubkey is OP_1 <32-byte-tweaked-output-key>; + # bytes [2:34] are the x-only output key the signature must verify against. + output_xonly_key = p2tr_bytes[2:34] + output_key_buf, _ = make_cbuffer(output_xonly_key.hex()) + + # Build the transaction from PSBT data so we can compute the sighash + tx_pp = POINTER(wally_tx)() + assert wally_tx_init_alloc(2, 0, 1, 1, byref(tx_pp)) == WALLY_OK + zero_txid, _ = make_cbuffer('00' * 32) + assert wally_tx_add_raw_input(tx_pp, zero_txid, 32, 0, 0, None, 0, None, 0) == WALLY_OK + out_script = b'\x00\x14' + b'\xab' * 20 + assert wally_tx_add_raw_output(tx_pp, 1000, out_script, len(out_script), 0) == WALLY_OK + + sighash_buf, _ = make_cbuffer('00' * 32) + ret = wally_psbt_get_input_signature_hash( + psbt, 0, tx_pp, None, 0, WALLY_SIGHASH_DEFAULT, sighash_buf, 32) + assert ret == WALLY_OK, 'sighash computation failed' + + ret = wally_ec_sig_verify( + output_key_buf, EC_XONLY_PUBLIC_KEY_LEN, + sighash_buf, 32, + EC_FLAG_SCHNORR, + sig_buf, EC_SIGNATURE_LEN) + assert ret == WALLY_OK, 'BIP-340 signature verification FAILED' + print('BIP-340 Schnorr signature verified successfully') + + # ── Cleanup ─────────────────────────────────────────────────────────────── + wally_musig_secnonce_free(sn1.value) + wally_musig_secnonce_free(sn2.value) + wally_musig_keyagg_cache_free(cache.value) + wally_tx_free(tx_pp) + wally_psbt_free(psbt) + + print('MuSig2 2-of-2 example complete') + + +if __name__ == '__main__': + main() diff --git a/docs/source/conf.py b/docs/source/conf.py index 72c7af06b..7b63a7226 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -128,7 +128,7 @@ def extract_docs(infile, outfile): for m in [ 'address', 'anti_exfil', 'bip32', 'bip38', 'bip39', 'bip85', 'coinselection', 'core', 'crypto', 'descriptor', 'elements', - 'map', 'psbt', 'script', 'symmetric', 'transaction' + 'map', 'musig', 'psbt', 'script', 'symmetric', 'transaction' ]: extract_docs('../../include/wally_%s.h' % m, '%s.rst' % m) diff --git a/docs/source/index.rst b/docs/source/index.rst index 2348eb154..873a6d83d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,6 +14,7 @@ libwally-core documentation bip85 coinselection map + musig psbt script descriptor @@ -29,6 +30,7 @@ libwally-core documentation Library Conventions Liquid Anti Exfil Protocol + Miniscript Satisfier Indices and tables ================== diff --git a/docs/source/musig.rst b/docs/source/musig.rst new file mode 100644 index 000000000..2ab876ead --- /dev/null +++ b/docs/source/musig.rst @@ -0,0 +1,454 @@ +Musig Functions +=============== + +.. c:function:: int wally_musig_keyagg_cache_free(struct wally_musig_keyagg_cache *cache) + + + Free a keyagg_cache. + + :param cache: The keyagg_cache to free. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_keyagg_cache_serialize(const struct wally_musig_keyagg_cache *cache, unsigned char *bytes_out, size_t len) + + + Serialize a keyagg_cache to its raw 197-byte form. + + :param cache: The keyagg_cache to serialize. + :param bytes_out: 197-byte output buffer. + :param len: Size of ``bytes_out``. Must be `WALLY_MUSIG_KEYAGG_CACHE_LEN`. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_keyagg_cache_parse(const unsigned char *bytes, size_t bytes_len, struct wally_musig_keyagg_cache **output) + + + Restore a keyagg_cache from its raw 197-byte form. + + :param bytes: The 197-byte serialized keyagg_cache. + :param bytes_len: Length of bytes. Must be WALLY_MUSIG_KEYAGG_CACHE_LEN. + :param output: Destination for the allocated keyagg_cache. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_secnonce_free(struct wally_musig_secnonce *nonce) + + + Free a secnonce, securely zeroing it first. + + :param nonce: The secnonce to free. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubnonce_parse(const unsigned char *bytes, size_t bytes_len, struct wally_musig_pubnonce **output) + + + Parse a public nonce from its 66-byte serialized form. + + :param bytes: The 66-byte serialized pubnonce. + :param bytes_len: Length of bytes. Must be WALLY_MUSIG_PUBNONCE_LEN. + :param output: Destination for the allocated pubnonce. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubnonce_serialize(const struct wally_musig_pubnonce *nonce, unsigned char *bytes_out, size_t len) + + + Serialize a public nonce to its 66-byte form. + + :param nonce: The pubnonce to serialize. + :param bytes_out: 66-byte output buffer. + :param len: Size of ``bytes_out``. Must be `WALLY_MUSIG_PUBNONCE_LEN`. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubnonce_free(struct wally_musig_pubnonce *nonce) + + + Free a pubnonce. + + :param nonce: The pubnonce to free. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_aggnonce_parse(const unsigned char *bytes, size_t bytes_len, struct wally_musig_aggnonce **output) + + + Parse an aggregate nonce from its 66-byte serialized form. + + :param bytes: The 66-byte serialized aggnonce. + :param bytes_len: Length of bytes. Must be WALLY_MUSIG_AGGNONCE_LEN. + :param output: Destination for the allocated aggnonce. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_aggnonce_serialize(const struct wally_musig_aggnonce *nonce, unsigned char *bytes_out, size_t len) + + + Serialize an aggregate nonce to its 66-byte form. + + :param nonce: The aggnonce to serialize. + :param bytes_out: 66-byte output buffer. + :param len: Size of ``bytes_out``. Must be `WALLY_MUSIG_AGGNONCE_LEN`. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_aggnonce_free(struct wally_musig_aggnonce *nonce) + + + Free an aggnonce. + + :param nonce: The aggnonce to free. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_session_free(struct wally_musig_session *session) + + + Free a session. + + :param session: The session to free. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_session_serialize(const struct wally_musig_session *session, unsigned char *bytes_out, size_t len) + + + Serialize a session to its raw 133-byte form. + + :param session: The session to serialize. + :param bytes_out: 133-byte output buffer. + :param len: Size of ``bytes_out``. Must be `WALLY_MUSIG_SESSION_LEN`. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_session_parse(const unsigned char *bytes, size_t bytes_len, struct wally_musig_session **output) + + + Restore a session from its raw 133-byte form. + + :param bytes: The 133-byte serialized session. + :param bytes_len: Length of bytes. Must be WALLY_MUSIG_SESSION_LEN. + :param output: Destination for the allocated session. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_partial_sig_parse(const unsigned char *bytes, size_t bytes_len, struct wally_musig_partial_sig **output) + + + Parse a partial signature from its 32-byte serialized form. + + :param bytes: The 32-byte serialized partial signature. + :param bytes_len: Length of bytes. Must be WALLY_MUSIG_PARTIAL_SIG_LEN. + :param output: Destination for the allocated partial_sig. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_partial_sig_serialize(const struct wally_musig_partial_sig *sig, unsigned char *bytes_out, size_t len) + + + Serialize a partial signature to its 32-byte form. + + :param sig: The partial_sig to serialize. + :param bytes_out: 32-byte output buffer. + :param len: Size of ``bytes_out``. Must be `WALLY_MUSIG_PARTIAL_SIG_LEN`. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_partial_sig_free(struct wally_musig_partial_sig *sig) + + + Free a partial signature. + + :param sig: The partial_sig to free. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubkey_agg(const unsigned char *pub_keys, size_t pub_keys_len, unsigned char *agg_pk_out, size_t agg_pk_out_len, struct wally_musig_keyagg_cache **cache_out) + + + Compute the MuSig2 aggregate public key from N individual public keys. + + :param pub_keys: Concatenated array of compressed public keys (each EC_PUBLIC_KEY_LEN bytes). + :param pub_keys_len: Length of pub_keys. Must be a non-zero multiple of EC_PUBLIC_KEY_LEN. + :param agg_pk_out: 32-byte buffer to receive the x-only aggregate public key. May be NULL. + :param agg_pk_out_len: Size of ``agg_pk_out``. Must be `EC_XONLY_PUBLIC_KEY_LEN`. + :param cache_out: Destination for the allocated keyagg_cache (required for signing). May be NULL. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubkey_get(const struct wally_musig_keyagg_cache *cache, unsigned char *pub_key_out, size_t pub_key_out_len) + + + Extract the non-xonly (compressed) aggregate public key from a keyagg_cache. + + :param cache: The keyagg_cache produced by wally_musig_pubkey_agg. + :param pub_key_out: 33-byte buffer to receive the compressed aggregate public key. + :param pub_key_out_len: Size of ``pub_key_out``. Must be `EC_PUBLIC_KEY_LEN`. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubkey_ec_tweak_add(struct wally_musig_keyagg_cache *cache, const unsigned char *tweak, size_t tweak_len, unsigned char *pub_key_out, size_t pub_key_out_len) + + + Apply BIP-32 plain EC tweaking to an aggregate key via the keyagg_cache. + + :param cache: The keyagg_cache to tweak (modified in place). + :param tweak: 32-byte tweak value. + :param tweak_len: Length of tweak. Must be 32. + :param pub_key_out: 33-byte buffer for the tweaked compressed public key. May be NULL. + :param pub_key_out_len: Size of ``pub_key_out``. Must be `EC_PUBLIC_KEY_LEN`. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubkey_xonly_tweak_add(struct wally_musig_keyagg_cache *cache, const unsigned char *tweak, size_t tweak_len, unsigned char *pub_key_out, size_t pub_key_out_len) + + + Apply BIP-341 x-only tweaking to an aggregate key via the keyagg_cache. + + :param cache: The keyagg_cache to tweak (modified in place). + :param tweak: 32-byte tweak value. + :param tweak_len: Length of tweak. Must be 32. + :param pub_key_out: 33-byte buffer for the tweaked compressed public key. May be NULL. + :param pub_key_out_len: Size of ``pub_key_out``. Must be `EC_PUBLIC_KEY_LEN`. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubkey_to_xpub(const unsigned char *agg_pk, size_t agg_pk_len, uint32_t version, struct ext_key **output) + + + Construct a BIP-32 synthetic extended public key from a MuSig2 aggregate + x-only public key, as specified by BIP-328. + + The chain code is the fixed constant SHA256("MuSig2MuSig2MuSig2"). The + resulting ext_key has depth=0, child_num=0, and no parent fingerprint. + Unhardened BIP-32 derivation (bip32_key_from_parent with + BIP32_FLAG_KEY_PUBLIC) is supported on the output key. Hardened derivation + is not possible (no private key). + + :param agg_pk: 32-byte x-only aggregate public key from wally_musig_pubkey_agg. + :param agg_pk_len: Must be EC_XONLY_PUBLIC_KEY_LEN (32). + :param version: BIP-32 version code. Use BIP32_VER_MAIN_PUBLIC or + BIP32_VER_TEST_PUBLIC. + :param output: Destination for the allocated ext_key. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubkeys_derive_then_agg(const unsigned char *xpubs, size_t xpubs_len, uint32_t child_num, unsigned char *agg_pk_out, size_t agg_pk_out_len, struct wally_musig_keyagg_cache **cache_out) + + + Derive child key from each xpub at child_num, sort derived pubkeys + lexicographically (BIP-390), then aggregate. + + :param xpubs: Concatenated 78-byte serialized BIP-32 extended public keys. + :param xpubs_len: Length of xpubs in bytes. Must be a multiple of BIP32_SERIALIZED_LEN and at least 2 * BIP32_SERIALIZED_LEN. + :param child_num: Unhardened child index to derive (< BIP32_INITIAL_HARDENED_CHILD). + :param agg_pk_out: Destination for the 32-byte x-only aggregate pubkey, or NULL. + :param agg_pk_out_len: Size of ``agg_pk_out``. Must be `EC_XONLY_PUBLIC_KEY_LEN`. + :param cache_out: Destination for the allocated keyagg_cache, or NULL. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_pubkeys_agg_then_derive(const unsigned char *pub_keys, size_t pub_keys_len, uint32_t version, uint32_t child_num, unsigned char *pub_key_out, size_t pub_key_out_len, struct ext_key **child_out) + + + Aggregate N pubkeys, construct BIP-328 synthetic xpub, then derive child_num. + + :param pub_keys: Concatenated 33-byte compressed public keys. + :param pub_keys_len: Length of pub_keys. Must be a multiple of EC_PUBLIC_KEY_LEN and at least 2 * EC_PUBLIC_KEY_LEN. + :param version: BIP32_VER_MAIN_PUBLIC or BIP32_VER_TEST_PUBLIC. + :param child_num: Unhardened child index to derive. + :param pub_key_out: Destination for the 33-byte compressed child pubkey, or NULL. + :param pub_key_out_len: Size of ``pub_key_out``. Must be `EC_PUBLIC_KEY_LEN`. + :param child_out: Destination for the allocated child ext_key, or NULL. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_nonce_gen(const unsigned char *session_secrand32, size_t session_secrand_len, const unsigned char *seckey, size_t seckey_len, const unsigned char *pubkey33, size_t pubkey_len, const struct wally_musig_keyagg_cache *keyagg_cache, const unsigned char *msg32, size_t msg_len, const unsigned char *extra_input32, size_t extra_len, struct wally_musig_secnonce **secnonce_out, struct wally_musig_pubnonce **pubnonce_out) + + + Generate a MuSig2 secret/public nonce pair. + + :param session_secrand32: 32-byte unique random session ID. MUST NOT be reused. + :param session_secrand_len: Must be 32. + :param seckey: 32-byte secret key of the signer (optional, can be NULL). + :param seckey_len: Must be 32 if seckey is non-NULL, 0 otherwise. + :param pubkey33: 33-byte compressed public key of this signer (required). + :param pubkey_len: Must be EC_PUBLIC_KEY_LEN (33). + :param keyagg_cache: keyagg_cache from wally_musig_pubkey_agg (optional, can be NULL). + :param msg32: 32-byte message to be signed, if known (optional, can be NULL). + :param msg_len: Must be 32 if msg32 is non-NULL, 0 otherwise. + :param extra_input32: 32-byte extra entropy input (optional, can be NULL). + :param extra_len: Must be 32 if extra_input32 is non-NULL, 0 otherwise. + :param secnonce_out: Destination for the allocated secret nonce. Must be kept secret. + :param pubnonce_out: Destination for the allocated public nonce to send to cosigners. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_nonce_gen_counter(uint64_t counter, const unsigned char *seckey, size_t seckey_len, const unsigned char *pubkey33, size_t pubkey_len, const struct wally_musig_keyagg_cache *keyagg_cache, const unsigned char *msg32, size_t msg_len, const unsigned char *extra_input32, size_t extra_len, struct wally_musig_secnonce **secnonce_out, struct wally_musig_pubnonce **pubnonce_out) + + + Generate a MuSig2 secret/public nonce pair using a counter-based session ID. + + This variant is intended for hardware wallets or deterministic signers that + cannot generate random session IDs. The uint64_t counter is serialized as an + 8-byte little-endian value, zero-padded to 32 bytes, and used as the + session_id32. Per BIP-327, seckey MUST be provided when using a counter. + + :param counter: Monotonically increasing counter. MUST NOT be reused with the same seckey. + :param seckey: 32-byte secret key of the signer (REQUIRED for counter mode). + :param seckey_len: Must be 32. + :param pubkey33: 33-byte compressed public key of this signer (required). + :param pubkey_len: Must be EC_PUBLIC_KEY_LEN (33). + :param keyagg_cache: keyagg_cache from wally_musig_pubkey_agg (optional, can be NULL). + :param msg32: 32-byte message to be signed, if known (optional, can be NULL). + :param msg_len: Must be 32 if msg32 is non-NULL, 0 otherwise. + :param extra_input32: 32-byte extra entropy input (optional, can be NULL). + :param extra_len: Must be 32 if extra_input32 is non-NULL, 0 otherwise. + :param secnonce_out: Destination for the allocated secret nonce. Must be kept secret. + :param pubnonce_out: Destination for the allocated public nonce to send to cosigners. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_nonce_agg(const unsigned char *pubnonces, size_t pubnonces_len, size_t n_pubnonces, struct wally_musig_aggnonce **aggnonce_out) + + + Aggregate N serialized public nonces into a single aggregate nonce. + + :param pubnonces: Flat array of serialized pubnonces (each WALLY_MUSIG_PUBNONCE_LEN bytes). + :param pubnonces_len: Total byte length. Must equal n_pubnonces * WALLY_MUSIG_PUBNONCE_LEN. + :param n_pubnonces: Number of pubnonces. Must be >= 2. + :param aggnonce_out: Destination for the allocated aggregate nonce. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_nonce_process(const struct wally_musig_aggnonce *aggnonce, const unsigned char *msg32, size_t msg32_len, const struct wally_musig_keyagg_cache *cache, const unsigned char *adaptor, size_t adaptor_len, struct wally_musig_session **session_out) + + + Process the aggregate nonce and message to create a signing session. + + Must be called by every participant after nonce aggregation and before signing. + + :param aggnonce: The aggregate nonce from wally_musig_nonce_agg. + :param msg32: The 32-byte message to sign. + :param msg32_len: Must be 32. + :param cache: The keyagg_cache from wally_musig_pubkey_agg (and optional tweaks). + :param adaptor: Optional 33-byte compressed adaptor public key (can be NULL). + :param adaptor_len: Must be EC_PUBLIC_KEY_LEN if adaptor is non-NULL, 0 otherwise. + :param session_out: Destination for the allocated session. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_partial_sign(struct wally_musig_secnonce *secnonce, const unsigned char *seckey, size_t seckey_len, const struct wally_musig_keyagg_cache *cache, const struct wally_musig_session *session, struct wally_musig_partial_sig **partial_sig_out) + + + Produce a partial signature for this participant. + + WARNING: The secnonce is irrevocably zeroed whenever secp256k1_musig_partial_sign + is reached (i.e., when WALLY_OK or WALLY_ERROR is returned). Input validation + failures (WALLY_EINVAL) do not consume the secnonce. Never attempt to sign + twice with the same secnonce. + + :param secnonce: The secret nonce from wally_musig_nonce_gen (zeroed after use). + :param seckey: The 32-byte secret key of this signer. + :param seckey_len: Must be 32. + :param cache: The keyagg_cache from wally_musig_pubkey_agg. + :param session: The session from wally_musig_nonce_process. + :param partial_sig_out: Destination for the allocated partial signature. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_partial_sig_verify(const struct wally_musig_partial_sig *sig, const struct wally_musig_pubnonce *pubnonce, const unsigned char *pubkey, size_t pubkey_len, const struct wally_musig_keyagg_cache *cache, const struct wally_musig_session *session) + + + Verify a partial signature from one participant. + + Returns WALLY_OK if valid, WALLY_ERROR if the signature is invalid, + WALLY_EINVAL for bad arguments. + + :param sig: The partial signature to verify. + :param pubnonce: The signer's public nonce (from round 1). + :param pubkey: The signer's 33-byte compressed public key. + :param pubkey_len: Must be EC_PUBLIC_KEY_LEN (33). + :param cache: The keyagg_cache from wally_musig_pubkey_agg. + :param session: The session from wally_musig_nonce_process. + + :return: See :ref:`error-codes` + + +.. c:function:: int wally_musig_partial_sig_agg(const unsigned char *partial_sigs, size_t partial_sigs_len, size_t n_sigs, const struct wally_musig_session *session, unsigned char *sig64_out, size_t sig64_out_len) + + + Aggregate N partial signatures into a final 64-byte BIP-340 Schnorr signature. + + :param partial_sigs: Flat array of serialized partial signatures + (each WALLY_MUSIG_PARTIAL_SIG_LEN bytes). + :param partial_sigs_len: Total byte length. Must equal n_sigs * WALLY_MUSIG_PARTIAL_SIG_LEN. + :param n_sigs: Number of partial signatures. Must be >= 2. + :param session: The session from wally_musig_nonce_process. + :param sig64_out: 64-byte buffer to receive the final Schnorr signature. + :param sig64_out_len: Size of ``sig64_out``. Must be `EC_SIGNATURE_LEN`. + + :return: See :ref:`error-codes` + + + +Musig Constants +--------------- + +.. c:macro:: WALLY_MUSIG_PUBNONCE_LEN + + Sizes of serialized MuSig2 objects + +.. c:macro:: WALLY_MUSIG_AGGNONCE_LEN + + + +.. c:macro:: WALLY_MUSIG_PARTIAL_SIG_LEN + + + +.. c:macro:: WALLY_MUSIG_KEYAGG_CACHE_LEN + + Sizes of opaque MuSig2 objects (for buffer allocation) + +.. c:macro:: WALLY_MUSIG_SESSION_LEN + + + +.. c:macro:: WALLY_MUSIG_SECNONCE_LEN + + + +.. c:macro:: WALLY_MUSIG2_CHAINCODE_LEN + + Length of the BIP-328 synthetic chain code (same as BIP-32 chain code) diff --git a/docs/source/satisfier.rst b/docs/source/satisfier.rst new file mode 100644 index 000000000..441ed6c04 --- /dev/null +++ b/docs/source/satisfier.rst @@ -0,0 +1,71 @@ +Miniscript Satisfier +==================== + +These functions produce non-malleable (or optionally malleable) witness +stacks for a miniscript expression encoded as an ``ms_node`` AST. + +The satisfier mirrors `rust-miniscript `__'s +``Satisfaction::sat_dissat`` at commit ``1834bc06``. + +Satisfier Context +----------------- + +.. c:type:: ms_satisfier + + Asset provider passed to :c:func:`satisfy_node`. All function + pointers may be ``NULL`` if the corresponding asset type is not + available. + + .. c:member:: bool (*lookup_sig)(...) + + Look up a signature for a public key. Called for ``pk_k``, + ``pk_h``, ``multi``, and ``multi_a`` fragments. + + .. c:member:: bool (*lookup_preimage)(...) + + Look up a 32-byte hash preimage. ``hash_type`` is one of + ``MS_HASH_SHA256``, ``MS_HASH_HASH256``, ``MS_HASH_RIPEMD160``, + ``MS_HASH_HASH160``. + + .. c:member:: bool (*check_older)(...) + + Return ``true`` if the relative locktime ``lock`` is currently + satisfied (i.e. the UTXO is old enough). + + .. c:member:: bool (*check_after)(...) + + Return ``true`` if the absolute locktime ``lock`` is currently + satisfied. + + .. c:member:: const unsigned char *leaf_hash + + 32-byte taproot leaf hash used for Schnorr signatures. Set to + ``NULL`` for segwit v0 scripts. + + .. c:member:: void *user_data + + Opaque pointer passed back to each callback. + +Functions +--------- + +.. c:function:: void satisfy_node(const ms_node *node, const ms_satisfier *stfr, bool malleable, ms_satisfaction *sat_out, ms_satisfaction *dissat_out) + + Compute both satisfaction and dissatisfaction for the miniscript + subtree rooted at *node*. + + The traversal is iterative (post-order), mirroring + ``rust-miniscript::Satisfaction::sat_dissat``. + + When *malleable* is ``false`` (the default for PSBT finalization) + the returned satisfaction is non-malleable: a third party cannot + replace it with a strictly lighter witness. When *malleable* is + ``true`` the cheapest witness is returned regardless of + malleability. + + On allocation failure, both outputs are set to + ``MS_WITNESS_IMPOSSIBLE``. + + Individual leaf terminals (``pk_k``, ``pk_h``, hash fragments, + timelocks, ``multi``, ``multi_a``) return ``MS_WITNESS_UNAVAILABLE`` + until the corresponding handler phase lands. diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am index 78de1631c..beee823ee 100644 --- a/fuzz/Makefile.am +++ b/fuzz/Makefile.am @@ -1,4 +1,4 @@ -noinst_PROGRAMS = fuzz_psbt_from_bytes fuzz_tx_from_bytes +noinst_PROGRAMS = fuzz_psbt_from_bytes fuzz_tx_from_bytes fuzz_descriptor fuzz_psbt_musig2 fuzz_psbt_from_bytes_SOURCES = fuzz_psbt_from_bytes.c fuzz_psbt_from_bytes_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) @@ -9,3 +9,13 @@ fuzz_tx_from_bytes_SOURCES = fuzz_tx_from_bytes.c fuzz_tx_from_bytes_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) fuzz_tx_from_bytes_LDFLAGS = -fsanitize=fuzzer fuzz_tx_from_bytes_LDADD = $(top_builddir)/src/libwallycore.la + +fuzz_descriptor_SOURCES = fuzz_descriptor.c +fuzz_descriptor_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +fuzz_descriptor_LDFLAGS = -fsanitize=fuzzer +fuzz_descriptor_LDADD = $(top_builddir)/src/libwallycore.la + +fuzz_psbt_musig2_SOURCES = fuzz_psbt_musig2.c +fuzz_psbt_musig2_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +fuzz_psbt_musig2_LDFLAGS = -fsanitize=fuzzer +fuzz_psbt_musig2_LDADD = $(top_builddir)/src/libwallycore.la diff --git a/fuzz/corpus/musig_descriptor/malformed_missing_paren b/fuzz/corpus/musig_descriptor/malformed_missing_paren new file mode 100644 index 000000000..612e23e84 --- /dev/null +++ b/fuzz/corpus/musig_descriptor/malformed_missing_paren @@ -0,0 +1 @@ +tr(musig(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5) \ No newline at end of file diff --git a/fuzz/corpus/musig_descriptor/valid_compressed_keys b/fuzz/corpus/musig_descriptor/valid_compressed_keys new file mode 100644 index 000000000..ee32fd7a6 --- /dev/null +++ b/fuzz/corpus/musig_descriptor/valid_compressed_keys @@ -0,0 +1 @@ +tr(musig(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)) \ No newline at end of file diff --git a/fuzz/corpus/musig_descriptor/valid_xpub_derivation b/fuzz/corpus/musig_descriptor/valid_xpub_derivation new file mode 100644 index 000000000..ae6342577 --- /dev/null +++ b/fuzz/corpus/musig_descriptor/valid_xpub_derivation @@ -0,0 +1 @@ +tr(musig([deadbeef/86h/0h/0h]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/0/*,[cafebabe/86h/0h/0h]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/*)) \ No newline at end of file diff --git a/fuzz/fuzz_descriptor.c b/fuzz/fuzz_descriptor.c new file mode 100644 index 000000000..60e8e0da5 --- /dev/null +++ b/fuzz/fuzz_descriptor.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include + +static void test_fuzz_descriptor(const char *str, uint32_t network) +{ + struct wally_descriptor *desc = NULL; + int ret; + + ret = wally_descriptor_parse(str, NULL, network, 0, &desc); + if (desc) { + uint32_t features = 0; + wally_descriptor_get_features(desc, &features); + + /* Canonicalize — must not crash */ + char *canon = NULL; + wally_descriptor_canonicalize(desc, 0, &canon); + wally_free_string(canon); + + /* Checksum — must not crash */ + char *chk = NULL; + wally_descriptor_get_checksum(desc, 0, &chk); + wally_free_string(chk); + + /* Address derivation for indices 0 and 1 */ + if (ret == WALLY_OK) { + char *addr = NULL; + wally_descriptor_to_address(desc, 0, 0, 0, 0, &addr); + wally_free_string(addr); + addr = NULL; + wally_descriptor_to_address(desc, 0, 0, 1, 0, &addr); + wally_free_string(addr); + } + + /* If musig() is present, walk participant keys */ + if (features & WALLY_MS_IS_MUSIG) { + size_t num_keys = 0; + wally_descriptor_get_num_keys(desc, &num_keys); + if (num_keys > 16) + num_keys = 16; + for (size_t k = 0; k < num_keys; k++) { + size_t np = 0; + if (wally_descriptor_get_musig_num_participants(desc, k, &np) == WALLY_OK) { + if (np > 16) + np = 16; + for (size_t p = 0; p < np; p++) { + char *pkey = NULL; + wally_descriptor_get_musig_participant_key(desc, k, p, &pkey); + wally_free_string(pkey); + } + } + } + } + + wally_descriptor_free(desc); + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + /* Treat input bytes as a NUL-terminated descriptor string */ + char *str = malloc(size + 1); + if (!str) + return 0; + memcpy(str, data, size); + str[size] = '\0'; + + /* Try all four network variants */ + static const uint32_t networks[] = { + WALLY_NETWORK_NONE, + WALLY_NETWORK_BITCOIN_MAINNET, + WALLY_NETWORK_BITCOIN_TESTNET, + WALLY_NETWORK_BITCOIN_REGTEST, + }; + + for (size_t i = 0; i < sizeof(networks) / sizeof(networks[0]); i++) + test_fuzz_descriptor(str, networks[i]); + + free(str); + return 0; +} diff --git a/fuzz/fuzz_psbt_musig2.c b/fuzz/fuzz_psbt_musig2.c new file mode 100644 index 000000000..315e491b0 --- /dev/null +++ b/fuzz/fuzz_psbt_musig2.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include + +/* + * MuSig2 per-input PSBT field types (BIP-370): + * 0x1a = PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS key: type(1) + agg_pubkey(33) + * 0x1b = PSBT_IN_MUSIG2_PUB_NONCE key: type(1) + part_pubkey(33) + agg_pubkey(33) + * 0x1c = PSBT_IN_MUSIG2_PARTIAL_SIG key: type(1) + xonly_part(32) + agg_pubkey(33) + * + * This fuzzer builds a minimal but structurally valid PSBT v0 frame and splices + * fuzz bytes into key-value pairs with each MuSig2 field type, specifically + * exercising the deserialization paths for types 0x1a, 0x1b, and 0x1c. + */ + +/* Minimal serialized tx: version=2, 1 input (all-zero txid), 1 output (0-val) */ +static const uint8_t s_minimal_tx[] = { + 0x02, 0x00, 0x00, 0x00, /* version=2 */ + 0x01, /* 1 input */ + /* txid (32 zero bytes) */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x00, 0x00, 0x00, 0x00, /* vout=0 */ + 0x00, /* scriptSig len=0 */ + 0xff, 0xff, 0xff, 0xff, /* sequence=0xffffffff */ + 0x01, /* 1 output */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0 satoshis */ + 0x00, /* scriptPubKey len=0 */ + 0x00, 0x00, 0x00, 0x00 /* locktime=0 */ +}; + +/* PSBT magic: 0x70736274ff */ +static const uint8_t s_psbt_magic[] = {0x70, 0x73, 0x62, 0x74, 0xff}; + +/* Write a compact-size (varint) integer, return bytes written */ +static size_t write_varint(uint8_t *buf, uint64_t v) +{ + if (v < 0xfd) { + buf[0] = (uint8_t)v; + return 1; + } else if (v <= 0xffff) { + buf[0] = 0xfd; + buf[1] = (uint8_t)(v & 0xff); + buf[2] = (uint8_t)((v >> 8) & 0xff); + return 3; + } else { + buf[0] = 0xfe; + buf[1] = (uint8_t)(v & 0xff); + buf[2] = (uint8_t)((v >> 8) & 0xff); + buf[3] = (uint8_t)((v >> 16) & 0xff); + buf[4] = (uint8_t)((v >> 24) & 0xff); + return 5; + } +} + +/* Append a key-value pair to the output buffer, return new offset */ +static size_t append_kv(uint8_t *out, size_t off, + const uint8_t *key, size_t key_len, + const uint8_t *val, size_t val_len) +{ + off += write_varint(out + off, key_len); + memcpy(out + off, key, key_len); + off += key_len; + off += write_varint(out + off, val_len); + if (val_len && val) + memcpy(out + off, val, val_len); + off += val_len; + return off; +} + +/* + * Build a minimal PSBT frame and inject fuzz bytes as the value of a + * per-input key-value pair with the given MuSig2 field type. + * The key suffix (after the type byte) is filled from fuzz data or zeros. + */ +static void fuzz_musig2_field(const uint8_t *fuzz, size_t fuzz_size, + uint8_t field_type) +{ + /* + * Key suffix lengths (bytes after the 1-byte type): + * 0x1a: agg_pubkey(33) = 33 + * 0x1b: part_pubkey(33) + agg_pubkey(33) = 66 + * 0x1c: xonly_part(32) + agg_pubkey(33) = 65 + */ + size_t key_suffix_len; + switch (field_type) { + case 0x1a: key_suffix_len = 33; break; + case 0x1b: key_suffix_len = 66; break; + case 0x1c: key_suffix_len = 65; break; + default: key_suffix_len = 33; break; + } + + /* Build the key: type byte + key_suffix (from fuzz or zero-padded) */ + uint8_t key[68]; + key[0] = field_type; + size_t from_fuzz = fuzz_size < key_suffix_len ? fuzz_size : key_suffix_len; + if (from_fuzz) + memcpy(key + 1, fuzz, from_fuzz); + if (from_fuzz < key_suffix_len) + memset(key + 1 + from_fuzz, 0, key_suffix_len - from_fuzz); + + /* Value: fuzz bytes beyond the key suffix (may be empty) */ + const uint8_t *val = fuzz_size > key_suffix_len ? fuzz + key_suffix_len : NULL; + size_t val_len = fuzz_size > key_suffix_len ? fuzz_size - key_suffix_len : 0; + + /* Allocate output buffer: magic + global(~80) + input(keysize + valsize) + output + extra */ + size_t buf_cap = 5 + 5 + sizeof(s_minimal_tx) + 10 + + 5 + (1 + key_suffix_len) + 5 + val_len + 16; + uint8_t *buf = malloc(buf_cap); + if (!buf) + return; + + size_t off = 0; + + /* 1. Magic */ + memcpy(buf + off, s_psbt_magic, 5); + off += 5; + + /* 2. Global map: UNSIGNED_TX (key type = 0x00) */ + static const uint8_t unsigned_tx_key[] = {0x00}; + off = append_kv(buf, off, unsigned_tx_key, 1, + s_minimal_tx, sizeof(s_minimal_tx)); + buf[off++] = 0x00; /* end of global map */ + + /* 3. Input 0 map: inject MuSig2-typed key-value pair */ + off = append_kv(buf, off, key, 1 + key_suffix_len, val, val_len); + buf[off++] = 0x00; /* end of input 0 map */ + + /* 4. Output 0 map: empty */ + buf[off++] = 0x00; + + /* Feed to the parser */ + struct wally_psbt *psbt = NULL; + wally_psbt_from_bytes(buf, off, 0, &psbt); + if (psbt) + wally_psbt_free(psbt); + + psbt = NULL; + wally_psbt_from_bytes(buf, off, WALLY_PSBT_PARSE_FLAG_STRICT, &psbt); + if (psbt) + wally_psbt_free(psbt); + + free(buf); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + /* Exercise all three MuSig2 per-input field type code paths */ + fuzz_musig2_field(data, size, 0x1a); /* PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS */ + fuzz_musig2_field(data, size, 0x1b); /* PSBT_IN_MUSIG2_PUB_NONCE */ + fuzz_musig2_field(data, size, 0x1c); /* PSBT_IN_MUSIG2_PARTIAL_SIG */ + + return 0; +} diff --git a/include/wally.hpp b/include/wally.hpp index 4e699a454..626b389dc 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -20,6 +20,9 @@ #include #include #include +#ifndef BUILD_STANDARD_SECP +#include +#endif /* These wrappers allow passing containers such as std::vector, std::array, * std::string and custom classes as input/output buffers to wally functions. @@ -599,6 +602,36 @@ inline int descriptor_get_key_origin_path_str_len(const DESCRIPTOR& descriptor, return detail::check_ret(__FUNCTION__, ret); } +template +inline int descriptor_get_musig_num_participants(const DESCRIPTOR& descriptor, size_t index, size_t* written) { + int ret = ::wally_descriptor_get_musig_num_participants(detail::get_p(descriptor), index, written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_musig_participant_key(const DESCRIPTOR& descriptor, size_t index, size_t participant_index, char** output) { + int ret = ::wally_descriptor_get_musig_participant_key(detail::get_p(descriptor), index, participant_index, output); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_musig_participant_key_features(const DESCRIPTOR& descriptor, size_t index, size_t participant_index, uint32_t* value_out) { + int ret = ::wally_descriptor_get_musig_participant_key_features(detail::get_p(descriptor), index, participant_index, value_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_musig_participant_key_origin_fingerprint(const DESCRIPTOR& descriptor, size_t index, size_t participant_index, BYTES_OUT& bytes_out) { + int ret = ::wally_descriptor_get_musig_participant_key_origin_fingerprint(detail::get_p(descriptor), index, participant_index, bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_musig_participant_key_origin_path_str(const DESCRIPTOR& descriptor, size_t index, size_t participant_index, char** output) { + int ret = ::wally_descriptor_get_musig_participant_key_origin_path_str(detail::get_p(descriptor), index, participant_index, output); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int descriptor_get_network(const DESCRIPTOR& descriptor, uint32_t* value_out) { int ret = ::wally_descriptor_get_network(detail::get_p(descriptor), value_out); @@ -1168,6 +1201,182 @@ inline bool merkle_path_xonly_public_key_verify(const KEY& key, const VAL& val) return ret == WALLY_OK; } +#ifndef BUILD_STANDARD_SECP +inline int musig_aggnonce_free(struct wally_musig_aggnonce* nonce) { + int ret = ::wally_musig_aggnonce_free(nonce); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_aggnonce_parse(const BYTES& bytes, struct wally_musig_aggnonce** output) { + int ret = ::wally_musig_aggnonce_parse(bytes.data(), bytes.size(), output); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_aggnonce_serialize(const NONCE& nonce, BYTES_OUT& bytes_out) { + int ret = ::wally_musig_aggnonce_serialize(detail::get_p(nonce), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +inline int musig_keyagg_cache_free(struct wally_musig_keyagg_cache* cache) { + int ret = ::wally_musig_keyagg_cache_free(cache); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_keyagg_cache_parse(const BYTES& bytes, struct wally_musig_keyagg_cache** output) { + int ret = ::wally_musig_keyagg_cache_parse(bytes.data(), bytes.size(), output); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_keyagg_cache_serialize(const CACHE& cache, BYTES_OUT& bytes_out) { + int ret = ::wally_musig_keyagg_cache_serialize(detail::get_p(cache), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_nonce_agg(const PUBNONCES& pubnonces, size_t n_pubnonces, struct wally_musig_aggnonce** aggnonce_out) { + int ret = ::wally_musig_nonce_agg(pubnonces.data(), pubnonces.size(), n_pubnonces, aggnonce_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_nonce_gen(const SESSION_SECRAND32& session_secrand32, const SECKEY& seckey, const PUBKEY33& pubkey33, const KEYAGG_CACHE& keyagg_cache, const MSG32& msg32, const EXTRA_INPUT32& extra_input32, struct wally_musig_secnonce** secnonce_out, struct wally_musig_pubnonce** pubnonce_out) { + int ret = ::wally_musig_nonce_gen(session_secrand32.data(), session_secrand32.size(), seckey.data(), seckey.size(), pubkey33.data(), pubkey33.size(), detail::get_p(keyagg_cache), msg32.data(), msg32.size(), extra_input32.data(), extra_input32.size(), secnonce_out, pubnonce_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_nonce_gen_counter(uint64_t counter, const SECKEY& seckey, const PUBKEY33& pubkey33, const KEYAGG_CACHE& keyagg_cache, const MSG32& msg32, const EXTRA_INPUT32& extra_input32, struct wally_musig_secnonce** secnonce_out, struct wally_musig_pubnonce** pubnonce_out) { + int ret = ::wally_musig_nonce_gen_counter(counter, seckey.data(), seckey.size(), pubkey33.data(), pubkey33.size(), detail::get_p(keyagg_cache), msg32.data(), msg32.size(), extra_input32.data(), extra_input32.size(), secnonce_out, pubnonce_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_nonce_process(const AGGNONCE& aggnonce, const MSG32& msg32, const CACHE& cache, const ADAPTOR& adaptor, struct wally_musig_session** session_out) { + int ret = ::wally_musig_nonce_process(detail::get_p(aggnonce), msg32.data(), msg32.size(), detail::get_p(cache), adaptor.data(), adaptor.size(), session_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_partial_sig_agg(const PARTIAL_SIGS& partial_sigs, size_t n_sigs, const SESSION& session, SIG64_OUT& sig64_out) { + int ret = ::wally_musig_partial_sig_agg(partial_sigs.data(), partial_sigs.size(), n_sigs, detail::get_p(session), sig64_out.data(), sig64_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +inline int musig_partial_sig_free(struct wally_musig_partial_sig* sig) { + int ret = ::wally_musig_partial_sig_free(sig); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_partial_sig_parse(const BYTES& bytes, struct wally_musig_partial_sig** output) { + int ret = ::wally_musig_partial_sig_parse(bytes.data(), bytes.size(), output); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_partial_sig_serialize(const SIG& sig, BYTES_OUT& bytes_out) { + int ret = ::wally_musig_partial_sig_serialize(detail::get_p(sig), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline bool musig_partial_sig_verify(const SIG& sig, const PUBNONCE& pubnonce, const PUBKEY& pubkey, const CACHE& cache, const struct wally_musig_session* session) { + int ret = ::wally_musig_partial_sig_verify(detail::get_p(sig), detail::get_p(pubnonce), pubkey.data(), pubkey.size(), detail::get_p(cache), session); + return ret == WALLY_OK; +} + +template +inline int musig_partial_sign(const SECNONCE& secnonce, const SECKEY& seckey, const CACHE& cache, const SESSION& session, struct wally_musig_partial_sig** partial_sig_out) { + int ret = ::wally_musig_partial_sign(detail::get_p(secnonce), seckey.data(), seckey.size(), detail::get_p(cache), detail::get_p(session), partial_sig_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubkey_agg(const PUB_KEYS& pub_keys, AGG_PK_OUT& agg_pk_out, struct wally_musig_keyagg_cache** cache_out) { + int ret = ::wally_musig_pubkey_agg(pub_keys.data(), pub_keys.size(), agg_pk_out.data(), agg_pk_out.size(), cache_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubkey_ec_tweak_add(const CACHE& cache, const TWEAK& tweak, PUB_KEY_OUT& pub_key_out) { + int ret = ::wally_musig_pubkey_ec_tweak_add(detail::get_p(cache), tweak.data(), tweak.size(), pub_key_out.data(), pub_key_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubkey_get(const CACHE& cache, PUB_KEY_OUT& pub_key_out) { + int ret = ::wally_musig_pubkey_get(detail::get_p(cache), pub_key_out.data(), pub_key_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubkey_to_xpub(const AGG_PK& agg_pk, uint32_t version, struct ext_key** output) { + int ret = ::wally_musig_pubkey_to_xpub(agg_pk.data(), agg_pk.size(), version, output); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubkey_xonly_tweak_add(const CACHE& cache, const TWEAK& tweak, PUB_KEY_OUT& pub_key_out) { + int ret = ::wally_musig_pubkey_xonly_tweak_add(detail::get_p(cache), tweak.data(), tweak.size(), pub_key_out.data(), pub_key_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubkeys_agg_then_derive(const PUB_KEYS& pub_keys, uint32_t version, uint32_t child_num, PUB_KEY_OUT& pub_key_out, struct ext_key** child_out) { + int ret = ::wally_musig_pubkeys_agg_then_derive(pub_keys.data(), pub_keys.size(), version, child_num, pub_key_out.data(), pub_key_out.size(), child_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubkeys_derive_then_agg(const XPUBS& xpubs, uint32_t child_num, AGG_PK_OUT& agg_pk_out, struct wally_musig_keyagg_cache** cache_out) { + int ret = ::wally_musig_pubkeys_derive_then_agg(xpubs.data(), xpubs.size(), child_num, agg_pk_out.data(), agg_pk_out.size(), cache_out); + return detail::check_ret(__FUNCTION__, ret); +} + +inline int musig_pubnonce_free(struct wally_musig_pubnonce* nonce) { + int ret = ::wally_musig_pubnonce_free(nonce); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubnonce_parse(const BYTES& bytes, struct wally_musig_pubnonce** output) { + int ret = ::wally_musig_pubnonce_parse(bytes.data(), bytes.size(), output); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_pubnonce_serialize(const NONCE& nonce, BYTES_OUT& bytes_out) { + int ret = ::wally_musig_pubnonce_serialize(detail::get_p(nonce), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +inline int musig_secnonce_free(struct wally_musig_secnonce* nonce) { + int ret = ::wally_musig_secnonce_free(nonce); + return detail::check_ret(__FUNCTION__, ret); +} + +inline int musig_session_free(struct wally_musig_session* session) { + int ret = ::wally_musig_session_free(session); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_session_parse(const BYTES& bytes, struct wally_musig_session** output) { + int ret = ::wally_musig_session_parse(bytes.data(), bytes.size(), output); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int musig_session_serialize(const SESSION& session, BYTES_OUT& bytes_out) { + int ret = ::wally_musig_session_serialize(detail::get_p(session), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} +#endif /* ndef BUILD_STANDARD_SECP */ + template inline int pbkdf2_hmac_sha256(const PASS& pass, const SALT& salt, uint32_t flags, uint32_t cost, BYTES_OUT& bytes_out) { int ret = ::wally_pbkdf2_hmac_sha256(pass.data(), pass.size(), salt.data(), salt.size(), flags, cost, bytes_out.data(), bytes_out.size()); @@ -1357,6 +1566,24 @@ inline int psbt_init_alloc(uint32_t version, size_t inputs_allocation_len, size_ return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_input_add_musig2_partial_sig(const INPUT& input, const PARTICIPANT& participant, const AGG_PUBKEY& agg_pubkey, const LEAF_HASH& leaf_hash, const PARTIAL_SIG& partial_sig) { + int ret = ::wally_psbt_input_add_musig2_partial_sig(detail::get_p(input), participant.data(), participant.size(), agg_pubkey.data(), agg_pubkey.size(), leaf_hash.data(), leaf_hash.size(), partial_sig.data(), partial_sig.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_input_add_musig2_participant_pubkeys(const INPUT& input, const AGG_PUBKEY& agg_pubkey, const PARTICIPANTS& participants) { + int ret = ::wally_psbt_input_add_musig2_participant_pubkeys(detail::get_p(input), agg_pubkey.data(), agg_pubkey.size(), participants.data(), participants.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_input_add_musig2_pubnonce(const INPUT& input, const PARTICIPANT& participant, const AGG_PUBKEY& agg_pubkey, const LEAF_HASH& leaf_hash, const PUBNONCE& pubnonce) { + int ret = ::wally_psbt_input_add_musig2_pubnonce(detail::get_p(input), participant.data(), participant.size(), agg_pubkey.data(), agg_pubkey.size(), leaf_hash.data(), leaf_hash.size(), pubnonce.data(), pubnonce.size()); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_input_add_signature(const INPUT& input, const PUB_KEY& pub_key, const SIG& sig) { int ret = ::wally_psbt_input_add_signature(detail::get_p(input), pub_key.data(), pub_key.size(), sig.data(), sig.size()); @@ -1384,6 +1611,24 @@ inline int psbt_input_find_keypath(const INPUT& input, const PUB_KEY& pub_key, s return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_input_find_musig2_partial_sig(const INPUT& input, const PARTICIPANT& participant, const AGG_PUBKEY& agg_pubkey, const LEAF_HASH& leaf_hash, size_t* written) { + int ret = ::wally_psbt_input_find_musig2_partial_sig(detail::get_p(input), participant.data(), participant.size(), agg_pubkey.data(), agg_pubkey.size(), leaf_hash.data(), leaf_hash.size(), written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_input_find_musig2_pubkey(const INPUT& input, const AGG_PUBKEY& agg_pubkey, size_t* written) { + int ret = ::wally_psbt_input_find_musig2_pubkey(detail::get_p(input), agg_pubkey.data(), agg_pubkey.size(), written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_input_find_musig2_pubnonce(const INPUT& input, const PARTICIPANT& participant, const AGG_PUBKEY& agg_pubkey, const LEAF_HASH& leaf_hash, size_t* written) { + int ret = ::wally_psbt_input_find_musig2_pubnonce(detail::get_p(input), participant.data(), participant.size(), agg_pubkey.data(), agg_pubkey.size(), leaf_hash.data(), leaf_hash.size(), written); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_input_find_signature(const INPUT& input, const PUB_KEY& pub_key, size_t* written) { int ret = ::wally_psbt_input_find_signature(detail::get_p(input), pub_key.data(), pub_key.size(), written); @@ -1396,6 +1641,18 @@ inline int psbt_input_find_unknown(const INPUT& input, const KEY& key, size_t* w return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_input_get_musig2_partial_sig_count(const INPUT& input, size_t* written) { + int ret = ::wally_psbt_input_get_musig2_partial_sig_count(detail::get_p(input), written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_input_get_musig2_pubnonce_count(const INPUT& input, size_t* written) { + int ret = ::wally_psbt_input_get_musig2_pubnonce_count(detail::get_p(input), written); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_input_is_finalized(const INPUT& input, size_t* written) { int ret = ::wally_psbt_input_is_finalized(detail::get_p(input), written); @@ -1426,6 +1683,12 @@ inline int psbt_input_set_keypaths(const INPUT& input, const struct wally_map* m return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_input_set_musig2_pubkeys(const INPUT& input, const struct wally_map* map_in) { + int ret = ::wally_psbt_input_set_musig2_pubkeys(detail::get_p(input), map_in); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_input_set_output_index(const INPUT& input, uint32_t index) { int ret = ::wally_psbt_input_set_output_index(detail::get_p(input), index); @@ -1540,6 +1803,30 @@ inline int psbt_is_input_finalized(const PSBT& psbt, size_t index, size_t* writt return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_musig2_add_nonce(const PSBT& psbt, size_t index, const SESSION_SECRAND32& session_secrand32, const SECKEY& seckey, const PUBKEY33& pubkey33, const AGG_PUBKEY& agg_pubkey, const LEAF_HASH& leaf_hash, const KEYAGG_CACHE& keyagg_cache, uint32_t flags, struct wally_musig_secnonce** secnonce_out) { + int ret = ::wally_psbt_musig2_add_nonce(detail::get_p(psbt), index, session_secrand32.data(), session_secrand32.size(), seckey.data(), seckey.size(), pubkey33.data(), pubkey33.size(), agg_pubkey.data(), agg_pubkey.size(), leaf_hash.data(), leaf_hash.size(), detail::get_p(keyagg_cache), flags, secnonce_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_musig2_finalize_input(const PSBT& psbt, size_t index, const AGG_PUBKEY& agg_pubkey, const LEAF_HASH& leaf_hash, const KEYAGG_CACHE& keyagg_cache, uint32_t flags) { + int ret = ::wally_psbt_musig2_finalize_input(detail::get_p(psbt), index, agg_pubkey.data(), agg_pubkey.size(), leaf_hash.data(), leaf_hash.size(), detail::get_p(keyagg_cache), flags); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_musig2_sign(const PSBT& psbt, size_t index, const SECNONCE& secnonce, const SECKEY& seckey, const PUBKEY33& pubkey33, const AGG_PUBKEY& agg_pubkey, const LEAF_HASH& leaf_hash, const KEYAGG_CACHE& keyagg_cache, uint32_t flags, struct wally_musig_partial_sig** partial_sig_out) { + int ret = ::wally_psbt_musig2_sign(detail::get_p(psbt), index, detail::get_p(secnonce), seckey.data(), seckey.size(), pubkey33.data(), pubkey33.size(), agg_pubkey.data(), agg_pubkey.size(), leaf_hash.data(), leaf_hash.size(), detail::get_p(keyagg_cache), flags, partial_sig_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_output_add_musig2_participant_pubkeys(const OUTPUT& output, const AGG_PUBKEY& agg_pubkey, const PARTICIPANTS& participants) { + int ret = ::wally_psbt_output_add_musig2_participant_pubkeys(detail::get_p(output), agg_pubkey.data(), agg_pubkey.size(), participants.data(), participants.size()); + return detail::check_ret(__FUNCTION__, ret); +} + inline int psbt_output_clear_amount(struct wally_psbt_output* output) { int ret = ::wally_psbt_output_clear_amount(output); return detail::check_ret(__FUNCTION__, ret); @@ -1551,6 +1838,12 @@ inline int psbt_output_find_keypath(const OUTPUT& output, const PUB_KEY& pub_key return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_output_find_musig2_pubkey(const OUTPUT& output, const AGG_PUBKEY& agg_pubkey, size_t* written) { + int ret = ::wally_psbt_output_find_musig2_pubkey(detail::get_p(output), agg_pubkey.data(), agg_pubkey.size(), written); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_output_find_unknown(const OUTPUT& output, const KEY& key, size_t* written) { int ret = ::wally_psbt_output_find_unknown(detail::get_p(output), key.data(), key.size(), written); @@ -1575,6 +1868,12 @@ inline int psbt_output_set_keypaths(const OUTPUT& output, const struct wally_map return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_output_set_musig2_pubkeys(const OUTPUT& output, const struct wally_map* map_in) { + int ret = ::wally_psbt_output_set_musig2_pubkeys(detail::get_p(output), map_in); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_output_set_redeem_script(const OUTPUT& output, const SCRIPT& script) { int ret = ::wally_psbt_output_set_redeem_script(detail::get_p(output), script.data(), script.size()); @@ -1611,6 +1910,12 @@ inline int psbt_output_taproot_keypath_add(const OUTPUT& output, const PUB_KEY& return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_populate_musig2_from_descriptor(const PSBT& psbt, const DESCRIPTOR& descriptor, uint32_t child_num, uint32_t flags) { + int ret = ::wally_psbt_populate_musig2_from_descriptor(detail::get_p(psbt), detail::get_p(descriptor), child_num, flags); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_remove_input(const PSBT& psbt, uint32_t index) { int ret = ::wally_psbt_remove_input(detail::get_p(psbt), index); @@ -3298,6 +3603,7 @@ inline bool is_elements_build() return ret != 0; } + } /* namespace wally */ #endif /* LIBWALLY_CORE_WALLY_HPP */ diff --git a/include/wally_descriptor.h b/include/wally_descriptor.h index 7ab484174..a1951fc46 100644 --- a/include/wally_descriptor.h +++ b/include/wally_descriptor.h @@ -12,29 +12,32 @@ struct wally_map; struct wally_descriptor; /*** miniscript-flags Miniscript/Descriptor parsing flags */ -#define WALLY_MINISCRIPT_TAPSCRIPT 0x01 /** Tapscript, use x-only pubkeys */ -#define WALLY_MINISCRIPT_ONLY 0x02 /** Only allow miniscript (not descriptor) expressions */ -#define WALLY_MINISCRIPT_REQUIRE_CHECKSUM 0x04 /** Require a checksum to be present */ -#define WALLY_MINISCRIPT_POLICY_TEMPLATE 0x08 /** Only allow policy templates with @n BIP32 keys */ -#define WALLY_MINISCRIPT_UNIQUE_KEYPATHS 0x10 /** For policy templates, ensure BIP32 derivation paths differ for identical keys */ -#define WALLY_MINISCRIPT_AS_ELEMENTS 0x20 /** Treat non-elements expressions as elements, e.g. tr() as eltr() */ -#define WALLY_MINISCRIPT_DEPTH_MASK 0xffff0000 /** Mask for limiting maximum depth */ -#define WALLY_MINISCRIPT_DEPTH_SHIFT 16 /** Shift to convert maximum depth to flags */ +#define WALLY_MINISCRIPT_TAPSCRIPT 0x01 /** Tapscript, use x-only pubkeys */ +#define WALLY_MINISCRIPT_ONLY 0x02 /** Only allow miniscript (not descriptor) expressions */ +#define WALLY_MINISCRIPT_REQUIRE_CHECKSUM 0x04 /** Require a checksum to be present */ +#define WALLY_MINISCRIPT_POLICY_TEMPLATE 0x08 /** Only allow policy templates with @n BIP32 keys */ +#define WALLY_MINISCRIPT_UNIQUE_KEYPATHS 0x10 /** For policy templates, ensure BIP32 derivation paths differ for identical keys */ +#define WALLY_MINISCRIPT_AS_ELEMENTS 0x20 /** Treat non-elements expressions as elements, e.g. tr() as eltr() */ +#define WALLY_MINISCRIPT_DEPTH_MASK 0xffff0000 /** Mask for limiting maximum depth */ +#define WALLY_MINISCRIPT_DEPTH_SHIFT 16 /** Shift to convert maximum depth to flags */ +#define WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH 128 /** BIP-341: maximum taptree depth */ /*** miniscript-features Miniscript/Descriptor feature flags */ -#define WALLY_MS_IS_RANGED 0x001 /** Allows key ranges via ``*`` */ -#define WALLY_MS_IS_MULTIPATH 0x002 /** Allows multiple paths via ```` */ -#define WALLY_MS_IS_PRIVATE 0x004 /** Contains at least one private key */ -#define WALLY_MS_IS_UNCOMPRESSED 0x008 /** Contains at least one uncompressed key */ -#define WALLY_MS_IS_RAW 0x010 /** Contains at least one raw key */ -#define WALLY_MS_IS_DESCRIPTOR 0x020 /** Contains only descriptor expressions (no miniscript) */ -#define WALLY_MS_IS_X_ONLY 0x040 /** Contains at least one x-only key */ -#define WALLY_MS_IS_PARENTED 0x080 /** Contains at least one key key with a parent key origin */ -#define WALLY_MS_IS_ELEMENTS 0x100 /** Contains Elements expressions or was parsed as Elements */ -#define WALLY_MS_IS_SLIP77 0x200 /** A confidential ct() descriptor with SLIP-77 blinding */ -#define WALLY_MS_IS_ELIP150 0x400 /** A confidential ct() descriptor with ELIP-150 blinding */ -#define WALLY_MS_IS_ELIP151 0x800 /** A confidential ct() descriptor with ELIP-151 blinding */ -#define WALLY_MS_ANY_BLINDING_KEY 0xE00 /** SLIP-77, ELIP-150 or ELIP-151 blinding key present */ +#define WALLY_MS_IS_RANGED 0x0001 /** Allows key ranges via ``*`` */ +#define WALLY_MS_IS_MULTIPATH 0x0002 /** Allows multiple paths via ```` */ +#define WALLY_MS_IS_PRIVATE 0x0004 /** Contains at least one private key */ +#define WALLY_MS_IS_UNCOMPRESSED 0x0008 /** Contains at least one uncompressed key */ +#define WALLY_MS_IS_RAW 0x0010 /** Contains at least one raw key */ +#define WALLY_MS_IS_DESCRIPTOR 0x0020 /** Contains only descriptor expressions (no miniscript) */ +#define WALLY_MS_IS_X_ONLY 0x0040 /** Contains at least one x-only key */ +#define WALLY_MS_IS_PARENTED 0x0080 /** Contains at least one key key with a parent key origin */ +#define WALLY_MS_IS_ELEMENTS 0x0100 /** Contains Elements expressions or was parsed as Elements */ +#define WALLY_MS_IS_SLIP77 0x0200 /** A confidential ct() descriptor with SLIP-77 blinding */ +#define WALLY_MS_IS_ELIP150 0x0400 /** A confidential ct() descriptor with ELIP-150 blinding */ +#define WALLY_MS_IS_ELIP151 0x0800 /** A confidential ct() descriptor with ELIP-151 blinding */ +#define WALLY_MS_IS_TAPSCRIPT 0x1000 /** Node is inside tapscript context (internal) */ +#define WALLY_MS_IS_MUSIG 0x2000 /** A musig() key aggregate (BIP-390) */ +#define WALLY_MS_ANY_BLINDING_KEY 0x0E00 /** SLIP-77, ELIP-150 or ELIP-151 blinding key present */ /*** ms-canonicalization-flags Miniscript/Descriptor canonicalization flags */ #define WALLY_MS_CANONICAL_NO_CHECKSUM 0x01 /** Do not include a checksum */ @@ -306,6 +309,88 @@ WALLY_CORE_API int wally_descriptor_get_key_origin_path_str( size_t index, char **output); +/** + * Get the number of participants in a musig() key aggregate. + * + * :param descriptor: The descriptor to get the participant count from. + * :param index: The index of the key in the descriptor. + * :param written: Destination for the number of participants. + * + * .. note:: Returns WALLY_EINVAL if the key at ``index`` is not a musig() aggregate. + */ +WALLY_CORE_API int wally_descriptor_get_musig_num_participants( + const struct wally_descriptor *descriptor, + size_t index, + size_t *written); + +/** + * Get a participant key from a musig() key aggregate. + * + * :param descriptor: The descriptor to get the participant key from. + * :param index: The index of the musig() key in the descriptor. + * :param participant_index: The index of the participant within musig(). + * :param output: Destination for the allocated participant key string. + *| The string returned should be freed using `wally_free_string`. + * + * .. note:: Returns WALLY_EINVAL if the key at ``index`` is not a musig() aggregate + *| or if ``participant_index`` is out of range. + */ +WALLY_CORE_API int wally_descriptor_get_musig_participant_key( + const struct wally_descriptor *descriptor, + size_t index, + size_t participant_index, + char **output); + +/** + * Get the features flags for a participant key in a musig() key aggregate. + * + * :param descriptor: The descriptor to get the participant key features from. + * :param index: The index of the musig() key in the descriptor. + * :param participant_index: The index of the participant within musig(). + * :param value_out: Destination for the resulting :ref:`miniscript-features`. + * + * .. note:: Returns WALLY_EINVAL if the key at ``index`` is not a musig() aggregate + *| or if ``participant_index`` is out of range. + */ +WALLY_CORE_API int wally_descriptor_get_musig_participant_key_features( + const struct wally_descriptor *descriptor, + size_t index, + size_t participant_index, + uint32_t *value_out); + +/** + * Get the key origin fingerprint for a participant key in a musig() key aggregate. + * + * :param descriptor: The descriptor to get the fingerprint from. + * :param index: The index of the musig() key in the descriptor. + * :param participant_index: The index of the participant within musig(). + * :param bytes_out: Destination for the 4-byte fingerprint. + * FIXED_SIZED_OUTPUT(len, bytes_out, BIP32_KEY_FINGERPRINT_LEN) + * + * .. note:: Returns WALLY_EINVAL if the participant key has no key origin. + */ +WALLY_CORE_API int wally_descriptor_get_musig_participant_key_origin_fingerprint( + const struct wally_descriptor *descriptor, + size_t index, + size_t participant_index, + unsigned char *bytes_out, + size_t len); + +/** + * Get the key origin path string for a participant key in a musig() key aggregate. + * + * :param descriptor: The descriptor to get the origin path from. + * :param index: The index of the musig() key in the descriptor. + * :param participant_index: The index of the participant within musig(). + * :param output: Destination for the allocated origin path string (empty if not present). + *| The string returned should be freed using `wally_free_string`. + */ +WALLY_CORE_API int wally_descriptor_get_musig_participant_key_origin_path_str( + const struct wally_descriptor *descriptor, + size_t index, + size_t participant_index, + char **output); + /** * Get the maximum length of a script corresponding to an output descriptor. * @@ -406,6 +491,178 @@ WALLY_CORE_API int wally_descriptor_to_addresses( char **output, size_t num_outputs); +/** + * Get the number of taptree leaves in a tr() descriptor. + * + * :param descriptor: Parsed tr() output descriptor. + * :param value_out: Destination for the number of taptree leaves. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_num_leaves( + const struct wally_descriptor *descriptor, + uint32_t *value_out); + +/** + * Get the script for a specific taptree leaf. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param variant: See `wally_descriptor_get_num_variants`. + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the compiled tapscript. + * :param len: Length of ``bytes_out`` in bytes. + * :param written: Destination for the number of bytes written. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_script( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t variant, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len, + size_t *written); + +/** + * Get the tapleaf hash for a specific taptree leaf. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param variant: See `wally_descriptor_get_num_variants`. + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the 32-byte tapleaf hash. + * :param len: Length of ``bytes_out``. Must be at least 32. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_hash( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t variant, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len); + +/** + * Get the BIP-341 control block for spending via a specific taptree leaf. + * + * Format: ``(0xc0 | parity) || internal_x_only_key (32) || merkle_path_siblings``. + * Call with ``bytes_out = NULL`` or ``len = 0`` to query the required size via ``*written``. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param variant: See `wally_descriptor_get_num_variants`. + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the control block bytes, or NULL to query size. + * :param len: Length of ``bytes_out`` in bytes. + * :param written: Destination for the number of bytes written (or required size). + */ +WALLY_CORE_API int wally_descriptor_get_taproot_control_block( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t variant, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len, + size_t *written); + +/** + * Get the number of keys in a specific taptree leaf's miniscript. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param value_out: Destination for the key count. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_num_keys( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t *value_out); + +/** + * Get the descriptor-level key index for a key within a specific taptree leaf. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param key_position: Zero-based position of the key within the leaf's miniscript. + * :param value_out: Destination for the descriptor-level key index + *| (suitable for use with `wally_descriptor_get_key`). + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_key_index( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t key_position, + uint32_t *value_out); + +/** + * Get the x-only internal key of a tr() descriptor. + * + * :param descriptor: Parsed tr() output descriptor. + * :param variant: See `wally_descriptor_get_num_variants`. + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the 32-byte x-only internal key. + * :param len: Length of ``bytes_out``. Must be at least 32. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_internal_key( + const struct wally_descriptor *descriptor, + uint32_t variant, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len); + +/** + * Get the derived x-only public key for a descriptor-level key at a given derivation index. + * + * :param descriptor: Parsed output descriptor. + * :param key_index: Descriptor-level key index (from `wally_descriptor_get_taproot_leaf_key_index`). + * :param variant: See `wally_descriptor_get_num_variants`. + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number for ranged keys, or 0 for static keys. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the 32-byte x-only public key. + * :param len: Length of ``bytes_out``. Must be at least 32. + */ +WALLY_CORE_API int wally_descriptor_get_key_xonly_public_key( + const struct wally_descriptor *descriptor, + size_t key_index, + uint32_t variant, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len); + +/** + * Get the merkle root of the taptree in a tr() descriptor. + * + * :param descriptor: Parsed tr() output descriptor. + * :param variant: See `wally_descriptor_get_num_variants`. + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the 32-byte merkle root. + * :param len: Length of ``bytes_out``. Must be at least 32. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_merkle_root( + const struct wally_descriptor *descriptor, + uint32_t variant, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len); + #ifdef __cplusplus } #endif diff --git a/include/wally_musig.h b/include/wally_musig.h new file mode 100644 index 000000000..907c40539 --- /dev/null +++ b/include/wally_musig.h @@ -0,0 +1,538 @@ +#ifndef LIBWALLY_CORE_MUSIG_H +#define LIBWALLY_CORE_MUSIG_H + +#include "wally_core.h" +#include "wally_crypto.h" +#include "wally_bip32.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* MuSig2 object sizes stay available even under BUILD_STANDARD_SECP: the inert + * musig2 PSBT field plumbing (storage/serialization) references these lengths, + * while the secp-backed crypto below is compiled out. */ +/** Sizes of serialized MuSig2 objects */ +#define WALLY_MUSIG_PUBNONCE_LEN 66 +#define WALLY_MUSIG_AGGNONCE_LEN 66 +#define WALLY_MUSIG_PARTIAL_SIG_LEN 32 + +/** Sizes of opaque MuSig2 objects (for buffer allocation) */ +#define WALLY_MUSIG_KEYAGG_CACHE_LEN 197 +#define WALLY_MUSIG_SESSION_LEN 133 +#define WALLY_MUSIG_SECNONCE_LEN 132 + +/** Length of the BIP-328 synthetic chain code (same as BIP-32 chain code) */ +#define WALLY_MUSIG2_CHAINCODE_LEN 32 + +#ifndef BUILD_STANDARD_SECP + +/* Opaque type wrapping secp256k1_musig_keyagg_cache. + * Holds the result of key aggregation; required for signing. */ +struct wally_musig_keyagg_cache; + +/* Opaque type wrapping secp256k1_musig_secnonce. + * WARNING: MUST NOT be copied or serialized. Zeroed on free and after use. */ +struct wally_musig_secnonce; + +/* Opaque type wrapping secp256k1_musig_pubnonce. Serializes to 66 bytes. */ +struct wally_musig_pubnonce; + +/* Opaque type wrapping secp256k1_musig_aggnonce. Serializes to 66 bytes. */ +struct wally_musig_aggnonce; + +/* Opaque type wrapping secp256k1_musig_session. Not required to be secret. */ +struct wally_musig_session; + +/* Opaque type wrapping secp256k1_musig_partial_sig. Serializes to 32 bytes. */ +struct wally_musig_partial_sig; + +/* --- Lifecycle functions --- */ + +/** + * Free a keyagg_cache. + * + * :param cache: The keyagg_cache to free. + */ +WALLY_CORE_API int wally_musig_keyagg_cache_free( + struct wally_musig_keyagg_cache *cache); + +/** + * Serialize a keyagg_cache to its raw 197-byte form. + * + * :param cache: The keyagg_cache to serialize. + * :param bytes_out: 197-byte output buffer. + * FIXED_SIZED_OUTPUT(len, bytes_out, WALLY_MUSIG_KEYAGG_CACHE_LEN) + */ +WALLY_CORE_API int wally_musig_keyagg_cache_serialize( + const struct wally_musig_keyagg_cache *cache, + unsigned char *bytes_out, + size_t len); + +/** + * Restore a keyagg_cache from its raw 197-byte form. + * + * The aggregate key is checked, which rejects a corrupted magic or aggregate-key + * field. This is NOT a full integrity check: tampering with the tweak or other + * fields is not detected, so do not treat a successful parse as authentication of + * data crossing a trust boundary. Only round-trip bytes produced by + * wally_musig_keyagg_cache_serialize(). + * + * :param bytes: The 197-byte serialized keyagg_cache. + * :param bytes_len: Length of bytes. Must be WALLY_MUSIG_KEYAGG_CACHE_LEN. + * :param output: Destination for the allocated keyagg_cache. + */ +WALLY_CORE_API int wally_musig_keyagg_cache_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_keyagg_cache **output); + +/** + * Free a secnonce, securely zeroing it first. + * + * :param nonce: The secnonce to free. + */ +WALLY_CORE_API int wally_musig_secnonce_free( + struct wally_musig_secnonce *nonce); + +/** + * Parse a public nonce from its 66-byte serialized form. + * + * :param bytes: The 66-byte serialized pubnonce. + * :param bytes_len: Length of bytes. Must be WALLY_MUSIG_PUBNONCE_LEN. + * :param output: Destination for the allocated pubnonce. + */ +WALLY_CORE_API int wally_musig_pubnonce_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_pubnonce **output); + +/** + * Serialize a public nonce to its 66-byte form. + * + * :param nonce: The pubnonce to serialize. + * :param bytes_out: 66-byte output buffer. + * FIXED_SIZED_OUTPUT(len, bytes_out, WALLY_MUSIG_PUBNONCE_LEN) + */ +WALLY_CORE_API int wally_musig_pubnonce_serialize( + const struct wally_musig_pubnonce *nonce, + unsigned char *bytes_out, + size_t len); + +/** + * Free a pubnonce. + * + * :param nonce: The pubnonce to free. + */ +WALLY_CORE_API int wally_musig_pubnonce_free( + struct wally_musig_pubnonce *nonce); + +/** + * Parse an aggregate nonce from its 66-byte serialized form. + * + * :param bytes: The 66-byte serialized aggnonce. + * :param bytes_len: Length of bytes. Must be WALLY_MUSIG_AGGNONCE_LEN. + * :param output: Destination for the allocated aggnonce. + */ +WALLY_CORE_API int wally_musig_aggnonce_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_aggnonce **output); + +/** + * Serialize an aggregate nonce to its 66-byte form. + * + * :param nonce: The aggnonce to serialize. + * :param bytes_out: 66-byte output buffer. + * FIXED_SIZED_OUTPUT(len, bytes_out, WALLY_MUSIG_AGGNONCE_LEN) + */ +WALLY_CORE_API int wally_musig_aggnonce_serialize( + const struct wally_musig_aggnonce *nonce, + unsigned char *bytes_out, + size_t len); + +/** + * Free an aggnonce. + * + * :param nonce: The aggnonce to free. + */ +WALLY_CORE_API int wally_musig_aggnonce_free( + struct wally_musig_aggnonce *nonce); + +/** + * Free a session. + * + * :param session: The session to free. + */ +WALLY_CORE_API int wally_musig_session_free( + struct wally_musig_session *session); + +/** + * Serialize a session to its raw 133-byte form. + * + * :param session: The session to serialize. + * :param bytes_out: 133-byte output buffer. + * FIXED_SIZED_OUTPUT(len, bytes_out, WALLY_MUSIG_SESSION_LEN) + */ +WALLY_CORE_API int wally_musig_session_serialize( + const struct wally_musig_session *session, + unsigned char *bytes_out, + size_t len); + +/** + * Restore a session from its raw 133-byte form. + * + * WARNING: Do NOT call this on bytes from untrusted sources. The struct is not + * cryptographically validated; malformed bytes produce undefined signing behaviour. + * Only round-trip bytes produced by wally_musig_session_serialize(). + * + * :param bytes: The 133-byte serialized session. + * :param bytes_len: Length of bytes. Must be WALLY_MUSIG_SESSION_LEN. + * :param output: Destination for the allocated session. + */ +WALLY_CORE_API int wally_musig_session_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_session **output); + +/** + * Parse a partial signature from its 32-byte serialized form. + * + * :param bytes: The 32-byte serialized partial signature. + * :param bytes_len: Length of bytes. Must be WALLY_MUSIG_PARTIAL_SIG_LEN. + * :param output: Destination for the allocated partial_sig. + */ +WALLY_CORE_API int wally_musig_partial_sig_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_partial_sig **output); + +/** + * Serialize a partial signature to its 32-byte form. + * + * :param sig: The partial_sig to serialize. + * :param bytes_out: 32-byte output buffer. + * FIXED_SIZED_OUTPUT(len, bytes_out, WALLY_MUSIG_PARTIAL_SIG_LEN) + */ +WALLY_CORE_API int wally_musig_partial_sig_serialize( + const struct wally_musig_partial_sig *sig, + unsigned char *bytes_out, + size_t len); + +/** + * Free a partial signature. + * + * :param sig: The partial_sig to free. + */ +WALLY_CORE_API int wally_musig_partial_sig_free( + struct wally_musig_partial_sig *sig); + +/* --- Key aggregation functions --- */ + +/** + * Compute the MuSig2 aggregate public key from N individual public keys. + * + * :param pub_keys: Concatenated array of compressed public keys (each EC_PUBLIC_KEY_LEN bytes). + * :param pub_keys_len: Length of pub_keys. Must be a non-zero multiple of EC_PUBLIC_KEY_LEN. + * :param agg_pk_out: 32-byte buffer to receive the x-only aggregate public key. May be NULL. + * FIXED_SIZED_OUTPUT(agg_pk_out_len, agg_pk_out, EC_XONLY_PUBLIC_KEY_LEN) + * :param cache_out: Destination for the allocated keyagg_cache (required for signing). May be NULL. + */ +WALLY_CORE_API int wally_musig_pubkey_agg( + const unsigned char *pub_keys, + size_t pub_keys_len, + unsigned char *agg_pk_out, + size_t agg_pk_out_len, + struct wally_musig_keyagg_cache **cache_out); + +/** + * Extract the non-xonly (compressed) aggregate public key from a keyagg_cache. + * + * :param cache: The keyagg_cache produced by wally_musig_pubkey_agg. + * :param pub_key_out: 33-byte buffer to receive the compressed aggregate public key. + * FIXED_SIZED_OUTPUT(pub_key_out_len, pub_key_out, EC_PUBLIC_KEY_LEN) + */ +WALLY_CORE_API int wally_musig_pubkey_get( + const struct wally_musig_keyagg_cache *cache, + unsigned char *pub_key_out, + size_t pub_key_out_len); + +/** + * Apply BIP-32 plain EC tweaking to an aggregate key via the keyagg_cache. + * + * :param cache: The keyagg_cache to tweak (modified in place). + * :param tweak: 32-byte tweak value. + * :param tweak_len: Length of tweak. Must be 32. + * :param pub_key_out: 33-byte buffer for the tweaked compressed public key. May be NULL. + * FIXED_SIZED_OUTPUT(pub_key_out_len, pub_key_out, EC_PUBLIC_KEY_LEN) + */ +WALLY_CORE_API int wally_musig_pubkey_ec_tweak_add( + struct wally_musig_keyagg_cache *cache, + const unsigned char *tweak, + size_t tweak_len, + unsigned char *pub_key_out, + size_t pub_key_out_len); + +/** + * Apply BIP-341 x-only tweaking to an aggregate key via the keyagg_cache. + * + * :param cache: The keyagg_cache to tweak (modified in place). + * :param tweak: 32-byte tweak value. + * :param tweak_len: Length of tweak. Must be 32. + * :param pub_key_out: 33-byte buffer for the tweaked compressed public key. May be NULL. + * FIXED_SIZED_OUTPUT(pub_key_out_len, pub_key_out, EC_PUBLIC_KEY_LEN) + */ +WALLY_CORE_API int wally_musig_pubkey_xonly_tweak_add( + struct wally_musig_keyagg_cache *cache, + const unsigned char *tweak, + size_t tweak_len, + unsigned char *pub_key_out, + size_t pub_key_out_len); + +/** + * Construct a BIP-32 synthetic extended public key from a MuSig2 aggregate + * x-only public key, as specified by BIP-328. + * + * The chain code is the fixed constant SHA256("MuSig2MuSig2MuSig2"). The + * resulting ext_key has depth=0, child_num=0, and no parent fingerprint. + * Unhardened BIP-32 derivation (bip32_key_from_parent with + * BIP32_FLAG_KEY_PUBLIC) is supported on the output key. Hardened derivation + * is not possible (no private key). + * + * :param agg_pk: 32-byte x-only aggregate public key from wally_musig_pubkey_agg. + * :param agg_pk_len: Must be EC_XONLY_PUBLIC_KEY_LEN (32). + * :param version: BIP-32 version code. Use BIP32_VER_MAIN_PUBLIC or + * BIP32_VER_TEST_PUBLIC. + * :param output: Destination for the allocated ext_key. + */ +WALLY_CORE_API int wally_musig_pubkey_to_xpub( + const unsigned char *agg_pk, + size_t agg_pk_len, + uint32_t version, + struct ext_key **output); + +/** + * Derive child key from each xpub at child_num, sort derived pubkeys + * lexicographically (BIP-390), then aggregate. + * + * :param xpubs: Concatenated 78-byte serialized BIP-32 extended public keys. + * :param xpubs_len: Length of xpubs in bytes. Must be a multiple of + *| BIP32_SERIALIZED_LEN and at least 2 * BIP32_SERIALIZED_LEN. + * :param child_num: Unhardened child index to derive (< BIP32_INITIAL_HARDENED_CHILD). + * :param agg_pk_out: Destination for the 32-byte x-only aggregate pubkey, or NULL. + * FIXED_SIZED_OUTPUT(agg_pk_out_len, agg_pk_out, EC_XONLY_PUBLIC_KEY_LEN) + * :param cache_out: Destination for the allocated keyagg_cache, or NULL. + */ +WALLY_CORE_API int wally_musig_pubkeys_derive_then_agg( + const unsigned char *xpubs, + size_t xpubs_len, + uint32_t child_num, + unsigned char *agg_pk_out, + size_t agg_pk_out_len, + struct wally_musig_keyagg_cache **cache_out); + +/** + * Aggregate N pubkeys, construct BIP-328 synthetic xpub, then derive child_num. + * + * :param pub_keys: Concatenated 33-byte compressed public keys. + * :param pub_keys_len: Length of pub_keys. Must be a multiple of EC_PUBLIC_KEY_LEN + *| and at least 2 * EC_PUBLIC_KEY_LEN. + * :param version: BIP32_VER_MAIN_PUBLIC or BIP32_VER_TEST_PUBLIC. + * :param child_num: Unhardened child index to derive. + * :param pub_key_out: Destination for the 33-byte compressed child pubkey, or NULL. + * FIXED_SIZED_OUTPUT(pub_key_out_len, pub_key_out, EC_PUBLIC_KEY_LEN) + * :param child_out: Destination for the allocated child ext_key, or NULL. + */ +WALLY_CORE_API int wally_musig_pubkeys_agg_then_derive( + const unsigned char *pub_keys, + size_t pub_keys_len, + uint32_t version, + uint32_t child_num, + unsigned char *pub_key_out, + size_t pub_key_out_len, + struct ext_key **child_out); + +/* --- Nonce generation and aggregation functions --- */ + +/** + * Generate a MuSig2 secret/public nonce pair. + * + * :param session_secrand32: 32-byte unique random session ID. MUST NOT be reused. + * :param session_secrand_len: Must be 32. + * :param seckey: 32-byte secret key of the signer (optional, can be NULL). + * :param seckey_len: Must be 32 if seckey is non-NULL, 0 otherwise. + * :param pubkey33: 33-byte compressed public key of this signer (required). + * :param pubkey_len: Must be EC_PUBLIC_KEY_LEN (33). + * :param keyagg_cache: keyagg_cache from wally_musig_pubkey_agg (optional, can be NULL). + * :param msg32: 32-byte message to be signed, if known (optional, can be NULL). + * :param msg_len: Must be 32 if msg32 is non-NULL, 0 otherwise. + * :param extra_input32: 32-byte extra entropy input (optional, can be NULL). + * :param extra_len: Must be 32 if extra_input32 is non-NULL, 0 otherwise. + * :param secnonce_out: Destination for the allocated secret nonce. Must be kept secret. + * :param pubnonce_out: Destination for the allocated public nonce to send to cosigners. + */ +WALLY_CORE_API int wally_musig_nonce_gen( + const unsigned char *session_secrand32, + size_t session_secrand_len, + const unsigned char *seckey, + size_t seckey_len, + const unsigned char *pubkey33, + size_t pubkey_len, + const struct wally_musig_keyagg_cache *keyagg_cache, + const unsigned char *msg32, + size_t msg_len, + const unsigned char *extra_input32, + size_t extra_len, + struct wally_musig_secnonce **secnonce_out, + struct wally_musig_pubnonce **pubnonce_out); + +/** + * Generate a MuSig2 secret/public nonce pair using a counter-based session ID. + * + * WARNING: Nonce reuse in MuSig2 is catastrophic. Calling this function with + * the same (counter, seckey) pair more than once — across calls, sessions, or + * process restarts — leaks the private key. The counter MUST be stored durably + * and incremented before each signing session. See BIP-327 §Nonce generation, + * "Synthetic nonces". Prefer wally_musig_nonce_gen() for non-hardware-wallet use. + * + * This variant is intended for hardware wallets or deterministic signers that + * cannot generate random session IDs. The uint64_t counter is serialized as an + * 8-byte little-endian value, zero-padded to 32 bytes, and used as the + * session_id32. Per BIP-327, seckey MUST be provided when using a counter. + * + * :param counter: Monotonically increasing counter. Reuse with the same seckey + * leaks the private key. Persist and increment in durable storage. + * :param seckey: 32-byte secret key of the signer (REQUIRED for counter mode). + * :param seckey_len: Must be 32. + * :param pubkey33: 33-byte compressed public key of this signer (required). + * :param pubkey_len: Must be EC_PUBLIC_KEY_LEN (33). + * :param keyagg_cache: keyagg_cache from wally_musig_pubkey_agg (optional, can be NULL). + * :param msg32: 32-byte message to be signed, if known (optional, can be NULL). + * :param msg_len: Must be 32 if msg32 is non-NULL, 0 otherwise. + * :param extra_input32: 32-byte extra entropy input (optional, can be NULL). + * :param extra_len: Must be 32 if extra_input32 is non-NULL, 0 otherwise. + * :param secnonce_out: Destination for the allocated secret nonce. Must be kept secret. + * :param pubnonce_out: Destination for the allocated public nonce to send to cosigners. + */ +WALLY_CORE_API int wally_musig_nonce_gen_counter( + uint64_t counter, + const unsigned char *seckey, + size_t seckey_len, + const unsigned char *pubkey33, + size_t pubkey_len, + const struct wally_musig_keyagg_cache *keyagg_cache, + const unsigned char *msg32, + size_t msg_len, + const unsigned char *extra_input32, + size_t extra_len, + struct wally_musig_secnonce **secnonce_out, + struct wally_musig_pubnonce **pubnonce_out); + +/** + * Aggregate N serialized public nonces into a single aggregate nonce. + * + * :param pubnonces: Flat array of serialized pubnonces (each WALLY_MUSIG_PUBNONCE_LEN bytes). + * :param pubnonces_len: Total byte length. Must equal n_pubnonces * WALLY_MUSIG_PUBNONCE_LEN. + * :param n_pubnonces: Number of pubnonces. Must be >= 2. + * :param aggnonce_out: Destination for the allocated aggregate nonce. + */ +WALLY_CORE_API int wally_musig_nonce_agg( + const unsigned char *pubnonces, + size_t pubnonces_len, + size_t n_pubnonces, + struct wally_musig_aggnonce **aggnonce_out); + +/* --- Signing and verification functions --- */ + +/** + * Process the aggregate nonce and message to create a signing session. + * + * Must be called by every participant after nonce aggregation and before signing. + * + * :param aggnonce: The aggregate nonce from wally_musig_nonce_agg. + * :param msg32: The 32-byte message to sign. + * :param msg32_len: Must be 32. + * :param cache: The keyagg_cache from wally_musig_pubkey_agg (and optional tweaks). + * :param adaptor: Optional 33-byte compressed adaptor public key (can be NULL). + * :param adaptor_len: Must be EC_PUBLIC_KEY_LEN if adaptor is non-NULL, 0 otherwise. + * :param session_out: Destination for the allocated session. + */ +WALLY_CORE_API int wally_musig_nonce_process( + const struct wally_musig_aggnonce *aggnonce, + const unsigned char *msg32, + size_t msg32_len, + const struct wally_musig_keyagg_cache *cache, + const unsigned char *adaptor, + size_t adaptor_len, + struct wally_musig_session **session_out); + +/** + * Produce a partial signature for this participant. + * + * WARNING: The secnonce is irrevocably zeroed whenever secp256k1_musig_partial_sign + * is reached (i.e., when WALLY_OK or WALLY_ERROR is returned). Input validation + * failures (WALLY_EINVAL) do not consume the secnonce. Never attempt to sign + * twice with the same secnonce. + * + * :param secnonce: The secret nonce from wally_musig_nonce_gen (zeroed after use). + * :param seckey: The 32-byte secret key of this signer. + * :param seckey_len: Must be 32. + * :param cache: The keyagg_cache from wally_musig_pubkey_agg. + * :param session: The session from wally_musig_nonce_process. + * :param partial_sig_out: Destination for the allocated partial signature. + */ +WALLY_CORE_API int wally_musig_partial_sign( + struct wally_musig_secnonce *secnonce, + const unsigned char *seckey, + size_t seckey_len, + const struct wally_musig_keyagg_cache *cache, + const struct wally_musig_session *session, + struct wally_musig_partial_sig **partial_sig_out); + +/** + * Verify a partial signature from one participant. + * + * Returns WALLY_OK if valid, WALLY_ERROR if the signature is invalid, + * WALLY_EINVAL for bad arguments. + * + * :param sig: The partial signature to verify. + * :param pubnonce: The signer's public nonce (from round 1). + * :param pubkey: The signer's 33-byte compressed public key. + * :param pubkey_len: Must be EC_PUBLIC_KEY_LEN (33). + * :param cache: The keyagg_cache from wally_musig_pubkey_agg. + * :param session: The session from wally_musig_nonce_process. + */ +WALLY_CORE_API int wally_musig_partial_sig_verify( + const struct wally_musig_partial_sig *sig, + const struct wally_musig_pubnonce *pubnonce, + const unsigned char *pubkey, + size_t pubkey_len, + const struct wally_musig_keyagg_cache *cache, + const struct wally_musig_session *session); + +/** + * Aggregate N partial signatures into a final 64-byte BIP-340 Schnorr signature. + * + * :param partial_sigs: Flat array of serialized partial signatures + * (each WALLY_MUSIG_PARTIAL_SIG_LEN bytes). + * :param partial_sigs_len: Total byte length. Must equal n_sigs * WALLY_MUSIG_PARTIAL_SIG_LEN. + * :param n_sigs: Number of partial signatures. Must be >= 2. + * :param session: The session from wally_musig_nonce_process. + * :param sig64_out: 64-byte buffer to receive the final Schnorr signature. + * FIXED_SIZED_OUTPUT(sig64_out_len, sig64_out, EC_SIGNATURE_LEN) + */ +WALLY_CORE_API int wally_musig_partial_sig_agg( + const unsigned char *partial_sigs, + size_t partial_sigs_len, + size_t n_sigs, + const struct wally_musig_session *session, + unsigned char *sig64_out, + size_t sig64_out_len); + +#endif /* ndef BUILD_STANDARD_SECP */ + +#ifdef __cplusplus +} +#endif + +#endif /* LIBWALLY_CORE_MUSIG_H */ diff --git a/include/wally_psbt.h b/include/wally_psbt.h index cd01d1500..412f558be 100644 --- a/include/wally_psbt.h +++ b/include/wally_psbt.h @@ -9,6 +9,15 @@ extern "C" { #endif +/** An opaque type holding a parsed minscript/descriptor expression */ +struct wally_descriptor; + +/* Forward declarations for MuSig2 opaque types used in PSBT functions */ +struct wally_musig_keyagg_cache; +struct wally_musig_secnonce; +struct wally_musig_partial_sig; + + /* PSBT Version number */ #define WALLY_PSBT_VERSION_0 0x0 #define WALLY_PSBT_VERSION_2 0x2 @@ -88,6 +97,9 @@ struct wally_psbt_input { /* Hashes and paths for taproot bip32 derivation path */ struct wally_map taproot_leaf_hashes; struct wally_map taproot_leaf_paths; + struct wally_map musig2_pubkeys; /* BIP-373: agg pubkey -> participant pubkeys */ + struct wally_map musig2_pubnonces; /* BIP-373: (participant||agg[||leaf]) -> pubnonce */ + struct wally_map musig2_partial_sigs; /* BIP-373: (participant||agg[||leaf]) -> partial_sig */ #ifndef WALLY_ABI_NO_ELEMENTS uint64_t issuance_amount; /* Issuance amount, or 0 if not given */ uint64_t inflation_keys; /* Number of reissuance tokens, or 0 if none given */ @@ -115,6 +127,7 @@ struct wally_psbt_output { /* Hashes and paths for taproot bip32 derivation path */ struct wally_map taproot_leaf_hashes; struct wally_map taproot_leaf_paths; + struct wally_map musig2_pubkeys; /* BIP-373: agg pubkey -> participant pubkeys */ #ifndef WALLY_ABI_NO_ELEMENTS uint32_t blinder_index; /* Index of the input whose owner should blind this output */ uint32_t has_blinder_index; @@ -371,6 +384,221 @@ WALLY_CORE_API int wally_psbt_input_set_taproot_internal_key( const unsigned char *pub_key, size_t pub_key_len); +/** + * Add a taproot leaf script (TAP_LEAF_SCRIPT) to a PSBT input. + * + * :param input: The input to update. + * :param control_block: The BIP-341 control block for the leaf. + * :param control_block_len: Length of ``control_block`` in bytes. + * :param script: The leaf script bytes. + * :param script_len: Length of ``script`` in bytes. Must be non-zero. + */ +WALLY_CORE_API int wally_psbt_input_add_taproot_leaf_script( + struct wally_psbt_input *input, + const unsigned char *control_block, + size_t control_block_len, + const unsigned char *script, + size_t script_len); + +/** + * Get the number of taproot leaf scripts in a PSBT input. + * + * :param input: The input to query. + * :param written: Destination for the count. + */ +WALLY_CORE_API int wally_psbt_input_get_taproot_leaf_script_count( + const struct wally_psbt_input *input, + size_t *written); + +/** + * Add a taproot script-path signature (TAP_SCRIPT_SIG) to a PSBT input. + * + * :param input: The input to update. + * :param pubkey_and_hash: Concatenation of x-only pubkey (32 bytes) and leaf hash (32 bytes). + * :param pubkey_and_hash_len: Must be 64. + * :param sig: The 64 or 65-byte Schnorr signature. + * :param sig_len: Length of ``sig``. + */ +WALLY_CORE_API int wally_psbt_input_add_taproot_leaf_signature( + struct wally_psbt_input *input, + const unsigned char *pubkey_and_hash, + size_t pubkey_and_hash_len, + const unsigned char *sig, + size_t sig_len); + +/** + * Get the number of taproot script-path signatures in a PSBT input. + * + * :param input: The input to query. + * :param written: Destination for the count. + */ +WALLY_CORE_API int wally_psbt_input_get_taproot_leaf_signature_count( + const struct wally_psbt_input *input, + size_t *written); + +/** + * Add or replace a musig2 participant pubkeys entry in an input. + * + * :param input: The input to update. + * :param agg_pubkey: The 33-byte compressed aggregate public key (map key). + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be `EC_PUBLIC_KEY_LEN`. + * :param participants: Concatenated 33-byte compressed participant public keys. + * :param participants_len: Length of ``participants``. Must be a multiple of + *| `EC_PUBLIC_KEY_LEN` and at least ``2 * EC_PUBLIC_KEY_LEN``. + */ +WALLY_CORE_API int wally_psbt_input_add_musig2_participant_pubkeys( + struct wally_psbt_input *input, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *participants, + size_t participants_len); + +/** + * Find a musig2 participant pubkeys entry in an input by aggregate pubkey. + * + * :param input: The input to search. + * :param agg_pubkey: The 33-byte compressed aggregate public key to look up. + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be `EC_PUBLIC_KEY_LEN`. + * :param written: On success, set to zero if not found, otherwise the 1-based index. + */ +WALLY_CORE_API int wally_psbt_input_find_musig2_pubkey( + const struct wally_psbt_input *input, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + size_t *written); + +/** + * Set the musig2 participant pubkeys map in an input. + * + * :param input: The input to update. + * :param map_in: Map of agg pubkey to participant pubkeys entries. + */ +WALLY_CORE_API int wally_psbt_input_set_musig2_pubkeys( + struct wally_psbt_input *input, + const struct wally_map *map_in); + +/** + * Add a MuSig2 pubnonce to an input. + * + * :param input: The input to update. + * :param participant: The participant's compressed public key (33 bytes). + * :param participant_len: Length of ``participant``. Must be `EC_PUBLIC_KEY_LEN`. + * :param agg_pubkey: The aggregate public key (33 bytes). + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be `EC_PUBLIC_KEY_LEN`. + * :param leaf_hash: The tapscript leaf hash (32 bytes) or NULL for key-path. + * :param leaf_hash_len: Length of ``leaf_hash``. Must be `SHA256_LEN` or 0. + * :param pubnonce: The 66-byte serialized pubnonce. + * :param pubnonce_len: Length of ``pubnonce``. Must be `WALLY_MUSIG_PUBNONCE_LEN`. + */ +WALLY_CORE_API int wally_psbt_input_add_musig2_pubnonce( + struct wally_psbt_input *input, + const unsigned char *participant, + size_t participant_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + const unsigned char *pubnonce, + size_t pubnonce_len); + +/** + * Find a MuSig2 pubnonce in an input. + * + * :param input: The input to search in. + * :param participant: The participant's compressed public key (33 bytes). + * :param participant_len: Length of ``participant``. Must be `EC_PUBLIC_KEY_LEN`. + * :param agg_pubkey: The aggregate public key (33 bytes). + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be `EC_PUBLIC_KEY_LEN`. + * :param leaf_hash: The tapscript leaf hash (32 bytes) or NULL for key-path. + * :param leaf_hash_len: Length of ``leaf_hash``. Must be `SHA256_LEN` or 0. + * :param written: On success, set to zero if not found, otherwise the 1-based index. + */ +WALLY_CORE_API int wally_psbt_input_find_musig2_pubnonce( + const struct wally_psbt_input *input, + const unsigned char *participant, + size_t participant_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + size_t *written); + +/** + * Get the number of MuSig2 pubnonces in an input. + * + * :param input: The input to query. + * :param written: On success, set to the number of pubnonce entries. + */ +WALLY_CORE_API int wally_psbt_input_get_musig2_pubnonce_count( + const struct wally_psbt_input *input, + size_t *written); + +/** + * Get the number of TAP_BIP32_DERIVATION entries in a PSBT input. + * + * :param input: The input to query. + * :param written: Destination for the count. + */ +WALLY_CORE_API int wally_psbt_input_get_taproot_keypaths_size( + const struct wally_psbt_input *input, + size_t *written); + +/** + * Add a MuSig2 partial signature to an input. + * + * :param input: The input to update. + * :param participant: The participant's compressed public key (33 bytes). + * :param participant_len: Length of ``participant``. Must be `EC_PUBLIC_KEY_LEN`. + * :param agg_pubkey: The aggregate public key (33 bytes). + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be `EC_PUBLIC_KEY_LEN`. + * :param leaf_hash: The tapscript leaf hash (32 bytes) or NULL for key-path. + * :param leaf_hash_len: Length of ``leaf_hash``. Must be `SHA256_LEN` or 0. + * :param partial_sig: The 32-byte partial signature. + * :param partial_sig_len: Length of ``partial_sig``. Must be `WALLY_MUSIG_PARTIAL_SIG_LEN`. + */ +WALLY_CORE_API int wally_psbt_input_add_musig2_partial_sig( + struct wally_psbt_input *input, + const unsigned char *participant, + size_t participant_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + const unsigned char *partial_sig, + size_t partial_sig_len); + +/** + * Find a MuSig2 partial signature in an input. + * + * :param input: The input to search in. + * :param participant: The participant's compressed public key (33 bytes). + * :param participant_len: Length of ``participant``. Must be `EC_PUBLIC_KEY_LEN`. + * :param agg_pubkey: The aggregate public key (33 bytes). + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be `EC_PUBLIC_KEY_LEN`. + * :param leaf_hash: The tapscript leaf hash (32 bytes) or NULL for key-path. + * :param leaf_hash_len: Length of ``leaf_hash``. Must be `SHA256_LEN` or 0. + * :param written: On success, set to zero if not found, otherwise the 1-based index. + */ +WALLY_CORE_API int wally_psbt_input_find_musig2_partial_sig( + const struct wally_psbt_input *input, + const unsigned char *participant, + size_t participant_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + size_t *written); + +/** + * Get the number of MuSig2 partial signatures in an input. + * + * :param input: The input to query. + * :param written: On success, set to the number of partial signature entries. + */ +WALLY_CORE_API int wally_psbt_input_get_musig2_partial_sig_count( + const struct wally_psbt_input *input, + size_t *written); + /** * Find a partial signature matching a pubkey in an input. * @@ -473,6 +701,47 @@ WALLY_CORE_API int wally_psbt_input_set_required_lockheight( WALLY_CORE_API int wally_psbt_input_clear_required_lockheight( struct wally_psbt_input *input); +/** + * Populate taproot PSBT input fields from a tr() descriptor. + * + * Sets TAP_INTERNAL_KEY, TAP_LEAF_SCRIPT entries, TAP_BIP32_DERIVATION + * entries, and TAP_MERKLE_ROOT on the given input. Does not sign. + * + * :param psbt: The PSBT to update. + * :param index: The input index to populate. + * :param descriptor: A parsed tr() descriptor. + * :param multi_index: Multi-path index for descriptor derivation. + * :param child_num: BIP-32 child derivation index for variable keys. + * :param flags: Must be zero. + */ +WALLY_CORE_API int wally_psbt_input_set_taproot_from_descriptor( + struct wally_psbt *psbt, + size_t index, + const struct wally_descriptor *descriptor, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags); + +/** + * Populate taproot PSBT output fields from a tr() descriptor. + * + * Sets TAP_INTERNAL_KEY and TAP_BIP32_DERIVATION entries on the given output. + * + * :param psbt: The PSBT to update. + * :param index: The output index to populate. + * :param descriptor: A parsed tr() descriptor. + * :param multi_index: Multi-path index for descriptor derivation. + * :param child_num: BIP-32 child derivation index for variable keys. + * :param flags: Must be zero. + */ +WALLY_CORE_API int wally_psbt_output_set_taproot_from_descriptor( + struct wally_psbt *psbt, + size_t index, + const struct wally_descriptor *descriptor, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags); + #ifndef WALLY_ABI_NO_ELEMENTS /** * Set the unblinded amount in an input. @@ -1450,6 +1719,46 @@ WALLY_CORE_API int wally_psbt_output_set_taproot_internal_key( const unsigned char *pub_key, size_t pub_key_len); +/** + * Add or replace a musig2 participant pubkeys entry in an output. + * + * :param output: The output to update. + * :param agg_pubkey: The 33-byte compressed aggregate public key (map key). + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be `EC_PUBLIC_KEY_LEN`. + * :param participants: Concatenated 33-byte compressed participant public keys. + * :param participants_len: Length of ``participants``. Must be a multiple of + *| `EC_PUBLIC_KEY_LEN` and at least ``2 * EC_PUBLIC_KEY_LEN``. + */ +WALLY_CORE_API int wally_psbt_output_add_musig2_participant_pubkeys( + struct wally_psbt_output *output, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *participants, + size_t participants_len); + +/** + * Find a musig2 participant pubkeys entry in an output by aggregate pubkey. + * + * :param output: The output to search. + * :param agg_pubkey: The 33-byte compressed aggregate public key to look up. + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be `EC_PUBLIC_KEY_LEN`. + * :param written: On success, set to zero if not found, otherwise the 1-based index. + */ +WALLY_CORE_API int wally_psbt_output_find_musig2_pubkey( + const struct wally_psbt_output *output, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + size_t *written); + +/** + * Set the musig2 participant pubkeys map in an output. + * + * :param output: The output to update. + * :param map_in: Map of agg pubkey to participant pubkeys entries. + */ +WALLY_CORE_API int wally_psbt_output_set_musig2_pubkeys( + struct wally_psbt_output *output, + const struct wally_map *map_in); #ifndef WALLY_ABI_NO_ELEMENTS /** @@ -2745,6 +3054,147 @@ WALLY_CORE_API int wally_psbt_is_elements( const struct wally_psbt *psbt, size_t *written); +/** + * Populate MuSig2 PSBT fields from a musig() descriptor. + * + * For each input and output in the PSBT, if the descriptor contains a musig() + * key expression, this function populates: + * + * - `PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS`: aggregate pubkey to participant pubkeys map + * - `PSBT_IN_TAP_BIP32_DERIVATION`: for each participant (x-only pubkey + derivation path) + * - `PSBT_IN_TAP_INTERNAL_KEY`: x-only aggregate pubkey (untweaked) + * - `PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS`: for each output + * + * :param psbt: The PSBT to populate. + * :param descriptor: The parsed descriptor containing musig() expressions. + * :param child_num: The BIP32 child number for ranged descriptors. + * :param flags: For future use, pass 0. + */ +WALLY_CORE_API int wally_psbt_populate_musig2_from_descriptor( + struct wally_psbt *psbt, + const struct wally_descriptor *descriptor, + uint32_t child_num, + uint32_t flags); + +/** + * Generate a MuSig2 nonce for a PSBT input and store it in the PSBT. + * + * Computes the input sighash (when available) and uses it as the message in + * nonce generation (per BIP-327). The resulting public nonce is stored in the + * PSBT under the composite key ``participant || agg_pubkey [|| leaf_hash]``. + * The secret nonce is returned to the caller, who MUST store it securely and + * supply it during Round 2 partial signing. The secret nonce MUST NOT be used + * more than once. + * + * Returns WALLY_ERROR if a pubnonce already exists for this participant/key + * combination (nonce reuse prevention). + * + * :param psbt: The PSBT to generate a nonce for. + * :param index: The zero-based index of the input to generate a nonce for. + * :param session_secrand32: 32 bytes of cryptographically secure random data. + *| This value MUST NOT be reused across signing sessions. + * :param session_secrand_len: Length of ``session_secrand32``. Must be 32. + * :param seckey: The signer's 32-byte private key (optional, improves security). + * :param seckey_len: Length of ``seckey``. Must be ``EC_PRIVATE_KEY_LEN`` or 0. + * :param pubkey33: The signer's 33-byte compressed public key (participant key). + * :param pubkey33_len: Length of ``pubkey33``. Must be ``EC_PUBLIC_KEY_LEN``. + * :param agg_pubkey: The 33-byte aggregate public key for this musig() group. + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be ``EC_PUBLIC_KEY_LEN``. + * :param leaf_hash: Optional 32-byte tapscript leaf hash for script-path spends. + * :param leaf_hash_len: Length of ``leaf_hash``. Must be ``SHA256_LEN`` or 0. + * :param keyagg_cache: Optional keyagg cache for this musig() group. + * :param flags: For future use, pass 0. + * :param secnonce_out: Destination for the secret nonce. The caller owns this + *| and must free it with `wally_musig_secnonce_free`. MUST NOT be reused. + */ +WALLY_CORE_API int wally_psbt_musig2_add_nonce( + struct wally_psbt *psbt, + size_t index, + const unsigned char *session_secrand32, + size_t session_secrand_len, + const unsigned char *seckey, + size_t seckey_len, + const unsigned char *pubkey33, + size_t pubkey33_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + const struct wally_musig_keyagg_cache *keyagg_cache, + uint32_t flags, + struct wally_musig_secnonce **secnonce_out); + +/** + * Produce a MuSig2 partial signature for a PSBT input (Round 2). + * + * Collects all public nonces for the given musig() group from the PSBT, + * aggregates them, processes the signing session with the input sighash, + * and produces a partial signature. The partial signature is stored in the + * PSBT under the composite key ``participant || agg_pubkey [|| leaf_hash]``. + * The secret nonce is zeroed after use (nonce reuse prevention). + * + * Returns ``WALLY_ERROR`` if any participant's pubnonce is missing. + * + * :param psbt: The PSBT to sign. + * :param index: The zero-based index of the input to sign. + * :param secnonce: The secret nonce from Round 1. Zeroed after use. + * :param seckey: The signer's 32-byte private key. + * :param seckey_len: Length of ``seckey``. Must be ``EC_PRIVATE_KEY_LEN``. + * :param pubkey33: The signer's 33-byte compressed public key (participant key). + * :param pubkey33_len: Length of ``pubkey33``. Must be ``EC_PUBLIC_KEY_LEN``. + * :param agg_pubkey: The 33-byte aggregate public key for this musig() group. + * :param agg_pubkey_len: Length of ``agg_pubkey``. Must be ``EC_PUBLIC_KEY_LEN``. + * :param leaf_hash: Optional 32-byte tapscript leaf hash for script-path spends. + * :param leaf_hash_len: Length of ``leaf_hash``. Must be ``SHA256_LEN`` or 0. + * :param keyagg_cache: The keyagg cache for this musig() group (with tweaks applied). + * :param flags: For future use, pass 0. + * :param partial_sig_out: Optional destination for the partial signature. The caller + *| owns this and must free it with `wally_musig_partial_sig_free`. Pass NULL + *| if not needed (the sig is always stored in the PSBT). + */ +WALLY_CORE_API int wally_psbt_musig2_sign( + struct wally_psbt *psbt, + size_t index, + struct wally_musig_secnonce *secnonce, + const unsigned char *seckey, + size_t seckey_len, + const unsigned char *pubkey33, + size_t pubkey33_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + const struct wally_musig_keyagg_cache *keyagg_cache, + uint32_t flags, + struct wally_musig_partial_sig **partial_sig_out); + +/** + * Aggregate MuSig2 partial signatures for one input and store the result + * as PSBT_IN_TAP_KEY_SIG (keypath spend) or in taproot_leaf_signatures + * (script path spend, when leaf_hash is non-NULL). + * + * MUST be called before wally_psbt_finalize_input(), and only after all + * participants have added their partial signatures via wally_psbt_musig2_sign(). + * + * :param psbt: The PSBT containing the input. + * :param index: The input index. + * :param agg_pubkey: 33-byte compressed aggregate public key. + * :param agg_pubkey_len: Must be EC_PUBLIC_KEY_LEN (33). + * :param leaf_hash: Optional 32-byte tapleaf hash (NULL for keypath spend). + * :param leaf_hash_len: Must be SHA256_LEN if leaf_hash is non-NULL, 0 otherwise. + * :param keyagg_cache: The keyagg_cache used during signing (with tweaks applied). + * :param flags: Must be 0. + */ +WALLY_CORE_API int wally_psbt_musig2_finalize_input( + struct wally_psbt *psbt, + size_t index, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + const struct wally_musig_keyagg_cache *keyagg_cache, + uint32_t flags); + #ifdef __cplusplus } #endif diff --git a/include/wally_script.h b/include/wally_script.h index d032ef36a..563ff97ce 100644 --- a/include/wally_script.h +++ b/include/wally_script.h @@ -27,6 +27,8 @@ extern "C" { #define WALLY_SCRIPTPUBKEY_P2WSH_LEN 34 /** OP_0 [SHA256] */ #define WALLY_SCRIPTPUBKEY_P2TR_LEN 34 /** OP_1 [X-ONLY-PUBKEY] */ +#define WALLY_LEAF_VERSION_TAPSCRIPT 0xc0 /** BIP-342 tapscript leaf version */ + #define WALLY_SCRIPTPUBKEY_OP_RETURN_MAX_LEN 83 /** OP_RETURN [80 bytes of data] */ #define WALLY_MAX_OP_RETURN_LEN 80 /* Maximum length of OP_RETURN data push */ @@ -171,6 +173,8 @@ extern "C" { #define OP_NOP9 0xb8 #define OP_NOP10 0xb9 +#define OP_CHECKSIGADD 0xba /* BIP-342 tapscript */ + #define OP_INVALIDOPCODE 0xff #endif /* WALLY_DISABLE_OP_CODE */ diff --git a/src/Makefile.am b/src/Makefile.am index 977593a51..9e9144147 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,7 @@ include_HEADERS = include_HEADERS += $(top_srcdir)/include/wally.hpp include_HEADERS += $(top_srcdir)/include/wally_address.h include_HEADERS += $(top_srcdir)/include/wally_anti_exfil.h +include_HEADERS += $(top_srcdir)/include/wally_musig.h include_HEADERS += $(top_srcdir)/include/wally_bip32.h include_HEADERS += $(top_srcdir)/include/wally_bip38.h include_HEADERS += $(top_srcdir)/include/wally_bip39.h @@ -152,6 +153,8 @@ libwallycore_la_SOURCES = \ bech32.c \ coins.c \ descriptor.c \ + miniscript_decode.c \ + miniscript_satisfy.c \ ecdh.c \ elements.c \ blech32.c \ @@ -160,6 +163,7 @@ libwallycore_la_SOURCES = \ internal.c \ map.c \ mnemonic.c \ + musig.c \ pbkdf2.c \ psbt.c \ pullpush.c \ @@ -181,6 +185,7 @@ libwallycore_la_INCLUDES = \ include/wally.hpp \ include/wally_address.h \ include/wally_anti_exfil.h \ + include/wally_musig.h \ include/wally_bip32.h \ include/wally_bip38.h \ include/wally_bip39.h \ @@ -285,6 +290,11 @@ test_descriptor_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_descriptor_LDADD += $(PYTHON_LIBS) endif +TESTS += test_miniscript_decode +noinst_PROGRAMS += test_miniscript_decode +test_miniscript_decode_SOURCES = ctest/test_miniscript_decode.c miniscript_decode.c miniscript_satisfy.c ctest/scriptint_shim.c ctest/satisfy_shim.c +test_miniscript_decode_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/include -I$(top_srcdir)/src $(libsecp256k1_CFLAGS) $(AM_CFLAGS) +test_miniscript_decode_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if BUILD_ELEMENTS TESTS += test_elements_tx noinst_PROGRAMS += test_elements_tx @@ -339,6 +349,11 @@ check-libwallycore: $(PYTHON_TEST_DEPS) $(AM_V_at)$(PYTHON_TEST) test/test_internal.py $(AM_V_at)$(PYTHON_TEST) test/test_map.py $(AM_V_at)$(PYTHON_TEST) test/test_mnemonic.py + $(AM_V_at)$(PYTHON_TEST) test/test_bip379_vectors.py + $(AM_V_at)$(PYTHON_TEST) test/test_satisfier_diff.py + $(AM_V_at)$(PYTHON_TEST) test/test_musig.py + $(AM_V_at)$(PYTHON_TEST) test/test_musig_vectors.py + $(AM_V_at)$(PYTHON_TEST) test/test_musig_interop.py $(AM_V_at)$(PYTHON_TEST) test/test_psbt.py $(AM_V_at)$(PYTHON_TEST) test/test_pbkdf2.py $(AM_V_at)$(PYTHON_TEST) test/test_script.py diff --git a/src/amalgamation/combined.c b/src/amalgamation/combined.c index 7952e1652..3964710e8 100644 --- a/src/amalgamation/combined.c +++ b/src/amalgamation/combined.c @@ -5,6 +5,13 @@ #define ENABLE_MODULE_ECDH 1 #define ENABLE_MODULE_EXTRAKEYS 1 #define ENABLE_MODULE_SCHNORRSIG 1 +/* MuSig2 follows the standard --enable-standard-secp / -DBUILD_STANDARD_SECP + * knob: enabled here by default, dropped when a consumer defines + * BUILD_STANDARD_SECP (which also compiles out wally's musig/s2c/anti-exfil + * layers). Taproot (schnorr/bip341) is unaffected either way. */ +#ifndef BUILD_STANDARD_SECP +#define ENABLE_MODULE_MUSIG 1 +#endif #define ENABLE_MODULE_GENERATOR 1 #define ENABLE_MODULE_ECDSA_S2C 1 #define ENABLE_MODULE_RECOVERY 1 @@ -68,7 +75,12 @@ #include "src/hex_.c" #include "src/hmac.c" #include "src/map.c" +#include "src/miniscript_decode.c" +#include "src/miniscript_satisfy.c" #include "src/mnemonic.c" +#ifndef BUILD_STANDARD_SECP +#include "src/musig.c" +#endif #include "src/pbkdf2.c" #include "src/pullpush.c" #include "src/psbt.c" diff --git a/src/ctest/satisfy_shim.c b/src/ctest/satisfy_shim.c new file mode 100644 index 000000000..9661f605c --- /dev/null +++ b/src/ctest/satisfy_shim.c @@ -0,0 +1,42 @@ +/* Local copies of satisfaction lifecycle functions from descriptor.c. + * These are internal to the library (hidden in the DSO) so the test + * binary needs its own copy when compiling miniscript_satisfy.c. */ +#include "config.h" +#include "descriptor_int.h" +#include +#include + +int ms_witness_init(ms_witness *w, uint32_t kind) +{ + memset(w, 0, sizeof(*w)); + w->kind = kind; + return WALLY_OK; +} + +void ms_witness_free(ms_witness *w) +{ + if (w) { + size_t i; + for (i = 0; i < w->num_items; i++) + wally_free(w->items[i].data); + wally_free(w->items); + memset(w, 0, sizeof(*w)); + } +} + +int ms_satisfaction_init(ms_satisfaction *s, uint32_t witness_kind) +{ + int ret = ms_witness_init(&s->witness, witness_kind); + s->has_sig = false; + s->absolute_timelock = 0; + s->relative_timelock = 0; + return ret; +} + +void ms_satisfaction_free(ms_satisfaction *s) +{ + if (s) { + ms_witness_free(&s->witness); + memset(s, 0, sizeof(*s)); + } +} diff --git a/src/ctest/scriptint_shim.c b/src/ctest/scriptint_shim.c new file mode 100644 index 000000000..e4a05b93d --- /dev/null +++ b/src/ctest/scriptint_shim.c @@ -0,0 +1,30 @@ +/* Provides scriptint_from_bytes for test_miniscript_decode. + * This function is internal to the library (hidden in the DSO) so + * the test binary needs its own copy when compiling miniscript_decode.c. */ +#include "config.h" +#include +#include "script_int.h" +#include + +int64_t scriptint_from_bytes(const unsigned char *bytes, size_t len, int64_t *value_out) +{ + int64_t mask = 0x80; + size_t i; + + if (value_out) + *value_out = 0; + + if (!bytes || len < 1 || len <= bytes[0] || bytes[0] > 4 || !value_out) + return WALLY_EINVAL; + + for (i = 0; i < bytes[0]; ++i) { + *value_out |= (int64_t)(bytes[i + 1]) << (8 * i); + mask <<= 8; + } + + if (bytes[i] & 0x80) { + *value_out ^= (mask >> 8); + *value_out = -*value_out; + } + return WALLY_OK; +} diff --git a/src/ctest/test_descriptor.c b/src/ctest/test_descriptor.c index 93cb74428..696cab8d7 100644 --- a/src/ctest/test_descriptor.c +++ b/src/ctest/test_descriptor.c @@ -414,6 +414,36 @@ static const struct descriptor_test { WALLY_NETWORK_BITCOIN_REGTEST, 0, 0, 0, NULL, 0, "51205fb8e39dbbdc7c831af59e44a9b2997f9daaf72c3e965b30982f3c731539e1db", "tp2ky708", VARS_STD + },{ + "descriptor - tr - single leaf pk", + "tr(x_only,pk(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "5120951b6ab79b75bf3083163e8c4a3df1cba0928e07b3b2e3732503bb7fe6df804b", + "", VARS_STD + },{ + "descriptor - tr - balanced 2-leaf", + "tr(x_only,{pk(key_1),pk(key_2)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "512082a4c5d240cadcf568140691f751370be05e3da59df98c3b1e92a37f1bfd7dfe", + "", VARS_STD + },{ + "descriptor - tr - unbalanced 3-leaf", + "tr(x_only,{pk(key_1),{pk(key_2),pk(key_3)}})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "51201edef6eaf60517b880b7c721436840e45c487f4f7d4b544848a1fa8ecae1a146", + "", VARS_STD + },{ + "descriptor - tr - multi_a leaf", + "tr(x_only,multi_a(2,key_1,key_2,key_3))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "5120e96b74eb71c05f7362ab7977c829d78256685d87fc4e1e44545146466caedd19", + "", VARS_STD + },{ + "descriptor - tr - mixed multi_a and and_v", + "tr(x_only,{multi_a(2,key_1,key_2,key_3),and_v(v:pk(key_1),older(52560))})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "51207b56ea61956475f5751c4da934cd2ac20d3088f327c60ffe249bc7a66b9952b0", + "", VARS_STD }, #ifdef BUILD_ELEMENTS /* Elements/Confidential descriptors */ @@ -980,10 +1010,54 @@ static const struct descriptor_test { "5192", /* 1 OP_0NOTEQUAL */ "d959hk4q", VARS_STD }, + { + "miniscript - pk_k segwit v0", + "c:pk_k(key_1)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "", VARS_STD + }, + { + "miniscript - pk_h segwit v0", + "c:pk_h(key_1)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "76a914d0721279e70d39fb4aa409b52839a0056454e3b588ac", + "", VARS_STD + }, { + "miniscript - sha256 segwit v0", + "sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed287", + "", VARS_STD + }, { + "miniscript - hash256 segwit v0", + "hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b87", + "", VARS_STD + }, { + "miniscript - ripemd160 segwit v0", + "ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87", + "", VARS_STD + }, { + "miniscript - hash160 segwit v0", + "hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a91420195b5a3d650c17f0f29f91c33f8f6335193d0787", + "", VARS_STD + }, /* * Miniscript taproot cases */ { + "miniscript - pk_k tapscript x-only", + "c:pk_k(x_only)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY | WALLY_MINISCRIPT_TAPSCRIPT, + "20b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0eac", + "", VARS_STD + }, { "miniscript - taproot raw pubkey", "c:pk_k(daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729)", WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY | WALLY_MINISCRIPT_TAPSCRIPT, @@ -1399,10 +1473,6 @@ static const struct descriptor_test { "descriptor - empty tr", "tr()", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD - },{ - "descriptor - tr - multi-child", - "tr(x_only,x_only)", /* FIXME: delete this case when script path is supported */ - WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ "descriptor - tr - any parent", "sh(tr(x_only))", @@ -1419,6 +1489,46 @@ static const struct descriptor_test { "descriptor - tr - invalid public key", "tr(uncompresseduncompressed)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - multi() fragment not allowed in tapscript", + "tr(x_only,multi(2,key_1,key_2))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - single element in braces not allowed", + "tr(x_only,{pk(key_1)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - three elements in braces not allowed", + "tr(x_only,{pk(key_1),pk(key_2),pk(key_3)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - empty braces not allowed", + "tr(x_only,{})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - wsh() inside tr not allowed", + "tr(x_only,wsh(pk(key_1)))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - tr() inside tr not allowed", + "tr(x_only,tr(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - wsh() inside taptree leaf not allowed", + "tr(x_only,{wsh(pk(key_1)),pk(key_2)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - tr() inside taptree leaf not allowed", + "tr(x_only,{tr(key_1),pk(key_2)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - wsh - tr() inside wsh not allowed", + "wsh(and_v(v:pk(key_1),tr(key_2)))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - multi_a not allowed outside tapscript context", + "wsh(multi_a(2,key_1,key_2))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ "descriptor - after - non number child", "wsh(after(key_1))", @@ -2327,6 +2437,16 @@ static const struct address_test { "address errchk - Invalid multi-path index", "pkh(mainnet_xpub/<0;1>)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 2, 0, ADDR("") + },{ + "address - tr - single leaf pk(key_1)", + "tr(x_only,pk(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, + ADDR("bc1pj5dk4dumwklnpqck86xy5003ewsf9rs8kwewxue9qwahleklsp9sdyja0e") + },{ + "address - tr - unbalanced 3-leaf", + "tr(x_only,{pk(key_1),{pk(key_2),pk(key_3)}})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, + ADDR("bc1prm00d6hkq5tm3q9hcus5x6zqu3wysl600494gjzg58agajhp59rqudfe9j") } }; @@ -2519,6 +2639,391 @@ static bool check_descriptor_to_address(const struct address_test *test) return true; } +static bool check_bytes_hex(const char *label, const unsigned char *buf, size_t len, + const char *expected_hex) +{ + char *hex = NULL; + bool ok; + if (wally_hex_from_bytes(buf, len, &hex) != WALLY_OK) { + printf("[%s] wally_hex_from_bytes failed\n", label); + return false; + } + ok = (strcmp(hex, expected_hex) == 0); + if (!ok) + printf("[%s] expected [%s], got [%s]\n", label, expected_hex, hex); + wally_free_string(hex); + return ok; +} + +static bool test_taproot_miniscript(void) +{ + struct wally_descriptor *desc = NULL, *desc2 = NULL; + char *canonical = NULL; + unsigned char buf[1024]; + size_t written; + uint32_t num_leaves, num_keys, key_idx; + int ret; + bool ok = true; + + /* --- tr(x_only, pk(key_1)) --- */ + ret = wally_descriptor_parse("tr(x_only,pk(key_1))", &g_vars[VARS_STD], + WALLY_NETWORK_BITCOIN_MAINNET, 0, &desc); + if (!check_ret("parse tr(x_only,pk(key_1))", ret, WALLY_OK)) { ok = false; goto done_single; } + + /* num_leaves = 1 */ + ret = wally_descriptor_get_taproot_num_leaves(desc, &num_leaves); + if (!check_ret("get_taproot_num_leaves", ret, WALLY_OK)) { ok = false; } + else if (num_leaves != 1) { printf("num_leaves: expected 1, got %u\n", num_leaves); ok = false; } + + /* internal_key = x_only */ + ret = wally_descriptor_get_taproot_internal_key(desc, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_taproot_internal_key", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("internal_key", + buf, 32, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e")) { ok = false; } + + /* leaf_script[0] = OP_20 OP_CHECKSIG */ + ret = wally_descriptor_get_taproot_leaf_script(desc, 0, 0, 0, 0, 0, buf, sizeof(buf), &written); + if (!check_ret("get_taproot_leaf_script", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("leaf_script[0]", + buf, written, + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac")) { ok = false; } + + /* leaf_hash[0] */ + ret = wally_descriptor_get_taproot_leaf_hash(desc, 0, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_taproot_leaf_hash", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("leaf_hash[0]", + buf, 32, + "815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1")) { ok = false; } + + /* merkle_root = leaf_hash (single leaf) */ + ret = wally_descriptor_get_taproot_merkle_root(desc, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_taproot_merkle_root", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("merkle_root", + buf, 32, + "815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1")) { ok = false; } + + /* control_block[0]: 1 + 32 = 33 bytes (no siblings for single leaf) */ + ret = wally_descriptor_get_taproot_control_block(desc, 0, 0, 0, 0, 0, buf, sizeof(buf), &written); + if (!check_ret("get_taproot_control_block", ret, WALLY_OK)) { ok = false; } + else if (written != 33) { printf("control_block size: expected 33, got %zu\n", written); ok = false; } + else if (!check_bytes_hex("control_block[0]", + buf, written, + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e")) { ok = false; } + else if (!check_ret("bip341_control_block_verify (single)", + wally_bip341_control_block_verify(buf, written), WALLY_OK)) { ok = false; } + + /* leaf_num_keys = 1 */ + ret = wally_descriptor_get_taproot_leaf_num_keys(desc, 0, &num_keys); + if (!check_ret("get_taproot_leaf_num_keys", ret, WALLY_OK)) { ok = false; } + else if (num_keys != 1) { printf("leaf_num_keys: expected 1, got %u\n", num_keys); ok = false; } + + /* leaf_key_index[0] = 1 (key_1 is 2nd key overall: 0=x_only, 1=key_1) */ + ret = wally_descriptor_get_taproot_leaf_key_index(desc, 0, 0, &key_idx); + if (!check_ret("get_taproot_leaf_key_index", ret, WALLY_OK)) { ok = false; } + else if (key_idx != 1) { printf("leaf_key_index: expected 1, got %u\n", key_idx); ok = false; } + + /* xonly_pubkey = key_1 stripped to x-only */ + ret = wally_descriptor_get_key_xonly_public_key(desc, key_idx, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_key_xonly_public_key", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("xonly_pubkey", + buf, 32, + "8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048")) { ok = false; } + +done_single: + wally_descriptor_free(desc); desc = NULL; + +#ifdef BUILD_ELEMENTS + /* Regression for the Elements TapLeaf tag fix: the identical leaf script + * must hash with the "TapLeaf/elements" tag under Elements, so its leaf hash + * differs from the Bitcoin tr() leaf hash (which uses "TapLeaf"). */ + { + struct wally_descriptor *btc = NULL, *el = NULL; + unsigned char btc_hash[32], el_hash[32]; + int r1 = wally_descriptor_parse("tr(x_only,pk(key_1))", &g_vars[VARS_STD], + WALLY_NETWORK_BITCOIN_MAINNET, 0, &btc); + int r2 = wally_descriptor_parse("tr(x_only,pk(key_1))", &g_vars[VARS_STD], + WALLY_NETWORK_NONE, WALLY_MINISCRIPT_AS_ELEMENTS, &el); + if (!check_ret("parse btc tr", r1, WALLY_OK) || + !check_ret("parse elements tr", r2, WALLY_OK)) { ok = false; } + else { + r1 = wally_descriptor_get_taproot_leaf_hash(btc, 0, 0, 0, 0, 0, btc_hash, 32); + r2 = wally_descriptor_get_taproot_leaf_hash(el, 0, 0, 0, 0, 0, el_hash, 32); + if (!check_ret("btc leaf_hash", r1, WALLY_OK) || + !check_ret("elements leaf_hash", r2, WALLY_OK)) { ok = false; } + else if (memcmp(btc_hash, el_hash, 32) == 0) { + printf("FAIL: Elements taproot leaf hash equals Bitcoin (TapLeaf/elements tag not applied)\n"); + ok = false; + } + } + wally_descriptor_free(btc); + wally_descriptor_free(el); + } +#endif + + /* --- tr(x_only, {pk(key_1), {pk(key_2), pk(key_3)}}) --- */ + ret = wally_descriptor_parse("tr(x_only,{pk(key_1),{pk(key_2),pk(key_3)}})", &g_vars[VARS_STD], + WALLY_NETWORK_BITCOIN_MAINNET, 0, &desc); + if (!check_ret("parse tr 3-leaf", ret, WALLY_OK)) { ok = false; goto done_3leaf; } + + /* num_leaves = 3 */ + ret = wally_descriptor_get_taproot_num_leaves(desc, &num_leaves); + if (!check_ret("get_taproot_num_leaves (3-leaf)", ret, WALLY_OK)) { ok = false; } + else if (num_leaves != 3) { printf("num_leaves: expected 3, got %u\n", num_leaves); ok = false; } + + /* leaf_hash[0] = hash of pk(key_1) */ + ret = wally_descriptor_get_taproot_leaf_hash(desc, 0, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_taproot_leaf_hash[0]", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("leaf_hash[0] (3-leaf)", + buf, 32, + "815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1")) { ok = false; } + + /* leaf_hash[1] = hash of pk(key_2) */ + ret = wally_descriptor_get_taproot_leaf_hash(desc, 1, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_taproot_leaf_hash[1]", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("leaf_hash[1] (3-leaf)", + buf, 32, + "7c285d60b6e125d82ed715992dae12db8091bd9b9d92c48d768e6c043deca50d")) { ok = false; } + + /* leaf_hash[2] = hash of pk(key_3) */ + ret = wally_descriptor_get_taproot_leaf_hash(desc, 2, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_taproot_leaf_hash[2]", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("leaf_hash[2] (3-leaf)", + buf, 32, + "15ba0270b5e0006a16b832bd0f875873bb957516603e9a08ae3e968dbf4672f8")) { ok = false; } + + /* merkle_root */ + ret = wally_descriptor_get_taproot_merkle_root(desc, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_taproot_merkle_root (3-leaf)", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("merkle_root (3-leaf)", + buf, 32, + "e6229e969670aedf50e45d06fb764d38d92090fb8ddd45051dbf572ce4aaa126")) { ok = false; } + + /* control_block sizes: depth=1 for leaf[0] (65 bytes), depth=2 for leaf[1] and leaf[2] (97 bytes each) */ + /* leaf[0] = pk(key_1) at depth 1: 1 + 32 + 1*32 = 65 bytes */ + /* leaf[1] = pk(key_2), leaf[2] = pk(key_3) at depth 2: 1 + 32 + 2*32 = 97 bytes */ + ret = wally_descriptor_get_taproot_control_block(desc, 0, 0, 0, 0, 0, buf, sizeof(buf), &written); + if (!check_ret("get_taproot_control_block[0] (3-leaf)", ret, WALLY_OK)) { ok = false; } + else if (written != 65) { printf("control_block[0] size: expected 65, got %zu\n", written); ok = false; } + else if (!check_ret("bip341_control_block_verify[0] (3-leaf)", + wally_bip341_control_block_verify(buf, written), WALLY_OK)) { ok = false; } + + ret = wally_descriptor_get_taproot_control_block(desc, 1, 0, 0, 0, 0, buf, sizeof(buf), &written); + if (!check_ret("get_taproot_control_block[1] (3-leaf)", ret, WALLY_OK)) { ok = false; } + else if (written != 97) { printf("control_block[1] size: expected 97, got %zu\n", written); ok = false; } + else if (!check_ret("bip341_control_block_verify[1] (3-leaf)", + wally_bip341_control_block_verify(buf, written), WALLY_OK)) { ok = false; } + + ret = wally_descriptor_get_taproot_control_block(desc, 2, 0, 0, 0, 0, buf, sizeof(buf), &written); + if (!check_ret("get_taproot_control_block[2] (3-leaf)", ret, WALLY_OK)) { ok = false; } + else if (written != 97) { printf("control_block[2] size: expected 97, got %zu\n", written); ok = false; } + else if (!check_ret("bip341_control_block_verify[2] (3-leaf)", + wally_bip341_control_block_verify(buf, written), WALLY_OK)) { ok = false; } + +done_3leaf: + wally_descriptor_free(desc); desc = NULL; + + /* --- tr(x_only) keypath-only --- */ + ret = wally_descriptor_parse("tr(x_only)", &g_vars[VARS_STD], + WALLY_NETWORK_BITCOIN_REGTEST, 0, &desc); + if (!check_ret("parse tr(x_only) keypath-only", ret, WALLY_OK)) { ok = false; goto done_keypath; } + + /* num_leaves = 0 */ + ret = wally_descriptor_get_taproot_num_leaves(desc, &num_leaves); + if (!check_ret("get_taproot_num_leaves (keypath-only)", ret, WALLY_OK)) { ok = false; } + else if (num_leaves != 0) { printf("num_leaves: expected 0, got %u\n", num_leaves); ok = false; } + + /* merkle_root: keypath-only has no taptree, must return WALLY_EINVAL */ + ret = wally_descriptor_get_taproot_merkle_root(desc, 0, 0, 0, 0, buf, 32); + if (!check_ret("get_taproot_merkle_root (keypath-only)", ret, WALLY_EINVAL)) { ok = false; } + +done_keypath: + wally_descriptor_free(desc); desc = NULL; + + /* --- tr(x_only, multi_a(2, key_1, key_2, key_3)) leaf script vector --- */ + ret = wally_descriptor_parse("tr(x_only,multi_a(2,key_1,key_2,key_3))", &g_vars[VARS_STD], + WALLY_NETWORK_BITCOIN_MAINNET, 0, &desc); + if (!check_ret("parse tr(x_only,multi_a)", ret, WALLY_OK)) { ok = false; goto done_multia; } + + ret = wally_descriptor_get_taproot_leaf_script(desc, 0, 0, 0, 0, 0, buf, sizeof(buf), &written); + if (!check_ret("get_taproot_leaf_script (multi_a)", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("leaf_script multi_a", buf, written, + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac" + "20a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ba" + "20b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ba" + "529c")) { ok = false; } + +done_multia: + wally_descriptor_free(desc); desc = NULL; + + /* --- Canonicalization round-trip: parse -> canonicalize -> re-parse -> compare scriptPubKey --- */ + ret = wally_descriptor_parse("tr(x_only,pk(key_1))", &g_vars[VARS_STD], + WALLY_NETWORK_BITCOIN_MAINNET, 0, &desc); + if (!check_ret("canon: parse", ret, WALLY_OK)) { ok = false; goto done_canon; } + + ret = wally_descriptor_canonicalize(desc, 0, &canonical); + if (!check_ret("canon: canonicalize", ret, WALLY_OK)) { ok = false; goto done_canon; } + + ret = wally_descriptor_parse(canonical, NULL, WALLY_NETWORK_BITCOIN_MAINNET, 0, &desc2); + if (!check_ret("canon: re-parse", ret, WALLY_OK)) { ok = false; goto done_canon; } + + { + unsigned char spk1[64], spk2[64]; + size_t w1 = 0, w2 = 0; + + ret = wally_descriptor_to_script(desc, 0, 0, 0, 0, 0, 0, spk1, sizeof(spk1), &w1); + if (!check_ret("canon: to_script orig", ret, WALLY_OK)) { ok = false; } + else { + ret = wally_descriptor_to_script(desc2, 0, 0, 0, 0, 0, 0, spk2, sizeof(spk2), &w2); + if (!check_ret("canon: to_script re-parsed", ret, WALLY_OK)) { ok = false; } + else if (w1 != w2 || memcmp(spk1, spk2, w1) != 0) { + printf("[canonicalize] scriptPubKey mismatch after round-trip\n"); + ok = false; + } + } + } + +done_canon: + wally_descriptor_free(desc); desc = NULL; + wally_descriptor_free(desc2); desc2 = NULL; + wally_free_string(canonical); canonical = NULL; + + /* --- BIP-341 reference vector #1: keypath-only, no script tree --- + * internalPubkey: d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d + * expectedScriptPubKey: 512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343 + * Source: src/data/bip341_vectors.json, entry[0] + */ + { + static struct wally_map_item bip341_items[] = { + { B("bip341_vec1"), B("d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d") } + }; + static const struct wally_map bip341_map = { + bip341_items, NUM_ELEMS(bip341_items), NUM_ELEMS(bip341_items), NULL + }; + unsigned char spk[34]; + size_t spk_len = 0; + + ret = wally_descriptor_parse("tr(bip341_vec1)", &bip341_map, + WALLY_NETWORK_BITCOIN_MAINNET, 0, &desc); + if (!check_ret("bip341_vec1: parse", ret, WALLY_OK)) { ok = false; goto done_bip341; } + + ret = wally_descriptor_to_script(desc, 0, 0, 0, 0, 0, 0, spk, sizeof(spk), &spk_len); + if (!check_ret("bip341_vec1: to_script", ret, WALLY_OK)) { ok = false; } + else if (!check_bytes_hex("bip341_vec1: scriptPubKey", spk, spk_len, + "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343")) { ok = false; } + +done_bip341: + wally_descriptor_free(desc); desc = NULL; + } + + return ok; +} + +static bool test_psbt_taproot_scriptpath(void) +{ + static const unsigned char prev_txid[WALLY_TXHASH_LEN] = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa + }; + static const unsigned char op_return[] = { 0x6a }; /* OP_RETURN for dummy spend output */ + struct wally_descriptor *desc = NULL; + struct wally_tx *tx = NULL; + struct wally_tx_input *txin = NULL; + struct wally_tx_output *utxo_out = NULL, *spend_out = NULL; + struct wally_psbt *psbt = NULL; + unsigned char script[64], priv_key[EC_PRIVATE_KEY_LEN]; + size_t script_len = 0; + int ret; + bool ok = true; + + /* Parse tr(x_only,pk(key_1)) descriptor */ + ret = wally_descriptor_parse("tr(x_only,pk(key_1))", &g_vars[VARS_STD], + WALLY_NETWORK_BITCOIN_MAINNET, 0, &desc); + if (!check_ret("psbt: parse", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Get P2TR output script (OP_1 + push32 + tweaked_output_key = 34 bytes) */ + ret = wally_descriptor_to_script(desc, 0, 0, 0, 0, 0, 0, script, sizeof(script), &script_len); + if (!check_ret("psbt: to_script", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Build spending transaction with one taproot input and a dummy output */ + ret = wally_tx_input_init_alloc(prev_txid, WALLY_TXHASH_LEN, 0, 0xffffffff, + NULL, 0, NULL, &txin); + if (!check_ret("psbt: tx_input_init", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + ret = wally_tx_output_init_alloc(0, op_return, sizeof(op_return), &spend_out); + if (!check_ret("psbt: tx_output_init", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + ret = wally_tx_init_alloc(2, 0, 1, 1, &tx); + if (!check_ret("psbt: tx_init", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + ret = wally_tx_add_input(tx, txin); + if (!check_ret("psbt: tx_add_input", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + ret = wally_tx_add_output(tx, spend_out); + if (!check_ret("psbt: tx_add_output", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Create PSBT v0 with properly initialized inputs via init_alloc + set_global_tx */ + ret = wally_psbt_init_alloc(WALLY_PSBT_VERSION_0, 0, 0, 0, 0, &psbt); + if (!check_ret("psbt: init_alloc", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + ret = wally_psbt_set_global_tx(psbt, tx); + if (!check_ret("psbt: set_global_tx", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Set witness_utxo: the previous output being spent */ + ret = wally_tx_output_init_alloc(100000000, script, script_len, &utxo_out); + if (!check_ret("psbt: utxo_out init", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + ret = wally_psbt_input_set_witness_utxo(&psbt->inputs[0], utxo_out); + if (!check_ret("psbt: set_witness_utxo", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Populate taproot PSBT input fields (internal key, leaf scripts, keypaths) */ + ret = wally_psbt_input_set_taproot_from_descriptor(psbt, 0, desc, 0, 0, 0); + if (!check_ret("psbt: set_taproot_from_descriptor", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Decode raw private key for key_1 from testnet WIF */ + ret = wally_wif_to_bytes("cNha6ams8o6qokphL3XfcUTRs7ggweD3SWn7YXLtB3Rrm3QDNxD4", + WALLY_ADDRESS_VERSION_WIF_TESTNET, WALLY_WIF_FLAG_COMPRESSED, + priv_key, sizeof(priv_key)); + if (!check_ret("psbt: wif_to_bytes", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Sign the script-path spend */ + ret = wally_psbt_sign(psbt, priv_key, sizeof(priv_key), 0); + if (!check_ret("psbt: sign", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Assert TAP_SCRIPT_SIG is present and non-empty */ + if (psbt->inputs[0].taproot_leaf_signatures.num_items == 0) { + printf("[psbt] no TAP_SCRIPT_SIG after signing\n"); + ok = false; + goto done_psbt; + } + + /* Finalize the PSBT input */ + ret = wally_psbt_finalize(psbt, 0); + if (!check_ret("psbt: finalize", ret, WALLY_OK)) { ok = false; goto done_psbt; } + + /* Assert final witness contains [sig, leaf_script, control_block] (3 items) */ + if (!psbt->inputs[0].final_witness) { + printf("[psbt] no final_witness after finalization\n"); + ok = false; + } else if (psbt->inputs[0].final_witness->num_items != 3) { + printf("[psbt] expected 3 witness items, got %zu\n", + psbt->inputs[0].final_witness->num_items); + ok = false; + } + +done_psbt: + wally_descriptor_free(desc); + wally_tx_free(tx); + wally_tx_input_free(txin); + wally_tx_output_free(utxo_out); + wally_tx_output_free(spend_out); + wally_psbt_free(psbt); + memset(priv_key, 0, sizeof(priv_key)); + return ok; +} + int main(void) { bool tests_ok = true; @@ -2538,6 +3043,16 @@ int main(void) } } + if (!test_taproot_miniscript()) { + printf("[test_taproot_miniscript] failed!\n"); + tests_ok = false; + } + + if (!test_psbt_taproot_scriptpath()) { + printf("[test_psbt_taproot_scriptpath] failed!\n"); + tests_ok = false; + } + wally_cleanup(0); return tests_ok ? 0 : 1; } diff --git a/src/ctest/test_miniscript_decode.c b/src/ctest/test_miniscript_decode.c new file mode 100644 index 000000000..1c4b27657 --- /dev/null +++ b/src/ctest/test_miniscript_decode.c @@ -0,0 +1,2723 @@ +#include "config.h" +#include "miniscript_decode.h" +#include +#include +#include +#include +#include +#include + +#define MAX_TOKENS 64 + +#define CHECK(expr) do { if (!(expr)) { printf("FAIL: %s\n", #expr); ok = false; } } while(0) + +static bool test_tokenize_script(void) +{ + bool ok = true; + token_t tokens[MAX_TOKENS]; + size_t count; + int ret; + + /* Empty script */ + ret = tokenize_script(NULL, 0, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 0); + + /* OP_0 */ + { + unsigned char script[] = { OP_0 }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_NUM); + CHECK(tokens[0].data.num == 0); + } + + /* OP_1 */ + { + unsigned char script[] = { OP_1 }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_NUM); + CHECK(tokens[0].data.num == 1); + } + + /* OP_16 */ + { + unsigned char script[] = { OP_16 }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_NUM); + CHECK(tokens[0].data.num == 16); + } + + /* OP_1NEGATE */ + { + unsigned char script[] = { OP_1NEGATE }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* Push data — 20-byte (TK_HASH20) */ + { + unsigned char script[21]; + script[0] = 0x14; /* push 20 bytes */ + memset(script + 1, 0xab, 20); + ret = tokenize_script(script, 21, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_HASH20); + CHECK(memcmp(tokens[0].data.hash20, script + 1, 20) == 0); + } + + /* Push data — 32-byte (TK_BYTES32) */ + { + unsigned char script[33]; + script[0] = 0x20; /* push 32 bytes */ + memset(script + 1, 0xcd, 32); + ret = tokenize_script(script, 33, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_BYTES32); + CHECK(memcmp(tokens[0].data.bytes32, script + 1, 32) == 0); + } + + /* Push data — 33-byte (TK_BYTES33) */ + { + unsigned char script[34]; + script[0] = 0x21; /* push 33 bytes */ + memset(script + 1, 0xef, 33); + ret = tokenize_script(script, 34, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_BYTES33); + CHECK(memcmp(tokens[0].data.bytes33, script + 1, 33) == 0); + } + + /* Push data — 65-byte (TK_BYTES65) */ + { + unsigned char script[66]; + script[0] = 0x41; /* push 65 bytes */ + memset(script + 1, 0x04, 65); + ret = tokenize_script(script, 66, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_BYTES65); + CHECK(memcmp(tokens[0].data.bytes65, script + 1, 65) == 0); + } + + /* Push data — CScriptNum: a minimally-encoded value (17, which has no + * dedicated push opcode) tokenizes to TK_NUM. */ + { + unsigned char script[] = { 0x01, 0x11 }; + ret = tokenize_script(script, 2, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_NUM); + CHECK(tokens[0].data.num == 17); + } + + /* Non-minimal numeric pushes must be rejected (anti-malleability): a value + * 0..16 must use OP_0/OP_1..OP_16, and redundant trailing bytes are invalid. */ + { + unsigned char small[] = { 0x01, 0x05 }; /* 5 must be OP_5 */ + unsigned char trailing[] = { 0x02, 0x11, 0x00 }; /* non-minimal 17 */ + ret = tokenize_script(small, 2, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + ret = tokenize_script(trailing, 3, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* Push data — unsupported length (5 bytes) */ + { + unsigned char script[] = { 0x05, 0, 0, 0, 0, 0 }; + ret = tokenize_script(script, 6, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* Push data — truncated (push-N but script too short) */ + { + unsigned char script[] = { 0x14 }; /* says push 20, but nothing follows */ + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* OP_PUSHDATA1 — valid (20 bytes) */ + { + unsigned char script[22]; + script[0] = OP_PUSHDATA1; + script[1] = 20; + memset(script + 2, 0x11, 20); + ret = tokenize_script(script, 22, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_HASH20); + CHECK(memcmp(tokens[0].data.hash20, script + 2, 20) == 0); + } + + /* OP_PUSHDATA1 — truncated (missing length byte) */ + { + unsigned char script[] = { OP_PUSHDATA1 }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* OP_PUSHDATA2 — valid (20 bytes, little-endian length) */ + { + unsigned char script[23]; + script[0] = OP_PUSHDATA2; + script[1] = 20; + script[2] = 0; + memset(script + 3, 0x22, 20); + ret = tokenize_script(script, 23, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_HASH20); + CHECK(memcmp(tokens[0].data.hash20, script + 3, 20) == 0); + } + + /* OP_PUSHDATA2 — truncated (only one length byte) */ + { + unsigned char script[] = { OP_PUSHDATA2, 20 }; + ret = tokenize_script(script, 2, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* Opcode-only tokens */ + { + unsigned char s[1]; + s[0] = OP_BOOLAND; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_BOOL_AND); + + s[0] = OP_BOOLOR; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_BOOL_OR); + + s[0] = OP_ADD; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_ADD); + + s[0] = OP_EQUAL; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_EQUAL); + + s[0] = OP_NUMEQUAL; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_NUM_EQUAL); + + s[0] = OP_CHECKSIG; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_CHECK_SIG); + + s[0] = OP_CHECKSIGADD; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_CHECK_SIG_ADD); + + s[0] = OP_CHECKMULTISIG; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_CHECK_MULTI_SIG); + + s[0] = OP_CHECKSEQUENCEVERIFY; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_CHECK_SEQUENCE_VERIFY); + + s[0] = OP_CHECKLOCKTIMEVERIFY; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_CHECK_LOCK_TIME_VERIFY); + + s[0] = OP_FROMALTSTACK; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_FROM_ALT_STACK); + + s[0] = OP_TOALTSTACK; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_TO_ALT_STACK); + + s[0] = OP_DROP; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_DROP); + + s[0] = OP_DUP; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_DUP); + + s[0] = OP_IF; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_IF); + + s[0] = OP_IFDUP; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_IF_DUP); + + s[0] = OP_NOTIF; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_NOT_IF); + + s[0] = OP_ELSE; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_ELSE); + + s[0] = OP_ENDIF; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_END_IF); + + s[0] = OP_0NOTEQUAL; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_ZERO_NOT_EQUAL); + + s[0] = OP_SIZE; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_SIZE); + + s[0] = OP_SWAP; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_SWAP); + + s[0] = OP_RIPEMD160; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_RIPEMD160); + + s[0] = OP_HASH160; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_HASH160); + + s[0] = OP_SHA256; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_SHA256); + + s[0] = OP_HASH256; + ret = tokenize_script(s, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK && count == 1 && tokens[0].kind == TK_HASH256); + } + + /* OP_EQUALVERIFY → TK_EQUAL, TK_VERIFY */ + { + unsigned char script[] = { OP_EQUALVERIFY }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 2); + CHECK(tokens[0].kind == TK_EQUAL); + CHECK(tokens[1].kind == TK_VERIFY); + } + + /* OP_NUMEQUALVERIFY → TK_NUM_EQUAL, TK_VERIFY */ + { + unsigned char script[] = { OP_NUMEQUALVERIFY }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 2); + CHECK(tokens[0].kind == TK_NUM_EQUAL); + CHECK(tokens[1].kind == TK_VERIFY); + } + + /* OP_CHECKSIGVERIFY → TK_CHECK_SIG, TK_VERIFY */ + { + unsigned char script[] = { OP_CHECKSIGVERIFY }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 2); + CHECK(tokens[0].kind == TK_CHECK_SIG); + CHECK(tokens[1].kind == TK_VERIFY); + } + + /* OP_CHECKMULTISIGVERIFY → TK_CHECK_MULTI_SIG, TK_VERIFY */ + { + unsigned char script[] = { OP_CHECKMULTISIGVERIFY }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 2); + CHECK(tokens[0].kind == TK_CHECK_MULTI_SIG); + CHECK(tokens[1].kind == TK_VERIFY); + } + + /* Standalone OP_VERIFY (n=0, no preceding token) */ + { + unsigned char script[] = { OP_VERIFY }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 1); + CHECK(tokens[0].kind == TK_VERIFY); + } + + /* OP_SIZE, OP_VERIFY → TK_SIZE, TK_VERIFY */ + { + unsigned char script[] = { OP_SIZE, OP_VERIFY }; + ret = tokenize_script(script, 2, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 2); + CHECK(tokens[0].kind == TK_SIZE); + CHECK(tokens[1].kind == TK_VERIFY); + } + + /* NonMinimalVerify: OP_EQUAL, OP_VERIFY → WALLY_EINVAL */ + { + unsigned char script[] = { OP_EQUAL, OP_VERIFY }; + ret = tokenize_script(script, 2, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* NonMinimalVerify: OP_CHECKSIG, OP_VERIFY → WALLY_EINVAL */ + { + unsigned char script[] = { OP_CHECKSIG, OP_VERIFY }; + ret = tokenize_script(script, 2, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* NonMinimalVerify: OP_CHECKMULTISIG, OP_VERIFY → WALLY_EINVAL */ + { + unsigned char script[] = { OP_CHECKMULTISIG, OP_VERIFY }; + ret = tokenize_script(script, 2, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* Unknown opcode (OP_RESERVED = 0x50) → WALLY_EINVAL */ + { + unsigned char script[] = { OP_RESERVED }; + ret = tokenize_script(script, 1, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* Buffer overflow: OP_DUP with max_tokens = 0 */ + { + unsigned char script[] = { OP_DUP }; + ret = tokenize_script(script, 1, tokens, 0, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* Buffer overflow: OP_EQUALVERIFY (emits 2 tokens) with max_tokens = 1 */ + { + unsigned char script[] = { OP_EQUALVERIFY }; + ret = tokenize_script(script, 1, tokens, 1, &count); + CHECK(ret == WALLY_EINVAL); + } + + /* Multi-token sequence: P2PKH-like script + * OP_DUP OP_HASH160 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG + * → TK_DUP, TK_HASH160, TK_HASH20, TK_EQUAL, TK_VERIFY, TK_CHECK_SIG */ + { + unsigned char script[25]; + script[0] = OP_DUP; + script[1] = OP_HASH160; + script[2] = 0x14; /* push 20 bytes */ + memset(script + 3, 0x33, 20); + script[23] = OP_EQUALVERIFY; + script[24] = OP_CHECKSIG; + ret = tokenize_script(script, 25, tokens, MAX_TOKENS, &count); + CHECK(ret == WALLY_OK); + CHECK(count == 6); + CHECK(tokens[0].kind == TK_DUP); + CHECK(tokens[1].kind == TK_HASH160); + CHECK(tokens[2].kind == TK_HASH20); + CHECK(memcmp(tokens[2].data.hash20, script + 3, 20) == 0); + CHECK(tokens[3].kind == TK_EQUAL); + CHECK(tokens[4].kind == TK_VERIFY); + CHECK(tokens[5].kind == TK_CHECK_SIG); + } + + return ok; +} + +static bool test_decode_pk(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* pk_k with a 33-byte compressed pubkey: script = 0x21 <33 bytes> */ + { + unsigned char script[34]; + unsigned char key[33]; + script[0] = 0x21; + memset(key, 0x02, 33); /* fake compressed pubkey */ + memcpy(script + 1, key, 33); + ret = decode_script_to_node(script, 34, 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->data_len == 33); + CHECK(memcmp(output->data, key, 33) == 0); + ms_node_free(output); output = NULL; + } + + /* pk_k with a 65-byte uncompressed pubkey: script = 0x41 <65 bytes> */ + { + unsigned char script[66]; + unsigned char key[65]; + script[0] = 0x41; + key[0] = 0x04; + memset(key + 1, 0xab, 64); + memcpy(script + 1, key, 65); + ret = decode_script_to_node(script, 66, 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->data_len == 65); + CHECK(memcmp(output->data, key, 65) == 0); + ms_node_free(output); output = NULL; + } + + /* A bare 32-byte x-only key is NOT valid in segwit-v0 context (keys must be + * 33-byte compressed or 65-byte uncompressed); it must be rejected. The valid + * tapscript case is tested below. */ + { + unsigned char script[33]; + unsigned char key[32]; + script[0] = 0x20; + memset(key, 0xcd, 32); + memcpy(script + 1, key, 32); + ret = decode_script_to_node(script, 33, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* pk_h: DUP HASH160 <20-byte-hash> EQUALVERIFY + * script = OP_DUP OP_HASH160 0x14 <20 bytes> OP_EQUALVERIFY */ + { + unsigned char script[25]; + unsigned char hash[20]; + memset(hash, 0x77, 20); + script[0] = OP_DUP; + script[1] = OP_HASH160; + script[2] = 0x14; + memcpy(script + 3, hash, 20); + script[23] = OP_EQUALVERIFY; + ret = decode_script_to_node(script, 24, 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_PK_H); + CHECK(output->data_len == 20); + CHECK(memcmp(output->data, hash, 20) == 0); + ms_node_free(output); output = NULL; + } + + /* pk_k with a 32-byte x-only key in tapscript context: WALLY_MS_IS_X_ONLY must be set */ + { + unsigned char script[33]; + unsigned char key[32]; + script[0] = 0x20; + memset(key, 0xef, 32); + memcpy(script + 1, key, 32); + ret = decode_script_to_node(script, 33, WALLY_MINISCRIPT_TAPSCRIPT, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->data_len == 32); + CHECK(memcmp(output->data, key, 32) == 0); + CHECK(output->flags & WALLY_MS_IS_X_ONLY); + ms_node_free(output); output = NULL; + } + + /* Error: truncated script (length byte claims 33 bytes but only 1 byte total) */ + { + unsigned char script[1]; + script[0] = 0x21; /* push 33 bytes, but nothing follows */ + ret = decode_script_to_node(script, 1, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Error: wrong pubkey length (34-byte push — not a valid key size) */ + { + unsigned char script[35]; + script[0] = 0x22; /* push 34 bytes */ + memset(script + 1, 0xab, 34); + ret = decode_script_to_node(script, 35, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + return ok; +} + +static bool test_decode_hash(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* sha256: OP_SIZE 0x0120 OP_EQUALVERIFY OP_SHA256 0x20 <32 bytes> OP_EQUAL */ + { + unsigned char hash32[32]; + unsigned char script[39]; + memset(hash32, 0xaa, 32); + script[0] = 0x82; /* OP_SIZE */ + script[1] = 0x01; script[2] = 0x20; /* push 1 byte = 32 */ + script[3] = 0x88; /* OP_EQUALVERIFY */ + script[4] = 0xa8; /* OP_SHA256 */ + script[5] = 0x20; /* push 32 bytes */ + memcpy(script + 6, hash32, 32); + script[38] = 0x87; /* OP_EQUAL */ + ret = decode_script_to_node(script, 39, 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_SHA256); + CHECK(output->data_len == 32); + CHECK(memcmp(output->data, hash32, 32) == 0); + ms_node_free(output); output = NULL; + } + + /* hash256: same shape, opcode byte 0xaa at offset 4 */ + { + unsigned char hash32[32]; + unsigned char script[39]; + memset(hash32, 0xaa, 32); + script[0] = 0x82; + script[1] = 0x01; script[2] = 0x20; + script[3] = 0x88; + script[4] = 0xaa; /* OP_HASH256 */ + script[5] = 0x20; + memcpy(script + 6, hash32, 32); + script[38] = 0x87; + ret = decode_script_to_node(script, 39, 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_HASH256); + CHECK(output->data_len == 32); + CHECK(memcmp(output->data, hash32, 32) == 0); + ms_node_free(output); output = NULL; + } + + /* ripemd160: OP_SIZE 0x0120 OP_EQUALVERIFY OP_RIPEMD160 0x14 <20 bytes> OP_EQUAL */ + { + unsigned char hash20[20]; + unsigned char script[27]; + memset(hash20, 0xbb, 20); + script[0] = 0x82; + script[1] = 0x01; script[2] = 0x20; + script[3] = 0x88; + script[4] = 0xa6; /* OP_RIPEMD160 */ + script[5] = 0x14; /* push 20 bytes */ + memcpy(script + 6, hash20, 20); + script[26] = 0x87; + ret = decode_script_to_node(script, 27, 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_RIPEMD160); + CHECK(output->data_len == 20); + CHECK(memcmp(output->data, hash20, 20) == 0); + ms_node_free(output); output = NULL; + } + + /* hash160: same shape, opcode byte 0xa9 at offset 4 */ + { + unsigned char hash20[20]; + unsigned char script[27]; + memset(hash20, 0xbb, 20); + script[0] = 0x82; + script[1] = 0x01; script[2] = 0x20; + script[3] = 0x88; + script[4] = 0xa9; /* OP_HASH160 */ + script[5] = 0x14; + memcpy(script + 6, hash20, 20); + script[26] = 0x87; + ret = decode_script_to_node(script, 27, 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_HASH160); + CHECK(output->data_len == 20); + CHECK(memcmp(output->data, hash20, 20) == 0); + ms_node_free(output); output = NULL; + } + + /* Error: truncated sha256 (missing OP_EQUAL at end) */ + { + unsigned char hash32[32]; + unsigned char script[38]; + memset(hash32, 0xaa, 32); + script[0] = 0x82; + script[1] = 0x01; script[2] = 0x20; + script[3] = 0x88; + script[4] = 0xa8; + script[5] = 0x20; + memcpy(script + 6, hash32, 32); + /* deliberately omit the trailing 0x87 */ + ret = decode_script_to_node(script, 38, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Error: wrong hash length (31-byte push instead of 32) */ + { + unsigned char script[39]; + script[0] = 0x82; + script[1] = 0x01; script[2] = 0x20; + script[3] = 0x88; + script[4] = 0xa8; /* OP_SHA256 */ + script[5] = 0x1f; /* push 31 bytes (invalid) */ + memset(script + 6, 0xaa, 31); + script[37] = 0x87; + script[38] = 0x00; /* padding to keep length same */ + ret = decode_script_to_node(script, 38, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + return ok; +} + +static bool test_decode_multi(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* multi(2, pk1, pk2, pk3): OP_2 push33(pk1) push33(pk2) push33(pk3) OP_3 OP_CHECKMULTISIG */ + { + unsigned char pk1[33], pk2[33], pk3[33]; + unsigned char script[1 + 34 + 34 + 34 + 1 + 1]; + size_t off = 0; + memset(pk1, 0x02, 33); + memset(pk2, 0x03, 33); + memset(pk3, 0x04, 33); + script[off++] = OP_2; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk2, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk3, 33); off += 33; + script[off++] = OP_3; + script[off++] = OP_CHECKMULTISIG; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_MULTI); + CHECK(output->number == 2); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->data_len == 33); + CHECK(memcmp(output->child->data, pk1, 33) == 0); + CHECK(output->child->next != NULL); + CHECK(memcmp(output->child->next->data, pk2, 33) == 0); + CHECK(output->child->next->next != NULL); + CHECK(memcmp(output->child->next->next->data, pk3, 33) == 0); + CHECK(output->child->next->next->next == NULL); + ms_node_free(output); output = NULL; + } + + /* multi(1, pk1): single key, threshold 1 (boundary) */ + { + unsigned char pk1[33]; + unsigned char script[1 + 34 + 1 + 1]; + size_t off = 0; + memset(pk1, 0xaa, 33); + script[off++] = OP_1; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = OP_1; + script[off++] = OP_CHECKMULTISIG; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_MULTI); + CHECK(output->number == 1); + CHECK(output->child != NULL); + CHECK(output->child->data_len == 33); + CHECK(memcmp(output->child->data, pk1, 33) == 0); + CHECK(output->child->next == NULL); + ms_node_free(output); output = NULL; + } + + /* Error path: k > n (k=3, n=2) → WALLY_EINVAL */ + { + unsigned char pk1[33], pk2[33]; + unsigned char script[1 + 34 + 34 + 1 + 1]; + size_t off = 0; + memset(pk1, 0x02, 33); + memset(pk2, 0x03, 33); + script[off++] = OP_3; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk2, 33); off += 33; + script[off++] = OP_2; + script[off++] = OP_CHECKMULTISIG; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + return ok; +} + +static bool test_decode_multi_a(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* multi_a(2, K1, K2, K3): K1 OP_CHECKSIG K2 OP_CHECKSIGADD K3 OP_CHECKSIGADD OP_2 OP_NUMEQUAL */ + { + unsigned char K1[32], K2[32], K3[32]; + unsigned char script[104]; + size_t off = 0; + memset(K1, 0x01, 32); + memset(K2, 0x02, 32); + memset(K3, 0x03, 32); + script[off++] = 0x20; memcpy(script + off, K1, 32); off += 32; + script[off++] = OP_CHECKSIG; + script[off++] = 0x20; memcpy(script + off, K2, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = 0x20; memcpy(script + off, K3, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = OP_2; + script[off++] = OP_NUMEQUAL; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_MULTI_A); + CHECK(output->number == 2); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->data_len == 32); + CHECK(memcmp(output->child->data, K1, 32) == 0); + CHECK(output->child->next != NULL); + CHECK(memcmp(output->child->next->data, K2, 32) == 0); + CHECK(output->child->next->next != NULL); + CHECK(memcmp(output->child->next->next->data, K3, 32) == 0); + CHECK(output->child->next->next->next == NULL); + ms_node_free(output); output = NULL; + } + + /* multi_a(1, K1): minimum valid (k=1, n=1) */ + { + unsigned char K1[32]; + unsigned char script[36]; + size_t off = 0; + memset(K1, 0xaa, 32); + script[off++] = 0x20; memcpy(script + off, K1, 32); off += 32; + script[off++] = OP_CHECKSIG; + script[off++] = OP_1; + script[off++] = OP_NUMEQUAL; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_MULTI_A); + CHECK(output->number == 1); + CHECK(output->child != NULL); + CHECK(output->child->data_len == 32); + CHECK(memcmp(output->child->data, K1, 32) == 0); + CHECK(output->child->next == NULL); + ms_node_free(output); output = NULL; + } + + /* Error: k > n (k=3, n=2) */ + { + unsigned char K1[32], K2[32]; + unsigned char script[70]; + size_t off = 0; + memset(K1, 0x02, 32); + memset(K2, 0x03, 32); + script[off++] = 0x20; memcpy(script + off, K1, 32); off += 32; + script[off++] = OP_CHECKSIG; + script[off++] = 0x20; memcpy(script + off, K2, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = OP_3; + script[off++] = OP_NUMEQUAL; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + return ok; +} + +static bool test_decode_and_v(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* and_v(v:older(100), pk_h(B)): + * script: <100> OP_CSV OP_VERIFY OP_DUP OP_HASH160 OP_EQUALVERIFY + * Tree: AND_V( VERIFY(OLDER(100)), PK_H ) */ + { + unsigned char hash[20]; + /* <100> = push 1 byte [0x64] */ + unsigned char script[] = { + 0x01, 0x64, /* push 1 byte: 100 */ + OP_CHECKSEQUENCEVERIFY, + OP_VERIFY, + OP_DUP, OP_HASH160, + 0x14, /* push 20 bytes */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* hash20 */ + OP_EQUALVERIFY + }; + memset(hash, 0xbb, 20); + memcpy(script + 7, hash, 20); + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_AND_V); + /* left child = v:older(100) = VERIFY wrapping OLDER */ + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_VERIFY); + CHECK(output->child->child != NULL); + CHECK(output->child->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->child->number == 100); + /* right child = pk_h */ + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_PK_H); + CHECK(output->child->next->data_len == 20); + CHECK(memcmp(output->child->next->data, hash, 20) == 0); + ms_node_free(output); output = NULL; + } + + /* Chained and_v: script [v:after(500)] [v:older(100)] [pk_h(C)] + * Decoder produces left-associative form: + * AND_V( AND_V(VERIFY(AFTER(500)), VERIFY(OLDER(100))), PK_H(C) ) */ + { + unsigned char hash[20]; + /* <500> = push 2 bytes [0xF4, 0x01] (500 little-endian, no sign extension needed) */ + unsigned char script[] = { + 0x02, 0xF4, 0x01, /* push 2 bytes: 500 */ + OP_CHECKLOCKTIMEVERIFY, + OP_VERIFY, + 0x01, 0x64, /* push 1 byte: 100 */ + OP_CHECKSEQUENCEVERIFY, + OP_VERIFY, + OP_DUP, OP_HASH160, + 0x14, /* push 20 bytes */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + OP_EQUALVERIFY + }; + memset(hash, 0xcc, 20); + memcpy(script + 12, hash, 20); + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + /* outer AND_V */ + CHECK(output->kind == KIND_MINISCRIPT_AND_V); + /* outer left = inner AND_V( v:after(500), v:older(100) ) */ + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_AND_V); + CHECK(output->child->child != NULL); + CHECK(output->child->child->kind == KIND_MINISCRIPT_VERIFY); + CHECK(output->child->child->child != NULL); + CHECK(output->child->child->child->kind == KIND_MINISCRIPT_AFTER); + CHECK(output->child->child->child->number == 500); + CHECK(output->child->child->next != NULL); + CHECK(output->child->child->next->kind == KIND_MINISCRIPT_VERIFY); + CHECK(output->child->child->next->child != NULL); + CHECK(output->child->child->next->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->child->next->child->number == 100); + /* outer right = pk_h */ + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_PK_H); + CHECK(output->child->next->data_len == 20); + CHECK(memcmp(output->child->next->data, hash, 20) == 0); + ms_node_free(output); output = NULL; + } + + return ok; +} + +static bool test_decode_and_b(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* and_b(older(100), s:pk_k(A)): + * script: <100> OP_CSV OP_SWAP OP_BOOLAND + * Tree: AND_B( OLDER(100), SWAP(PK_K(A)) ) */ + { + unsigned char key[33]; + unsigned char script[2 + 1 + 1 + 1 + 33 + 1]; /* 39 bytes */ + size_t off = 0; + memset(key, 0x02, 33); + script[off++] = 0x01; script[off++] = 0x64; /* push 1 byte: 100 */ + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_SWAP; + script[off++] = 0x21; /* push 33 bytes */ + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_BOOLAND; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_AND_B); + /* left (B) = older(100) */ + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + /* right (W) = s:pk_k(A) = SWAP wrapping PK_K */ + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_SWAP); + CHECK(output->child->next->child != NULL); + CHECK(output->child->next->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->child->data_len == 33); + CHECK(memcmp(output->child->next->child->data, key, 33) == 0); + ms_node_free(output); output = NULL; + } + + return ok; +} + +static bool test_decode_or_b(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + unsigned char key[33]; + unsigned char script[2 + 1 + 1 + 1 + 33 + 1]; /* 39 bytes */ + size_t off = 0; + memset(key, 0x02, 33); + script[off++] = 0x01; script[off++] = 0x64; /* push 1 byte: 100 */ + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_SWAP; + script[off++] = 0x21; /* push 33 bytes */ + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_BOOLOR; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_OR_B); + /* left (B) = older(100) */ + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + /* right (W) = s:pk_k(A) = SWAP wrapping PK_K */ + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_SWAP); + CHECK(output->child->next->child != NULL); + CHECK(output->child->next->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->child->data_len == 33); + CHECK(memcmp(output->child->next->child->data, key, 33) == 0); + ms_node_free(output); output = NULL; + return ok; +} + +static bool test_decode_or_c(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + unsigned char key[33]; + unsigned char script[2 + 1 + 1 + 1 + 33 + 1]; /* 39 bytes */ + size_t off = 0; + memset(key, 0x03, 33); + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_NOTIF; + script[off++] = 0x21; + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_OR_C); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->data_len == 33); + CHECK(memcmp(output->child->next->data, key, 33) == 0); + ms_node_free(output); output = NULL; + return ok; +} + +static bool test_decode_or_d(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + unsigned char key[33]; + unsigned char script[2 + 1 + 1 + 1 + 1 + 33 + 1]; /* 40 bytes */ + size_t off = 0; + memset(key, 0x04, 33); + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_IFDUP; + script[off++] = OP_NOTIF; + script[off++] = 0x21; + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_OR_D); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->data_len == 33); + CHECK(memcmp(output->child->next->data, key, 33) == 0); + ms_node_free(output); output = NULL; + return ok; +} + +static bool test_decode_or_i(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + unsigned char key[33]; + unsigned char script[1 + 2 + 1 + 1 + 1 + 33 + 1]; /* 40 bytes */ + size_t off = 0; + memset(key, 0x05, 33); + script[off++] = OP_IF; + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_ELSE; + script[off++] = 0x21; + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_OR_I); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->data_len == 33); + CHECK(memcmp(output->child->next->data, key, 33) == 0); + ms_node_free(output); output = NULL; + return ok; +} + +static bool test_decode_andor(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + unsigned char keyA[33], keyB[33]; + unsigned char script[2 + 1 + 1 + 1 + 33 + 1 + 1 + 33 + 1]; /* 74 bytes */ + size_t off = 0; + memset(keyA, 0x02, 33); + memset(keyB, 0x03, 33); + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_NOTIF; + script[off++] = 0x21; + memcpy(script + off, keyB, 33); off += 33; + script[off++] = OP_ELSE; + script[off++] = 0x21; + memcpy(script + off, keyA, 33); off += 33; + script[off++] = OP_ENDIF; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_ANDOR); + /* child X = older(100) */ + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + /* Y = pk_k(A) (true branch) */ + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->data_len == 33); + CHECK(memcmp(output->child->next->data, keyA, 33) == 0); + /* Z = pk_k(B) (false branch) */ + CHECK(output->child->next->next != NULL); + CHECK(output->child->next->next->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->next->data_len == 33); + CHECK(memcmp(output->child->next->next->data, keyB, 33) == 0); + ms_node_free(output); output = NULL; + return ok; +} + +static bool test_decode_thresh(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* thresh(2, older(100), s:pk_k(A)): + * script: <100> OP_CSV OP_SWAP OP_ADD OP_2 OP_EQUAL + * Tree: THRESH(2, OLDER(100), SWAP(PK_K(A))) */ + { + unsigned char keyA[33]; + unsigned char script[2 + 1 + 1 + 1 + 33 + 1 + 1 + 1]; /* 41 bytes */ + size_t off = 0; + memset(keyA, 0x02, 33); + script[off++] = 0x01; script[off++] = 0x64; /* push 1 byte: 100 */ + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_SWAP; + script[off++] = 0x21; memcpy(script + off, keyA, 33); off += 33; + script[off++] = OP_ADD; + script[off++] = OP_2; + script[off++] = OP_EQUAL; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_THRESH); + CHECK(output->number == 2); + /* first child = older(100) (e, base expr) */ + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + /* second child = s:pk_k(A) (W expr) */ + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_SWAP); + CHECK(output->child->next->child != NULL); + CHECK(output->child->next->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->child->data_len == 33); + CHECK(memcmp(output->child->next->child->data, keyA, 33) == 0); + CHECK(output->child->next->next == NULL); + ms_node_free(output); output = NULL; + } + + /* thresh(3, older(100), s:pk_k(A), s:pk_k(B)): + * script: <100> OP_CSV OP_SWAP OP_ADD OP_SWAP OP_ADD OP_3 OP_EQUAL + * Tree: THRESH(3, OLDER(100), SWAP(PK_K(A)), SWAP(PK_K(B))) */ + { + unsigned char keyA[33], keyB[33]; + unsigned char script[2 + 1 + 1 + 1 + 33 + 1 + 1 + 1 + 33 + 1 + 1 + 1]; /* 77 bytes */ + size_t off = 0; + memset(keyA, 0x02, 33); + memset(keyB, 0x03, 33); + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_SWAP; + script[off++] = 0x21; memcpy(script + off, keyA, 33); off += 33; + script[off++] = OP_ADD; + script[off++] = OP_SWAP; + script[off++] = 0x21; memcpy(script + off, keyB, 33); off += 33; + script[off++] = OP_ADD; + script[off++] = OP_3; + script[off++] = OP_EQUAL; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_THRESH); + CHECK(output->number == 3); + /* first child = older(100) */ + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + /* second child = s:pk_k(A) */ + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_SWAP); + CHECK(output->child->next->child != NULL); + CHECK(output->child->next->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(memcmp(output->child->next->child->data, keyA, 33) == 0); + /* third child = s:pk_k(B) */ + CHECK(output->child->next->next != NULL); + CHECK(output->child->next->next->kind == KIND_MINISCRIPT_SWAP); + CHECK(output->child->next->next->child != NULL); + CHECK(output->child->next->next->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(memcmp(output->child->next->next->child->data, keyB, 33) == 0); + CHECK(output->child->next->next->next == NULL); + ms_node_free(output); output = NULL; + } + + return ok; +} + +static bool test_decode_wrappers(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* c:pk_k(A) = OP_CHECKSIG */ + { + unsigned char key[33]; + unsigned char script[35]; + memset(key, 0x02, 33); + script[0] = 0x21; + memcpy(script + 1, key, 33); + script[34] = OP_CHECKSIG; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_CHECK); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->data_len == 33); + CHECK(memcmp(output->child->data, key, 33) == 0); + ms_node_free(output); output = NULL; + } + + /* n:older(100) = <100> OP_CSV OP_0NOTEQUAL */ + { + unsigned char script[] = { 0x01, 0x64, OP_CHECKSEQUENCEVERIFY, OP_0NOTEQUAL }; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_ZERO_NOT_EQUAL); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + ms_node_free(output); output = NULL; + } + + /* d:pk_k(A) = OP_DUP OP_IF OP_ENDIF */ + { + unsigned char key[33]; + unsigned char script[37]; + size_t off = 0; + memset(key, 0x02, 33); + script[off++] = OP_DUP; + script[off++] = OP_IF; + script[off++] = 0x21; + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_DUP_IF); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->data_len == 33); + CHECK(memcmp(output->child->data, key, 33) == 0); + ms_node_free(output); output = NULL; + } + + /* j:pk_k(A) = OP_SIZE OP_0NOTEQUAL OP_IF OP_ENDIF */ + { + unsigned char key[33]; + unsigned char script[38]; + size_t off = 0; + memset(key, 0x02, 33); + script[off++] = OP_SIZE; + script[off++] = OP_0NOTEQUAL; + script[off++] = OP_IF; + script[off++] = 0x21; + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_NON_ZERO); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->data_len == 33); + CHECK(memcmp(output->child->data, key, 33) == 0); + ms_node_free(output); output = NULL; + } + + /* t:older(100) = <100> OP_CSV OP_1 */ + { + unsigned char script[] = { 0x01, 0x64, OP_CHECKSEQUENCEVERIFY, OP_1 }; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_AND_V); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_OLDER); + CHECK(output->child->number == 100); + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_JUST_1); + CHECK(output->child->next->next == NULL); + ms_node_free(output); output = NULL; + } + + /* l:pk_k(A) = OP_IF OP_0 OP_ELSE OP_ENDIF */ + { + unsigned char key[33]; + unsigned char script[38]; + size_t off = 0; + memset(key, 0x02, 33); + script[off++] = OP_IF; + script[off++] = OP_0; + script[off++] = OP_ELSE; + script[off++] = 0x21; + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_OR_I); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_JUST_0); + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->next->data_len == 33); + CHECK(memcmp(output->child->next->data, key, 33) == 0); + ms_node_free(output); output = NULL; + } + + /* u:pk_k(A) = OP_IF OP_ELSE OP_0 OP_ENDIF */ + { + unsigned char key[33]; + unsigned char script[38]; + size_t off = 0; + memset(key, 0x02, 33); + script[off++] = OP_IF; + script[off++] = 0x21; + memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ELSE; + script[off++] = OP_0; + script[off++] = OP_ENDIF; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_OK); + CHECK(output != NULL); + CHECK(output->kind == KIND_MINISCRIPT_OR_I); + CHECK(output->child != NULL); + CHECK(output->child->kind == KIND_MINISCRIPT_PK_K); + CHECK(output->child->data_len == 33); + CHECK(memcmp(output->child->data, key, 33) == 0); + CHECK(output->child->next != NULL); + CHECK(output->child->next->kind == KIND_MINISCRIPT_JUST_0); + ms_node_free(output); output = NULL; + } + + return ok; +} + +typedef struct { + uint32_t max_relative; + uint32_t max_absolute; +} tl_ctx_t; + +static bool tl_check_older(const ms_satisfier *stfr, uint32_t lock) +{ + const tl_ctx_t *ctx = (const tl_ctx_t *)stfr->user_data; + return lock <= ctx->max_relative; +} + +static bool tl_check_after(const ms_satisfier *stfr, uint32_t lock) +{ + const tl_ctx_t *ctx = (const tl_ctx_t *)stfr->user_data; + return lock <= ctx->max_absolute; +} + +typedef struct { + const unsigned char *pk; + unsigned char sig[71]; + size_t sig_len; +} sig_entry_t; + +typedef struct { + const sig_entry_t *entries; + size_t n; +} sig_ctx_t; + +static bool multi_lookup_sig(const ms_satisfier *stfr, + const unsigned char *pk, size_t pk_len, + unsigned char *sig_out, size_t *sig_len_out) +{ + const sig_ctx_t *ctx = (const sig_ctx_t *)stfr->user_data; + for (size_t i = 0; i < ctx->n; i++) { + if (pk_len == 33 && memcmp(pk, ctx->entries[i].pk, 33) == 0) { + memcpy(sig_out, ctx->entries[i].sig, ctx->entries[i].sig_len); + *sig_len_out = ctx->entries[i].sig_len; + return true; + } + } + return false; +} + +static bool multi_a_lookup_sig(const ms_satisfier *stfr, + const unsigned char *pk, size_t pk_len, + unsigned char *sig_out, size_t *sig_len_out) +{ + const sig_ctx_t *ctx = (const sig_ctx_t *)stfr->user_data; + for (size_t i = 0; i < ctx->n; i++) { + if (pk_len == 32 && memcmp(pk, ctx->entries[i].pk, 32) == 0) { + memcpy(sig_out, ctx->entries[i].sig, ctx->entries[i].sig_len); + *sig_len_out = ctx->entries[i].sig_len; + return true; + } + } + return false; +} + +static void make_fake_sig(unsigned char *sig, unsigned char r_byte, unsigned char s_byte) +{ + sig[0] = 0x30; sig[1] = 0x44; + sig[2] = 0x02; sig[3] = 0x20; + memset(sig + 4, r_byte, 32); + sig[36] = 0x02; sig[37] = 0x20; + memset(sig + 38, s_byte, 32); + sig[70] = 0x01; +} + +static void make_fake_schnorr_sig(unsigned char *sig, unsigned char byte) +{ + memset(sig, byte, 64); +} + +typedef struct { + sig_ctx_t sig; + tl_ctx_t tl; +} thresh_sig_tl_ctx_t; + +static bool thresh_sig_tl_lookup_sig(const ms_satisfier *stfr, + const unsigned char *pk, size_t pk_len, + unsigned char *sig_out, size_t *sig_len_out) +{ + const thresh_sig_tl_ctx_t *ctx = (const thresh_sig_tl_ctx_t *)stfr->user_data; + for (size_t i = 0; i < ctx->sig.n; i++) { + if (pk_len == 33 && memcmp(pk, ctx->sig.entries[i].pk, 33) == 0) { + memcpy(sig_out, ctx->sig.entries[i].sig, ctx->sig.entries[i].sig_len); + *sig_len_out = ctx->sig.entries[i].sig_len; + return true; + } + } + return false; +} + +static bool thresh_sig_tl_check_older(const ms_satisfier *stfr, uint32_t lock) +{ + const thresh_sig_tl_ctx_t *ctx = (const thresh_sig_tl_ctx_t *)stfr->user_data; + return lock <= ctx->tl.max_relative; +} + +static bool test_satisfy_multi(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + + unsigned char pk1[33], pk2[33], pk3[33]; + memset(pk1, 0x11, 33); + memset(pk2, 0x22, 33); + memset(pk3, 0x33, 33); + + /* Case 1: multi(2, pk1, pk2, pk3) — 3 sigs available, expect first 2 chosen */ + { + unsigned char script[1 + 34 + 34 + 34 + 1 + 1]; + size_t off = 0; + script[off++] = OP_2; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk2, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk3, 33); off += 33; + script[off++] = OP_3; + script[off++] = OP_CHECKMULTISIG; + + sig_entry_t entries[3]; + entries[0].pk = pk1; make_fake_sig(entries[0].sig, 0x01, 0x02); entries[0].sig_len = 71; + entries[1].pk = pk2; make_fake_sig(entries[1].sig, 0x0a, 0x0b); entries[1].sig_len = 71; + entries[2].pk = pk3; make_fake_sig(entries[2].sig, 0x0c, 0x0d); entries[2].sig_len = 71; + + sig_ctx_t ctx = { entries, 3 }; + ms_satisfier stfr = { multi_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 3); + CHECK(sat.witness.items[0].data_len == 0); + CHECK(sat.witness.items[1].data_len == 71); + CHECK(memcmp(sat.witness.items[1].data, entries[0].sig, 71) == 0); + CHECK(sat.witness.items[2].data_len == 71); + CHECK(memcmp(sat.witness.items[2].data, entries[1].sig, 71) == 0); + CHECK(sat.has_sig == true); + CHECK(dissat.witness.kind == MS_WITNESS_STACK); + CHECK(dissat.witness.num_items == 3); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: multi(2, pk1, pk2, pk3) — only 1 sig available */ + { + unsigned char script[1 + 34 + 34 + 34 + 1 + 1]; + size_t off = 0; + script[off++] = OP_2; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk2, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk3, 33); off += 33; + script[off++] = OP_3; + script[off++] = OP_CHECKMULTISIG; + + sig_entry_t entry1; + entry1.pk = pk1; make_fake_sig(entry1.sig, 0x01, 0x02); entry1.sig_len = 71; + sig_ctx_t ctx = { &entry1, 1 }; + ms_satisfier stfr = { multi_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: multi(2, pk1, pk2, pk3) — NULL satisfier */ + { + unsigned char script[1 + 34 + 34 + 34 + 1 + 1]; + size_t off = 0; + script[off++] = OP_2; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk2, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk3, 33); off += 33; + script[off++] = OP_3; + script[off++] = OP_CHECKMULTISIG; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 4: multi(1, pk1) — k=1, n=1, 1 sig available */ + { + unsigned char script[1 + 34 + 1 + 1]; + size_t off = 0; + script[off++] = OP_1; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = OP_1; + script[off++] = OP_CHECKMULTISIG; + + sig_entry_t entry1; + entry1.pk = pk1; make_fake_sig(entry1.sig, 0x01, 0x02); entry1.sig_len = 71; + sig_ctx_t ctx = { &entry1, 1 }; + ms_satisfier stfr = { multi_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 2); + CHECK(sat.witness.items[0].data_len == 0); + CHECK(sat.witness.items[1].data_len == 71); + CHECK(sat.has_sig == true); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_satisfy_multi_a(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + + unsigned char pk1[32], pk2[32], pk3[32]; + memset(pk1, 0x11, 32); + memset(pk2, 0x22, 32); + memset(pk3, 0x33, 32); + + /* Case 1: multi_a(2, pk1, pk2, pk3) — 3 sigs available, expect first 2 chosen */ + { + unsigned char script[104]; + size_t off = 0; + script[off++] = 0x20; memcpy(script + off, pk1, 32); off += 32; + script[off++] = OP_CHECKSIG; + script[off++] = 0x20; memcpy(script + off, pk2, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = 0x20; memcpy(script + off, pk3, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = OP_2; + script[off++] = OP_NUMEQUAL; + + sig_entry_t entries[3]; + entries[0].pk = pk1; make_fake_schnorr_sig(entries[0].sig, 0x01); entries[0].sig_len = 64; + entries[1].pk = pk2; make_fake_schnorr_sig(entries[1].sig, 0x02); entries[1].sig_len = 64; + entries[2].pk = pk3; make_fake_schnorr_sig(entries[2].sig, 0x03); entries[2].sig_len = 64; + + sig_ctx_t ctx = { entries, 3 }; + ms_satisfier stfr = { multi_a_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 3); + CHECK(sat.witness.items[0].data_len == 0); + CHECK(sat.witness.items[1].data_len == 64); + CHECK(memcmp(sat.witness.items[1].data, entries[1].sig, 64) == 0); + CHECK(sat.witness.items[2].data_len == 64); + CHECK(memcmp(sat.witness.items[2].data, entries[0].sig, 64) == 0); + CHECK(sat.has_sig == true); + CHECK(dissat.witness.kind == MS_WITNESS_STACK); + CHECK(dissat.witness.num_items == 3); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: multi_a(2, pk1, pk2, pk3) — only 1 sig available (pk2 only) */ + { + unsigned char script[104]; + size_t off = 0; + script[off++] = 0x20; memcpy(script + off, pk1, 32); off += 32; + script[off++] = OP_CHECKSIG; + script[off++] = 0x20; memcpy(script + off, pk2, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = 0x20; memcpy(script + off, pk3, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = OP_2; + script[off++] = OP_NUMEQUAL; + + sig_entry_t entry1; + entry1.pk = pk2; make_fake_schnorr_sig(entry1.sig, 0x02); entry1.sig_len = 64; + sig_ctx_t ctx = { &entry1, 1 }; + ms_satisfier stfr = { multi_a_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: multi_a(2, pk1, pk2, pk3) — NULL satisfier */ + { + unsigned char script[104]; + size_t off = 0; + script[off++] = 0x20; memcpy(script + off, pk1, 32); off += 32; + script[off++] = OP_CHECKSIG; + script[off++] = 0x20; memcpy(script + off, pk2, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = 0x20; memcpy(script + off, pk3, 32); off += 32; + script[off++] = OP_CHECKSIGADD; + script[off++] = OP_2; + script[off++] = OP_NUMEQUAL; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 4: multi_a(1, pk1) — k=1, n=1, sig available */ + { + unsigned char script[36]; + size_t off = 0; + script[off++] = 0x20; memcpy(script + off, pk1, 32); off += 32; + script[off++] = OP_CHECKSIG; + script[off++] = OP_1; + script[off++] = OP_NUMEQUAL; + + sig_entry_t entry1; + entry1.pk = pk1; make_fake_schnorr_sig(entry1.sig, 0x01); entry1.sig_len = 64; + sig_ctx_t ctx = { &entry1, 1 }; + ms_satisfier stfr = { multi_a_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 1); + CHECK(sat.witness.items[0].data_len == 64); + CHECK(sat.has_sig == true); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_satisfy_timelocks(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + + /* Case 1: older(100) — check_older returns true */ + { + unsigned char script[] = { 0x01, 0x64, OP_CHECKSEQUENCEVERIFY }; + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 100, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.relative_timelock == 100); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: older(100) — check_older returns false */ + { + unsigned char script[] = { 0x01, 0x64, OP_CHECKSEQUENCEVERIFY }; + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 0, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: older(100) — no satisfier (NULL) */ + { + unsigned char script[] = { 0x01, 0x64, OP_CHECKSEQUENCEVERIFY }; + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 4: after(500) — check_after returns true */ + { + unsigned char script[] = { 0x02, 0xF4, 0x01, OP_CHECKLOCKTIMEVERIFY }; + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 0, 500 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.absolute_timelock == 500); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 5: after(500) — check_after returns false */ + { + unsigned char script[] = { 0x02, 0xF4, 0x01, OP_CHECKLOCKTIMEVERIFY }; + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 0, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 6: and_v(v:older(100), older(200)) — timelocks merged (max) */ + { + /* Script: <100> OP_CSV OP_VERIFY <200> OP_CSV + * 200 = 0xC8 has high bit set, needs 2-byte CScriptNum encoding: 0xC8 0x00 */ + unsigned char script[] = { + 0x01, 0x64, /* push 1 byte: 100 */ + OP_CHECKSEQUENCEVERIFY, + OP_VERIFY, + 0x02, 0xC8, 0x00, /* push 2 bytes: 200 (0xC8 needs sign byte) */ + OP_CHECKSEQUENCEVERIFY + }; + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 200, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.relative_timelock == 200); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 7: and_v(v:older(100), after(500)) — mixed timelocks */ + { + /* Script: <100> OP_CSV OP_VERIFY <500> OP_CLTV */ + unsigned char script[] = { + 0x01, 0x64, /* push 1 byte: 100 */ + OP_CHECKSEQUENCEVERIFY, + OP_VERIFY, + 0x02, 0xF4, 0x01, /* push 2 bytes: 500 */ + OP_CHECKLOCKTIMEVERIFY + }; + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 100, 500 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.relative_timelock == 100); + CHECK(sat.absolute_timelock == 500); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_satisfy_or_b(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + unsigned char key[33]; + memset(key, 0x02, 33); + + /* Script: older(100) OP_SWAP OP_BOOLOR = or_b(older(100), s:pk_k(key)) */ + unsigned char script[2 + 1 + 1 + 1 + 33 + 1]; /* 39 bytes */ + size_t off = 0; + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_SWAP; + script[off++] = 0x21; memcpy(script + off, key, 33); off += 33; + script[off++] = OP_BOOLOR; + + /* Case 1: timelock met */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 100, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 1); + CHECK(sat.witness.items[0].data_len == 0); /* dissat of s:pk_k: empty push */ + CHECK(sat.relative_timelock == 100); + CHECK(sat.has_sig == false); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: timelock NOT met */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 0, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: NULL satisfier */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_satisfy_or_c(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + unsigned char key[33]; + memset(key, 0x03, 33); + + /* Script: older(100) OP_NOTIF OP_ENDIF = or_c(older(100), pk_k(key)) */ + unsigned char script[2 + 1 + 1 + 1 + 33 + 1]; /* 39 bytes */ + size_t off = 0; + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_NOTIF; + script[off++] = 0x21; memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + + /* Case 1: timelock met */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 100, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.relative_timelock == 100); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: timelock NOT met */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 0, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: NULL satisfier */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_satisfy_or_d(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + unsigned char key[33]; + memset(key, 0x04, 33); + + /* Script: older(100) OP_IFDUP OP_NOTIF OP_ENDIF = or_d(older(100), pk_k(key)) */ + unsigned char script[2 + 1 + 1 + 1 + 1 + 33 + 1]; /* 40 bytes */ + size_t off = 0; + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_IFDUP; + script[off++] = OP_NOTIF; + script[off++] = 0x21; memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + + /* Case 1: timelock met */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 100, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.relative_timelock == 100); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: timelock NOT met */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 0, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: NULL satisfier */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_satisfy_or_i(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + unsigned char key[33]; + memset(key, 0x05, 33); + + /* Script: OP_IF older(100) OP_ELSE OP_ENDIF = or_i(older(100), pk_k(key)) */ + unsigned char script[1 + 2 + 1 + 1 + 1 + 33 + 1]; /* 40 bytes */ + size_t off = 0; + script[off++] = OP_IF; + script[off++] = 0x01; script[off++] = 0x64; + script[off++] = OP_CHECKSEQUENCEVERIFY; + script[off++] = OP_ELSE; + script[off++] = 0x21; memcpy(script + off, key, 33); off += 33; + script[off++] = OP_ENDIF; + + /* Case 1: timelock met */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 100, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 1); + CHECK(sat.witness.items[0].data_len == 1); + CHECK(sat.witness.items[0].data[0] == 0x01); + CHECK(sat.relative_timelock == 100); + CHECK(dissat.witness.kind == MS_WITNESS_STACK); + CHECK(dissat.witness.num_items == 2); + CHECK(dissat.witness.items[0].data_len == 0); /* pk_k dissat: empty push */ + CHECK(dissat.witness.items[1].data_len == 0); /* right-branch selector: empty push */ + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: timelock NOT met */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 0, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + CHECK(dissat.witness.kind == MS_WITNESS_STACK); + CHECK(dissat.witness.num_items == 2); + CHECK(dissat.witness.items[0].data_len == 0); + CHECK(dissat.witness.items[1].data_len == 0); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: NULL satisfier */ + { + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + CHECK(dissat.witness.kind == MS_WITNESS_STACK); + CHECK(dissat.witness.num_items == 2); + CHECK(dissat.witness.items[0].data_len == 0); + CHECK(dissat.witness.items[1].data_len == 0); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_satisfy_andor(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + + unsigned char pk_A[33], pk_B[33], pk_C[33]; + memset(pk_A, 0x0A, 33); + memset(pk_B, 0x0B, 33); + memset(pk_C, 0x0C, 33); + + /* andor(pk_k(A), pk_k(B), pk_k(C)): + * OP_CHECKSIG OP_NOTIF OP_CHECKSIG OP_ELSE OP_CHECKSIG OP_ENDIF */ + unsigned char script[3 * (1 + 33 + 1) + 1 + 1 + 1]; /* 108 bytes */ + size_t off = 0; + script[off++] = 0x21; memcpy(script + off, pk_A, 33); off += 33; + script[off++] = OP_CHECKSIG; + script[off++] = OP_NOTIF; + script[off++] = 0x21; memcpy(script + off, pk_C, 33); off += 33; + script[off++] = OP_CHECKSIG; + script[off++] = OP_ELSE; + script[off++] = 0x21; memcpy(script + off, pk_B, 33); off += 33; + script[off++] = OP_CHECKSIG; + script[off++] = OP_ENDIF; + + /* Case 1: sigs for A and B available → sat via concat(sat_Y, sat_X) = [sig_B, sig_A] */ + { + sig_entry_t entries[2]; + entries[0].pk = pk_A; make_fake_sig(entries[0].sig, 0xA1, 0xA2); entries[0].sig_len = 71; + entries[1].pk = pk_B; make_fake_sig(entries[1].sig, 0xB1, 0xB2); entries[1].sig_len = 71; + sig_ctx_t ctx = { entries, 2 }; + ms_satisfier stfr = { multi_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 2); + CHECK(sat.witness.items[0].data_len == 71); /* sig_B */ + CHECK(memcmp(sat.witness.items[0].data, entries[1].sig, 71) == 0); + CHECK(sat.witness.items[1].data_len == 71); /* sig_A */ + CHECK(memcmp(sat.witness.items[1].data, entries[0].sig, 71) == 0); + CHECK(sat.has_sig == true); + CHECK(dissat.witness.kind == MS_WITNESS_STACK); + CHECK(dissat.witness.num_items == 2); + CHECK(dissat.witness.items[0].data_len == 0); /* dissat_Z = empty */ + CHECK(dissat.witness.items[1].data_len == 0); /* dissat_X = empty */ + CHECK(dissat.has_sig == false); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: only sig_A available → sat_Y and sat_Z both IMPOSSIBLE → sat IMPOSSIBLE */ + { + sig_entry_t entry; + entry.pk = pk_A; make_fake_sig(entry.sig, 0xA1, 0xA2); entry.sig_len = 71; + sig_ctx_t ctx = { &entry, 1 }; + ms_satisfier stfr = { multi_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + CHECK(dissat.witness.kind == MS_WITNESS_STACK); + CHECK(dissat.witness.num_items == 2); + CHECK(dissat.witness.items[0].data_len == 0); + CHECK(dissat.witness.items[1].data_len == 0); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: only sig_C available → sat via concat(sat_Z, dissat_X) = [sig_C, empty] */ + { + sig_entry_t entry; + entry.pk = pk_C; make_fake_sig(entry.sig, 0xC1, 0xC2); entry.sig_len = 71; + sig_ctx_t ctx = { &entry, 1 }; + ms_satisfier stfr = { multi_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 2); + CHECK(sat.witness.items[0].data_len == 71); /* sig_C */ + CHECK(memcmp(sat.witness.items[0].data, entry.sig, 71) == 0); + CHECK(sat.witness.items[1].data_len == 0); /* dissat_X = empty */ + CHECK(sat.has_sig == true); + CHECK(dissat.witness.kind == MS_WITNESS_STACK); + CHECK(dissat.witness.num_items == 2); + CHECK(dissat.witness.items[0].data_len == 0); + CHECK(dissat.witness.items[1].data_len == 0); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_satisfy_thresh(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + + unsigned char keyA[33], keyB[33]; + memset(keyA, 0x0A, 33); + memset(keyB, 0x0B, 33); + + /* thresh(2, older(100), s:pk_k(A)): + * script: <100> OP_CSV OP_SWAP OP_ADD OP_2 OP_EQUAL */ + unsigned char scriptA[2 + 1 + 1 + 1 + 33 + 1 + 1 + 1]; /* 41 bytes */ + { + size_t off = 0; + scriptA[off++] = 0x01; scriptA[off++] = 0x64; + scriptA[off++] = OP_CHECKSEQUENCEVERIFY; + scriptA[off++] = OP_SWAP; + scriptA[off++] = 0x21; memcpy(scriptA + off, keyA, 33); off += 33; + scriptA[off++] = OP_ADD; + scriptA[off++] = OP_2; + scriptA[off++] = OP_EQUAL; + } + + /* thresh(3, older(100), s:pk_k(A), s:pk_k(B)): + * script: <100> OP_CSV OP_SWAP OP_ADD OP_SWAP OP_ADD OP_3 OP_EQUAL */ + unsigned char scriptB[2 + 1 + 1 + 1 + 33 + 1 + 1 + 1 + 33 + 1 + 1 + 1]; /* 77 bytes */ + { + size_t off = 0; + scriptB[off++] = 0x01; scriptB[off++] = 0x64; + scriptB[off++] = OP_CHECKSEQUENCEVERIFY; + scriptB[off++] = OP_SWAP; + scriptB[off++] = 0x21; memcpy(scriptB + off, keyA, 33); off += 33; + scriptB[off++] = OP_ADD; + scriptB[off++] = OP_SWAP; + scriptB[off++] = 0x21; memcpy(scriptB + off, keyB, 33); off += 33; + scriptB[off++] = OP_ADD; + scriptB[off++] = OP_3; + scriptB[off++] = OP_EQUAL; + } + + /* Case 1: thresh(2, older(100), s:pk_k(A)): timelock met, sig_A available → SAT */ + { + sig_entry_t entry; + entry.pk = keyA; make_fake_sig(entry.sig, 0xA1, 0xA2); entry.sig_len = 71; + thresh_sig_tl_ctx_t ctx = { { &entry, 1 }, { 100, 0 } }; + ms_satisfier stfr = { thresh_sig_tl_lookup_sig, NULL, NULL, thresh_sig_tl_check_older, NULL, NULL, &ctx }; + + ret = decode_script_to_node(scriptA, sizeof(scriptA), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 1); + CHECK(sat.witness.items[0].data_len == 71); + CHECK(memcmp(sat.witness.items[0].data, entry.sig, 71) == 0); + CHECK(sat.has_sig == true); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 2: thresh(2, older(100), s:pk_k(A)): timelock not met, sig available → UNAVAILABLE + * older returns UNAVAILABLE when timelock not met, which propagates through thresh concat */ + { + sig_entry_t entry; + entry.pk = keyA; make_fake_sig(entry.sig, 0xA1, 0xA2); entry.sig_len = 71; + thresh_sig_tl_ctx_t ctx = { { &entry, 1 }, { 0, 0 } }; + ms_satisfier stfr = { thresh_sig_tl_lookup_sig, NULL, NULL, thresh_sig_tl_check_older, NULL, NULL, &ctx }; + + ret = decode_script_to_node(scriptA, sizeof(scriptA), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 3: thresh(2, older(100), s:pk_k(A)): timelock met, no sig → IMPOSSIBLE */ + { + thresh_sig_tl_ctx_t ctx = { { NULL, 0 }, { 100, 0 } }; + ms_satisfier stfr = { thresh_sig_tl_lookup_sig, NULL, NULL, thresh_sig_tl_check_older, NULL, NULL, &ctx }; + + ret = decode_script_to_node(scriptA, sizeof(scriptA), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 4: thresh(2, older(100), s:pk_k(A)): NULL satisfier → IMPOSSIBLE */ + { + ret = decode_script_to_node(scriptA, sizeof(scriptA), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 5: thresh(3, older(100), s:pk_k(A), s:pk_k(B)): all three met → SAT */ + { + sig_entry_t entries[2]; + entries[0].pk = keyA; make_fake_sig(entries[0].sig, 0xA1, 0xA2); entries[0].sig_len = 71; + entries[1].pk = keyB; make_fake_sig(entries[1].sig, 0xB1, 0xB2); entries[1].sig_len = 71; + thresh_sig_tl_ctx_t ctx = { { entries, 2 }, { 100, 0 } }; + ms_satisfier stfr = { thresh_sig_tl_lookup_sig, NULL, NULL, thresh_sig_tl_check_older, NULL, NULL, &ctx }; + + ret = decode_script_to_node(scriptB, sizeof(scriptB), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 2); + CHECK(sat.witness.items[0].data_len == 71); /* sig_B first (last child, first in witness) */ + CHECK(memcmp(sat.witness.items[0].data, entries[1].sig, 71) == 0); + CHECK(sat.witness.items[1].data_len == 71); /* sig_A second */ + CHECK(memcmp(sat.witness.items[1].data, entries[0].sig, 71) == 0); + CHECK(sat.has_sig == true); + CHECK(dissat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 6: thresh(3, older(100), s:pk_k(A), s:pk_k(B)): k=3 but only older+sig_A → IMPOSSIBLE */ + { + sig_entry_t entry; + entry.pk = keyA; make_fake_sig(entry.sig, 0xA1, 0xA2); entry.sig_len = 71; + thresh_sig_tl_ctx_t ctx = { { &entry, 1 }, { 100, 0 } }; + ms_satisfier stfr = { thresh_sig_tl_lookup_sig, NULL, NULL, thresh_sig_tl_check_older, NULL, NULL, &ctx }; + + ret = decode_script_to_node(scriptB, sizeof(scriptB), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* Case 7: thresh(3, older(100), s:pk_k(A), s:pk_k(B)): malleable mode, all met → SAT */ + { + sig_entry_t entries[2]; + entries[0].pk = keyA; make_fake_sig(entries[0].sig, 0xA1, 0xA2); entries[0].sig_len = 71; + entries[1].pk = keyB; make_fake_sig(entries[1].sig, 0xB1, 0xB2); entries[1].sig_len = 71; + thresh_sig_tl_ctx_t ctx = { { entries, 2 }, { 100, 0 } }; + ms_satisfier stfr = { thresh_sig_tl_lookup_sig, NULL, NULL, thresh_sig_tl_check_older, NULL, NULL, &ctx }; + + ret = decode_script_to_node(scriptB, sizeof(scriptB), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, &stfr, true, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_STACK); + CHECK(sat.witness.num_items == 2); + CHECK(sat.has_sig == true); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +static bool test_decode_negative(void) +{ + bool ok = true; + ms_node *output = NULL; + int ret; + + /* Tokenizer-level: OP_1NEGATE alone */ + { + unsigned char script[] = { OP_1NEGATE }; + ret = decode_script_to_node(script, 1, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Tokenizer-level: truncated push (0x21 claims 33 bytes but script ends) */ + { + unsigned char script[] = { 0x21 }; + ret = decode_script_to_node(script, 1, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Tokenizer-level: OP_RESERVED (0x50) — unknown opcode */ + { + unsigned char script[] = { OP_RESERVED }; + ret = decode_script_to_node(script, 1, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Decoder-level: single OP_CHECKSIG — no preceding expression to wrap */ + { + unsigned char script[] = { OP_CHECKSIG }; + ret = decode_script_to_node(script, 1, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Decoder-level: empty script — NT_EXPRESSION gets NULL from tk_cursor_peek */ + { + ret = decode_script_to_node(NULL, 0, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Decoder-level: pk_k then stray OP_CHECKSIG — is_and_v triggers NT_EXPRESSION + * which finds no further expression after consuming TK_CHECK_SIG */ + { + unsigned char script[35]; + script[0] = OP_CHECKSIG; + script[1] = 0x21; + memset(script + 2, 0x02, 33); + ret = decode_script_to_node(script, 35, 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Semantic: multi(0, pk1) — k=0 rejected */ + { + unsigned char pk1[33]; + unsigned char script[1 + 34 + 1 + 1]; + size_t off = 0; + memset(pk1, 0x02, 33); + script[off++] = OP_0; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = OP_1; + script[off++] = OP_CHECKMULTISIG; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Semantic: multi(3, pk1, pk2) — k > n rejected */ + { + unsigned char pk1[33], pk2[33]; + unsigned char script[1 + 34 + 34 + 1 + 1]; + size_t off = 0; + memset(pk1, 0x02, 33); + memset(pk2, 0x03, 33); + script[off++] = OP_3; + script[off++] = 0x21; memcpy(script + off, pk1, 33); off += 33; + script[off++] = 0x21; memcpy(script + off, pk2, 33); off += 33; + script[off++] = OP_2; + script[off++] = OP_CHECKMULTISIG; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Semantic: thresh(0, pk_k(A)) — k=0 rejected */ + { + unsigned char keyA[33]; + unsigned char script[34 + 1 + 1]; + size_t off = 0; + memset(keyA, 0x02, 33); + script[off++] = 0x21; memcpy(script + off, keyA, 33); off += 33; + script[off++] = OP_0; + script[off++] = OP_EQUAL; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + /* Semantic: thresh(3, pk_k(A), s:pk_k(B)) — k=3 > n=2 rejected */ + { + unsigned char keyA[33], keyB[33]; + unsigned char script[34 + 1 + 34 + 1 + 1 + 1]; + size_t off = 0; + memset(keyA, 0x02, 33); + memset(keyB, 0x03, 33); + script[off++] = 0x21; memcpy(script + off, keyA, 33); off += 33; + script[off++] = OP_SWAP; + script[off++] = 0x21; memcpy(script + off, keyB, 33); off += 33; + script[off++] = OP_ADD; + script[off++] = OP_3; + script[off++] = OP_EQUAL; + ret = decode_script_to_node(script, sizeof(script), 0, &output); + CHECK(ret == WALLY_EINVAL); + CHECK(output == NULL); + } + + return ok; +} + +static bool test_satisfy_negative(void) +{ + bool ok = true; + ms_node *node = NULL; + ms_satisfaction sat, dissat; + int ret; + + /* pk_k, no sig — lookup_sig always returns false */ + { + unsigned char script[34]; + script[0] = 0x21; + memset(script + 1, 0x02, 33); + ret = decode_script_to_node(script, 34, 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + sig_ctx_t ctx = { NULL, 0 }; + ms_satisfier stfr = { multi_lookup_sig, NULL, NULL, NULL, NULL, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* pk_h, no sig or key — lookup_pkh is NULL */ + { + unsigned char script[24]; + unsigned char hash20[20]; + memset(hash20, 0x77, 20); + script[0] = OP_DUP; + script[1] = OP_HASH160; + script[2] = 0x14; + memcpy(script + 3, hash20, 20); + script[23] = OP_EQUALVERIFY; + ret = decode_script_to_node(script, 24, 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + satisfy_node(node, NULL, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* sha256, no preimage — lookup_preimage is NULL */ + { + unsigned char hash32[32]; + unsigned char script[39]; + memset(hash32, 0xaa, 32); + script[0] = 0x82; /* OP_SIZE */ + script[1] = 0x01; script[2] = 0x20; /* push 1 byte: 32 */ + script[3] = 0x88; /* OP_EQUALVERIFY */ + script[4] = 0xa8; /* OP_SHA256 */ + script[5] = 0x20; /* push 32 bytes */ + memcpy(script + 6, hash32, 32); + script[38] = 0x87; /* OP_EQUAL */ + ret = decode_script_to_node(script, 39, 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + ms_satisfier stfr = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + /* older(100), timelock not met — check_older returns false */ + { + unsigned char script[] = { 0x01, 0x64, OP_CHECKSEQUENCEVERIFY }; + ret = decode_script_to_node(script, sizeof(script), 0, &node); + CHECK(ret == WALLY_OK); + CHECK(node != NULL); + tl_ctx_t ctx = { 0, 0 }; + ms_satisfier stfr = { NULL, NULL, NULL, tl_check_older, tl_check_after, NULL, &ctx }; + satisfy_node(node, &stfr, false, &sat, &dissat); + CHECK(sat.witness.kind == MS_WITNESS_UNAVAILABLE); + ms_satisfaction_free(&sat); + ms_satisfaction_free(&dissat); + ms_node_free(node); node = NULL; + } + + return ok; +} + +int main(void) +{ + bool ok = true; + if (!test_tokenize_script()) { + printf("[test_tokenize_script] failed!\n"); + ok = false; + } + if (!test_decode_pk()) { + printf("[test_decode_pk] failed!\n"); + ok = false; + } + if (!test_decode_hash()) { + printf("[test_decode_hash] failed!\n"); + ok = false; + } + if (!test_decode_multi()) { + printf("[test_decode_multi] failed!\n"); + ok = false; + } + if (!test_decode_multi_a()) { + printf("[test_decode_multi_a] failed!\n"); + ok = false; + } + if (!test_decode_and_v()) { + printf("[test_decode_and_v] failed!\n"); + ok = false; + } + if (!test_decode_and_b()) { + printf("[test_decode_and_b] failed!\n"); + ok = false; + } + if (!test_decode_or_b()) { + printf("[test_decode_or_b] failed!\n"); + ok = false; + } + if (!test_decode_or_c()) { + printf("[test_decode_or_c] failed!\n"); + ok = false; + } + if (!test_decode_or_d()) { + printf("[test_decode_or_d] failed!\n"); + ok = false; + } + if (!test_decode_or_i()) { + printf("[test_decode_or_i] failed!\n"); + ok = false; + } + if (!test_decode_andor()) { + printf("[test_decode_andor] failed!\n"); + ok = false; + } + if (!test_decode_thresh()) { + printf("[test_decode_thresh] failed!\n"); + ok = false; + } + if (!test_decode_wrappers()) { + printf("[test_decode_wrappers] failed!\n"); + ok = false; + } + if (!test_satisfy_timelocks()) { + printf("[test_satisfy_timelocks] failed!\n"); + ok = false; + } + if (!test_satisfy_or_b()) { + printf("[test_satisfy_or_b] failed!\n"); + ok = false; + } + if (!test_satisfy_or_c()) { + printf("[test_satisfy_or_c] failed!\n"); + ok = false; + } + if (!test_satisfy_or_d()) { + printf("[test_satisfy_or_d] failed!\n"); + ok = false; + } + if (!test_satisfy_or_i()) { + printf("[test_satisfy_or_i] failed!\n"); + ok = false; + } + if (!test_satisfy_multi()) { + printf("[test_satisfy_multi] failed!\n"); + ok = false; + } + if (!test_satisfy_multi_a()) { + printf("[test_satisfy_multi_a] failed!\n"); + ok = false; + } + if (!test_satisfy_andor()) { + printf("[test_satisfy_andor] failed!\n"); + ok = false; + } + if (!test_satisfy_thresh()) { + printf("[test_satisfy_thresh] failed!\n"); + ok = false; + } + if (!test_decode_negative()) { + printf("[test_decode_negative] failed!\n"); + ok = false; + } + if (!test_satisfy_negative()) { + printf("[test_satisfy_negative] failed!\n"); + ok = false; + } + wally_cleanup(0); + return ok ? 0 : 1; +} diff --git a/src/data/bip327/det_sign_vectors.json b/src/data/bip327/det_sign_vectors.json new file mode 100644 index 000000000..261669ccd --- /dev/null +++ b/src/data/bip327/det_sign_vectors.json @@ -0,0 +1,144 @@ +{ + "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "020000000000000000000000000000000000000000000000000000000000000007" + ], + "msgs": [ + "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "2626262626262626262626262626262626262626262626262626262626262626262626262626" + ], + "valid_test_cases": [ + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [0, 1, 2], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "03D96275257C2FCCBB6EEB77BDDF51D3C88C26EE1626C6CDA8999B9D34F4BA13A60309BE2BF883C6ABE907FA822D9CA166D51A3DCC28910C57528F6983FC378B7843", + "41EA65093F71D084785B20DC26A887CD941C9597860A21660CBDB9CC2113CAD3" + ] + }, + { + "rand": null, + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 0, 2], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 1, + "expected": [ + "028FBCCF5BB73A7B61B270BAD15C0F9475D577DD85C2157C9D38BEF1EC922B48770253BE3638C87369BC287E446B7F2C8CA5BEB9FFBD1EA082C62913982A65FC214D", + "AEAA31262637BFA88D5606679018A0FEEEC341F3107D1199857F6C81DE61B8DD" + ] + }, + { + "rand": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "aggothernonce": "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "key_indices": [1, 2, 0], + "tweaks": [], + "is_xonly": [], + "msg_index": 1, + "signer_index": 2, + "expected": [ + "024FA8D774F0C8743FAA77AFB4D08EE5A013C2E8EEAD8A6F08A77DDD2D28266DB803050905E8C994477F3F2981861A2E3791EF558626E645FBF5AA131C5D6447C2C2", + "FEE28A56B8556B7632E42A84122C51A4861B1F2DEC7E81B632195E56A52E3E13" + ], + "comment": "Message longer than 32 bytes" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046", + "key_indices": [0, 1, 2], + "tweaks": ["E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB"], + "is_xonly": [true], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "031E07C0D11A0134E55DB1FC16095ADCBD564236194374AA882BFB3C78273BF673039D0336E8CA6288C00BFC1F8B594563529C98661172B9BC1BE85C23A4CE1F616B", + "7B1246C5889E59CB0375FA395CC86AC42D5D7D59FD8EAB4FDF1DCAB2B2F006EA" + ], + "comment": "Tweaked public key" + } + ], + "error_test_cases": [ + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 0, 3], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 1, + "error": { + "type": "invalid_contribution", + "signer": 2, + "contrib": "pubkey" + }, + "comment": "Signer 2 provided an invalid public key" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 2], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 1, + "error": { + "type": "value", + "message": "The signer's pubkey must be included in the list of pubkeys." + }, + "comment": "The signers pubkey is not in the list of pubkeys" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0437C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 2, 0], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 2, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggothernonce" + }, + "comment": "aggothernonce is invalid due wrong tag, 0x04, in the first half" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0000000000000000000000000000000000000000000000000000000000000000000287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 2, 0], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 2, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggothernonce" + }, + "comment": "aggothernonce is invalid because first half corresponds to point at infinity" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 2, 0], + "tweaks": ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"], + "is_xonly": [false], + "msg_index": 0, + "signer_index": 2, + "error": { + "type": "value", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is invalid because it exceeds group size" + } + ] +} diff --git a/src/data/bip327/key_agg_vectors.json b/src/data/bip327/key_agg_vectors.json new file mode 100644 index 000000000..b2e623de6 --- /dev/null +++ b/src/data/bip327/key_agg_vectors.json @@ -0,0 +1,88 @@ +{ + "pubkeys": [ + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + "020000000000000000000000000000000000000000000000000000000000000005", + "02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", + "04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" + ], + "tweaks": [ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B" + ], + "valid_test_cases": [ + { + "key_indices": [0, 1, 2], + "expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C" + }, + { + "key_indices": [2, 1, 0], + "expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B" + }, + { + "key_indices": [0, 0, 0], + "expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935" + }, + { + "key_indices": [0, 0, 1, 1], + "expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E" + } + ], + "error_test_cases": [ + { + "key_indices": [0, 3], + "tweak_indices": [], + "is_xonly": [], + "error": { + "type": "invalid_contribution", + "signer": 1, + "contrib": "pubkey" + }, + "comment": "Invalid public key" + }, + { + "key_indices": [0, 4], + "tweak_indices": [], + "is_xonly": [], + "error": { + "type": "invalid_contribution", + "signer": 1, + "contrib": "pubkey" + }, + "comment": "Public key exceeds field size" + }, + { + "key_indices": [5, 0], + "tweak_indices": [], + "is_xonly": [], + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubkey" + }, + "comment": "First byte of public key is not 2 or 3" + }, + { + "key_indices": [0, 1], + "tweak_indices": [0], + "is_xonly": [true], + "error": { + "type": "value", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is out of range" + }, + { + "key_indices": [6], + "tweak_indices": [1], + "is_xonly": [false], + "error": { + "type": "value", + "message": "The result of tweaking cannot be infinity." + }, + "comment": "Intermediate tweaking result is point at infinity" + } + ] +} diff --git a/src/data/bip327/nonce_agg_vectors.json b/src/data/bip327/nonce_agg_vectors.json new file mode 100644 index 000000000..1c04b8818 --- /dev/null +++ b/src/data/bip327/nonce_agg_vectors.json @@ -0,0 +1,51 @@ +{ + "pnonces": [ + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ], + "valid_test_cases": [ + { + "pnonce_indices": [0, 1], + "expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8" + }, + { + "pnonce_indices": [2, 3], + "expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000", + "comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes" + } + ], + "error_test_cases": [ + { + "pnonce_indices": [0, 4], + "error": { + "type": "invalid_contribution", + "signer": 1, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half" + }, + { + "pnonce_indices": [5, 1], + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate" + }, + { + "pnonce_indices": [6, 1], + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 0 is invalid because second half exceeds field size" + } + ] +} diff --git a/src/data/bip327/nonce_gen_vectors.json b/src/data/bip327/nonce_gen_vectors.json new file mode 100644 index 000000000..ced946f3e --- /dev/null +++ b/src/data/bip327/nonce_gen_vectors.json @@ -0,0 +1,44 @@ +{ + "test_cases": [ + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "sk": "0202020202020202020202020202020202020202020202020202020202020202", + "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", + "msg": "0101010101010101010101010101010101010101010101010101010101010101", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "B114E502BEAA4E301DD08A50264172C84E41650E6CB726B410C0694D59EFFB6495B5CAF28D045B973D63E3C99A44B807BDE375FD6CB39E46DC4A511708D0E9D2024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "expected_pubnonce": "02F7BE7089E8376EB355272368766B17E88E7DB72047D05E56AA881EA52B3B35DF02C29C8046FDD0DED4C7E55869137200FBDBFE2EB654267B6D7013602CAED3115A" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "sk": "0202020202020202020202020202020202020202020202020202020202020202", + "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", + "msg": "", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "E862B068500320088138468D47E0E6F147E01B6024244AE45EAC40ACE5929B9F0789E051170B9E705D0B9EB49049A323BBBBB206D8E05C19F46C6228742AA7A9024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "expected_pubnonce": "023034FA5E2679F01EE66E12225882A7A48CC66719B1B9D3B6C4DBD743EFEDA2C503F3FD6F01EB3A8E9CB315D73F1F3D287CAFBB44AB321153C6287F407600205109" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "sk": "0202020202020202020202020202020202020202020202020202020202020202", + "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", + "msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "3221975ACBDEA6820EABF02A02B7F27D3A8EF68EE42787B88CBEFD9AA06AF3632EE85B1A61D8EF31126D4663A00DD96E9D1D4959E72D70FE5EBB6E7696EBA66F024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "expected_pubnonce": "02E5BBC21C69270F59BD634FCBFA281BE9D76601295345112C58954625BF23793A021307511C79F95D38ACACFF1B4DA98228B77E65AA216AD075E9673286EFB4EAF3" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "sk": null, + "pk": "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "aggpk": null, + "msg": null, + "extra_in": null, + "expected_secnonce": "89BDD787D0284E5E4D5FC572E49E316BAB7E21E3B1830DE37DFE80156FA41A6D0B17AE8D024C53679699A6FD7944D9C4A366B514BAF43088E0708B1023DD289702F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "expected_pubnonce": "02C96E7CB1E8AA5DAC64D872947914198F607D90ECDE5200DE52978AD5DED63C000299EC5117C2D29EDEE8A2092587C3909BE694D5CFF0667D6C02EA4059F7CD9786" + } + ] +} diff --git a/src/data/bip327/sig_agg_vectors.json b/src/data/bip327/sig_agg_vectors.json new file mode 100644 index 000000000..519562c34 --- /dev/null +++ b/src/data/bip327/sig_agg_vectors.json @@ -0,0 +1,152 @@ +{ + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05", + "03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C", + "02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581" + ], + "pnonces": [ + "036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E", + "03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00", + "02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6", + "031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9", + "023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A", + "02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00" + ], + "tweaks": [ + "B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C", + "A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC", + "75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8" + ], + "psigs": [ + "B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB", + "6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64", + "9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505", + "66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15", + "4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE", + "DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4", + "97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC", + "53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869", + "valid_test_cases": [ + { + "aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B", + "nonce_indices": [ + 0, + 1 + ], + "key_indices": [ + 0, + 1 + ], + "tweak_indices": [], + "is_xonly": [], + "psig_indices": [ + 0, + 1 + ], + "expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E" + }, + { + "aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20", + "nonce_indices": [ + 0, + 2 + ], + "key_indices": [ + 0, + 2 + ], + "tweak_indices": [], + "is_xonly": [], + "psig_indices": [ + 2, + 3 + ], + "expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9" + }, + { + "aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D", + "nonce_indices": [ + 0, + 3 + ], + "key_indices": [ + 0, + 2 + ], + "tweak_indices": [ + 0 + ], + "is_xonly": [ + false + ], + "psig_indices": [ + 4, + 5 + ], + "expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC" + }, + { + "aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD", + "nonce_indices": [ + 0, + 4 + ], + "key_indices": [ + 0, + 3 + ], + "tweak_indices": [ + 0, + 1, + 2 + ], + "is_xonly": [ + true, + false, + true + ], + "psig_indices": [ + 6, + 7 + ], + "expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E" + } + ], + "error_test_cases": [ + { + "aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD", + "nonce_indices": [ + 0, + 4 + ], + "key_indices": [ + 0, + 3 + ], + "tweak_indices": [ + 0, + 1, + 2 + ], + "is_xonly": [ + true, + false, + true + ], + "psig_indices": [ + 7, + 8 + ], + "error": { + "type": "invalid_contribution", + "signer": 1, + "contrib": "psig" + }, + "comment": "Partial signature is invalid because it exceeds group size" + } + ] +} diff --git a/src/data/bip327/sign_verify_vectors.json b/src/data/bip327/sign_verify_vectors.json new file mode 100644 index 000000000..f71c8dd9d --- /dev/null +++ b/src/data/bip327/sign_verify_vectors.json @@ -0,0 +1,212 @@ +{ + "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661", + "020000000000000000000000000000000000000000000000000000000000000007" + ], + "secnonces": [ + "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" + ], + "pnonces": [ + "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046", + "0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480" + ], + "aggnonces": [ + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009", + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ], + "msgs": [ + "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "", + "2626262626262626262626262626262626262626262626262626262626262626262626262626" + ], + "valid_test_cases": [ + { + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "expected": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB" + }, + { + "key_indices": [1, 0, 2], + "nonce_indices": [1, 0, 2], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 1, + "expected": "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 2, + "expected": "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900" + }, + { + "key_indices": [0, 1], + "nonce_indices": [0, 3], + "aggnonce_index": 1, + "msg_index": 0, + "signer_index": 0, + "expected": "AE386064B26105404798F75DE2EB9AF5EDA5387B064B83D049CB7C5E08879531", + "comment": "Both halves of aggregate nonce correspond to point at infinity" + }, + { + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "aggnonce_index": 0, + "msg_index": 1, + "signer_index": 0, + "expected": "D7D63FFD644CCDA4E62BC2BC0B1D02DD32A1DC3030E155195810231D1037D82D", + "comment": "Empty message" + }, + { + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "aggnonce_index": 0, + "msg_index": 2, + "signer_index": 0, + "expected": "E184351828DA5094A97C79CABDAAA0BFB87608C32E8829A4DF5340A6F243B78C", + "comment": "38-byte message" + } + ], + "sign_error_test_cases": [ + { + "key_indices": [1, 2], + "aggnonce_index": 0, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "value", + "message": "The signer's pubkey must be included in the list of pubkeys." + }, + "comment": "The signers pubkey is not in the list of pubkeys. This test case is optional: it can be skipped by implementations that do not check that the signer's pubkey is included in the list of pubkeys." + }, + { + "key_indices": [1, 0, 3], + "aggnonce_index": 0, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "invalid_contribution", + "signer": 2, + "contrib": "pubkey" + }, + "comment": "Signer 2 provided an invalid public key" + }, + { + "key_indices": [1, 2, 0], + "aggnonce_index": 2, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half" + }, + { + "key_indices": [1, 2, 0], + "aggnonce_index": 3, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate" + }, + { + "key_indices": [1, 2, 0], + "aggnonce_index": 4, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid because second half exceeds field size" + }, + { + "key_indices": [0, 1, 2], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 1, + "error": { + "type": "value", + "message": "first secnonce value is out of range." + }, + "comment": "Secnonce is invalid which may indicate nonce reuse" + } + ], + "verify_fail_test_cases": [ + { + "sig": "FED54434AD4CFE953FC527DC6A5E5BE8F6234907B7C187559557CE87A0541C46", + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "msg_index": 0, + "signer_index": 0, + "comment": "Wrong signature (which is equal to the negation of valid signature)" + }, + { + "sig": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB", + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "msg_index": 0, + "signer_index": 1, + "comment": "Wrong signer" + }, + { + "sig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "msg_index": 0, + "signer_index": 0, + "comment": "Signature exceeds group size" + } + ], + "verify_error_test_cases": [ + { + "sig": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB", + "key_indices": [0, 1, 2], + "nonce_indices": [4, 1, 2], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubnonce" + }, + "comment": "Invalid pubnonce" + }, + { + "sig": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB", + "key_indices": [3, 1, 2], + "nonce_indices": [0, 1, 2], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubkey" + }, + "comment": "Invalid pubkey" + } + ] +} diff --git a/src/data/bip327/tweak_vectors.json b/src/data/bip327/tweak_vectors.json new file mode 100644 index 000000000..d0a7cfe83 --- /dev/null +++ b/src/data/bip327/tweak_vectors.json @@ -0,0 +1,84 @@ +{ + "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ], + "secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "pnonces": [ + "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046" + ], + "aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "tweaks": [ + "E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB", + "AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455", + "F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0", + "1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "valid_test_cases": [ + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0], + "is_xonly": [true], + "signer_index": 2, + "expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91", + "comment": "A single x-only tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0], + "is_xonly": [false], + "signer_index": 2, + "expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D", + "comment": "A single plain tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1], + "is_xonly": [false, true], + "signer_index": 2, + "expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408", + "comment": "A plain tweak followed by an x-only tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1, 2, 3], + "is_xonly": [false, false, true, true], + "signer_index": 2, + "expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435", + "comment": "Four tweaks: plain, plain, x-only, x-only." + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1, 2, 3], + "is_xonly": [true, false, true, false], + "signer_index": 2, + "expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239", + "comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error." + } + ], + "error_test_cases": [ + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [4], + "is_xonly": [false], + "signer_index": 2, + "error": { + "type": "value", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is invalid because it exceeds group size" + } + ] +} diff --git a/src/data/bip379/gen_golden/Cargo.lock b/src/data/bip379/gen_golden/Cargo.lock new file mode 100644 index 000000000..d7fc9c02f --- /dev/null +++ b/src/data/bip379/gen_golden/Cargo.lock @@ -0,0 +1,250 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative 0.2.2", + "hex_lit", + "secp256k1", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-units" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346568ebaab2918487cea76dd55dae13c27bb618cdb737c952e69eb2017c4118" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.2", +] + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "gen_golden" +version = "0.1.0" +dependencies = [ + "bitcoin", + "miniscript", + "serde", + "serde_json", +] + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366fa3443ac84474447710ec17bb00b05dfbd096137817981e86f992f21a2793" + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "miniscript" +version = "13.0.0" +dependencies = [ + "bech32", + "bitcoin", + "hex-conservative 1.0.1", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/src/data/bip379/gen_golden/Cargo.toml b/src/data/bip379/gen_golden/Cargo.toml new file mode 100644 index 000000000..108ab2df4 --- /dev/null +++ b/src/data/bip379/gen_golden/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "gen_golden" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "gen_golden" +path = "src/main.rs" + +[dependencies] +miniscript = { path = "../../../../../rust-miniscript", features = ["std"] } +bitcoin = { version = "0.32.6", default-features = false, features = ["std"] } +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } diff --git a/src/data/bip379/gen_golden/src/main.rs b/src/data/bip379/gen_golden/src/main.rs new file mode 100644 index 000000000..ba82e46eb --- /dev/null +++ b/src/data/bip379/gen_golden/src/main.rs @@ -0,0 +1,99 @@ +use std::collections::BTreeMap; + +use bitcoin::hashes::{ripemd160, sha256, Hash}; +use bitcoin::hex::DisplayHex; +use miniscript::miniscript::satisfy::{Preimage32, Satisfier}; +use miniscript::{Miniscript, Segwitv0}; + +// SHA256([0x12;32]) and RIPEMD160([0x78;32]) — must match test_satisfier_diff.py +const SHA256_PRE: [u8; 32] = [0x12u8; 32]; +const RIPEMD160_PRE: [u8; 32] = [0x78u8; 32]; +const SHA256_H: &str = "b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94"; +const RIPEMD_H: &str = "6646adac9fb158a6df66130746d48a0e7f9db390"; + +struct CorpusSatisfier { + expected_sha256: sha256::Hash, + expected_ripemd160: ripemd160::Hash, +} + +impl CorpusSatisfier { + fn new() -> Self { + Self { + expected_sha256: sha256::Hash::hash(&SHA256_PRE), + expected_ripemd160: ripemd160::Hash::hash(&RIPEMD160_PRE), + } + } +} + +impl Satisfier for CorpusSatisfier { + fn lookup_sha256(&self, h: &sha256::Hash) -> Option { + if h == &self.expected_sha256 { + Some(SHA256_PRE) + } else { + None + } + } + + fn lookup_ripemd160(&self, h: &ripemd160::Hash) -> Option { + if h == &self.expected_ripemd160 { + Some(RIPEMD160_PRE) + } else { + None + } + } + // lookup_hash256, lookup_hash160, check_after, check_older all return None/false + // (defaults) — hash256/hash160 can't be satisfied with our preimages; we don't + // model timelocks so golden data stays stable regardless of tx fields. +} + +fn sub_h(ms: &str) -> String { + let ms = ms.replace("hash256(H)", &format!("hash256({})", SHA256_H)); + let ms = ms.replace("sha256(H)", &format!("sha256({})", SHA256_H)); + let ms = ms.replace("hash160(H)", &format!("hash160({})", RIPEMD_H)); + ms.replace("ripemd160(H)", &format!("ripemd160({})", RIPEMD_H)) +} + +fn main() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let vectors_path = format!("{}/../miniscript_vectors.json", manifest_dir); + let golden_path = format!("{}/../satisfier_golden.json", manifest_dir); + + let vectors_data = std::fs::read_to_string(&vectors_path) + .unwrap_or_else(|e| panic!("failed to read {}: {}", vectors_path, e)); + let vectors: serde_json::Value = + serde_json::from_str(&vectors_data).expect("failed to parse miniscript_vectors.json"); + + let satisfier = CorpusSatisfier::new(); + let mut golden = BTreeMap::>::new(); + + let valid_cases = vectors["valid_cases"] + .as_array() + .expect("valid_cases not array"); + + for tc in valid_cases { + let ms_raw = tc["miniscript"].as_str().expect("miniscript not string"); + let substituted = sub_h(ms_raw); + + let ms = match Miniscript::::from_str_insane(&substituted) { + Ok(m) => m, + Err(_) => continue, + }; + + let stack = match ms.satisfy_malleable(&satisfier) { + Ok(s) => s, + Err(_) => continue, + }; + + let script_hex = format!("{:x}", ms.encode()); + let mut witness: Vec = stack.iter().map(|item| item.to_lower_hex_string()).collect(); + witness.push(script_hex); + + golden.insert(substituted, witness); + } + + let out = serde_json::to_string_pretty(&golden).expect("failed to serialize golden"); + std::fs::write(&golden_path, out) + .unwrap_or_else(|e| panic!("failed to write {}: {}", golden_path, e)); + + println!("wrote {} golden entries to {}", golden.len(), golden_path); +} diff --git a/src/data/bip379/miniscript_vectors.json b/src/data/bip379/miniscript_vectors.json new file mode 100644 index 000000000..53638a3f6 --- /dev/null +++ b/src/data/bip379/miniscript_vectors.json @@ -0,0 +1,63 @@ +{ + "source": "rust-miniscript bitcoind-tests/tests/data/random_ms.txt and src/miniscript/ms_tests.rs", + "commit": "1834bc0635278b0fcdb6b6b2ebe3a7fef2b8154e", + "note": "H is a placeholder substituted by the test: sha256(H)/hash256(H) use a 32-byte hash; ripemd160(H)/hash160(H) use a 20-byte hash", + "valid_cases": [ + {"miniscript": "and_b(lltvln:after(1231488000),s:pk(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", "comment": "random_ms.txt line 1"}, + {"miniscript": "uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "comment": "random_ms.txt line 2"}, + {"miniscript": "or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", "comment": "random_ms.txt line 3"}, + {"miniscript": "j:and_v(vdv:after(1567547623),older(16))", "comment": "random_ms.txt line 4"}, + {"miniscript": "t:and_v(vu:hash256(H),v:sha256(H))", "comment": "random_ms.txt line 5"}, + {"miniscript": "t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(H))", "comment": "random_ms.txt line 6"}, + {"miniscript": "or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", "comment": "random_ms.txt line 7"}, + {"miniscript": "or_d(sha256(H),and_n(un:after(499999999),older(4194305)))", "comment": "random_ms.txt line 8"}, + {"miniscript": "and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(H))", "comment": "random_ms.txt line 9"}, + {"miniscript": "j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", "comment": "random_ms.txt line 10"}, + {"miniscript": "and_b(older(16),s:or_d(sha256(H),n:after(1567547623)))", "comment": "random_ms.txt line 11"}, + {"miniscript": "j:and_v(v:ripemd160(H),or_d(sha256(H),older(16)))", "comment": "random_ms.txt line 12"}, + {"miniscript": "and_b(hash256(H),a:and_b(hash256(H),a:older(1)))", "comment": "random_ms.txt line 13"}, + {"miniscript": "thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "comment": "random_ms.txt line 14"}, + {"miniscript": "and_n(sha256(H),t:or_i(v:older(4252898),v:older(16)))", "comment": "random_ms.txt line 15"}, + {"miniscript": "or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(H))", "comment": "random_ms.txt line 16"}, + {"miniscript": "c:and_v(or_c(sha256(H),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", "comment": "random_ms.txt line 17"}, + {"miniscript": "c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(H)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "comment": "random_ms.txt line 18"}, + {"miniscript": "and_v(andor(hash256(H),v:hash256(H),v:older(50000)),after(1231488000))", "comment": "random_ms.txt line 19"}, + {"miniscript": "andor(hash256(H),j:and_v(v:ripemd160(H),older(4194305)),ripemd160(H))", "comment": "random_ms.txt line 20"}, + {"miniscript": "or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(H))", "comment": "random_ms.txt line 21"}, + {"miniscript": "thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(H),a:ripemd160(H))", "comment": "random_ms.txt line 22"}, + {"miniscript": "and_n(sha256(H),uc:and_v(v:older(16),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", "comment": "random_ms.txt line 23"}, + {"miniscript": "and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(15),a:older(16)))", "comment": "random_ms.txt line 24"}, + {"miniscript": "c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", "comment": "random_ms.txt line 25"}, + {"miniscript": "or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", "comment": "random_ms.txt line 26"}, + {"miniscript": "c:andor(ripemd160(H),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(H),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", "comment": "random_ms.txt line 27"}, + {"miniscript": "c:andor(u:ripemd160(H),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", "comment": "random_ms.txt line 28"}, + {"miniscript": "c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "comment": "random_ms.txt line 29"}, + {"miniscript": "multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00)", "comment": "random_ms.txt line 30"}, + {"miniscript": "multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00)", "comment": "random_ms.txt line 31"}, + {"miniscript": "thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "comment": "random_ms.txt line 32"}, + {"miniscript": "thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "comment": "random_ms.txt line 33"}, + {"miniscript": "c:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01)", "comment": "random_ms.txt line 34"} + ], + "invalid_cases": [ + {"miniscript": "or_b(or_i(0,sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc)),after(1))", "comment": "ms_tests.rs: or_i(V,B) mixes types"}, + {"miniscript": "dc:sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc)", "comment": "ms_tests.rs: c: requires K type, sha256 is B"}, + {"miniscript": "or_b(c:sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),after(1))", "comment": "ms_tests.rs: c: requires K type, sha256 is B"}, + {"miniscript": "or_d(s:or_d(after(500000001),after(500000001)),after(500000001))", "comment": "ms_tests.rs: or_d first arg not Bdu (after is not dissatisfiable)"}, + {"miniscript": "cs:or_c(after(500000001),s:after(500000001))", "comment": "ms_tests.rs: s: requires Bo, after is Bz"}, + {"miniscript": "ns:or_d(after(500000001),sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc))", "comment": "ms_tests.rs: or_d first arg not Bdu"}, + {"miniscript": "cda:sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc)", "comment": "ms_tests.rs: c: requires K type, sha256 is B"}, + {"miniscript": "or_b(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),or_b(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),after(1)))", "comment": "ms_tests.rs: or_b second arg must be W, after is B"}, + {"miniscript": "ds:or_c(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc))", "comment": "ms_tests.rs: or_c second arg must be V, sha256 is B"}, + {"miniscript": "dvs:or_b(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc))", "comment": "ms_tests.rs: or_b second arg must be W, sha256 is B"}, + {"miniscript": "d:or_b(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc))", "comment": "ms_tests.rs: or_b second arg must be W, sha256 is B"}, + {"miniscript": "ca:after(1)", "comment": "ms_tests.rs: c: requires K type, a:after is W"}, + {"miniscript": "na:after(500000001)", "comment": "ms_tests.rs: n: requires B type, a:after is W"}, + {"miniscript": "js:after(1)", "comment": "ms_tests.rs: s: requires Bo type, after is Bz (zero-input)"}, + {"miniscript": "n:or_c(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc))", "comment": "ms_tests.rs: or_c second arg must be V, sha256 is B"}, + {"miniscript": "jvs:or_c(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc))", "comment": "ms_tests.rs: or_c second arg must be V, sha256 is B"}, + {"miniscript": "cvs:or_c(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc))", "comment": "ms_tests.rs: or_c second arg must be V, sha256 is B"}, + {"miniscript": "or_d(sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc),or_c(after(500000001),sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc)))", "comment": "ms_tests.rs: or_c second arg must be V, sha256 is B"}, + {"miniscript": "and_b(after(500000001),and_v(after(500000001),after(500000001)))", "comment": "ms_tests.rs: and_v first arg must be V, after is B"}, + {"miniscript": "or_d(after(1),and_v(after(1),1))", "comment": "ms_tests.rs: and_v first arg must be V, after is B"} + ] +} diff --git a/src/data/bip379/satisfier_golden.json b/src/data/bip379/satisfier_golden.json new file mode 100644 index 000000000..fce5e30cb --- /dev/null +++ b/src/data/bip379/satisfier_golden.json @@ -0,0 +1,41 @@ +{ + "andor(hash256(b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94),j:and_v(v:ripemd160(6646adac9fb158a6df66130746d48a0e7f9db390),older(4194305)),ripemd160(6646adac9fb158a6df66130746d48a0e7f9db390))": [ + "7878787878787878787878787878787878787878787878787878787878787878", + "0000000000000000000000000000000000000000000000000000000000000000", + "82012088aa20b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94876482012088a6146646adac9fb158a6df66130746d48a0e7f9db390876782926382012088a6146646adac9fb158a6df66130746d48a0e7f9db3908803010040b26868" + ], + "j:and_v(v:ripemd160(6646adac9fb158a6df66130746d48a0e7f9db390),or_d(sha256(b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94),older(16)))": [ + "1212121212121212121212121212121212121212121212121212121212121212", + "7878787878787878787878787878787878787878787878787878787878787878", + "82926382012088a6146646adac9fb158a6df66130746d48a0e7f9db3908882012088a820b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc9487736460b26868" + ], + "or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94))": [ + "1212121212121212121212121212121212121212121212121212121212121212", + "", + "766303e2e440b26903e2e440b2696892736482012088a820b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc948768" + ], + "or_d(sha256(b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94),and_n(un:after(499999999),older(4194305)))": [ + "1212121212121212121212121212121212121212121212121212121212121212", + "82012088a820b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc948773646304ff64cd1db19267006864006703010040b26868" + ], + "or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94))": [ + "1212121212121212121212121212121212121212121212121212121212121212", + "", + "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc948768" + ], + "t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94))": [ + "1212121212121212121212121212121212121212121212121212121212121212", + "", + "", + "", + "", + "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a820b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94886703010040b2696851" + ], + "thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc94),a:ripemd160(6646adac9fb158a6df66130746d48a0e7f9db390))": [ + "7878787878787878787878787878787878787878787878787878787878787878", + "1212121212121212121212121212121212121212121212121212121212121212", + "", + "025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc", + "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820b6acca81a0939a856c35e4c4188e95b91731aab1d4629a4cee79dd09ded4fc9487936b82012088a6146646adac9fb158a6df66130746d48a0e7f9db390876c935287" + ] +} \ No newline at end of file diff --git a/src/data/psbt.json b/src/data/psbt.json index 55859450a..8913e8337 100644 --- a/src/data/psbt.json +++ b/src/data/psbt.json @@ -1,5 +1,21 @@ { "invalid": [ + { + "comment": "PSBT_OUT_TAP_TREE leaf with odd (parity-bit) leaf version", + "psbt": "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgCoy9yG3hzhwPnK6yLW33ztNoP+Qj4F0eQCqHk0HW9vUAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAEGbwLBIiBzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAqwCwCIgYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWmsAcAiIET6pJoDON5IjI3//s37bzKfOAvVZu8gyN9tgT6rHEJzrCEHRPqkmgM43kiMjf/+zftvMp84C9Vm7yDI322BPqscQnM5AfBreYuSoQ7ZqdC7/Trxc6U7FhfaOkFZygCCFs2Fay4Odystp1YAAIABAACAAQAAgAAAAAADAAAAIQdQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAUAfEYeXSEHYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWk5ARis5AmIl4Xg6nDO67jhyokqenjq7eDy4pbPQ1lhqPTKdystp1YAAIABAACAAgAAgAAAAAADAAAAIQdzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAjkBKaW0kVCQFi11mv0/4Pk/ozJgVtC0CIy5M8rngmy42Cx3Ky2nVgAAgAEAAIADAACAAAAAAAMAAAAA" + }, + { + "comment": "PSBT_OUT_TAP_TREE leaf depth exceeds the BIP-341 maximum", + "psbt": "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgCoy9yG3hzhwPnK6yLW33ztNoP+Qj4F0eQCqHk0HW9vUAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAEGb4HAIiBzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAqwCwCIgYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWmsAcAiIET6pJoDON5IjI3//s37bzKfOAvVZu8gyN9tgT6rHEJzrCEHRPqkmgM43kiMjf/+zftvMp84C9Vm7yDI322BPqscQnM5AfBreYuSoQ7ZqdC7/Trxc6U7FhfaOkFZygCCFs2Fay4Odystp1YAAIABAACAAQAAgAAAAAADAAAAIQdQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAUAfEYeXSEHYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWk5ARis5AmIl4Xg6nDO67jhyokqenjq7eDy4pbPQ1lhqPTKdystp1YAAIABAACAAgAAgAAAAAADAAAAIQdzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAjkBKaW0kVCQFi11mv0/4Pk/ozJgVtC0CIy5M8rngmy42Cx3Ky2nVgAAgAEAAIADAACAAAAAAAMAAAAA" + }, + { + "comment": "PSBT_OUT_TAP_TREE leaf script length overruns the value buffer", + "psbt": "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgCoy9yG3hzhwPnK6yLW33ztNoP+Qj4F0eQCqHk0HW9vUAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAEGbwLAIiBzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAqwCwCIgYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWmsAcB/IET6pJoDON5IjI3//s37bzKfOAvVZu8gyN9tgT6rHEJzrCEHRPqkmgM43kiMjf/+zftvMp84C9Vm7yDI322BPqscQnM5AfBreYuSoQ7ZqdC7/Trxc6U7FhfaOkFZygCCFs2Fay4Odystp1YAAIABAACAAQAAgAAAAAADAAAAIQdQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAUAfEYeXSEHYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWk5ARis5AmIl4Xg6nDO67jhyokqenjq7eDy4pbPQ1lhqPTKdystp1YAAIABAACAAgAAgAAAAAADAAAAIQdzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAjkBKaW0kVCQFi11mv0/4Pk/ozJgVtC0CIy5M8rngmy42Cx3Ky2nVgAAgAEAAIADAACAAAAAAAMAAAAA" + }, + { + "comment": "PSBT_IN_TAP_LEAF_SCRIPT trailing leaf version does not match the control block", + "psbt": "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMJCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA" + }, { "comment": "Network transaction, not PSBT format", "psbt": "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==" diff --git a/src/descriptor.c b/src/descriptor.c index 83a74fe7b..827b334c5 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -2,6 +2,7 @@ #include "script.h" #include "script_int.h" +#include "descriptor_int.h" #include #include @@ -9,6 +10,9 @@ #include #include #include +#ifndef BUILD_STANDARD_SECP +#include +#endif #ifdef BUILD_ELEMENTS #include #endif @@ -55,7 +59,7 @@ /* OP_1 properties: Bzufmxk */ #define PROP_OP_1 (TYPE_B | PROP_Z | PROP_U | PROP_F | PROP_M | PROP_X | PROP_K) -#define KIND_MINISCRIPT 0x01 +/* KIND_MINISCRIPT is defined in descriptor_int.h */ #define KIND_DESCRIPTOR 0x02 /* Output Descriptor */ #define KIND_RAW 0x04 #define KIND_NUMBER 0x08 @@ -73,6 +77,7 @@ #define DESCRIPTOR_MIN_SIZE 20 #define MINISCRIPT_MULTI_MAX 20 +#define MULTI_A_NUM_KEYS_MAX 999 /* BIP-342: stack limited to 1000 elements, one used by the threshold */ #define REDEEM_SCRIPT_MAX_SIZE 520 #define WITNESS_SCRIPT_MAX_SIZE 10000 #define DESCRIPTOR_SEQUENCE_LOCKTIME_TYPE_FLAG 0x00400000 @@ -95,28 +100,10 @@ #define KIND_DESCRIPTOR_CT (0x00300000 | KIND_DESCRIPTOR) #define KIND_DESCRIPTOR_SLIP77 (0x00400000 | KIND_DESCRIPTOR) #define KIND_DESCRIPTOR_ELIP151 (0x00500000 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_MUSIG (0x00600000 | KIND_DESCRIPTOR) -/* miniscript */ -#define KIND_MINISCRIPT_PK (0x00000100 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_PKH (0x00000200 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_MULTI (0x00000300 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_PK_K (0x00001000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_PK_H (0x00002000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_OLDER (0x00010000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_AFTER (0x00020000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_SHA256 (0x00030000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_HASH256 (0x00040000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_RIPEMD160 (0x00050000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_HASH160 (0x00060000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_THRESH (0x00070000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_ANDOR (0x01000000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_AND_V (0x02000000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_AND_B (0x03000000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_AND_N (0x04000000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_OR_B (0x05000000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_OR_C (0x06000000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_OR_D (0x07000000 | KIND_MINISCRIPT) -#define KIND_MINISCRIPT_OR_I (0x08000000 | KIND_MINISCRIPT) +/* miniscript KIND_MINISCRIPT_* constants are defined in descriptor_int.h */ +#define KIND_TAPTREE_BRANCH 0x40 struct addr_ver_t { const unsigned char network; @@ -185,23 +172,6 @@ static const struct addr_ver_t g_address_versions[] = { }, }; -/* A node in a parsed miniscript expression */ -typedef struct ms_node_t { - struct ms_node_t *next; - struct ms_node_t *child; - struct ms_node_t *parent; - uint32_t kind; - uint32_t type_properties; - int64_t number; - const char *child_path; - const char *data; - uint32_t data_len; - uint32_t child_path_len; - char wrapper_str[12]; - unsigned short flags; /* WALLY_MS_IS_ flags */ - unsigned char builtin; -} ms_node; - typedef struct wally_descriptor { char *src; /* The canonical source script */ size_t src_len; /* Length of src */ @@ -229,21 +199,6 @@ static int ctx_add_key_node(ms_ctx *ctx, ms_node *node) static int ensure_unique_policy_keys(const ms_ctx *ctx); -/* Built-in miniscript expressions */ -typedef int (*node_verify_fn_t)(ms_ctx *ctx, ms_node *node); -typedef int (*node_gen_fn_t)(ms_ctx *ctx, ms_node *node, - unsigned char *script, size_t script_len, size_t *written); - -struct ms_builtin_t { - const char *name; - const uint32_t name_len; - const uint32_t kind; - const uint32_t type_properties; - const uint32_t child_count; /* Number of expected children */ - const node_verify_fn_t verify_fn; - const node_gen_fn_t generate_fn; -}; - /* FIXME: the max is actually 20 in a witness script */ #define CHECKMULTISIG_NUM_KEYS_MAX 15 struct multisig_sort_data_t { @@ -301,6 +256,7 @@ static const struct addr_ver_t *addr_ver_from_family( static const struct ms_builtin_t *builtin_get(const ms_node *node); static int generate_script(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written); +static int node_generation_size(const ms_node *node, size_t *total); static int is_valid_policy_map(const struct wally_map *map_in, bool *is_elements); static bool is_elements_policy_map(const struct wally_map *map_in) @@ -595,8 +551,11 @@ static int node_is_top(const ms_node *node) static bool node_is_root(const ms_node *node) { - /* True if this is a (possibly temporary) top level node, or an argument of a builtin */ - return !node->parent || node->parent->builtin; + /* True if this is a (possibly temporary) top level node, or an argument of a builtin, + * or a direct child of a taptree branch node (each taptree leaf is an independent + * miniscript expression that must be validated as its own root). */ + return !node->parent || node->parent->builtin || + node->parent->kind == KIND_TAPTREE_BRANCH; } #ifdef BUILD_ELEMENTS @@ -622,6 +581,40 @@ static void node_free(ms_node *node) } } +int ms_witness_init(ms_witness *w, uint32_t kind) +{ + memset(w, 0, sizeof(*w)); + w->kind = kind; + return WALLY_OK; +} + +void ms_witness_free(ms_witness *w) +{ + if (w) { + for (size_t i = 0; i < w->num_items; i++) + wally_free(w->items[i].data); + wally_free(w->items); + memset(w, 0, sizeof(*w)); + } +} + +int ms_satisfaction_init(ms_satisfaction *s, uint32_t witness_kind) +{ + int ret = ms_witness_init(&s->witness, witness_kind); + s->has_sig = false; + s->absolute_timelock = 0; + s->relative_timelock = 0; + return ret; +} + +void ms_satisfaction_free(ms_satisfaction *s) +{ + if (s) { + ms_witness_free(&s->witness); + memset(s, 0, sizeof(*s)); + } +} + static bool has_two_different_lock_states(uint32_t primary, uint32_t secondary) { return ((primary & PROP_G) && (secondary & PROP_H)) || @@ -728,6 +721,9 @@ static int verify_multi(ms_ctx *ctx, ms_node *node) const int64_t count = node_get_child_count(node); ms_node *top, *key; + if (node->flags & WALLY_MS_IS_TAPSCRIPT) + return WALLY_EINVAL; /* Use multi_a/sortedmulti_a inside tapscript */ + if (count < 2 || count - 1 > MINISCRIPT_MULTI_MAX) return WALLY_EINVAL; @@ -747,6 +743,43 @@ static int verify_multi(ms_ctx *ctx, ms_node *node) return WALLY_OK; } +static int verify_multi_a(ms_ctx *ctx, ms_node *node) +{ + (void)ctx; + const int64_t count = node_get_child_count(node); + ms_node *top, *key; + /* multi_a only valid inside tapscript */ + if (!(node->flags & WALLY_MS_IS_TAPSCRIPT)) + return WALLY_EINVAL; + + /* at least threshold + 1 key */ + if (count < 2 || count - 1 > MULTI_A_NUM_KEYS_MAX) + return WALLY_EINVAL; + + top = node->child; + if ( + /* top should never be NULL as there is at least 2 elements */ + !top ||!top->next || + /* threshold must be a plain value */ + top->builtin || top->kind != KIND_NUMBER || + /* threshold must be at least 1 */ + top->number <= 0 || + /* threshold must be <= key count */ + count - 1 < top->number + ) + return WALLY_EINVAL; + + key = top->next; + while (key) { + /* only bare key allowed */ + if (key->builtin || !(key->kind & KIND_KEY)) + return WALLY_EINVAL; + key = key->next; + } + node->type_properties = builtin_get(node)->type_properties; + return WALLY_OK; +} + static int verify_addr(ms_ctx *ctx, ms_node *node) { (void)ctx; @@ -766,10 +799,28 @@ static int verify_raw(ms_ctx *ctx, ms_node *node) return WALLY_OK; } +#ifndef BUILD_STANDARD_SECP +static int musig_pubkey_cmp(const void *a, const void *b) +{ + return memcmp(a, b, EC_PUBLIC_KEY_LEN); +} + +static bool node_is_musig(const ms_node *node) +{ + return node->builtin && builtin_get(node)->kind == KIND_DESCRIPTOR_MUSIG; +} +#else +static bool node_is_musig(const ms_node *node) { (void)node; return false; } +#endif /* ndef BUILD_STANDARD_SECP */ + static int verify_raw_tr(ms_ctx *ctx, ms_node *node) { - if (node->parent || node->child->builtin || !(node->child->kind & KIND_KEY) || - node_has_uncompressed_key(ctx, node)) + if (node->parent) + return WALLY_EINVAL; + if (!node_is_musig(node->child) && + (node->child->builtin || !(node->child->kind & KIND_KEY))) + return WALLY_EINVAL; + if (node_has_uncompressed_key(ctx, node)) return WALLY_EINVAL; node->type_properties = builtin_get(node)->type_properties; return WALLY_OK; @@ -778,15 +829,189 @@ static int verify_raw_tr(ms_ctx *ctx, ms_node *node) static int verify_tr(ms_ctx *ctx, ms_node *node) { const uint32_t child_count = node_get_child_count(node); - if (child_count != 1u) - return WALLY_EINVAL; /* FIXME: Support script paths */ - if (!node_is_top(node) || node->child->builtin || !(node->child->kind & KIND_KEY) || - node_has_uncompressed_key(ctx, node)) + /* only tr(key) and tr(key, tree) is valid */ + if (child_count < 1u || child_count > 2u) + return WALLY_EINVAL; + if (!node_is_top(node)) + return WALLY_EINVAL; + if (!node_is_musig(node->child) && + (node->child->builtin || !(node->child->kind & KIND_KEY))) + return WALLY_EINVAL; + if (node_has_uncompressed_key(ctx, node)) return WALLY_EINVAL; node->type_properties = builtin_get(node)->type_properties; return WALLY_OK; } +#ifndef BUILD_STANDARD_SECP +static int verify_musig(ms_ctx *ctx, ms_node *node) +{ + const uint32_t count = node_get_child_count(node); + ms_node *key; + + /* BIP-390: musig() requires at least 2 participants */ + if (count < 2) + return WALLY_EINVAL; + + /* BIP-390: musig() is only valid in taproot context */ + /* TODO: also allow miniscript pk/pkh parent kinds when tapscript path support is added (currently blocked by tr() FIXME) */ + if (!node->parent || !node->parent->builtin) + return WALLY_EINVAL; + { + const uint32_t parent_kind = builtin_get(node->parent)->kind; + if (parent_kind != KIND_DESCRIPTOR_TR && parent_kind != KIND_DESCRIPTOR_RAW_TR) + return WALLY_EINVAL; + } + + /* Each child must be a raw key expression; no nested musig(), no independent trailing paths */ + key = node->child; + while (key) { + if (key->builtin || !(key->kind & KIND_KEY)) + return WALLY_EINVAL; + /* BIP-390: participant keys must not use hardened derivation (no private key available) */ + if (key->child_path_len) { + uint32_t key_features; + if (bip32_path_str_n_get_features(key->child_path, key->child_path_len, + &key_features) != WALLY_OK) + return WALLY_EINVAL; + if (key_features & BIP32_PATH_IS_HARDENED) + return WALLY_EINVAL; + } + key = key->next; + } + + /* Validate trailing derivation path if present (set in analyze_miniscript) */ + if (node->child_path_len) { + uint32_t features, num_elems, num_multi, wildcard_pos; + if (bip32_path_str_n_get_features(node->child_path, + node->child_path_len, + &features) != WALLY_OK) + return WALLY_EINVAL; + /* BIP-390: no hardened derivation after musig() */ + if (features & BIP32_PATH_IS_HARDENED) + return WALLY_EINVAL; + if (!(features & BIP32_PATH_IS_BARE)) + return WALLY_EINVAL; + num_elems = (features & BIP32_PATH_LEN_MASK) >> BIP32_PATH_LEN_SHIFT; + num_multi = (features & BIP32_PATH_MULTI_MASK) >> BIP32_PATH_MULTI_SHIFT; + if (num_multi) { + if (ctx->num_multipaths != 1 && ctx->num_multipaths != num_multi) + return WALLY_EINVAL; /* Conflicting multi-path lengths */ + ctx->num_multipaths = num_multi; + ctx->features |= WALLY_MS_IS_MULTIPATH; + node->flags |= WALLY_MS_IS_MULTIPATH; + } + if (features & BIP32_PATH_IS_WILDCARD) { + wildcard_pos = (features & BIP32_PATH_WILDCARD_MASK) >> BIP32_PATH_WILDCARD_SHIFT; + if (wildcard_pos != num_elems - 1) + return WALLY_EINVAL; /* Wildcard must be last element */ + ctx->features |= WALLY_MS_IS_RANGED; + node->flags |= WALLY_MS_IS_RANGED; + } + if (num_elems > ctx->max_path_elems) + ctx->max_path_elems = num_elems; + } + + node->type_properties = builtin_get(node)->type_properties; + /* Register the musig() aggregate itself as a key for introspection */ + node->flags |= WALLY_MS_IS_MUSIG; + ctx->features |= WALLY_MS_IS_MUSIG; + return ctx_add_key_node(ctx, node); +} + +static int generate_musig(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + const uint32_t n = node_get_child_count(node); + unsigned char *pubkeys = NULL; + unsigned char agg_xonly[EC_XONLY_PUBLIC_KEY_LEN]; + struct ext_key *synthetic_xpub = NULL; + ms_node *key; + uint32_t i; + int ret = WALLY_ENOMEM; + + *written = 0; + + pubkeys = wally_malloc(n * EC_PUBLIC_KEY_LEN); + if (!pubkeys) + return WALLY_ENOMEM; + + /* Step 1: Resolve each participant key to a 33-byte compressed pubkey. + * BIP-390 requires musig() participants to be standard key expressions + * (xpub, WIF, compressed pubkey), which always produce 33-byte output. */ + key = node->child; + for (i = 0; i < n; i++, key = key->next) { + unsigned char buf[EC_PUBLIC_KEY_LEN]; + size_t key_written = 0; + ret = generate_script(ctx, key, buf, sizeof(buf), &key_written); + if (ret != WALLY_OK) + goto cleanup; + if (key_written != EC_PUBLIC_KEY_LEN) { + ret = WALLY_EINVAL; + goto cleanup; + } + memcpy(pubkeys + i * EC_PUBLIC_KEY_LEN, buf, EC_PUBLIC_KEY_LEN); + } + + /* Step 2: Sort pubkeys lexicographically per BIP-390 */ + qsort(pubkeys, n, EC_PUBLIC_KEY_LEN, musig_pubkey_cmp); + + if (node->child_path_len) { + /* Aggregate-then-derive: musig(k1,k2)/path + * Per BIP-390: aggregate -> BIP-328 synthetic xpub -> derive child. */ + const bool is_mainnet = !ctx->addr_ver || + ctx->addr_ver->network == WALLY_NETWORK_BITCOIN_MAINNET || + ctx->addr_ver->network == WALLY_NETWORK_LIQUID; + const uint32_t bip32_ver = is_mainnet ? BIP32_VER_MAIN_PUBLIC : BIP32_VER_TEST_PUBLIC; + const uint32_t path_flags = BIP32_FLAG_STR_WILDCARD | + BIP32_FLAG_STR_BARE | + BIP32_FLAG_STR_MULTIPATH; + const uint32_t derive_flags = BIP32_FLAG_SKIP_HASH | BIP32_FLAG_KEY_PUBLIC; + struct ext_key derived = {0}; + size_t path_len = 0; + + ret = wally_musig_pubkey_agg(pubkeys, n * EC_PUBLIC_KEY_LEN, + agg_xonly, sizeof(agg_xonly), NULL); + if (ret != WALLY_OK) + goto cleanup; + + ret = wally_musig_pubkey_to_xpub(agg_xonly, sizeof(agg_xonly), + bip32_ver, &synthetic_xpub); + if (ret != WALLY_OK) + goto cleanup; + + ret = bip32_path_from_str_n( + node->child_path, node->child_path_len, + (node->flags & WALLY_MS_IS_RANGED) ? ctx->child_num : 0, + (node->flags & WALLY_MS_IS_MULTIPATH) ? ctx->multi_index : 0, + path_flags, ctx->path_buff, ctx->max_path_elems, &path_len); + if (ret == WALLY_OK) + ret = bip32_key_from_parent_path(synthetic_xpub, ctx->path_buff, + path_len, derive_flags, &derived); + if (ret == WALLY_OK) { + if (script_len >= EC_XONLY_PUBLIC_KEY_LEN) + memcpy(script, derived.pub_key + 1, EC_XONLY_PUBLIC_KEY_LEN); + *written = EC_XONLY_PUBLIC_KEY_LEN; + } + wally_clear(&derived, sizeof(derived)); /* always clear key material */ + } else { + /* No trailing path: aggregate and return x-only key directly */ + ret = wally_musig_pubkey_agg(pubkeys, n * EC_PUBLIC_KEY_LEN, + agg_xonly, sizeof(agg_xonly), NULL); + if (ret == WALLY_OK) { + if (script_len >= EC_XONLY_PUBLIC_KEY_LEN) + memcpy(script, agg_xonly, EC_XONLY_PUBLIC_KEY_LEN); + *written = EC_XONLY_PUBLIC_KEY_LEN; + } + } + +cleanup: + bip32_key_free(synthetic_xpub); /* NULL-safe; defensive free */ + wally_free(pubkeys); + return ret; +} +#endif /* ndef BUILD_STANDARD_SECP */ + static int verify_delay(ms_ctx *ctx, ms_node *node) { (void)ctx; @@ -949,7 +1174,7 @@ static int verify_or_b(ms_ctx *ctx, ms_node *node) ((x_prop & y_prop) & PROP_E)) node->type_properties |= x_prop & y_prop & PROP_M; - return WALLY_OK; + return (node->type_properties & TYPE_B) ? WALLY_OK : WALLY_EINVAL; } static int verify_or_c(ms_ctx *ctx, ms_node *node) @@ -968,7 +1193,7 @@ static int verify_or_c(ms_ctx *ctx, ms_node *node) if (x_prop & PROP_E && ((x_prop | y_prop) & PROP_S)) node->type_properties |= x_prop & y_prop & PROP_M; - return WALLY_OK; + return (node->type_properties & TYPE_V) ? WALLY_OK : WALLY_EINVAL; } static int verify_or_d(ms_ctx *ctx, ms_node *node) @@ -1171,6 +1396,9 @@ static int node_verify_wrappers(ms_node *node) *properties &= ~PROP_F; *properties |= PROP_E; } + /* tapscript: d: gains u property */ + if (node->flags & WALLY_MS_IS_TAPSCRIPT) + *properties |= PROP_U; break; case 'v': PROP_REQUIRE(TYPE_B); @@ -1234,7 +1462,7 @@ static int node_verify_wrappers(ms_node *node) static int generate_number(int64_t number, ms_node *parent, unsigned char *script, size_t script_len, size_t *written) { - if ((parent && !parent->builtin)) + if (parent && !parent->builtin && parent->kind != KIND_TAPTREE_BRANCH) return WALLY_EINVAL; if (number >= -1 && number <= 16) { @@ -1301,7 +1529,8 @@ static int generate_pk_h(ms_ctx *ctx, ms_node *node, if (script_len >= WALLY_SCRIPTPUBKEY_P2PKH_LEN - 1) { ret = generate_pk_k(ctx, node, buff+3, sizeof(buff)-3, written); if (ret == WALLY_OK) { - if (node->child->flags & WALLY_MS_IS_X_ONLY) + if ((node->child->flags & WALLY_MS_IS_X_ONLY) && + !(node->flags & WALLY_MS_IS_TAPSCRIPT)) return WALLY_EINVAL; script[0] = OP_DUP; script[1] = OP_HASH160; @@ -1350,7 +1579,9 @@ static int generate_sh_wsh(ms_ctx *ctx, ms_node *node, static int generate_inplace_checksig(unsigned char *script, size_t script_len, size_t *written) { - if (!*written || (*written + 1 > WITNESS_SCRIPT_MAX_SIZE)) + /* Witness script size limit enforced in generate_inplace_wrappers() for + * segwit v0 only; tapscript has no script size restriction. */ + if (!*written) return WALLY_EINVAL; *written += 1; @@ -1509,6 +1740,76 @@ static int generate_multi(ms_ctx *ctx, ms_node *node, return ret; } +static int generate_multi_a(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* Emit: OP_CHECKSIG OP_CHECKSIGADD ... OP_CHECKSIGADD OP_NUMEQUAL */ + size_t offset = 0; + uint32_t count, i; + ms_node *child = node->child; + struct multisig_sort_data_t *sorted = NULL; + int ret = WALLY_OK; + + if (!child || !node->builtin) + return WALLY_EINVAL; + + count = node_get_child_count(node) - 1; /* subtract threshold child */ + + sorted = wally_malloc(count * sizeof(struct multisig_sort_data_t)); + if (!sorted) + return WALLY_ENOMEM; + + /* skip threshold child */ + child = child->next; + /* Collect all key children */ + for (i = 0; ret == WALLY_OK && i < count; ++i) { + struct multisig_sort_data_t *item = sorted + i; + /* Keys in tapscript are x-only (32 bytes raw) */ + ret = generate_script(ctx, child, item->pubkey, sizeof(item->pubkey), &item->pubkey_len); + /* Must be 32-byte x-only key */ + if (ret == WALLY_OK && item->pubkey_len != EC_XONLY_PUBLIC_KEY_LEN) + ret = WALLY_ERROR; + child = child->next; + } + + if (ret == WALLY_OK) { + /* For sortedmulti_a, sort keys lexicographically */ + if (node->kind == KIND_MINISCRIPT_MULTI_A_S) + qsort(sorted, count, sizeof(sorted[0]), compare_multisig_node); + + /* Emit keys with OP_CHECKSIG (first) and OP_CHECKSIGADD (rest) */ + for (i = 0; ret == WALLY_OK && i < count; ++i) { + const size_t key_len = sorted[i].pubkey_len; + /* push opcode + key bytes + OP_CHECKSIG/OP_CHECKSIGADD */ + if (offset + key_len + 2 <= script_len) { + /* push opcode (0x20 for 32-byte key) */ + script[offset] = key_len & 0xff; + memcpy(script + offset + 1, sorted[i].pubkey, key_len); + script[offset + key_len + 1] = (i == 0) ? OP_CHECKSIG : OP_CHECKSIGADD; + } + offset += key_len + 2; /* push + key + opcode */ + } + + if (ret == WALLY_OK) { + /* Emit threshold OP_NUMEQUAL */ + size_t number_len; + const int64_t threshold = node->child->number; + /* Pass NULL when buffer is exhausted to get required size without writing */ + unsigned char *num_script = offset < script_len ? script + offset : NULL; + size_t remaining_len = offset < script_len ? script_len - offset : 0; + ret = generate_number(threshold, node->parent, num_script, + remaining_len, &number_len); + if (ret == WALLY_OK) { + *written = offset + number_len + 1; + if (*written <= script_len) + script[*written - 1] = OP_NUMEQUAL; + } + } + } + wally_free(sorted); + return ret; +} + static int generate_raw(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written) { @@ -1541,11 +1842,260 @@ static int generate_raw_tr(ms_ctx *ctx, ms_node *node, return ret; } +static bool ms_ctx_is_elements(const ms_ctx *ctx) +{ +#ifdef BUILD_ELEMENTS + return (ctx->features & WALLY_MS_IS_ELEMENTS) != 0; +#else + (void)ctx; + return false; +#endif +} + +static int compute_tapbranch_hash(const unsigned char *left, + const unsigned char *right, + bool is_elements, + unsigned char *hash_out) +{ + unsigned char buf[SHA256_LEN * 2]; + const unsigned char *first = left, *second = right; + + /* BIP-341: child hashes are sorted lexicographically before hashing so the + * merkle path doesn't need to encode left/right direction. + * If k_j < e_j: k_{j+1} = hash_TapBranch(k_j || e_j) + * If k_j >= e_j: k_{j+1} = hash_TapBranch(e_j || k_j) + */ + if (memcmp(left, right, SHA256_LEN) > 0) { + first = right; + second = left; + } + + memcpy(buf, first, SHA256_LEN); + memcpy(buf + SHA256_LEN, second, SHA256_LEN); + return wally_bip340_tagged_hash(buf, sizeof(buf), + is_elements ? "TapBranch/elements" : "TapBranch", + hash_out, SHA256_LEN); +} + +/* Compute the BIP-341 tapleaf hash for a single miniscript leaf node. */ +static int leaf_tapleaf_hash(ms_ctx *ctx, ms_node *leaf, unsigned char *hash_out) +{ + unsigned char *script_buf; + size_t script_buf_len = 0, written = 0; + int ret; + + /* Leaf node: must be a complete miniscript expression (type B/V/K/W) */ + if (!(leaf->type_properties & TYPE_MASK)) + return WALLY_EINVAL; + + ret = node_generation_size(leaf, &script_buf_len); + if (ret != WALLY_OK) + return ret; + if (!(script_buf = wally_malloc(script_buf_len))) + return WALLY_ENOMEM; + + ret = generate_script(ctx, leaf, script_buf, script_buf_len, &written); + if (ret == WALLY_OK) + ret = tapleaf_hash(WALLY_LEAF_VERSION_TAPSCRIPT, script_buf, written, + ms_ctx_is_elements(ctx), hash_out); + wally_free(script_buf); + return ret; +} + +static int collect_merkle_path_impl(ms_ctx *ctx, ms_node *subtree_root, + uint32_t target_index, uint32_t *current_index, + unsigned char *path_out, uint32_t *path_len, + unsigned char *hash_out, bool *found); + +/* Compute the taptree merkle root. This reuses the merkle-path walk with an + * unmatchable target index, so no leaf ever matches and no path is written + * (hence path_out may be NULL). */ +static int compute_taptree_hash(ms_ctx *ctx, ms_node *subtree_root, + unsigned char *hash_out) +{ + uint32_t current_index = 0, path_len = 0; + bool found = false; + return collect_merkle_path_impl(ctx, subtree_root, UINT32_MAX, + ¤t_index, NULL, &path_len, + hash_out, &found); +} + +static uint32_t count_taptree_leaves(const ms_node *node) +{ + if (!node) return 0; + if (node->kind == KIND_TAPTREE_BRANCH) { + if (!node->child || !node->child->next) return 0; + return count_taptree_leaves(node->child) + + count_taptree_leaves(node->child->next); + } + return 1; +} + +/* Recursive helper for find_taptree_leaf. Caller MUST initialise + * *current_index to 0 before the (top-level) call. */ +static ms_node *find_taptree_leaf_impl(ms_node *node, uint32_t target_index, uint32_t *current_index) +{ + if (!node) return NULL; + if (node->kind == KIND_TAPTREE_BRANCH) { + if (!node->child || !node->child->next) return NULL; + ms_node *found = find_taptree_leaf_impl(node->child, target_index, current_index); + if (found) return found; + return find_taptree_leaf_impl(node->child->next, target_index, current_index); + } + if (*current_index == target_index) return node; + (*current_index)++; + return NULL; +} + +/* Return the n-th leaf of the taptree (DFS left-first), or NULL if + * target_index is out of range. */ +static ms_node *find_taptree_leaf(ms_node *taptree_root, uint32_t target_index) +{ + uint32_t current_index = 0; + return find_taptree_leaf_impl(taptree_root, target_index, ¤t_index); +} + +/* Recursive helper for collect_merkle_path. Caller MUST initialise *path_len + * to 0, *current_index to 0, and *found to false before the (top-level) call. + * As recursion unwinds (walking back from the target leaf to the root), each + * branch on the path appends its sibling hash to path_out, in leaf-to-root + * order. */ +static int collect_merkle_path_impl(ms_ctx *ctx, ms_node *subtree_root, + uint32_t target_index, uint32_t *current_index, + unsigned char *path_out, uint32_t *path_len, + unsigned char *hash_out, bool *found) +{ + if (subtree_root->kind == KIND_TAPTREE_BRANCH) { + unsigned char left_hash[SHA256_LEN], right_hash[SHA256_LEN]; + bool left_found = false, right_found = false; + int ret; + + /* a branch has 2 child */ + if (!subtree_root->child || !subtree_root->child->next) + return WALLY_EINVAL; + + ret = collect_merkle_path_impl(ctx, subtree_root->child, target_index, current_index, + path_out, path_len, left_hash, &left_found); + if (ret != WALLY_OK) + return ret; + ret = collect_merkle_path_impl(ctx, subtree_root->child->next, target_index, current_index, + path_out, path_len, right_hash, &right_found); + if (ret != WALLY_OK) + return ret; + + if (left_found) { + memcpy(path_out + (*path_len) * SHA256_LEN, right_hash, SHA256_LEN); + (*path_len)++; + *found = true; + } else if (right_found) { + memcpy(path_out + (*path_len) * SHA256_LEN, left_hash, SHA256_LEN); + (*path_len)++; + *found = true; + } + return compute_tapbranch_hash(left_hash, right_hash, + ms_ctx_is_elements(ctx), hash_out); + } else { + int ret = leaf_tapleaf_hash(ctx, subtree_root, hash_out); + if (ret == WALLY_OK) { + if (*current_index == target_index) + *found = true; + (*current_index)++; + } + return ret; + } +} + +/* Build the merkle proof for the target leaf in the taptree. + * + * The function walks from the taptree root to the target leaf, writing + * nothing to path_out on the way in. As recursion unwinds (i.e. while + * walking back from the leaf to the root), each branch on the path appends + * its sibling hash to path_out. The result is a sequence of 32-byte sibling + * hashes in leaf-to-root order: + * path_out[0] = the spent leaf's immediate sibling + * path_out[1] = the next sibling closer to the root + * ... + * path_out[*path_len_out - 1] = the sibling closest to the root + * + * The root itself is never in the proof; a verifier reconstructs it by + * starting at the spent leaf and combining it with each sibling in order, + * effectively re-walking the same leaf-to-root path. BIP-341 sorts each pair + * lexicographically before hashing, so left/right direction is not encoded. + * + * Outputs: + * path_out - merkle path siblings, packed contiguously (32 bytes each) + * path_len_out - number of 32-byte hashes written to path_out + * hash_out - the merkle root of the entire taptree + * + * Returns WALLY_EINVAL if target_index does not identify a leaf in the tree. + */ +static int collect_merkle_path(ms_ctx *ctx, ms_node *taptree_root, + uint32_t target_index, + unsigned char *path_out, uint32_t *path_len_out, + unsigned char *hash_out) +{ + uint32_t current_index = 0; + bool found = false; + int ret; + + *path_len_out = 0; + ret = collect_merkle_path_impl(ctx, taptree_root, target_index, + ¤t_index, path_out, path_len_out, + hash_out, &found); + if (ret == WALLY_OK && !found) + return WALLY_EINVAL; + return ret; +} + +static uint32_t count_keys_in_subtree(const ms_node *node) +{ + uint32_t count = 0; + const ms_node *child; + if (!node) return 0; + if (node->kind & KIND_KEY) return 1; + if (node->builtin) { + for (child = node->child; child; child = child->next) + count += count_keys_in_subtree(child); + } + return count; +} + +/* Recursive helper for find_nth_key_in_subtree. Caller MUST initialise + * *current_index to 0 before the (top-level) call. */ +static ms_node *find_nth_key_in_subtree_impl(ms_node *node, uint32_t target_index, uint32_t *current_index) +{ + ms_node *child, *found; + if (!node) return NULL; + if (node->kind & KIND_KEY) { + if (*current_index == target_index) return node; + (*current_index)++; + return NULL; + } + if (node->builtin) { + for (child = node->child; child; child = child->next) { + found = find_nth_key_in_subtree_impl(child, target_index, current_index); + if (found) return found; + } + } + return NULL; +} + +/* Return the n-th key (DFS left-first) inside a miniscript subtree, or NULL + * if target_index is out of range. */ +static ms_node *find_nth_key_in_subtree(ms_node *subtree_root, uint32_t target_index) +{ + uint32_t current_index = 0; + return find_nth_key_in_subtree_impl(subtree_root, target_index, ¤t_index); +} + static int generate_tr(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written) { unsigned char tweaked[EC_PUBLIC_KEY_LEN]; unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN + 1]; + unsigned char merkle_root[SHA256_LEN]; + const unsigned char *root_ptr = NULL; + size_t root_len = 0; size_t pubkey_len = 0; uint32_t tweak_flags = 0; int ret; @@ -1556,13 +2106,22 @@ static int generate_tr(ms_ctx *ctx, ms_node *node, if (ret != WALLY_OK || pubkey_len != EC_XONLY_PUBLIC_KEY_LEN + 1) return WALLY_EINVAL; /* Should be PUSH_32 [x-only pubkey] */ + /* node->child->next == taptree */ + if (node->child->next) { + ret = compute_taptree_hash(ctx, node->child->next, merkle_root); + if (ret != WALLY_OK) + return ret; + root_ptr = merkle_root; + root_len = SHA256_LEN; + } + /* Tweak it into a compressed pubkey */ #ifdef BUILD_ELEMENTS if (ctx->features & WALLY_MS_IS_ELEMENTS) tweak_flags = EC_FLAG_ELEMENTS; #endif ret = wally_ec_public_key_bip341_tweak(pubkey + 1, pubkey_len - 1, - NULL, 0, /* FIXME: Support script path */ + root_ptr, root_len, tweak_flags, tweaked, sizeof(tweaked)); if (ret == WALLY_OK && script_len >= WALLY_SCRIPTPUBKEY_P2TR_LEN) { @@ -1928,7 +2487,8 @@ static int generate_inplace_wrappers(ms_node *node, default: return WALLY_ERROR; /* Wrapper type not found, should not happen */ } - if (*written + output_len > WITNESS_SCRIPT_MAX_SIZE) + if (!(node->flags & WALLY_MS_IS_TAPSCRIPT) && + *written + output_len > WITNESS_SCRIPT_MAX_SIZE) return WALLY_EINVAL; *written += output_len; } @@ -1936,7 +2496,7 @@ static int generate_inplace_wrappers(ms_node *node, } #define I_NAME(name) name, sizeof(name) - 1 -static const struct ms_builtin_t g_builtins[] = { +const struct ms_builtin_t g_builtins[] = { /* output descriptor */ { I_NAME("sh"), @@ -1999,6 +2559,14 @@ static const struct ms_builtin_t g_builtins[] = { TYPE_NONE, 0xffffffff, verify_tr, generate_tr }, +#ifndef BUILD_STANDARD_SECP + { + I_NAME("musig"), + KIND_DESCRIPTOR_MUSIG, + TYPE_NONE, + 0xffffffff, verify_musig, generate_musig + }, +#endif /* ndef BUILD_STANDARD_SECP */ /* miniscript */ { I_NAME("pk_k"), @@ -2083,6 +2651,16 @@ static const struct ms_builtin_t g_builtins[] = { I_NAME("thresh"), KIND_MINISCRIPT_THRESH, TYPE_B | PROP_D | PROP_U, 0xffffffff, verify_thresh, generate_thresh + }, { + I_NAME("multi_a"), + KIND_MINISCRIPT_MULTI_A, + TYPE_B | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S | PROP_K, + 0xffffffff, verify_multi_a, generate_multi_a + }, { + I_NAME("sortedmulti_a"), + KIND_MINISCRIPT_MULTI_A_S, + TYPE_B | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S | PROP_K, + 0xffffffff, verify_multi_a, generate_multi_a } /* Elements confidential descriptors */ #ifdef BUILD_ELEMENTS @@ -2171,6 +2749,9 @@ static int generate_script(ms_ctx *ctx, ms_node *node, } } } + } else if (node->kind == KIND_TAPTREE_BRANCH) { + /* Taptree branch nodes cannot be directly generated as a script */ + return WALLY_EINVAL; } else if ((node->kind & KIND_BIP32) == KIND_BIP32) { output_len = node->flags & WALLY_MS_IS_X_ONLY ? EC_XONLY_PUBLIC_KEY_LEN : EC_PUBLIC_KEY_LEN; if (output_len > script_len) { @@ -2281,7 +2862,7 @@ static int analyze_address(ms_ctx *ctx, const char *str, size_t str_len, /* take the possible hex data in node->data, if it is a valid key then * convert it to an allocated binary buffer and make this node a key node */ -static int analyze_key_hex(ms_ctx *ctx, ms_node *node, +static int analyze_key_hex(ms_ctx *ctx, ms_node *node, ms_node *parent, uint32_t flags, bool is_ct_key, bool *is_hex) { unsigned char key[EC_PUBLIC_KEY_UNCOMPRESSED_LEN], *key_p = key; @@ -2318,8 +2899,10 @@ static int analyze_key_hex(ms_ctx *ctx, ms_node *node, if (key_len == EC_XONLY_PUBLIC_KEY_LEN && !allow_xonly) return WALLY_OK; /* X-only not allowed here */ if (key_len != EC_XONLY_PUBLIC_KEY_LEN) { - if (flags & WALLY_MINISCRIPT_TAPSCRIPT) - return WALLY_OK; /* Only X-only pubkeys allowed under tapscript */ + if (flags & WALLY_MINISCRIPT_TAPSCRIPT) { + /* In tapscript, compressed keys are accepted and stripped to x-only */ + make_xonly = true; + } if (make_xonly) { /* Convert to x-only */ --key_len; @@ -2352,6 +2935,8 @@ static int analyze_key_hex(ms_ctx *ctx, ms_node *node, } node->flags |= WALLY_MS_IS_RAW; ctx->features |= WALLY_MS_IS_RAW; + if (parent && node_is_musig(parent)) + return WALLY_OK; return ctx_add_key_node(ctx, node); } @@ -2410,7 +2995,7 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, } /* Check for a hex public key (hex private keys allowed for ct() only) */ - ret = analyze_key_hex(ctx, node, flags, is_ct_key, &is_hex); + ret = analyze_key_hex(ctx, node, parent, flags, is_ct_key, &is_hex); if (ret == WALLY_OK && is_hex) return WALLY_OK; @@ -2439,6 +3024,11 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, node->kind = KIND_PRIVATE_KEY; ctx->features |= (WALLY_MS_IS_PRIVATE | WALLY_MS_IS_RAW); node->flags |= (WALLY_MS_IS_PRIVATE | WALLY_MS_IS_RAW); +#ifndef BUILD_STANDARD_SECP + if (parent && node_is_musig(parent)) + ret = WALLY_OK; + else +#endif ret = ctx_add_key_node(ctx, node); } wally_clear(privkey, sizeof(privkey)); @@ -2519,6 +3109,11 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, node->flags |= WALLY_MS_IS_X_ONLY; ctx->features |= WALLY_MS_IS_X_ONLY; } +#ifndef BUILD_STANDARD_SECP + if (parent && node_is_musig(parent)) + ret = WALLY_OK; + else +#endif ret = ctx_add_key_node(ctx, node); } } @@ -2576,12 +3171,141 @@ static int analyze_miniscript_value(ms_ctx *ctx, const char *str, size_t str_len return analyze_miniscript_key(ctx, flags, node, parent, force_ct); } +/* Forward declaration */ +static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, + uint32_t kind, uint32_t flags, ms_node *prev_node, + ms_node *parent, ms_node **output); + +/* + * Recursive helper for parse_taptree. Tracks the current branch depth + * to enforce the BIP-341 maximum of WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH. + */ +static int parse_taptree_impl(ms_ctx *ctx, const char *str, size_t str_len, + uint32_t kind, uint32_t flags, uint32_t depth, + ms_node *parent, ms_node *prev_sibling, ms_node **output) +{ + int ret; + + if (!str_len) + return WALLY_EINVAL; + + if (depth > WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH) + return WALLY_EINVAL; /* BIP-341 allows a merkle path of up to 128 (leaf at depth 128) */ + + if (str[0] == '{') { + /* Branch node: {LEFT, RIGHT} */ + size_t j, brace_depth = 1, paren_depth = 0, comma_pos = 0; + ms_node *node, *left = NULL, *right = NULL; + + /* Minimum 3 chars: `{`, ≥1 byte of content, `}`. The actual minimum + * valid branch is much larger (each leaf must be a typed miniscript + * expression); this is just a buffer-size sanity check before we + * start scanning. */ + if (str_len < 3 || str[str_len - 1] != '}') + return WALLY_EINVAL; + + /* Find the comma separating left and right subtrees at brace_depth=1, paren_depth=0 */ + for (j = 1; j < str_len - 1; ++j) { + if (str[j] == '{') ++brace_depth; + else if (str[j] == '}') { + if (!brace_depth) + return WALLY_EINVAL; + --brace_depth; + } else if (str[j] == '(') ++paren_depth; + else if (str[j] == ')') { + if (!paren_depth) + return WALLY_EINVAL; /* Unmatched ')' */ + --paren_depth; + } else if (str[j] == ',' && brace_depth == 1 && paren_depth == 0) { + if (comma_pos != 0) + return WALLY_EINVAL; /* Multiple commas at separator level */ + comma_pos = j; + } + } + /* comma_pos == 0: no separator found + * comma_pos == 1: empty left subtree ({,b}) + * comma_pos == str_len - 2: empty right subtree ({a,}) */ + if (comma_pos == 0 || comma_pos == 1 || comma_pos == str_len - 2) + return WALLY_EINVAL; + + /* Allocate branch node */ + if (!(node = wally_calloc(sizeof(*node)))) + return WALLY_ENOMEM; + node->kind = KIND_TAPTREE_BRANCH; + node->parent = parent; + + /* Parse left subtree: str[1..comma_pos-1] */ + ret = parse_taptree_impl(ctx, str + 1, comma_pos - 1, + kind, flags, depth + 1, node, NULL, &left); + if (ret != WALLY_OK) { + node_free(node); /* node_free() will also free left */ + return ret; + } + + /* Parse right subtree: str[comma_pos+1..str_len-2] */ + ret = parse_taptree_impl(ctx, str + comma_pos + 1, str_len - comma_pos - 2, + kind, flags, depth + 1, node, left, &right); + if (ret != WALLY_OK) { + node_free(node); /* node_free() will free all children*/ + return ret; + } + (void)right; /* linked via left->next by the recursive call */ + + /* Link branch node to its parent and previous sibling */ + *output = node; + /* First child (left arm of {L,R}): link as parent's first child */ + if (parent && !parent->child) + parent->child = node; + /* Subsequent child (right arm of {L,R}, or the taptree of tr(KEY,T)): + * link via the previous sibling */ + else if (prev_sibling) + prev_sibling->next = node; + } else { + /* Leaf node: bare miniscript expression in tapscript context */ + ret = analyze_miniscript(ctx, str, str_len, KIND_MINISCRIPT, + flags | WALLY_MINISCRIPT_TAPSCRIPT, + prev_sibling, parent, output); + if (ret == WALLY_OK && *output) { + /* A taptree leaf must be a complete miniscript expression (type B/V/K/W). + * Raw key/value nodes (bare keys, numbers) are not valid leaves. */ + if (!((*output)->type_properties & TYPE_MASK)) { + if (prev_sibling) + prev_sibling->next = NULL; /* unlink from sibling chain */ + else if (parent) + parent->child = NULL; /* reset dangling pointer */ + node_free(*output); + *output = NULL; + ret = WALLY_EINVAL; + } + } + } + + return ret; +} + +/* + * Parse a taptree expression: either a bare miniscript leaf or a {LEFT,RIGHT} + * branch. + * str/str_len: the taptree text (not including the surrounding parentheses + * of tr()) + * parent: the parent node (the tr() node) + * prev_sibling: previous sibling node (the tr() internal key, for linked list) + * output: destination for the created node + */ +static int parse_taptree(ms_ctx *ctx, const char *str, size_t str_len, + uint32_t kind, uint32_t flags, + ms_node *parent, ms_node *prev_sibling, ms_node **output) +{ + return parse_taptree_impl(ctx, str, str_len, kind, flags, 0, + parent, prev_sibling, output); +} + static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, uint32_t kind, uint32_t flags, ms_node *prev_node, ms_node *parent, ms_node **output) { size_t i, offset = 0, child_offset = 0; - uint32_t indent = 0; + uint32_t indent = 0, brace_depth = 0; bool seen_indent = false, collect_child = false, copy_child = false; ms_node *node, *child = NULL, *prev_child = NULL; int ret = WALLY_OK; @@ -2635,12 +3359,22 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, } } seen_indent = true; + } else if (str[i] == '{') { + ++brace_depth; + seen_indent = true; + } else if (str[i] == '}') { + if (!brace_depth) { + ret = WALLY_EINVAL; /* Unmatched '}' */ + break; + } + --brace_depth; + seen_indent = true; } else if (str[i] == ',') { if (!indent) { ret = WALLY_EINVAL; /* Comma outside of ()'s */ break; } - if (collect_child && (indent == 1)) { + if (collect_child && (indent == 1) && brace_depth == 0) { copy_child = true; } seen_indent = true; @@ -2661,11 +3395,20 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, } if (copy_child) { - if (i - child_offset && - (ret = analyze_miniscript(ctx, str + child_offset, i - child_offset, - kind, flags, prev_child, - node, &child)) != WALLY_OK) - break; + if (i - child_offset) { + if (node->kind == KIND_DESCRIPTOR_TR && prev_child != NULL) { + /* Second argument of tr() is the taptree: parse_taptree + * handles both {LEFT,RIGHT} branches and a single bare + * miniscript leaf. */ + ret = parse_taptree(ctx, str + child_offset, i - child_offset, + kind, flags, node, prev_child, &child); + } else { + ret = analyze_miniscript(ctx, str + child_offset, i - child_offset, + kind, flags, prev_child, node, &child); + } + if (ret != WALLY_OK) + break; + } prev_child = child; child = NULL; @@ -2684,6 +3427,26 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, flags, node, parent); } + /* Propagate tapscript context flag BEFORE verification so verify functions can check it */ + if (flags & WALLY_MINISCRIPT_TAPSCRIPT) { + node->flags |= WALLY_MS_IS_TAPSCRIPT; + ctx->features |= WALLY_MS_IS_TAPSCRIPT; + } + +#ifndef BUILD_STANDARD_SECP + /* Capture trailing derivation path for musig() nodes: musig(k1,k2)/path */ + if (ret == WALLY_OK && node->builtin && + builtin_get(node)->kind == KIND_DESCRIPTOR_MUSIG && + offset < str_len && str[offset] != '#') { + if (str[offset] == '/') { + node->child_path = str + offset + 1; /* skip leading '/' */ + node->child_path_len = str_len - offset - 1; + } else { + ret = WALLY_EINVAL; /* Unexpected trailing content after musig() */ + } + } +#endif /* ndef BUILD_STANDARD_SECP */ + if (ret == WALLY_OK && node->builtin) { const uint32_t expected_children = builtin_get(node)->child_count; if (expected_children != 0xffffffff && node_get_child_count(node) != expected_children) @@ -2775,6 +3538,18 @@ static int node_generation_size(const ms_node *node, size_t *total) case KIND_DESCRIPTOR_TR: *total += WALLY_SCRIPTPUBKEY_P2TR_LEN; break; + case KIND_MINISCRIPT_MULTI_A: + case KIND_MINISCRIPT_MULTI_A_S: + /* Each key: 1 (push) + 32 (x-only key) + 1 (OP_CHECKSIG/OP_CHECKSIGADD) = 34. + * Plus threshold (up to 3 bytes) + OP_NUMEQUAL (1 byte) = 4. */ + *total += (node_get_child_count(node) - 1) * 34 + 4; + break; +#ifndef BUILD_STANDARD_SECP + case KIND_DESCRIPTOR_MUSIG: + /* Placeholder: aggregated key is one x-only pubkey (32 bytes) */ + *total += EC_XONLY_PUBLIC_KEY_LEN; + break; +#endif /* ndef BUILD_STANDARD_SECP */ case KIND_MINISCRIPT_PK_K: *total += 1; break; @@ -2836,6 +3611,8 @@ static int node_generation_size(const ms_node *node, size_t *total) *total += EC_XONLY_PUBLIC_KEY_LEN; else *total += EC_PUBLIC_KEY_LEN; + } else if (node->kind == KIND_TAPTREE_BRANCH) { + /* Taptree branch nodes don't contribute to scriptPubkey size */ } else return WALLY_ERROR; /* Should not happen */ @@ -3392,6 +4169,41 @@ static const ms_node *descriptor_get_key(const struct wally_descriptor *descript return (ms_node *)descriptor->keys.items[index].value; } +static int format_key_node(const struct wally_descriptor *descriptor, + const ms_node *node, char **output) +{ + if (node->kind == KIND_PUBLIC_KEY) + return wally_hex_from_bytes((const unsigned char *)node->data, + node->data_len, output); + if (node->kind == KIND_PRIVATE_KEY) { + uint32_t flags = node->flags & WALLY_MS_IS_UNCOMPRESSED ? WALLY_WIF_FLAG_UNCOMPRESSED : 0; + if (!descriptor->addr_ver) + return WALLY_EINVAL; + return wally_wif_from_bytes((const unsigned char *)node->data, node->data_len, + descriptor->addr_ver->version_wif, + flags, output); + } + if ((node->kind & KIND_BIP32) == KIND_BIP32) { + if (node->child_path_len) { + /* Include the derivation path: / */ + size_t total = node->data_len + 1 + node->child_path_len; + char *buf = (char *)wally_malloc(total + 1); + if (!buf) + return WALLY_ENOMEM; + memcpy(buf, node->data, node->data_len); + buf[node->data_len] = '/'; + memcpy(buf + node->data_len + 1, node->child_path, node->child_path_len); + buf[total] = '\0'; + *output = buf; + } else { + if (!(*output = wally_strdup_n(node->data, node->data_len))) + return WALLY_ENOMEM; + } + return WALLY_OK; + } + return WALLY_ERROR; /* Unknown key type */ +} + int wally_descriptor_get_key(const struct wally_descriptor *descriptor, size_t index, char **output) { @@ -3417,6 +4229,10 @@ int wally_descriptor_get_key(const struct wally_descriptor *descriptor, if (node->kind == KIND_PRIVATE_KEY || node->kind == KIND_RAW) goto return_hex; } +#endif +#ifndef BUILD_STANDARD_SECP + if (node->kind == KIND_DESCRIPTOR_MUSIG) + return WALLY_EINVAL; /* musig() aggregate: use wally_descriptor_get_musig_* */ #endif if (node->kind == KIND_PUBLIC_KEY) { #ifdef BUILD_ELEMENTS @@ -3463,6 +4279,151 @@ int wally_descriptor_get_key_features(const struct wally_descriptor *descriptor, return WALLY_OK; } +int wally_descriptor_get_musig_num_participants( + const struct wally_descriptor *descriptor, + size_t index, size_t *written) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (written) + *written = 0; + if (!node || !written) + return WALLY_EINVAL; +#ifndef BUILD_STANDARD_SECP + if (node->kind == KIND_DESCRIPTOR_MUSIG) { + const ms_node *k = node->child; + size_t count = 0; + + while (k) { ++count; k = k->next; } + *written = count; + return WALLY_OK; + } +#endif + return WALLY_EINVAL; /* Not a musig() key */ +} + +int wally_descriptor_get_musig_participant_key( + const struct wally_descriptor *descriptor, + size_t index, size_t participant_index, + char **output) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (output) + *output = NULL; + if (!node || !output) + return WALLY_EINVAL; +#ifdef BUILD_STANDARD_SECP + (void)participant_index; +#endif +#ifndef BUILD_STANDARD_SECP + if (node->kind == KIND_DESCRIPTOR_MUSIG) { + const ms_node *k = node->child; + size_t i = 0; + + while (k && i < participant_index) { k = k->next; ++i; } + if (!k) + return WALLY_EINVAL; /* participant_index out of range */ + return format_key_node(descriptor, k, output); + } +#endif + return WALLY_EINVAL; /* Not a musig() key */ +} + +int wally_descriptor_get_musig_participant_key_features( + const struct wally_descriptor *descriptor, + size_t index, size_t participant_index, + uint32_t *value_out) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (value_out) + *value_out = 0; + if (!node || !value_out) + return WALLY_EINVAL; +#ifdef BUILD_STANDARD_SECP + (void)participant_index; +#endif +#ifndef BUILD_STANDARD_SECP + if (node->kind == KIND_DESCRIPTOR_MUSIG) { + const ms_node *k = node->child; + size_t i = 0; + + while (k && i < participant_index) { k = k->next; ++i; } + if (!k) + return WALLY_EINVAL; + *value_out = k->flags; + return WALLY_OK; + } +#endif + return WALLY_EINVAL; +} + +int wally_descriptor_get_musig_participant_key_origin_fingerprint( + const struct wally_descriptor *descriptor, + size_t index, size_t participant_index, + unsigned char *bytes_out, size_t len) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (!node || !bytes_out || len != BIP32_KEY_FINGERPRINT_LEN) + return WALLY_EINVAL; +#ifdef BUILD_STANDARD_SECP + (void)participant_index; +#endif +#ifndef BUILD_STANDARD_SECP + if (node->kind == KIND_DESCRIPTOR_MUSIG) { + const ms_node *k = node->child; + const char *fingerprint; + size_t written, i = 0; + int ret; + + while (k && i < participant_index) { k = k->next; ++i; } + if (!k || !(k->flags & WALLY_MS_IS_PARENTED)) + return WALLY_EINVAL; + fingerprint = descriptor->src + (((uint64_t)k->number) >> 32u) + 1; + ret = wally_hex_n_to_bytes(fingerprint, BIP32_KEY_FINGERPRINT_LEN * 2, + bytes_out, len, &written); + return ret == WALLY_OK && written != BIP32_KEY_FINGERPRINT_LEN ? WALLY_EINVAL : ret; + } +#endif + return WALLY_EINVAL; +} + +int wally_descriptor_get_musig_participant_key_origin_path_str( + const struct wally_descriptor *descriptor, + size_t index, size_t participant_index, + char **output) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (output) + *output = NULL; + if (!node || !output) + return WALLY_EINVAL; +#ifdef BUILD_STANDARD_SECP + (void)participant_index; +#endif +#ifndef BUILD_STANDARD_SECP + if (node->kind == KIND_DESCRIPTOR_MUSIG) { + const ms_node *k = node->child; + const char *path; + size_t path_len, i = 0; + + while (k && i < participant_index) { k = k->next; ++i; } + if (!k) + return WALLY_EINVAL; + path_len = k->flags & WALLY_MS_IS_PARENTED ? k->number & 0xffffffff : 0; + path_len = path_len < 11u ? 0 : path_len - 11u; + path = descriptor->src + (((uint64_t)k->number) >> 32u) + 10u; + if (!(*output = wally_strdup_n(path, path_len))) + return WALLY_ENOMEM; + return WALLY_OK; + } +#endif + return WALLY_EINVAL; +} + int wally_descriptor_get_key_child_path_str_len( const struct wally_descriptor *descriptor, size_t index, size_t *written) { @@ -3601,3 +4562,410 @@ static int ensure_unique_policy_keys(const ms_ctx *ctx) } return WALLY_OK; } + +int wally_descriptor_get_taproot_num_leaves( + const struct wally_descriptor *descriptor, + uint32_t *value_out) +{ + if (value_out) + *value_out = 0; + if (!descriptor || !value_out) + return WALLY_EINVAL; + if (descriptor->top_node->kind != KIND_DESCRIPTOR_TR) + return WALLY_EINVAL; + if (!descriptor->top_node->child) + return WALLY_ERROR; /* tr() with no internal key — corrupt AST */ + if (!descriptor->top_node->child->next) + return WALLY_OK; /* key-only tr(KEY), 0 leaves */ + *value_out = count_taptree_leaves(descriptor->top_node->child->next); + return WALLY_OK; +} + +int wally_descriptor_get_taproot_leaf_script( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t variant, uint32_t multi_index, + uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len, size_t *written) +{ + ms_ctx ctx; + ms_node *taptree, *leaf; + int ret; + + if (written) + *written = 0; + if (!descriptor || !written || (bytes_out && !len) || flags) + return WALLY_EINVAL; + if (descriptor->top_node->kind != KIND_DESCRIPTOR_TR || + variant >= descriptor->num_variants || + child_num >= BIP32_INITIAL_HARDENED_CHILD || + multi_index >= descriptor->num_multipaths) + return WALLY_EINVAL; + if (!descriptor->top_node->child) + return WALLY_ERROR; /* tr() with no internal key — corrupt AST */ + taptree = descriptor->top_node->child->next; + if (!taptree) + return WALLY_EINVAL; /* key-only tr() */ + if (leaf_index >= count_taptree_leaves(taptree)) + return WALLY_EINVAL; + + leaf = find_taptree_leaf(taptree, leaf_index); + if (!leaf) + return WALLY_EINVAL; + + memcpy(&ctx, descriptor, sizeof(ctx)); + ctx.variant = variant; + ctx.child_num = child_num; + ctx.multi_index = multi_index; + ctx.path_buff = NULL; + if (ctx.max_path_elems && + !(ctx.path_buff = wally_malloc(ctx.max_path_elems * sizeof(uint32_t)))) + return WALLY_ENOMEM; + + /* leaf->parent->kind == KIND_TAPTREE_BRANCH => node_is_root() is true */ + ret = generate_script(&ctx, leaf, bytes_out, len, written); + wally_free(ctx.path_buff); + return ret; +} + +int wally_descriptor_get_taproot_leaf_hash( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t variant, uint32_t multi_index, + uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len) +{ + ms_ctx ctx; + ms_node *taptree, *leaf; + int ret; + + if (!descriptor || !bytes_out || len < SHA256_LEN || flags) + return WALLY_EINVAL; + if (descriptor->top_node->kind != KIND_DESCRIPTOR_TR || + variant >= descriptor->num_variants || + child_num >= BIP32_INITIAL_HARDENED_CHILD || + multi_index >= descriptor->num_multipaths) + return WALLY_EINVAL; + if (!descriptor->top_node->child) + return WALLY_ERROR; /* tr() with no internal key — corrupt AST */ + taptree = descriptor->top_node->child->next; + if (!taptree) + return WALLY_EINVAL; + if (leaf_index >= count_taptree_leaves(taptree)) + return WALLY_EINVAL; + + leaf = find_taptree_leaf(taptree, leaf_index); + if (!leaf) + return WALLY_EINVAL; + + memcpy(&ctx, descriptor, sizeof(ctx)); + ctx.variant = variant; + ctx.child_num = child_num; + ctx.multi_index = multi_index; + ctx.path_buff = NULL; + if (ctx.max_path_elems && + !(ctx.path_buff = wally_malloc(ctx.max_path_elems * sizeof(uint32_t)))) + return WALLY_ENOMEM; + + ret = leaf_tapleaf_hash(&ctx, leaf, bytes_out); + + wally_free(ctx.path_buff); + return ret; +} + +int wally_descriptor_get_taproot_control_block( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t variant, uint32_t multi_index, + uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len, size_t *written) +{ + ms_ctx ctx; + unsigned char pubkey[EC_XONLY_PUBLIC_KEY_LEN + 1]; /* PUSH_32 + x-only key */ + unsigned char tweaked[EC_PUBLIC_KEY_LEN]; + unsigned char *path_buf = NULL; + unsigned char merkle_root[SHA256_LEN]; + ms_node *taptree; + uint32_t path_len = 0, tweak_flags; + size_t pubkey_len = 0, cb_size; + int ret; + + if (written) + *written = 0; + if (!descriptor || !written || flags) + return WALLY_EINVAL; + if (descriptor->top_node->kind != KIND_DESCRIPTOR_TR || + variant >= descriptor->num_variants || + child_num >= BIP32_INITIAL_HARDENED_CHILD || + multi_index >= descriptor->num_multipaths) + return WALLY_EINVAL; + if (!descriptor->top_node->child) + return WALLY_ERROR; /* tr() with no internal key — corrupt AST */ + taptree = descriptor->top_node->child->next; + if (!taptree) + return WALLY_EINVAL; /* key-only tr() has no control block */ + if (leaf_index >= count_taptree_leaves(taptree)) + return WALLY_EINVAL; + + memcpy(&ctx, descriptor, sizeof(ctx)); + ctx.variant = variant; + ctx.child_num = child_num; + ctx.multi_index = multi_index; + ctx.path_buff = NULL; + if (ctx.max_path_elems && + !(ctx.path_buff = wally_malloc(ctx.max_path_elems * sizeof(uint32_t)))) + return WALLY_ENOMEM; + + path_buf = wally_malloc(128 * SHA256_LEN); + if (!path_buf) { + ret = WALLY_ENOMEM; + goto cleanup; + } + + /* Extract x-only internal key: generates PUSH_32 [x-only key] */ + /* descriptor->top_node->parent == NULL so node_is_root() passes */ + ret = generate_pk_k_impl(&ctx, descriptor->top_node, pubkey, sizeof(pubkey), + true /* force_xonly */, &pubkey_len); + if (ret != WALLY_OK || pubkey_len != EC_XONLY_PUBLIC_KEY_LEN + 1) { + ret = WALLY_EINVAL; + goto cleanup; + } + + /* Collect merkle path for target leaf */ + ret = collect_merkle_path(&ctx, taptree, leaf_index, + path_buf, &path_len, merkle_root); + if (ret != WALLY_OK) + goto cleanup; + + /* Tweak to get parity bit. Use the same tweak tag as generate_tr() so the + * control block parity matches the scriptPubKey output key; for Elements + * descriptors this is the "TapTweak/elements" tag, not "TapTweak". */ + tweak_flags = 0; +#ifdef BUILD_ELEMENTS + if (descriptor->features & WALLY_MS_IS_ELEMENTS) + tweak_flags = EC_FLAG_ELEMENTS; +#endif + ret = wally_ec_public_key_bip341_tweak(pubkey + 1, EC_XONLY_PUBLIC_KEY_LEN, + merkle_root, SHA256_LEN, + tweak_flags, tweaked, sizeof(tweaked)); + if (ret != WALLY_OK) + goto cleanup; + + cb_size = 1u + EC_XONLY_PUBLIC_KEY_LEN + (size_t)path_len * SHA256_LEN; + *written = cb_size; + if (bytes_out && len >= cb_size) { + bytes_out[0] = (unsigned char)(0xc0 | (tweaked[0] == 0x03 ? 1 : 0)); + memcpy(bytes_out + 1, pubkey + 1, EC_XONLY_PUBLIC_KEY_LEN); + if (path_len) + memcpy(bytes_out + 1 + EC_XONLY_PUBLIC_KEY_LEN, path_buf, + (size_t)path_len * SHA256_LEN); + } + +cleanup: + wally_free(path_buf); + wally_free(ctx.path_buff); + return ret; +} + +int wally_descriptor_get_taproot_leaf_num_keys( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t *value_out) +{ + ms_node *taptree, *leaf; + + if (value_out) + *value_out = 0; + if (!descriptor || !value_out) + return WALLY_EINVAL; + if (descriptor->top_node->kind != KIND_DESCRIPTOR_TR) + return WALLY_EINVAL; + if (!descriptor->top_node->child) + return WALLY_ERROR; /* tr() with no internal key — corrupt AST */ + taptree = descriptor->top_node->child->next; + if (!taptree) + return WALLY_EINVAL; + if (leaf_index >= count_taptree_leaves(taptree)) + return WALLY_EINVAL; + + leaf = find_taptree_leaf(taptree, leaf_index); + if (!leaf) + return WALLY_EINVAL; + + *value_out = count_keys_in_subtree(leaf); + return WALLY_OK; +} + +int wally_descriptor_get_taproot_leaf_key_index( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t key_position, + uint32_t *value_out) +{ + ms_node *taptree, *leaf, *key_node; + size_t i; + + if (value_out) + *value_out = 0; + if (!descriptor || !value_out) + return WALLY_EINVAL; + if (descriptor->top_node->kind != KIND_DESCRIPTOR_TR) + return WALLY_EINVAL; + if (!descriptor->top_node->child) + return WALLY_ERROR; /* tr() with no internal key — corrupt AST */ + taptree = descriptor->top_node->child->next; + if (!taptree) + return WALLY_EINVAL; + if (leaf_index >= count_taptree_leaves(taptree)) + return WALLY_EINVAL; + + leaf = find_taptree_leaf(taptree, leaf_index); + if (!leaf) + return WALLY_EINVAL; + + if (key_position >= count_keys_in_subtree(leaf)) + return WALLY_EINVAL; + + key_node = find_nth_key_in_subtree(leaf, key_position); + if (!key_node) + return WALLY_EINVAL; + + /* Map key node pointer to descriptor-level key index */ + for (i = 0; i < descriptor->keys.num_items; i++) { + if ((ms_node *)descriptor->keys.items[i].value == key_node) { + *value_out = (uint32_t)i; + return WALLY_OK; + } + } + return WALLY_EINVAL; /* key not found in map (should not happen) */ +} + +int wally_descriptor_get_taproot_internal_key( + const struct wally_descriptor *descriptor, + uint32_t variant, uint32_t multi_index, uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len) +{ + ms_ctx ctx; + unsigned char pubkey[EC_XONLY_PUBLIC_KEY_LEN + 1]; /* PUSH_32 + x-only key */ + size_t pubkey_len = 0; + int ret; + + if (!descriptor || !bytes_out || len < EC_XONLY_PUBLIC_KEY_LEN || flags) + return WALLY_EINVAL; + if (descriptor->top_node->kind != KIND_DESCRIPTOR_TR || + variant >= descriptor->num_variants || + child_num >= BIP32_INITIAL_HARDENED_CHILD || + multi_index >= descriptor->num_multipaths) + return WALLY_EINVAL; + + if (!descriptor->top_node->child) + return WALLY_ERROR; /* tr() with no internal key — corrupt AST */ + + memcpy(&ctx, descriptor, sizeof(ctx)); + ctx.variant = variant; + ctx.child_num = child_num; + ctx.multi_index = multi_index; + ctx.path_buff = NULL; + if (ctx.max_path_elems && + !(ctx.path_buff = wally_malloc(ctx.max_path_elems * sizeof(uint32_t)))) + return WALLY_ENOMEM; + + ret = generate_script(&ctx, descriptor->top_node->child, pubkey, sizeof(pubkey), + &pubkey_len); + wally_free(ctx.path_buff); + + if (ret == WALLY_OK) { + if (pubkey_len == EC_XONLY_PUBLIC_KEY_LEN) { + memcpy(bytes_out, pubkey, EC_XONLY_PUBLIC_KEY_LEN); + } else if (pubkey_len == EC_PUBLIC_KEY_LEN) { + /* Compressed key: strip the parity byte */ + memcpy(bytes_out, pubkey + 1, EC_XONLY_PUBLIC_KEY_LEN); + } else { + ret = WALLY_EINVAL; + } + } + return ret; +} + +int wally_descriptor_get_key_xonly_public_key( + const struct wally_descriptor *descriptor, + size_t key_index, + uint32_t variant, uint32_t multi_index, uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len) +{ + const ms_node *key_node; + ms_ctx ctx; + unsigned char pubkey[EC_PUBLIC_KEY_LEN]; + size_t written = 0; + int ret; + + if (!descriptor || !bytes_out || len < EC_XONLY_PUBLIC_KEY_LEN || flags) + return WALLY_EINVAL; + if (variant >= descriptor->num_variants || + child_num >= BIP32_INITIAL_HARDENED_CHILD || + multi_index >= descriptor->num_multipaths) + return WALLY_EINVAL; + if (!(key_node = descriptor_get_key(descriptor, key_index))) + return WALLY_EINVAL; + + memcpy(&ctx, descriptor, sizeof(ctx)); + ctx.variant = variant; + ctx.child_num = child_num; + ctx.multi_index = multi_index; + ctx.path_buff = NULL; + if (ctx.max_path_elems && + !(ctx.path_buff = wally_malloc(ctx.max_path_elems * sizeof(uint32_t)))) + return WALLY_ENOMEM; + + /* Generate the pubkey for this key node */ + ret = generate_script(&ctx, (ms_node *)key_node, pubkey, sizeof(pubkey), &written); + wally_free(ctx.path_buff); + + if (ret == WALLY_OK) { + if (written == EC_XONLY_PUBLIC_KEY_LEN) { + memcpy(bytes_out, pubkey, EC_XONLY_PUBLIC_KEY_LEN); + } else if (written == EC_PUBLIC_KEY_LEN) { + /* Compressed key: strip the parity byte */ + memcpy(bytes_out, pubkey + 1, EC_XONLY_PUBLIC_KEY_LEN); + } else { + ret = WALLY_EINVAL; + } + } + return ret; +} + +int wally_descriptor_get_taproot_merkle_root( + const struct wally_descriptor *descriptor, + uint32_t variant, uint32_t multi_index, uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len) +{ + ms_ctx ctx; + ms_node *taptree; + int ret; + + if (!descriptor || !bytes_out || len < SHA256_LEN || flags) + return WALLY_EINVAL; + if (descriptor->top_node->kind != KIND_DESCRIPTOR_TR || + variant >= descriptor->num_variants || + child_num >= BIP32_INITIAL_HARDENED_CHILD || + multi_index >= descriptor->num_multipaths) + return WALLY_EINVAL; + if (!descriptor->top_node->child) + return WALLY_ERROR; /* tr() with no internal key — corrupt AST */ + taptree = descriptor->top_node->child->next; + if (!taptree) + return WALLY_EINVAL; /* key-only tr() has no merkle root */ + + memcpy(&ctx, descriptor, sizeof(ctx)); + ctx.variant = variant; + ctx.child_num = child_num; + ctx.multi_index = multi_index; + ctx.path_buff = NULL; + if (ctx.max_path_elems && + !(ctx.path_buff = wally_malloc(ctx.max_path_elems * sizeof(uint32_t)))) + return WALLY_ENOMEM; + + ret = compute_taptree_hash(&ctx, taptree, bytes_out); + wally_free(ctx.path_buff); + return ret; +} diff --git a/src/descriptor_int.h b/src/descriptor_int.h new file mode 100644 index 000000000..671030159 --- /dev/null +++ b/src/descriptor_int.h @@ -0,0 +1,185 @@ +#ifndef WALLY_DESCRIPTOR_INT_H +#define WALLY_DESCRIPTOR_INT_H + +#include +#include +#include + +/* ms_node kind base values */ +#define KIND_MINISCRIPT 0x01 + +/* Miniscript terminal/compound node kinds */ +#define KIND_MINISCRIPT_PK (0x00000100 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_PKH (0x00000200 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_MULTI (0x00000300 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_PK_K (0x00001000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_PK_H (0x00002000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OLDER (0x00010000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_AFTER (0x00020000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_SHA256 (0x00030000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_HASH256 (0x00040000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_RIPEMD160 (0x00050000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_HASH160 (0x00060000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_THRESH (0x00070000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_ANDOR (0x01000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_AND_V (0x02000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_AND_B (0x03000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_AND_N (0x04000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OR_B (0x05000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OR_C (0x06000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OR_D (0x07000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OR_I (0x08000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_MULTI_A (0x09000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_MULTI_A_S (0x0A000000 | KIND_MINISCRIPT) + +/* Wrapper node kinds (decoder-only; not used by the string parser) */ +#define KIND_MINISCRIPT_ALT (0x0B000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_SWAP (0x0C000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_CHECK (0x0D000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_DUP_IF (0x0E000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_VERIFY (0x0F000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_NON_ZERO (0x10000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_ZERO_NOT_EQUAL (0x11000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_JUST_0 (0x12000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_JUST_1 (0x13000000 | KIND_MINISCRIPT) + +/* Witness state kinds (maps to Rust Witness enum variants) */ +#define MS_WITNESS_IMPOSSIBLE 0u /* No valid satisfaction exists */ +#define MS_WITNESS_UNAVAILABLE 1u /* Missing data; third party may satisfy */ +#define MS_WITNESS_STACK 2u /* Stack data available */ + +typedef struct ms_witness_item_t { + unsigned char *data; + size_t data_len; +} ms_witness_item; + +typedef struct ms_witness_t { + uint32_t kind; /* MS_WITNESS_* constant */ + ms_witness_item *items; + size_t num_items; + size_t items_allocation_len; /* allocated capacity */ +} ms_witness; + +typedef struct ms_satisfaction_t { + ms_witness witness; + bool has_sig; /* true if satisfaction contains a signature */ + uint32_t absolute_timelock; /* 0 = absent */ + uint32_t relative_timelock; /* 0 = absent */ +} ms_satisfaction; + +int ms_witness_init(ms_witness *w, uint32_t kind); +void ms_witness_free(ms_witness *w); +int ms_satisfaction_init(ms_satisfaction *s, uint32_t witness_kind); +void ms_satisfaction_free(ms_satisfaction *s); +ms_satisfaction satisfaction_best(ms_satisfaction a, ms_satisfaction b); +void satisfaction_or_b(ms_satisfaction sat_l, ms_satisfaction dissat_l, + ms_satisfaction sat_r, ms_satisfaction dissat_r, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable); +void satisfaction_or_c(ms_satisfaction sat_l, ms_satisfaction dissat_l, + ms_satisfaction sat_r, ms_satisfaction dissat_r, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable); +void satisfaction_or_d(ms_satisfaction sat_l, ms_satisfaction dissat_l, + ms_satisfaction sat_r, ms_satisfaction dissat_r, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable); +void satisfaction_or_i(ms_satisfaction sat_l, ms_satisfaction dissat_l, + ms_satisfaction sat_r, ms_satisfaction dissat_r, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable); +void satisfaction_andor(ms_satisfaction sat_x, ms_satisfaction dissat_x, + ms_satisfaction sat_y, ms_satisfaction dissat_y, + ms_satisfaction sat_z, ms_satisfaction dissat_z, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable); +void satisfaction_thresh(size_t k, size_t n, + ms_satisfaction *sats, + ms_satisfaction *dissats, + ms_satisfaction *sat_out, + ms_satisfaction *dissat_out); +void satisfaction_thresh_mall(size_t k, size_t n, + ms_satisfaction *sats, + ms_satisfaction *dissats, + ms_satisfaction *sat_out, + ms_satisfaction *dissat_out); + +ms_satisfaction ms_satisfaction_clone(const ms_satisfaction *src); + +/* Hash type constants for lookup_preimage */ +#define MS_HASH_SHA256 0u +#define MS_HASH_HASH256 1u +#define MS_HASH_RIPEMD160 2u +#define MS_HASH_HASH160 3u + +/* Asset provider for satisfy_node. Mirrors rust-miniscript AssetProvider. */ +typedef struct ms_satisfier_t { + /* Write a DER/Schnorr sig into sig_out; set *sig_len_out. Return true if available. */ + bool (*lookup_sig)(const struct ms_satisfier_t *stfr, + const unsigned char *pk, size_t pk_len, + unsigned char *sig_out, size_t *sig_len_out); + /* For pk_h fragments: given the 20-byte HASH160, resolve the public key and + * (when available) a signature. On return: pk_out/pk_len_out are always set + * when the function returns true; *sig_len_out > 0 only when a signature is + * also available. Returns false when the public key is unknown, which maps to + * MS_WITNESS_IMPOSSIBLE for sat and MS_WITNESS_UNAVAILABLE for dissat. May be NULL if no pk_h + * fragments are expected. */ + bool (*lookup_pkh)(const struct ms_satisfier_t *stfr, + const unsigned char *hash20, + unsigned char *pk_out, size_t *pk_len_out, + unsigned char *sig_out, size_t *sig_len_out); + /* Write the 32-byte preimage of hash into preimage_out. hash_type = MS_HASH_*. */ + bool (*lookup_preimage)(const struct ms_satisfier_t *stfr, + const unsigned char *hash, size_t hash_len, + uint32_t hash_type, + unsigned char preimage_out[32]); + /* Return true if relative locktime lock is satisfied. */ + bool (*check_older)(const struct ms_satisfier_t *stfr, uint32_t lock); + /* Return true if absolute locktime lock is satisfied. */ + bool (*check_after)(const struct ms_satisfier_t *stfr, uint32_t lock); + /* 32-byte taproot leaf hash; NULL for segwit v0. */ + const unsigned char *leaf_hash; + void *user_data; +} ms_satisfier; + +/* A node in a parsed miniscript expression */ +typedef struct ms_node_t { + struct ms_node_t *next; + struct ms_node_t *child; + struct ms_node_t *parent; + uint32_t kind; + uint32_t type_properties; + int64_t number; + const char *child_path; + const char *data; + uint32_t data_len; + uint32_t child_path_len; + char wrapper_str[12]; + unsigned short flags; /* WALLY_MS_IS_ flags */ + unsigned char builtin; +} ms_node; + +typedef struct wally_descriptor ms_ctx; + +/* Built-in miniscript expressions */ +typedef int (*node_verify_fn_t)(ms_ctx *ctx, ms_node *node); +typedef int (*node_gen_fn_t)(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written); + +struct ms_builtin_t { + const char *name; + const uint32_t name_len; + const uint32_t kind; + const uint32_t type_properties; + const uint32_t child_count; /* Number of expected children */ + const node_verify_fn_t verify_fn; + const node_gen_fn_t generate_fn; +}; + +extern const struct ms_builtin_t g_builtins[]; + +void satisfy_node(const ms_node *node, const ms_satisfier *stfr, + bool malleable, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out); + +#endif /* WALLY_DESCRIPTOR_INT_H */ diff --git a/src/internal.c b/src/internal.c index 451fdf4d0..18f1c56ca 100644 --- a/src/internal.c +++ b/src/internal.c @@ -367,9 +367,23 @@ static int wally_internal_ec_nonce_fn(unsigned char *nonce32, return secp256k1_nonce_function_default(nonce32, msg32, key32, algo16, data, attempt); } +static void wally_secp_illegal_callback(const char *str, void *data) +{ + /* secp256k1's default illegal-argument callback calls abort(), which would + * crash the host process when an API precondition is violated - e.g. when a + * malformed keyagg_cache/session parsed from untrusted bytes is later loaded. + * Ignore it instead so the calling wally_ function returns WALLY_ERROR. */ + (void)str; + (void)data; +} + struct secp256k1_context_struct *wally_get_new_secp_context(void) { - return secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + struct secp256k1_context_struct *ctx; + ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + if (ctx) + secp256k1_context_set_illegal_callback(ctx, wally_secp_illegal_callback, NULL); + return ctx; } struct secp256k1_context_struct *wally_internal_secp_context(void) diff --git a/src/internal.h b/src/internal.h index 70a93d013..8e0fc4ed1 100644 --- a/src/internal.h +++ b/src/internal.h @@ -54,6 +54,13 @@ int keypair_xonly_pub(secp256k1_xonly_pubkey *xpubkey, const secp256k1_keypair * int keypair_sec(unsigned char *output, const secp256k1_keypair *keypair); int keypair_xonly_tweak_add(secp256k1_keypair *keypair, const unsigned char *tweak); +/* Compute the BIP-341 TapTweak scalar t = tagged_hash(TapTweak, pubkey_xonly || merkle_root). + * pub_key may be x-only (32) or compressed (33); merkle_root may be NULL (key-path only). + * flags may include EC_FLAG_ELEMENTS to select the Elements tag. Defined in sign.c. */ +int get_bip341_tweak(const unsigned char *pub_key, size_t pub_key_len, + const unsigned char *merkle_root, uint32_t flags, + unsigned char *tweak, size_t tweak_len); + #define PUBKEY_COMPRESSED SECP256K1_EC_COMPRESSED #define PUBKEY_UNCOMPRESSED SECP256K1_EC_UNCOMPRESSED diff --git a/src/miniscript_decode.c b/src/miniscript_decode.c new file mode 100644 index 000000000..0b73b552f --- /dev/null +++ b/src/miniscript_decode.c @@ -0,0 +1,1202 @@ +#include "config.h" +#include "miniscript_decode.h" +#include +#include +#include +#include +#include "script_int.h" + +#define MULTI_A_NUM_KEYS_MAX 999 + +struct terminal_stack_t { + ms_node **nodes; + size_t len; + size_t cap; +}; + +terminal_stack_t *terminal_stack_new(size_t capacity) +{ + terminal_stack_t *s = wally_malloc(sizeof(*s)); + if (!s) return NULL; + s->nodes = wally_malloc(capacity * sizeof(ms_node *)); + if (!s->nodes) { wally_free(s); return NULL; } + s->len = 0; + s->cap = capacity; + return s; +} + +void terminal_stack_free(terminal_stack_t *s) +{ + if (s) { wally_free(s->nodes); wally_free(s); } +} + +int terminal_stack_push(terminal_stack_t *s, ms_node *node) +{ + if (s->len == s->cap) { + size_t new_cap = s->cap ? s->cap * 2 : 1; + ms_node **new_nodes = wally_malloc(new_cap * sizeof(ms_node *)); + if (!new_nodes) return WALLY_ENOMEM; + memcpy(new_nodes, s->nodes, s->len * sizeof(ms_node *)); + wally_free(s->nodes); + s->nodes = new_nodes; + s->cap = new_cap; + } + s->nodes[s->len++] = node; + return WALLY_OK; +} + +ms_node *terminal_stack_pop(terminal_stack_t *s) +{ + if (s->len == 0) return NULL; + return s->nodes[--s->len]; +} + +size_t terminal_stack_size(const terminal_stack_t *s) +{ + return s->len; +} + +int tokenize_script(const unsigned char *script, size_t script_len, + token_t *tokens, size_t max_tokens, size_t *out_count) +{ + size_t i, n = 0; + + for (i = 0; i < script_len; ++i) { + unsigned char op = script[i]; + + if (op == OP_0) { + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n].kind = TK_NUM; + tokens[n++].data.num = 0; + continue; + } + if (op == OP_1NEGATE) + return WALLY_EINVAL; + if (op >= OP_1 && op <= OP_16) { + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n].kind = TK_NUM; + tokens[n++].data.num = (uint32_t)(op - OP_1 + 1); + continue; + } + if (op >= 0x01 && op <= OP_PUSHDATA4) { + size_t data_len; + const unsigned char *data; + + if (op < OP_PUSHDATA1) { + data_len = op; + if (i + 1 + data_len > script_len) return WALLY_EINVAL; + data = script + i + 1; + i += data_len; + } else if (op == OP_PUSHDATA1) { + if (i + 1 >= script_len) return WALLY_EINVAL; + data_len = script[i + 1]; + if (i + 2 + data_len > script_len) return WALLY_EINVAL; + data = script + i + 2; + i += 1 + data_len; + } else if (op == OP_PUSHDATA2) { + if (i + 2 >= script_len) return WALLY_EINVAL; + data_len = (size_t)script[i + 1] | ((size_t)script[i + 2] << 8); + if (i + 3 + data_len > script_len) return WALLY_EINVAL; + data = script + i + 3; + i += 2 + data_len; + } else { /* OP_PUSHDATA4 */ + if (i + 4 >= script_len) return WALLY_EINVAL; + data_len = (size_t)script[i + 1] | ((size_t)script[i + 2] << 8) | + ((size_t)script[i + 3] << 16) | ((size_t)script[i + 4] << 24); + if (i + 5 + data_len > script_len) return WALLY_EINVAL; + data = script + i + 5; + i += 4 + data_len; + } + + if (n >= max_tokens) return WALLY_EINVAL; + if (data_len == 20) { + tokens[n].kind = TK_HASH20; + memcpy(tokens[n].data.hash20, data, 20); + } else if (data_len == 32) { + tokens[n].kind = TK_BYTES32; + memcpy(tokens[n].data.bytes32, data, 32); + } else if (data_len == 33) { + tokens[n].kind = TK_BYTES33; + memcpy(tokens[n].data.bytes33, data, 33); + } else if (data_len == 65) { + tokens[n].kind = TK_BYTES65; + memcpy(tokens[n].data.bytes65, data, 65); + } else if (data_len >= 1 && data_len <= 4) { + /* Script number (CScriptNum): 1–4 byte little-endian with sign bit */ + unsigned char sbuf[5]; + int64_t n64; + sbuf[0] = (unsigned char)data_len; + memcpy(sbuf + 1, data, data_len); + if (scriptint_from_bytes(sbuf, data_len + 1, &n64) != WALLY_OK) + return WALLY_EINVAL; + if (n64 < 0 || n64 > UINT32_MAX) + return WALLY_EINVAL; + /* Enforce minimal push encoding (anti-malleability): values + * 0..16 must use OP_0/OP_1..OP_16, and the CScriptNum must be + * minimally encoded (no redundant high 0x00 / negative-zero). */ + if (n64 <= 16) + return WALLY_EINVAL; + if ((data[data_len - 1] & 0x7f) == 0 && + (data_len < 2 || (data[data_len - 2] & 0x80) == 0)) + return WALLY_EINVAL; + tokens[n].kind = TK_NUM; + tokens[n].data.num = (uint32_t)n64; + } else { + return WALLY_EINVAL; + } + n++; + continue; + } + + switch (op) { + case OP_BOOLAND: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_BOOL_AND; + break; + case OP_BOOLOR: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_BOOL_OR; + break; + case OP_ADD: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_ADD; + break; + case OP_EQUAL: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_EQUAL; + break; + case OP_EQUALVERIFY: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_EQUAL; + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_VERIFY; + break; + case OP_NUMEQUAL: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_NUM_EQUAL; + break; + case OP_NUMEQUALVERIFY: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_NUM_EQUAL; + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_VERIFY; + break; + case OP_CHECKSIG: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_CHECK_SIG; + break; + case OP_CHECKSIGVERIFY: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_CHECK_SIG; + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_VERIFY; + break; + case OP_CHECKSIGADD: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_CHECK_SIG_ADD; + break; + case OP_CHECKMULTISIG: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_CHECK_MULTI_SIG; + break; + case OP_CHECKMULTISIGVERIFY: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_CHECK_MULTI_SIG; + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_VERIFY; + break; + case OP_CHECKSEQUENCEVERIFY: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_CHECK_SEQUENCE_VERIFY; + break; + case OP_CHECKLOCKTIMEVERIFY: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_CHECK_LOCK_TIME_VERIFY; + break; + case OP_FROMALTSTACK: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_FROM_ALT_STACK; + break; + case OP_TOALTSTACK: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_TO_ALT_STACK; + break; + case OP_DROP: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_DROP; + break; + case OP_DUP: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_DUP; + break; + case OP_IF: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_IF; + break; + case OP_IFDUP: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_IF_DUP; + break; + case OP_NOTIF: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_NOT_IF; + break; + case OP_ELSE: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_ELSE; + break; + case OP_ENDIF: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_END_IF; + break; + case OP_0NOTEQUAL: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_ZERO_NOT_EQUAL; + break; + case OP_SIZE: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_SIZE; + break; + case OP_SWAP: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_SWAP; + break; + case OP_VERIFY: + /* NonMinimalVerify: standalone VERIFY after Equal/CheckSig/CheckMultiSig + * is non-minimal — the combined opcode should have been used instead */ + if (n > 0) { + tk_kind last = tokens[n - 1].kind; + if (last == TK_EQUAL || last == TK_CHECK_SIG || last == TK_CHECK_MULTI_SIG) + return WALLY_EINVAL; + } + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_VERIFY; + break; + case OP_RIPEMD160: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_RIPEMD160; + break; + case OP_HASH160: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_HASH160; + break; + case OP_SHA256: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_SHA256; + break; + case OP_HASH256: + if (n >= max_tokens) return WALLY_EINVAL; + tokens[n++].kind = TK_HASH256; + break; + default: + return WALLY_EINVAL; + } + } + + *out_count = n; + return WALLY_OK; +} + +/* ─── nonterm_stack_t ─────────────────────────────────────────────────────── */ + +struct nonterm_stack_t { + nonterm_t *items; + size_t len; + size_t cap; +}; + +nonterm_stack_t *nonterm_stack_new(size_t capacity) +{ + nonterm_stack_t *s = wally_malloc(sizeof(*s)); + if (!s) return NULL; + s->items = wally_malloc(capacity * sizeof(nonterm_t)); + if (!s->items) { wally_free(s); return NULL; } + s->len = 0; + s->cap = capacity; + return s; +} + +void nonterm_stack_free(nonterm_stack_t *s) +{ + if (s) { wally_free(s->items); wally_free(s); } +} + +int nonterm_stack_push(nonterm_stack_t *s, nonterm_t nt) +{ + if (s->len == s->cap) { + size_t new_cap = s->cap ? s->cap * 2 : 1; + nonterm_t *new_items = wally_malloc(new_cap * sizeof(nonterm_t)); + if (!new_items) return WALLY_ENOMEM; + memcpy(new_items, s->items, s->len * sizeof(nonterm_t)); + wally_free(s->items); + s->items = new_items; + s->cap = new_cap; + } + s->items[s->len++] = nt; + return WALLY_OK; +} + +bool nonterm_stack_pop(nonterm_stack_t *s, nonterm_t *out) +{ + if (s->len == 0) return false; + *out = s->items[--s->len]; + return true; +} + +size_t nonterm_stack_size(const nonterm_stack_t *s) +{ + return s->len; +} + +/* ─── tk_cursor_t ────────────────────────────────────────────────────────── */ + +typedef struct { + const token_t *tokens; + size_t pos; +} tk_cursor_t; + +static void tk_cursor_init(tk_cursor_t *c, const token_t *t, size_t n) +{ + c->tokens = t; + c->pos = n; +} + +static const token_t *tk_cursor_next(tk_cursor_t *c) +{ + if (c->pos == 0) return NULL; + return &c->tokens[--c->pos]; +} + +static const token_t *tk_cursor_peek(const tk_cursor_t *c) +{ + if (c->pos == 0) return NULL; + return &c->tokens[c->pos - 1]; +} + +static void tk_cursor_un_next(tk_cursor_t *c) +{ + c->pos++; +} + +/* ─── ms_node helpers ────────────────────────────────────────────────────── */ + +void ms_node_free(ms_node *node) +{ + /* Free `node` and all of its descendants iteratively. Recursing on ->child + * would overflow the stack on deeply-nested attacker-supplied scripts, so we + * thread an explicit work-list through the ->next links of the descendant + * nodes we own. node->next (a sibling still owned by the caller) is untouched. */ + ms_node *stack; + if (!node) return; + stack = node->child; + wally_free((void *)node->data); + wally_free(node); + while (stack) { + ms_node *m = stack; + ms_node *child; + stack = stack->next; + child = m->child; + while (child) { + ms_node *sib = child->next; + child->next = stack; + stack = child; + child = sib; + } + wally_free((void *)m->data); + wally_free(m); + } +} + +static ms_node *node_alloc(uint32_t kind) +{ + ms_node *n = wally_calloc(sizeof(*n)); + if (n) n->kind = kind; + return n; +} + +/* ─── reduce helpers ─────────────────────────────────────────────────────── */ + +static int reduce1(terminal_stack_t *term, uint32_t kind) +{ + ms_node *child = terminal_stack_pop(term); + if (!child) return WALLY_EINVAL; + ms_node *parent = node_alloc(kind); + if (!parent) { ms_node_free(child); return WALLY_ENOMEM; } + parent->child = child; + child->parent = parent; + int ret = terminal_stack_push(term, parent); + if (ret != WALLY_OK) ms_node_free(parent); + return ret; +} + +static int reduce2(terminal_stack_t *term, uint32_t kind) +{ + ms_node *left = terminal_stack_pop(term); + ms_node *right = terminal_stack_pop(term); + if (!left || !right) { + ms_node_free(left); + ms_node_free(right); + return WALLY_EINVAL; + } + ms_node *parent = node_alloc(kind); + if (!parent) { ms_node_free(left); ms_node_free(right); return WALLY_ENOMEM; } + parent->child = left; + left->next = right; + left->parent = parent; + right->parent = parent; + int ret = terminal_stack_push(term, parent); + if (ret != WALLY_OK) ms_node_free(parent); + return ret; +} + +/* Consume the SIZE 32 EQUALVERIFY prefix (tokens right-to-left: VERIFY EQUAL NUM(32) SIZE). */ +static bool consume_hash_suffix(tk_cursor_t *c) +{ + const token_t *t; + t = tk_cursor_next(c); if (!t || t->kind != TK_VERIFY) return false; + t = tk_cursor_next(c); if (!t || t->kind != TK_EQUAL) return false; + t = tk_cursor_next(c); if (!t || t->kind != TK_NUM || t->data.num != 32) return false; + t = tk_cursor_next(c); if (!t || t->kind != TK_SIZE) return false; + return true; +} + +static ms_node *make_hash_node(uint32_t kind, const unsigned char *hash, size_t hash_len) +{ + ms_node *n = node_alloc(kind); + if (!n) return NULL; + unsigned char *buf = wally_malloc(hash_len); + if (!buf) { ms_node_free(n); return NULL; } + memcpy(buf, hash, hash_len); + n->data = (const char *)buf; + n->data_len = (uint32_t)hash_len; + return n; +} + +static bool is_and_v(const tk_cursor_t *cursor) +{ + const token_t *tok = tk_cursor_peek(cursor); + if (!tok) return false; + switch (tok->kind) { + case TK_IF: + case TK_NOT_IF: + case TK_ELSE: + case TK_TO_ALT_STACK: + case TK_SWAP: + return false; + default: + return true; + } +} + +/* ─── decode_script_to_node ──────────────────────────────────────────────── */ + +int decode_script_to_node(const unsigned char *script, size_t script_len, + uint32_t ctx_flags, ms_node **output) +{ + int ret = WALLY_OK; + nonterm_stack_t *nonterm = NULL; + terminal_stack_t *term = NULL; + token_t *tokens = NULL; + nonterm_t nt; + + size_t max_tokens = script_len * 2 + 1; + tokens = wally_malloc(max_tokens * sizeof(token_t)); + if (!tokens) return WALLY_ENOMEM; + size_t n_tokens = 0; + ret = tokenize_script(script, script_len, tokens, max_tokens, &n_tokens); + if (ret != WALLY_OK) { wally_free(tokens); return ret; } + + tk_cursor_t cursor; + tk_cursor_init(&cursor, tokens, n_tokens); + + nonterm = nonterm_stack_new(n_tokens + 4); + term = terminal_stack_new(n_tokens + 4); + if (!nonterm || !term) { ret = WALLY_ENOMEM; goto cleanup; } + + nt.kind = NT_MAYBE_AND_V; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + + nonterm_t cur; + while (nonterm_stack_pop(nonterm, &cur)) { + switch (cur.kind) { + + case NT_EXPRESSION: { + const token_t *tok = tk_cursor_peek(&cursor); + if (!tok) { ret = WALLY_EINVAL; goto cleanup; } + + if (tok->kind == TK_BYTES33 || tok->kind == TK_BYTES65 || tok->kind == TK_BYTES32) { + /* pk_k: single key push */ + const unsigned char *key_bytes; + size_t key_len; + unsigned char *buf; + ms_node *n; + tok = tk_cursor_next(&cursor); + if (tok->kind == TK_BYTES33) { + key_bytes = tok->data.bytes33; key_len = 33; + } else if (tok->kind == TK_BYTES65) { + key_bytes = tok->data.bytes65; key_len = 65; + } else { + /* 32-byte x-only keys are only valid in tapscript context */ + if (!(ctx_flags & WALLY_MINISCRIPT_TAPSCRIPT)) { + ret = WALLY_EINVAL; + goto cleanup; + } + key_bytes = tok->data.bytes32; key_len = 32; + } + n = node_alloc(KIND_MINISCRIPT_PK_K); + if (!n) { ret = WALLY_ENOMEM; goto cleanup; } + buf = wally_malloc(key_len); + if (!buf) { ms_node_free(n); ret = WALLY_ENOMEM; goto cleanup; } + memcpy(buf, key_bytes, key_len); + n->data = (const char *)buf; + n->data_len = (uint32_t)key_len; + if (key_len == 32 && (ctx_flags & WALLY_MINISCRIPT_TAPSCRIPT)) + n->flags |= WALLY_MS_IS_X_ONLY; + ret = terminal_stack_push(term, n); + if (ret != WALLY_OK) { ms_node_free(n); goto cleanup; } + break; + } else if (tok->kind == TK_EQUAL) { + /* Hash fragments (sha256/hash256/ripemd160/hash160) or thresh. + * Script: SIZE 32 EQUALVERIFY EQUAL + * Tokens right-to-left: EQUAL, , , VERIFY, EQUAL, NUM(32), SIZE */ + const token_t *t2, *t3; + ms_node *n; + tk_cursor_next(&cursor); /* consume TK_EQUAL */ + t2 = tk_cursor_next(&cursor); + if (!t2) { ret = WALLY_EINVAL; goto cleanup; } + + if (t2->kind == TK_BYTES32) { + unsigned char hash32[32]; + memcpy(hash32, t2->data.bytes32, 32); + t3 = tk_cursor_next(&cursor); + if (!t3) { ret = WALLY_EINVAL; goto cleanup; } + uint32_t kind; + if (t3->kind == TK_SHA256) kind = KIND_MINISCRIPT_SHA256; + else if (t3->kind == TK_HASH256) kind = KIND_MINISCRIPT_HASH256; + else { ret = WALLY_EINVAL; goto cleanup; } + if (!consume_hash_suffix(&cursor)) { ret = WALLY_EINVAL; goto cleanup; } + n = make_hash_node(kind, hash32, 32); + if (!n) { ret = WALLY_ENOMEM; goto cleanup; } + ret = terminal_stack_push(term, n); + if (ret != WALLY_OK) { ms_node_free(n); goto cleanup; } + } else if (t2->kind == TK_HASH20) { + unsigned char hash20[20]; + memcpy(hash20, t2->data.hash20, 20); + t3 = tk_cursor_next(&cursor); + if (!t3) { ret = WALLY_EINVAL; goto cleanup; } + uint32_t kind; + if (t3->kind == TK_RIPEMD160) kind = KIND_MINISCRIPT_RIPEMD160; + else if (t3->kind == TK_HASH160) kind = KIND_MINISCRIPT_HASH160; + else { ret = WALLY_EINVAL; goto cleanup; } + if (!consume_hash_suffix(&cursor)) { ret = WALLY_EINVAL; goto cleanup; } + n = make_hash_node(kind, hash20, 20); + if (!n) { ret = WALLY_ENOMEM; goto cleanup; } + ret = terminal_stack_push(term, n); + if (ret != WALLY_OK) { ms_node_free(n); goto cleanup; } + } else if (t2->kind == TK_NUM) { + /* thresh continuation: EQUAL NUM(k) → ThreshW{k,0} */ + nt.kind = NT_THRESH_W; + nt.k = t2->data.num; + nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + ret = WALLY_EINVAL; + goto cleanup; + } + break; + } else if (tok->kind == TK_CHECK_SEQUENCE_VERIFY || tok->kind == TK_CHECK_LOCK_TIME_VERIFY) { + uint32_t kind = (tok->kind == TK_CHECK_SEQUENCE_VERIFY) + ? KIND_MINISCRIPT_OLDER : KIND_MINISCRIPT_AFTER; + const token_t *t2; + ms_node *n; + tk_cursor_next(&cursor); /* consume CSV/CLTV token */ + t2 = tk_cursor_next(&cursor); + if (!t2 || t2->kind != TK_NUM) { ret = WALLY_EINVAL; goto cleanup; } + n = node_alloc(kind); + if (!n) { ret = WALLY_ENOMEM; goto cleanup; } + n->number = (int64_t)t2->data.num; + ret = terminal_stack_push(term, n); + if (ret != WALLY_OK) { ms_node_free(n); goto cleanup; } + break; + } else if (tok->kind == TK_VERIFY) { + /* pk_h, v:hash_fragment, v:thresh, or general v:X. + * Tokens right-to-left: VERIFY [EQUAL VERIFY EQUAL NUM(32) SIZE] + * or VERIFY EQUAL HASH20 HASH160 DUP (pk_h) + * or VERIFY (v:X) */ + const token_t *t2, *t3, *t4, *t5; + ms_node *n; + tk_cursor_next(&cursor); /* consume TK_VERIFY */ + t2 = tk_cursor_peek(&cursor); + + if (t2 && t2->kind == TK_EQUAL) { + tk_cursor_next(&cursor); /* consume TK_EQUAL */ + t3 = tk_cursor_next(&cursor); + if (!t3) { ret = WALLY_EINVAL; goto cleanup; } + + if (t3->kind == TK_HASH20) { + unsigned char hash20[20]; + memcpy(hash20, t3->data.hash20, 20); + t4 = tk_cursor_next(&cursor); + if (!t4) { ret = WALLY_EINVAL; goto cleanup; } + + if (t4->kind == TK_HASH160) { + /* pk_h or v:hash160: disambiguate by next token */ + t5 = tk_cursor_peek(&cursor); + if (!t5) { ret = WALLY_EINVAL; goto cleanup; } + if (t5->kind == TK_DUP) { + /* pk_h: DUP HASH160 EQUALVERIFY */ + tk_cursor_next(&cursor); /* consume TK_DUP */ + n = make_hash_node(KIND_MINISCRIPT_PK_H, hash20, 20); + if (!n) { ret = WALLY_ENOMEM; goto cleanup; } + ret = terminal_stack_push(term, n); + if (ret != WALLY_OK) { ms_node_free(n); goto cleanup; } + } else if (t5->kind == TK_VERIFY) { + /* v:hash160: SIZE 32 EQUALVERIFY HASH160 EQUALVERIFY */ + if (!consume_hash_suffix(&cursor)) { ret = WALLY_EINVAL; goto cleanup; } + nt.kind = NT_VERIFY; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + n = make_hash_node(KIND_MINISCRIPT_HASH160, hash20, 20); + if (!n) { ret = WALLY_ENOMEM; goto cleanup; } + ret = terminal_stack_push(term, n); + if (ret != WALLY_OK) { ms_node_free(n); goto cleanup; } + } else { + ret = WALLY_EINVAL; + goto cleanup; + } + } else if (t4->kind == TK_RIPEMD160) { + /* v:ripemd160: SIZE 32 EQUALVERIFY RIPEMD160 EQUALVERIFY */ + if (!consume_hash_suffix(&cursor)) { ret = WALLY_EINVAL; goto cleanup; } + nt.kind = NT_VERIFY; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + n = make_hash_node(KIND_MINISCRIPT_RIPEMD160, hash20, 20); + if (!n) { ret = WALLY_ENOMEM; goto cleanup; } + ret = terminal_stack_push(term, n); + if (ret != WALLY_OK) { ms_node_free(n); goto cleanup; } + } else { + ret = WALLY_EINVAL; + goto cleanup; + } + } else if (t3->kind == TK_BYTES32) { + unsigned char hash32[32]; + memcpy(hash32, t3->data.bytes32, 32); + t4 = tk_cursor_next(&cursor); + if (!t4) { ret = WALLY_EINVAL; goto cleanup; } + uint32_t kind; + if (t4->kind == TK_SHA256) kind = KIND_MINISCRIPT_SHA256; + else if (t4->kind == TK_HASH256) kind = KIND_MINISCRIPT_HASH256; + else { ret = WALLY_EINVAL; goto cleanup; } + /* v:sha256 or v:hash256 */ + if (!consume_hash_suffix(&cursor)) { ret = WALLY_EINVAL; goto cleanup; } + nt.kind = NT_VERIFY; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + n = make_hash_node(kind, hash32, 32); + if (!n) { ret = WALLY_ENOMEM; goto cleanup; } + ret = terminal_stack_push(term, n); + if (ret != WALLY_OK) { ms_node_free(n); goto cleanup; } + } else if (t3->kind == TK_NUM) { + /* v:thresh */ + nt.kind = NT_VERIFY; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_THRESH_W; + nt.k = t3->data.num; + nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + ret = WALLY_EINVAL; + goto cleanup; + } + } else { + /* general v:X — TK_VERIFY already consumed, X starts at current position */ + nt.kind = NT_VERIFY; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } + break; + } else if (tok->kind == TK_CHECK_MULTI_SIG) { + const token_t *t2; + uint32_t n, k; + ms_node *prev = NULL, *parent; + + /* OP_CHECKMULTISIG(VERIFY) is disabled in tapscript (BIP-342); + * only multi_a (OP_CHECKSIGADD form) is permitted there. */ + if (ctx_flags & WALLY_MINISCRIPT_TAPSCRIPT) { + ret = WALLY_EINVAL; + goto cleanup; + } + + tk_cursor_next(&cursor); /* consume TK_CHECK_MULTI_SIG */ + + t2 = tk_cursor_next(&cursor); + if (!t2 || t2->kind != TK_NUM || t2->data.num < 1 || t2->data.num > 20) { + ret = WALLY_EINVAL; + goto cleanup; + } + n = t2->data.num; + + for (uint32_t i = 0; i < n; i++) { + const token_t *kt = tk_cursor_next(&cursor); + const unsigned char *kbytes; + size_t klen; + ms_node *key_node; + unsigned char *buf; + + if (!kt || (kt->kind != TK_BYTES33 && kt->kind != TK_BYTES65)) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_EINVAL; + goto cleanup; + } + if (kt->kind == TK_BYTES33) { kbytes = kt->data.bytes33; klen = 33; } + else { kbytes = kt->data.bytes65; klen = 65; } + + key_node = node_alloc(KIND_MINISCRIPT_PK_K); + buf = key_node ? wally_malloc(klen) : NULL; + if (!key_node || !buf) { + ms_node_free(key_node); + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_ENOMEM; + goto cleanup; + } + memcpy(buf, kbytes, klen); + key_node->data = (const char *)buf; + key_node->data_len = (uint32_t)klen; + key_node->next = prev; + prev = key_node; + } + + t2 = tk_cursor_next(&cursor); + if (!t2 || t2->kind != TK_NUM || t2->data.num < 1 || t2->data.num > n) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_EINVAL; + goto cleanup; + } + k = t2->data.num; + + parent = node_alloc(KIND_MINISCRIPT_MULTI); + if (!parent) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_ENOMEM; + goto cleanup; + } + parent->number = (int64_t)k; + { ms_node *p = prev; while (p) { p->parent = parent; p = p->next; } } + parent->child = prev; + + ret = terminal_stack_push(term, parent); + if (ret != WALLY_OK) { ms_node_free(parent); goto cleanup; } + break; + } else if (tok->kind == TK_NUM_EQUAL) { + /* multi_a / sortedmulti_a: + * script: K1 OP_CHECKSIG K2 OP_CHECKSIGADD ... Kn OP_CHECKSIGADD k OP_NUMEQUAL + * reading right-to-left: NUMEQUAL k (CHECKSIGADD Kn)... (CHECKSIG K1) */ + const token_t *t2; + uint32_t n = 0, k; + ms_node *prev = NULL, *parent; + bool done = false; + + tk_cursor_next(&cursor); /* consume TK_NUM_EQUAL */ + + t2 = tk_cursor_next(&cursor); + if (!t2 || t2->kind != TK_NUM || t2->data.num < 1) { + ret = WALLY_EINVAL; + goto cleanup; + } + k = t2->data.num; + + while (!done) { + const token_t *opcode_tok, *key_tok; + ms_node *key_node; + unsigned char *buf; + + if (n >= MULTI_A_NUM_KEYS_MAX) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_EINVAL; + goto cleanup; + } + + opcode_tok = tk_cursor_next(&cursor); + if (!opcode_tok) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_EINVAL; + goto cleanup; + } + + if (opcode_tok->kind == TK_CHECK_SIG) { + done = true; + } else if (opcode_tok->kind != TK_CHECK_SIG_ADD) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_EINVAL; + goto cleanup; + } + + key_tok = tk_cursor_next(&cursor); + if (!key_tok || key_tok->kind != TK_BYTES32) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_EINVAL; + goto cleanup; + } + + key_node = node_alloc(KIND_MINISCRIPT_PK_K); + buf = key_node ? wally_malloc(32) : NULL; + if (!key_node || !buf) { + ms_node_free(key_node); + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_ENOMEM; + goto cleanup; + } + memcpy(buf, key_tok->data.bytes32, 32); + key_node->data = (const char *)buf; + key_node->data_len = 32; + key_node->next = prev; /* prepend — keys decode Kn..K1, prepend restores K1..Kn */ + prev = key_node; + n++; + } + + if (k > n) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_EINVAL; + goto cleanup; + } + + parent = node_alloc(KIND_MINISCRIPT_MULTI_A); + if (!parent) { + ms_node *p = prev; + while (p) { ms_node *nx = p->next; p->next = NULL; ms_node_free(p); p = nx; } + ret = WALLY_ENOMEM; + goto cleanup; + } + parent->number = (int64_t)k; + { ms_node *p = prev; while (p) { p->parent = parent; p = p->next; } } + parent->child = prev; + + ret = terminal_stack_push(term, parent); + if (ret != WALLY_OK) { ms_node_free(parent); goto cleanup; } + break; + } else if (tok->kind == TK_BOOL_AND) { + tk_cursor_next(&cursor); /* consume TK_BOOL_AND */ + nt.kind = NT_AND_B; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_W_EXPRESSION; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + break; + } else if (tok->kind == TK_BOOL_OR) { + tk_cursor_next(&cursor); /* consume TK_BOOL_OR */ + nt.kind = NT_OR_B; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_W_EXPRESSION; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + break; + } else if (tok->kind == TK_END_IF) { + tk_cursor_next(&cursor); /* consume TK_END_IF */ + nt.kind = NT_END_IF; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_MAYBE_AND_V; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + break; + } else if (tok->kind == TK_CHECK_SIG) { + tk_cursor_next(&cursor); /* consume TK_CHECK_SIG */ + nt.kind = NT_CHECK; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + break; + } else if (tok->kind == TK_ZERO_NOT_EQUAL) { + tk_cursor_next(&cursor); /* consume TK_ZERO_NOT_EQUAL */ + nt.kind = NT_ZERO_NOT_EQUAL; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + break; + } else if (tok->kind == TK_NUM && (tok->data.num == 0 || tok->data.num == 1)) { + tok = tk_cursor_next(&cursor); /* consume TK_NUM */ + uint32_t just_kind = (tok->data.num == 0) ? KIND_MINISCRIPT_JUST_0 : KIND_MINISCRIPT_JUST_1; + ms_node *jn = node_alloc(just_kind); + if (!jn) { ret = WALLY_ENOMEM; goto cleanup; } + ret = terminal_stack_push(term, jn); + if (ret != WALLY_OK) { ms_node_free(jn); goto cleanup; } + break; + } + ret = WALLY_EINVAL; + goto cleanup; + } + + case NT_MAYBE_AND_V: + if (is_and_v(&cursor)) { + nt.kind = NT_AND_V; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } + break; + + case NT_SWAP: { + const token_t *tok = tk_cursor_next(&cursor); + if (!tok || tok->kind != TK_SWAP) { ret = WALLY_EINVAL; goto cleanup; } + ret = reduce1(term, KIND_MINISCRIPT_SWAP); + if (ret != WALLY_OK) goto cleanup; + break; + } + + case NT_ALT: { + const token_t *tok = tk_cursor_next(&cursor); + if (!tok || tok->kind != TK_TO_ALT_STACK) { ret = WALLY_EINVAL; goto cleanup; } + ret = reduce1(term, KIND_MINISCRIPT_ALT); + if (ret != WALLY_OK) goto cleanup; + break; + } + + case NT_CHECK: + ret = reduce1(term, KIND_MINISCRIPT_CHECK); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_DUP_IF: + ret = reduce1(term, KIND_MINISCRIPT_DUP_IF); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_VERIFY: + ret = reduce1(term, KIND_MINISCRIPT_VERIFY); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_NON_ZERO: + ret = reduce1(term, KIND_MINISCRIPT_NON_ZERO); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_ZERO_NOT_EQUAL: + ret = reduce1(term, KIND_MINISCRIPT_ZERO_NOT_EQUAL); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_AND_V: + if (is_and_v(&cursor)) { + nt.kind = NT_AND_V; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_MAYBE_AND_V; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + ret = reduce2(term, KIND_MINISCRIPT_AND_V); + if (ret != WALLY_OK) goto cleanup; + } + break; + + case NT_AND_B: + ret = reduce2(term, KIND_MINISCRIPT_AND_B); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_OR_B: + ret = reduce2(term, KIND_MINISCRIPT_OR_B); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_OR_C: + ret = reduce2(term, KIND_MINISCRIPT_OR_C); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_OR_D: + ret = reduce2(term, KIND_MINISCRIPT_OR_D); + if (ret != WALLY_OK) goto cleanup; + break; + + case NT_TERN: { + ms_node *a = terminal_stack_pop(term); + ms_node *b = terminal_stack_pop(term); + ms_node *c = terminal_stack_pop(term); + if (!a || !b || !c) { + ms_node_free(a); ms_node_free(b); ms_node_free(c); + ret = WALLY_EINVAL; goto cleanup; + } + ms_node *parent = node_alloc(KIND_MINISCRIPT_ANDOR); + if (!parent) { + ms_node_free(a); ms_node_free(b); ms_node_free(c); + ret = WALLY_ENOMEM; goto cleanup; + } + parent->child = a; + a->next = c; + c->next = b; + a->parent = c->parent = b->parent = parent; + if ((ret = terminal_stack_push(term, parent)) != WALLY_OK) { + ms_node_free(parent); + goto cleanup; + } + break; + } + + case NT_THRESH_W: { + const token_t *tok = tk_cursor_next(&cursor); + if (!tok) { ret = WALLY_EINVAL; goto cleanup; } + if (tok->kind == TK_ADD) { + nt.kind = NT_THRESH_W; + nt.k = cur.k; + nt.n = cur.n + 1; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_W_EXPRESSION; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + tk_cursor_un_next(&cursor); + nt.kind = NT_THRESH_E; + nt.k = cur.k; + nt.n = cur.n + 1; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } + break; + } + + case NT_THRESH_E: { + ms_node *parent = node_alloc(KIND_MINISCRIPT_THRESH); + if (!parent) { ret = WALLY_ENOMEM; goto cleanup; } + if (cur.k == 0 || cur.k > cur.n) { + ms_node_free(parent); + ret = WALLY_EINVAL; + goto cleanup; + } + parent->number = (int64_t)cur.k; + ms_node *head = NULL, *tail = NULL; + for (uint32_t i = 0; i < cur.n; i++) { + ms_node *child = terminal_stack_pop(term); + if (!child) { ms_node_free(parent); ret = WALLY_EINVAL; goto cleanup; } + child->parent = parent; + child->next = NULL; + if (!head) { head = tail = child; } + else { tail->next = child; tail = child; } + } + parent->child = head; + if ((ret = terminal_stack_push(term, parent)) != WALLY_OK) { + ms_node_free(parent); + goto cleanup; + } + break; + } + + case NT_END_IF: { + const token_t *tok = tk_cursor_next(&cursor); + if (!tok) { ret = WALLY_EINVAL; goto cleanup; } + if (tok->kind == TK_ELSE) { + nt.kind = NT_END_IF_ELSE; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_MAYBE_AND_V; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else if (tok->kind == TK_IF) { + const token_t *tok2 = tk_cursor_next(&cursor); + if (!tok2) { ret = WALLY_EINVAL; goto cleanup; } + if (tok2->kind == TK_DUP) { + nt.kind = NT_DUP_IF; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else if (tok2->kind == TK_ZERO_NOT_EQUAL) { + const token_t *tok3 = tk_cursor_next(&cursor); + if (!tok3 || tok3->kind != TK_SIZE) { ret = WALLY_EINVAL; goto cleanup; } + nt.kind = NT_NON_ZERO; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + ret = WALLY_EINVAL; + goto cleanup; + } + } else if (tok->kind == TK_NOT_IF) { + nt.kind = NT_END_IF_NOT_IF; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + ret = WALLY_EINVAL; + goto cleanup; + } + break; + } + + case NT_END_IF_NOT_IF: { + const token_t *tok = tk_cursor_next(&cursor); + if (!tok) { ret = WALLY_EINVAL; goto cleanup; } + if (tok->kind == TK_IF_DUP) { + nt.kind = NT_OR_D; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + tk_cursor_un_next(&cursor); + nt.kind = NT_OR_C; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } + nt.kind = NT_EXPRESSION; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + break; + } + + case NT_END_IF_ELSE: { + const token_t *tok = tk_cursor_next(&cursor); + if (!tok) { ret = WALLY_EINVAL; goto cleanup; } + if (tok->kind == TK_IF) { + ret = reduce2(term, KIND_MINISCRIPT_OR_I); + if (ret != WALLY_OK) goto cleanup; + } else if (tok->kind == TK_NOT_IF) { + nt.kind = NT_TERN; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + ret = WALLY_EINVAL; + goto cleanup; + } + break; + } + + case NT_W_EXPRESSION: { + const token_t *tok = tk_cursor_next(&cursor); + if (!tok) { ret = WALLY_EINVAL; goto cleanup; } + if (tok->kind == TK_FROM_ALT_STACK) { + nt.kind = NT_ALT; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } else { + tk_cursor_un_next(&cursor); + nt.kind = NT_SWAP; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + } + nt.kind = NT_MAYBE_AND_V; nt.k = nt.n = 0; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + nt.kind = NT_EXPRESSION; + if ((ret = nonterm_stack_push(nonterm, nt)) != WALLY_OK) goto cleanup; + break; + } + + } /* end switch */ + } /* end while */ + + if (terminal_stack_size(term) != 1) { + ret = WALLY_EINVAL; + goto cleanup; + } + *output = terminal_stack_pop(term); + ret = WALLY_OK; + +cleanup: + if (ret != WALLY_OK) { + ms_node *node; + while ((node = terminal_stack_pop(term)) != NULL) + ms_node_free(node); + } + wally_free(tokens); + nonterm_stack_free(nonterm); + terminal_stack_free(term); + return ret; +} diff --git a/src/miniscript_decode.h b/src/miniscript_decode.h new file mode 100644 index 000000000..3379479fb --- /dev/null +++ b/src/miniscript_decode.h @@ -0,0 +1,130 @@ +#ifndef LIBWALLY_MINISCRIPT_DECODE_H +#define LIBWALLY_MINISCRIPT_DECODE_H + +#include "config.h" +#include "descriptor_int.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + /* Opcode-only tokens */ + TK_BOOL_AND, + TK_BOOL_OR, + TK_ADD, + TK_EQUAL, + TK_NUM_EQUAL, + TK_CHECK_SIG, + TK_CHECK_SIG_ADD, + TK_CHECK_MULTI_SIG, + TK_CHECK_SEQUENCE_VERIFY, + TK_CHECK_LOCK_TIME_VERIFY, + TK_FROM_ALT_STACK, + TK_TO_ALT_STACK, + TK_DROP, + TK_DUP, + TK_IF, + TK_IF_DUP, + TK_NOT_IF, + TK_ELSE, + TK_END_IF, + TK_ZERO_NOT_EQUAL, + TK_SIZE, + TK_SWAP, + TK_VERIFY, + TK_RIPEMD160, + TK_HASH160, + TK_SHA256, + TK_HASH256, + /* Data-carrying tokens */ + TK_NUM, /* uint32_t */ + TK_HASH20, /* 20-byte digest (RIPEMD160 / HASH160) */ + TK_BYTES32, /* 32-byte digest (SHA256 / HASH256) or KEY32 */ + TK_BYTES33, /* 33-byte compressed pubkey */ + TK_BYTES65, /* 65-byte uncompressed pubkey */ +} tk_kind; + +typedef struct token_t { + tk_kind kind; + union { + uint32_t num; /* TK_NUM */ + uint8_t hash20[20]; /* TK_HASH20 */ + uint8_t bytes32[32]; /* TK_BYTES32 */ + uint8_t bytes33[33]; /* TK_BYTES33 */ + uint8_t bytes65[65]; /* TK_BYTES65 */ + } data; +} token_t; + +/* Tokenize a Script into an array of tokens. + * tokens must point to a caller-allocated array of at least max_tokens elements. + * On success *out_count is set to the number of tokens written. + */ +int tokenize_script(const unsigned char *script, size_t script_len, + token_t *tokens, size_t max_tokens, + size_t *out_count); + +typedef enum { + NT_EXPRESSION, + NT_W_EXPRESSION, + NT_SWAP, + NT_MAYBE_AND_V, + NT_ALT, + NT_CHECK, + NT_DUP_IF, + NT_VERIFY, + NT_NON_ZERO, + NT_ZERO_NOT_EQUAL, + NT_AND_V, + NT_AND_B, + NT_TERN, + NT_OR_B, + NT_OR_D, + NT_OR_C, + NT_THRESH_W, /* carries k, n */ + NT_THRESH_E, /* carries k, n */ + NT_END_IF, + NT_END_IF_NOT_IF, + NT_END_IF_ELSE, +} nonterm_kind; + +typedef struct nonterm_t { + nonterm_kind kind; + uint32_t k; /* used by NT_THRESH_W / NT_THRESH_E */ + uint32_t n; +} nonterm_t; + +typedef struct terminal_stack_t terminal_stack_t; + +terminal_stack_t *terminal_stack_new(size_t capacity); +void terminal_stack_free(terminal_stack_t *s); +int terminal_stack_push(terminal_stack_t *s, ms_node *node); +ms_node *terminal_stack_pop(terminal_stack_t *s); +size_t terminal_stack_size(const terminal_stack_t *s); + +typedef struct nonterm_stack_t nonterm_stack_t; + +nonterm_stack_t *nonterm_stack_new(size_t capacity); +void nonterm_stack_free(nonterm_stack_t *s); +int nonterm_stack_push(nonterm_stack_t *s, nonterm_t nt); +bool nonterm_stack_pop(nonterm_stack_t *s, nonterm_t *out); +size_t nonterm_stack_size(const nonterm_stack_t *s); + +/* Decode a raw Bitcoin Script into an ms_node AST. + * ctx_flags: WALLY_MINISCRIPT_TAPSCRIPT or 0 (segwit v0). + * On success *output owns the tree; caller must free with ms_node_free(). + */ +int decode_script_to_node(const unsigned char *script, size_t script_len, + uint32_t ctx_flags, ms_node **output); + +/* Free a decoder-allocated ms_node tree (children + data). */ +void ms_node_free(ms_node *node); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBWALLY_MINISCRIPT_DECODE_H */ diff --git a/src/miniscript_satisfy.c b/src/miniscript_satisfy.c new file mode 100644 index 000000000..22494cab3 --- /dev/null +++ b/src/miniscript_satisfy.c @@ -0,0 +1,1308 @@ +#include "config.h" +#include "internal.h" +#include "descriptor_int.h" +#include + +static size_t witness_weight(const ms_witness *w) +{ + if (w->kind != MS_WITNESS_STACK) + return SIZE_MAX; + size_t total = 0; + for (size_t i = 0; i < w->num_items; i++) { + size_t item = w->items[i].data_len; + /* Saturate at SIZE_MAX-1 so SIZE_MAX stays the non-stack sentinel. */ + if (item >= SIZE_MAX - 1 || total >= SIZE_MAX - 1 - item) + return SIZE_MAX - 1; + total += item + 1; + } + return total; +} + +/* Weight delta (sat - dissat) for sorting thresh candidates. + * Returns INT64_MAX when sat is unavailable/impossible (avoid choosing). + * Returns INT64_MIN when dissat is unavailable/impossible (prefer choosing). */ +static int64_t thresh_weight_delta(const ms_satisfaction *sat, const ms_satisfaction *dsat) +{ + if (sat->witness.kind == MS_WITNESS_IMPOSSIBLE || + sat->witness.kind == MS_WITNESS_UNAVAILABLE) + return INT64_MAX; + if (dsat->witness.kind == MS_WITNESS_IMPOSSIBLE || + dsat->witness.kind == MS_WITNESS_UNAVAILABLE) + return INT64_MIN; + return (int64_t)witness_weight(&sat->witness) - + (int64_t)witness_weight(&dsat->witness); +} + +/* Precomputed sort key for a single thresh element. The key depends only on + * the element, so it can be computed once instead of in the O(n^2) sort. */ +struct thresh_key { + int imp; /* sat is impossible (sorts last) */ + int has_sig; /* sat carries a signature (sorts after sig-less) */ + int64_t delta; /* weight(sat) - weight(dissat) */ +}; + +static struct thresh_key thresh_make_key(const ms_satisfaction *sat, + const ms_satisfaction *dissat) +{ + struct thresh_key k; + k.imp = sat->witness.kind == MS_WITNESS_IMPOSSIBLE ? 1 : 0; + k.has_sig = sat->has_sig ? 1 : 0; + k.delta = thresh_weight_delta(sat, dissat); + return k; +} + +/* Non-malleable order: (is_impossible, has_sig, weight_delta) ascending */ +static int thresh_cmp_full(const struct thresh_key *a, const struct thresh_key *b) +{ + if (a->imp != b->imp) return a->imp - b->imp; + if (a->has_sig != b->has_sig) return a->has_sig - b->has_sig; + return (a->delta > b->delta) - (a->delta < b->delta); +} + +/* Malleable order: weight_delta only */ +static int thresh_cmp_mall(const struct thresh_key *a, const struct thresh_key *b) +{ + return (a->delta > b->delta) - (a->delta < b->delta); +} + +/* Insertion sort on index array (ascending). + * + * Each element's sort key is computed once up front (O(n) weight passes) + * rather than re-derived for every comparison (O(n^2) weight passes). On OOM + * the key array is left NULL and the keys are computed on demand instead. */ +static void thresh_sort(size_t *indices, size_t n, + const ms_satisfaction *sats, + const ms_satisfaction *dissats, int mall) +{ + struct thresh_key *keys = n ? wally_malloc(n * sizeof(*keys)) : NULL; + + for (size_t i = 0; keys && i < n; i++) + keys[i] = thresh_make_key(&sats[i], &dissats[i]); + + for (size_t i = 1; i < n; i++) { + size_t key = indices[i]; + size_t j = i; + while (j > 0) { + struct thresh_key ka, kb; + const struct thresh_key *pa, *pb; + if (keys) { + pa = &keys[indices[j - 1]]; + pb = &keys[key]; + } else { + ka = thresh_make_key(&sats[indices[j - 1]], &dissats[indices[j - 1]]); + kb = thresh_make_key(&sats[key], &dissats[key]); + pa = &ka; + pb = &kb; + } + if ((mall ? thresh_cmp_mall(pa, pb) : thresh_cmp_full(pa, pb)) <= 0) + break; + indices[j] = indices[j - 1]; + j--; + } + indices[j] = key; + } + wally_free(keys); +} + +/* + * Select the non-malleable minimum-weight satisfaction between a and b. + * Both a and b are consumed by this call; the caller must not use them + * afterwards. The caller owns the returned ms_satisfaction and must free + * it with ms_satisfaction_free() when done. + */ +ms_satisfaction satisfaction_best(ms_satisfaction a, ms_satisfaction b) +{ + ms_satisfaction result; + + /* Impossible short-circuits: if one side is impossible, take the other */ + if (a.witness.kind == MS_WITNESS_IMPOSSIBLE) + return b; + if (b.witness.kind == MS_WITNESS_IMPOSSIBLE) + return a; + + /* Neither has a sig: malleability vector, return unavailable */ + if (!a.has_sig && !b.has_sig) { + ms_satisfaction_free(&a); + ms_satisfaction_free(&b); + ms_satisfaction_init(&result, MS_WITNESS_UNAVAILABLE); + return result; + } + + /* Only b has a sig: third party can't malleate a (no sig to remove) */ + if (!a.has_sig) { + ms_satisfaction_free(&b); + return a; + } + + /* Only a has a sig: take b */ + if (!b.has_sig) { + ms_satisfaction_free(&a); + return b; + } + + /* Both have sigs: choose the lighter witness */ + if (witness_weight(&a.witness) <= witness_weight(&b.witness)) { + ms_satisfaction_free(&b); + return a; + } + ms_satisfaction_free(&a); + return b; +} + +/* Clone a satisfaction, deep-copying witness item data. On OOM returns IMPOSSIBLE. */ +ms_satisfaction ms_satisfaction_clone(const ms_satisfaction *src) +{ + ms_satisfaction result; + ms_satisfaction_init(&result, src->witness.kind); + result.has_sig = src->has_sig; + result.absolute_timelock = src->absolute_timelock; + result.relative_timelock = src->relative_timelock; + + if (src->witness.kind != MS_WITNESS_STACK || !src->witness.num_items) + return result; + + result.witness.items = wally_malloc(src->witness.num_items * sizeof(ms_witness_item)); + if (!result.witness.items) { + result.witness.kind = MS_WITNESS_IMPOSSIBLE; + return result; + } + result.witness.items_allocation_len = src->witness.num_items; + + for (size_t i = 0; i < src->witness.num_items; i++) { + const ms_witness_item *si = &src->witness.items[i]; + unsigned char *data = NULL; + if (si->data_len) { + data = wally_malloc(si->data_len); + if (!data) { + ms_satisfaction_free(&result); + ms_satisfaction_init(&result, MS_WITNESS_IMPOSSIBLE); + return result; + } + memcpy(data, si->data, si->data_len); + } + result.witness.items[i].data = data; + result.witness.items[i].data_len = si->data_len; + result.witness.num_items++; + } + return result; +} + +/* + * Concatenate two satisfactions: result has a's items followed by b's items. + * Consumes a and b. On OOM returns IMPOSSIBLE. + * + * Mirrors rust-miniscript Witness::combine(b.stack, a.stack) called as + * a.concatenate_rev(b) — the caller must pass args in (right, left) order + * when building a witness where right-fragment items precede left-fragment + * items (the common case for binary fragments). + */ +static ms_satisfaction satisfaction_concat(ms_satisfaction a, ms_satisfaction b) +{ + bool b_has_sig; + uint32_t b_abs, b_rel; + size_t new_count; + ms_witness_item *new_items; + + if (a.witness.kind == MS_WITNESS_IMPOSSIBLE) { + ms_satisfaction_free(&b); + return a; + } + if (b.witness.kind == MS_WITNESS_IMPOSSIBLE) { + ms_satisfaction_free(&a); + return b; + } + if (a.witness.kind == MS_WITNESS_UNAVAILABLE) { + ms_satisfaction_free(&b); + return a; + } + if (b.witness.kind == MS_WITNESS_UNAVAILABLE) { + ms_satisfaction_free(&a); + return b; + } + + /* Save b's scalar fields before it is freed */ + b_has_sig = b.has_sig; + b_abs = b.absolute_timelock; + b_rel = b.relative_timelock; + + new_count = a.witness.num_items + b.witness.num_items; + + if (new_count > a.witness.items_allocation_len) { + new_items = wally_malloc(new_count * sizeof(ms_witness_item)); + if (!new_items) { + ms_satisfaction_free(&a); + ms_satisfaction_free(&b); + ms_satisfaction_init(&a, MS_WITNESS_IMPOSSIBLE); + return a; + } + if (a.witness.num_items) + memcpy(new_items, a.witness.items, a.witness.num_items * sizeof(ms_witness_item)); + wally_free(a.witness.items); + a.witness.items = new_items; + a.witness.items_allocation_len = new_count; + } + + /* Transfer ownership of b's item data pointers into a */ + for (size_t i = 0; i < b.witness.num_items; i++) + a.witness.items[a.witness.num_items + i] = b.witness.items[i]; + a.witness.num_items = new_count; + + /* Prevent double-free: item data is now owned by a */ + b.witness.num_items = 0; + ms_satisfaction_free(&b); + + a.has_sig |= b_has_sig; + if (b_abs > a.absolute_timelock) + a.absolute_timelock = b_abs; + if (b_rel > a.relative_timelock) + a.relative_timelock = b_rel; + + return a; +} + +/* + * Malleable minimum: pick the cheaper satisfaction without enforcing + * non-malleability. Consumes a and b. + * + * Mirrors rust-miniscript Satisfaction::minimum_mall. + */ +static ms_satisfaction satisfaction_minimum_mall(ms_satisfaction a, ms_satisfaction b) +{ + bool has_sig; + + if (a.witness.kind == MS_WITNESS_IMPOSSIBLE || a.witness.kind == MS_WITNESS_UNAVAILABLE) { + ms_satisfaction_free(&a); + return b; + } + if (b.witness.kind == MS_WITNESS_IMPOSSIBLE || b.witness.kind == MS_WITNESS_UNAVAILABLE) { + ms_satisfaction_free(&b); + return a; + } + + /* Both are stacks: take the lighter; has_sig only if both carry a sig */ + has_sig = a.has_sig && b.has_sig; + + if (witness_weight(&a.witness) <= witness_weight(&b.witness)) { + ms_satisfaction_free(&b); + a.has_sig = has_sig; + return a; + } + ms_satisfaction_free(&a); + b.has_sig = has_sig; + return b; +} + +/* + * Append a single push item to a satisfaction's witness stack, taking + * ownership of `data` (no copy). data == NULL / data_len == 0 pushes an + * empty item. Consumes s and `data`; on OOM frees `data` and returns + * IMPOSSIBLE. + */ +static ms_satisfaction satisfaction_push_item_take(ms_satisfaction s, + unsigned char *data, + size_t data_len) +{ + size_t n; + ms_witness_item *new_items; + + if (s.witness.kind != MS_WITNESS_STACK) { + wally_free(data); + return s; + } + + n = s.witness.num_items; + + if (n + 1 > s.witness.items_allocation_len) { + /* Grow geometrically: building a k-item stack via repeated pushes + * is then amortized O(k) rather than O(k^2) reallocations. */ + size_t new_cap = s.witness.items_allocation_len ? + s.witness.items_allocation_len * 2 : 4; + new_items = wally_malloc(new_cap * sizeof(ms_witness_item)); + if (!new_items) { + wally_free(data); + ms_satisfaction_free(&s); + ms_satisfaction_init(&s, MS_WITNESS_IMPOSSIBLE); + return s; + } + if (n) + memcpy(new_items, s.witness.items, n * sizeof(ms_witness_item)); + wally_free(s.witness.items); + s.witness.items = new_items; + s.witness.items_allocation_len = new_cap; + } + + s.witness.items[n].data = data; + s.witness.items[n].data_len = data_len; + s.witness.num_items = n + 1; + return s; +} + +/* + * Append a single push item to a satisfaction's witness stack, copying + * `data`. data == NULL / data_len == 0 pushes an empty item (OP_0 / false). + * Consumes s; on OOM returns IMPOSSIBLE. + * + * Used by satisfaction_or_i to attach the IF/ELSE branch selector byte. + */ +static ms_satisfaction satisfaction_push_item(ms_satisfaction s, + const unsigned char *data, + size_t data_len) +{ + unsigned char *item_data = NULL; + + if (s.witness.kind != MS_WITNESS_STACK) + return s; + + if (data_len) { + item_data = wally_malloc(data_len); + if (!item_data) { + ms_satisfaction_free(&s); + ms_satisfaction_init(&s, MS_WITNESS_IMPOSSIBLE); + return s; + } + memcpy(item_data, data, data_len); + } + return satisfaction_push_item_take(s, item_data, data_len); +} + +/* + * or_b(X, Y) satisfaction and dissatisfaction. + * + * Script: [X] [Y] BOOLOR + * Witnesses (right/inner items precede left/outer in array): + * sat = best( concat(r_sat, l_dis), concat(r_dis, l_sat) ) + * dsat = concat(r_dis, l_dis) + * + * Mirrors rust-miniscript Terminal::OrB arm in sat_dissat.rs. + */ +void satisfaction_or_b(ms_satisfaction sat_l, ms_satisfaction dissat_l, + ms_satisfaction sat_r, ms_satisfaction dissat_r, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable) +{ + ms_satisfaction dissat_l_clone = ms_satisfaction_clone(&dissat_l); + ms_satisfaction dissat_r_clone = ms_satisfaction_clone(&dissat_r); + + *dissat_out = satisfaction_concat(dissat_r_clone, dissat_l_clone); + + if (malleable) + *sat_out = satisfaction_minimum_mall( + satisfaction_concat(sat_r, dissat_l), + satisfaction_concat(dissat_r, sat_l)); + else + *sat_out = satisfaction_best( + satisfaction_concat(sat_r, dissat_l), + satisfaction_concat(dissat_r, sat_l)); +} + +/* + * or_c(X, Y) satisfaction and dissatisfaction. + * + * Script: [X] NOTIF [Y] ENDIF + * Witnesses: + * sat = best( sat_l, concat(r_sat, l_dis) ) + * dsat = IMPOSSIBLE (or_c has no valid dissatisfaction) + * + * Mirrors rust-miniscript Terminal::OrC arm in sat_dissat.rs. + */ +void satisfaction_or_c(ms_satisfaction sat_l, ms_satisfaction dissat_l, + ms_satisfaction sat_r, ms_satisfaction dissat_r, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable) +{ + ms_satisfaction_free(&dissat_r); + ms_satisfaction_init(dissat_out, MS_WITNESS_IMPOSSIBLE); + + if (malleable) + *sat_out = satisfaction_minimum_mall(sat_l, satisfaction_concat(sat_r, dissat_l)); + else + *sat_out = satisfaction_best(sat_l, satisfaction_concat(sat_r, dissat_l)); +} + +/* + * or_d(X, Y) satisfaction and dissatisfaction. + * + * Script: [X] IFDUP NOTIF [Y] ENDIF + * Witnesses: + * sat = best( sat_l, concat(r_sat, l_dis) ) + * dsat = concat(r_dis, l_dis) + * + * Mirrors rust-miniscript Terminal::OrD arm in sat_dissat.rs. + */ +void satisfaction_or_d(ms_satisfaction sat_l, ms_satisfaction dissat_l, + ms_satisfaction sat_r, ms_satisfaction dissat_r, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable) +{ + ms_satisfaction dissat_l_clone = ms_satisfaction_clone(&dissat_l); + + *dissat_out = satisfaction_concat(dissat_r, dissat_l_clone); + + if (malleable) + *sat_out = satisfaction_minimum_mall(sat_l, satisfaction_concat(sat_r, dissat_l)); + else + *sat_out = satisfaction_best(sat_l, satisfaction_concat(sat_r, dissat_l)); +} + +/* + * or_i(X, Y) satisfaction and dissatisfaction. + * + * Script: IF [X] ELSE [Y] ENDIF + * The branch selector byte (0x01 = left / empty = right) is appended to the + * sub-satisfaction and sits on top of the witness stack when the script runs. + * Witnesses: + * sat = best( sat_l ++ [0x01], sat_r ++ [] ) + * dsat = minimum_mall( dissat_l ++ [0x01], dissat_r ++ [] ) + * + * Mirrors rust-miniscript Terminal::OrI arm in sat_dissat.rs. + */ +void satisfaction_or_i(ms_satisfaction sat_l, ms_satisfaction dissat_l, + ms_satisfaction sat_r, ms_satisfaction dissat_r, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable) +{ + static const unsigned char push_1_data[] = {0x01}; + + if (malleable) + *sat_out = satisfaction_minimum_mall( + satisfaction_push_item(sat_l, push_1_data, 1), + satisfaction_push_item(sat_r, NULL, 0)); + else + *sat_out = satisfaction_best( + satisfaction_push_item(sat_l, push_1_data, 1), + satisfaction_push_item(sat_r, NULL, 0)); + + *dissat_out = satisfaction_minimum_mall( + satisfaction_push_item(dissat_l, push_1_data, 1), + satisfaction_push_item(dissat_r, NULL, 0)); +} + +/* + * andor(X, Y, Z) satisfaction and dissatisfaction. + * + * Script: [X] NOTIF [Z] ELSE [Y] ENDIF + * Witnesses: + * sat = best( concat(sat_y, sat_x), concat(sat_z, dissat_x) ) + * dsat = concat(dissat_z, dissat_x) + * + * (inner/Y-Z items precede outer/X in array) + * + * dissat_y is unused: the Y branch is only reached when X is satisfied, + * so the overall dissatisfaction always takes the Z path (dissat_x + dissat_z). + * + * Mirrors rust-miniscript Terminal::AndOr arm in sat_dissat.rs. + */ +void satisfaction_andor(ms_satisfaction sat_x, ms_satisfaction dissat_x, + ms_satisfaction sat_y, ms_satisfaction dissat_y, + ms_satisfaction sat_z, ms_satisfaction dissat_z, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out, + bool malleable) +{ + ms_satisfaction dissat_x_clone = ms_satisfaction_clone(&dissat_x); + + ms_satisfaction_free(&dissat_y); + + *dissat_out = satisfaction_concat(dissat_z, dissat_x_clone); + + if (malleable) + *sat_out = satisfaction_minimum_mall( + satisfaction_concat(sat_y, sat_x), + satisfaction_concat(sat_z, dissat_x)); + else + *sat_out = satisfaction_best( + satisfaction_concat(sat_y, sat_x), + satisfaction_concat(sat_z, dissat_x)); +} + +/* + * thresh(k, X1, ..., Xn) malleable satisfaction and dissatisfaction. + * + * Consumes every element in sats[] and dissats[]. + * Mirrors rust-miniscript Satisfaction::thresh_mall. + */ +void satisfaction_thresh_mall(size_t k, size_t n, + ms_satisfaction *sats, + ms_satisfaction *dissats, + ms_satisfaction *sat_out, + ms_satisfaction *dissat_out) +{ + size_t i; + + /* 1. Compute dissat_out from clones of original dissats */ + ms_satisfaction dsat_acc; + ms_satisfaction_init(&dsat_acc, MS_WITNESS_STACK); + for (i = 0; i < n; i++) { + ms_satisfaction cl = ms_satisfaction_clone(&dissats[i]); + dsat_acc = satisfaction_concat(cl, dsat_acc); + } + *dissat_out = dsat_acc; + + /* 2. Build and sort index array by weight delta (malleable) */ + size_t *indices = wally_malloc(n * sizeof(size_t)); + if (!indices) { + for (i = 0; i < n; i++) { + ms_satisfaction_free(&sats[i]); + ms_satisfaction_free(&dissats[i]); + } + ms_satisfaction_init(sat_out, MS_WITNESS_IMPOSSIBLE); + return; + } + for (i = 0; i < n; i++) indices[i] = i; + thresh_sort(indices, n, sats, dissats, 1); + + /* 3. Swap first k: dissats[indices[i]] gets the chosen sat */ + for (i = 0; i < k; i++) { + ms_satisfaction tmp = dissats[indices[i]]; + dissats[indices[i]] = sats[indices[i]]; + sats[indices[i]] = tmp; + } + + /* 4. Free the leftover sats[] entries (unchosen sats + swapped-out dissats) */ + for (i = 0; i < n; i++) ms_satisfaction_free(&sats[i]); + + /* 5. Fold dissats[] (now ret_stack) for sat_out */ + ms_satisfaction sat_acc; + ms_satisfaction_init(&sat_acc, MS_WITNESS_STACK); + for (i = 0; i < n; i++) + sat_acc = satisfaction_concat(dissats[i], sat_acc); + *sat_out = sat_acc; + + wally_free(indices); +} + +/* + * thresh(k, X1, ..., Xn) non-malleable satisfaction and dissatisfaction. + * + * Consumes every element in sats[] and dissats[]. + * Mirrors rust-miniscript Satisfaction::thresh. + */ +void satisfaction_thresh(size_t k, size_t n, + ms_satisfaction *sats, + ms_satisfaction *dissats, + ms_satisfaction *sat_out, + ms_satisfaction *dissat_out) +{ + size_t i; + + /* 1. Compute dissat_out from clones of original dissats */ + ms_satisfaction dsat_acc; + ms_satisfaction_init(&dsat_acc, MS_WITNESS_STACK); + for (i = 0; i < n; i++) { + ms_satisfaction cl = ms_satisfaction_clone(&dissats[i]); + dsat_acc = satisfaction_concat(cl, dsat_acc); + } + *dissat_out = dsat_acc; + + /* 2. Build and sort index array with non-malleable key */ + size_t *indices = wally_malloc(n * sizeof(size_t)); + if (!indices) { + for (i = 0; i < n; i++) { + ms_satisfaction_free(&sats[i]); + ms_satisfaction_free(&dissats[i]); + } + ms_satisfaction_init(sat_out, MS_WITNESS_IMPOSSIBLE); + return; + } + for (i = 0; i < n; i++) indices[i] = i; + thresh_sort(indices, n, sats, dissats, 0); + + /* 3. Swap first k: dissats[indices[i]] gets the chosen sat */ + for (i = 0; i < k; i++) { + ms_satisfaction tmp = dissats[indices[i]]; + dissats[indices[i]] = sats[indices[i]]; + sats[indices[i]] = tmp; + } + + /* 4. Malleability check A: if k-th chosen's original dissat is Impossible, + * we could not find k non-impossible satisfactions — overall impossible. */ + if (sats[indices[k - 1]].witness.kind == MS_WITNESS_IMPOSSIBLE) { + for (i = 0; i < n; i++) { + ms_satisfaction_free(&sats[i]); + ms_satisfaction_free(&dissats[i]); + } + wally_free(indices); + ms_satisfaction_init(sat_out, MS_WITNESS_IMPOSSIBLE); + return; + } + + /* 5. Malleability check B: if the first unchosen element's original sat is + * not impossible and has no sig, a third party can malleate — unavailable. */ + if (k < n && + sats[indices[k]].witness.kind != MS_WITNESS_IMPOSSIBLE && + !sats[indices[k]].has_sig) { + for (i = 0; i < n; i++) { + ms_satisfaction_free(&sats[i]); + ms_satisfaction_free(&dissats[i]); + } + wally_free(indices); + ms_satisfaction_init(sat_out, MS_WITNESS_UNAVAILABLE); + return; + } + + /* 6. Free leftover sats[], fold dissats[] (ret_stack) for sat_out */ + for (i = 0; i < n; i++) ms_satisfaction_free(&sats[i]); + ms_satisfaction sat_acc; + ms_satisfaction_init(&sat_acc, MS_WITNESS_STACK); + for (i = 0; i < n; i++) + sat_acc = satisfaction_concat(dissats[i], sat_acc); + *sat_out = sat_acc; + + wally_free(indices); +} + +typedef struct { + ms_satisfaction sat; + ms_satisfaction dissat; +} sat_dissat_t; + +static size_t ms_node_count(const ms_node *node) +{ + /* Count `node`, its ->next siblings and all descendants iteratively. An + * explicit heap stack (grown by hand, as there is no wally_realloc) avoids + * the unbounded recursion that would overflow the stack on deeply-nested + * attacker-supplied scripts. On allocation failure we return 0, which the + * caller (satisfy_node) treats as an unsatisfiable/impossible tree. */ + size_t count = 0, cap = 0, sp = 0; + const ms_node **stack = NULL; + const ms_node *cur = node; + + while (cur || sp) { + if (cur) { + ++count; + if (cur->child) { + if (sp == cap) { + size_t new_cap = cap ? cap * 2 : 32; + const ms_node **grown = wally_malloc(new_cap * sizeof(*grown)); + if (!grown) { wally_free(stack); return 0; } + if (sp) + memcpy(grown, stack, sp * sizeof(*grown)); + wally_free(stack); + stack = grown; + cap = new_cap; + } + stack[sp++] = cur->child; + } + cur = cur->next; + } else + cur = stack[--sp]; + } + wally_free(stack); + return count; +} + +typedef struct { + const ms_node *node; + const ms_node *cur_child; +} trav_frame_t; + +void satisfy_node(const ms_node *node, const ms_satisfier *stfr, + bool malleable, + ms_satisfaction *sat_out, ms_satisfaction *dissat_out) +{ + size_t cap = ms_node_count(node); + if (!cap) { + ms_satisfaction_init(sat_out, MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_init(dissat_out, MS_WITNESS_IMPOSSIBLE); + return; + } + + trav_frame_t *trav = wally_malloc(cap * sizeof(trav_frame_t)); + sat_dissat_t *result = wally_malloc(cap * sizeof(sat_dissat_t)); + if (!trav || !result) { + wally_free(trav); wally_free(result); + ms_satisfaction_init(sat_out, MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_init(dissat_out, MS_WITNESS_IMPOSSIBLE); + return; + } + + size_t tsp = 0; + size_t rsp = 0; + + trav[tsp++] = (trav_frame_t){ node, node->child }; + + while (tsp > 0) { + trav_frame_t *top = &trav[tsp - 1]; + + if (top->cur_child) { + const ms_node *child = top->cur_child; + top->cur_child = child->next; + trav[tsp++] = (trav_frame_t){ child, child->child }; + continue; + } + + const ms_node *n = top->node; + tsp--; + + sat_dissat_t entry; + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_IMPOSSIBLE); + + static const unsigned char push_1[] = {0x01}; + static const unsigned char zero32[32] = {0}; + + switch (n->kind) { + + case KIND_MINISCRIPT_JUST_0: + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_STACK); + break; + + case KIND_MINISCRIPT_JUST_1: + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.sat, MS_WITNESS_STACK); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_IMPOSSIBLE); + break; + + case KIND_MINISCRIPT_PK_K: { + unsigned char sig_buf[73]; + size_t sig_len = 0; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_STACK); + entry.dissat = satisfaction_push_item(entry.dissat, NULL, 0); + if (stfr && stfr->lookup_sig && + stfr->lookup_sig(stfr, (const unsigned char *)n->data, + n->data_len, sig_buf, &sig_len)) { + ms_satisfaction_init(&entry.sat, MS_WITNESS_STACK); + entry.sat = satisfaction_push_item(entry.sat, sig_buf, sig_len); + entry.sat.has_sig = true; + } else { + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + } + break; + } + + case KIND_MINISCRIPT_PK_H: { + unsigned char pk_buf[65]; + unsigned char sig_buf[73]; + size_t pk_len = 0, sig_len = 0; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + if (stfr && stfr->lookup_pkh && + stfr->lookup_pkh(stfr, (const unsigned char *)n->data, + pk_buf, &pk_len, sig_buf, &sig_len)) { + /* dissat: [0, pubkey] */ + ms_satisfaction_init(&entry.dissat, MS_WITNESS_STACK); + entry.dissat = satisfaction_push_item(entry.dissat, NULL, 0); + entry.dissat = satisfaction_push_item(entry.dissat, pk_buf, pk_len); + /* sat: [sig, pubkey] or IMPOSSIBLE if no sig */ + if (sig_len > 0) { + ms_satisfaction_init(&entry.sat, MS_WITNESS_STACK); + entry.sat = satisfaction_push_item(entry.sat, sig_buf, sig_len); + entry.sat = satisfaction_push_item(entry.sat, pk_buf, pk_len); + entry.sat.has_sig = true; + } else { + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + } + } else { + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_UNAVAILABLE); + } + break; + } + + case KIND_MINISCRIPT_SHA256: + case KIND_MINISCRIPT_HASH256: + case KIND_MINISCRIPT_RIPEMD160: + case KIND_MINISCRIPT_HASH160: { + unsigned char preimage[32]; + uint32_t hash_type; + if (n->kind == KIND_MINISCRIPT_SHA256) hash_type = MS_HASH_SHA256; + else if (n->kind == KIND_MINISCRIPT_HASH256) hash_type = MS_HASH_HASH256; + else if (n->kind == KIND_MINISCRIPT_RIPEMD160) hash_type = MS_HASH_RIPEMD160; + else hash_type = MS_HASH_HASH160; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + if (stfr && stfr->lookup_preimage && + stfr->lookup_preimage(stfr, (const unsigned char *)n->data, + n->data_len, hash_type, preimage)) { + ms_satisfaction_init(&entry.sat, MS_WITNESS_STACK); + entry.sat = satisfaction_push_item(entry.sat, preimage, 32); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_STACK); + entry.dissat = satisfaction_push_item(entry.dissat, zero32, 32); + } else { + ms_satisfaction_init(&entry.sat, MS_WITNESS_UNAVAILABLE); + /* Dissatisfaction for hash fragments is always possible: + * any 32-byte non-matching value suffices. */ + ms_satisfaction_init(&entry.dissat, MS_WITNESS_STACK); + entry.dissat = satisfaction_push_item(entry.dissat, zero32, 32); + } + break; + } + + case KIND_MINISCRIPT_OLDER: { + uint32_t lock = (uint32_t)n->number; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_IMPOSSIBLE); + if (stfr && stfr->check_older && stfr->check_older(stfr, lock)) { + ms_satisfaction_init(&entry.sat, MS_WITNESS_STACK); + entry.sat.relative_timelock = lock; + } else { + ms_satisfaction_init(&entry.sat, MS_WITNESS_UNAVAILABLE); + } + break; + } + + case KIND_MINISCRIPT_AFTER: { + uint32_t lock = (uint32_t)n->number; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_IMPOSSIBLE); + if (stfr && stfr->check_after && stfr->check_after(stfr, lock)) { + ms_satisfaction_init(&entry.sat, MS_WITNESS_STACK); + entry.sat.absolute_timelock = lock; + } else { + ms_satisfaction_init(&entry.sat, MS_WITNESS_UNAVAILABLE); + } + break; + } + + case KIND_MINISCRIPT_MULTI: { + size_t child_n = 0; + for (const ms_node *c = n->child; c; c = c->next) child_n++; + size_t k = (size_t)n->number; + + ms_satisfaction *sats = wally_malloc(child_n * sizeof(ms_satisfaction)); + ms_satisfaction *dissats = wally_malloc(child_n * sizeof(ms_satisfaction)); + if (!sats || !dissats) { + wally_free(sats); wally_free(dissats); + for (size_t i = 0; i < child_n; i++) { + rsp--; + ms_satisfaction_free(&result[rsp].sat); + ms_satisfaction_free(&result[rsp].dissat); + } + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_IMPOSSIBLE); + break; + } + + /* Pop children from result stack preserving original key order */ + for (size_t i = child_n; i-- > 0; ) { + sat_dissat_t sd = result[--rsp]; + sats[i] = sd.sat; + dissats[i] = sd.dissat; + } + + for (size_t i = 0; i < child_n; i++) + ms_satisfaction_free(&dissats[i]); + wally_free(dissats); + + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + + /* dissat: dummy + k zeros = k+1 empty items */ + ms_satisfaction_init(&entry.dissat, MS_WITNESS_STACK); + for (size_t i = 0; i <= k; i++) + entry.dissat = satisfaction_push_item(entry.dissat, NULL, 0); + + /* Collect indices of keys with available signatures */ + size_t *avail = wally_malloc(child_n * sizeof(size_t)); + if (!avail) { + for (size_t i = 0; i < child_n; i++) + ms_satisfaction_free(&sats[i]); + wally_free(sats); + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + break; + } + size_t navail = 0; + for (size_t i = 0; i < child_n; i++) { + if (sats[i].witness.kind == MS_WITNESS_STACK) + avail[navail++] = i; + } + + if (navail < k) { + for (size_t i = 0; i < child_n; i++) + ms_satisfaction_free(&sats[i]); + wally_free(sats); + wally_free(avail); + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + break; + } + + if (navail > k) { + /* Sort avail[] by witness weight ascending, keep k lightest */ + for (size_t i = 1; i < navail; i++) { + size_t tmp = avail[i], j = i; + size_t tmp_w = witness_weight(&sats[tmp].witness); + while (j > 0 && + witness_weight(&sats[avail[j - 1]].witness) > tmp_w) { + avail[j] = avail[j - 1]; + j--; + } + avail[j] = tmp; + } + /* Free the heaviest sigs and mark them freed */ + for (size_t i = k; i < navail; i++) { + ms_satisfaction_free(&sats[avail[i]]); + ms_satisfaction_init(&sats[avail[i]], MS_WITNESS_IMPOSSIBLE); + } + navail = k; + /* Re-sort chosen indices by key position (ascending) */ + for (size_t i = 1; i < k; i++) { + size_t tmp = avail[i], j = i; + while (j > 0 && avail[j - 1] > tmp) { + avail[j] = avail[j - 1]; + j--; + } + avail[j] = tmp; + } + } + /* avail[0..k-1] holds chosen key indices in ascending order */ + + /* Free all unchosen sats */ + { + size_t ai = 0; + for (size_t i = 0; i < child_n; i++) { + if (ai < k && avail[ai] == i) + ai++; + else + ms_satisfaction_free(&sats[i]); + } + } + + /* sat: dummy item followed by k signatures in key order */ + ms_satisfaction_init(&entry.sat, MS_WITNESS_STACK); + entry.sat = satisfaction_push_item(entry.sat, NULL, 0); + for (size_t i = 0; i < k; i++) { + size_t idx = avail[i]; + if (sats[idx].witness.num_items > 0) { + /* Move the signature item into entry.sat (no copy) */ + entry.sat = satisfaction_push_item_take(entry.sat, + sats[idx].witness.items[0].data, + sats[idx].witness.items[0].data_len); + sats[idx].witness.items[0].data = NULL; + sats[idx].witness.items[0].data_len = 0; + } + ms_satisfaction_free(&sats[idx]); + } + entry.sat.has_sig = true; + + wally_free(sats); + wally_free(avail); + break; + } + + case KIND_MINISCRIPT_PK: + case KIND_MINISCRIPT_PKH: + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.sat, MS_WITNESS_UNAVAILABLE); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_UNAVAILABLE); + break; + + case KIND_MINISCRIPT_MULTI_A: + case KIND_MINISCRIPT_MULTI_A_S: { + size_t child_n = 0; + for (const ms_node *c = n->child; c; c = c->next) child_n++; + size_t k = (size_t)n->number; + + ms_satisfaction *sats = wally_malloc(child_n * sizeof(ms_satisfaction)); + ms_satisfaction *dissats = wally_malloc(child_n * sizeof(ms_satisfaction)); + if (!sats || !dissats) { + wally_free(sats); wally_free(dissats); + for (size_t i = 0; i < child_n; i++) { + rsp--; + ms_satisfaction_free(&result[rsp].sat); + ms_satisfaction_free(&result[rsp].dissat); + } + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_IMPOSSIBLE); + break; + } + + /* Pop children from result stack preserving original key order */ + for (size_t i = child_n; i-- > 0; ) { + sat_dissat_t sd = result[--rsp]; + sats[i] = sd.sat; + dissats[i] = sd.dissat; + } + + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + + /* dissat: n empty items, one per key (all dissatisfied, no dummy prefix) */ + ms_satisfaction_init(&entry.dissat, MS_WITNESS_STACK); + for (size_t i = 0; i < child_n; i++) + entry.dissat = satisfaction_push_item(entry.dissat, NULL, 0); + for (size_t i = 0; i < child_n; i++) + ms_satisfaction_free(&dissats[i]); + wally_free(dissats); + + /* Collect indices of keys with available signatures */ + size_t *avail = wally_malloc(child_n * sizeof(size_t)); + if (!avail) { + for (size_t i = 0; i < child_n; i++) + ms_satisfaction_free(&sats[i]); + wally_free(sats); + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + break; + } + size_t navail = 0; + for (size_t i = 0; i < child_n; i++) { + if (sats[i].witness.kind == MS_WITNESS_STACK) + avail[navail++] = i; + } + + if (navail < k) { + for (size_t i = 0; i < child_n; i++) + ms_satisfaction_free(&sats[i]); + wally_free(sats); + wally_free(avail); + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + break; + } + + if (navail > k) { + /* Sort avail[] by witness weight ascending, keep k lightest */ + for (size_t i = 1; i < navail; i++) { + size_t tmp = avail[i], j = i; + size_t tmp_w = witness_weight(&sats[tmp].witness); + while (j > 0 && + witness_weight(&sats[avail[j - 1]].witness) > tmp_w) { + avail[j] = avail[j - 1]; + j--; + } + avail[j] = tmp; + } + /* Free the heaviest sigs and mark them freed */ + for (size_t i = k; i < navail; i++) { + ms_satisfaction_free(&sats[avail[i]]); + ms_satisfaction_init(&sats[avail[i]], MS_WITNESS_IMPOSSIBLE); + } + navail = k; + /* Re-sort chosen indices by key position (ascending) */ + for (size_t i = 1; i < k; i++) { + size_t tmp = avail[i], j = i; + while (j > 0 && avail[j - 1] > tmp) { + avail[j] = avail[j - 1]; + j--; + } + avail[j] = tmp; + } + } + /* avail[0..k-1] holds chosen key indices in ascending order */ + + /* + * sat: n witness items in reverse key order (Kn first at stack + * bottom, K1 last at stack top). Chosen keys contribute their sig; + * unchosen keys contribute an empty byte string. + * Use two-pointer scan since avail[] is sorted ascending. + */ + ms_satisfaction_init(&entry.sat, MS_WITNESS_STACK); + { + size_t ai = k; + for (size_t i = child_n; i-- > 0; ) { + bool chosen = (ai > 0 && avail[ai - 1] == i); + if (chosen) { + ai--; + if (sats[i].witness.num_items > 0) { + /* Move the signature item into entry.sat (no copy) */ + entry.sat = satisfaction_push_item_take(entry.sat, + sats[i].witness.items[0].data, + sats[i].witness.items[0].data_len); + sats[i].witness.items[0].data = NULL; + sats[i].witness.items[0].data_len = 0; + } + } else { + entry.sat = satisfaction_push_item(entry.sat, NULL, 0); + } + ms_satisfaction_free(&sats[i]); + } + } + entry.sat.has_sig = true; + + wally_free(sats); + wally_free(avail); + break; + } + + case KIND_MINISCRIPT_ALT: + case KIND_MINISCRIPT_SWAP: + case KIND_MINISCRIPT_CHECK: + case KIND_MINISCRIPT_ZERO_NOT_EQUAL: { + sat_dissat_t child = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + entry = child; + break; + } + + case KIND_MINISCRIPT_DUP_IF: { + sat_dissat_t child = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_free(&child.dissat); + ms_satisfaction_init(&child.dissat, MS_WITNESS_STACK); + entry.dissat = satisfaction_push_item(child.dissat, NULL, 0); + entry.sat = satisfaction_push_item(child.sat, push_1, 1); + break; + } + + case KIND_MINISCRIPT_VERIFY: { + sat_dissat_t child = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_free(&child.dissat); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_IMPOSSIBLE); + entry.sat = child.sat; + break; + } + + case KIND_MINISCRIPT_NON_ZERO: { + /* j:X = SIZE 0NOTEQUAL IF [X] ENDIF. Satisfied by X's satisfaction + * (whose top element is non-zero-length); dissatisfied by a single + * empty push (SIZE=0 -> false -> IF skipped). Mirrors DUP_IF's + * dissatisfaction. Previously this was set to IMPOSSIBLE, which made + * any fragment needing to dissatisfy a j:-wrapped child unsatisfiable. */ + sat_dissat_t child = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_free(&child.dissat); + ms_satisfaction_init(&child.dissat, MS_WITNESS_STACK); + entry.dissat = satisfaction_push_item(child.dissat, NULL, 0); + entry.sat = child.sat; + break; + } + + case KIND_MINISCRIPT_AND_B: { + sat_dissat_t r = result[--rsp]; + sat_dissat_t l = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + entry.sat = satisfaction_concat(r.sat, l.sat); + entry.dissat = satisfaction_concat(r.dissat, l.dissat); + break; + } + + case KIND_MINISCRIPT_AND_V: { + sat_dissat_t r = result[--rsp]; + sat_dissat_t l = result[--rsp]; + ms_satisfaction l_sat_clone = ms_satisfaction_clone(&l.sat); + ms_satisfaction_free(&l.dissat); + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + entry.sat = satisfaction_concat(r.sat, l.sat); + entry.dissat = satisfaction_concat(r.dissat, l_sat_clone); + break; + } + + case KIND_MINISCRIPT_AND_N: { + sat_dissat_t y = result[--rsp]; + sat_dissat_t x = result[--rsp]; + ms_satisfaction_free(&y.dissat); + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + entry.sat = satisfaction_concat(y.sat, x.sat); + entry.dissat = x.dissat; + break; + } + + case KIND_MINISCRIPT_ANDOR: { + sat_dissat_t z = result[--rsp]; + sat_dissat_t y = result[--rsp]; + sat_dissat_t x = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + satisfaction_andor(x.sat, x.dissat, y.sat, y.dissat, + z.sat, z.dissat, + &entry.sat, &entry.dissat, malleable); + break; + } + + case KIND_MINISCRIPT_OR_B: { + sat_dissat_t r = result[--rsp]; + sat_dissat_t l = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + satisfaction_or_b(l.sat, l.dissat, r.sat, r.dissat, + &entry.sat, &entry.dissat, malleable); + break; + } + + case KIND_MINISCRIPT_OR_C: { + sat_dissat_t r = result[--rsp]; + sat_dissat_t l = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + satisfaction_or_c(l.sat, l.dissat, r.sat, r.dissat, + &entry.sat, &entry.dissat, malleable); + break; + } + + case KIND_MINISCRIPT_OR_D: { + sat_dissat_t r = result[--rsp]; + sat_dissat_t l = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + satisfaction_or_d(l.sat, l.dissat, r.sat, r.dissat, + &entry.sat, &entry.dissat, malleable); + break; + } + + case KIND_MINISCRIPT_OR_I: { + sat_dissat_t r = result[--rsp]; + sat_dissat_t l = result[--rsp]; + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + satisfaction_or_i(l.sat, l.dissat, r.sat, r.dissat, + &entry.sat, &entry.dissat, malleable); + break; + } + + case KIND_MINISCRIPT_THRESH: { + size_t child_n = 0; + for (const ms_node *c = n->child; c; c = c->next) child_n++; + size_t k = (size_t)n->number; + + ms_satisfaction *sats = wally_malloc(child_n * sizeof(ms_satisfaction)); + ms_satisfaction *dissats = wally_malloc(child_n * sizeof(ms_satisfaction)); + if (!sats || !dissats) { + wally_free(sats); wally_free(dissats); + for (size_t i = 0; i < child_n; i++) { + rsp--; + ms_satisfaction_free(&result[rsp].sat); + ms_satisfaction_free(&result[rsp].dissat); + } + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + ms_satisfaction_init(&entry.sat, MS_WITNESS_IMPOSSIBLE); + ms_satisfaction_init(&entry.dissat, MS_WITNESS_IMPOSSIBLE); + break; + } + for (size_t i = child_n; i-- > 0; ) { + sat_dissat_t sd = result[--rsp]; + sats[i] = sd.sat; + dissats[i] = sd.dissat; + } + ms_satisfaction_free(&entry.sat); + ms_satisfaction_free(&entry.dissat); + if (malleable) + satisfaction_thresh_mall(k, child_n, sats, dissats, &entry.sat, &entry.dissat); + else + satisfaction_thresh(k, child_n, sats, dissats, &entry.sat, &entry.dissat); + wally_free(sats); + wally_free(dissats); + break; + } + + default: + break; + } + + result[rsp++] = entry; + } + + *sat_out = result[0].sat; + *dissat_out = result[0].dissat; + + wally_free(trav); + wally_free(result); +} diff --git a/src/musig.c b/src/musig.c new file mode 100644 index 000000000..8056e7842 --- /dev/null +++ b/src/musig.c @@ -0,0 +1,1047 @@ +#include "internal.h" +#include +#include +#include + +#ifndef BUILD_STANDARD_SECP +#include + +/* Struct bodies kept private to prevent inadvertent copying (nonce-reuse risk) */ +struct wally_musig_keyagg_cache { unsigned char data[197]; }; +struct wally_musig_secnonce { unsigned char data[132]; }; +struct wally_musig_pubnonce { unsigned char data[132]; }; +struct wally_musig_aggnonce { unsigned char data[132]; }; +struct wally_musig_session { unsigned char data[133]; }; +struct wally_musig_partial_sig { unsigned char data[36]; }; + +/* Comparison function for qsort: lexicographic order of 33-byte compressed pubkeys */ +static int musig2_keyagg_pubkey_cmp(const void *a, const void *b) +{ + return memcmp(a, b, EC_PUBLIC_KEY_LEN); +} + +/* BIP-328 synthetic xpub chain code: SHA256("MuSig2MuSig2MuSig2") */ +static const unsigned char MUSIG2_CHAINCODE[WALLY_MUSIG2_CHAINCODE_LEN] = { + 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 +}; + +/* Compile-time size assertions to catch upstream secp256k1-zkp ABI changes */ +typedef char assert_keyagg_cache_size[ + sizeof(secp256k1_musig_keyagg_cache) == sizeof(struct wally_musig_keyagg_cache) ? 1 : -1]; +typedef char assert_secnonce_size[ + sizeof(secp256k1_musig_secnonce) == sizeof(struct wally_musig_secnonce) ? 1 : -1]; +typedef char assert_pubnonce_size[ + sizeof(secp256k1_musig_pubnonce) == sizeof(struct wally_musig_pubnonce) ? 1 : -1]; +typedef char assert_aggnonce_size[ + sizeof(secp256k1_musig_aggnonce) == sizeof(struct wally_musig_aggnonce) ? 1 : -1]; +typedef char assert_session_size[ + sizeof(secp256k1_musig_session) == sizeof(struct wally_musig_session) ? 1 : -1]; +typedef char assert_partial_sig_size[ + sizeof(secp256k1_musig_partial_sig) == sizeof(struct wally_musig_partial_sig) ? 1 : -1]; + +/* keyagg_cache lifecycle */ + +WALLY_CORE_API int wally_musig_keyagg_cache_free( + struct wally_musig_keyagg_cache *cache) +{ + if (cache) + clear_and_free(cache, sizeof(*cache)); + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_keyagg_cache_serialize( + const struct wally_musig_keyagg_cache *cache, + unsigned char *bytes_out, + size_t len) +{ + if (!cache || !bytes_out || len != WALLY_MUSIG_KEYAGG_CACHE_LEN) + return WALLY_EINVAL; + memcpy(bytes_out, cache->data, WALLY_MUSIG_KEYAGG_CACHE_LEN); + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_keyagg_cache_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_keyagg_cache **output) +{ + const secp256k1_context *ctx = secp_ctx(); + struct wally_musig_keyagg_cache *cache; + secp256k1_pubkey agg_pk; + + if (!bytes || bytes_len != WALLY_MUSIG_KEYAGG_CACHE_LEN || !output) + return WALLY_EINVAL; + *output = NULL; + if (!ctx) + return WALLY_ENOMEM; + cache = wally_calloc(sizeof(*cache)); + if (!cache) + return WALLY_ENOMEM; + memcpy(cache->data, bytes, WALLY_MUSIG_KEYAGG_CACHE_LEN); + /* Validate by extracting the aggregate key. secp256k1-zkp has no full + * validator for this struct, but this rejects a corrupted magic or + * aggregate-key field rather than deferring the failure to signing. */ + if (!secp256k1_musig_pubkey_get(ctx, &agg_pk, + (const secp256k1_musig_keyagg_cache *)cache)) { + clear_and_free(cache, sizeof(*cache)); + return WALLY_EINVAL; + } + *output = cache; + return WALLY_OK; +} + +/* secnonce lifecycle */ + +WALLY_CORE_API int wally_musig_secnonce_free( + struct wally_musig_secnonce *nonce) +{ + if (nonce) + clear_and_free(nonce, sizeof(*nonce)); + return WALLY_OK; +} + +/* pubnonce parse/serialize/free */ + +WALLY_CORE_API int wally_musig_pubnonce_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_pubnonce **output) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_musig_pubnonce *nonce; + + if (!bytes || bytes_len != WALLY_MUSIG_PUBNONCE_LEN || !output) + return WALLY_EINVAL; + *output = NULL; + if (!ctx) + return WALLY_ENOMEM; + + nonce = wally_calloc(sizeof(*nonce)); + if (!nonce) + return WALLY_ENOMEM; + + if (!secp256k1_musig_pubnonce_parse(ctx, nonce, bytes)) { + wally_free(nonce); + return WALLY_EINVAL; + } + *output = (struct wally_musig_pubnonce *)nonce; + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_pubnonce_serialize( + const struct wally_musig_pubnonce *nonce, + unsigned char *bytes_out, + size_t len) +{ + const secp256k1_context *ctx = secp_ctx(); + + if (!nonce || !bytes_out || len != WALLY_MUSIG_PUBNONCE_LEN) + return WALLY_EINVAL; + if (!ctx) + return WALLY_ENOMEM; + + if (!secp256k1_musig_pubnonce_serialize( + ctx, bytes_out, + (const secp256k1_musig_pubnonce *)nonce)) + return WALLY_ERROR; + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_pubnonce_free( + struct wally_musig_pubnonce *nonce) +{ + if (nonce) + clear_and_free(nonce, sizeof(*nonce)); + return WALLY_OK; +} + +/* aggnonce parse/serialize/free */ + +WALLY_CORE_API int wally_musig_aggnonce_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_aggnonce **output) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_musig_aggnonce *nonce; + + if (!bytes || bytes_len != WALLY_MUSIG_AGGNONCE_LEN || !output) + return WALLY_EINVAL; + *output = NULL; + if (!ctx) + return WALLY_ENOMEM; + + nonce = wally_calloc(sizeof(*nonce)); + if (!nonce) + return WALLY_ENOMEM; + + if (!secp256k1_musig_aggnonce_parse(ctx, nonce, bytes)) { + wally_free(nonce); + return WALLY_EINVAL; + } + *output = (struct wally_musig_aggnonce *)nonce; + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_aggnonce_serialize( + const struct wally_musig_aggnonce *nonce, + unsigned char *bytes_out, + size_t len) +{ + const secp256k1_context *ctx = secp_ctx(); + + if (!nonce || !bytes_out || len != WALLY_MUSIG_AGGNONCE_LEN) + return WALLY_EINVAL; + if (!ctx) + return WALLY_ENOMEM; + + if (!secp256k1_musig_aggnonce_serialize( + ctx, bytes_out, + (const secp256k1_musig_aggnonce *)nonce)) + return WALLY_ERROR; + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_aggnonce_free( + struct wally_musig_aggnonce *nonce) +{ + if (nonce) + clear_and_free(nonce, sizeof(*nonce)); + return WALLY_OK; +} + +/* session lifecycle */ + +WALLY_CORE_API int wally_musig_session_free( + struct wally_musig_session *session) +{ + if (session) + clear_and_free(session, sizeof(*session)); + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_session_serialize( + const struct wally_musig_session *session, + unsigned char *bytes_out, + size_t len) +{ + if (!session || !bytes_out || len != WALLY_MUSIG_SESSION_LEN) + return WALLY_EINVAL; + memcpy(bytes_out, session->data, WALLY_MUSIG_SESSION_LEN); + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_session_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_session **output) +{ + struct wally_musig_session *session; + + if (!bytes || bytes_len != WALLY_MUSIG_SESSION_LEN || !output) + return WALLY_EINVAL; + *output = NULL; + session = wally_calloc(sizeof(*session)); + if (!session) + return WALLY_ENOMEM; + memcpy(session->data, bytes, WALLY_MUSIG_SESSION_LEN); + *output = session; + return WALLY_OK; +} + +/* partial_sig parse/serialize/free */ + +WALLY_CORE_API int wally_musig_partial_sig_parse( + const unsigned char *bytes, + size_t bytes_len, + struct wally_musig_partial_sig **output) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_musig_partial_sig *sig; + + if (!bytes || bytes_len != WALLY_MUSIG_PARTIAL_SIG_LEN || !output) + return WALLY_EINVAL; + *output = NULL; + if (!ctx) + return WALLY_ENOMEM; + + sig = wally_calloc(sizeof(*sig)); + if (!sig) + return WALLY_ENOMEM; + + if (!secp256k1_musig_partial_sig_parse(ctx, sig, bytes)) { + wally_free(sig); + return WALLY_EINVAL; + } + *output = (struct wally_musig_partial_sig *)sig; + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_partial_sig_serialize( + const struct wally_musig_partial_sig *sig, + unsigned char *bytes_out, + size_t len) +{ + const secp256k1_context *ctx = secp_ctx(); + + if (!sig || !bytes_out || len != WALLY_MUSIG_PARTIAL_SIG_LEN) + return WALLY_EINVAL; + if (!ctx) + return WALLY_ENOMEM; + + if (!secp256k1_musig_partial_sig_serialize( + ctx, bytes_out, + (const secp256k1_musig_partial_sig *)sig)) + return WALLY_ERROR; + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_partial_sig_free( + struct wally_musig_partial_sig *sig) +{ + if (sig) + clear_and_free(sig, sizeof(*sig)); + return WALLY_OK; +} + +/* --- Key aggregation functions --- */ + +WALLY_CORE_API int wally_musig_pubkey_agg( + const unsigned char *pub_keys, + size_t pub_keys_len, + unsigned char *agg_pk_out, + size_t agg_pk_out_len, + struct wally_musig_keyagg_cache **cache_out) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_pubkey *pubkeys_parsed = NULL; + const secp256k1_pubkey **pubkey_ptrs = NULL; + secp256k1_musig_keyagg_cache *cache = NULL; + secp256k1_xonly_pubkey xonly; + size_t n_pubkeys, i; + int ret = WALLY_EINVAL; + + if (!pub_keys || !pub_keys_len || pub_keys_len % EC_PUBLIC_KEY_LEN != 0) + return WALLY_EINVAL; + if (agg_pk_out && agg_pk_out_len != EC_XONLY_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (!agg_pk_out && !cache_out) + return WALLY_EINVAL; + if (cache_out) + *cache_out = NULL; + if (!ctx) + return WALLY_ENOMEM; + + n_pubkeys = pub_keys_len / EC_PUBLIC_KEY_LEN; + if (n_pubkeys < 2) + return WALLY_EINVAL; + + pubkeys_parsed = wally_calloc(n_pubkeys * sizeof(secp256k1_pubkey)); + if (!pubkeys_parsed) + return WALLY_ENOMEM; + + pubkey_ptrs = wally_calloc(n_pubkeys * sizeof(secp256k1_pubkey *)); + if (!pubkey_ptrs) { + wally_free(pubkeys_parsed); + return WALLY_ENOMEM; + } + + for (i = 0; i < n_pubkeys; i++) { + if (!pubkey_parse(&pubkeys_parsed[i], + pub_keys + i * EC_PUBLIC_KEY_LEN, + EC_PUBLIC_KEY_LEN)) + goto cleanup; + pubkey_ptrs[i] = &pubkeys_parsed[i]; + } + + if (cache_out) { + cache = wally_calloc(sizeof(secp256k1_musig_keyagg_cache)); + if (!cache) { + ret = WALLY_ENOMEM; + goto cleanup; + } + } + + if (!secp256k1_musig_pubkey_agg(ctx, + agg_pk_out ? &xonly : NULL, + cache, pubkey_ptrs, n_pubkeys)) + goto cleanup; + + if (agg_pk_out) + xpubkey_serialize(agg_pk_out, &xonly); + + if (cache_out) { + *cache_out = (struct wally_musig_keyagg_cache *)cache; + cache = NULL; + } + ret = WALLY_OK; + +cleanup: + if (cache) + wally_free(cache); + wally_free(pubkey_ptrs); + wally_free(pubkeys_parsed); + return ret; +} + +WALLY_CORE_API int wally_musig_pubkey_get( + const struct wally_musig_keyagg_cache *cache, + unsigned char *pub_key_out, + size_t pub_key_out_len) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_pubkey agg_pk; + size_t len = EC_PUBLIC_KEY_LEN; + + if (!cache || !pub_key_out || pub_key_out_len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (!ctx) + return WALLY_ENOMEM; + + if (!secp256k1_musig_pubkey_get(ctx, &agg_pk, + (const secp256k1_musig_keyagg_cache *)cache)) + return WALLY_ERROR; + + pubkey_serialize(pub_key_out, &len, &agg_pk, PUBKEY_COMPRESSED); + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_pubkey_ec_tweak_add( + struct wally_musig_keyagg_cache *cache, + const unsigned char *tweak, + size_t tweak_len, + unsigned char *pub_key_out, + size_t pub_key_out_len) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_pubkey output_pk; + size_t len = EC_PUBLIC_KEY_LEN; + + if (!cache || !tweak || tweak_len != 32) + return WALLY_EINVAL; + if (pub_key_out && pub_key_out_len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (!ctx) + return WALLY_ENOMEM; + + if (!secp256k1_musig_pubkey_ec_tweak_add(ctx, + pub_key_out ? &output_pk : NULL, + (secp256k1_musig_keyagg_cache *)cache, + tweak)) + return WALLY_ERROR; + + if (pub_key_out) + pubkey_serialize(pub_key_out, &len, &output_pk, PUBKEY_COMPRESSED); + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_pubkey_xonly_tweak_add( + struct wally_musig_keyagg_cache *cache, + const unsigned char *tweak, + size_t tweak_len, + unsigned char *pub_key_out, + size_t pub_key_out_len) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_pubkey output_pk; + size_t len = EC_PUBLIC_KEY_LEN; + + if (!cache || !tweak || tweak_len != 32) + return WALLY_EINVAL; + if (pub_key_out && pub_key_out_len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (!ctx) + return WALLY_ENOMEM; + + if (!secp256k1_musig_pubkey_xonly_tweak_add(ctx, + pub_key_out ? &output_pk : NULL, + (secp256k1_musig_keyagg_cache *)cache, + tweak)) + return WALLY_ERROR; + + if (pub_key_out) + pubkey_serialize(pub_key_out, &len, &output_pk, PUBKEY_COMPRESSED); + return WALLY_OK; +} + +/* --- Nonce generation and aggregation functions --- */ + +WALLY_CORE_API int wally_musig_nonce_gen( + const unsigned char *session_secrand32, + size_t session_secrand_len, + const unsigned char *seckey, + size_t seckey_len, + const unsigned char *pubkey33, + size_t pubkey_len, + const struct wally_musig_keyagg_cache *keyagg_cache, + const unsigned char *msg32, + size_t msg_len, + const unsigned char *extra_input32, + size_t extra_len, + struct wally_musig_secnonce **secnonce_out, + struct wally_musig_pubnonce **pubnonce_out) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_musig_secnonce *secnonce = NULL; + secp256k1_musig_pubnonce *pubnonce = NULL; + secp256k1_pubkey pubkey; + + if (!session_secrand32 || session_secrand_len != 32) + return WALLY_EINVAL; + if (mem_is_zero(session_secrand32, session_secrand_len)) + return WALLY_EINVAL; /* All-zero session randomness is never valid: it must be + * unique and uniformly random. Reject the most common + * uninitialized/predictable input as defense-in-depth. + * The caller is still responsible for real entropy and + * never reusing a value across signing sessions. */ + if (seckey && seckey_len != 32) + return WALLY_EINVAL; + if (!seckey && seckey_len != 0) + return WALLY_EINVAL; + if (!pubkey33 || pubkey_len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (msg32 && msg_len != 32) + return WALLY_EINVAL; + if (!msg32 && msg_len != 0) + return WALLY_EINVAL; + if (extra_input32 && extra_len != 32) + return WALLY_EINVAL; + if (!extra_input32 && extra_len != 0) + return WALLY_EINVAL; + if (!secnonce_out || !pubnonce_out) + return WALLY_EINVAL; + *secnonce_out = NULL; + *pubnonce_out = NULL; + if (!ctx) + return WALLY_ENOMEM; + + if (!pubkey_parse(&pubkey, pubkey33, pubkey_len)) + return WALLY_EINVAL; + + secnonce = wally_calloc(sizeof(secp256k1_musig_secnonce)); + if (!secnonce) + return WALLY_ENOMEM; + + pubnonce = wally_calloc(sizeof(secp256k1_musig_pubnonce)); + if (!pubnonce) { + wally_free(secnonce); + return WALLY_ENOMEM; + } + + { + /* secp256k1 zeroes the session randomness buffer in place to prevent + * reuse; copy our const input into a mutable local for the call. */ + unsigned char secrand[32]; + int ok; + memcpy(secrand, session_secrand32, sizeof(secrand)); + ok = secp256k1_musig_nonce_gen(ctx, secnonce, pubnonce, + secrand, seckey, &pubkey, + msg32, + keyagg_cache ? (const secp256k1_musig_keyagg_cache *)keyagg_cache : NULL, + extra_input32); + wally_clear(secrand, sizeof(secrand)); + if (!ok) { + clear_and_free(secnonce, sizeof(*secnonce)); + wally_free(pubnonce); + return WALLY_ERROR; + } + } + + *secnonce_out = (struct wally_musig_secnonce *)secnonce; + *pubnonce_out = (struct wally_musig_pubnonce *)pubnonce; + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_nonce_gen_counter( + uint64_t counter, + const unsigned char *seckey, + size_t seckey_len, + const unsigned char *pubkey33, + size_t pubkey_len, + const struct wally_musig_keyagg_cache *keyagg_cache, + const unsigned char *msg32, + size_t msg_len, + const unsigned char *extra_input32, + size_t extra_len, + struct wally_musig_secnonce **secnonce_out, + struct wally_musig_pubnonce **pubnonce_out) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_musig_secnonce *secnonce = NULL; + secp256k1_musig_pubnonce *pubnonce = NULL; + secp256k1_keypair keypair; + int ret; + + /* seckey is REQUIRED for counter mode */ + if (!seckey || seckey_len != 32) + return WALLY_EINVAL; + if (!pubkey33 || pubkey_len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (msg32 && msg_len != 32) + return WALLY_EINVAL; + if (!msg32 && msg_len != 0) + return WALLY_EINVAL; + if (extra_input32 && extra_len != 32) + return WALLY_EINVAL; + if (!extra_input32 && extra_len != 0) + return WALLY_EINVAL; + if (!secnonce_out || !pubnonce_out) + return WALLY_EINVAL; + *secnonce_out = NULL; + *pubnonce_out = NULL; + if (!ctx) + return WALLY_ENOMEM; + + /* pubkey33 is length-checked above; counter mode derives the public key + * from the secret key via the keypair, using secp256k1's dedicated + * counter API (a low-entropy session_id is rejected by nonce_gen). */ + if (!keypair_create(&keypair, seckey)) + return WALLY_EINVAL; + + secnonce = wally_calloc(sizeof(secp256k1_musig_secnonce)); + if (!secnonce) { + wally_clear(&keypair, sizeof(keypair)); + return WALLY_ENOMEM; + } + + pubnonce = wally_calloc(sizeof(secp256k1_musig_pubnonce)); + if (!pubnonce) { + wally_free(secnonce); + wally_clear(&keypair, sizeof(keypair)); + return WALLY_ENOMEM; + } + + ret = secp256k1_musig_nonce_gen_counter(ctx, secnonce, pubnonce, + counter, &keypair, msg32, + keyagg_cache ? (const secp256k1_musig_keyagg_cache *)keyagg_cache : NULL, + extra_input32); + wally_clear(&keypair, sizeof(keypair)); + + if (!ret) { + clear_and_free(secnonce, sizeof(*secnonce)); + wally_free(pubnonce); + return WALLY_ERROR; + } + + *secnonce_out = (struct wally_musig_secnonce *)secnonce; + *pubnonce_out = (struct wally_musig_pubnonce *)pubnonce; + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_nonce_agg( + const unsigned char *pubnonces, + size_t pubnonces_len, + size_t n_pubnonces, + struct wally_musig_aggnonce **aggnonce_out) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_musig_pubnonce *parsed = NULL; + const secp256k1_musig_pubnonce **ptrs = NULL; + secp256k1_musig_aggnonce *aggnonce = NULL; + size_t i; + int ret = WALLY_EINVAL; + + if (!pubnonces || n_pubnonces < 2) + return WALLY_EINVAL; + if (pubnonces_len != n_pubnonces * WALLY_MUSIG_PUBNONCE_LEN) + return WALLY_EINVAL; + if (!aggnonce_out) + return WALLY_EINVAL; + *aggnonce_out = NULL; + if (!ctx) + return WALLY_ENOMEM; + + parsed = wally_calloc(n_pubnonces * sizeof(secp256k1_musig_pubnonce)); + if (!parsed) + return WALLY_ENOMEM; + + ptrs = wally_calloc(n_pubnonces * sizeof(secp256k1_musig_pubnonce *)); + if (!ptrs) { + wally_free(parsed); + return WALLY_ENOMEM; + } + + for (i = 0; i < n_pubnonces; i++) { + if (!secp256k1_musig_pubnonce_parse(ctx, &parsed[i], + pubnonces + i * WALLY_MUSIG_PUBNONCE_LEN)) + goto cleanup; + ptrs[i] = &parsed[i]; + } + + aggnonce = wally_calloc(sizeof(secp256k1_musig_aggnonce)); + if (!aggnonce) { + ret = WALLY_ENOMEM; + goto cleanup; + } + + if (!secp256k1_musig_nonce_agg(ctx, aggnonce, ptrs, n_pubnonces)) { + ret = WALLY_ERROR; + goto cleanup; + } + + *aggnonce_out = (struct wally_musig_aggnonce *)aggnonce; + aggnonce = NULL; + ret = WALLY_OK; + +cleanup: + if (aggnonce) + wally_free(aggnonce); + wally_free(ptrs); + wally_free(parsed); + return ret; +} + +WALLY_CORE_API int wally_musig_nonce_process( + const struct wally_musig_aggnonce *aggnonce, + const unsigned char *msg32, + size_t msg32_len, + const struct wally_musig_keyagg_cache *cache, + const unsigned char *adaptor, + size_t adaptor_len, + struct wally_musig_session **session_out) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_musig_session *session = NULL; + secp256k1_pubkey adaptor_pk; + int ret = WALLY_EINVAL; + + if (!aggnonce || !msg32 || msg32_len != 32) + return WALLY_EINVAL; + if (!cache || !session_out) + return WALLY_EINVAL; + if (adaptor && adaptor_len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (!adaptor && adaptor_len) + return WALLY_EINVAL; + *session_out = NULL; + if (!ctx) + return WALLY_ENOMEM; + + if (adaptor && !pubkey_parse(&adaptor_pk, adaptor, adaptor_len)) + return WALLY_EINVAL; + + session = wally_calloc(sizeof(secp256k1_musig_session)); + if (!session) + return WALLY_ENOMEM; + + if (!secp256k1_musig_nonce_process(ctx, session, + (const secp256k1_musig_aggnonce *)aggnonce, + msg32, + (const secp256k1_musig_keyagg_cache *)cache, + adaptor ? &adaptor_pk : NULL)) { + ret = WALLY_ERROR; + goto cleanup; + } + + *session_out = (struct wally_musig_session *)session; + session = NULL; + ret = WALLY_OK; + +cleanup: + if (session) + wally_free(session); + return ret; +} + +WALLY_CORE_API int wally_musig_partial_sign( + struct wally_musig_secnonce *secnonce, + const unsigned char *seckey, + size_t seckey_len, + const struct wally_musig_keyagg_cache *cache, + const struct wally_musig_session *session, + struct wally_musig_partial_sig **partial_sig_out) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_keypair keypair; + secp256k1_musig_partial_sig *partial_sig = NULL; + int ret = WALLY_EINVAL; + + if (!secnonce || !seckey || seckey_len != 32) + return WALLY_EINVAL; + if (!cache || !session || !partial_sig_out) + return WALLY_EINVAL; + *partial_sig_out = NULL; + if (!ctx) + return WALLY_ENOMEM; + + if (!secp256k1_keypair_create(ctx, &keypair, seckey)) { + ret = WALLY_EINVAL; + goto cleanup; + } + + partial_sig = wally_calloc(sizeof(secp256k1_musig_partial_sig)); + if (!partial_sig) { + ret = WALLY_ENOMEM; + goto cleanup; + } + + if (!secp256k1_musig_partial_sign(ctx, partial_sig, + (secp256k1_musig_secnonce *)secnonce, + &keypair, + (const secp256k1_musig_keyagg_cache *)cache, + (const secp256k1_musig_session *)session)) { + ret = WALLY_ERROR; + goto cleanup; + } + + *partial_sig_out = (struct wally_musig_partial_sig *)partial_sig; + partial_sig = NULL; + ret = WALLY_OK; + +cleanup: + wally_clear(&keypair, sizeof(keypair)); + if (partial_sig) + wally_free(partial_sig); + return ret; +} + +WALLY_CORE_API int wally_musig_partial_sig_verify( + const struct wally_musig_partial_sig *sig, + const struct wally_musig_pubnonce *pubnonce, + const unsigned char *pubkey, + size_t pubkey_len, + const struct wally_musig_keyagg_cache *cache, + const struct wally_musig_session *session) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_pubkey pk; + + if (!sig || !pubnonce || !pubkey || pubkey_len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (!cache || !session) + return WALLY_EINVAL; + if (!ctx) + return WALLY_ENOMEM; + + if (!pubkey_parse(&pk, pubkey, pubkey_len)) + return WALLY_EINVAL; + + if (!secp256k1_musig_partial_sig_verify(ctx, + (const secp256k1_musig_partial_sig *)sig, + (const secp256k1_musig_pubnonce *)pubnonce, + &pk, + (const secp256k1_musig_keyagg_cache *)cache, + (const secp256k1_musig_session *)session)) + return WALLY_ERROR; + + return WALLY_OK; +} + +WALLY_CORE_API int wally_musig_partial_sig_agg( + const unsigned char *partial_sigs, + size_t partial_sigs_len, + size_t n_sigs, + const struct wally_musig_session *session, + unsigned char *sig64_out, + size_t sig64_out_len) +{ + const secp256k1_context *ctx = secp_ctx(); + secp256k1_musig_partial_sig *parsed = NULL; + const secp256k1_musig_partial_sig **ptrs = NULL; + size_t i; + int ret = WALLY_EINVAL; + + if (!partial_sigs || n_sigs < 2) + return WALLY_EINVAL; + if (partial_sigs_len != n_sigs * WALLY_MUSIG_PARTIAL_SIG_LEN) + return WALLY_EINVAL; + if (!session || !sig64_out || sig64_out_len != EC_SIGNATURE_LEN) + return WALLY_EINVAL; + if (!ctx) + return WALLY_ENOMEM; + + parsed = wally_calloc(n_sigs * sizeof(secp256k1_musig_partial_sig)); + if (!parsed) + return WALLY_ENOMEM; + + ptrs = wally_calloc(n_sigs * sizeof(secp256k1_musig_partial_sig *)); + if (!ptrs) { + wally_free(parsed); + return WALLY_ENOMEM; + } + + for (i = 0; i < n_sigs; i++) { + if (!secp256k1_musig_partial_sig_parse(ctx, &parsed[i], + partial_sigs + i * WALLY_MUSIG_PARTIAL_SIG_LEN)) { + ret = WALLY_EINVAL; + goto cleanup; + } + ptrs[i] = &parsed[i]; + } + + if (!secp256k1_musig_partial_sig_agg(ctx, sig64_out, + (const secp256k1_musig_session *)session, + ptrs, n_sigs)) { + ret = WALLY_ERROR; + goto cleanup; + } + + ret = WALLY_OK; + +cleanup: + wally_free(ptrs); + wally_free(parsed); + return ret; +} + +WALLY_CORE_API int wally_musig_pubkey_to_xpub( + const unsigned char *agg_pk, + size_t agg_pk_len, + uint32_t version, + struct ext_key **output) +{ + unsigned char compressed_pk[EC_PUBLIC_KEY_LEN]; /* 33 bytes: 0x02 prefix + 32-byte x */ + int ret; + + if (!agg_pk || agg_pk_len != EC_XONLY_PUBLIC_KEY_LEN || !output) + return WALLY_EINVAL; + if (version != BIP32_VER_MAIN_PUBLIC && version != BIP32_VER_TEST_PUBLIC) + return WALLY_EINVAL; + *output = NULL; + + /* Convert x-only (32-byte) aggregate pubkey to compressed (33-byte) form. + * BIP-340: x-only keys are treated as having even parity (0x02 prefix). */ + compressed_pk[0] = 0x02; + memcpy(compressed_pk + 1, agg_pk, EC_XONLY_PUBLIC_KEY_LEN); + + /* Construct ext_key at depth 0 with no parent, using fixed BIP-328 chain code */ + ret = bip32_key_init_alloc( + version, + 0, /* depth */ + 0, /* child_num */ + MUSIG2_CHAINCODE, WALLY_MUSIG2_CHAINCODE_LEN, + compressed_pk, EC_PUBLIC_KEY_LEN, + NULL, 0, /* no private key */ + NULL, 0, /* hash160: computed from pub_key */ + NULL, 0, /* parent160: zeros for root key */ + output); + + wally_clear(compressed_pk, sizeof(compressed_pk)); + return ret; +} + +WALLY_CORE_API int wally_musig_pubkeys_derive_then_agg( + const unsigned char *xpubs, + size_t xpubs_len, + uint32_t child_num, + unsigned char *agg_pk_out, + size_t agg_pk_out_len, + struct wally_musig_keyagg_cache **cache_out) +{ + unsigned char *sorted_pubkeys = NULL; + struct ext_key hdkey, child; + size_t n_xpubs, i; + int ret = WALLY_EINVAL; + + if (!xpubs || xpubs_len < 2 * BIP32_SERIALIZED_LEN || + xpubs_len % BIP32_SERIALIZED_LEN != 0) + return WALLY_EINVAL; + if (child_num >= BIP32_INITIAL_HARDENED_CHILD) + return WALLY_EINVAL; + if (agg_pk_out && agg_pk_out_len != EC_XONLY_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + if (!agg_pk_out && !cache_out) + return WALLY_EINVAL; + + n_xpubs = xpubs_len / BIP32_SERIALIZED_LEN; + + sorted_pubkeys = wally_malloc(n_xpubs * EC_PUBLIC_KEY_LEN); + if (!sorted_pubkeys) + return WALLY_ENOMEM; + + for (i = 0; i < n_xpubs; i++) { + ret = bip32_key_unserialize(xpubs + i * BIP32_SERIALIZED_LEN, + BIP32_SERIALIZED_LEN, &hdkey); + if (ret != WALLY_OK) + goto cleanup; + + ret = bip32_key_from_parent(&hdkey, child_num, + BIP32_FLAG_KEY_PUBLIC, &child); + if (ret != WALLY_OK) + goto cleanup; + + memcpy(sorted_pubkeys + i * EC_PUBLIC_KEY_LEN, + child.pub_key, EC_PUBLIC_KEY_LEN); + } + + qsort(sorted_pubkeys, n_xpubs, EC_PUBLIC_KEY_LEN, musig2_keyagg_pubkey_cmp); + + ret = wally_musig_pubkey_agg(sorted_pubkeys, n_xpubs * EC_PUBLIC_KEY_LEN, + agg_pk_out, agg_pk_out_len, cache_out); + +cleanup: + wally_clear_2(&hdkey, sizeof(hdkey), &child, sizeof(child)); + wally_free(sorted_pubkeys); + return ret; +} + +WALLY_CORE_API int wally_musig_pubkeys_agg_then_derive( + const unsigned char *pub_keys, + size_t pub_keys_len, + uint32_t version, + uint32_t child_num, + unsigned char *pub_key_out, + size_t pub_key_out_len, + struct ext_key **child_out) +{ + unsigned char agg_pk[EC_XONLY_PUBLIC_KEY_LEN]; + struct ext_key *synthetic_xpub = NULL; + struct ext_key *child = NULL; + unsigned char *sorted = NULL; + int ret; + + if (!pub_keys || pub_keys_len < 2 * EC_PUBLIC_KEY_LEN || + pub_keys_len % EC_PUBLIC_KEY_LEN != 0) + return WALLY_EINVAL; + if (version != BIP32_VER_MAIN_PUBLIC && version != BIP32_VER_TEST_PUBLIC) + return WALLY_EINVAL; + if (child_num >= BIP32_INITIAL_HARDENED_CHILD) + return WALLY_EINVAL; + if (!pub_key_out && !child_out) + return WALLY_EINVAL; + if (pub_key_out && pub_key_out_len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + + sorted = wally_malloc(pub_keys_len); + if (!sorted) + return WALLY_ENOMEM; + memcpy(sorted, pub_keys, pub_keys_len); + qsort(sorted, pub_keys_len / EC_PUBLIC_KEY_LEN, EC_PUBLIC_KEY_LEN, musig2_keyagg_pubkey_cmp); + + ret = wally_musig_pubkey_agg(sorted, pub_keys_len, + agg_pk, sizeof(agg_pk), NULL); + if (ret != WALLY_OK) + goto cleanup; + + ret = wally_musig_pubkey_to_xpub(agg_pk, sizeof(agg_pk), version, + &synthetic_xpub); + if (ret != WALLY_OK) + goto cleanup; + + ret = bip32_key_from_parent_alloc(synthetic_xpub, child_num, + BIP32_FLAG_KEY_PUBLIC, &child); + if (ret != WALLY_OK) + goto cleanup; + + if (pub_key_out) + memcpy(pub_key_out, child->pub_key, EC_PUBLIC_KEY_LEN); + + if (child_out) { + *child_out = child; + child = NULL; + } + +cleanup: + if (child) + bip32_key_free(child); + if (synthetic_xpub) + bip32_key_free(synthetic_xpub); + wally_free(sorted); + wally_clear(agg_pk, sizeof(agg_pk)); + return ret; +} + +#endif /* ndef BUILD_STANDARD_SECP */ diff --git a/src/psbt.c b/src/psbt.c index 5806aab88..a56ba0f15 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -1,9 +1,12 @@ #include "internal.h" +#include #include +#include #include #include #include +#include #include #include "psbt_io.h" @@ -11,6 +14,8 @@ #include "script.h" #include "pullpush.h" #include "tx_io.h" +#include "descriptor_int.h" +#include "miniscript_decode.h" /* TODO: * - When setting utxo in an input via the psbt (in the SWIG @@ -31,7 +36,7 @@ #define PSBT_ID_ALL_FLAGS (WALLY_PSBT_ID_AS_V2 | WALLY_PSBT_ID_USE_LOCKTIME) /* All allowed flags for wally_psbt_from_[bytes|base64]() */ -#define PSBT_ALL_PARSE_FLAGS (WALLY_PSBT_PARSE_FLAG_STRICT|WALLY_PSBT_PARSE_FLAG_LOOSE) +#define PSBT_ALL_PARSE_FLAGS (WALLY_PSBT_PARSE_FLAG_STRICT | WALLY_PSBT_PARSE_FLAG_LOOSE) static const uint8_t PSBT_MAGIC[5] = {'p', 's', 'b', 't', 0xff}; static const uint8_t PSET_MAGIC[5] = {'p', 's', 'e', 't', 0xff}; @@ -39,7 +44,7 @@ static const uint8_t PSET_MAGIC[5] = {'p', 's', 'e', 't', 0xff}; #define MAX_INVALID_SATOSHI ((uint64_t) -1) /* Note we mask given indices regardless of PSBT/PSET, since enormous * indices can never be valid on BTC either */ -#define MASK_INDEX(index) ((index) & WALLY_TX_INDEX_MASK) +#define MASK_INDEX(index) ((index)&WALLY_TX_INDEX_MASK) #define TR_MAX_MERKLE_PATH_LEN 128u @@ -118,7 +123,7 @@ static struct wally_psbt_input *psbt_get_input(const struct wally_psbt *psbt, si (psbt->version == PSBT_0 && (!psbt->tx || index >= psbt->tx->num_inputs))) return NULL; return &psbt->inputs[index]; - } +} static struct wally_psbt_output *psbt_get_output(const struct wally_psbt *psbt, size_t index) { @@ -148,7 +153,7 @@ static const struct wally_tx_output *utxo_from_input(const struct wally_psbt *ps if ((!psbt || psbt->version == PSBT_2)) { if (input->index < input->utxo->num_outputs && !mem_is_zero(input->txhash, WALLY_TXHASH_LEN)) - return &input->utxo->outputs[input->index]; + return &input->utxo->outputs[input->index]; } } } @@ -185,77 +190,77 @@ int wally_psbt_get_input_signature_type(const struct wally_psbt *psbt, /* Set a struct member on a parent struct */ #define SET_STRUCT(PARENT, NAME, STRUCT_TYPE, CLONE_FN, FREE_FN) \ - int PARENT ## _set_ ## NAME(struct PARENT *parent, const struct STRUCT_TYPE *p) { \ - int ret = WALLY_OK; \ - struct STRUCT_TYPE *new_p = NULL; \ - if (!parent) return WALLY_EINVAL; \ - if (p && (ret = CLONE_FN(p, &new_p)) != WALLY_OK) return ret; \ - FREE_FN(parent->NAME); \ - parent->NAME = new_p; \ - return ret; \ - } + int PARENT ## _set_ ## NAME(struct PARENT *parent, const struct STRUCT_TYPE *p) { \ + int ret = WALLY_OK; \ + struct STRUCT_TYPE *new_p = NULL; \ + if (!parent) return WALLY_EINVAL; \ + if (p && (ret = CLONE_FN(p, &new_p)) != WALLY_OK) return ret; \ + FREE_FN(parent->NAME); \ + parent->NAME = new_p; \ + return ret; \ + } #ifdef BUILD_ELEMENTS #define SET_STRUCT_PSET(PARENT, NAME, STRUCT_TYPE, CLONE_FN, FREE_FN) SET_STRUCT(PARENT, NAME, STRUCT_TYPE, CLONE_FN, FREE_FN) #else #define SET_STRUCT_PSET(PARENT, NAME, STRUCT_TYPE, CLONE_FN, FREE_FN) \ - int PARENT ## _set_ ## NAME(struct PARENT *parent, const struct STRUCT_TYPE *p) { \ - return WALLY_ERROR; \ - } + int PARENT ## _set_ ## NAME(struct PARENT *parent, const struct STRUCT_TYPE *p) { \ + return WALLY_ERROR; \ + } #endif /* BUILD_ELEMENTS */ /* Set/find in and add a map value member on a parent struct */ #define SET_MAP(PARENT, NAME, ADD_POST) \ - int PARENT ## _set_ ## NAME ## s(struct PARENT *parent, const struct wally_map *map_in) { \ - if (!parent) return WALLY_EINVAL; \ - return wally_map_assign(&parent->NAME ## s, map_in); \ - } \ - int PARENT ## _find_ ## NAME(const struct PARENT *parent, \ - const unsigned char *key, size_t key_len, \ - size_t *written) { \ - if (written) *written = 0; \ - if (!parent) return WALLY_EINVAL; \ - return wally_map_find(&parent->NAME ## s, key, key_len, written); \ - } \ - int PARENT ## _add_ ## NAME ## ADD_POST(struct PARENT *parent, \ - const unsigned char *key, size_t key_len, \ - const unsigned char *value, size_t value_len) { \ - if (!parent) return WALLY_EINVAL; \ - return wally_map_add(&parent->NAME ## s, key, key_len, value, value_len); \ - } + int PARENT ## _set_ ## NAME ## s(struct PARENT *parent, const struct wally_map *map_in) { \ + if (!parent) return WALLY_EINVAL; \ + return wally_map_assign(&parent->NAME ## s, map_in); \ + } \ + int PARENT ## _find_ ## NAME(const struct PARENT *parent, \ + const unsigned char *key, size_t key_len, \ + size_t *written) { \ + if (written) *written = 0; \ + if (!parent) return WALLY_EINVAL; \ + return wally_map_find(&parent->NAME ## s, key, key_len, written); \ + } \ + int PARENT ## _add_ ## NAME ## ADD_POST(struct PARENT *parent, \ + const unsigned char *key, size_t key_len, \ + const unsigned char *value, size_t value_len) { \ + if (!parent) return WALLY_EINVAL; \ + return wally_map_add(&parent->NAME ## s, key, key_len, value, value_len); \ + } /* Add a keypath to parent structs keypaths member */ #define ADD_KEYPATH(PARENT) \ - int PARENT ## _keypath_add(struct PARENT *parent, \ - const unsigned char *pub_key, size_t pub_key_len, \ - const unsigned char *fingerprint, size_t fingerprint_len, \ - const uint32_t *child_path, size_t child_path_len) { \ - if (!parent) return WALLY_EINVAL; \ - return wally_map_keypath_add(&parent->keypaths, pub_key, pub_key_len, \ - fingerprint, fingerprint_len, \ - child_path, child_path_len); \ - } + int PARENT ## _keypath_add(struct PARENT *parent, \ + const unsigned char *pub_key, size_t pub_key_len, \ + const unsigned char *fingerprint, size_t fingerprint_len, \ + const uint32_t * child_path, size_t child_path_len) { \ + if (!parent) return WALLY_EINVAL; \ + return wally_map_keypath_add(&parent->keypaths, pub_key, pub_key_len, \ + fingerprint, fingerprint_len, \ + child_path, child_path_len); \ + } /* Add a taproot keypath to parent structs keypaths member */ #define ADD_TAP_KEYPATH(PARENT) \ - int PARENT ## _taproot_keypath_add(struct PARENT *parent, \ - const unsigned char *pub_key, size_t pub_key_len, \ - const unsigned char *tapleaf_hashes, size_t tapleaf_hashes_len, \ - const unsigned char *fingerprint, size_t fingerprint_len, \ - const uint32_t *child_path, size_t child_path_len) { \ - int ret; \ - if (!parent) return WALLY_EINVAL; \ - ret = wally_merkle_path_xonly_public_key_verify(pub_key, pub_key_len, tapleaf_hashes, tapleaf_hashes_len); \ - if (ret == WALLY_OK) \ + int PARENT ## _taproot_keypath_add(struct PARENT *parent, \ + const unsigned char *pub_key, size_t pub_key_len, \ + const unsigned char *tapleaf_hashes, size_t tapleaf_hashes_len, \ + const unsigned char *fingerprint, size_t fingerprint_len, \ + const uint32_t * child_path, size_t child_path_len) { \ + int ret; \ + if (!parent) return WALLY_EINVAL; \ + ret = wally_merkle_path_xonly_public_key_verify(pub_key, pub_key_len, tapleaf_hashes, tapleaf_hashes_len); \ + if (ret == WALLY_OK) \ ret = wally_map_keypath_add(&parent->taproot_leaf_paths, \ - pub_key, pub_key_len, \ - fingerprint, fingerprint_len, \ - child_path, child_path_len); \ - if (ret == WALLY_OK) \ + pub_key, pub_key_len, \ + fingerprint, fingerprint_len, \ + child_path, child_path_len); \ + if (ret == WALLY_OK) \ ret = wally_map_merkle_path_add(&parent->taproot_leaf_hashes, \ pub_key, pub_key_len, \ tapleaf_hashes, tapleaf_hashes_len); \ - return ret; \ - } + return ret; \ + } static int map_field_get_len(const struct wally_map *map_in, uint32_t type, size_t *written) @@ -308,41 +313,41 @@ static int map_field_set(struct wally_map *map_in, uint32_t type, /* Methods for a binary buffer field from a PSBT input/output */ #define MAP_INNER_FIELD(typ, name, FT, mapname) \ - int wally_psbt_ ## typ ## _get_ ## name ## _len(const struct wally_psbt_ ## typ *p, \ - size_t * written) { \ - return map_field_get_len(p ? &p->mapname : NULL, FT, written); \ - } \ - int wally_psbt_ ## typ ## _get_ ## name(const struct wally_psbt_ ## typ *p, \ - unsigned char *bytes_out, size_t len, size_t * written) { \ - return map_field_get(p ? &p->mapname : NULL, FT, bytes_out, len, written); \ - } \ - int wally_psbt_ ## typ ## _clear_ ## name(struct wally_psbt_ ## typ *p) { \ - return wally_map_remove_integer(p ? &p->mapname : NULL, FT); \ - } \ - int wally_psbt_ ## typ ## _set_ ## name(struct wally_psbt_ ## typ *p, \ - const unsigned char *value, size_t value_len) { \ - return map_field_set(p ? &p->mapname : NULL, FT, value, value_len); \ - } + int wally_psbt_ ## typ ## _get_ ## name ## _len(const struct wally_psbt_ ## typ *p, \ + size_t *written) { \ + return map_field_get_len(p ? &p->mapname : NULL, FT, written); \ + } \ + int wally_psbt_ ## typ ## _get_ ## name(const struct wally_psbt_ ## typ *p, \ + unsigned char *bytes_out, size_t len, size_t *written) { \ + return map_field_get(p ? &p->mapname : NULL, FT, bytes_out, len, written); \ + } \ + int wally_psbt_ ## typ ## _clear_ ## name(struct wally_psbt_ ## typ *p) { \ + return wally_map_remove_integer(p ? &p->mapname : NULL, FT); \ + } \ + int wally_psbt_ ## typ ## _set_ ## name(struct wally_psbt_ ## typ *p, \ + const unsigned char *value, size_t value_len) { \ + return map_field_set(p ? &p->mapname : NULL, FT, value, value_len); \ + } #ifdef BUILD_ELEMENTS #define MAP_INNER_FIELD_PSET(typ, name, FT) MAP_INNER_FIELD(typ, name, FT, pset_fields) #else #define MAP_INNER_FIELD_PSET(typ, name, FT) \ - int wally_psbt_ ## typ ## _get_ ## name ## _len(const struct wally_psbt_ ## typ *p, \ - size_t * written) { \ - return WALLY_ERROR; \ - } \ - int wally_psbt_ ## typ ## _get_ ## name(const struct wally_psbt_ ## typ *p, \ - unsigned char *bytes_out, size_t len, size_t * written) { \ - return WALLY_ERROR; \ - } \ - int wally_psbt_ ## typ ## _clear_ ## name(struct wally_psbt_ ## typ *p) { \ - return WALLY_ERROR; \ - } \ - int wally_psbt_ ## typ ## _set_ ## name(struct wally_psbt_ ## typ *p, \ - const unsigned char *value, size_t value_len) { \ - return WALLY_ERROR; \ - } + int wally_psbt_ ## typ ## _get_ ## name ## _len(const struct wally_psbt_ ## typ *p, \ + size_t *written) { \ + return WALLY_ERROR; \ + } \ + int wally_psbt_ ## typ ## _get_ ## name(const struct wally_psbt_ ## typ *p, \ + unsigned char *bytes_out, size_t len, size_t *written) { \ + return WALLY_ERROR; \ + } \ + int wally_psbt_ ## typ ## _clear_ ## name(struct wally_psbt_ ## typ *p) { \ + return WALLY_ERROR; \ + } \ + int wally_psbt_ ## typ ## _set_ ## name(struct wally_psbt_ ## typ *p, \ + const unsigned char *value, size_t value_len) { \ + return WALLY_ERROR; \ + } #endif /* BUILD_ELEMENTS */ int wally_psbt_input_is_finalized(const struct wally_psbt_input *input, @@ -394,6 +399,149 @@ SET_STRUCT(wally_psbt_input, final_witness, wally_tx_witness_stack, SET_MAP(wally_psbt_input, keypath,) ADD_KEYPATH(wally_psbt_input) ADD_TAP_KEYPATH(wally_psbt_input) +SET_MAP(wally_psbt_input, musig2_pubkey,) +int wally_psbt_input_add_musig2_participant_pubkeys(struct wally_psbt_input *input, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *participants, + size_t participants_len) +{ + if (!input || !agg_pubkey || agg_pubkey_len != EC_PUBLIC_KEY_LEN || + !participants || participants_len < EC_PUBLIC_KEY_LEN * 2 || + participants_len % EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + return wally_map_replace(&input->musig2_pubkeys, + agg_pubkey, agg_pubkey_len, + participants, participants_len); +} +static int musig2_composite_key_build(const unsigned char *participant, + const unsigned char *agg_pubkey, + const unsigned char *leaf_hash, + unsigned char *key_out, size_t *key_len_out) +{ + size_t key_len = EC_PUBLIC_KEY_LEN * 2 + (leaf_hash ? SHA256_LEN : 0); + memcpy(key_out, participant, EC_PUBLIC_KEY_LEN); + memcpy(key_out + EC_PUBLIC_KEY_LEN, agg_pubkey, EC_PUBLIC_KEY_LEN); + if (leaf_hash) + memcpy(key_out + EC_PUBLIC_KEY_LEN * 2, leaf_hash, SHA256_LEN); + *key_len_out = key_len; + return WALLY_OK; +} + +int wally_psbt_input_add_musig2_pubnonce(struct wally_psbt_input *input, + const unsigned char *participant, + size_t participant_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + const unsigned char *pubnonce, + size_t pubnonce_len) +{ + unsigned char key[EC_PUBLIC_KEY_LEN * 2 + SHA256_LEN]; + size_t key_len; + + if (!input || + !participant || participant_len != EC_PUBLIC_KEY_LEN || + !agg_pubkey || agg_pubkey_len != EC_PUBLIC_KEY_LEN || + (leaf_hash && leaf_hash_len != SHA256_LEN) || + (!leaf_hash && leaf_hash_len != 0) || + !pubnonce || pubnonce_len != WALLY_MUSIG_PUBNONCE_LEN) + return WALLY_EINVAL; + + musig2_composite_key_build(participant, agg_pubkey, leaf_hash, key, &key_len); + return wally_map_replace(&input->musig2_pubnonces, key, key_len, pubnonce, pubnonce_len); +} + +int wally_psbt_input_find_musig2_pubnonce(const struct wally_psbt_input *input, + const unsigned char *participant, + size_t participant_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + size_t *written) +{ + unsigned char key[EC_PUBLIC_KEY_LEN * 2 + SHA256_LEN]; + size_t key_len; + + if (!input || !written || + !participant || participant_len != EC_PUBLIC_KEY_LEN || + !agg_pubkey || agg_pubkey_len != EC_PUBLIC_KEY_LEN || + (leaf_hash && leaf_hash_len != SHA256_LEN) || + (!leaf_hash && leaf_hash_len != 0)) + return WALLY_EINVAL; + + musig2_composite_key_build(participant, agg_pubkey, leaf_hash, key, &key_len); + return wally_map_find(&input->musig2_pubnonces, key, key_len, written); +} + +int wally_psbt_input_get_musig2_pubnonce_count(const struct wally_psbt_input *input, + size_t *written) +{ + if (!input || !written) + return WALLY_EINVAL; + *written = input->musig2_pubnonces.num_items; + return WALLY_OK; +} + +int wally_psbt_input_add_musig2_partial_sig(struct wally_psbt_input *input, + const unsigned char *participant, + size_t participant_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + const unsigned char *partial_sig, + size_t partial_sig_len) +{ + unsigned char key[EC_PUBLIC_KEY_LEN * 2 + SHA256_LEN]; + size_t key_len; + + if (!input || + !participant || participant_len != EC_PUBLIC_KEY_LEN || + !agg_pubkey || agg_pubkey_len != EC_PUBLIC_KEY_LEN || + (leaf_hash && leaf_hash_len != SHA256_LEN) || + (!leaf_hash && leaf_hash_len != 0) || + !partial_sig || partial_sig_len != WALLY_MUSIG_PARTIAL_SIG_LEN) + return WALLY_EINVAL; + + musig2_composite_key_build(participant, agg_pubkey, leaf_hash, key, &key_len); + return wally_map_replace(&input->musig2_partial_sigs, key, key_len, partial_sig, partial_sig_len); +} + +int wally_psbt_input_find_musig2_partial_sig(const struct wally_psbt_input *input, + const unsigned char *participant, + size_t participant_len, + const unsigned char *agg_pubkey, + size_t agg_pubkey_len, + const unsigned char *leaf_hash, + size_t leaf_hash_len, + size_t *written) +{ + unsigned char key[EC_PUBLIC_KEY_LEN * 2 + SHA256_LEN]; + size_t key_len; + + if (!input || !written || + !participant || participant_len != EC_PUBLIC_KEY_LEN || + !agg_pubkey || agg_pubkey_len != EC_PUBLIC_KEY_LEN || + (leaf_hash && leaf_hash_len != SHA256_LEN) || + (!leaf_hash && leaf_hash_len != 0)) + return WALLY_EINVAL; + + musig2_composite_key_build(participant, agg_pubkey, leaf_hash, key, &key_len); + return wally_map_find(&input->musig2_partial_sigs, key, key_len, written); +} + +int wally_psbt_input_get_musig2_partial_sig_count(const struct wally_psbt_input *input, + size_t *written) +{ + if (!input || !written) + return WALLY_EINVAL; + *written = input->musig2_partial_sigs.num_items; + return WALLY_OK; +} + SET_MAP(wally_psbt_input, signature, _internal) int wally_psbt_input_add_signature(struct wally_psbt_input *input, const unsigned char *pub_key, size_t pub_key_len, @@ -529,6 +677,73 @@ static int map_leaf_hashes_verify(const unsigned char *key, size_t key_len, return ret; } +/* BIP-371 PSBT_IN_TAP_SCRIPT_SIG: key = x-only pubkey(32) + leaf hash(32), + * value = 64 or 65 byte BIP-340 Schnorr signature. */ +static int taproot_leaf_signature_verify(const unsigned char *key, size_t key_len, + const unsigned char *val, size_t val_len) +{ + if (!key || key_len != EC_XONLY_PUBLIC_KEY_LEN + SHA256_LEN) + return WALLY_EINVAL; + if (wally_ec_xonly_public_key_verify(key, EC_XONLY_PUBLIC_KEY_LEN) != WALLY_OK) + return WALLY_EINVAL; + if (!val || (val_len != EC_SIGNATURE_LEN && val_len != EC_SIGNATURE_LEN + 1)) + return WALLY_EINVAL; + return WALLY_OK; +} + +/* BIP-371 PSBT_IN_TAP_LEAF_SCRIPT: key = BIP-341 control block, value = the + * tapscript (the leaf version is taken from the control block, see BIP-341). */ +static int taproot_leaf_script_verify(const unsigned char *key, size_t key_len, + const unsigned char *val, size_t val_len) +{ + if (wally_bip341_control_block_verify(key, key_len) != WALLY_OK) + return WALLY_EINVAL; + if (!val || !val_len) + return WALLY_EINVAL; + return WALLY_OK; +} + +/* BIP-371 PSBT_OUT_TAP_TREE value: a depth-first sequence of + * <8-bit depth> <8-bit leaf version>