diff --git a/fastcrypto-vdf/benches/class_group.rs b/fastcrypto-vdf/benches/class_group.rs index a0e49f96fa..a2252b8c33 100644 --- a/fastcrypto-vdf/benches/class_group.rs +++ b/fastcrypto-vdf/benches/class_group.rs @@ -46,11 +46,6 @@ fn qf_from_seed_single(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]; diff --git a/fastcrypto-vdf/src/class_group/hash.rs b/fastcrypto-vdf/src/class_group/hash.rs index 55c3448b1d..d3b2fbd83d 100644 --- a/fastcrypto-vdf/src/class_group/hash.rs +++ b/fastcrypto-vdf/src/class_group/hash.rs @@ -17,25 +17,26 @@ use num_traits::Signed; 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 { - // 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; } @@ -43,26 +44,52 @@ impl QuadraticForm { 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 { + // 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); + } + // 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); diff --git a/fastcrypto-vdf/src/class_group/tests.rs b/fastcrypto-vdf/src/class_group/tests.rs index 89a3ca869f..5695932b5b 100644 --- a/fastcrypto-vdf/src/class_group/tests.rs +++ b/fastcrypto-vdf/src/class_group/tests.rs @@ -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; } @@ -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(); @@ -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); @@ -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); @@ -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()); +} diff --git a/fastcrypto-vdf/src/vdf/mod.rs b/fastcrypto-vdf/src/vdf/mod.rs index 50c47d0062..7e89766ee7 100644 --- a/fastcrypto-vdf/src/vdf/mod.rs +++ b/fastcrypto-vdf/src/vdf/mod.rs @@ -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); @@ -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());