16 releases (7 breaking)
new 0.8.2 | Jun 21, 2025 |
---|---|
0.7.0 | Jun 19, 2025 |
#143 in Magic Beans
1,920 downloads per month
110KB
1.5K
SLoC
VRF-WASM
A WASM-compatible Verifiable Random Function (VRF) implementation based on FastCrypto.
FastCrypto has C dependencies (secp256k1-sys
, blst
) that prevent WASM compilation even when compiling with wasm
feature flags. This library extracts only the VRF module which uses pure Rust dependencies.
Features
- WASM Compatible: Runs in browsers, Node.js, and WASM workers
- Cryptographically Secure: ECVRF implementation following draft-irtf-cfrg-vrf-15
- Lightweight: Pure Rust, no FFI overhead ~143KB WASM binary when compiled for web
- Flexible: Use as Rust library or compile to WASM
Installation
To use vrf-wasm
, you must explicitly enable a feature flag for your target environment.
Browser/Web Applications
[dependencies]
vrf-wasm = { version = "0.8", features = ["browser"] }
NEAR Smart Contracts
[dependencies]
vrf-wasm = { version = "0.8", default-features = false, features = ["near"] }
If no feature is selected, you will get a compile-time error with instructions.
Usage
Basic VRF Operations
use vrf_wasm::ecvrf::ECVRFKeyPair;
use vrf_wasm::vrf::{VRFKeyPair, VRFProof};
use vrf_wasm::rng::WasmRng;
// Generate a keypair
let mut rng = WasmRng::default();
let keypair = ECVRFKeyPair::generate(&mut rng);
// Create VRF proof for input
let input = b"Hello, VRF!";
let (hash, proof) = keypair.output(input);
// Verify the proof
assert!(proof.verify(input, &keypair.pk).is_ok());
// The hash is deterministic for the same key and input
let (hash2, _) = keypair.output(input);
assert_eq!(hash, hash2);
println!("VRF Hash: {}", hex::encode(hash));
Deterministic KeyPair Generation
use vrf_wasm::ecvrf::ECVRFKeyPair;
use vrf_wasm::vrf::VRFKeyPair;
use vrf_wasm::rng::WasmRngFromSeed;
use rand_core::SeedableRng;
// Generate deterministic keypair from seed
let seed = [42u8; 32];
let mut rng = WasmRngFromSeed::from_seed(seed);
let keypair = ECVRFKeyPair::generate(&mut rng);
// Same seed always generates same keypair
let mut rng2 = WasmRngFromSeed::from_seed(seed);
let keypair2 = ECVRFKeyPair::generate(&mut rng2);
// Prove this by generating same VRF output
let input = b"test";
let (hash1, _) = keypair.output(input);
let (hash2, _) = keypair2.output(input);
assert_eq!(hash1, hash2);
Serialization
use vrf_wasm::ecvrf::{ECVRFKeyPair, ECVRFProof, ECVRFPublicKey};
use vrf_wasm::vrf::VRFKeyPair;
// All types implement Serialize/Deserialize
let mut rng = vrf_wasm::rng::WasmRng::default();
let keypair = ECVRFKeyPair::generate(&mut rng);
let input = b"data";
let proof = keypair.prove(input);
// Serialize to bytes
let public_key_bytes = bincode::serialize(&keypair.pk).unwrap();
let proof_bytes = bincode::serialize(&proof).unwrap();
// Deserialize
let public_key: ECVRFPublicKey = bincode::deserialize(&public_key_bytes).unwrap();
let deserialized_proof: ECVRFProof = bincode::deserialize(&proof_bytes).unwrap();
// Verify still works
assert!(deserialized_proof.verify(input, &public_key).is_ok());
VRF Component Extraction
For cross-verification scenarios where you need to inspect or reconstruct VRF proofs:
use vrf_wasm::ecvrf::{ECVRFKeyPair, ECVRFProof};
use vrf_wasm::vrf::VRFKeyPair;
use vrf_wasm::rng::WasmRng;
let mut rng = WasmRng::default();
let keypair = ECVRFKeyPair::generate(&mut rng);
let proof = keypair.prove(b"input");
// Extract individual components
let gamma_bytes = proof.gamma_bytes(); // [u8; 32] - compressed point
let challenge_bytes = proof.challenge_bytes(); // [u8; 16] - challenge
let scalar_bytes = proof.scalar_bytes(); // [u8; 32] - scalar
// Extract all at once
let (gamma, challenge, scalar) = proof.to_components();
// Reconstruct proof from components (for cross-verification)
let reconstructed = ECVRFProof::from_components(&gamma, &challenge, &scalar).unwrap();
assert!(reconstructed.verify(b"input", &keypair.pk).is_ok());
Conditional Compilation & Feature Flags
VRF-WASM uses conditional compilation to provide optimized RNG implementations for different target environments:
Available Features
Feature | Target Environment | RNG Implementation | Default |
---|---|---|---|
browser |
Web browsers, JavaScript | crypto.getRandomValues() via getrandom |
✅ Yes |
near |
NEAR smart contracts | env::random_seed() + block-based entropy + ChaCha20 |
❌ No |
Building for Different Targets
Browser/JavaScript (Default)
# Default build - includes browser RNG
cargo build
# WASM for web
wasm-pack build --target web
NEAR Smart Contracts
# NEAR-specific build (NEAR features only)
cargo build --no-default-features --features near --target wasm32-unknown-unknown
# With cargo-near (recommended for NEAR contracts)
cargo install cargo-near
cargo near build
Environment-Specific RNG Usage
// Generic usage (works with any feature configuration)
use vrf_wasm::rng::WasmRng;
let mut rng = WasmRng::default();
// Browser-specific (when browser feature is enabled)
use vrf_wasm::rng::BrowserWasmRng;
let mut rng = BrowserWasmRng::default();
// NEAR-specific (when near feature is enabled)
use vrf_wasm::rng::NearWasmRng;
let mut rng = NearWasmRng::default();
Binary Size
Target | Binary Size | Notes |
---|---|---|
Native (release) | ~2MB | Full Rust binary |
WASM (release) | ~143KB | Optimized for web |
WASM (compressed) | ~58KB | With Brotli compression |
Attribution
This project is derived from FastCrypto by Mysten Labs, Inc.
Original Copyright: Copyright (c) 2022, Mysten Labs, Inc. License: Apache License 2.0 Original Repository: https://github.com/MystenLabs/fastcrypto/
Advanced: Using with NEAR Smart Contracts
When building a project that uses vrf-wasm
for both on-chain (NEAR) and off-chain (testing) purposes, you need to handle Cargo's feature unification.
The Problem: Feature Unification
If your Cargo.toml
has this:
# In [dependencies]
vrf-wasm = { version = "0.8", default-features = false, features = ["near"] }
# In [dev-dependencies]
vrf-wasm = { version = "0.8", features = ["browser"] }
Cargo will enable both near
and browser
features for all builds, which can cause conflicts.
The Solution: Target-Specific Dev-Dependencies
To solve this, use a [target]
configuration in your project's Cargo.toml
to specify that the browser version of vrf-wasm
should only be used for native testing environments, not for the wasm32
smart contract build.
# In your project's Cargo.toml
[dependencies]
# This will be used for your smart contract build
vrf-wasm = { version = "0.8", default-features = false, features = ["near"] }
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
# This will be used for your native tests (`cargo test`)
vrf-wasm = { version = "0.8", features = ["browser"] }
This configuration ensures:
- ✅
cargo build --target wasm32-unknown-unknown
: Compilesvrf-wasm
with only thenear
feature. - ✅
cargo test
: Compilesvrf-wasm
with thebrowser
feature for your test suite.
This approach prevents feature conflicts and allows you to test your NEAR smart contracts with a browser-compatible version of the VRF library.
Dependencies
~12MB
~218K SLoC