OutLayer Documentation
VRF — Verifiable Random Function
Cryptographically provable randomness for NEAR smart contracts. No oracle trust required — anyone can verify the proof on-chain.
Overview#
OutLayer VRF provides verifiable random numbers for WASI modules. Unlike plain randomness (e.g. getrandom), VRF produces a cryptographic proof alongside each random output. Anyone can verify this proof — no trust in the server required.
VRF vs plain random: With plain random numbers, you trust the server gave you honest results. With VRF, the result includes an Ed25519 signature that proves the output was computed correctly from a known key — verifiable by anyone, on-chain or off-chain.
Each VRF call returns three values:
- output_hex — 32-byte random value (SHA256 of the signature)
- signature_hex — Ed25519 signature (the proof)
- alpha — the signed message:
vrf:{request_id}:{sender_id}:{user_seed}
How It Works#
WASI Module Worker (TEE) Keystore (TEE)
| | |
| vrf::random("coin-flip") | |
|----------------------------->| |
| | alpha = "vrf:42:alice.near:coin-flip"
| | POST /vrf/generate {alpha} |
| |----------------------------->|
| | | signature = Ed25519_sign(vrf_sk, alpha)
| | | output = SHA256(signature)
| | {output_hex, signature_hex} |
| |<-----------------------------|
| VrfOutput { |
| output_hex, |
| signature_hex, |
| alpha |
| } |
|<-----------------------------|Alpha Format
vrf:{request_id}:{sender_id}:{user_seed}- request_id — from blockchain event or HTTPS call ID. Auto-injected by worker, WASM cannot set it.
- sender_id — signer account (blockchain) or payment key owner (HTTPS). Auto-injected by worker.
- user_seed — arbitrary string from your WASM module. Must not contain
:.
Example: vrf:98321:alice.near:coin-flip
Cryptographic Primitives
| Primitive | Usage |
|---|---|
| HMAC-SHA256 | Key derivation: HMAC-SHA256(master_secret, "vrf-key") → Ed25519 keypair |
| Ed25519 (RFC 8032) | Deterministic signature: sign(vrf_sk, alpha) → 64-byte signature |
| SHA-256 | Output derivation: SHA256(signature) → 32-byte random output |
SDK Usage#
Add the OutLayer SDK to your Cargo.toml:
[dependencies]
outlayer = "0.2"Generate Random Output
use outlayer::vrf;
// Get verifiable random output
let result = vrf::random("my-seed")?;
println!("Random: {}", result.output_hex); // SHA256(signature), 32 bytes hex
println!("Proof: {}", result.signature_hex); // Ed25519 signature, 64 bytes hex
println!("Alpha: {}", result.alpha); // "vrf:{request_id}:{sender_id}:my-seed"
// Or get raw bytes
let (bytes, signature_hex, alpha) = vrf::random_bytes("my-seed")?;
// bytes: [u8; 32]
// Get VRF public key (for including in output)
let pubkey = vrf::public_key()?;Map to a Range
let result = vrf::random("roll")?;
let first_4_bytes = u32::from_be_bytes(hex_to_bytes(&result.output_hex[..8]));
let roll = (first_4_bytes as u64 * 100 / (u32::MAX as u64 + 1)) as u32; // 0..=99Multiple Random Values
Use unique sub-seeds for independent values:
for i in 0..5 {
let result = vrf::random(&format!("card:{}", i))?;
// Each call gets a unique alpha -> unique output
}Constraints
user_seedmust not contain:(used as alpha delimiter)- Max 10 VRF calls per execution
- VRF requires keystore — project must be deployed on OutLayer
On-Chain Verification#
Verify VRF output in a NEAR smart contract using native ed25519_verify (~1 TGas):
use near_sdk::env;
fn verify_vrf(
vrf_pubkey: &[u8; 32], // from GET /vrf/pubkey or hardcoded
alpha: &str, // from VRF output
signature: &[u8; 64], // from VRF output (signature_hex decoded)
) -> bool {
env::ed25519_verify(signature, alpha.as_bytes(), vrf_pubkey)
}Deploying a Contract with VRF
Step 1. Get the VRF public key:
# Mainnet
curl -s https://api.outlayer.fastnear.com/vrf/pubkey | jq -r .vrf_public_key_hex
# Testnet
curl -s https://testnet-api.outlayer.fastnear.com/vrf/pubkey | jq -r .vrf_public_key_hexStep 2. Initialize contract with the pubkey:
near call my-vrf.near new '{
"outlayer_contract_id": "outlayer.near",
"project_id": "alice.near/vrf-ark",
"vrf_pubkey_hex": "a1b2c3d4..."
}' --accountId my-vrf.nearStep 3. Verify it was stored:
near view my-vrf.near get_vrf_pubkey
# "a1b2c3d4..."If the keystore rotates the VRF key (rare), update via set_vrf_pubkey (contract owner only).
Complete Example: Coin Flip
WASI module generates VRF, contract verifies:
// 1. Contract requests execution
ext_outlayer::ext(outlayer_contract_id)
.with_attached_deposit(NearToken::from_millinear(50))
.request_execution(
json!({"Project": {"project_id": "alice.near/vrf-ark"}}),
Some(resource_limits),
Some(r#"{"seed":"coin-flip","max":1}"#.to_string()),
None,
Some("Json".to_string()),
Some(player.clone()),
)
.then(ext_self::ext(current_account_id()).on_vrf_result(player, choice));
// 2. In callback - verify proof
let entry = &vrf_response.results[0];
let sig_bytes: [u8; 64] = hex::decode(&entry.signature_hex).try_into().unwrap();
let valid = env::ed25519_verify(&sig_bytes, entry.alpha.as_bytes(), &self.vrf_pubkey);
assert!(valid, "VRF proof verification failed");Full contract example: vrf-contract on GitHub
Security Properties#
1. Deterministic — no re-rolling
Ed25519 signatures are deterministic per RFC 8032. Same key + same alpha = same signature = same output. The worker cannot retry to get a different result.
2. Unpredictable without the key
The VRF private key lives only inside TEE (Intel TDX via Phala Cloud). It is derived from the master secret via HMAC-SHA256(master_secret, "vrf-key"). The master secret is distributed through MPC key ceremony — no single party holds it.
3. Non-manipulable alpha
The WASM module only provides user_seed. The worker auto-prepends request_id (from the blockchain event) and sender_id (the caller's account). Same seed by different users or different requests produces different output.
4. Publicly verifiable
Anyone can verify the VRF output — no trust in the TEE required: ed25519_verify(vrf_pubkey, alpha, signature) and SHA256(signature) == output.
5. Consistent across keystore instances
All keystore instances derive the VRF keypair from the same master secret with fixed seed "vrf-key". All instances produce the same public key and same output for same alpha.
6. Rate-limited
Max 10 VRF calls per WASM execution. Prevents abuse of the signing endpoint.
User Verification Guide#
1. Get the VRF Public Key
curl https://api.outlayer.fastnear.com/vrf/pubkey
# {"vrf_public_key_hex":"a1b2c3d4..."} (64 hex chars = 32 bytes)2. Verify with Python (PyNaCl)
from nacl.signing import VerifyKey
import hashlib
vrf_pubkey_hex = "..." # from /vrf/pubkey
signature_hex = "..." # from result
alpha = "vrf:98321:alice.near:coin-flip"
vrf_pubkey = bytes.fromhex(vrf_pubkey_hex)
signature = bytes.fromhex(signature_hex)
# Verify: Ed25519 signature over alpha
verify_key = VerifyKey(vrf_pubkey)
verify_key.verify(alpha.encode(), signature) # raises if invalid
print("Signature VALID")
# Verify: output = SHA256(signature)
output = hashlib.sha256(signature).hexdigest()
print(f"Output: {output}")
# If mapped to range: first 4 bytes -> u32 -> scale
first_4 = int(output[:8], 16)
mapped = first_4 * (max_value + 1) // (2**32)
print(f"Mapped value: {mapped}")3. Verify with JavaScript (tweetnacl)
import nacl from 'tweetnacl';
import { createHash } from 'crypto';
const vrfPubkey = Buffer.from(vrfPubkeyHex, 'hex');
const signature = Buffer.from(signatureHex, 'hex');
const alpha = Buffer.from('vrf:98321:alice.near:coin-flip');
// Verify signature
const valid = nacl.sign.detached.verify(alpha, signature, vrfPubkey);
console.log('Valid:', valid);
// Verify output
const output = createHash('sha256').update(signature).digest('hex');
console.log('Output:', output);Verification Checklist
ed25519_verify(vrf_pubkey, alpha, signature)— signature is validSHA256(signature) == output_hex— output matches signature- Alpha contains correct
request_idfrom blockchain event - Alpha contains correct
sender_id(the caller) - VRF public key matches
GET /vrf/pubkey
If all 5 checks pass, the random output is provably correct and was not manipulated.
API Reference#
Endpoint
| Endpoint | Method | Auth | Response |
|---|---|---|---|
| /vrf/pubkey | GET | Public | {"vrf_public_key_hex": "..."} |
SDK Functions
| Function | Returns | Description |
|---|---|---|
| vrf::random(seed) | Result<VrfOutput> | Random output + proof |
| vrf::random_bytes(seed) | Result<([u8; 32], String, String)> | Raw bytes + signature + alpha |
| vrf::public_key() | Result<String> | VRF public key hex |