nonce-fn-defense-in-depth-inputs

Tue Apr 14 2026

Why this note exists Triggered by a review comment on bitcoin/bips#2070 suggesting XonlyPk instead of PlainPk for thresh_pk inside nonce_gen. The interesting part isn't the choice itself — it's the framing that makes the choice obvious.

The underlying concept: hedged (synthetic) nonces

Schnorr nonce kk must be unpredictable and never reused across different messages with the same key — reuse leaks the secret. Three strategies:

  1. Pure random: k$k \gets \text{\textdollar}. Catastrophic if RNG fails.
  2. Pure deterministic (RFC 6979): k=PRF(sk,m)k = \text{PRF}(sk, m). Catastrophic under fault attacks; and in multi-party DL signatures (MuSig/FROST) it enables key-recovery attacks by a malicious co-signer who replays the session with a different message — this is why the BIP explicitly forbids fully deterministic nonces.
  3. Hedged/synthetic (BIP340, BIP327, Ed25519-hedged): k=H(randskm)k = H(\text{rand} \,\|\, sk \,\|\, m \,\|\, \dots). Secure if either rand is good or the deterministic inputs uniquely bind the session. This is what nonce_gen does.

Core references worth reading:

  • BIP340 Default Signing — the synthetic-nonce construction, and the rationale note explaining why aux_rand is XORed into sk (link)
  • BIP327 NonceGen — MuSig2's version, essentially what we copied (link)
  • Aranha, Novaes, Takahashi, Yarom, Tibouchi — "LadderLeak" / "Hedged Public-Key Signatures in the Standard Model" (PKC 2020, eprint 2019/956) — the formal model for why hedging gives you "best of both worlds."
  • RFC 6979 — the deterministic baseline you're hedging against.
  • NIST SP 800-186 / FIPS 186-5 §6.3 — nonce requirements for ECDSA/EdDSA.

Choosing inputs to the hash

The question "what goes in the hash" separates cleanly into two layers.

Layer 1 — what's needed for security when rand' is uniform. Answer: nothing else. A uniform 32-byte rand' hashed through SHA256 already gives a uniform 32-byte nonce. Every other input is redundant in this case.

Layer 2 — what's needed for security when rand' fails (repeats, low entropy, biased, or attacker-controlled). Then the hash inputs must, collectively, uniquely identify the signing session so that two distinct sessions can never collide on the hash input. For a signer that means binding:

  • its own secret share (so different keys → different kk),
  • the message (so message-substitution attacks can't reuse kk),
  • the group context (threshold pubkey / other signers' shares) — so a malicious peer can't trick you into reusing kk across different groups,
  • anything else session-specific (extra_in).

This is the "defense-in-depth" framing in the BIP. The guiding principle is domain separation / session binding: the hash input should be a canonical, injective encoding of the session. Length prefixes (len(x) ‖ x) are exactly for this — they prevent ambiguous concatenation ("ab" ‖ "c" vs "a" ‖ "bc").

XonlyPk vs PlainPk for thresh_pk

Given the framing above, here's the actual tradeoff:

  • When rand' is good: identical. Both choices produce uniform output; the 1-bit y-parity difference is invisible after SHA256.
  • When rand' fails: still effectively identical. What matters is that thresh_pk uniquely identifies this group. Both XonlyPk (32B) and PlainPk (33B) do that — the y-parity is a deterministic function of x for a fixed curve point, so it adds zero real entropy about "which group."
  • Collision resistance: SHA256 gives you ~128-bit collision resistance regardless. 1 extra bit of input is meaningless.
  • Consistency: BIP340 and BIP327 both use the x-only form of the (aggregate) pubkey in their nonce hashes. A FROST signer ultimately produces a BIP340 signature under the x-only threshold pubkey, so XonlyPk is the canonical identity of the group from the verifier's perspective. Using the same representation in nonce_gen means there's one canonical "group ID" throughout the spec.
  • PlainPk being slightly beneficial: technically true (1 extra bit, no canonicalization step), but the bit is cryptographically worthless and the canonicalization is trivial.

So the reviewer's argument is essentially: since security doesn't depend on this choice, pick the one that matches the rest of the Bitcoin/BIP340/BIP327 ecosystem. That's a reasonable "principle of least surprise" call. It "doesn't matter too much" — and that's precisely why consistency wins the tiebreak.

The meta-principle

A useful way to think about these design choices: security-critical inputs (here: rand', and secshare as the hedge) must be chosen for cryptographic reasons; defense-in-depth inputs (here: pubshare, thresh_pk, m, extra_in) should be chosen for engineering reasons — canonicality, consistency with neighboring specs, ease of auditing, absence of ambiguity. Debating the cryptographic merit of XonlyPk vs PlainPk is a category error: neither is "more secure," so you decide it on engineering grounds, and ecosystem consistency is the strongest engineering ground available.