Skip to content

Commit

Permalink
Add Pietrzak's VDF construction (#818)
Browse files Browse the repository at this point in the history
* First draft of pietrzak's vdf

* Use fs

* clean up

* Handle iterations that are not a power of two

* Remove obsolete comment

* simplify

* Allow shorter proofs

* refactor

* clean up + docs

* docs

* Refactor

* Compute proof size

* Use biguint for challenge

* unused import

* refactor

* clean up

* simplify

* comment

* simplify

* simplify more

* clean up

* simplify

* fmt

* Fix iteration computation

* clean up fs

* simplify

* Review comments

* Handle overflow gracefully
  • Loading branch information
jonas-lj authored Sep 5, 2024
1 parent c85d4cf commit 25bd5d8
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 69 deletions.
18 changes: 6 additions & 12 deletions fastcrypto-vdf/src/class_group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,26 +205,22 @@ impl QuadraticForm {
}

impl Doubling for QuadraticForm {
fn double(&self) -> Self {
fn double(self) -> Self {
// Slightly optimised version of Algorithm 2 from Jacobson, Jr, Michael & Poorten, Alfred
// (2002). "Computational aspects of NUCOMP", Lecture Notes in Computer Science.
// (https://www.researchgate.net/publication/221451638_Computational_aspects_of_NUCOMP)
// The paragraph numbers and variable names follow the paper.

let u = &self.a;
let v = &self.b;
let w = &self.c;

let EuclideanAlgorithmOutput {
gcd: g,
x: _,
y,
a_divided_by_gcd: capital_by,
b_divided_by_gcd: capital_dy,
} = extended_euclidean_algorithm(u, v, false);
} = extended_euclidean_algorithm(&self.a, &self.b, false);

let (bx, x, by, y, iterated) = partial_xgcd(
(&y * w).mod_floor(&capital_by),
(&y * &self.c).mod_floor(&capital_by),
capital_by.clone(),
self.partial_gcd_limit(),
);
Expand All @@ -234,11 +230,11 @@ impl Doubling for QuadraticForm {
let mut v3 = -(by * &bx) << 1;

if !iterated {
let dx = (&bx * &capital_dy - w) / &capital_by;
v3 += v;
let dx = (&bx * &capital_dy - &self.c) / &capital_by;
v3 += &self.b;
w3 -= &g * &dx;
} else {
let dx = (&bx * &capital_dy - w * &x) / &capital_by;
let dx = (&bx * &capital_dy - &self.c * &x) / &capital_by;
let q1 = &dx * &y;
let mut dy = &q1 + &capital_dy;
v3 += &g * (&dy + &q1);
Expand Down Expand Up @@ -295,8 +291,6 @@ impl ParameterizedGroupElement for QuadraticForm {
/// The discriminant of a quadratic form defines the class group.
type ParameterType = Discriminant;

type ScalarType = BigInt;

fn zero(discriminant: &Self::ParameterType) -> Self {
Self::from_a_b_and_discriminant(BigInt::one(), BigInt::one(), discriminant)
.expect("Doesn't fail")
Expand Down
57 changes: 52 additions & 5 deletions fastcrypto-vdf/src/math/parameterized_group.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::ops::{Add, Neg};

use fastcrypto::error::FastCryptoResult;
use fastcrypto::groups::Doubling;
use num_bigint::BigUint;
use std::ops::{Add, Neg};

/// This trait is implemented by types which can be used as parameters for a parameterized group.
/// See [ParameterizedGroupElement].
Expand All @@ -21,12 +21,59 @@ pub trait ParameterizedGroupElement:
/// The type of the parameter which uniquely defines this group.
type ParameterType: Parameter;

/// Integer type used for multiplication.
type ScalarType: From<u64>;

/// Return an instance of the identity element in this group.
fn zero(parameter: &Self::ParameterType) -> Self;

/// Returns true if this is an element of the group defined by `parameter`.
fn is_in_group(&self, parameter: &Self::ParameterType) -> bool;
}

/// Compute self * scalar using a "Double-and-Add" algorithm for a positive scalar.
pub(crate) fn multiply<G: ParameterizedGroupElement>(
input: &G,
scalar: &BigUint,
parameter: &G::ParameterType,
) -> G {
(0..scalar.bits())
.rev()
.map(|i| scalar.bit(i))
.fold(G::zero(parameter), |acc, bit| {
let mut res = acc.double();
if bit {
res = res + input;
}
res
})
}

#[cfg(test)]
mod tests {
use crate::class_group::discriminant::Discriminant;
use crate::class_group::QuadraticForm;
use crate::math::parameterized_group::{multiply, Parameter, ParameterizedGroupElement};
use num_bigint::BigUint;
use num_traits::{One, Zero};

#[test]
fn test_scalar_multiplication() {
let discriminant = Discriminant::from_seed(b"test", 256).unwrap();
let input = QuadraticForm::generator(&discriminant);

// Edge cases
assert_eq!(
QuadraticForm::zero(&discriminant),
multiply(&input, &BigUint::zero(), &discriminant)
);
assert_eq!(input, multiply(&input, &BigUint::one(), &discriminant));

let exponent = 12345u64;
let output = multiply(&input, &BigUint::from(exponent), &discriminant);

// Check alignment with repeated addition.
let mut expected_output = input.clone();
for _ in 1..exponent {
expected_output = expected_output + &input;
}
assert_eq!(output, expected_output);
}
}
1 change: 1 addition & 0 deletions fastcrypto-vdf/src/vdf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use fastcrypto::error::FastCryptoResult;

pub mod pietrzak;
pub mod wesolowski;

/// This represents a Verifiable Delay Function (VDF) construction.
Expand Down
179 changes: 179 additions & 0 deletions fastcrypto-vdf/src/vdf/pietrzak/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::math::parameterized_group::{multiply, ParameterizedGroupElement};
use crate::vdf::VDF;
use fastcrypto::error::FastCryptoError::{InvalidInput, InvalidProof};
use fastcrypto::error::FastCryptoResult;
use fastcrypto::hash::{HashFunction, Keccak256};
use num_bigint::BigUint;
use num_integer::Integer;
use serde::Serialize;
use std::mem;

/// Default size in bytes of the Fiat-Shamir challenge used in proving and verification.
///
/// This is based on Pietrzak (2018), "Simple Verifiable Delay Functions" (https://eprint.iacr.org/2018/627.pdf)
/// which states that the challenge should be 2^l bits (see section 6), where l is the security
/// parameter. Soundness is proven in section 6.3 in this paper.
pub const DEFAULT_CHALLENGE_SIZE_IN_BYTES: usize = 16;

/// This implements Pietrzak's VDF construction from https://eprint.iacr.org/2018/627.pdf.
///
/// The VDF is, as in [crate::vdf::wesolowski::WesolowskisVDF], based on the repeated squaring of an
/// element in a group of unknown order. However, in this construction, proofs are larger and
/// verification is slower than in Wesolowski's construction, but the output of a VDF is unique,
/// assuming that the used group have no small subgroups, and proving is faster for the same number
/// of iterations.
pub struct PietrzaksVDF<G: ParameterizedGroupElement> {
group_parameter: G::ParameterType,
iterations: u64,
}

impl<G: ParameterizedGroupElement> PietrzaksVDF<G> {
/// Create a new VDF using the group defined by the given group parameter. Evaluating this VDF
/// will require computing `2^iterations * input` which requires `iterations` group operations.
pub fn new(group_parameter: G::ParameterType, iterations: u64) -> Self {
Self {
group_parameter,
iterations,
}
}
}

impl<G: ParameterizedGroupElement + Serialize> VDF for PietrzaksVDF<G>
where
G::ParameterType: Serialize,
{
type InputType = G;
type OutputType = G;
type ProofType = Vec<G>;

fn evaluate(&self, input: &G) -> FastCryptoResult<(G, Vec<G>)> {
// Proof generation works but is not optimised.

if !input.is_in_group(&self.group_parameter) || self.iterations == 0 {
return Err(InvalidInput);
}

// Compute output = 2^iterations * input
let output = input.clone().repeated_doubling(self.iterations);

let mut x = input.clone();
let mut y = output.clone();
let mut t = self.iterations;

let mut proof = Vec::new();

// Compute the full proof. This loop may stop at any time which will give a shorter proof
// that is computationally harder to verify.
while t != 1 {
if check_parity_and_iterate(&mut t) {
y = y.double();
}

// TODO: Precompute some of the mu's to speed up the proof generation.
let mu = x.clone().repeated_doubling(t);

let r = compute_challenge(&x, &y, self.iterations, &mu, &self.group_parameter);
x = multiply(&x, &r, &self.group_parameter) + &mu;
y = multiply(&mu, &r, &self.group_parameter) + &y;

proof.push(mu);
}

Ok((output, proof))
}

fn verify(&self, input: &G, output: &G, proof: &Vec<G>) -> FastCryptoResult<()> {
if !input.is_in_group(&self.group_parameter)
|| !output.is_in_group(&self.group_parameter)
|| !proof.iter().all(|mu| mu.is_in_group(&self.group_parameter))
|| self.iterations == 0
{
return Err(InvalidInput);
}

let mut x = input.clone();
let mut y = output.clone();
let mut t = self.iterations;

for mu in proof {
if check_parity_and_iterate(&mut t) {
y = y.double();
}

let r = compute_challenge(&x, &y, self.iterations, mu, &self.group_parameter);
x = multiply(&x, &r, &self.group_parameter) + mu;
y = multiply(mu, &r, &self.group_parameter) + y;
}

// In case the proof is shorter than the full proof, we need to compute the remaining powers.
x = x.repeated_doubling(t);
if x != y {
return Err(InvalidProof);
}
Ok(())
}
}

/// Compute the Fiat-Shamir challenge used in Pietrzak's VDF construction.
fn compute_challenge<G: ParameterizedGroupElement + Serialize>(
input: &G,
output: &G,
iterations: u64,
mu: &G,
group_parameter: &G::ParameterType,
) -> BigUint
where
G::ParameterType: Serialize,
{
let seed = bcs::to_bytes(&(input, output, iterations, mu, group_parameter))
.expect("Failed to serialize Fiat-Shamir input.");
let hash = Keccak256::digest(seed);
BigUint::from_bytes_be(&hash.digest[..DEFAULT_CHALLENGE_SIZE_IN_BYTES])
}

/// Replace t with (t+1) >> 1 and return true iff the input was odd.
#[inline]
fn check_parity_and_iterate(t: &mut u64) -> bool {
mem::replace(t, (*t >> 1) + (*t & 1)).is_odd()
}

#[cfg(test)]
mod tests {
use crate::class_group::discriminant::Discriminant;
use crate::class_group::QuadraticForm;
use crate::math::parameterized_group::Parameter;
use crate::vdf::pietrzak::PietrzaksVDF;
use crate::vdf::VDF;

#[test]
fn test_vdf() {
let iterations = 136u64;
let discriminant = Discriminant::from_seed(&[0, 1, 2], 512).unwrap();

let input = QuadraticForm::generator(&discriminant);

let vdf = PietrzaksVDF::<QuadraticForm>::new(discriminant.clone(), iterations);
let (output, proof) = vdf.evaluate(&input).unwrap();

assert!(vdf.verify(&input, &output, &proof).is_ok());

let other_input = input.clone() + &input;
assert!(vdf.verify(&other_input, &output, &proof).is_err())
}

#[test]
fn test_vdf_edge_cases() {
let discriminant = Discriminant::from_seed(&[0, 1, 2], 512).unwrap();
let input = QuadraticForm::generator(&discriminant);

assert!(PietrzaksVDF::<QuadraticForm>::new(discriminant.clone(), 1)
.evaluate(&input)
.is_ok());
assert!(PietrzaksVDF::<QuadraticForm>::new(discriminant.clone(), 0)
.evaluate(&input)
.is_err());
}
}
30 changes: 7 additions & 23 deletions fastcrypto-vdf/src/vdf/wesolowski/fiat_shamir.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::class_group::discriminant::Discriminant;
use crate::class_group::QuadraticForm;
use crate::math::hash_prime::hash_prime;
use crate::math::parameterized_group::ParameterizedGroupElement;
use crate::vdf::wesolowski::WesolowskisVDF;
use fastcrypto::groups::multiplier::ScalarMultiplier;
use num_bigint::BigInt;
use serde::Serialize;
use num_bigint::BigUint;

/// Default size in bytes of the Fiat-Shamir challenge used in proving and verification.
///
Expand All @@ -23,11 +21,11 @@ pub const DEFAULT_CHALLENGE_SIZE_IN_BYTES: usize = 33;
pub trait FiatShamir<G: ParameterizedGroupElement>: Sized {
/// Compute the prime modulus used in proving and verification. This is a Fiat-Shamir construction
/// to make the Wesolowski VDF non-interactive.
fn compute_challenge<M: ScalarMultiplier<G, G::ScalarType>>(
fn compute_challenge<M: ScalarMultiplier<G, BigUint>>(
vdf: &WesolowskisVDF<G, Self, M>,
input: &G,
output: &G,
) -> G::ScalarType;
) -> BigUint;
}

/// Implementation of the Fiat-Shamir challenge generation for usage with Wesolowski's VDF construction.
Expand All @@ -36,31 +34,17 @@ pub trait FiatShamir<G: ParameterizedGroupElement>: Sized {
pub struct StrongFiatShamir {}

impl FiatShamir<QuadraticForm> for StrongFiatShamir {
fn compute_challenge<M: ScalarMultiplier<QuadraticForm, BigInt>>(
fn compute_challenge<M: ScalarMultiplier<QuadraticForm, BigUint>>(
vdf: &WesolowskisVDF<QuadraticForm, Self, M>,
input: &QuadraticForm,
output: &QuadraticForm,
) -> BigInt {
let seed = bcs::to_bytes(&FiatShamirInput {
input,
output,
iterations: vdf.iterations,
group_parameter: &vdf.group_parameter,
})
.expect("Failed to serialize FiatShamirInput");
) -> BigUint {
let seed = bcs::to_bytes(&(input, output, vdf.iterations, &vdf.group_parameter))
.expect("Failed to serialize Fiat-Shamir input");
hash_prime(
&seed,
DEFAULT_CHALLENGE_SIZE_IN_BYTES,
&[0, 8 * DEFAULT_CHALLENGE_SIZE_IN_BYTES - 1],
)
.into()
}
}

#[derive(Serialize)]
struct FiatShamirInput<'a> {
input: &'a QuadraticForm,
output: &'a QuadraticForm,
iterations: u64,
group_parameter: &'a Discriminant,
}
Loading

0 comments on commit 25bd5d8

Please sign in to comment.