diff --git a/Cargo.toml b/Cargo.toml index 6ffe789..dcd65fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,13 @@ repository = "https://github.com/runtime-machines/verkle-tree-rust" ark-std = { version = "^0.3.0", default-features = false } rand = { version = "0.8.3" } ark-ff = { version = "^0.3.0", default-features = false } -ark-ec = { version = "^0.3.0", default-features = false } \ No newline at end of file +ark-ec = { version = "^0.3.0", default-features = false } +num-bigint = "0.4.3" +curve25519-dalek = {version = "3.2.0", features = ["serde"] } +merlin = { version = "3", default-features = false } +sha2 = {version = "0.9", default-features = false} + +[dependencies.bulletproofs] +git = "https://github.com/runtime-machines/bulletproofs.git" +branch = "fix-range-proof" +features = ["avx2_backend", "scalar_range_proof"] \ No newline at end of file diff --git a/src/committer.rs b/src/committer.rs index 0fa0de6..70c1e17 100644 --- a/src/committer.rs +++ b/src/committer.rs @@ -1 +1,2 @@ +// TODO: keep this or the proving scheme one pub trait Committer {} diff --git a/src/lib.rs b/src/lib.rs index 56e955d..4ed3916 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +mod proving_schemes; + mod committer; mod db; mod errors; diff --git a/src/proving_schemes/bulletproof/mod.rs b/src/proving_schemes/bulletproof/mod.rs new file mode 100644 index 0000000..5762fdb --- /dev/null +++ b/src/proving_schemes/bulletproof/mod.rs @@ -0,0 +1,274 @@ +mod polynomial; + +use bulletproofs::{BulletproofGens, InnerProductProof}; +use curve25519_dalek::{ + constants::RISTRETTO_BASEPOINT_POINT, + ristretto::{CompressedRistretto, RistrettoPoint}, + scalar::Scalar, + traits::MultiscalarMul, +}; +use merlin::Transcript; +use sha2::Sha512; + +use self::polynomial::Polynomial; + +use super::{ + transcript_protocol::TranscriptProtocol, ProvingScheme, MAX_GENERATORS, +}; + +/// Represent the `ProvingScheme` based on the Bulletproof protocol +struct BulletproofPS { + gens: Vec, +} + +type ScalarPolynomialPoint = (Scalar, Scalar); + +// TODO: handle errors +// TODO: manage generators more efficiently + +impl ProvingScheme for BulletproofPS { + type Scalar = Scalar; + type Commit = CompressedRistretto; + type Proof = InnerProductProof; + + fn instantiate_generators() -> BulletproofPS { + BulletproofPS { + gens: create_gens(MAX_GENERATORS * 2), + } + } + + fn add_new_generator(&mut self) { + self.gens = create_gens(self.gens.len() + 1); + } + + fn compute_commitment( + &self, + children: &[Scalar], + ) -> (Vec, CompressedRistretto) { + let points = compute_scalar_polynomial_points(children); + + let polynomial_coefficients = Polynomial::lagrange(&points).0; + + let commitment = RistrettoPoint::multiscalar_mul( + &polynomial_coefficients, + &self.gens[..points.len()], + ) + .compress(); + + (polynomial_coefficients, commitment) + } + + fn from_bytes_to_scalar(bytes: &[u8]) -> Self::Scalar { + Scalar::hash_from_bytes::(bytes) + } + + fn from_commitment_to_scalar(com: &Self::Commit) -> Self::Scalar { + Scalar::hash_from_bytes::(com.as_bytes()) + } + + fn prove( + &self, + polynomial_coefficients: &[Scalar], + position: u128, + _evaluation: Self::Scalar, + ) -> InnerProductProof { + let mut transcript = Transcript::new(b"InnerProductProofNode"); + let q = (transcript.challenge_scalar(b"w")) * RISTRETTO_BASEPOINT_POINT; + + let n = polynomial_coefficients.len(); + + let g_h_factors = vec![Scalar::one(); n]; + + let (g_vec, h_vec) = split_gens(&self.gens[..(n * 2)]); + + let b_vec = compute_b_vec(n, position); + + InnerProductProof::create( + &mut transcript, + &q, + &g_h_factors, + &g_h_factors, + g_vec[..n].to_vec(), + h_vec[..n].to_vec(), + polynomial_coefficients.to_vec(), + b_vec, + ) + } + + fn verify( + &self, + com: &CompressedRistretto, + proof: &InnerProductProof, + children_count: usize, + position: u128, + evaluation: Self::Scalar, + ) -> bool { + let mut transcript = Transcript::new(b"InnerProductProofNode"); + let q = (transcript.challenge_scalar(b"w")) * RISTRETTO_BASEPOINT_POINT; + + let n = children_count.next_power_of_two(); + + let g_h_factors = vec![Scalar::one(); n]; + + let (g_vec, h_vec) = split_gens(&self.gens[..(n * 2)]); + + let a_com = com.decompress().unwrap(); + let b_vec = compute_b_vec(n, position); + let b_com = RistrettoPoint::multiscalar_mul(b_vec, &h_vec[..n]); + let p = a_com + b_com + (q * evaluation); + + proof + .verify( + n, + &mut transcript, + &g_h_factors, + &g_h_factors, + &p, + &q, + g_vec, + h_vec, + ) + .is_ok() + } +} + +fn create_gens(gens_capacity: usize) -> Vec { + let padded_length = gens_capacity.next_power_of_two(); + let bp_gens = BulletproofGens::new(padded_length, 1); + bp_gens.share(0).G(padded_length).cloned().collect() +} + +fn compute_scalar_polynomial_points( + polynomial_evaluations: &[Scalar], +) -> Vec { + let scalar_polynomial_points: Vec<_> = polynomial_evaluations + .iter() + .enumerate() + .map(|(position, &polynomial_evaluation)| { + (Scalar::from(position as u64), polynomial_evaluation) + }) + .collect(); + + padding_scalar_polynomial_points(&scalar_polynomial_points) +} + +fn padding_scalar_polynomial_points( + scalar_polynomial_points: &[ScalarPolynomialPoint], +) -> Vec { + scalar_polynomial_points + .iter() + .copied() + .chain( + (scalar_polynomial_points.len() + ..scalar_polynomial_points.len().next_power_of_two()) + .map(|index| (Scalar::from(index as u64), Scalar::zero())), + ) + .collect() +} + +fn split_gens( + gens: &[RistrettoPoint], +) -> (&[RistrettoPoint], &[RistrettoPoint]) { + let (g_vec, h_vec) = gens.split_at((gens.len() + 1) / 2); + (g_vec, h_vec) +} + +fn compute_b_vec(length: usize, position: u128) -> Vec { + let length: u32 = length.try_into().unwrap(); + (0..length) + .map(|index| Scalar::from(position.pow(index))) + .collect() +} + +#[cfg(test)] +mod test { + use curve25519_dalek::scalar::Scalar; + + use crate::proving_schemes::{ + bulletproof::polynomial::Polynomial, ProvingScheme, + }; + + use super::{BulletproofPS, ScalarPolynomialPoint}; + + fn generate_bytes() -> Vec<[u8; 32]> { + vec![ + [ + 0x90, 0x76, 0x33, 0xfe, 0x1c, 0x4b, 0x66, 0xa4, 0xa2, 0x8d, + 0x2d, 0xd7, 0x67, 0x83, 0x86, 0xc3, 0x53, 0xd0, 0xde, 0x54, + 0x55, 0xd4, 0xfc, 0x9d, 0xe8, 0xef, 0x7a, 0xc3, 0x1f, 0x35, + 0xbb, 0x05, + ], + [ + 0x6c, 0x33, 0x74, 0xa1, 0x89, 0x4f, 0x62, 0x21, 0x0a, 0xaa, + 0x2f, 0xe1, 0x86, 0xa6, 0xf9, 0x2c, 0xe0, 0xaa, 0x75, 0xc2, + 0x77, 0x95, 0x81, 0xc2, 0x95, 0xfc, 0x08, 0x17, 0x9a, 0x73, + 0x94, 0x0c, + ], + [ + 0xef, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, + 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, + ], + ] + } + + fn generate_positions() -> Vec { + generate_bytes() + .iter() + .enumerate() + .map(|(position, &evaluation)| { + ( + Scalar::from(position as u128), + Scalar::from_bytes_mod_order(evaluation), + ) + }) + .collect() + } + + #[test] + fn correct_polynomial_construction() { + let bytes = generate_positions(); + + let polynomial = Polynomial::lagrange(&bytes); + + assert_eq!(polynomial.eval(&Scalar::from(0_u128)), bytes[0].1); + assert_eq!(polynomial.eval(&Scalar::from(1_u128)), bytes[1].1); + assert_eq!(polynomial.eval(&Scalar::from(2_u128)), bytes[2].1); + } + + #[test] + fn correct_prove_verification() { + let scheme = BulletproofPS::instantiate_generators(); + let bytes: Vec<[u8; 32]> = generate_bytes(); + let scalars: Vec<_> = bytes + .iter() + .map(|byte| Scalar::from_bytes_mod_order(*byte)) + .collect(); + + let (polynomial_coefficients, com) = + scheme.compute_commitment(&scalars); + + let proof = scheme.prove( + &polynomial_coefficients, + 1, + Scalar::from_bytes_mod_order(bytes[1]), + ); + + assert!(scheme.verify( + &com, + &proof, + bytes.len(), + 1, + Scalar::from_bytes_mod_order(bytes[1]) + )); + + assert!(!scheme.verify( + &com, + &proof, + bytes.len(), + 0, + Scalar::from_bytes_mod_order(bytes[1]) + )); + } +} diff --git a/src/proving_schemes/bulletproof/polynomial.rs b/src/proving_schemes/bulletproof/polynomial.rs new file mode 100644 index 0000000..685b36b --- /dev/null +++ b/src/proving_schemes/bulletproof/polynomial.rs @@ -0,0 +1,164 @@ +use curve25519_dalek::scalar::Scalar; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Polynomial(pub(crate) Vec); + +impl Polynomial { + pub fn new(coefficients: Vec) -> Self { + let mut poly = Polynomial(coefficients); + poly.normalize(); + poly + } + + pub fn from(coefficients: &[u128]) -> Self { + Polynomial::new( + coefficients + .iter() + .map(|n| Scalar::from(*n)) + .collect::>(), + ) + } + + pub fn zero() -> Self { + Polynomial(vec![Scalar::zero()]) + } + + pub fn one() -> Self { + Polynomial(vec![Scalar::one()]) + } + + pub fn degree(&self) -> usize { + self.0.len() - 1 + } + + // Creates a polinomial that contains a set of `p` points, by using lagrange + pub fn lagrange(p: &[(Scalar, Scalar)]) -> Self { + let k = p.len(); + let mut l = Polynomial::zero(); + for j in 0..k { + let mut l_j = Polynomial::one(); + for i in 0..k { + if i != j { + let c = (p[j].0 - p[i].0).invert(); + l_j = &l_j * &Polynomial::new(vec![-(c * p[i].0), c]); + } + } + l += &(&l_j * &p[j].1); + } + l + } + + pub fn eval(&self, x: &Scalar) -> Scalar { + let mut x_pow = Scalar::one(); + let mut y = self.0[0]; + for (i, _) in self.0.iter().enumerate().skip(1) { + x_pow *= x; + y += &(x_pow * self.0[i]); + } + y + } + + // Remove ending zeroes + pub fn normalize(&mut self) { + if self.0.len() > 1 && self.0[self.0.len() - 1] == Scalar::zero() { + let zero = Scalar::zero(); + let first_non_zero = self.0.iter().rev().position(|p| p != &zero); + if let Some(first_non_zero) = first_non_zero { + self.0.resize(self.0.len() - first_non_zero, Scalar::zero()); + } else { + self.0.resize(1, Scalar::zero()); + } + } + } + + pub fn is_zero(&self) -> bool { + self.0.len() == 1 && self.0[0] == Scalar::zero() + } + + pub fn set(&mut self, i: usize, p: Scalar) { + if self.0.len() < i + 1 { + self.0.resize(i + 1, Scalar::zero()); + } + self.0[i] = p; + self.normalize(); + } + + pub fn get(&mut self, i: usize) -> Option<&Scalar> { + self.0.get(i) + } +} + +impl std::ops::AddAssign<&Polynomial> for Polynomial { + fn add_assign(&mut self, rhs: &Polynomial) { + for n in 0..std::cmp::max(self.0.len(), rhs.0.len()) { + if n >= self.0.len() { + self.0.push(rhs.0[n]); + } else if n < self.0.len() && n < rhs.0.len() { + self.0[n] += rhs.0[n]; + } + } + self.normalize(); + } +} + +impl std::ops::AddAssign<&Scalar> for Polynomial { + fn add_assign(&mut self, rhs: &Scalar) { + self.0[0] += rhs; + } +} + +impl std::ops::SubAssign<&Polynomial> for Polynomial { + fn sub_assign(&mut self, rhs: &Polynomial) { + for n in 0..std::cmp::max(self.0.len(), rhs.0.len()) { + if n >= self.0.len() { + self.0.push(rhs.0[n]); + } else if n < self.0.len() && n < rhs.0.len() { + self.0[n] -= rhs.0[n]; + } + } + self.normalize(); + } +} + +impl std::ops::Mul<&Polynomial> for &Polynomial { + type Output = Polynomial; + fn mul(self, rhs: &Polynomial) -> Self::Output { + let mut mul: Vec = std::iter::repeat(Scalar::zero()) + .take(self.0.len() + rhs.0.len() - 1) + .collect(); + for n in 0..self.0.len() { + for m in 0..rhs.0.len() { + mul[n + m] += self.0[n] * rhs.0[m]; + } + } + Polynomial(mul) + } +} + +impl std::ops::Mul<&Scalar> for &Polynomial { + type Output = Polynomial; + fn mul(self, rhs: &Scalar) -> Self::Output { + if rhs == &Scalar::zero() { + Polynomial::zero() + } else { + Polynomial(self.0.iter().map(|v| v * rhs).collect::>()) + } + } +} + +impl std::ops::Div for Polynomial { + type Output = (Polynomial, Polynomial); + + fn div(self, rhs: Polynomial) -> Self::Output { + let (mut q, mut r) = (Polynomial::zero(), self); + while !r.is_zero() && r.degree() >= rhs.degree() { + let lead_r = r.0[r.0.len() - 1]; + let lead_d = rhs.0[rhs.0.len() - 1]; + let mut t = Polynomial::zero(); + t.set(r.0.len() - rhs.0.len(), lead_r * lead_d.invert()); + q += &t; + r -= &(&rhs * &t); + } + (q, r) + } +} diff --git a/src/proving_schemes/mod.rs b/src/proving_schemes/mod.rs new file mode 100644 index 0000000..b745a8f --- /dev/null +++ b/src/proving_schemes/mod.rs @@ -0,0 +1,46 @@ +mod bulletproof; +mod transcript_protocol; + +pub const MAX_GENERATORS: usize = 256; + +pub trait ProvingScheme { + type Scalar; + type Commit; + type Proof; + + // Return a `ProvingScheme` by instantiating a starting amount of generators + fn instantiate_generators() -> Self; + + // Increase the generators' quantity + fn add_new_generator(&mut self); + + /// Generate a polynomial and its commitment from slice of bytes + fn compute_commitment( + &self, + children: &[Self::Scalar], + ) -> (Vec, Self::Commit); + + /// Convert a slice of bytes into a scalar (field element) + fn from_bytes_to_scalar(input: &[u8]) -> Self::Scalar; + + /// Convert a point (group element) into a scalar (field element) + fn from_commitment_to_scalar(input: &Self::Commit) -> Self::Scalar; + + /// Compute the proof that the evaluation at a given position is a node's child + fn prove( + &self, + polynomial_coefficients: &[Self::Scalar], + position: u128, + evaluation: Self::Scalar, + ) -> Self::Proof; + + /// Verify that points the evaluation at a given position is a node's child + fn verify( + &self, + commitment: &Self::Commit, + proof: &Self::Proof, + children_count: usize, + position: u128, + evaluation: Self::Scalar, + ) -> bool; +} diff --git a/src/proving_schemes/transcript_protocol.rs b/src/proving_schemes/transcript_protocol.rs new file mode 100644 index 0000000..72a3e88 --- /dev/null +++ b/src/proving_schemes/transcript_protocol.rs @@ -0,0 +1,60 @@ +use std::borrow::Borrow; + +use curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar}; +use merlin::Transcript; + +/// Trait providing methods to append different types to a transcript +pub trait TranscriptProtocol { + fn append_usize(&mut self, label: &'static [u8], value: usize); + fn append_points_vector(&mut self, label: &'static [u8], vec: I) + where + I: IntoIterator, + I::Item: Borrow; + fn append_point( + &mut self, + label: &'static [u8], + point: &CompressedRistretto, + ); + fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar); + fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar; +} + +impl TranscriptProtocol for Transcript { + fn append_usize(&mut self, label: &'static [u8], value: usize) { + let bytes = value.to_be_bytes(); + // Strip leading zero to obtain the same number of bytes regardless of the size of usize + let cut_position = bytes + .iter() + .position(|byte| *byte != 0) + .unwrap_or(bytes.len() - 1); + self.append_message(label, &bytes[cut_position..]); + } + + fn append_points_vector(&mut self, label: &'static [u8], vec: I) + where + I: IntoIterator, + I::Item: Borrow, + { + for point in vec { + self.append_point(label, point.borrow()); + } + } + + fn append_point( + &mut self, + label: &'static [u8], + point: &CompressedRistretto, + ) { + self.append_message(label, point.as_bytes()); + } + + fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar) { + self.append_message(label, scalar.as_bytes()) + } + + fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar { + let mut buf = [0; 64]; + self.challenge_bytes(label, &mut buf); + Scalar::from_bytes_mod_order_wide(&buf) + } +}