Skip to content

Tapscript & MuSig2 support, with a miniscript decoder and satisfier#535

Draft
odudex wants to merge 9 commits into
ElementsProject:masterfrom
odudex:tapscript-pr
Draft

Tapscript & MuSig2 support, with a miniscript decoder and satisfier#535
odudex wants to merge 9 commits into
ElementsProject:masterfrom
odudex:tapscript-pr

Conversation

@odudex

@odudex odudex commented Jun 30, 2026

Copy link
Copy Markdown

This PR adds tapscript (taproot script-path) descriptors, a Script→miniscript decoder
and a witness satisfier, MuSig2 (BIP-327) signing, and the descriptor / PSBT plumbing to
use them end-to-end.

The bulk of this work was authored by @pythcoiner. My own contributions are some bug fixes,
hardening, the amalgamation build integration folded into the relevant commits and a rebase proposed in #533 .

Goals

  • Tapscript descriptorstr() script-path spending: taptree parsing,
    multi_a/sortedmulti_a fragments, BIP-341 merkle-root/leaf hashing, and the
    tree/leaf/control-block accessor APIs.
  • Miniscript — a Script→miniscript decoder and a non-malleable, minimum-weight
    witness satisfier (covering tapscript).
  • MuSig2 (BIP-327) — key aggregation, nonce handling, the full signing pipeline,
    and BIP-328 synthetic-xpub helpers over the secp256k1-zkp musig module.
  • musig() descriptors (BIP-390)tr(musig(...)) parsing, address derivation,
    and participant introspection.
  • PSBT — BIP-371 (taproot) and BIP-373 (PSBT MuSig2) fields, signing, and
    finalization, wired up from descriptors.
  • Keep all of the above available in the single-file amalgamation build, gated so
    consumers building with -DBUILD_STANDARD_SECP (MuSig2 compiled out) still build clean.

Review structure

The original development history (100+ commits) has been reorganized into 9
self-contained commits
, each a coherent milestone with its own tests. The intent is
to make this reviewable and mergeable gradually. Each commit builds, passes its
tests, and can be assessed (or landed) on its own rather than as one monolithic diff.

# Commit What it adds
1 descriptor: add taproot (tapscript) descriptor support tr() script paths: multi_a/sortedmulti_a, taptree parsing & BIP-341 leaf/merkle hashing, script-path address derivation, tree/leaf/control-block accessors
2 miniscript: add Script-to-miniscript decoder Standalone tokenizer + recursive-descent decoder for all fragments, with stack-overflow / non-minimal-push hardening; BIP-379 differential vectors
3 miniscript: add miniscript satisfier Per-fragment non-malleable minimum-weight satisfaction/dissatisfaction with overflow-saturated weights and bounded stack usage
4 musig2: add MuSig2 (BIP-327) core API Key aggregation, nonce gen/agg, signing pipeline, opaque type lifecycle, BIP-328 helpers, bindings; non-aborting handling of malformed cache/session; BIP-327/328 vectors
5 descriptor: add musig() key expressions (BIP-390) musig() as a taproot internal/leaf key, tr(musig(...)) derivation, participant introspection API; BIP-390 vectors
6 psbt: add taproot (BIP-371) and MuSig2 (BIP-373) support Taproot + MuSig2 PSBT fields, script-path sighash & signing, finalizers, descriptor-based population; BIP-373 interop tests
7 fuzz: add fuzzers for musig() descriptor and PSBT MuSig2 fields PSBT MuSig2 fuzzer, musig() descriptor fuzz cases + seed corpus
8 docs: document the MuSig2 API and add a 2-of-2 PSBT example MuSig2 API reference, docs index, CHANGES.md, runnable 2-of-2 MuSig2 PSBT example
9 build: add musig and miniscript sources to the amalgamation Includes the new sources in the single-file build, gated on BUILD_STANDARD_SECP

BIPs implemented

BIP-327 (MuSig2), BIP-328 (xpub for aggregate keys), BIP-341 (Taproot), BIP-371
(PSBT Taproot fields), BIP-373 (PSBT MuSig2 fields), BIP-379 (Miniscript),
BIP-390 (musig() descriptors).

Tests

This branch was integrated in Kern project, where tapscript is under tests.

Musig2 support is not currently being tested in a consumer project. I believe @pythcoiner is baking some ideas and can share a north for practical tests.

@odudex odudex marked this pull request as draft July 2, 2026 19:40
@odudex

odudex commented Jul 2, 2026

Copy link
Copy Markdown
Author

@jgriffiths I changed the PR back to draft after spotting a few issues and opportunities for improvement.

Could you let me know when you're about to start reviewing it? That way I can keep refining the implementation and improving the PR's conciseness and digestibility through small iterations, amending changes into the feature commit as I go.

Once you start the review, I'll stop rewriting the commit(s) you're reviewing and propose any further changes as follow-up commits instead.

pythcoiner and others added 3 commits July 2, 2026 17:22
Adds tr() descriptors with script paths: the WALLY_LEAF_VERSION_TAPSCRIPT
constant, multi_a/sortedmulti_a tapscript fragments, taptree parsing with
BIP-341 merkle-root/leaf hashing, tr() address derivation including the
key-path tweak, and the taproot tree/leaf/control-block/key-enumeration
accessor APIs. Includes the C and Python descriptor test suites.

Co-authored-by: odudex <odudex@proton.me>
Adds a standalone miniscript decoder (miniscript_decode.{c,h}): a Script
tokenizer and recursive-descent decoder for all miniscript fragments
(pk_k/pk_h, hashes, timelocks, multi/multi_a, and_v/and_b, or_b/c/d/i,
andor, thresh, and the wrapper fragments), with stack-overflow and
non-minimal-push hardening. Relocates ms_node and the shared KIND_*
constants into descriptor_int.h so the new translation unit can share
them. Includes the C decoder tests and the BIP-379 differential vectors.

Also tightens the string parser's verify_or_b/verify_or_c to require
their canonical child types (TYPE_B/TYPE_V), matching the decoder and
the BIP-379 reference vectors.

Co-authored-by: odudex <odudex@proton.me>
Adds the witness satisfier (miniscript_satisfy.c): per-fragment
satisfaction/dissatisfaction with non-malleable minimum-weight selection
(satisfaction_best, or_b/c/d/i, andor, thresh with a DP sort) and a
satisfy_node dispatch over the decoded tree, plus the ms_witness /
ms_satisfaction structures and lifecycle helpers. Includes witness_weight
overflow saturation, iterative tree traversal to bound stack usage, and
the geometric-growth / move-not-copy / precomputed-weight optimizations.
Exercised directly by the C satisfy tests.

Co-authored-by: odudex <odudex@proton.me>
@jgriffiths

Copy link
Copy Markdown
Contributor

Hi @odudex now that the PR is composed of a few larger commits, I will be starting with the first commit and reviewing each one essentially independently. This means I will take that commit on its own into a local branch and review/change as needed, without considering the context of future commits (each commit needs to pass independent review and will be updated as needed without considering the potential fallout on following commits).

At the earliest I will start with the first commit at the middle to end of next week. If it makes things easier, I can comment here when I have begun assessing each commit? If you want to merge back a change to a commit later than that one, then you can feel free to do so until the point that I comment here that I have started on it, at which point feel free to push new commits with a comment that it should be merged back to a given commit. For such mergeback commits I will manually make the change(s) needed while reviewing and it can be dropped when the commit in question is merged.

Please let me know if the above approach isn't clear or doesn't work for you. In the meantime until I comment here that I have started, please feel free to merge back/force push changes at will, thanks.

@odudex

odudex commented Jul 2, 2026

Copy link
Copy Markdown
Author

Please let me know if the above approach isn't clear or doesn't work for you. In the meantime until I comment here that I have started, please feel free to merge back/force push changes at will, thanks.

Perfect! I'll keep improving and force-pushing, prioritizing 1st commit, until you start reviewing it. Then I'll wait your feedback and adjust course accordingly.

pythcoiner and others added 6 commits July 2, 2026 20:19
Adds the MuSig2 implementation (musig.c) over the secp256k1-zkp musig
module: key aggregation, nonce generation and aggregation, the signing
pipeline (nonce_process, partial_sign, verify, aggregate), and lifecycle
helpers for the opaque keyagg-cache / nonce / session / partial-sig types,
plus BIP-328 synthetic-xpub helpers and the language bindings. Enables the
secp256k1 musig module in the build and routes a malformed parsed
keyagg-cache/session through a non-aborting illegal-argument callback.
Includes the BIP-327/328 vector and protocol test suites.

The bindings are regenerated from the headers via tools/build_wrappers.py
(also picking up wrappers for the earlier descriptor taproot APIs). The
Python SWIG wrapper gains the opaque output-parameter typemaps that the
Java wrapper already had, and a contrib test covering the full signing
flow.

Co-authored-by: odudex <odudex@proton.me>
Adds the musig() key expression to the descriptor parser (valid only as a
taproot internal key or tapscript leaf key), a format_key_node helper for
serializing key nodes, and the musig() introspection API (participant
count and per-participant keys). This enables parsing and address
derivation for tr(musig(...)) descriptors. Includes the musig() descriptor
parsing / address-generation tests and the BIP-390 vectors.
Adds PSBT support for the new descriptor types. BIP-371 taproot: internal
key, leaf scripts, merkle root and tap bip32 derivation fields; script-path
sighash, signing and descriptor-based taproot population; and the finalizers
(p2wsh, p2tr key/script path, multisig). BIP-373 MuSig2: participant pubkey,
public-nonce and partial-sig fields with nonce generation, partial signing,
aggregation and finalization, plus wally_psbt_populate_musig2_from_descriptor.
Includes the PSBT, MuSig2-PSBT, BIP-373 interop and satisfier differential
test suites.

Co-authored-by: odudex <odudex@proton.me>
Adds fuzz_descriptor (descriptor-string parse, script/address generation
and key introspection, network chosen from the input) and
fuzz_psbt_musig2 (splices fuzz bytes into BIP-373 MuSig2 key-value pairs
in a minimal PSBT frame, covering both the key-path and script-path
compound key forms), with seed corpora for both. The PSBT parse fuzzer
now also runs wally_psbt_finalize, reaching the script-to-miniscript
decoder and satisfier with attacker-controlled scripts.

Co-authored-by: odudex <odudex@proton.me>
Registers the musig module in the docs index and adds
contrib/musig2_psbt_2of2.py, a complete runnable 2-of-2 key-path
example: aggregation, both nonce/signing rounds via the PSBT MuSig2
helpers, finalization and BIP-340 verification against the tweaked
output key. Adds the 1.5.5 changelog entries for the taproot,
miniscript and MuSig2 additions.

Co-authored-by: odudex <odudex@proton.me>
Includes musig.c and the miniscript decoder/satisfier in the single-file
amalgamation build, gated on BUILD_STANDARD_SECP for the MuSig2 module.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants