Verifiable Randomness with Tenzro VRF
A Verifiable Random Function (VRF) lets one party produce an output that is both pseudorandom and provably derived from a secret key + input. Tenzro implements ECVRF-EDWARDS25519-SHA512-TAI per RFC 9381 §5.4.1.1 — reusing existing Ed25519 validator keys — and exposes it as EVM precompile 0x1007. This tutorial covers proof generation, verification, and integration with the NFT factory's mintRandom().
What You'll Build
- A VRF keypair and a signed proof over arbitrary input bytes
- Off-chain proof verification via
tenzro_verifyVrfProof - On-chain verification from a Solidity contract using precompile
0x1007 - A random NFT mint through
NFT.mintRandom(collection, alpha, proof)
Cryptographic Overview
ECVRF-EDWARDS25519-SHA512-TAI (RFC 9381 §5.4.1.1)
prove(sk, alpha):
H = hash_to_curve_try_and_increment(pk, alpha)
Gamma = sk * H
k = hash_to_scalar(sk, H)
c = hash_points(H, Gamma, k*G, k*H)
s = k + c * sk (mod L)
pi = Gamma || c || s -> 80 bytes
beta = SHA-512(0x03 || 0x02 || cofactor*Gamma) -> 64 bytes
verify(pk, alpha, pi):
(Gamma, c, s) = decode(pi)
U = s*G - c*pk
V = s*H - c*Gamma
c' = hash_points(H, Gamma, U, V)
return c == c' ; if ok, beta = SHA-512(...)
Key properties:
* deterministic — same (sk, alpha) always yields the same beta
* unforgeable — only the sk holder can produce a valid pi
* pseudorandom — beta is indistinguishable from random to anyone without sk
* verifiable — anyone with pk can check pi in O(1) pairings-free opsStep 1: Generate a VRF Key
Tenzro VRF uses Ed25519 keys directly — any existing validator key doubles as a VRF key. You can also mint a fresh one:
tenzro vrf keygen
-> {
secret_key: "ed25519:...", // 32-byte Ed25519 seed
public_key: "0x..." // compressed Edwards25519 point
}Step 2: Produce a Proof
The input alpha can be any byte string — typically a commitment like collection_id || mint_index or lottery_round || block_hash. The proof is 80 bytes and the deterministic output is 64 bytes:
tenzro_generateVrfProof({
"secret_key": "ed25519:...",
"alpha": "0x48656c6c6f" // arbitrary input bytes (hex)
})
-> {
proof: "0x...", // 80-byte ECVRF proof (Gamma || c || s)
output: "0x...", // 64-byte deterministic hash (beta)
public_key: "0x..."
}Step 3: Verify Off-Chain
tenzro_verifyVrfProof({
"public_key": "0x...",
"alpha": "0x48656c6c6f",
"proof": "0x..."
})
-> {
valid: true,
output: "0x..." // same beta as produced by the prover
}Step 4: Verify On-Chain (Precompile 0x1007)
EVM contracts on Tenzro can call the VRF precompile directly — no external oracle, no callback latency, no subscription funding:
// Solidity — verify a VRF proof from inside an EVM contract
// via the Tenzro precompile at 0x1007.
interface IVrfPrecompile {
function verify(
bytes32 publicKey,
bytes calldata alpha,
bytes calldata proof
) external view returns (bool ok, bytes32 beta);
}
address constant VRF = address(0x1007);
function rollDice(bytes calldata alpha, bytes calldata proof) external view returns (uint8) {
(bool ok, bytes32 beta) = IVrfPrecompile(VRF).verify(PUBLIC_KEY, alpha, proof);
require(ok, "bad VRF proof");
return uint8(uint256(beta) % 6) + 1;
}Step 5: Random NFT Minting
The built-in NFT factory exposes mintRandom(uint256 collection, bytes alpha, bytes proof) — selector 0x52517e21 — which internally calls 0x1007, derives a collision-checked tokenId, and assigns a rarity tier:
// NFT Factory mintRandom(uint256 collection, bytes alpha, bytes proof)
// selector 0x52517e21 — consumes a verified VRF output to derive:
// token_id = keccak256(collection || beta) & COLLISION_MASK
// rarity = rarityTierFromBeta(beta)
// and rejects duplicate token_ids in the same collection.
import { TenzroClient } from "@tenzro/sdk";
const client = new TenzroClient();
// 1. Generate a proof off-chain
const { proof, output } = await client.vrf.prove({
secret_key: process.env.VRF_SK!,
alpha: "0x" + Buffer.from("collection-7/mint-42").toString("hex"),
});
// 2. Submit to NFT factory (on-chain contract calls the 0x1007 precompile)
const tx = await client.nft.mintRandom({
collection_id: 7,
alpha: "0x" + Buffer.from("collection-7/mint-42").toString("hex"),
proof,
});
console.log("minted tokenId", tx.token_id, "rarity", tx.rarity);Step 6: CLI
# Generate a VRF keypair (Ed25519-compatible)
tenzro vrf keygen
# Produce a proof for input bytes
tenzro vrf prove \
--secret-key ed25519:... \
--alpha-hex 48656c6c6f
# Verify a proof against the public key + input
tenzro vrf verify \
--public-key 0x... \
--alpha-hex 48656c6c6f \
--proof 0x...Step 7: Rust SDK
use tenzro_sdk::TenzroClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TenzroClient::connect(Default::default()).await?;
let alpha = b"lottery-round-17";
// Prove
let proof = client.vrf().prove(&secret_key, alpha).await?;
println!("proof: {}", proof.proof);
println!("output: {}", proof.output);
// Verify (anyone with the public key can do this)
let v = client.vrf().verify(&public_key, alpha, &proof.proof).await?;
assert!(v.valid);
// Map 64-byte beta to a winner index
let winner = u64::from_be_bytes(v.output[..8].try_into()?) % NUM_TICKETS;
println!("winner ticket: {}", winner);
Ok(())
}vs Chainlink VRF. Chainlink VRF is an off-chain oracle subscription model with callback latency and per-request fees. Tenzro VRF is a native precompile: O(1) verification inside the same transaction that consumes the randomness, no subscription, no oracle trust, and the same RFC 9381 algorithm used by Algorand, Cardano, and the broader Ed25519 ecosystem.
Safe-Use Checklist
- Commit to alpha before revealing the proof — otherwise the prover can bias output by choosing inputs
- Use a fresh alpha per draw — never reuse
(sk, alpha)across rounds - Bind alpha to chain state (block hash, round ID) to make the prover commit before seeing the randomness
- Reject low-order keys — the precompile already does this, but off-chain callers should too
What You Learned
- RFC 9381 ECVRF — Edwards25519 + SHA-512, 80-byte proofs, 64-byte outputs
- Precompile 0x1007 — on-chain verification without external oracles
- NFT.mintRandom — auditable on-chain randomness for collections and rarity
- CLI + SDK —
tenzro vrf, Rust, and TypeScript flows
Next Steps
- See the NFT Marketplace tutorial for the full minting flow
- See the ZK Proof Verification tutorial for complementary Groth16 proofs
- Read RFC 9381 for the full VRF specification