Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Pietrzak's VDF construction #818

Merged
merged 28 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>)> {
jonas-lj marked this conversation as resolved.
Show resolved Hide resolved
// 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 {
jonas-lj marked this conversation as resolved.
Show resolved Hide resolved
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;
jonas-lj marked this conversation as resolved.
Show resolved Hide resolved
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
Loading