Skip to content

tree: fix mock signer nonce handling in signing tests - #1090

Merged
darioAnongba merged 2 commits into
mainfrom
darioAnongba/tree-musig2-mock-nonces
Aug 5, 2026
Merged

tree: fix mock signer nonce handling in signing tests#1090
darioAnongba merged 2 commits into
mainfrom
darioAnongba/tree-musig2-mock-nonces

Conversation

@darioAnongba

@darioAnongba darioAnongba commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

In this PR, we fix two MuSig2 nonce handling bugs in the mockMuSig2Signer that backs the lib/tree signing tests, and extend the suite so the full ceremony is actually verified end to end.

The mock had two problems that stayed invisible in the existing suite. MuSig2CreateSession advertised a freshly generated nonce in the session info, but created the underlying session via ctx.NewSession(), which generates its own internal pair, so peers aggregated a nonce the session never signed with. And MuSig2RegisterCombinedNonce fed the pre-aggregated nonce through RegisterPubNonce, which treats the aggregate as one peer's nonce: the session's combined nonce became ownNonce + aggregate instead of the aggregate, and haveAll only flipped for 2-signer sessions. btcec v2.5.0 has the right primitive for this coordinator-style flow, Session.RegisterCombinedNonce, and the mock now uses it along with musig2.WithPreGeneratedNonce at session creation.

Why the suite never noticed

Every ceremony in the lib/tree tests is 2-of-2, so the miscounted registration still flipped haveAll, and no test ever combined partial signatures across participants: TestFullSigningFlow stopped at "partial signature is non-nil", which a signature over a wrong combined nonce satisfies just as well as a correct one. The pattern only blew up when the asset-tree e2e in #1084 ran a real multi-party ceremony against an in-process signer copied from this mock and hit "not all nonces registered".

To keep the mock honest going forward, we extend both TestFullSigningFlow subtests to combine the two parties' partial signatures for every transaction and verify the final Schnorr signature against the node's final taproot key. Both subtests fail against the old mock with ErrFinalSigInvalid and pass with the fix.

The real production path is covered by the lumos itests (real client daemon against the in-process operator), so this is a test-only change: the value is a correct in-repo reference for input.MuSig2Signer behavior and a fast local signal for lib/tree signing regressions. The second commit ports the same fix to round/harness_test.go's realMuSig2Signer, which was copied from this mock. No round test outcome changes: the error-path test that deliberately skips nonce registration still gets its expected "not all nonces registered" failure, and the harness's real combines already went through raw btcd sessions.

In this commit, we fix two MuSig2 nonce handling bugs in the
mockMuSig2Signer used by the lib/tree signing tests.

First, MuSig2CreateSession advertised a freshly generated nonce in the
session info but created the underlying musig2 session via
ctx.NewSession(), which generates its own internal nonce pair. The
nonce peers aggregated was never the nonce the session signed with. We
now pass the advertised nonces into the session via
musig2.WithPreGeneratedNonce.

Second, MuSig2RegisterCombinedNonce registered the pre-aggregated
nonce through RegisterPubNonce, which treats the aggregate as a single
peer nonce. The session's combined nonce then became ownNonce +
aggregate instead of the aggregate, and haveAll only flipped for
2-signer sessions. We now use the RegisterCombinedNonce primitive that
btcec v2.5.0 provides for exactly this coordinator-style flow.

These bugs were invisible because every ceremony in the suite is
2-of-2 and no test ever combined partial signatures across
participants: a partial signature over a wrong combined nonce looks
identical to a correct one until the aggregate is verified. We close
that gap by extending TestFullSigningFlow to combine both parties'
partial signatures per transaction and verify the final Schnorr
signature against the node's final taproot key. Both subtests fail
against the old mock with ErrFinalSigInvalid and pass with the fix.
@darioAnongba darioAnongba self-assigned this Aug 4, 2026
In this commit, we port the lib/tree mock signer fixes to the round
test harness's realMuSig2Signer, which was copied from the same broken
pattern: MuSig2CreateSession created its musig2 session without
WithPreGeneratedNonce, so the advertised nonce was never the one the
session signed with, and MuSig2RegisterCombinedNonce fed the aggregate
through RegisterPubNonce, corrupting the session's combined nonce and
only flipping haveAll for 2-signer sessions.

No round test currently drives a full combine through this signer: the
error-path test (newUnaggregatedSignerSession) still gets its expected
"not all nonces registered" failure since allNoncesKnown starts
false, and generateValidTreeSignatures builds raw btcd sessions
directly for real combines. The fix is about keeping the harness a
correct reference for input.MuSig2Signer behavior rather than changing
any current test outcome.
@darioAnongba
darioAnongba merged commit 5ac2f69 into main Aug 5, 2026
17 of 19 checks passed
@darioAnongba
darioAnongba deleted the darioAnongba/tree-musig2-mock-nonces branch August 5, 2026 10:20
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.

1 participant