frost: document deterministic_sign's nonce derivation domain
BIP 445's det_nonce_hash commits to the secret share, my_id, u, the
sorted ids, the aggothernonce, the x-only tweaked threshold public key
and the message. It does not commit to the pubshares, to the untweaked
threshold public key, or to the accumulated tweaks. Because Q and -Q
share an x-coordinate, two tweak caches can agree on everything the
derivation hashes and still disagree on the sign g*gacc that multiplies
the secret share -- a cache initialized from thresh_pk and one
initialized from its negation being the smallest example.
Two calls differing only in that emit the same pubnonce and partial
signatures s = k + e*lambda*d and s' = k - e*lambda*d over the identical
k = k1 + b*k2, so subtracting them yields the secret share. Demonstrated
on a sole signer (u = 1, ids = {0}, pubshares = NULL) with thresh_sk =
0x11.. and msg = 0x42..:
tweaked pk (cache A) 4f355bdc...075871aa
tweaked pk (cache B) 4f355bdc...075871aa same x-only key
pubnonce A == pubnonce B nonce reused
sA - sB 0d7d9c4e...aa748ffa
-2*e*d 0d7d9c4e...aa748ffa d recovered
Nothing inside a single call can catch this. The self-verification that
Sign performs passes in both cases, because each partial signature is
individually valid under the cache it was produced with;
validate_session_params likewise only ties the pubshares to the cache's
own Q0, which both caches satisfy by construction. Note also that
pubshares is optional, so there need not be a second value to disagree
with.
No code change: this is the specified derivation, and committing to Q0
or to gacc here would diverge from BIP 445 and invalidate the
det_sign test vectors. The obligation is the caller's, so state it where
the caller will meet it -- in the function's own documentation and
alongside the existing secnonce and session_secrand32 rules in frost.md.
The rule is that the tweak cache and the pubshares are fixed key
material settled at key generation, never per-session parameters taken
from a coordinator or a peer; under that discipline a repeated call is
byte-identical and harmless, which is the point of a deterministic
nonce.
Worth raising against the BIP: the spec could close this by hashing the
untweaked threshold public key, at the cost of new test vectors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -520,6 +520,25 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_sign(
|
||||
* through NonceAgg as a pubnonce contribution, and a pubnonce's components
|
||||
* are never the point at infinity); if it does, this function fails.
|
||||
*
|
||||
* WARNING: the derivation above is the whole of what the nonce depends on. It
|
||||
* does NOT commit to the pubshares, to the untweaked threshold public key, or
|
||||
* to which tweaks the cache accumulated -- only to the x-only encoding of the
|
||||
* _tweaked_ threshold public key (this is BIP 445's det_nonce_hash, not a
|
||||
* deviation). Two tweak caches can therefore agree on that x-only key and
|
||||
* still disagree on the sign g*gacc that multiplies the secret share, because
|
||||
* Q and -Q have the same x-coordinate: a cache initialized from the threshold
|
||||
* public key and one initialized from its negation are the simplest example.
|
||||
* Two calls that differ only in that way emit the SAME pubnonce and two
|
||||
* partial signatures that differ only in the sign of the secret-share term,
|
||||
* which is two equations in the nonce and the secret share -- the secret
|
||||
* share falls out of the pair.
|
||||
*
|
||||
* The caller must therefore treat the tweak cache and the pubshares as fixed
|
||||
* key material belonging to the group, established once at key generation,
|
||||
* and never as per-session parameters accepted from a coordinator or any
|
||||
* other peer. Given that, repeating a call reproduces a byte-identical result
|
||||
* and is harmless, which is the point of a deterministic nonce.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or signing fails, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: partial_sig: pointer to a partial_sig object
|
||||
|
||||
@@ -96,6 +96,29 @@ Security notes
|
||||
unique for every call to `secp256k1_frost_nonce_gen`. Passing the secret
|
||||
share to `nonce_gen` is recommended as defense-in-depth against bad
|
||||
randomness.
|
||||
- `secp256k1_frost_deterministic_sign` has no `session_secrand32` to keep
|
||||
fresh; its safety rests instead on what the nonce derivation commits to. Per
|
||||
BIP 445's `det_nonce_hash` that is the secret share, `my_id`, `u`, the sorted
|
||||
ids, the aggothernonce, the **x-only** tweaked threshold public key, and the
|
||||
message — and nothing else. In particular it does not commit to the
|
||||
pubshares, to the untweaked threshold public key, or to the accumulated
|
||||
tweaks. Since `Q` and `-Q` share an x-coordinate, a tweak cache initialized
|
||||
from the threshold public key and one initialized from its negation present
|
||||
the same x-only key to the derivation while disagreeing on the sign `g*gacc`
|
||||
that multiplies the secret share. Two calls differing only in that produce
|
||||
the same pubnonce and partial signatures `s = k + e*lambda*d` and
|
||||
`s' = k - e*lambda*d`, where `k` is the identical `k1 + b*k2`; subtracting
|
||||
them yields `d` directly. The same holds for any two caches that agree on
|
||||
the tweaked x-only key but not on `g*gacc`.
|
||||
|
||||
This is a property of the specified derivation, not of this implementation,
|
||||
and it is not detectable from inside a single call: the self-verification in
|
||||
`Sign` passes in both cases, because each signature is individually valid
|
||||
under its own cache. The caller carries the obligation. Treat the tweak
|
||||
cache and the pubshares as fixed key material established once at key
|
||||
generation, and never accept either as a per-session parameter from the
|
||||
coordinator or another peer. Under that discipline a repeated call is
|
||||
byte-identical and harmless, which is what the deterministic nonce is for.
|
||||
- Final signatures produced by `secp256k1_frost_partial_sig_agg` are ordinary
|
||||
BIP340 signatures; they are verified with `secp256k1_schnorrsig_verify`
|
||||
against the (tweaked) x-only threshold public key.
|
||||
|
||||
Reference in New Issue
Block a user