ceremonyclient/crates/vdf/src/lib.rs
Cassandra Heart d1b833e8b1
v2.1.0.2
2025-10-05 19:24:02 -05:00

378 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2018 Chia Network Inc and POA Networks Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO(2.1.1): Uncomment and resolve unpredictable_function_pointer_comparisons by upgrading uniffi (more work than just a version bump)
// #![deny(warnings)]
//! # Rust implementations of class groups and verifiable delay functions
//!
//! This repo includes three crates
//!
//! * `classgroup`, which includes a class group implementation, as well as a
//! trait for class groups.
//! * `vdf`, which includes a Verifiable Delay Function (VDF) trait, as well as
//! an implementation of that trait.
//! * `vdf-cli`, which includes a command-line interface to the `vdf` crate. It
//! also includes additional commands, which are deprecated and will later be
//! replaced by a CLI to the `classgroup` crate.
//!
//! ## Usage
//!
//! First, install Rust, Cargo, and the GNU Multiprecision Library (GMP). Then,
//! follow one of the below steps.
//!
//! ### To use the command line interface
//!
//! ```sh
//! $ git clone https://github.com/poanetwork/vdf
//! $ cd vdf
//! $ cargo install
//! $ vdf-cli aa 100
//! 005271e8f9ab2eb8a2906e851dfcb5542e4173f016b85e29d481a108dc82ed3b3f97937b7aa824801138d1771dea8dae2f6397e76a80613afda30f2c30a34b040baaafe76d5707d68689193e5d211833b372a6a4591abb88e2e7f2f5a5ec818b5707b86b8b2c495ca1581c179168509e3593f9a16879620a4dc4e907df452e8dd0ffc4f199825f54ec70472cc061f22eb54c48d6aa5af3ea375a392ac77294e2d955dde1d102ae2ace494293492d31cff21944a8bcb4608993065c9a00292e8d3f4604e7465b4eeefb494f5bea102db343bb61c5a15c7bdf288206885c130fa1f2d86bf5e4634fdc4216bc16ef7dac970b0ee46d69416f9a9acee651d158ac64915b
//! $ vdf-cli aa 100 005271e8f9ab2eb8a2906e851dfcb5542e4173f016b85e29d481a108dc82ed3b3f97937b7aa824801138d1771dea8dae2f6397e76a80613afda30f2c30a34b040baaafe76d5707d68689193e5d211833b372a6a4591abb88e2e7f2f5a5ec818b5707b86b8b2c495ca1581c179168509e3593f9a16879620a4dc4e907df452e8dd0ffc4f199825f54ec70472cc061f22eb54c48d6aa5af3ea375a392ac77294e2d955dde1d102ae2ace494293492d31cff21944a8bcb4608993065c9a00292e8d3f4604e7465b4eeefb494f5bea102db343bb61c5a15c7bdf288206885c130fa1f2d86bf5e4634fdc4216bc16ef7dac970b0ee46d69416f9a9acee651d158ac64915b
//! Proof is valid
//! ```
//!
//! ### To use the VDF library
//!
//! ```rust
//! extern crate vdf;
//! use vdf::{InvalidProof, PietrzakVDFParams, VDFParams, WesolowskiVDFParams, VDF};
//! const CORRECT_SOLUTION: &[u8] =
//! b"\x00\x52\x71\xe8\xf9\xab\x2e\xb8\xa2\x90\x6e\x85\x1d\xfc\xb5\x54\x2e\x41\x73\xf0\x16\
//! \xb8\x5e\x29\xd4\x81\xa1\x08\xdc\x82\xed\x3b\x3f\x97\x93\x7b\x7a\xa8\x24\x80\x11\x38\
//! \xd1\x77\x1d\xea\x8d\xae\x2f\x63\x97\xe7\x6a\x80\x61\x3a\xfd\xa3\x0f\x2c\x30\xa3\x4b\
//! \x04\x0b\xaa\xaf\xe7\x6d\x57\x07\xd6\x86\x89\x19\x3e\x5d\x21\x18\x33\xb3\x72\xa6\xa4\
//! \x59\x1a\xbb\x88\xe2\xe7\xf2\xf5\xa5\xec\x81\x8b\x57\x07\xb8\x6b\x8b\x2c\x49\x5c\xa1\
//! \x58\x1c\x17\x91\x68\x50\x9e\x35\x93\xf9\xa1\x68\x79\x62\x0a\x4d\xc4\xe9\x07\xdf\x45\
//! \x2e\x8d\xd0\xff\xc4\xf1\x99\x82\x5f\x54\xec\x70\x47\x2c\xc0\x61\xf2\x2e\xb5\x4c\x48\
//! \xd6\xaa\x5a\xf3\xea\x37\x5a\x39\x2a\xc7\x72\x94\xe2\xd9\x55\xdd\xe1\xd1\x02\xae\x2a\
//! \xce\x49\x42\x93\x49\x2d\x31\xcf\xf2\x19\x44\xa8\xbc\xb4\x60\x89\x93\x06\x5c\x9a\x00\
//! \x29\x2e\x8d\x3f\x46\x04\xe7\x46\x5b\x4e\xee\xfb\x49\x4f\x5b\xea\x10\x2d\xb3\x43\xbb\
//! \x61\xc5\xa1\x5c\x7b\xdf\x28\x82\x06\x88\x5c\x13\x0f\xa1\xf2\xd8\x6b\xf5\xe4\x63\x4f\
//! \xdc\x42\x16\xbc\x16\xef\x7d\xac\x97\x0b\x0e\xe4\x6d\x69\x41\x6f\x9a\x9a\xce\xe6\x51\
//! \xd1\x58\xac\x64\x91\x5b";
//!
//! fn main() {
//! let pietrzak_vdf = PietrzakVDFParams(2048).new();
//! assert_eq!(
//! &pietrzak_vdf.solve(b"\xaa", 100).unwrap()[..],
//! CORRECT_SOLUTION
//! );
//! assert!(pietrzak_vdf.verify(b"\xaa", 100, CORRECT_SOLUTION).is_ok());
//! }
//! ```
//!
//! ### To run the benchmarks
//!
//! Benchmarks are provided for the classgroup operations. Run `cargo bench`
//! to run them. Additional benchmarks are under development.
use classgroup;
mod create_discriminant;
use std::fmt::Debug;
pub use self::{
create_discriminant::create_discriminant,
proof_pietrzak::{PietrzakVDF, PietrzakVDFParams},
proof_wesolowski::{WesolowskiVDF, WesolowskiVDFParams},
};
/// Message used to report an internal miscalculation of serialization buffer
/// sizes.
const INCORRECT_BUFFER_SIZE: &str =
"internal error: incorrect buffer size calculation (this is a bug)";
mod proof_of_time;
mod proof_pietrzak;
mod proof_wesolowski;
uniffi::include_scaffolding!("lib");
/// An empty struct indicating verification failure.
///
/// For security reasons, the functions that perform verification *do not*
/// return any information on failure. Use `VDF::validate_params` to check if
/// the parameters are correct.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)]
pub struct InvalidProof;
/// An error return indicating an invalid number of iterations. The string is a
/// human-readable message describing the valid iterations. It should not be
/// interpreted by programs.
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)]
pub struct InvalidIterations(String);
/// The type of VDF parameters.
///
/// Parameters represent public information that can be shared by all users
/// of the protocol. As such, they must implement `Clone`, so that they can
/// be duplicated. They also must implement `Send`, so that a parallel
/// application can send them safely across threads.
///
/// The parameters *do not* include the difficulty level (usually an
/// iteration count), since that can be separate for each invocation.
///
/// This must implement `Clone` and `Eq`.
pub trait VDFParams: Clone + Eq {
type VDF: VDF + Sized;
/// Creates an instance of this VDF from the given parameters.
///
/// # Performance
///
/// This method is expected to be fairly cheap. For example, it is okay if
/// it allocates memory, but it should not perform expensive computations or
/// I/O.
///
/// # Panics
///
/// This method **MUST NOT** fail due to invalid values for `params`. Such
/// errors should be checked by the factory functions for `Self::Params`.
///
/// This function **MAY** panic for other reasons. For example, it is
/// allowed to panic if an allocation fails, or if a needed external library
/// could not be dynamically loaded.
fn new(self) -> Self::VDF;
}
/// A Verifiable Delay Function (VDF).
///
/// VDFs are problems that require a certain amount of time to solve, even on a
/// parallel machine, but can be validated much more easily.
///
/// While VDFs are considered to be cryptographic primitives, they generally do
/// *not* operate on highly sensitive data. As such, implementers of this trait
/// **do not** guarantee that they will be immune to side-channel attacks, and
/// consumers of this trait **MUST NOT** expect this.
///
/// Instances of this trait are *not* expected to be `Sync`. This allows them
/// to reuse allocations (such as scratch memory) across invocations without
/// the need for locking. However, they **MUST** be `Send` and `Clone`, so that
/// consumers can duplicate them and send them across threads.
pub trait VDF: Send + Debug {
/// Solve an instance of this VDF, with challenge `challenge` and difficulty
/// `difficulty`.
///
/// The output is to be returned in a `Vec<u8>`, so it can be stored to disk
/// or sent over the network.
///
/// # Challenge format
///
/// The challenge is an opaque byte string of arbitrary length.
/// Implementors **MUST NOT** make any assumptions about its contents,
/// and **MUST** produce distinct outputs for distinct challenges
/// (except with negligible probability).
///
/// This can be most easily implemented by using the challenge as part of
/// the input of a cryptographic hash function. The VDFs provided in this
/// crate use this strategy.
///
/// The difficulty must be checked before performing any expensive
/// computations.
///
/// Most applications will generate the challenge using a
/// cryptographically-secure pseudorandom number generator, but implementors
/// **MUST NOT** rely on this. In particular, this function must be secure
/// even if `challenge` is chosen by an adversary. Excessive values for
/// `difficulty` may cause excessive resource consumption, but must not
/// create any other vulnerabilities.
///
/// # Complexity
///
/// The VDFs in this crate consume memory that does not depend on
/// `difficulty`, and time linearly proportional to `difficulty`.
/// Implementors of this trait should document the resource use.
///
/// # Purity
///
/// This method must have no side effects. In particular, it must be
/// **deterministic**: it must always return the same output for the same
/// inputs, except with negligible probability. Furthermore, while it may
/// change `self` via interior mutability, such changes must not affect
/// future calls to this method, `Self::check_difficulty`, or
/// `Self::verify`. They *may* affect the `Debug` output.
fn solve(&self, challenge: &[u8], difficulty: u64) -> Result<Vec<u8>, InvalidIterations>;
/// Check that the difficulty is valid.
///
/// This must return `Ok` if and only if `difficulty` is valid. Otherwise,
/// it must return `Err`.
///
/// # Rationale
///
/// It would be more idiomatic Rust to use the type system to enforce that a
/// difficulty has been validated before use. However, I (Demi) have not
/// yet figured out an object-safe way to do so.
fn check_difficulty(&self, difficulty: u64) -> Result<(), InvalidIterations>;
/// Verifies an alleged solution of this VDF, with challenge `challenge` and
/// difficulty `difficulty`. Return `Ok(())` on success, or
/// `Err(InvalidProof)` on failure.
///
/// This function *does not* return any extended error information for
/// security reasons. To check that the difficulty is correct, call
/// `Self::check_difficulty`.
///
/// # Uniqueness of valid solutions
///
/// For any `(challenge, difficulty)` tuple, there must be at most one
/// `alleged_solution` (as measured by `Eq`) that causes this function to
/// return `Ok(())`. If the difficulty is valid (as determined by
/// `check_difficulty`), there must be exactly one such solution; otherwise,
/// there must be none.
///
/// # Purity
///
/// This method must have no side effects. In particular, it must be
/// **deterministic**: it must always return the same output for the same
/// inputs. Furthermore, while it may change `self` via interior
/// mutability, such changes must not affect future calls to this method,
/// `Self::prove`, or `Self::check_difficulty`. Such changes **MAY** affect
/// debugging output.
fn verify(
&self,
challenge: &[u8],
difficulty: u64,
alleged_solution: &[u8],
) -> Result<(), InvalidProof>;
}
/// Solve and prove with the Wesolowski VDF using the given parameters.
/// Outputs the concatenated solution and proof (in this order).
pub fn wesolowski_solve(int_size_bits: u16, challenge: &[u8], difficulty: u32) -> Vec<u8> {
let vdf = WesolowskiVDFParams(int_size_bits).new();
vdf.solve(challenge, difficulty.into()).expect("invalid difficulty")
}
/// Verify with the Wesolowski VDF using the given parameters.
/// `alleged_solution` is the output of `wesolowski_solve`.
pub fn wesolowski_verify(int_size_bits: u16, challenge: &[u8], difficulty: u32, alleged_solution: &[u8]) -> bool {
let vdf = WesolowskiVDFParams(int_size_bits).new();
vdf.verify(challenge, difficulty.into(), alleged_solution).is_ok()
}
/// Solve (single worker `i`) with Wesolowski using ID-bound base and a shared prime `b`.
/// Produces this workers blob in the same layout as `wesolowski_solve`: `[y_i | π_i]`.
pub fn wesolowski_solve_multi(
int_size_bits: u16,
challenge: &[u8],
difficulty: u32,
ids: &Vec<Vec<u8>>,
i: u32,
) -> Vec<u8> {
use classgroup::{gmp_classgroup::GmpClassGroup, BigNumExt, ClassGroup};
use crate::proof_of_time::serialize;
use crate::proof_wesolowski::{commit_ids, hash_prime_fixed, worker_prove_id_bound};
assert!(!ids.is_empty(), "ids must be non-empty");
assert!((i as usize) < ids.len(), "worker index out of range");
// Bind the entire ID set and derive shared prime b
let ids_commitment = commit_ids(&ids);
let b = hash_prime_fixed::<<GmpClassGroup as ClassGroup>::BigNum>(
challenge,
int_size_bits,
difficulty as u64,
&ids_commitment,
);
// This worker runs its own chain on its ID-bound base
let (y_i, pi_i) = worker_prove_id_bound::<<GmpClassGroup as ClassGroup>::BigNum, GmpClassGroup>(
challenge,
int_size_bits,
difficulty as u64,
&ids[i as usize],
&b,
);
serialize(&[pi_i], &y_i, int_size_bits as usize)
}
/// Verify all workers in one shot from their individual blobs.
/// `alleged_solutions` must be parallel to `ids`, each entry a `[y_i | π_i]` blob
/// produced by `wesolowski_solve_multi` for that same `id`.
pub fn wesolowski_verify_multi(
int_size_bits: u16,
challenge: &[u8],
difficulty: u32,
ids: &Vec<Vec<u8>>,
alleged_solutions: &Vec<Vec<u8>>,
) -> bool {
use classgroup::{gmp_classgroup::GmpClassGroup, BigNum, BigNumExt, ClassGroup};
use crate::proof_wesolowski::{commit_ids, hash_prime_fixed, hash_to_exponent};
// Basic shape checks
if ids.is_empty() || ids.len() != alleged_solutions.len() { return false; }
if (usize::MAX - 16) < int_size_bits as usize { return false; }
let elem_units = ((int_size_bits as usize) + 16) >> 4; // element is 2 * elem_units bytes
if elem_units == 0 { return false; }
let element_len = elem_units * 2;
let blob_len = element_len * 2; // [y | π]
// Discriminant and canonical base x
let disc: <GmpClassGroup as ClassGroup>::BigNum = create_discriminant(challenge, int_size_bits);
let x = GmpClassGroup::from_ab_discriminant(2.into(), 1.into(), disc.clone());
// Fixed prime b from (challenge, bits, t, ids_commitment)
let ids_slices: Vec<&[u8]> = ids.iter().map(|v| v.as_slice()).collect();
let ids_commitment = commit_ids(&ids_slices);
let b = hash_prime_fixed::<<GmpClassGroup as ClassGroup>::BigNum>(
challenge,
int_size_bits,
difficulty as u64,
&ids_commitment,
);
let two = <<GmpClassGroup as ClassGroup>::BigNum>::from(2u64);
if !(b > two) { return false; }
// r = 2^t mod b
let mut r = <<GmpClassGroup as ClassGroup>::BigNum>::from(0u64);
r.mod_powm(
&<<GmpClassGroup as ClassGroup>::BigNum>::from(2u64),
&<<GmpClassGroup as ClassGroup>::BigNum>::from(difficulty as u64),
&b,
);
// Aggregate Y = ∏ y_i and Π = ∏ π_i; compute S = Σ h_i
let (first_id, first_blob) = (&ids[0], &alleged_solutions[0]);
if first_blob.len() != blob_len { return false; }
let (y0_bytes, pi0_bytes) = first_blob.split_at(element_len);
let mut y_agg = GmpClassGroup::from_bytes(y0_bytes, disc.clone());
let mut pi_agg = GmpClassGroup::from_bytes(pi0_bytes, disc.clone());
let mut S = hash_to_exponent::<<GmpClassGroup as ClassGroup>::BigNum>(challenge, first_id.as_slice());
for (id_bytes, blob) in ids.iter().zip(alleged_solutions.iter()).skip(1) {
if blob.len() != blob_len { return false; }
let (y_bytes, pi_bytes) = blob.split_at(element_len);
let y_i = GmpClassGroup::from_bytes(y_bytes, disc.clone());
let pi_i = GmpClassGroup::from_bytes(pi_bytes, disc.clone());
y_agg = &y_agg * &y_i;
pi_agg = &pi_agg * &pi_i;
S = S + hash_to_exponent::<<GmpClassGroup as ClassGroup>::BigNum>(challenge, id_bytes.as_slice());
}
// Single aggregated check: Π^b * x^{ r * S } ?= Y
let mut lhs = pi_agg.clone();
lhs.pow(b.clone()); // Π^b
let mut x_exp = x.clone();
x_exp.pow(r * S); // x^{r*S}
lhs *= &x_exp;
lhs == y_agg
}