diff --git a/Cargo.toml b/Cargo.toml index 3fed51b96fa4824ed8b1944178d1d97733e9305f..fe2526e0d30554ee108fcee703063597b739bdeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,3 +62,7 @@ required-features = ["aplonk"] [[example]] name = "fri" required-features = ["fri"] + +[[example]] +name = "fec" +required-features = ["fri"] \ No newline at end of file diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 5b980d23761cd31e92c272912c39bbc18306a4bb..2b866b2d25cabea57095434645e662c5379e9cdc 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -20,6 +20,7 @@ ark-secp256r1 = "0.4.0" ark-std = "0.4.0" ark-vesta = "0.4.0" clap = { version = "4.5.4", features = ["derive"] } -komodo = { path = ".." } +komodo = { path = "..", features = ["fri"] } plnk = { git = "https://gitlab.isae-supaero.fr/a.stevan/plnk", tag = "0.7.0", version = "0.7.0" } rand = "0.8.5" +dragoonfri = { version = "0.1.0"} diff --git a/benchmarks/src/bin/fec.rs b/benchmarks/src/bin/fec.rs index 173438d28c6138cd603b059aaa6bb630795e229e..d73830605c614bb67eb5f331618bc5113adf2c1d 100644 --- a/benchmarks/src/bin/fec.rs +++ b/benchmarks/src/bin/fec.rs @@ -1,8 +1,10 @@ // see `examples/benches/README.md` use ark_ff::PrimeField; - +use ark_poly::univariate::DensePolynomial; +use benchmarks::fields::Fq128; use clap::{arg, command, Parser, ValueEnum}; -use komodo::{algebra::linalg::Matrix, fec}; +use dragoonfri::algorithms::Sha3_512; +use komodo::{algebra::linalg::Matrix, fec, fri}; use plnk::Bencher; use rand::{rngs::ThreadRng, thread_rng, Rng, RngCore}; @@ -10,6 +12,14 @@ fn random_bytes(n: usize, rng: &mut ThreadRng) -> Vec<u8> { (0..n).map(|_| rng.gen::<u8>()).collect() } +fn random_loss<T>(shards: &mut Vec<T>, k: usize, rng: &mut impl Rng) { + // Randomly drop some shards until k are left + while shards.len() > k { + let i = rng.gen_range(0..shards.len()); + shards.remove(i); + } +} + fn build_encoding_mat<F: PrimeField>( k: usize, n: usize, @@ -24,48 +34,93 @@ fn build_encoding_mat<F: PrimeField>( .collect(); Matrix::vandermonde_unchecked(&points, k) } + _ => panic!("FFT encoding is not supported for matrix encoding"), } } fn template<F: PrimeField>(b: &Bencher, nb_bytes: usize, k: usize, n: usize, encoding: &Encoding) { let mut rng = thread_rng(); - let encoding_mat = build_encoding_mat(k, n, encoding, &mut rng); - - plnk::bench( - b, - &format!( - r#"{{"bytes": {}, "step": "encode", "k": {}, "n": {}}}"#, - nb_bytes, k, n - ), - || { + match encoding { + Encoding::Fft => { + assert_eq!(n.count_ones(), 1, "n must be a power of 2"); + assert_eq!(k.count_ones(), 1, "k must be a power of 2"); let bytes = random_bytes(nb_bytes, &mut rng); + let mut shards: Vec<fec::Shard<F>> = vec![]; + plnk::bench( + b, + &format!( + r#"{{"bytes": {}, "step": "encode", "method": "fft", "k": {}, "n": {}}}"#, + nb_bytes, k, n + ), + || { + plnk::timeit(|| { + let evaluations = fri::evaluate::<F>(&bytes, k, n); + shards = fri::encode::<F>(&bytes, evaluations, k) + }) + }, + ); + + let evaluations = fri::evaluate::<F>(&bytes, k, n); + let mut blocks = + fri::prove::<2, F, Sha3_512, DensePolynomial<F>>(evaluations, shards, 2, 2, 1) + .unwrap(); + + random_loss(&mut blocks, k, &mut rng); + + plnk::bench( + b, + &format!( + r#"{{"bytes": {}, "step": "decode", "method":"fft" "k": {}, "n": {}}}"#, + nb_bytes, k, n + ), + || { + plnk::timeit(|| { + fri::decode::<F, Sha3_512>(blocks.clone(), n); + }) + }, + ); + } + _ => { + let encoding_mat = build_encoding_mat(k, n, encoding, &mut rng); + + plnk::bench( + b, + &format!( + r#"{{"bytes": {}, "step": "encode", "method": "matrix", "k": {}, "n": {}}}"#, + nb_bytes, k, n + ), + || { + let bytes = random_bytes(nb_bytes, &mut rng); - plnk::timeit(|| fec::encode::<F>(&bytes, &encoding_mat).unwrap()) - }, - ); + plnk::timeit(|| fec::encode::<F>(&bytes, &encoding_mat).unwrap()) + }, + ); - let encoding_mat = build_encoding_mat(k, k, encoding, &mut rng); + let encoding_mat = build_encoding_mat(k, k, encoding, &mut rng); - plnk::bench( - b, - &format!( - r#"{{"bytes": {}, "step": "decode", "k": {}, "n": {}}}"#, - nb_bytes, k, n - ), - || { - let bytes = random_bytes(nb_bytes, &mut rng); - let shards = fec::encode::<F>(&bytes, &encoding_mat).unwrap(); + plnk::bench( + b, + &format!( + r#"{{"bytes": {}, "step": "decode", "method":"matrix", "k": {}, "n": {}}}"#, + nb_bytes, k, n + ), + || { + let bytes = random_bytes(nb_bytes, &mut rng); + let shards = fec::encode::<F>(&bytes, &encoding_mat).unwrap(); - plnk::timeit(|| fec::decode::<F>(shards.clone()).unwrap()) - }, - ); + plnk::timeit(|| fec::decode::<F>(shards.clone()).unwrap()) + }, + ); + } + } } #[derive(ValueEnum, Clone)] enum Encoding { Vandermonde, Random, + Fft, } #[derive(ValueEnum, Clone, Hash, PartialEq, Eq)] @@ -73,6 +128,7 @@ enum Curve { BLS12381, BN254, Pallas, + FP128, } #[derive(Parser)] @@ -124,6 +180,9 @@ fn main() { cli.n, &cli.encoding, ), + Curve::FP128 => { + template::<Fq128>(&b.with_name("FP128"), n, cli.k, cli.n, &cli.encoding) + } } } } diff --git a/examples/fec.rs b/examples/fec.rs new file mode 100644 index 0000000000000000000000000000000000000000..ab49d8a1e263a4bfec1a550184fd80e4bfd8881c --- /dev/null +++ b/examples/fec.rs @@ -0,0 +1,106 @@ +use ark_poly::univariate::DensePolynomial; +use clap::{arg, Parser, ValueEnum}; +use dragoonfri::algorithms::Sha3_512; +use dragoonfri_test_utils::Fq; +use komodo::algebra::linalg; +use komodo::{fec, fri}; +use rand::{Rng, RngCore, SeedableRng}; + +use ark_ff::PrimeField; +use komodo::fec::Shard; +use std::time::Instant; + +#[path = "utils/time.rs"] +mod time; + +fn random_loss<T>(shards: &mut Vec<T>, k: usize, rng: &mut impl Rng) { + // Randomly drop some shards until k are left + while shards.len() > k { + let i = rng.gen_range(0..shards.len()); + shards.remove(i); + } +} + +#[derive(ValueEnum, Debug, Clone)] +enum Coding { + Matrix, + Fft, +} + +#[derive(ValueEnum, Debug, Clone)] +enum FiniteField { + FP128, +} + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(short, long)] + data_size: usize, + + #[arg(long, default_value = "1234")] + seed: u64, + + #[arg(short)] + k: usize, + + #[arg(short)] + n: usize, + + #[arg(long, default_value = "fp128")] + finite_field: FiniteField, + + #[arg(long, default_value = "matrix")] + coding: Coding, +} + +fn encode_fft<F: PrimeField>(bytes: &[u8], k: usize, n: usize) -> Vec<Shard<F>> { + let evaluations = fri::evaluate::<F>(bytes, k, n); + fri::encode::<F>(bytes, evaluations, k) +} + +fn run<F: PrimeField>(bytes: &[u8], k: usize, n: usize, seed: u64, coding: Coding) { + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + + match coding { + Coding::Matrix => { + let matrix = linalg::Matrix::random(k, n, &mut rng); + let mut shards = timeit_and_print!("encoding", fec::encode, bytes, &matrix).unwrap(); + random_loss(&mut shards, k, &mut rng); + let recovered = timeit_and_print!("decoding", fec::decode::<F>, shards).unwrap(); + assert_eq!(bytes, recovered); + } + Coding::Fft => { + assert_eq!(n.count_ones(), 1, "n must be a power of 2"); + assert_eq!(k.count_ones(), 1, "k must be a power of 2"); + let shards = timeit_and_print!("encoding", encode_fft::<F>, bytes, k, n); + + let evaluations = fri::evaluate::<F>(bytes, k, n); + let mut blocks = + fri::prove::<2, F, Sha3_512, DensePolynomial<F>>(evaluations, shards, 2, 2, 1) + .unwrap(); + + random_loss(&mut blocks, k, &mut rng); + + let recovered = timeit_and_print!("decoding", fri::decode::<F, Sha3_512>, blocks, n); + assert_eq!( + bytes, recovered, + "decoded data does not match original data" + ); + } + } +} + +fn main() { + let args = Args::parse(); + + let mut rng = rand::rngs::StdRng::seed_from_u64(args.seed); + + let mut bytes = vec![0u8; args.data_size]; + rng.fill_bytes(&mut bytes); + + let (k, n) = (args.k, args.n); + match args.finite_field { + FiniteField::FP128 => run::<Fq>(&bytes, k, n, args.seed, args.coding), + } +} diff --git a/examples/utils/time.rs b/examples/utils/time.rs new file mode 100644 index 0000000000000000000000000000000000000000..db9e627675e59bf8b36d9f0aaa49b2d87e8ea481 --- /dev/null +++ b/examples/utils/time.rs @@ -0,0 +1,54 @@ +/// measure the time it takes to apply a function on a set of arguments and returns the result of +/// the call +/// +/// ```rust +/// fn add(a: i32, b: i32) { a + b } +/// let (res, time) = timeit!(add, 1, 2); +/// ``` +/// will be the same as +/// ```rust +/// fn add(a: i32, b: i32) { a + b } +/// let (res, time) = { +/// let start = Instant::now(); +/// let res = add(1, 2); +/// let time = start.elapsed(); +/// (res, time) +/// }; +/// ``` +#[macro_export] +macro_rules! timeit { + ($func:expr, $( $args:expr ),*) => {{ + let start = Instant::now(); + let res = $func( $( $args ),* ); + let time = start.elapsed(); + (res, time) + }}; +} + +/// same as [`timeit`] but prints a name and the time at the end directly +/// +/// ```rust +/// fn add(a: i32, b: i32) { a + b } +/// let res = timeit_and_print!("addition", add, 1, 2); +/// ``` +/// will be the same as +/// ```rust +/// fn add(a: i32, b: i32) { a + b } +/// let res = { +/// print!("addition: "); +/// let start = Instant::now(); +/// let res = add(1, 2); +/// let time = start.elapsed(); +/// println!("{}", time.as_nanos()); +/// res +/// }; +/// ``` +#[macro_export] +macro_rules! timeit_and_print { + ($name: expr, $func:expr, $( $args:expr ),*) => {{ + print!("{}: ", $name); + let (res, time) = timeit!($func, $($args),*); + println!("{}", time.as_nanos()); + res + }}; +}