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

Use an XOF like Shake128 #15

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions vdf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ description = "An implementation of Verifiable Delay Functions (VDFs) in Rust"
classgroup = { path = "../classgroup", version = "^0.1.0" }
num-traits = "0.2"
sha2 = "0.8"
sha3 = "0.8"
bit-vec = "0.5"

[dev-dependencies]
Expand Down
59 changes: 28 additions & 31 deletions vdf/src/create_discriminant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,9 @@ include!(concat!(env!("OUT_DIR"), "/constants.rs"));

use classgroup::BigNumExt;
use num_traits::Zero;
use sha2::{digest::FixedOutput, Digest, Sha256};
use sha3::{digest::{Input, ExtendableOutput, XofReader}, Shake128};
use std::u16;

fn random_bytes_from_seed(seed: &[u8], byte_count: usize) -> Vec<u8> {
assert!(byte_count <= 32 * ((1 << 16) - 1));
let mut blob = Vec::with_capacity(byte_count);
let mut extra: u16 = 0;
while blob.len() < byte_count {
let mut hasher = Sha256::new();
hasher.input(seed);
let extra_bits: [u8; 2] = [((extra & 0xFF00) >> 8) as _, (extra & 0xFF) as _];
hasher.input(&extra_bits);
blob.extend_from_slice(&hasher.fixed_result()[..]);
extra += 1;
}
blob.resize(byte_count, 0);
blob
}

/// Create a discriminant from a seed (a byte string) and a bit length (a
/// `u16`). The discriminant is guaranteed to be a negative prime number that
/// fits in `length` bits, except with negligible probability (less than
Expand All @@ -62,27 +46,31 @@ fn random_bytes_from_seed(seed: &[u8], byte_count: usize) -> Vec<u8> {
///
/// This function is guaranteed not to panic for any inputs whatsoever, unless
/// memory allocation fails and the allocator in use panics in that case.
pub fn create_discriminant<T: BigNumExt>(seed: &[u8], length: u16) -> T {
let (mut n, residue) = {
// The number of “extra” bits (that don’t evenly fit in a byte)
let extra: u8 = (length as u8) & 7;

// The number of random bytes needed (the number of bytes that hold `length`
// bits, plus 2).
let random_bytes_len = ((usize::from(length) + 7) >> 3) + 2;
let random_bytes = random_bytes_from_seed(seed, random_bytes_len);
let (n, last_2) = random_bytes.split_at(random_bytes_len - 2);
let numerator = (usize::from(last_2[0]) << 8) + usize::from(last_2[1]);
pub fn create_discriminant<T: BigNumExt>(seed: &[u8], bit_length: u16) -> T {
let mut h = Shake128::default().chain(seed).xof_result();

let mut n = {
// The number of extra bits that do not evenly fit in a byte)
let extra: u8 = (bit_length as u8) & 7;

let byte_length = (usize::from(bit_length) + 7) >> 3;
let mut n = vec![0u8; byte_length];
h.read(&mut n);

// If there are any extra bits, right shift `n` so that it fits
// in `length` bits, discarding the least significant bits.
let n = T::from(n) >> usize::from((8 - extra) & 7);
(n, RESIDUES[numerator % RESIDUES.len()])
T::from(&n[..]) >> usize::from((8 - extra) & 7)
};
n.setbit(usize::from(length - 1));
n.setbit(usize::from(bit_length - 1));
debug_assert!(n >= Zero::zero());
let rem = n.frem_u32(M);

let residue = {
let mut numerator = [0u8; 2];
h.read(&mut numerator);
RESIDUES[u16::from_le_bytes(numerator) as usize % RESIDUES.len()]
};

// HACK HACK `rust-gmp` doesn’t expose += and -= with i32 or i64
if residue > rem {
n = n + u64::from(residue - rem);
Expand Down Expand Up @@ -125,11 +113,19 @@ pub fn create_discriminant<T: BigNumExt>(seed: &[u8], length: u16) -> T {
}
}


#[cfg(test)]
mod test {
use super::*;
use classgroup::{gmp_classgroup::GmpClassGroup, ClassGroup};
type Mpz = <GmpClassGroup as ClassGroup>::BigNum;

#[test]
fn check_discriminant_size() {
assert_eq!( GmpClassGroup::size_in_bits(& create_discriminant::<Mpz>(b"\xaa", 997)), 997);
}

/*
use std::str::FromStr;

#[test]
Expand Down Expand Up @@ -196,4 +192,5 @@ mod test {
\xa5\x8a"[..]
);
}
*/
}
25 changes: 4 additions & 21 deletions vdf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,12 @@
//! ```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() {
//! # 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());
//! }
//! let solution = pietrzak_vdf.solve(b"\xaa", 100).unwrap();
//! assert!(pietrzak_vdf.verify(b"\xaa", 100, &solution[..]).is_ok());
//! # }
//! ```
//!
//! ### To run the benchmarks
Expand Down
16 changes: 7 additions & 9 deletions vdf/src/proof_pietrzak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,18 @@ where
for<'a, 'b> &'a T: std::ops::Mul<&'b T, Output = T>,
for<'a, 'b> &'a T::BigNum: std::ops::Mul<&'b T::BigNum, Output = T::BigNum>,
{
use sha2::{digest::FixedOutput, Digest, Sha256};
use sha3::{digest::{Input, ExtendableOutput, XofReader}, Shake128};

let size = (int_size_bits + 16) >> 4;
let mut v = Vec::with_capacity(size * 2);
for _ in 0..size * 2 {
v.push(0)
}
let mut hasher = Sha256::new();
let mut v = vec![0; size * 2];
let mut h = Shake128::default();
afck marked this conversation as resolved.
Show resolved Hide resolved
for i in &[&x, &y, &sqrt_mu] {
i.serialize(&mut v).expect(super::INCORRECT_BUFFER_SIZE);
hasher.input(&v);
h.input(&v);
}
let res = hasher.fixed_result();
T::unsigned_deserialize_bignum(&res[..16])
let mut res = [0u8; 16];
h.xof_result().read(&mut res);
T::unsigned_deserialize_bignum(&res[..])
}

fn create_proof_of_time_pietrzak<T>(
Expand Down
55 changes: 28 additions & 27 deletions vdf/src/proof_wesolowski.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

use super::proof_of_time::{iterate_squarings, serialize};
use classgroup::{gmp_classgroup::GmpClassGroup, BigNum, BigNumExt, ClassGroup};
use sha2::{digest::FixedOutput, Digest, Sha256};
use sha3::{digest::{Input, ExtendableOutput, XofReader}, Shake128};
use std::{cmp::Eq, collections::HashMap, hash::Hash, mem, u64, usize};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -66,6 +66,7 @@ impl super::VDF for WesolowskiVDF {
.map_err(|()| super::InvalidProof)
}
}

/// To quote the original Python code:
///
/// > Create `L` and `k` parameters from papers, based on how many iterations
Expand All @@ -88,41 +89,41 @@ pub fn approximate_parameters(t: f64) -> (usize, u8, u64) {
(l as _, k as _, w as _)
}

fn u64_to_bytes(q: u64) -> [u8; 8] {
if false {
// This use of `std::mem::transumte` is correct, but still not justified.
unsafe { std::mem::transmute(q.to_be()) }
} else {
[
(q >> 56) as u8,
(q >> 48) as u8,
(q >> 40) as u8,
(q >> 32) as u8,
(q >> 24) as u8,
(q >> 16) as u8,
(q >> 8) as u8,
q as u8,
]
}
}

/// As on page 10 of Wesolowski's paper, we uniformly sample a prime
/// from amongst the first 2^129 primes. According to the prime number
/// theorem, the prime counting function `π(x)` can be approximated
/// by `x / log x` asymptotically, so like `2^128` when `x = 2^134` or
/// `2^122` when `x = 2^128`, which still leaves some margine.
///
/// Assuming the Riemann hypothesis, there is stronger approximation
/// `Li(x) - π(x) = O(\sqrt(x) \log x)` where `Li(x)` is the
/// [offset logarithmic integral](https://en.wikipedia.org/wiki/Logarithmic_integral_function),
/// so `Li(2^y) - Li(2) = \int_2^{2^y} dt/ln t = 2^y / y` and
/// `y = 134` gives at least 128 bits of security.
///
/// We may however have use for extra security margin against
/// an adversary with some influence over the random oracle.
///
///
/// Quote:
///
/// > Creates a random prime based on input s.
fn hash_prime<T: BigNum>(seed: &[&[u8]]) -> T {
let mut j = 0u64;
let mut h = Shake128::default();
h.input(b"prime");
for i in seed {
h.input(i);
}
let mut h = h.xof_result();
loop {
let mut hasher = Sha256::new();
hasher.input(b"prime");
hasher.input(u64_to_bytes(j));
for i in seed {
hasher.input(i);
}
let n = T::from(&hasher.fixed_result()[..16]);
// Ideally we should use 17 bytes here for 134 bits
let mut b = [0u8; 16];
h.read(&mut b);
let n = T::from(&b[..]);
if n.probab_prime(2) {
break n;
}
j += 1;
}
}

Expand Down