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

Ensure size of hash function image #727

Merged
merged 16 commits into from
Feb 12, 2024
5 changes: 0 additions & 5 deletions fastcrypto-vdf/benches/class_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ fn qf_from_seed_single<M: Measurement>(discriminant_string: &str, group: &mut Be
Discriminant::try_from(BigInt::from_str_radix(discriminant_string, 10).unwrap())
.unwrap();

// The hash function fails if k is too large
if k > (discriminant.bits() >> 5) as u16 {
continue;
}

let bits = discriminant.bits();
group.bench_function(format!("{} bits/{}", bits, k), move |b| {
let mut seed = [0u8; 32];
Expand Down
53 changes: 40 additions & 13 deletions fastcrypto-vdf/src/class_group/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,79 @@
use rand::distributions::uniform::UniformSampler;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use std::cmp::min;
use std::ops::{AddAssign, ShlAssign, Shr};

impl QuadraticForm {
/// Generate a random quadratic form from a seed with the given discriminant. This method is deterministic and it is
/// a random oracle on a large subset of the class group, namely the group elements whose `a` coordinate is a
/// product of K primes all smaller than (sqrt(|discriminant|)/2)^{1/k}.
/// product of `k` primes all smaller than `(sqrt(|discriminant|)/2)^{1/k}`.
///
/// The K parameter must be smaller than the number of bits in the discriminant divided by 32 to ensure that the
/// sample space for the primes is large enough. Otherwise an error is returned.
/// Increasing `k` speeds-up the function (at least up to some break even point), but it also decreases the size of
/// the range of the hash function, so `k` must be picked no larger than the `k` computed in [largest_allowed_k]. If
/// it is larger, an [InvalidInput] error is returned.
pub fn hash_to_group(
seed: &[u8],
discriminant: &Discriminant,
k: u16,
) -> FastCryptoResult<Self> {
// Sample a and b such that a < sqrt(|discriminant|)/2 and b' is the square root of the
// discriminant modulo a.
// Sample a and b such that a < sqrt(|discriminant|)/2 and b is the square root of the discriminant modulo a.
let (a, mut b) = sample_modulus(discriminant, seed, k)?;

// b must be odd
// b must be odd but may be negative
if b.is_even() {
b -= &a;
}

Ok(QuadraticForm::from_a_b_discriminant(a, b, discriminant)
.expect("a and b are constructed such that this never fails"))
}

/// Generate a random quadratic form from a seed with the given discriminant. This method is deterministic, and it
/// is a random oracle on a large subset of the class group. This method picks a default `k` parameter and calls the
/// [hash_to_group](QuadraticForm::hash_to_group) function with this `k`.
pub fn hash_to_group_with_default_parameters(
seed: &[u8],
discriminant: &Discriminant,
) -> FastCryptoResult<Self> {
// Let k be the largest power of two in the range up to 64
let largest_k = largest_allowed_k(discriminant) + 1;
let k = min(64, largest_k.next_power_of_two() >> 1);
Self::hash_to_group(seed, discriminant, k)
}
}

/// Increasing `k` reduces the range of the hash function for a given discriminant. This function returns a choice of
/// `k` such that the range is at least `2^256`, and chooses this it as large as possible. Consult the paper for
/// details.
fn largest_allowed_k(discriminant: &Discriminant) -> u16 {
let bits = discriminant.bits();
let lambda = 256.0;
let log_b = bits as f64 / 2.0 - 1.0;
let numerator = log_b - lambda;
let denominator = (log_b * 2.0_f64.ln()).log2() + 1.0;
(numerator / denominator).floor() as u16
}

/// Sample a product of K primes and return this along with the square root of the discriminant modulo a.
/// Sample a product of `k` primes and return this along with the square root of the discriminant modulo `a`. If `k` is
/// larger than the largest allowed `k` (as computed in [largest_allowed_k]) for the given discriminant, an
/// [InvalidInput] error is returned.
fn sample_modulus(
discriminant: &Discriminant,
seed: &[u8],
k: u16,
) -> FastCryptoResult<(BigInt, BigInt)> {
// This heuristic bound ensures that the range of the hash function has size at least 2^256.
if k > largest_allowed_k(discriminant) {
return Err(InvalidInput);

Check warning on line 84 in fastcrypto-vdf/src/class_group/hash.rs

View check run for this annotation

Codecov / codecov/patch

fastcrypto-vdf/src/class_group/hash.rs#L84

Added line #L84 was not covered by tests
}

// If a is smaller than this bound and |b| < a, the form is guaranteed to be reduced.
let mut bound: BigInt = discriminant.as_bigint().abs().sqrt().shr(1);
if k > 1 {
bound = bound.nth_root(k as u32);
}

// This heuristic bound ensures that there will be enough distinct primes to sample from, so we won't end up in an
// infinite loop. Consult the paper for details on how to pick the parameters.
if k > (discriminant.bits() >> 5) as u16 {
return Err(InvalidInput);
}

// Seed a rng with the hash of the seed
let mut rng = ChaCha8Rng::from_seed(Sha256::digest(seed).digest);
let mut factors = Vec::with_capacity(k as usize);
Expand Down
20 changes: 14 additions & 6 deletions fastcrypto-vdf/src/class_group/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ use rand::{thread_rng, RngCore};

#[test]
fn test_multiplication() {
let discriminant = Discriminant::try_from(BigInt::from(-47)).unwrap();
let discriminant = Discriminant::from_seed(b"discriminant seed", 800).unwrap();
let generator = QuadraticForm::generator(&discriminant);
let mut current = QuadraticForm::zero(&discriminant);
for i in 0..10000 {
for i in 0..1000 {
assert_eq!(current, generator.mul(&BigInt::from(i)));
current = current + &generator;
}
Expand Down Expand Up @@ -73,7 +73,7 @@ fn test_discriminant_to_from_bytes() {
#[test]
fn test_qf_from_seed() {
let mut seed = [0u8; 32];
let discriminant = Discriminant::from_seed(&seed, 512).unwrap();
let discriminant = Discriminant::from_seed(&seed, 1024).unwrap();

for _ in 0..10 {
let qf = QuadraticForm::hash_to_group(&seed, &discriminant, 1).unwrap();
Expand All @@ -92,7 +92,7 @@ fn test_qf_from_seed() {

#[test]
fn qf_from_seed_sanity_tests() {
let discriminant = Discriminant::from_seed(b"discriminant seed", 512).unwrap();
let discriminant = Discriminant::from_seed(b"discriminant seed", 800).unwrap();
let base_qf = QuadraticForm::hash_to_group(b"qf seed", &discriminant, 6).unwrap();
assert_eq!(base_qf.discriminant(), discriminant);

Expand All @@ -109,14 +109,14 @@ fn qf_from_seed_sanity_tests() {
assert_ne!(base_qf, other_qf);

let mut seed = [0u8; 32];
for _ in 0..1000 {
for _ in 0..10 {
// Different seed
thread_rng().fill_bytes(&mut seed);
let other_qf = QuadraticForm::hash_to_group(&seed, &discriminant, 6).unwrap();
assert_ne!(base_qf, other_qf);
}

let other_discriminant = Discriminant::from_seed(b"other_discriminant seed", 512).unwrap();
let other_discriminant = Discriminant::from_seed(b"other discriminant seed", 800).unwrap();
// Same seed, same k, other discriminant
let other_qf = QuadraticForm::hash_to_group(b"qf seed", &other_discriminant, 6).unwrap();
assert_ne!(base_qf, other_qf);
Expand All @@ -141,3 +141,11 @@ fn qf_from_seed_regression_tests() {
let qf = QuadraticForm::hash_to_group(b"seed", &discriminant, 4).unwrap();
assert_eq!(qf.to_bytes(), hex::decode("009602d12a2199c0d002e25d9f3ab2972ca4a625d272801e86da11690b28ce005e83ac8f58d6ff97945d2e15a6fba11dd37b3ada8ef8cc2e8fe505fcc470dbd76562992c9ac14f02febcd05ee14dae7f611cce78676d1301dfc97c8c2c0d49ee00a5d6bd9ab50540a533518c7cf58f7756a74595ae10a759d0cd6e72f557193a1a7d7a68ec180bb985aef1f1f64ed10637041069d343de9900e0f9e5bf7c4c79a58012dcbf3c8c062dd4971ab88176c6dfd1ce314d85f98c31ba67c1dd99ce3f7d2a04be56deaec618436e6cc5fc68d9417d955478f04dfc0af4e85726e3f1c0a7d9613d3b994035efbd1fcc90a2186895f43732b787b717211b882227496de79dd3cb6d3387ee8041a6d9a9dfa83af3fbbe57e89b408077241fe0aef8dd585982205c7d846921421ffcc84e455cd60fa9c15f13b31d5e1a96f4ed37506571baf9eb935f8b7aa184b94ea3662db78cda03164e34490c7cf2e74c4cc68793e855b0c0d1e3a4c7683102a114480629f5373bcac4ea28e6a0601a3b").unwrap());
}

#[test]
fn qf_default_hash_test() {
let discriminant = Discriminant::try_from(-BigInt::from_str_radix("c3811f4ad2f4a7bdf2ed89385866ad526c6dd3aa942e04c141d0562a8e7b014f08804f47b3c2ecbba0a5a0ad8f4d8e869a10cff13dbc522aea141f6d1c42913f2d3bff8d3e7656c72523a2e9d47f838234bd65f05ef3ca86c2f640bca6630ed8d1da21e30a67f83e25b89c32c2d0dc0bacb81bd971b0932a82d131b4a74bff36b60b66543105da2c3ecb1a4e8c2cb6d47c1e85942cce8f3fc50c27856e6dfbd15c0bd5017fea15ae0eb43dfb32b2d947c3131d1951f00bcc40352eeb65e364551e40d13768f443406760ee6b37a5b5819d3f630c034c7f42212ad49c803772aaafd4cd1f87697c68d5a6b0855f475b370b20058558993e76759caa38edbc82407b4e3559bade5f7479a860ebef62fed82d657765ebb8f7f375c2b78f73669760e4bd4932177087a49a0b68d7", 16).unwrap()).unwrap();

let qf = QuadraticForm::hash_to_group_with_default_parameters(b"seed", &discriminant).unwrap();
assert_eq!(qf.to_bytes(), hex::decode("008b04397d87d59220ec1bbbf79f471689ad0a48f67625abed478749b2f110d79990684b782160a7e00288c240160d10da0198298fd68f7e3fa0964975f1816d5bb38c978ea1bc9fb5aaefa62971435de9565801c80e545b497d00783c0d518722311c6fa7e924bff4f4765f6f3c6b3de8dcf1c314e7ea8df998de524af394e5cec7dfc867cf07f7eb501dfc270111ff304620732b3d44d3ceeadbd054e20eb953eed85ac684044b1192c1ccaeb9ba79695b28204e7148e8560e4b782c6b20ea20123bda9061eed1920d7ff1fd9741220ee1fac09f596524a12aa993734f2fa4ccf792d46c3bf8320073def0c938e6fb608b8866f70fc380f1a37f3fd9c52935837f5ff06ef6ab882599460e7b950ab17a75602a0b29523ab99c4d030923244a5a9e0431759c59a33a471641c013dadaebdc711baf3a05320330959f13b88c6619c64201bc10517c0bbc69524e6d3345eaeade453ea1ebe8b4ce41068e321399c41e8a90831f9713aa2df564423dfa2fe36e65ccf8157c9ebd24f4ac545482b1a609b7bce94316af8e53cbe191ba073b312a60831ea1f657a92ded17350710ed").unwrap());
}
22 changes: 13 additions & 9 deletions fastcrypto-vdf/src/vdf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ mod tests {
// Number of iterations for the VDF
let t = 500;

// Parameter for the hash to quadratic form
let k = 4;

// VDF construction
let vdf = StrongVDF::new(discriminant.clone(), t);

Expand All @@ -80,20 +77,27 @@ mod tests {
);

// Compute the VDF input from the combined randomness
let input = QuadraticForm::hash_to_group(&combined_randomness, &discriminant, k).unwrap();
assert_eq!(input.to_bytes(), hex::decode("003f0dd96f00382016209d7073d324903c1c769d1c68beeeaeb88c22252236d4a6f60cee389f3c9ebfc9b599556d850d02eb3aeabb0c330e7e8e07a882e77b3cff005f009643dcf5bc3584db554f8352a8178cae3e0aa2e6358c5321ae160a632fdc61c0888d918a510361e3542ed1ad27908683e89aa1ddf03b76abf174071fead13845470bcf0957ee97f3695fcfdcbe330f59d5e7d30672fdab30c4afe2d1ad91").unwrap());
let input = QuadraticForm::hash_to_group_with_default_parameters(
&combined_randomness,
&discriminant,
)
.unwrap();
assert_eq!(input.to_bytes(), hex::decode("003d1daa654704dd48f4d9f841ffe9b7dc89ef998ff05f32f0f6e3534c471f47c7cded2041d78d0e6d485bf6074f47bda32a132334c5b3791b530e2999d7410072fdb7dd5859c497f0711b87c4fc787208d1969bd9f661958ae9646fbf5c735a3fdb07c32d33991d38879723cdb1ebbb1ecbf4b5d9e549e92b9c3b791407f05ccb9ea0d82c2982a5c6264cc293c6e328eb07ae7094336e89b01c74b115646a775019bfb7d413449378488c1e5e67e32160c6d3").unwrap());

// Compute the output of the VDF
let (output, proof) = vdf.evaluate(&input).unwrap();
assert_eq!(output.to_bytes(), hex::decode("00406f3b8eb978b15578b3b2417c8d9b69e717f95bafde1d8ea2f8ad95233700cb695674f844d0b200d10858ee8cef0d41435bbddcfb8374d16a6991cd092d3862880040d8d398a74e60c35578d6412e9ba609ba5cacf5d5b9b4e45fc69312b5a427ee8a2ab11196536713258a42ca67ee248bc0eeee1a6047479e94a6bda2bc29253f0f").unwrap());
assert_eq!(proof.to_bytes(), hex::decode("00407bcbbe400b2c9cd3b311a8be17509ccad8d18d45ff9c934aea798d908ca0ccb46a805b637ee51df0338f8d28ae5ea90c0d209ebb2d4e1c97cea16e51ce157a4900408666e9ce6e5a6d294f7b66f5c8feefa5bf7e5a6e8d98b084a0205f4179ac79b4f04efa641bafad779e035276ff44ee02c4dced65dc68d60bab6815202328fe5f").unwrap());
assert_eq!(output.to_bytes(), hex::decode("004013829c9d086b35690a80ee0e68212db9737f65e203fd793277952c5213c3bf5e2fcc8cc01001f13c309b1edae3c9ef551d0fc371d0f2dd17944919a75d82db340040f0fd1a66112bb398a7577d1637955ce1c53c127a00d657d5138f7379ae57206b86715164c313f66ea4f0519149d1799f149d35c6a9d5a97a27ba376c336525ff").unwrap());
assert_eq!(proof.to_bytes(), hex::decode("00405747c1e3d2af1d2b091f7366cbeff4c9836dc0b5bb6e6032053af6aa589348d000abb10250540258d9bf70ed81810b6d4229af8567b51eb8a08b6d72d9e52f880040203a9dbb321818cbac6f9ca011af9544b91c94b357f924e5d29cf94d5e28b9148d8e7febbb495a76d1d159c8785a6c01120a124f08c72a140e812c58eaa70de1").unwrap());

// Verify the output and proof
assert!(vdf.verify(&input, &output, &proof).is_ok());

// Try with another input. This should fail.
let another_input =
QuadraticForm::hash_to_group(b"some other randomness", &discriminant, k).unwrap();
let another_input = QuadraticForm::hash_to_group_with_default_parameters(
b"some other randomness",
&discriminant,
)
.unwrap();

// Verify the output and proof
assert!(vdf.verify(&another_input, &output, &proof).is_err());
Expand Down
Loading