r/CryptoTechnology • u/Fine_Buy4387 • 1h ago
My fuzzer found that my blockchain's leader VRF gave different results depending on the libsodium build. Here's what happened and how I fixed it.
I'm building Finalis, a BFT blockchain in C++20 (open source, testnet only, no token sale). I wanted to share a bug that I think is relevant to anyone using VRFs or Ed25519 point checks in consensus code.
**The setup.** Each block's leader publishes a VRF proof, and its output feeds the chain's randomness. My VRF hashed to the curve with a try-and-increment loop that accepted the first candidate for which libsodium's crypto_core_ed25519_is_valid_point() returned true.
**What the fuzzer found.** The ClusterFuzzLite job (Ubuntu 20.04 image) crashed on startup: a proof my test fixture had just created was rejected. Isolating it: the same key and input gave a *different proof* on Ubuntu 20.04 (libsodium 1.0.18-1) than on Ubuntu 24.04 (1.0.18-1ubuntu0.24.04.1), and each rejected the other's. Both report version 1.0.18, but the point-validation behavior differs between the two packages. In a live network, that's a chain split waiting to happen.
**The fix.** I switched to the standard RFC 9381 ECVRF-EDWARDS25519-SHA512-TAI. Its hash-to-curve multiplies by the cofactor instead of asking "is this point valid?". I decode points and verify proofs with plain big-integer arithmetic, so libsodium's validation never decides the result. Proving still uses libsodium's constant-time code for the secret scalars. Every node checks the three RFC test vectors at startup and refuses to run if it can't reproduce them. Since validators had to agree on new outputs, I reset the testnet instead of shipping it.
**Lessons I took from it:**
- Don't let a library's "is valid" check define consensus behavior unless its exact semantics are pinned.
- The same version string doesn't mean the same behavior. Distro security backports change code without changing the version.
- Run fuzzers and tests on more than one OS image. Mine only caught this because CI used an older base image.
Happy to answer questions, and I'd welcome criticism of the approach. Code: github.com/FinalisCore/finalis-core (src/crypto/vrf.cpp)