lnwallet: add secret nonce stashing to MusigSession for test vectors
What changed, and why it matters
This commit adds a test-only feature that lets LND temporarily save the secret random number (nonce) used in MuSig2 signing when running in a special test-vector generation mode. The saved value is automatically erased after one read to prevent accidental reuse. There is no indication this feature is active in normal production code, and the change is explicitly described as being for interoperability test vectors only.
Verify that customNonceRand can only be supplied by test code and is never reachable from RPC, wallet, or mainnet signing flows. Confirm that the consume-on-read clearing cannot be bypassed or observed by concurrent callers. No immediate security patch appears necessary, but treat any future expansion of this accessor as requiring careful review.
Security signals we found
Secret nonce is exposed through a new accessor, but only in test-vector mode gated by customNonceRand
Nonce is cleared after read to mitigate reuse risk
Change is explicitly framed by the commit message as test-only infrastructure for interoperability test vectors
No production signing path appears to enable customNonceRand based on the diff alone
Evidence from the diff
The patch extends MusigSession with lastSecNonce, an optional field that stores the raw 97-byte musig2.SecNonce when customNonceRand is set. customNonceRand is already documented as a test-only option for deterministic, reproducible nonces. The new lastSigningSecNonce() accessor returns the nonce and clears it immediately (consume-on-read). The change is gated behind customNonceRand.WhenSome, so it should only affect test code that opts into deterministic nonce generation.
Changed components
lnwallet/musig_session.goMusigSession structSignCommit methodnew lastSigningSecNonce methodInspect captured patch +24 / −0
diff --git a/lnwallet/musig_session.go b/lnwallet/musig_session.go
index 4c4c1a0..8da961f 100644
--- a/lnwallet/musig_session.go
+++ b/lnwallet/musig_session.go
@@ -242,6 +242,12 @@ type MusigSession struct {
// deterministic JIT signing nonces. This should only be set in tests
// that need reproducible MuSig2 signatures.
customNonceRand fn.Option[io.Reader]
+
+ // lastSecNonce holds the secret nonce from the most recent JIT nonce
+ // generation. This is only populated when customNonceRand is set
+ // (test vector generation), allowing test code to extract the raw
+ // 97-byte secret nonces for inclusion in interop test vectors.
+ lastSecNonce fn.Option[[musig2.SecNonceSize]byte]
}
// NewPartialMusigSession creates a new musig2 session given only the
@@ -372,6 +378,14 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
if err != nil {
return nil, err
}
+
+ // When using deterministic nonce generation (test vector
+ // mode), stash the secret nonce so it can be extracted
+ // for inclusion in interop test vectors.
+ m.customNonceRand.WhenSome(func(_ io.Reader) {
+ m.lastSecNonce = fn.Some(signingNonce.SecNonce)
+ })
+
if err := m.FinalizeSession(*signingNonce); err != nil {
return nil, err
}
@@ -432,6 +446,16 @@ func (m *MusigSession) VerificationNonce() *musig2.Nonces {
return &m.nonces.VerificationNonce
}
+// lastSigningSecNonce returns the secret nonce from the most recent JIT nonce
+// generation, if available. This is only populated when customNonceRand is set
+// (test vector generation mode). The value is cleared after being read to
+// prevent accidental nonce reuse.
+func (m *MusigSession) lastSigningSecNonce() fn.Option[[musig2.SecNonceSize]byte] {
+ nonce := m.lastSecNonce
+ m.lastSecNonce = fn.None[[musig2.SecNonceSize]byte]()
+ return nonce
+}
+
// musigSessionOpts is a set of options that can be used to modify calls to the
// musig session.
type musigSessionOpts struct {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.