Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions crates/vrf_fun/RUSTSEC-0000-0000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
```toml
[advisory]
id = "RUSTSEC-0000-0000"
package = "vrf_fun"
date = "2026-02-27"
url = "https://github.com/LLFourn/secp256kfun/pull/244"
categories = ["crypto-failure"]
keywords = ["nonce-reuse", "key-recovery", "vrf"]
license = "CC-BY-4.0"

[affected.functions]
"vrf_fun::Vrf::prove" = ["= 0.12.0"]
"vrf_fun::rfc9381::tai::prove" = ["= 0.12.0"]
"vrf_fun::rfc9381::sswu::prove" = ["= 0.12.0"]
"vrf_fun::rfc9381::Rfc9381Transcript::gen_rng" = ["= 0.12.0"]

[versions]
patched = []
```

# RFC 9381 VRF nonce reuse allows secret key recovery

The `ProverTranscript` implementation for `Rfc9381Transcript` computed the
nonce seed from a fresh hasher rather than the transcript state. The nonce
depended only on a fixed domain separator and the secret key, not on the VRF
input:

```
nonce = H("vrf-nonce-gen" || secret_key)
```

Because the nonce was identical across all proofs made with the same key,
any two VRF proofs on different inputs leaked the secret key via:

```
x = (s1 - s2) / (c1 - c2)
```

where `(c1, s1)` and `(c2, s2)` are the challenge-response pairs from the
two proofs.

The fix incorporates the transcript state (which includes the public key,
hash-to-curve point, and gamma) into the nonce derivation.