Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Crate: kyn-vdf

Stage: 06 (Verification)

Reading Time: 25 minutes

Depends On: kyn-vdf (Squaring / Proof Generation), kyn-types

What Is This?

This document covers the culminating step of the kyn-vdf crate: the Wesolowski verification phase. While earlier stages deal with the brutally slow and computationally intense process of generating a Verifiable Delay Function (VDF) proof through repeated squarings, this stage is entirely about validating that proof almost instantly. Specifically, this documentation explores the derivation of the Fiat-Shamir challenge, the fast verification mathematics, the pure-Rust entry point verify_chia_vdf in lib.rs, and the comprehensive error handling taxonomy defined in error.rs. In any system utilizing VDFs, the prover does all the heavy lifting over a sustained period of time, while the verifier must be able to confirm the work rapidly. This file explains how the Kinetic network achieves that critical asymmetry using pure Rust, enabling seamless execution across servers, mobile devices, and web browsers.

Why Kinetic Needs This

In a decentralized network like Kinetic, time must be objectively verifiable by anyone. Traditional consensus mechanisms often rely on raw hashing power (Proof of Work) or capital lockups (Proof of Stake). However, to prevent certain types of network manipulation— such as long-range attacks where an adversary tries to rewrite history— we need a cryptographic guarantee that a specific, unavoidable amount of sequential wall-clock time has passed. We use a VDF to prove this passage of time. A node generating a proof might spend 30 days of continuous CPU time computing billions of sequential operations. However, if verifying that proof also took 30 days, the entire network would grind to a halt. The nodes would never be able to synchronize. The entire system relies on an asymmetry: proof generation is intentionally slow and un-parallelizable, but verification must be almost instantaneous. Wesolowski’s protocol provides this exact mathematical property. It reduces a time-intensive operation into a quick cryptographic check, allowing the network to agree on the passage of time without having to re-compute the entire sequence.

But why do we need this specific implementation in pure Rust? Why not just use the existing, optimized C++ libraries provided by projects like Chia? The Kinetic architecture requires maximum portability and decentralization. We envision a network where a full node or a capable light client can run directly inside a standard web browser using WebAssembly (WASM). Traditional C++ cryptography libraries often rely heavily on underlying operating system APIs, custom memory allocators, and hardware-specific assembly instructions. These dependencies make them notoriously difficult and sometimes impossible to compile reliably to WASM. By rewriting the validation logic—specifically the Wesolowski verifier—in pure Rust, we leverage Rust’s powerful cross-compilation toolchains. More importantly, by utilizing the #![no_std] capability, we guarantee that this code does not depend on the standard C-like operating system underneath. It requires only a basic memory allocator. This means our VDF verifier can be deployed to any environment, including a browser, ensuring that users do not have to trust a centralized server to verify network state. It is lightweight, secure, and universally portable, which is a hard requirement for the frontend accessibility of the Kinetic ecosystem.

How It Works

The magic of Wesolowski verification lies in how it condenses billions of operations into a single equation: pi^B * x^r = y. To understand how this works in our codebase, we need to break down the environment, the variables, the math, and the exact code path. To prevent the prover from taking parallel shortcuts, the VDF operates within an Imaginary Quadratic Class Group. The crucial property of this mathematical group is that its “order” (the number of elements it contains) is unknown. Because the order is unknown, the prover cannot use shortcuts like Fermat’s Little Theorem to skip ahead in the computation. They are forced to perform every single squaring operation sequentially: x, x^2, x^4, x^8... up to x^(2^T).

When a prover finishes computing y = x^(2^T) (where T is the number of squarings, representing the time elapsed), they don’t just broadcast y. They also calculate and broadcast a proof element, pi. The verifier (any Kinetic node) must check if pi correctly proves that y was derived from x after exactly T squarings.

-> See: kyn-vdf/src/chia.rs — Lines 599 to 615 The first step in verification is determining the challenge, B. We cannot let the prover simply choose B, otherwise they could forge a valid-looking proof without doing the actual work. Instead, we use the Fiat-Shamir heuristic to make the process non-interactive and secure. We hash the public inputs together to deterministically generate a prime number. In our implementation, the function get_b takes the generator element x and the final output element y. It runs them through a secure cryptographic hash function to produce a 264-bit prime challenge. Because both the prover and verifier use the exact same deterministic hashing process on the identical inputs, they both arrive at the exact same prime B. The prover could not know B before they finished computing y, locking them into the honest computation.

-> See: kyn-vdf/src/chia.rs — Lines 616 to 640 Once the challenge B is derived, we must calculate the remainder r. Imagine dividing the total number of squarings 2^T by our challenge prime B. You would get some quotient q and a remainder r. , this is expressed as: 2^T = q * B + r. The verifier needs the remainder r. Fortunately, r is trivial to calculate quickly using modular arithmetic because we don’t actually need to compute the number 2^T. We simply compute r = 2^T mod B. Since B is a 264-bit prime, this modular exponentiation is fast, executing in microseconds.

-> See: kyn-vdf/src/chia.rs — Lines 641 to 670 Now we enter the core execution of verify_wesolowski. The prover has provided the proof pi. Under the hood, pi is actually the generator x raised to the quotient q (so, pi = x^q). The prover had to calculate this during the proof generation phase. We need to prove that the output equals the generator after T squarings: y = x^(2^T). We substitute our division formula for 2^T into the equation: y = x^(q * B + r). Using basic exponent rules, we can split this into two parts: y = (x^q)^B * x^r. Since the prover gave us pi (which represents x^q), we substitute pi in: y = pi^B * x^r. This is exactly the equation that verify_wesolowski calculates and checks. It takes the proof pi, raises it to the power of our derived prime challenge B, and multiplies it by the generator x raised to our calculated remainder r. If the result equals the claimed output y, the proof is cryptographically valid. Raising elements to the power of B and r only takes O(log B) operations using square-and-multiply algorithms. Since B is a 264-bit number, it takes roughly 264 squarings to verify the proof. It does not matter if T was a thousand or a billion; verification always takes roughly 264 operations. This is the breakthrough of Wesolowski’s protocol: compressing 30 days of computation into less than 100 milliseconds of verification time.

-> See: kyn-vdf/src/lib.rs While chia.rs handles the complex underlying mathematics, lib.rs provides the clean, unified entry point for the rest of the application: verify_chia_vdf. It bridges the gap between the raw bytes received over the Kinetic peer-to-peer network and the complex mathematical objects required by the verifier. It takes the serialized byte arrays for the discriminant, x, y, and the proof pi. It deserializes them, handles instantiation of the class group elements, and then cleanly orchestrates the call into verify_wesolowski. It acts as a strict abstraction boundary, shielding the rest of the Kinetic network daemon from the mathematical complexity, presenting a simple function that returns a successful Result or an error.

Key Pieces

get_b (Function)

-> See: kyn-vdf/src/chia.rs This function is responsible for executing the Fiat-Shamir transformation. It takes the serialized forms of the input generator and the output element, concatenates them, and hashes them repeatedly until it finds a valid 264-bit prime number. This matters tremendously because the entire security model of Wesolowski verification rests on B being a prime number that the prover could not predict or manipulate. By deriving it directly from the hash of the output y, we force the prover to finish computing the time-delay y before they can even know what B is, rendering forgery impossible.

verify_wesolowski (Function)

-> See: kyn-vdf/src/chia.rs This is the core mathematical engine of the crate. It is the direct implementation of the pi^B * x^r = y validation logic. It takes the generator x, the output y, the proof pi, the prime challenge B, and the remainder r. It performs the necessary imaginary quadratic class group operations— specifically squarings and multiplications— to evaluate the left side of the equation. This function relies on an underlying big-integer arithmetic library to handle the numbers involved. It is deeply optimized because a node syncing to the Kinetic network from scratch might need to verify thousands of these proofs in rapid succession.

verify_chia_vdf (Function)

-> See: kyn-vdf/src/lib.rs This is the primary public API of the crate. It is the integration and orchestration point. It accepts raw byte slices (&[u8]) for the inputs and the iteration count T. It safely parses these bytes into the mathematical ClassGroupElement structures, diligently handles any potential deserialization errors or malformed data, and coordinates the execution of verify_wesolowski. This is the function that the higher-level Kinetic daemon, the block validator, or the WASM wrapper will actively call.

KynVdfError (Enum)

-> See: kyn-vdf/src/error.rs This file defines the comprehensive taxonomy of everything that can possibly go wrong during the validation process. Using the thiserror crate, it defines clear variants such as:

  • InvalidProofLength: Triggered if the byte array for pi is the wrong size.
  • InvalidDiscriminantIdentity: Triggered if the prime discriminant doesn’t meet the strict cryptographic requirements for the class group.
  • VerificationFailed: Triggered if the core math equation simply doesn’t balance out. This matters deeply because in an untrusted decentralized network, a node will constantly receive garbage data or malicious payloads. You must reject bad data with precise, typed, and understandable reasons to prevent cascading network failures and to allow developers to debug complex peer-to-peer data transmission issues effectively. The thiserror crate shines here by automatically deriving the Display and Error traits, eliminating hundreds of lines of tedious boilerplate code.

How This Connects to the Rest of Kinetic

CROSS-CRATE: This entire module acts as a vital, foundational cryptographic primitive for both the kinetic-verify and kinetic-node crates. When a Kinetic node receives a new block over the network or a time-challenge response, it cannot trust the data blindly. It extracts the raw VDF bytes from the block header and passes them directly to verify_chia_vdf exposed by lib.rs. If the function returns an error, the block is instantly dropped and the peer is penalized.

FORWARD DEPENDENCY: Because this verification logic is written in pure Rust and avoids standard library operating system calls (utilizing #![no_std]), it directly enables the kinetic-wasm crate. The kinetic-wasm crate will simply import kyn-vdf, wrap the verify_chia_vdf function in wasm-bindgen bindings, and output a Javascript-compatible WebAssembly module. This architecture guarantees that a Kinetic light client running in a web browser can verify time proofs locally and securely, without ever having to trust a centralized RPC server. This keeps the network genuinely decentralized down to the consumer frontend.

Quick Reference

  • The Core Equation: pi^B * x^r == y
  • T: The number of iterations (squarings), representing the exact amount of time elapsed.
  • x: The initial generator value (the public input).
  • y: The output value after precisely T squarings.
  • pi: The proof element provided by the block generator.
  • B: The Fiat-Shamir prime challenge, securely derived by hashing x and y.
  • r: The remainder of 2^T mod B.
  • Time Complexity: Proving requires O(T) sequential steps. Verifying requires only O(log B) steps.
  • Portability Factor: Written in Pure Rust with zero C++ dependencies. no_std compatible, suited for WebAssembly compilation.

Open Questions / Things to Revisit

  • Class Group Arithmetic Optimization: Are the underlying multiplications and squarings within verify_wesolowski as heavily optimized as possible? While the overall verification complexity is a minimal O(log B), implementing constant-time optimizations or advanced algorithms (like NUDUPL) in the underlying large integer library could yield measurable sync-time improvements during initial block download.
  • WebAssembly Bundle Size: While pure Rust guarantees WASM compatibility, pulling in robust large integer math libraries might significantly inflate the final .wasm binary size. We should actively monitor and measure the compiled bundle size of kinetic-wasm to ensure it remains lightweight enough for instantaneous browser loading.
  • Error Granularity and Telemetry: Should the generic VerificationFailed error variant be split into more specific granular variants? For example, separating a “mathematical mismatch” from an “invalid group element structure” might vastly improve debugging capabilities when analyzing edge cases in peer-to-peer data transmission or identifying new attack vectors on the node software.