Code

The same construction from how it works — Eval, the Wesolowski proof, and verification — implemented in Python, JavaScript, and Rust. Every sample on this page has actually been run and checked for a correct roundtrip and a correctly-rejected tampered result across multiple values of T; none of this is pseudocode.

Python

No third-party dependencies — just hashlib and secrets from the standard library.

import hashlib
import secrets

def is_probable_prime(n: int, rounds: int = 20) -> bool:
    """Miller–Rabin primality test."""
    if n < 2:
        return False
    for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47):
        if n == p:
            return True
        if n % p == 0:
            return False
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for _ in range(rounds):
        a = secrets.randbelow(n - 3) + 2
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True

def generate_prime(bits: int) -> int:
    while True:
        n = secrets.randbits(bits)
        n |= (1 << (bits - 1))  # exact bit length
        n |= 1                   # odd
        if is_probable_prime(n):
            return n

def generate_demo_modulus(total_bits: int = 512) -> int:
    """NOT a trusted setup — see /how-it-works for why generating your own
    p, q here defeats the purpose in a real deployment."""
    half = total_bits // 2
    return generate_prime(half) * generate_prime(half)

def eval_vdf(x: int, T: int, N: int) -> int:
    """y = x^(2^T) mod N via T sequential squarings."""
    y = x
    for _ in range(T):
        y = (y * y) % N
    return y

def hash_to_prime(x: int, y: int, T: int, prime_bits: int = 128) -> int:
    """Hash the statement, search upward for a prime."""
    counter = 0
    while True:
        data = f"{x:x}|{y:x}|{T}|{counter}".encode()
        h = int.from_bytes(hashlib.sha256(data).digest(), "big")
        h %= 1 << prime_bits
        h |= 1 << (prime_bits - 1)
        h |= 1
        if is_probable_prime(h):
            return h
        counter += 1

def prove(x: int, y: int, T: int, N: int):
    """Wesolowski proof: pi = x^floor(2^T / ell) mod N."""
    ell = hash_to_prime(x, y, T)
    q, r = divmod(1 << T, ell)  # full integer division, NOT modular
    pi = pow(x, q, N)           # fast modpow — but q has ~T bits
    return pi, ell, r

def verify(x: int, y: int, T: int, N: int, pi: int, ell: int) -> bool:
    """No T-step loop anywhere — fast regardless of T."""
    ell_check = hash_to_prime(x, y, T)
    if ell_check != ell:
        return False
    r = pow(2, T, ell)  # fast — exponent is only T, not 2^T
    return y % N == (pow(pi, ell, N) * pow(x, r, N)) % N

# --- usage ---
N = generate_demo_modulus(512)
x, T = 7, 100_000
y = eval_vdf(x, T, N)
pi, ell, r = prove(x, y, T, N)
assert verify(x, y, T, N, pi, ell)
assert not verify(x, (y + 1) % N, T, N, pi, ell)  # tampered y is rejected

JavaScript

This is the same implementation running live in the demo on this site — see src/lib/vdf.js in the site’s own repository (the live version also yields to the browser’s event loop periodically so a progress UI can repaint during long computations — omitted here for clarity). Uses only native BigInt and the Web Crypto API — no dependencies.

// Modular exponentiation safe for huge exponents: bits are extracted once
// via toString(2) rather than repeatedly right-shifting the exponent —
// shifting a shrinking-but-still-huge BigInt in a loop is quadratic, not
// linear, in the exponent's bit length, which matters once exponents reach
// the millions of bits this VDF's proof generation produces.
function modPow(base, exp, mod) {
  base %= mod;
  if (base < 0n) base += mod;
  if (exp === 0n) return 1n % mod;
  const bits = exp.toString(2);
  let result = 1n;
  for (let i = 0; i < bits.length; i++) {
    result = (result * result) % mod;
    if (bits[i] === '1') result = (result * base) % mod;
  }
  return result;
}

function isProbablePrime(n, rounds = 20) {
  const smallPrimes = [2n, 3n, 5n, 7n, 11n, 13n, 17n, 19n, 23n, 29n, 31n, 37n, 41n, 43n, 47n];
  if (n < 2n) return false;
  for (const p of smallPrimes) {
    if (n === p) return true;
    if (n % p === 0n) return false;
  }
  let d = n - 1n, r = 0n;
  while (d % 2n === 0n) { d /= 2n; r += 1n; }
  witnessLoop: for (let i = 0; i < rounds; i++) {
    const bytes = new Uint8Array(Math.ceil(n.toString(2).length / 8));
    crypto.getRandomValues(bytes);
    let a = 0n;
    for (const b of bytes) a = (a << 8n) | BigInt(b);
    a = (a % (n - 3n)) + 2n;
    let x = modPow(a, d, n);
    if (x === 1n || x === n - 1n) continue;
    for (let j = 0n; j < r - 1n; j++) {
      x = (x * x) % n;
      if (x === n - 1n) continue witnessLoop;
    }
    return false;
  }
  return true;
}

// y = x^(2^T) mod N via T sequential squarings.
function evalVDF(x, T, N) {
  let y = x;
  for (let i = 0n; i < T; i++) y = (y * y) % N;
  return y;
}

async function sha256ToBigInt(bytes) {
  const digest = await crypto.subtle.digest('SHA-256', bytes);
  let n = 0n;
  for (const b of new Uint8Array(digest)) n = (n << 8n) | BigInt(b);
  return n;
}

// Hash the statement, search upward for a prime.
async function hashToPrime(x, y, T, primeBits = 128) {
  const enc = new TextEncoder();
  let counter = 0;
  while (true) {
    const input = enc.encode(`${x.toString(16)}|${y.toString(16)}|${T}|${counter}`);
    let h = await sha256ToBigInt(input);
    h %= 1n << BigInt(primeBits);
    h |= 1n << BigInt(primeBits - 1);
    h |= 1n;
    if (isProbablePrime(h)) return h;
    counter++;
  }
}

// Wesolowski proof: pi = x^floor(2^T / ell) mod N.
async function prove(x, y, T, N) {
  const ell = await hashToPrime(x, y, T);
  const twoT = 1n << T; // full integer, NOT modular
  const q = twoT / ell;
  const r = modPow(2n, T, ell); // fast — exponent is only T
  const pi = modPow(x, q, N);   // fast modpow — but q has ~T bits
  return { pi, ell, r };
}

// No T-step loop anywhere — fast regardless of T.
async function verify(x, y, T, N, pi, ell) {
  const ellCheck = await hashToPrime(x, y, T);
  if (ellCheck !== ell) return false;
  const r = modPow(2n, T, ell);
  return ((y % N) + N) % N === (modPow(pi, ell, N) * modPow(x, r, N)) % N;
}

Rust

Uses num-bigint (with its rand feature), num-integer, and sha2.

use num_bigint::{BigUint, RandBigInt};
use num_integer::Integer;
use num_traits::{One, Zero};
use sha2::{Digest, Sha256};

fn is_probable_prime(n: &BigUint, rounds: u32) -> bool {
    let small_primes: [u32; 15] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47];
    if n < &BigUint::from(2u32) {
        return false;
    }
    for &p in &small_primes {
        let p = BigUint::from(p);
        if n == &p { return true; }
        if (n % &p).is_zero() { return false; }
    }
    let (one, two) = (BigUint::one(), BigUint::from(2u32));
    let n_minus_1 = n - &one;
    let mut d = n_minus_1.clone();
    let mut r = 0u32;
    while (&d % &two).is_zero() {
        d /= &two;
        r += 1;
    }

    let mut rng = rand::thread_rng();
    'witness: for _ in 0..rounds {
        let a = rng.gen_biguint_range(&two, &(n - &two));
        let mut x = a.modpow(&d, n);
        if x == one || x == n_minus_1 { continue; }
        for _ in 0..r - 1 {
            x = (&x * &x) % n;
            if x == n_minus_1 { continue 'witness; }
        }
        return false;
    }
    true
}

fn generate_prime(bits: u64) -> BigUint {
    let mut rng = rand::thread_rng();
    loop {
        let mut candidate = rng.gen_biguint(bits);
        candidate.set_bit(bits - 1, true); // exact bit length
        candidate.set_bit(0, true);        // odd
        if is_probable_prime(&candidate, 20) {
            return candidate;
        }
    }
}

/// NOT a trusted setup — see /how-it-works for why generating your own
/// p, q here defeats the purpose in a real deployment.
fn generate_demo_modulus(total_bits: u64) -> BigUint {
    let half = total_bits / 2;
    generate_prime(half) * generate_prime(half)
}

/// y = x^(2^T) mod N via T sequential squarings.
fn eval_vdf(x: &BigUint, t: u64, n: &BigUint) -> BigUint {
    let mut y = x.clone();
    for _ in 0..t {
        y = (&y * &y) % n;
    }
    y
}

/// Hash the statement, search upward for a prime.
fn hash_to_prime(x: &BigUint, y: &BigUint, t: u64, prime_bits: u32) -> BigUint {
    let mut counter: u64 = 0;
    loop {
        let input = format!("{:x}|{:x}|{}|{}", x, y, t, counter);
        let digest = Sha256::digest(input.as_bytes());
        let mut h = BigUint::from_bytes_be(&digest) % (BigUint::one() << prime_bits);
        h.set_bit((prime_bits - 1) as u64, true);
        h.set_bit(0, true);
        if is_probable_prime(&h, 20) {
            return h;
        }
        counter += 1;
    }
}

/// Wesolowski proof: pi = x^floor(2^T / ell) mod N.
fn prove(x: &BigUint, y: &BigUint, t: u64, n: &BigUint) -> (BigUint, BigUint, BigUint) {
    let ell = hash_to_prime(x, y, t, 128);
    let two_t = BigUint::one() << t; // full integer, NOT modular
    let (q, r) = two_t.div_mod_floor(&ell);
    let pi = x.modpow(&q, n); // fast modpow — but q has ~t bits
    (pi, ell, r)
}

/// No T-step loop anywhere — fast regardless of T.
fn verify(x: &BigUint, y: &BigUint, t: u64, n: &BigUint, pi: &BigUint, ell: &BigUint) -> bool {
    if hash_to_prime(x, y, t, 128) != *ell {
        return false;
    }
    let r = BigUint::from(2u32).modpow(&BigUint::from(t), ell); // fast — exponent is only t
    (y % n) == (pi.modpow(ell, n) * x.modpow(&r, n)) % n
}

fn main() {
    let n = generate_demo_modulus(512);
    let (x, t) = (BigUint::from(7u32), 100_000u64);
    let y = eval_vdf(&x, t, &n);
    let (pi, ell, _r) = prove(&x, &y, t, &n);

    assert!(verify(&x, &y, t, &n, &pi, &ell));
    let tampered_y = (&y + BigUint::one()) % &n;
    assert!(!verify(&x, &tampered_y, t, &n, &pi, &ell));
}

Sources: implements the construction derived on how it works. The Rust sample uses num-bigint, num-integer, and sha2.