diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index c4c7c18a..5f05f713 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -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 diff --git a/src/modules/frost/frost.md b/src/modules/frost/frost.md index e634c573..7fc31085 100644 --- a/src/modules/frost/frost.md +++ b/src/modules/frost/frost.md @@ -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.