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

test(VDF): add bench tests for VDF in rsa group #42

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ sha2 = "0.9.1"
name = "bench-vdf-class"
path = "benches/vdf/class_group.rs"
harness = false

[[bench]]
name = "bench-vdf-rsa"
path = "benches/vdf/rsa_group.rs"
harness = false
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,32 @@ Use `Cargo build`.
The library uses bindings to PARI c library. Running `Cargo build` for the first time will take PARI from the _depend_ folder and install it on the machine. It was tested on MacOS and Linux. If you encounter a problem with installation of PARI, please open an issue and try to install it [manually](https://pari.math.u-bordeaux.fr/download.html). Bindings are generated automatically on the fly which might slow down the build procces by a few seconds.


Test
Unit-test
-------------------
Tests in rust are multi-thearded if possible. However, PARI configuration supports a single thread. Therefore to make sure all tests run with defined behaviour please use `cargo test -- --test-threads=1`.

**Usage**

We use tests to demonstrate correctness of each primitive: At the end of each primitive `.rs` file there is a test to show the correct usage of the primitive. There is usually one test or more to show soundness of the implementation, i.e. not knowing a witness will fail a PoK. For all tests we assume 128bit security (conservatively translates into 1600bit Discriminant).

Bench-test
-------------------
Currenly this library supports benchmarking Wesolowski VDF in class_group vs in rsa_group. As aforementioned, PARI configuration only supports a single thread. To benchtest using PARI we need to use `--jobs 1` flag.

You can change and expand the test cases in `benches/vdf/class_group.rs`. You may also need to increase pari_init size (first parameter in `pari_init`) in src/primitives/vdf.rs if testing a larger `t`. We recommend testing each case respectively (i.e., `for &i in &[1_000] {`, `for &i in &[2_000] {` ..., instead of `for &i in &[1_000, 2_000, 5_000] {`) if the memory is limited.

**Usage**

benchmarking class_group VDF:
```
cargo bench --bench bench-vdf-class --jobs 1
```

benchmarking rsa_group VDF:
```
cargo bench --bench bench-vdf-rsa --jobs 1
```

Security
-------------------
Security assumptions can differ between primitives and are discussed in the relevant papers. They should be understood well before using any primitive. The code is not audited and we did not attempted to make it constant time. Do not use this library in production system.
Expand Down
2 changes: 1 addition & 1 deletion benches/vdf/class_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn benches_class(c: &mut Criterion) {
// change below to `for &i in &[1_000, 2_000, 5_000, 10_000, 100_000, 1_000_000] {` if needed to expand test cases,
// may also need to increase pari_init size (first parameter in `pari_init`) in src/primitives/vdf.rs
for &i in &[1_0] {
// precompute for verification
// precompute for benchmarking verification
let t = BigInt::from(i);
let vdf_out_proof = VDF::eval(&a_b_delta, &seed, &t);
let res = vdf_out_proof.verify();
Expand Down
135 changes: 135 additions & 0 deletions benches/vdf/rsa_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#[macro_use]
extern crate criterion;

use criterion::Criterion;
use rug::{integer::Order, Integer};
use sha2::{Digest, Sha256};

/// algo_2 from the paper
fn verify(modulus: &Integer, g: &Integer, t: u64, y: &Integer, pi: &Integer) -> bool {
let modulus = modulus.clone();

let l = hash_to_prime(&modulus, &[&g, &y]);

let r = Integer::from(2).pow_mod(&Integer::from(t), &l).unwrap();
let pi_l = pi.clone().pow_mod(&l, &modulus).unwrap();
let g_r = g.clone().pow_mod(&r, &modulus).unwrap();
let pi_l_g_r = pi_l * g_r;

Integer::from(pi_l_g_r.div_rem_floor(modulus.clone()).1) == y.clone()
}

/// algo_3 from the paper
fn eval(modulus: &Integer, g: &Integer, t: u64) -> (Integer, Integer) {
let modulus = modulus.clone();

// y <- (g^2)^t
let mut y = g.clone();
for _ in 0..t {
y = y.clone() * y.clone();
y = y.div_rem_floor(modulus.clone()).1;
}

let l = hash_to_prime(&modulus, &[&g, &y]);

// algo_4 from the paper, long division
// TODO: consider algo_5 instead
let mut b: Integer;
let mut r = Integer::from(1);
let mut r2: Integer;
let two = Integer::from(2);
let mut pi = Integer::from(1);

for _ in 0..t {
r2 = r.clone() * two.clone();
b = r2.clone().div_rem_floor(l.clone()).0;
r = r2.clone().div_rem_floor(l.clone()).1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to run r2.clone().div_rem_floor(l.clone()) only once. (instead of twice)

let pi_2 = pi.clone().pow_mod(&two, &modulus).unwrap();
let g_b = g.clone().pow_mod(&b, &modulus).unwrap();
pi = pi_2 * g_b;
}
pi = Integer::from(pi.div_rem_floor(modulus.clone()).1);
(y, pi)
}

/// int(H("residue"||x)) mod N
fn h_g(modulus: &Integer, seed: &Integer) -> Integer {
let modulus = modulus.clone();
let mut hasher = Sha256::new();
hasher.update("residue".as_bytes());
hasher.update(seed.to_digits::<u8>(Order::Lsf));
let hashed = Integer::from_digits(&hasher.finalize(), Order::Lsf);

// inverse, to get enough security bits
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked and we can actually cannot do the inverse trick as it do not provide a random element in the group.
Instead you should concat results of nine sha256 and take the result modulo N

Copy link
Contributor Author

@0xmountaintop 0xmountaintop Nov 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I run recursive hashing only on the imtermediate hashing result itself, then at the end use them to reconstruct the randomness?

Or should I hash->construct->hash->construct repeatedly?

The latter seems can offer more entropy?

i.e., should I

let mut part = h_g_inner(seed);
let mut result = part.clone();
let mut ent = HASH_ENT;
while ent < GROUP_ENT {
    part = h_g_inner(&part);
    result = (result << HASH_ENT) + part.clone();
    ent += HASH_ENT;
}
result.div_rem_floor(modulus.clone()).1

or

let mut result = h_g_inner(seed);
let mut ent = HASH_ENT;
while ent < GROUP_ENT {
    result = h_g_inner(&result);
    result = (result.clone() << HASH_ENT) + result.clone();
    ent += HASH_ENT;
}
result.div_rem_floor(modulus.clone()).1

Copy link
Contributor Author

@0xmountaintop 0xmountaintop Nov 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also come up with another idea.

We can actually

sha256("part1"||seed) || sha256("part2"||seed) || ... || sha256("part8"||seed)

i.e., we keep using the same seed for a part of the input, but introduces "partXXX" to provide different randomness.

We then concat then all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of the methods above works and we should take the fastest

match hashed.invert(&modulus.clone()) {
Ok(inverse) => inverse,
Err(unchanged) => unchanged,
}
}

fn hash_to_prime(modulus: &Integer, inputs: &[&Integer]) -> Integer {
let mut hasher = Sha256::new();
for input in inputs {
hasher.update(input.to_digits::<u8>(Order::Lsf));
hasher.update("\n".as_bytes());
}
let hashed = Integer::from_digits(&hasher.finalize(), Order::Lsf);

// inverse, to get enough security bits
let inverse = match hashed.invert(&modulus.clone()) {
Ok(inverse) => inverse,
Err(unchanged) => unchanged,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually not needed in this function

Copy link
Contributor Author

@0xmountaintop 0xmountaintop Nov 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the same trick as #42 (comment) here?


Integer::from(inverse.next_prime().div_rem_floor(modulus.clone()).1)
}

fn benches_rsa(c: &mut Criterion) {
let bench_eval = |c: &mut Criterion, difficulty: u64, modulus: &Integer, seed: &Integer| {
c.bench_function(&format!("eval with difficulty {}", difficulty), move |b| {
b.iter(|| eval(&modulus, &seed, difficulty))
});
};
let bench_verify = |c: &mut Criterion,
difficulty: u64,
modulus: &Integer,
seed: &Integer,
y: &Integer,
pi: &Integer| {
c.bench_function(
&format!("verify with difficulty {}", difficulty),
move |b| b.iter(|| verify(&modulus, &seed, difficulty, &y, &pi)),
);
};

/// RSA-2048 modulus, taken from [Wikipedia](https://en.wikipedia.org/wiki/RSA_numbers#RSA-2048).
pub const MODULUS: &str =
"251959084756578934940271832400483985714292821262040320277771378360436620207075955562640185258807\
8440691829064124951508218929855914917618450280848912007284499268739280728777673597141834727026189\
6375014971824691165077613379859095700097330459748808428401797429100642458691817195118746121515172\
6546322822168699875491824224336372590851418654620435767984233871847744479207399342365848238242811\
9816381501067481045166037730605620161967625613384414360383390441495263443219011465754445417842402\
0924616515723350778707749817125772467962926386356373289912154831438167899885040445364023527381951\
378636564391212010397122822120720357";
let modulus = Integer::from_str_radix(MODULUS, 10).unwrap();

const TEST_HASH: &str = "1eeb30c7163271850b6d018e8282093ac6755a771da6267edf6c9b4fce9242ba";
let seed_hash = Integer::from_str_radix(TEST_HASH, 16).unwrap();
let seed = Integer::from(seed_hash.div_rem_floor(modulus.clone()).1);

// g <- H_G(x)
let g = h_g(&modulus, &seed);

for &i in &[1_000, 2_000, 5_000, 10_000, 100_000, 1_000_000] {
// precompute for benchmarking verification
let (y, pi) = eval(&modulus, &g, i);
let result = verify(&modulus, &g, i, &y, &pi);
assert!(result);

bench_eval(c, i, &modulus, &seed);
bench_verify(c, i, &modulus, &seed, &y, &pi)
}
}

criterion_group!(benches, benches_rsa);
criterion_main!(benches);