NebulaVRF is a Verifiable Random Function (VRF) core for Soroban developers. It transforms a secure seed into cryptographically sound, verifiable randomness without external oracles. You can generate randomness locally and, if needed, submit proofs for on-chain verification on testnet.
View full API documentation and live endpoint usage: api/docs.md
This crate is ideal for developers building dApps that need:
- Randomized game mechanics or loot drops
- Unbiased DAO voting and committee selection
- Fair token lotteries or airdrops
- NFT trait randomization
- Anything else that might require randomization
Built in pure Rust using the blst BLS12-381 signature library. Designed to be compiled to WASM and later integrated into Soroban smart contracts.
- Secure randomness generation using BLS signatures (min-sig mode)
- Proof of randomness—anyone can verify correctness
- Deterministic output (same seed → same randomness)
- Unpredictable if seed is kept secret
- Commit–reveal scheme to prevent last-mover bias
Add nebula-vrf to your Cargo.toml:
[dependencies]
nebula-vrf = "0.1.4"If you need the optional API features (for running the HTTP server), include the api feature:
[dependencies]
nebula-vrf = { version = "0.1.4", features = ["api"] }Then use it in your code:
use nebula_vrf::vrf::generate_random;
use nebula_vrf::vrf::verify_proof;
use nebula_vrf::vrf::commit::{commit, verify_commit};- User generates a private random
seed(not shared initially). - Hash of seed is committed on-chain (via the testnet contract) using
commit(). - After a delay, the seed is revealed and passed into
generate_random(). - The function returns:
output— a random-looking 48-byte value (BLS signature)public_key— the proof of validity (BLS pubkey)
- Others verify the output using
verify_proof().
The Soroban testnet contract lives in the vrf-testnet repo. It expects:
commitment = sha256(seed || salt)- G1 pubkey (96 bytes, uncompressed)
- G2 signature (192 bytes, uncompressed)
- DST:
NEBULA-VRF-V01-BLS12381G2
| Function | Input Type | Output Type | Description |
|---|---|---|---|
generate_random(seed) |
&[u8] |
VRFProof { output: Vec<u8>, public_key: Vec<u8> } |
Main VRF output |
verify_proof(seed, output, pubkey) |
&[u8], byte arrays |
Result<(), VRFError> |
Verifies randomness |
commit(seed) |
&[u8] |
[u8; 32] |
SHA256-based hash of seed |
verify_commit(seed, commitment) |
&[u8], &[u8; 32] |
bool |
Check if seed matches hash |
The output (Vec<u8> of 48 bytes) is cryptographically strong pseudorandomness. You can extract values like:
let output = &vrf.output;
let as_u64 = u64::from_le_bytes(output[0..8].try_into().unwrap());
let as_u128 = u128::from_le_bytes(output[0..16].try_into().unwrap());
let as_hash = sha2::Sha256::digest(output);If you are coming from the NebulaVRF testnet contract, you can generate testnet‑compatible payloads (seed, salt, commitment, pubkey, signature) here. These helpers are for demo/testing only — for real integrations you should implement your own seed/salt generation logic.
Generate payloads:
cargo run --example sample_payloadsRun the local API:
cargo run --bin nebula_vrf_api --features apiEndpoints:
GET http://localhost:3000/payloadsGET http://localhost:3000/payloads?seed_len=8&salt_len=8
Use this for secure randomness:
use rand::rngs::OsRng;
use rand::RngCore;
fn generate_secure_seed() -> [u8; 32] {
let mut seed = [0u8; 32];
OsRng.fill_bytes(&mut seed);
seed
}- Never hardcode seeds
- Never reuse seeds
- Always commit before revealing
use nebula_vrf::vrf::generate_random;
let seed = b"user-supplied-entropy";
let vrf = generate_random(seed).unwrap();
println!("Randomness: {:?}", vrf.output);
println!("Proof (public key): {:?}", vrf.public_key);use nebula_vrf::vrf::verify_proof;
let is_valid = verify_proof(seed, &vrf.output, &vrf.public_key).unwrap();
assert!(is_valid);use nebula_vrf::vrf::commit::{commit, verify_commit};
let seed = b"secret-seed";
let commitment = commit(seed);
// Save or store `commitment` on-chain
assert!(verify_commit(seed, &commitment)); // later when revealedRun tests:
cargo test -- --nocaptureCoverage:
- Determinism (same seed → same randomness)
- Collision resistance
- Corruption rejection (sig/pubkey tampering)
- Edge cases (empty seed)
- Commit-reveal validation
- Based on the IETF CFRG VRF Draft Spec
- Uses BLS12-381 signatures (min-sig mode)
- Domain separation follows RFC 9380 format
- Similar to ETH2.0 / Chainlink / Polkadot randomness implementations
nebula-vrf/
├── src/
│ ├── lib.rs
│ ├── vrf/
│ │ ├── bls.rs # Core BLS logic
│ │ ├── commit.rs # Commit-reveal layer
│ │ └── types.rs # Error types
│ └── utils/
│ └── hash.rs # SHA256 utilities
├── tests/
│ └── vrf_tests.rs # Full test suite
Is the randomness deterministic?
Yes, same seed → same output. This allows verifiability.
Can attackers predict the randomness?
Not if the seed is secret and committed before reveal.
How many bits of randomness do I get?
384 bits (48 bytes) directly. You can truncate or hash to get u64, u128, SHA256, etc.
Can this be used on-chain?
Yes. The testnet contract lives in the vrf-testnet repo and verifies proofs on-chain.
MIT — Free for public and commercial use in Stellar/Soroban dApps.
Built by Mukund Jha
GitHub | https://nebulavrf.vercel.app
Supported by Stellar Community Fund