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

PrimitiveUsage
HMAC-SHA256Key derivation: HMAC-SHA256(master_secret, "vrf-key") → Ed25519 keypair
Ed25519 (RFC 8032)Deterministic signature: sign(vrf_sk, alpha) → 64-byte signature
SHA-256Output 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..=99

Multiple 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_seed must 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_hex

Step 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.near

Step 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

  1. ed25519_verify(vrf_pubkey, alpha, signature) — signature is valid
  2. SHA256(signature) == output_hex — output matches signature
  3. Alpha contains correct request_id from blockchain event
  4. Alpha contains correct sender_id (the caller)
  5. 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

EndpointMethodAuthResponse
/vrf/pubkeyGETPublic{"vrf_public_key_hex": "..."}

SDK Functions

FunctionReturnsDescription
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

Related Resources