diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml new file mode 100644 index 0000000..d3ed68f --- /dev/null +++ b/.github/workflows/clippy.yml @@ -0,0 +1,11 @@ +name: Clippy check +on: [push, pull_request] +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - run: rustup component add clippy + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9635bb9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,13 @@ +name: Test +on: [push, pull_request] +env: + CARGO_TERM_COLOR: always +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3ae4124 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ipa-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ark-std = "0.3.0" +ark-ff = "0.3.0" +ark-ec = "0.3.0" +ark-ed-on-bn254 = "0.3.0" +rand = { version = "0.8", features = [ "std", "std_rng" ] } diff --git a/README.md b/README.md index 0dde3e2..824977b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ipa-rs [![Test](https://github.com/arnaucube/ipa-rs/workflows/Test/badge.svg)](https://github.com/arnaucube/ipa-rs/actions?query=workflow%3ATest) -Inner Product Argument (IPA) version from Halo paper (https://eprint.iacr.org/2019/1021.pdf) implementation done to get familiar with [arkworks](https://arkworks.rs) and the IPA scheme. +Inner Product Argument (IPA) version from Halo paper (https://eprint.iacr.org/2019/1021.pdf) implementation done to get familiar with [arkworks](https://arkworks.rs) and the modified IPA scheme. > Warning: do not use this code in production. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a356d75 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,277 @@ +extern crate ark_ed_on_bn254; +use ark_ec::ProjectiveCurve; +use ark_ed_on_bn254::{EdwardsProjective, Fr}; +use ark_ff::{fields::PrimeField, Field}; // BigInteger +use ark_std::{UniformRand, Zero}; + +#[allow(non_snake_case)] +pub struct IPA { + d: u32, + H: EdwardsProjective, + Gs: Vec, + rng: rand::rngs::ThreadRng, +} + +#[allow(non_snake_case)] +pub struct Proof { + a: Fr, + b: Fr, // TODO not needed + G: EdwardsProjective, // TODO not needed + l: Vec, + r: Vec, + L: Vec, + R: Vec, +} + +#[allow(non_snake_case)] +#[allow(clippy::many_single_char_names)] +impl IPA { + pub fn new(d: u32) -> IPA { + let mut rng = ark_std::rand::thread_rng(); + + let mut gs: Vec = Vec::new(); + for _ in 0..d { + gs.push(EdwardsProjective::rand(&mut rng)); + } + + IPA { + d, + H: EdwardsProjective::rand(&mut rng), + Gs: gs, + rng, + } + } + + pub fn commit(&self, a: &[Fr], r: Fr) -> EdwardsProjective { + inner_product_point(a, &self.Gs) + self.H.mul(r.into_repr()) + } + + pub fn ipa(&mut self, a: &[Fr], b: &[Fr], u: &[Fr], U: &EdwardsProjective) -> Proof { + let mut a = a.to_owned(); + let mut b = b.to_owned(); + let mut G = self.Gs.clone(); + + let k = (f64::from(self.d as u32).log2()) as usize; + let mut l: Vec = vec![Fr::zero(); k]; + let mut r: Vec = vec![Fr::zero(); k]; + let mut L: Vec = vec![EdwardsProjective::zero(); k]; + let mut R: Vec = vec![EdwardsProjective::zero(); k]; + + for j in (0..k).rev() { + let m = a.len() / 2; + let a_lo = a[..m].to_vec(); + let a_hi = a[m..].to_vec(); + let b_lo = b[..m].to_vec(); + let b_hi = b[m..].to_vec(); + let G_lo = G[..m].to_vec(); + let G_hi = G[m..].to_vec(); + + l[j] = Fr::rand(&mut self.rng); + r[j] = Fr::rand(&mut self.rng); + + L[j] = inner_product_point(&a_lo, &G_hi) + + self.H.mul(l[j].into_repr()) + + U.mul(inner_product_field(&a_lo, &b_hi).into_repr()); + R[j] = inner_product_point(&a_hi, &G_lo) + + self.H.mul(r[j].into_repr()) + + U.mul(inner_product_field(&a_hi, &b_lo).into_repr()); + + let uj = u[j]; + let uj_inv = u[j].inverse().unwrap(); + + a = vec_add( + &vec_scalar_mul_field(&a_lo, &uj), + &vec_scalar_mul_field(&a_hi, &uj_inv), + ); + b = vec_add( + &vec_scalar_mul_field(&b_lo, &uj_inv), + &vec_scalar_mul_field(&b_hi, &uj), + ); + G = vec_add_point( + &vec_scalar_mul_point(&G_lo, &uj_inv), + &vec_scalar_mul_point(&G_hi, &uj), + ); + } + + // TODO assert len a,b,G == 1 + + Proof { + a: a[0], + b: b[0], + G: G[0], + l, + r, + L, + R, + } + } + pub fn verify( + &self, + P: &EdwardsProjective, + p: &Proof, + r: &Fr, + u: &[Fr], + U: &EdwardsProjective, + ) -> bool { + let mut q_0 = *P; + let mut r = *r; + + // TODO compute b & G without getting them in the proof package + + #[allow(clippy::needless_range_loop)] + for j in 0..u.len() { + let uj2 = u[j].square(); + let uj_inv2 = u[j].inverse().unwrap().square(); + + q_0 = q_0 + p.L[j].mul(uj2.into_repr()) + p.R[j].mul(uj_inv2.into_repr()); + r = r + p.l[j] * uj2 + p.r[j] * uj_inv2; + } + + let q_1 = + p.G.mul(p.a.into_repr()) + self.H.mul(r.into_repr()) + U.mul((p.a * p.b).into_repr()); + + q_0 == q_1 + } +} + +fn inner_product_field(a: &[Fr], b: &[Fr]) -> Fr { + // TODO require lens equal + let mut c: Fr = Fr::zero(); + for i in 0..a.len() { + c += a[i] * b[i]; + } + c +} + +fn inner_product_point(a: &[Fr], b: &[EdwardsProjective]) -> EdwardsProjective { + // TODO require lens equal + let mut c: EdwardsProjective = EdwardsProjective::zero(); + for i in 0..a.len() { + c += b[i].mul(a[i].into_repr()); + } + c +} + +fn vec_add(a: &[Fr], b: &[Fr]) -> Vec { + // TODO require len equal + let mut c: Vec = vec![Fr::zero(); a.len()]; + for i in 0..a.len() { + c[i] = a[i] + b[i]; + } + c +} +fn vec_add_point(a: &[EdwardsProjective], b: &[EdwardsProjective]) -> Vec { + // TODO require len equal + let mut c: Vec = vec![EdwardsProjective::zero(); a.len()]; + for i in 0..a.len() { + c[i] = a[i] + b[i]; + } + c +} + +fn vec_scalar_mul_field(a: &[Fr], b: &Fr) -> Vec { + let mut c: Vec = vec![Fr::zero(); a.len()]; + for i in 0..a.len() { + c[i] = a[i] * b; + } + c +} +fn vec_scalar_mul_point(a: &[EdwardsProjective], b: &Fr) -> Vec { + let mut c: Vec = vec![EdwardsProjective::zero(); a.len()]; + for i in 0..a.len() { + c[i] = a[i].mul(b.into_repr()); + } + c +} + +#[allow(dead_code)] +fn powers_of(x: Fr, d: u32) -> Vec { + let mut c: Vec = vec![Fr::zero(); d as usize]; + c[0] = x; + for i in 1..d as usize { + // TODO redo better + c[i] = c[i - 1] * x; + } + c +} + +// fn inner_product(a: Vec, b: Vec) -> T { +// // require lens equal +// let mut c: T = Zero(); +// for i in 0..a.len() { +// c = c + a[i] * b[i]; +// } +// c +// } + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use super::*; + + #[test] + fn test_utils() { + // let a = Fr::from(1 as u32); + // let b = Fr::one(); + // println!("A: {:?}", Fr::from(1 as u32)); + // println!("A: {:?}", a); + // println!("B: {:?}", b); + + let a = vec![ + Fr::from(1 as u32), + Fr::from(2 as u32), + Fr::from(3 as u32), + Fr::from(4 as u32), + ]; + let b = vec![ + Fr::from(1 as u32), + Fr::from(2 as u32), + Fr::from(3 as u32), + Fr::from(4 as u32), + ]; + let c = inner_product_field(&a, &b); + println!("c: {:?}", c); + + // let result = 2 + 2; + // assert_eq!(result, 4); + } + + #[test] + fn test_inner_product() { + let d = 8; + let mut ipa = IPA::new(d); + + let a = vec![ + Fr::from(1 as u32), + Fr::from(2 as u32), + Fr::from(3 as u32), + Fr::from(4 as u32), + Fr::from(5 as u32), + Fr::from(6 as u32), + Fr::from(7 as u32), + Fr::from(8 as u32), + ]; + + let x = Fr::from(3 as u32); + let b = powers_of(x, ipa.d); + + let r = Fr::rand(&mut ipa.rng); + + let mut P = ipa.commit(&a, r); + let v = inner_product_field(&a, &b); + + let U = EdwardsProjective::rand(&mut ipa.rng); + + let k = (f64::from(ipa.d as u32).log2()) as usize; + let mut u: Vec = vec![Fr::zero(); k]; + for j in 0..k { + u[j] = Fr::rand(&mut ipa.rng); + } + + P = P + U.mul(v.into_repr()); + + let proof = ipa.ipa(&a, &b, &u, &U); + let verif = ipa.verify(&P, &proof, &r, &u, &U); + assert!(verif); + } +}