Skip to content
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions fastcrypto-zkp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ bcs.workspace = true
byte-slice-cast = "1.2.2"
fastcrypto = { path = "../fastcrypto", version = "0.1.5" }
derive_more = "0.99.16"
merlin = "3.0.0"
num-bigint = { version = "0.4", default-features = false, features = ["rand"] }
schemars = "0.8.10"
serde = { version = "1.0.152", features = ["derive"] }
Expand Down
3 changes: 3 additions & 0 deletions fastcrypto-zkp/src/bp3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# BulletProofs++ (alias BP3)

Implementation of the [Bulletproofs++](https://eprint.iacr.org/2022/510.pdf) protocol.
2 changes: 2 additions & 0 deletions fastcrypto-zkp/src/bp3/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod wnla;
mod util;
77 changes: 77 additions & 0 deletions fastcrypto-zkp/src/bp3/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::cmp::max;
use std::ops::{Add, Mul};
use fastcrypto::groups::{GroupElement, ristretto255::RistrettoScalar};

// Splits vector `v` into two vectors `v_even` and `v_odd` where `v_even` contains the elements at even indices and
// `v_odd` contains the elements at odd indices.
Comment thread
jonas-lj marked this conversation as resolved.
Outdated
pub fn reduce<T>(v: &[T]) -> (Vec<T>, Vec<T>)
where
T: Copy
{
let mut v_even = Vec::with_capacity((v.len() + 1) / 2);
let mut v_odd = Vec::with_capacity(v.len() / 2);
for (i, val) in v.iter().enumerate() {
if i % 2 == 0 { v_even.push(*val); } else { v_odd.push(*val); }
}
(v_even, v_odd)
}

// Extends vector `v` to length `n` by appending default values of type `T` if `v` is shorter than `n`.
pub fn extend<T>(v: &[T], n: usize) -> Vec<T>
where
T: Copy + Default
{
let mut v_ext = Vec::with_capacity(n);
v_ext.extend_from_slice(&v[..v.len().min(n)]);
v_ext.resize(n, T::default());
v_ext
}

// Computes the inner product of two vectors `v` and `w`, i.e., result = sum_i (v_i * w_i).
pub fn inner_product<T>(v: &[T], w: &[RistrettoScalar]) -> T
where
T: Copy + Mul<RistrettoScalar, Output = T> + Add<Output = T> + Default,
{
let mut result = T::default(); // ZERO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In case T is RistrettoPoint, this should be done using multi_scalar_mul which is much faster.

let v_ext = extend(v, max(v.len(), w.len()));
let w_ext = &extend(w, max(v.len(), w.len()));
Comment thread
jonas-lj marked this conversation as resolved.
Outdated
v_ext.iter().zip(w_ext).for_each(|(v_val, w_val)| {
result = result.add(v_val.mul(*w_val));
});
result
}

// Computes the weighted inner product of two vectors `v` and `w` with a given `weight`, i.e., result = sum_i (v_i * w_i * weight^i).
pub fn weighted_inner_product<T>(v: &[T], w: &[RistrettoScalar], weight: &RistrettoScalar) -> T
where
T: Copy + Mul<RistrettoScalar, Output = T> + Add<Output = T> + Default,
{
let mut exp = RistrettoScalar::generator(); // ONE
let mut result = T::default(); // ZERO
let v_ext = extend(v, max(v.len(), w.len()));
Comment thread
jonas-lj marked this conversation as resolved.
Outdated
let w_ext = &extend(w, max(v.len(), w.len()));
v_ext.iter().zip(w_ext).for_each(|(v_val, w_val)| {
exp = exp.mul(weight);
Comment thread
jonas-lj marked this conversation as resolved.
Outdated
result = result.add(v_val.mul(w_val.mul(&exp)));
});
result
}

// Scales each element of vector `v` by a given `scalar`, i.e., result[i] = v[i] * scalar.
pub fn scale<'a, T>(v: &[T], scalar: &'a RistrettoScalar) -> Vec<T>
where
T: Copy + Mul<&'a RistrettoScalar, Output = T>
{
v.iter().map(|x| x.mul(scalar)).collect()
}

// Adds two vectors `v` and `w` element-wise, i.e., result[i] = v[i] + w[i].
pub fn add<T>(v: &[T], w: &[T]) -> Vec<T>
where
T: Copy + Add<Output = T> + Default
{
// let mut result = Vec::with_capacity(max(v.len(), w.len()));
let v_ext = extend(v, max(v.len(), w.len()));
let w_ext = &extend(w, max(v.len(), w.len()));
v_ext.iter().zip(w_ext).map(|(v_val, w_val)| v_val.add(*w_val)).collect::<Vec<T>>()
}
217 changes: 217 additions & 0 deletions fastcrypto-zkp/src/bp3/wnla.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#![allow(non_snake_case)]

use std::ops::{Add, Mul, Sub};
use fastcrypto::groups::ristretto255::*;
use fastcrypto::groups::{GroupElement, Scalar};
use fastcrypto::serde_helpers::ToFromByteArray;
use merlin::Transcript;
use crate::bp3::util::*;

pub struct WeightNormLinearArgument {
Comment thread
jonas-lj marked this conversation as resolved.
Outdated
pub Gen: RistrettoPoint,
pub G: Vec<RistrettoPoint>,
pub H: Vec<RistrettoPoint>,
pub c: Vec<RistrettoScalar>,
pub rho: RistrettoScalar,
pub mu: RistrettoScalar,
}

#[derive(Clone, Debug)]
pub struct Proof {
pub R: Vec<RistrettoPoint>,
pub X: Vec<RistrettoPoint>,
pub l: Vec<RistrettoScalar>,
pub n: Vec<RistrettoScalar>,
}

impl WeightNormLinearArgument {

/// Computes a weight norm linear argument commitment `C` for vectors `l` and `n`:
/// `C = v * Gen + <H, l> + <G, n> with v = <c, l> + <n, n>_mu`.
pub fn commit(&self, l: &[RistrettoScalar], n: &[RistrettoScalar]) -> RistrettoPoint {
let v = inner_product(&self.c, l).
add(weighted_inner_product(n, n, &self.mu));
self.Gen.mul(v).
add(&inner_product(&self.H, l)).
add(&inner_product(&self.G, n))
}


/// Verifies a weight norm linear argument proof.
/// TODO: This is the "naive" (recursive) verification. We can optimize it as described on page 14 of the paper.
pub fn verify(&self, C: &RistrettoPoint, t: &mut Transcript, proof: &Proof) -> bool {
if proof.X.len() != proof.R.len() {
return false;
}

if proof.X.is_empty() {
return C.eq(&self.commit(&proof.l, &proof.n));
}

let (c0, c1) = reduce(&self.c);
let (G0, G1) = reduce(&self.G);
let (H0, H1) = reduce(&self.H);

// Add messages to Fiat Shamir transcript
t.append_message(b"wnla:C", &C.to_byte_array());
t.append_message(b"wnla:X", &proof.X.last().unwrap().to_byte_array());
t.append_message(b"wnla:R", &proof.R.last().unwrap().to_byte_array());
t.append_u64(b"wlna:l.len", self.H.len() as u64);
t.append_u64(b"wlna:n.len", self.G.len() as u64);

// Compute Fiat Shamir challenge gamma
let mut buf = [0u8; 64];
t.challenge_bytes(b"wnla:gamma", &mut buf);
let gamma = RistrettoScalar::from_bytes_mod_order_wide(&buf);

// G' = rho * G0 + gamma * G1
let Gp = add(&scale(&G0, &self.rho), &scale(&G1, &gamma));
// H' = H0 + gamma * H1
let Hp = add(&H0, &scale(&H1, &gamma));
// c' = c0 + gamma * c1
let cp = add(&c0, &scale(&c1, &gamma));
// C' = C + gamma * X + (gamma^2 - 1) * R
let Cp = C.
add(&proof.X.last().unwrap().mul(&gamma)).
add(&proof.R.last().unwrap().mul(&gamma.mul(&gamma).sub(&RistrettoScalar::generator())));

let wnla = WeightNormLinearArgument {
Gen: self.Gen,
G: Gp,
H: Hp,
c: cp,
rho: self.mu,
mu: self.mu.mul(&self.mu)
};

let proofp = Proof {
R: proof.R[..proof.R.len() - 1].to_vec(),
X: proof.X[..proof.X.len() - 1].to_vec(),
l: proof.l.clone(),
n: proof.n.clone(),
};

wnla.verify(&Cp, t, &proofp)
}

/// Creates a weight norm linear argument proof.
pub fn prove(&self, C: &RistrettoPoint, t: &mut Transcript, l: Vec<RistrettoScalar>, n: Vec<RistrettoScalar>) -> Proof {
if l.len() + n.len() < 6 {
return Proof {
R: vec![],
X: vec![],
l: l,
n: n,
};
}

let rho_inv = self.rho.inverse().unwrap();

let (c0, c1) = reduce(&self.c);
let (l0, l1) = reduce(&l);
let (n0, n1) = reduce(&n);
let (G0, G1) = reduce(&self.G);
let (H0, H1) = reduce(&self.H);

let mu_squared = self.mu.mul(&self.mu);

// v_x = 2 * rho_inv * <n0, n1>_{mu_squared} + <c0, l1> + <c1, l0>
let vx = weighted_inner_product(&n0, &n1, &mu_squared).
mul(&rho_inv.mul(&RistrettoScalar::from(2u64))).
add(&inner_product(&c0, &l1)).
add(&inner_product(&c1, &l0));

// v_r = <n1, n1>_{mu_squared} + <c1, l1>
let vr = weighted_inner_product(&n1, &n1, &mu_squared).
add(&inner_product(&c1, &l1));

// X = v_x * Gen + <H0, l1> + <H1, l0> + <G0, rho * n1> + <G1, rho_inv * n0>
let X = self.Gen.mul(vx).
add(&inner_product(&H0, &l1)).
add(&inner_product(&H1, &l0)).
add(&inner_product(&G0, &scale(&n1, &self.rho))).
add(&inner_product(&G1, &scale(&n0, &rho_inv)));
Comment thread
jonas-lj marked this conversation as resolved.
Outdated

// R = v_r * Gen + <H1, l1> + <G1, n1>
let R = self.Gen.mul(vr).
add(&inner_product(&H1, &l1)).
add(&inner_product(&G1, &n1));
Comment thread
jonas-lj marked this conversation as resolved.
Outdated

// Add messages to Fiat Shamir transcript
t.append_message(b"wnla:C", &C.to_byte_array());
t.append_message(b"wnla:X", &X.to_byte_array());
t.append_message(b"wnla:R", &R.to_byte_array());
t.append_u64(b"wlna:l.len", l.len() as u64);
t.append_u64(b"wlna:n.len", n.len() as u64);

// Compute Fiat Shamir challenge gamma
let mut buf = [0u8; 64];
t.challenge_bytes(b"wnla:gamma", &mut buf);
let gamma = RistrettoScalar::from_bytes_mod_order_wide(&buf);

// H' = H0 + gamma * H1
let Hp = add(&H0, &scale(&H1, &gamma));
// G' = rho * G0 + gamma * G1
let Gp = add(&scale(&G0, &self.rho), &scale(&G1, &gamma));
// c' = c0 + gamma * c1
let cp = add(&c0, &scale(&c1, &gamma));
// l' = l0 + gamma * l1
let lp = add(&l0, &scale(&l1, &gamma));
// n' = rho_inv * n0 + gamma * n1
let np = add(&scale(&n0, &rho_inv), &scale(&n1, &gamma));

let wnla = WeightNormLinearArgument {
Gen: self.Gen,
G: Gp,
H: Hp,
c: cp,
rho: self.mu,
mu: mu_squared
};

let mut proof = wnla.prove(&wnla.commit(&lp, &np), t, lp, np);
proof.R.push(R);
proof.X.push(X);
proof
}

}


#[cfg(test)]
mod tests {
use crate::bp3::wnla::*;
use ark_std::rand::thread_rng;

#[test]
fn test_weight_norm_linear_argument() {
const N: usize = 4;
let mut rand = thread_rng();

let Gen = RistrettoPoint::generator();
let G = (0..N).map(|_| RistrettoPoint::random(&mut rand)).collect::<Vec<_>>();
let H = (0..N).map(|_| RistrettoPoint::random(&mut rand)).collect::<Vec<_>>();
let c = (0..N).map(|_| RistrettoScalar::rand(&mut rand)).collect::<Vec<_>>();
let rho = RistrettoScalar::rand(&mut rand);

let wnla = WeightNormLinearArgument{
Gen: Gen,
G: G,
H: H,
c: c,
rho: rho,
mu: rho.mul(&rho)
};

let l = (0..N).map(|_| RistrettoScalar::rand(&mut rand)).collect::<Vec<_>>();
let n = (0..N).map(|_| RistrettoScalar::rand(&mut rand)).collect::<Vec<_>>();

let C = wnla.commit(&l, &n);

let mut pt = merlin::Transcript::new(b"wnla test");
let proof = wnla.prove(&C, &mut pt, l, n);
let mut vt = merlin::Transcript::new(b"wnla test");
assert!(wnla.verify(&C, &mut vt, &proof));

}
}
2 changes: 2 additions & 0 deletions fastcrypto-zkp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub mod bls12381;

pub mod bn254;

pub mod bp3;

/// Simple circuits used in benchmarks and demos
pub mod dummy_circuits;

Expand Down
12 changes: 12 additions & 0 deletions fastcrypto/src/groups/ristretto255.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ impl RistrettoPoint {
pub fn from_uniform_bytes(bytes: &[u8; 64]) -> Self {
RistrettoPoint(ExternalPoint::from_uniform_bytes(bytes))
}

pub fn random<R: AllowedRng>(rng: &mut R) -> Self {
Comment thread
jonas-lj marked this conversation as resolved.
Outdated
let mut bytes = [0u8; 64];
rng.fill_bytes(&mut bytes);
Self::from_uniform_bytes(&bytes)
}
}

impl Doubling for RistrettoPoint {
Expand Down Expand Up @@ -150,6 +156,12 @@ impl From<u64> for RistrettoScalar {
}
}

impl Default for RistrettoScalar {
Comment thread
jonas-lj marked this conversation as resolved.
Outdated
fn default() -> Self {
RistrettoScalar(ExternalScalar::ZERO)
}
}

impl Mul<RistrettoScalar> for RistrettoScalar {
type Output = RistrettoScalar;

Expand Down
Loading