mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-14 06:59:24 +00:00
Upstream GBP, divisor, circuit abstraction, and EC gadgets from FCMP++
This commit is contained in:
33
crypto/evrf/generalized-bulletproofs/Cargo.toml
Normal file
33
crypto/evrf/generalized-bulletproofs/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "generalized-bulletproofs"
|
||||
version = "0.1.0"
|
||||
description = "Generalized Bulletproofs"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/generalized-bulletproofs"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["ciphersuite", "ff", "group"]
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["std", "zeroize_derive"] }
|
||||
|
||||
blake2 = { version = "0.10", default-features = false, features = ["std"] }
|
||||
|
||||
multiexp = { path = "../../multiexp", version = "0.4", default-features = false, features = ["std", "batch"] }
|
||||
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_core = { version = "0.6", features = ["getrandom"] }
|
||||
|
||||
transcript = { package = "flexible-transcript", path = "../../transcript", features = ["recommended"] }
|
||||
|
||||
ciphersuite = { path = "../../ciphersuite", features = ["ristretto"] }
|
||||
|
||||
[features]
|
||||
tests = []
|
||||
21
crypto/evrf/generalized-bulletproofs/LICENSE
Normal file
21
crypto/evrf/generalized-bulletproofs/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2024 Luke Parker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
6
crypto/evrf/generalized-bulletproofs/README.md
Normal file
6
crypto/evrf/generalized-bulletproofs/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Generalized Bulletproofs
|
||||
|
||||
An implementation of
|
||||
[Generalized Bulletproofs](https://repo.getmonero.org/monero-project/ccs-proposals/uploads/a9baa50c38c6312efc0fea5c6a188bb9/gbp.pdf),
|
||||
a variant of the Bulletproofs arithmetic circuit statement to support Pedersen
|
||||
vector commitments.
|
||||
@@ -0,0 +1,679 @@
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use multiexp::{multiexp, multiexp_vartime};
|
||||
use ciphersuite::{group::ff::Field, Ciphersuite};
|
||||
|
||||
use crate::{
|
||||
ScalarVector, PointVector, ProofGenerators, PedersenCommitment, PedersenVectorCommitment,
|
||||
BatchVerifier,
|
||||
transcript::*,
|
||||
lincomb::accumulate_vector,
|
||||
inner_product::{IpError, IpStatement, IpWitness, P},
|
||||
};
|
||||
pub use crate::lincomb::{Variable, LinComb};
|
||||
|
||||
/// An Arithmetic Circuit Statement.
|
||||
///
|
||||
/// Bulletproofs' constraints are of the form
|
||||
/// `aL * aR = aO, WL * aL + WR * aR + WO * aO = WV * V + c`.
|
||||
///
|
||||
/// Generalized Bulletproofs modifies this to
|
||||
/// `aL * aR = aO, WL * aL + WR * aR + WO * aO + WCG * C_G + WCH * C_H = WV * V + c`.
|
||||
///
|
||||
/// We implement the latter, yet represented (for simplicity) as
|
||||
/// `aL * aR = aO, WL * aL + WR * aR + WO * aO + WCG * C_G + WCH * C_H + WV * V + c = 0`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArithmeticCircuitStatement<'a, C: Ciphersuite> {
|
||||
generators: ProofGenerators<'a, C>,
|
||||
|
||||
constraints: Vec<LinComb<C::F>>,
|
||||
C: PointVector<C>,
|
||||
V: PointVector<C>,
|
||||
}
|
||||
|
||||
impl<'a, C: Ciphersuite> Zeroize for ArithmeticCircuitStatement<'a, C> {
|
||||
fn zeroize(&mut self) {
|
||||
self.constraints.zeroize();
|
||||
self.C.zeroize();
|
||||
self.V.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
/// The witness for an arithmetic circuit statement.
|
||||
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct ArithmeticCircuitWitness<C: Ciphersuite> {
|
||||
aL: ScalarVector<C::F>,
|
||||
aR: ScalarVector<C::F>,
|
||||
aO: ScalarVector<C::F>,
|
||||
|
||||
c: Vec<PedersenVectorCommitment<C>>,
|
||||
v: Vec<PedersenCommitment<C>>,
|
||||
}
|
||||
|
||||
/// An error incurred during arithmetic circuit proof operations.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum AcError {
|
||||
/// The vectors of scalars which are multiplied against each other were of different lengths.
|
||||
DifferingLrLengths,
|
||||
/// The matrices of constraints are of different lengths.
|
||||
InconsistentAmountOfConstraints,
|
||||
/// A constraint referred to a non-existent term.
|
||||
ConstrainedNonExistentTerm,
|
||||
/// A constraint referred to a non-existent commitment.
|
||||
ConstrainedNonExistentCommitment,
|
||||
/// There weren't enough generators to prove for this statement.
|
||||
NotEnoughGenerators,
|
||||
/// The witness was inconsistent to the statement.
|
||||
///
|
||||
/// Sanity checks on the witness are always performed. If the library is compiled with debug
|
||||
/// assertions on, the satisfaction of all constraints and validity of the commitmentsd is
|
||||
/// additionally checked.
|
||||
InconsistentWitness,
|
||||
/// There was an error from the inner-product proof.
|
||||
Ip(IpError),
|
||||
/// The proof wasn't complete and the necessary values could not be read from the transcript.
|
||||
IncompleteProof,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> ArithmeticCircuitWitness<C> {
|
||||
/// Constructs a new witness instance.
|
||||
pub fn new(
|
||||
aL: ScalarVector<C::F>,
|
||||
aR: ScalarVector<C::F>,
|
||||
c: Vec<PedersenVectorCommitment<C>>,
|
||||
v: Vec<PedersenCommitment<C>>,
|
||||
) -> Result<Self, AcError> {
|
||||
if aL.len() != aR.len() {
|
||||
Err(AcError::DifferingLrLengths)?;
|
||||
}
|
||||
|
||||
// The Pedersen Vector Commitments don't have their variables' lengths checked as they aren't
|
||||
// paired off with each other as aL, aR are
|
||||
|
||||
// The PVC commit function ensures there's enough generators for their amount of terms
|
||||
// If there aren't enough/the same generators when this is proven for, it'll trigger
|
||||
// InconsistentWitness
|
||||
|
||||
let aO = aL.clone() * &aR;
|
||||
Ok(ArithmeticCircuitWitness { aL, aR, aO, c, v })
|
||||
}
|
||||
}
|
||||
|
||||
struct YzChallenges<C: Ciphersuite> {
|
||||
y_inv: ScalarVector<C::F>,
|
||||
z: ScalarVector<C::F>,
|
||||
}
|
||||
|
||||
impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
|
||||
// The amount of multiplications performed.
|
||||
fn n(&self) -> usize {
|
||||
self.generators.len()
|
||||
}
|
||||
|
||||
// The amount of constraints.
|
||||
fn q(&self) -> usize {
|
||||
self.constraints.len()
|
||||
}
|
||||
|
||||
// The amount of Pedersen vector commitments.
|
||||
fn c(&self) -> usize {
|
||||
self.C.len()
|
||||
}
|
||||
|
||||
// The amount of Pedersen commitments.
|
||||
fn m(&self) -> usize {
|
||||
self.V.len()
|
||||
}
|
||||
|
||||
/// Create a new ArithmeticCircuitStatement for the specified relationship.
|
||||
///
|
||||
/// The `LinComb`s passed as `constraints` will be bound to evaluate to 0.
|
||||
///
|
||||
/// The constraints are not transcripted. They're expected to be deterministic from the context
|
||||
/// and higher-level statement. If your constraints are variable, you MUST transcript them before
|
||||
/// calling prove/verify.
|
||||
///
|
||||
/// The commitments are expected to have been transcripted extenally to this statement's
|
||||
/// invocation. That's practically ensured by taking a `Commitments` struct here, which is only
|
||||
/// obtainable via a transcript.
|
||||
pub fn new(
|
||||
generators: ProofGenerators<'a, C>,
|
||||
constraints: Vec<LinComb<C::F>>,
|
||||
commitments: Commitments<C>,
|
||||
) -> Result<Self, AcError> {
|
||||
let Commitments { C, V } = commitments;
|
||||
|
||||
for constraint in &constraints {
|
||||
if Some(generators.len()) <= constraint.highest_a_index {
|
||||
Err(AcError::ConstrainedNonExistentTerm)?;
|
||||
}
|
||||
if Some(C.len()) <= constraint.highest_c_index {
|
||||
Err(AcError::ConstrainedNonExistentCommitment)?;
|
||||
}
|
||||
if Some(V.len()) <= constraint.highest_v_index {
|
||||
Err(AcError::ConstrainedNonExistentCommitment)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { generators, constraints, C, V })
|
||||
}
|
||||
|
||||
fn yz_challenges(&self, y: C::F, z_1: C::F) -> YzChallenges<C> {
|
||||
let y_inv = y.invert().unwrap();
|
||||
let y_inv = ScalarVector::powers(y_inv, self.n());
|
||||
|
||||
// Powers of z *starting with z**1*
|
||||
// We could reuse powers and remove the first element, yet this is cheaper than the shift that
|
||||
// would require
|
||||
let q = self.q();
|
||||
let mut z = ScalarVector(Vec::with_capacity(q));
|
||||
z.0.push(z_1);
|
||||
for _ in 1 .. q {
|
||||
z.0.push(*z.0.last().unwrap() * z_1);
|
||||
}
|
||||
z.0.truncate(q);
|
||||
|
||||
YzChallenges { y_inv, z }
|
||||
}
|
||||
|
||||
/// Prove for this statement/witness.
|
||||
pub fn prove<R: RngCore + CryptoRng>(
|
||||
self,
|
||||
rng: &mut R,
|
||||
transcript: &mut Transcript,
|
||||
mut witness: ArithmeticCircuitWitness<C>,
|
||||
) -> Result<(), AcError> {
|
||||
let n = self.n();
|
||||
let c = self.c();
|
||||
let m = self.m();
|
||||
|
||||
// Check the witness length and pad it to the necessary power of two
|
||||
if witness.aL.len() > n {
|
||||
Err(AcError::NotEnoughGenerators)?;
|
||||
}
|
||||
while witness.aL.len() < n {
|
||||
witness.aL.0.push(C::F::ZERO);
|
||||
witness.aR.0.push(C::F::ZERO);
|
||||
witness.aO.0.push(C::F::ZERO);
|
||||
}
|
||||
for c in &mut witness.c {
|
||||
if c.g_values.len() > n {
|
||||
Err(AcError::NotEnoughGenerators)?;
|
||||
}
|
||||
if c.h_values.len() > n {
|
||||
Err(AcError::NotEnoughGenerators)?;
|
||||
}
|
||||
// The Pedersen vector commitments internally have n terms
|
||||
while c.g_values.len() < n {
|
||||
c.g_values.0.push(C::F::ZERO);
|
||||
}
|
||||
while c.h_values.len() < n {
|
||||
c.h_values.0.push(C::F::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the witness's consistency with the statement
|
||||
if (c != witness.c.len()) || (m != witness.v.len()) {
|
||||
Err(AcError::InconsistentWitness)?;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
for (commitment, opening) in self.V.0.iter().zip(witness.v.iter()) {
|
||||
if *commitment != opening.commit(self.generators.g(), self.generators.h()) {
|
||||
Err(AcError::InconsistentWitness)?;
|
||||
}
|
||||
}
|
||||
for (commitment, opening) in self.C.0.iter().zip(witness.c.iter()) {
|
||||
if Some(*commitment) !=
|
||||
opening.commit(
|
||||
self.generators.g_bold_slice(),
|
||||
self.generators.h_bold_slice(),
|
||||
self.generators.h(),
|
||||
)
|
||||
{
|
||||
Err(AcError::InconsistentWitness)?;
|
||||
}
|
||||
}
|
||||
for constraint in &self.constraints {
|
||||
let eval =
|
||||
constraint
|
||||
.WL
|
||||
.iter()
|
||||
.map(|(i, weight)| *weight * witness.aL[*i])
|
||||
.chain(constraint.WR.iter().map(|(i, weight)| *weight * witness.aR[*i]))
|
||||
.chain(constraint.WO.iter().map(|(i, weight)| *weight * witness.aO[*i]))
|
||||
.chain(
|
||||
constraint.WCG.iter().zip(&witness.c).flat_map(|(weights, c)| {
|
||||
weights.iter().map(|(j, weight)| *weight * c.g_values[*j])
|
||||
}),
|
||||
)
|
||||
.chain(
|
||||
constraint.WCH.iter().zip(&witness.c).flat_map(|(weights, c)| {
|
||||
weights.iter().map(|(j, weight)| *weight * c.h_values[*j])
|
||||
}),
|
||||
)
|
||||
.chain(constraint.WV.iter().map(|(i, weight)| *weight * witness.v[*i].value))
|
||||
.chain(core::iter::once(constraint.c))
|
||||
.sum::<C::F>();
|
||||
|
||||
if eval != C::F::ZERO {
|
||||
Err(AcError::InconsistentWitness)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let alpha = C::F::random(&mut *rng);
|
||||
let beta = C::F::random(&mut *rng);
|
||||
let rho = C::F::random(&mut *rng);
|
||||
|
||||
let AI = {
|
||||
let alg = witness.aL.0.iter().enumerate().map(|(i, aL)| (*aL, self.generators.g_bold(i)));
|
||||
let arh = witness.aR.0.iter().enumerate().map(|(i, aR)| (*aR, self.generators.h_bold(i)));
|
||||
let ah = core::iter::once((alpha, self.generators.h()));
|
||||
let mut AI_terms = alg.chain(arh).chain(ah).collect::<Vec<_>>();
|
||||
let AI = multiexp(&AI_terms);
|
||||
AI_terms.zeroize();
|
||||
AI
|
||||
};
|
||||
let AO = {
|
||||
let aog = witness.aO.0.iter().enumerate().map(|(i, aO)| (*aO, self.generators.g_bold(i)));
|
||||
let bh = core::iter::once((beta, self.generators.h()));
|
||||
let mut AO_terms = aog.chain(bh).collect::<Vec<_>>();
|
||||
let AO = multiexp(&AO_terms);
|
||||
AO_terms.zeroize();
|
||||
AO
|
||||
};
|
||||
|
||||
let mut sL = ScalarVector(Vec::with_capacity(n));
|
||||
let mut sR = ScalarVector(Vec::with_capacity(n));
|
||||
for _ in 0 .. n {
|
||||
sL.0.push(C::F::random(&mut *rng));
|
||||
sR.0.push(C::F::random(&mut *rng));
|
||||
}
|
||||
let S = {
|
||||
let slg = sL.0.iter().enumerate().map(|(i, sL)| (*sL, self.generators.g_bold(i)));
|
||||
let srh = sR.0.iter().enumerate().map(|(i, sR)| (*sR, self.generators.h_bold(i)));
|
||||
let rh = core::iter::once((rho, self.generators.h()));
|
||||
let mut S_terms = slg.chain(srh).chain(rh).collect::<Vec<_>>();
|
||||
let S = multiexp(&S_terms);
|
||||
S_terms.zeroize();
|
||||
S
|
||||
};
|
||||
|
||||
transcript.push_point(AI);
|
||||
transcript.push_point(AO);
|
||||
transcript.push_point(S);
|
||||
let y = transcript.challenge();
|
||||
let z = transcript.challenge();
|
||||
let YzChallenges { y_inv, z } = self.yz_challenges(y, z);
|
||||
let y = ScalarVector::powers(y, n);
|
||||
|
||||
// t is a n'-term polynomial
|
||||
// While Bulletproofs discuss it as a 6-term polynomial, Generalized Bulletproofs re-defines it
|
||||
// as `2(n' + 1)`-term, where `n'` is `2 (c + 1)`.
|
||||
// When `c = 0`, `n' = 2`, and t is `6` (which lines up with Bulletproofs having a 6-term
|
||||
// polynomial).
|
||||
|
||||
// ni = n'
|
||||
let ni = 2 * (c + 1);
|
||||
// These indexes are from the Generalized Bulletproofs paper
|
||||
#[rustfmt::skip]
|
||||
let ilr = ni / 2; // 1 if c = 0
|
||||
#[rustfmt::skip]
|
||||
let io = ni; // 2 if c = 0
|
||||
#[rustfmt::skip]
|
||||
let is = ni + 1; // 3 if c = 0
|
||||
#[rustfmt::skip]
|
||||
let jlr = ni / 2; // 1 if c = 0
|
||||
#[rustfmt::skip]
|
||||
let jo = 0; // 0 if c = 0
|
||||
#[rustfmt::skip]
|
||||
let js = ni + 1; // 3 if c = 0
|
||||
|
||||
// If c = 0, these indexes perfectly align with the stated powers of X from the Bulletproofs
|
||||
// paper for the following coefficients
|
||||
|
||||
// Declare the l and r polynomials, assigning the traditional coefficients to their positions
|
||||
let mut l = vec![];
|
||||
let mut r = vec![];
|
||||
for _ in 0 .. (is + 1) {
|
||||
l.push(ScalarVector::new(0));
|
||||
r.push(ScalarVector::new(0));
|
||||
}
|
||||
|
||||
let mut l_weights = ScalarVector::new(n);
|
||||
let mut r_weights = ScalarVector::new(n);
|
||||
let mut o_weights = ScalarVector::new(n);
|
||||
for (constraint, z) in self.constraints.iter().zip(&z.0) {
|
||||
accumulate_vector(&mut l_weights, &constraint.WL, *z);
|
||||
accumulate_vector(&mut r_weights, &constraint.WR, *z);
|
||||
accumulate_vector(&mut o_weights, &constraint.WO, *z);
|
||||
}
|
||||
|
||||
l[ilr] = (r_weights * &y_inv) + &witness.aL;
|
||||
l[io] = witness.aO.clone();
|
||||
l[is] = sL;
|
||||
r[jlr] = l_weights + &(witness.aR.clone() * &y);
|
||||
r[jo] = o_weights - &y;
|
||||
r[js] = sR * &y;
|
||||
|
||||
// Pad as expected
|
||||
for l in &mut l {
|
||||
debug_assert!((l.len() == 0) || (l.len() == n));
|
||||
if l.len() == 0 {
|
||||
*l = ScalarVector::new(n);
|
||||
}
|
||||
}
|
||||
for r in &mut r {
|
||||
debug_assert!((r.len() == 0) || (r.len() == n));
|
||||
if r.len() == 0 {
|
||||
*r = ScalarVector::new(n);
|
||||
}
|
||||
}
|
||||
|
||||
// We now fill in the vector commitments
|
||||
// We use unused coefficients of l increasing from 0 (skipping ilr), and unused coefficients of
|
||||
// r decreasing from n' (skipping jlr)
|
||||
|
||||
let mut cg_weights = Vec::with_capacity(witness.c.len());
|
||||
let mut ch_weights = Vec::with_capacity(witness.c.len());
|
||||
for i in 0 .. witness.c.len() {
|
||||
let mut cg = ScalarVector::new(n);
|
||||
let mut ch = ScalarVector::new(n);
|
||||
for (constraint, z) in self.constraints.iter().zip(&z.0) {
|
||||
if let Some(WCG) = constraint.WCG.get(i) {
|
||||
accumulate_vector(&mut cg, WCG, *z);
|
||||
}
|
||||
if let Some(WCH) = constraint.WCH.get(i) {
|
||||
accumulate_vector(&mut ch, WCH, *z);
|
||||
}
|
||||
}
|
||||
cg_weights.push(cg);
|
||||
ch_weights.push(ch);
|
||||
}
|
||||
|
||||
for (i, (c, (cg_weights, ch_weights))) in
|
||||
witness.c.iter().zip(cg_weights.into_iter().zip(ch_weights)).enumerate()
|
||||
{
|
||||
let i = i + 1;
|
||||
let j = ni - i;
|
||||
|
||||
l[i] = c.g_values.clone();
|
||||
l[j] = ch_weights * &y_inv;
|
||||
r[j] = cg_weights;
|
||||
r[i] = (c.h_values.clone() * &y) + &r[i];
|
||||
}
|
||||
|
||||
// Multiply them to obtain t
|
||||
let mut t = ScalarVector::new(1 + (2 * (l.len() - 1)));
|
||||
for (i, l) in l.iter().enumerate() {
|
||||
for (j, r) in r.iter().enumerate() {
|
||||
let new_coeff = i + j;
|
||||
t[new_coeff] += l.inner_product(r.0.iter());
|
||||
}
|
||||
}
|
||||
|
||||
// Per Bulletproofs, calculate masks tau for each t where (i > 0) && (i != 2)
|
||||
// Per Generalized Bulletproofs, calculate masks tau for each t where i != n'
|
||||
// With Bulletproofs, t[0] is zero, hence its omission, yet Generalized Bulletproofs uses it
|
||||
let mut tau_before_ni = vec![];
|
||||
for _ in 0 .. ni {
|
||||
tau_before_ni.push(C::F::random(&mut *rng));
|
||||
}
|
||||
let mut tau_after_ni = vec![];
|
||||
for _ in 0 .. t.0[(ni + 1) ..].len() {
|
||||
tau_after_ni.push(C::F::random(&mut *rng));
|
||||
}
|
||||
// Calculate commitments to the coefficients of t, blinded by tau
|
||||
debug_assert_eq!(t.0[0 .. ni].len(), tau_before_ni.len());
|
||||
for (t, tau) in t.0[0 .. ni].iter().zip(tau_before_ni.iter()) {
|
||||
transcript.push_point(multiexp(&[(*t, self.generators.g()), (*tau, self.generators.h())]));
|
||||
}
|
||||
debug_assert_eq!(t.0[(ni + 1) ..].len(), tau_after_ni.len());
|
||||
for (t, tau) in t.0[(ni + 1) ..].iter().zip(tau_after_ni.iter()) {
|
||||
transcript.push_point(multiexp(&[(*t, self.generators.g()), (*tau, self.generators.h())]));
|
||||
}
|
||||
|
||||
let x: ScalarVector<C::F> = ScalarVector::powers(transcript.challenge(), t.len());
|
||||
|
||||
let poly_eval = |poly: &[ScalarVector<C::F>], x: &ScalarVector<_>| -> ScalarVector<_> {
|
||||
let mut res = ScalarVector::<C::F>::new(poly[0].0.len());
|
||||
for (i, coeff) in poly.iter().enumerate() {
|
||||
res = res + &(coeff.clone() * x[i]);
|
||||
}
|
||||
res
|
||||
};
|
||||
let l = poly_eval(&l, &x);
|
||||
let r = poly_eval(&r, &x);
|
||||
|
||||
let t_caret = l.inner_product(r.0.iter());
|
||||
|
||||
let mut V_weights = ScalarVector::new(self.V.len());
|
||||
for (constraint, z) in self.constraints.iter().zip(&z.0) {
|
||||
// We use `-z`, not `z`, as we write our constraint as `... + WV V = 0` not `= WV V + ..`
|
||||
// This means we need to subtract `WV V` from both sides, which we accomplish here
|
||||
accumulate_vector(&mut V_weights, &constraint.WV, -*z);
|
||||
}
|
||||
|
||||
let tau_x = {
|
||||
let mut tau_x_poly = vec![];
|
||||
tau_x_poly.extend(tau_before_ni);
|
||||
tau_x_poly.push(V_weights.inner_product(witness.v.iter().map(|v| &v.mask)));
|
||||
tau_x_poly.extend(tau_after_ni);
|
||||
|
||||
let mut tau_x = C::F::ZERO;
|
||||
for (i, coeff) in tau_x_poly.into_iter().enumerate() {
|
||||
tau_x += coeff * x[i];
|
||||
}
|
||||
tau_x
|
||||
};
|
||||
|
||||
// Calculate u for the powers of x variable to ilr/io/is
|
||||
let u = {
|
||||
// Calculate the first part of u
|
||||
let mut u = (alpha * x[ilr]) + (beta * x[io]) + (rho * x[is]);
|
||||
|
||||
// Incorporate the commitment masks multiplied by the associated power of x
|
||||
for (i, commitment) in witness.c.iter().enumerate() {
|
||||
let i = i + 1;
|
||||
u += x[i] * commitment.mask;
|
||||
}
|
||||
u
|
||||
};
|
||||
|
||||
// Use the Inner-Product argument to prove for this
|
||||
// P = t_caret * g + l * g_bold + r * (y_inv * h_bold)
|
||||
|
||||
let mut P_terms = Vec::with_capacity(1 + (2 * self.generators.len()));
|
||||
debug_assert_eq!(l.len(), r.len());
|
||||
for (i, (l, r)) in l.0.iter().zip(r.0.iter()).enumerate() {
|
||||
P_terms.push((*l, self.generators.g_bold(i)));
|
||||
P_terms.push((y_inv[i] * r, self.generators.h_bold(i)));
|
||||
}
|
||||
|
||||
// Protocol 1, inlined, since our IpStatement is for Protocol 2
|
||||
transcript.push_scalar(tau_x);
|
||||
transcript.push_scalar(u);
|
||||
transcript.push_scalar(t_caret);
|
||||
let ip_x = transcript.challenge();
|
||||
P_terms.push((ip_x * t_caret, self.generators.g()));
|
||||
IpStatement::new(
|
||||
self.generators,
|
||||
y_inv,
|
||||
ip_x,
|
||||
// Safe since IpStatement isn't a ZK proof
|
||||
P::Prover(multiexp_vartime(&P_terms)),
|
||||
)
|
||||
.unwrap()
|
||||
.prove(transcript, IpWitness::new(l, r).unwrap())
|
||||
.map_err(AcError::Ip)
|
||||
}
|
||||
|
||||
/// Verify a proof for this statement.
|
||||
pub fn verify<R: RngCore + CryptoRng>(
|
||||
self,
|
||||
rng: &mut R,
|
||||
verifier: &mut BatchVerifier<C>,
|
||||
transcript: &mut VerifierTranscript,
|
||||
) -> Result<(), AcError> {
|
||||
let n = self.n();
|
||||
let c = self.c();
|
||||
|
||||
let ni = 2 * (c + 1);
|
||||
|
||||
let ilr = ni / 2;
|
||||
let io = ni;
|
||||
let is = ni + 1;
|
||||
let jlr = ni / 2;
|
||||
|
||||
let l_r_poly_len = 1 + ni + 1;
|
||||
let t_poly_len = (2 * l_r_poly_len) - 1;
|
||||
|
||||
let AI = transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?;
|
||||
let AO = transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?;
|
||||
let S = transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?;
|
||||
let y = transcript.challenge();
|
||||
let z = transcript.challenge();
|
||||
let YzChallenges { y_inv, z } = self.yz_challenges(y, z);
|
||||
|
||||
let mut l_weights = ScalarVector::new(n);
|
||||
let mut r_weights = ScalarVector::new(n);
|
||||
let mut o_weights = ScalarVector::new(n);
|
||||
for (constraint, z) in self.constraints.iter().zip(&z.0) {
|
||||
accumulate_vector(&mut l_weights, &constraint.WL, *z);
|
||||
accumulate_vector(&mut r_weights, &constraint.WR, *z);
|
||||
accumulate_vector(&mut o_weights, &constraint.WO, *z);
|
||||
}
|
||||
let r_weights = r_weights * &y_inv;
|
||||
|
||||
let delta = r_weights.inner_product(l_weights.0.iter());
|
||||
|
||||
let mut T_before_ni = Vec::with_capacity(ni);
|
||||
let mut T_after_ni = Vec::with_capacity(t_poly_len - ni - 1);
|
||||
for _ in 0 .. ni {
|
||||
T_before_ni.push(transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?);
|
||||
}
|
||||
for _ in 0 .. (t_poly_len - ni - 1) {
|
||||
T_after_ni.push(transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?);
|
||||
}
|
||||
let x: ScalarVector<C::F> = ScalarVector::powers(transcript.challenge(), t_poly_len);
|
||||
|
||||
let tau_x = transcript.read_scalar::<C>().map_err(|_| AcError::IncompleteProof)?;
|
||||
let u = transcript.read_scalar::<C>().map_err(|_| AcError::IncompleteProof)?;
|
||||
let t_caret = transcript.read_scalar::<C>().map_err(|_| AcError::IncompleteProof)?;
|
||||
|
||||
// Lines 88-90, modified per Generalized Bulletproofs as needed w.r.t. t
|
||||
{
|
||||
let verifier_weight = C::F::random(&mut *rng);
|
||||
// lhs of the equation, weighted to enable batch verification
|
||||
verifier.g += t_caret * verifier_weight;
|
||||
verifier.h += tau_x * verifier_weight;
|
||||
|
||||
let mut V_weights = ScalarVector::new(self.V.len());
|
||||
for (constraint, z) in self.constraints.iter().zip(&z.0) {
|
||||
// We use `-z`, not `z`, as we write our constraint as `... + WV V = 0` not `= WV V + ..`
|
||||
// This means we need to subtract `WV V` from both sides, which we accomplish here
|
||||
accumulate_vector(&mut V_weights, &constraint.WV, -*z);
|
||||
}
|
||||
V_weights = V_weights * x[ni];
|
||||
|
||||
// rhs of the equation, negated to cause a sum to zero
|
||||
// `delta - z...`, instead of `delta + z...`, is done for the same reason as in the above WV
|
||||
// matrix transform
|
||||
verifier.g -= verifier_weight *
|
||||
x[ni] *
|
||||
(delta - z.inner_product(self.constraints.iter().map(|constraint| &constraint.c)));
|
||||
for pair in V_weights.0.into_iter().zip(self.V.0) {
|
||||
verifier.additional.push((-verifier_weight * pair.0, pair.1));
|
||||
}
|
||||
for (i, T) in T_before_ni.into_iter().enumerate() {
|
||||
verifier.additional.push((-verifier_weight * x[i], T));
|
||||
}
|
||||
for (i, T) in T_after_ni.into_iter().enumerate() {
|
||||
verifier.additional.push((-verifier_weight * x[ni + 1 + i], T));
|
||||
}
|
||||
}
|
||||
|
||||
let verifier_weight = C::F::random(&mut *rng);
|
||||
// Multiply `x` by `verifier_weight` as this effects `verifier_weight` onto most scalars and
|
||||
// saves a notable amount of operations
|
||||
let x = x * verifier_weight;
|
||||
|
||||
// This following block effectively calculates P, within the multiexp
|
||||
{
|
||||
verifier.additional.push((x[ilr], AI));
|
||||
verifier.additional.push((x[io], AO));
|
||||
// h' ** y is equivalent to h as h' is h ** y_inv
|
||||
let mut log2_n = 0;
|
||||
while (1 << log2_n) != n {
|
||||
log2_n += 1;
|
||||
}
|
||||
verifier.h_sum[log2_n] -= verifier_weight;
|
||||
verifier.additional.push((x[is], S));
|
||||
|
||||
// Lines 85-87 calculate WL, WR, WO
|
||||
// We preserve them in terms of g_bold and h_bold for a more efficient multiexp
|
||||
let mut h_bold_scalars = l_weights * x[jlr];
|
||||
for (i, wr) in (r_weights * x[jlr]).0.into_iter().enumerate() {
|
||||
verifier.g_bold[i] += wr;
|
||||
}
|
||||
// WO is weighted by x**jo where jo == 0, hence why we can ignore the x term
|
||||
h_bold_scalars = h_bold_scalars + &(o_weights * verifier_weight);
|
||||
|
||||
let mut cg_weights = Vec::with_capacity(self.C.len());
|
||||
let mut ch_weights = Vec::with_capacity(self.C.len());
|
||||
for i in 0 .. self.C.len() {
|
||||
let mut cg = ScalarVector::new(n);
|
||||
let mut ch = ScalarVector::new(n);
|
||||
for (constraint, z) in self.constraints.iter().zip(&z.0) {
|
||||
if let Some(WCG) = constraint.WCG.get(i) {
|
||||
accumulate_vector(&mut cg, WCG, *z);
|
||||
}
|
||||
if let Some(WCH) = constraint.WCH.get(i) {
|
||||
accumulate_vector(&mut ch, WCH, *z);
|
||||
}
|
||||
}
|
||||
cg_weights.push(cg);
|
||||
ch_weights.push(ch);
|
||||
}
|
||||
|
||||
// Push the terms for C, which increment from 0, and the terms for WC, which decrement from
|
||||
// n'
|
||||
for (i, (C, (WCG, WCH))) in
|
||||
self.C.0.into_iter().zip(cg_weights.into_iter().zip(ch_weights)).enumerate()
|
||||
{
|
||||
let i = i + 1;
|
||||
let j = ni - i;
|
||||
verifier.additional.push((x[i], C));
|
||||
h_bold_scalars = h_bold_scalars + &(WCG * x[j]);
|
||||
for (i, scalar) in (WCH * &y_inv * x[j]).0.into_iter().enumerate() {
|
||||
verifier.g_bold[i] += scalar;
|
||||
}
|
||||
}
|
||||
|
||||
// All terms for h_bold here have actually been for h_bold', h_bold * y_inv
|
||||
h_bold_scalars = h_bold_scalars * &y_inv;
|
||||
for (i, scalar) in h_bold_scalars.0.into_iter().enumerate() {
|
||||
verifier.h_bold[i] += scalar;
|
||||
}
|
||||
|
||||
// Remove u * h from P
|
||||
verifier.h -= verifier_weight * u;
|
||||
}
|
||||
|
||||
// Prove for lines 88, 92 with an Inner-Product statement
|
||||
// This inlines Protocol 1, as our IpStatement implements Protocol 2
|
||||
let ip_x = transcript.challenge();
|
||||
// P is amended with this additional term
|
||||
verifier.g += verifier_weight * ip_x * t_caret;
|
||||
IpStatement::new(self.generators, y_inv, ip_x, P::Verifier { verifier_weight })
|
||||
.unwrap()
|
||||
.verify(verifier, transcript)
|
||||
.map_err(AcError::Ip)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
360
crypto/evrf/generalized-bulletproofs/src/inner_product.rs
Normal file
360
crypto/evrf/generalized-bulletproofs/src/inner_product.rs
Normal file
@@ -0,0 +1,360 @@
|
||||
use multiexp::multiexp_vartime;
|
||||
use ciphersuite::{group::ff::Field, Ciphersuite};
|
||||
|
||||
#[rustfmt::skip]
|
||||
use crate::{ScalarVector, PointVector, ProofGenerators, BatchVerifier, transcript::*, padded_pow_of_2};
|
||||
|
||||
/// An error from proving/verifying Inner-Product statements.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum IpError {
|
||||
/// An incorrect amount of generators was provided.
|
||||
IncorrectAmountOfGenerators,
|
||||
/// The witness was inconsistent to the statement.
|
||||
///
|
||||
/// Sanity checks on the witness are always performed. If the library is compiled with debug
|
||||
/// assertions on, whether or not this witness actually opens `P` is checked.
|
||||
InconsistentWitness,
|
||||
/// The proof wasn't complete and the necessary values could not be read from the transcript.
|
||||
IncompleteProof,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub(crate) enum P<C: Ciphersuite> {
|
||||
Verifier { verifier_weight: C::F },
|
||||
Prover(C::G),
|
||||
}
|
||||
|
||||
/// The Bulletproofs Inner-Product statement.
|
||||
///
|
||||
/// This is for usage with Protocol 2 from the Bulletproofs paper.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct IpStatement<'a, C: Ciphersuite> {
|
||||
generators: ProofGenerators<'a, C>,
|
||||
// Weights for h_bold
|
||||
h_bold_weights: ScalarVector<C::F>,
|
||||
// u as the discrete logarithm of G
|
||||
u: C::F,
|
||||
// P
|
||||
P: P<C>,
|
||||
}
|
||||
|
||||
/// The witness for the Bulletproofs Inner-Product statement.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct IpWitness<C: Ciphersuite> {
|
||||
// a
|
||||
a: ScalarVector<C::F>,
|
||||
// b
|
||||
b: ScalarVector<C::F>,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> IpWitness<C> {
|
||||
/// Construct a new witness for an Inner-Product statement.
|
||||
///
|
||||
/// If the witness is less than a power of two, it is padded to the nearest power of two.
|
||||
///
|
||||
/// This functions return None if the lengths of a, b are mismatched or either are empty.
|
||||
pub(crate) fn new(mut a: ScalarVector<C::F>, mut b: ScalarVector<C::F>) -> Option<Self> {
|
||||
if a.0.is_empty() || (a.len() != b.len()) {
|
||||
None?;
|
||||
}
|
||||
|
||||
// Pad to the nearest power of 2
|
||||
let missing = padded_pow_of_2(a.len()) - a.len();
|
||||
a.0.reserve(missing);
|
||||
b.0.reserve(missing);
|
||||
for _ in 0 .. missing {
|
||||
a.0.push(C::F::ZERO);
|
||||
b.0.push(C::F::ZERO);
|
||||
}
|
||||
|
||||
Some(Self { a, b })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: Ciphersuite> IpStatement<'a, C> {
|
||||
/// Create a new Inner-Product statement.
|
||||
///
|
||||
/// This does not perform any transcripting of any variables within this statement. They must be
|
||||
/// deterministic to the existing transcript.
|
||||
pub(crate) fn new(
|
||||
generators: ProofGenerators<'a, C>,
|
||||
h_bold_weights: ScalarVector<C::F>,
|
||||
u: C::F,
|
||||
P: P<C>,
|
||||
) -> Result<Self, IpError> {
|
||||
if generators.h_bold_slice().len() != h_bold_weights.len() {
|
||||
Err(IpError::IncorrectAmountOfGenerators)?
|
||||
}
|
||||
Ok(Self { generators, h_bold_weights, u, P })
|
||||
}
|
||||
|
||||
/// Prove for this Inner-Product statement.
|
||||
///
|
||||
/// Returns an error if this statement couldn't be proven for (such as if the witness isn't
|
||||
/// consistent).
|
||||
pub(crate) fn prove(
|
||||
self,
|
||||
transcript: &mut Transcript,
|
||||
witness: IpWitness<C>,
|
||||
) -> Result<(), IpError> {
|
||||
let (mut g_bold, mut h_bold, u, mut P, mut a, mut b) = {
|
||||
let IpStatement { generators, h_bold_weights, u, P } = self;
|
||||
let u = generators.g() * u;
|
||||
|
||||
// Ensure we have the exact amount of generators
|
||||
if generators.g_bold_slice().len() != witness.a.len() {
|
||||
Err(IpError::IncorrectAmountOfGenerators)?;
|
||||
}
|
||||
// Acquire a local copy of the generators
|
||||
let g_bold = PointVector::<C>(generators.g_bold_slice().to_vec());
|
||||
let h_bold = PointVector::<C>(generators.h_bold_slice().to_vec()).mul_vec(&h_bold_weights);
|
||||
|
||||
let IpWitness { a, b } = witness;
|
||||
|
||||
let P = match P {
|
||||
P::Prover(point) => point,
|
||||
P::Verifier { .. } => {
|
||||
panic!("prove called with a P specification which was for the verifier")
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure this witness actually opens this statement
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let ag = a.0.iter().cloned().zip(g_bold.0.iter().cloned());
|
||||
let bh = b.0.iter().cloned().zip(h_bold.0.iter().cloned());
|
||||
let cu = core::iter::once((a.inner_product(b.0.iter()), u));
|
||||
if P != multiexp_vartime(&ag.chain(bh).chain(cu).collect::<Vec<_>>()) {
|
||||
Err(IpError::InconsistentWitness)?;
|
||||
}
|
||||
}
|
||||
|
||||
(g_bold, h_bold, u, P, a, b)
|
||||
};
|
||||
|
||||
// `else: (n > 1)` case, lines 18-35 of the Bulletproofs paper
|
||||
// This interprets `g_bold.len()` as `n`
|
||||
while g_bold.len() > 1 {
|
||||
// Split a, b, g_bold, h_bold as needed for lines 20-24
|
||||
let (a1, a2) = a.clone().split();
|
||||
let (b1, b2) = b.clone().split();
|
||||
|
||||
let (g_bold1, g_bold2) = g_bold.split();
|
||||
let (h_bold1, h_bold2) = h_bold.split();
|
||||
|
||||
let n_hat = g_bold1.len();
|
||||
|
||||
// Sanity
|
||||
debug_assert_eq!(a1.len(), n_hat);
|
||||
debug_assert_eq!(a2.len(), n_hat);
|
||||
debug_assert_eq!(b1.len(), n_hat);
|
||||
debug_assert_eq!(b2.len(), n_hat);
|
||||
debug_assert_eq!(g_bold1.len(), n_hat);
|
||||
debug_assert_eq!(g_bold2.len(), n_hat);
|
||||
debug_assert_eq!(h_bold1.len(), n_hat);
|
||||
debug_assert_eq!(h_bold2.len(), n_hat);
|
||||
|
||||
// cl, cr, lines 21-22
|
||||
let cl = a1.inner_product(b2.0.iter());
|
||||
let cr = a2.inner_product(b1.0.iter());
|
||||
|
||||
let L = {
|
||||
let mut L_terms = Vec::with_capacity(1 + (2 * g_bold1.len()));
|
||||
for (a, g) in a1.0.iter().zip(g_bold2.0.iter()) {
|
||||
L_terms.push((*a, *g));
|
||||
}
|
||||
for (b, h) in b2.0.iter().zip(h_bold1.0.iter()) {
|
||||
L_terms.push((*b, *h));
|
||||
}
|
||||
L_terms.push((cl, u));
|
||||
// Uses vartime since this isn't a ZK proof
|
||||
multiexp_vartime(&L_terms)
|
||||
};
|
||||
|
||||
let R = {
|
||||
let mut R_terms = Vec::with_capacity(1 + (2 * g_bold1.len()));
|
||||
for (a, g) in a2.0.iter().zip(g_bold1.0.iter()) {
|
||||
R_terms.push((*a, *g));
|
||||
}
|
||||
for (b, h) in b1.0.iter().zip(h_bold2.0.iter()) {
|
||||
R_terms.push((*b, *h));
|
||||
}
|
||||
R_terms.push((cr, u));
|
||||
multiexp_vartime(&R_terms)
|
||||
};
|
||||
|
||||
// Now that we've calculate L, R, transcript them to receive x (26-27)
|
||||
transcript.push_point(L);
|
||||
transcript.push_point(R);
|
||||
let x: C::F = transcript.challenge();
|
||||
let x_inv = x.invert().unwrap();
|
||||
|
||||
// The prover and verifier now calculate the following (28-31)
|
||||
g_bold = PointVector(Vec::with_capacity(g_bold1.len()));
|
||||
for (a, b) in g_bold1.0.into_iter().zip(g_bold2.0.into_iter()) {
|
||||
g_bold.0.push(multiexp_vartime(&[(x_inv, a), (x, b)]));
|
||||
}
|
||||
h_bold = PointVector(Vec::with_capacity(h_bold1.len()));
|
||||
for (a, b) in h_bold1.0.into_iter().zip(h_bold2.0.into_iter()) {
|
||||
h_bold.0.push(multiexp_vartime(&[(x, a), (x_inv, b)]));
|
||||
}
|
||||
P = (L * (x * x)) + P + (R * (x_inv * x_inv));
|
||||
|
||||
// 32-34
|
||||
a = (a1 * x) + &(a2 * x_inv);
|
||||
b = (b1 * x_inv) + &(b2 * x);
|
||||
}
|
||||
|
||||
// `if n = 1` case from line 14-17
|
||||
|
||||
// Sanity
|
||||
debug_assert_eq!(g_bold.len(), 1);
|
||||
debug_assert_eq!(h_bold.len(), 1);
|
||||
debug_assert_eq!(a.len(), 1);
|
||||
debug_assert_eq!(b.len(), 1);
|
||||
|
||||
// We simply send a/b
|
||||
transcript.push_scalar(a[0]);
|
||||
transcript.push_scalar(b[0]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
This has room for optimization worth investigating further. It currently takes
|
||||
an iterative approach. It can be optimized further via divide and conquer.
|
||||
|
||||
Assume there are 4 challenges.
|
||||
|
||||
Iterative approach (current):
|
||||
1. Do the optimal multiplications across challenge column 0 and 1.
|
||||
2. Do the optimal multiplications across that result and column 2.
|
||||
3. Do the optimal multiplications across that result and column 3.
|
||||
|
||||
Divide and conquer (worth investigating further):
|
||||
1. Do the optimal multiplications across challenge column 0 and 1.
|
||||
2. Do the optimal multiplications across challenge column 2 and 3.
|
||||
3. Multiply both results together.
|
||||
|
||||
When there are 4 challenges (n=16), the iterative approach does 28 multiplications
|
||||
versus divide and conquer's 24.
|
||||
*/
|
||||
fn challenge_products(challenges: &[(C::F, C::F)]) -> Vec<C::F> {
|
||||
let mut products = vec![C::F::ONE; 1 << challenges.len()];
|
||||
|
||||
if !challenges.is_empty() {
|
||||
products[0] = challenges[0].1;
|
||||
products[1] = challenges[0].0;
|
||||
|
||||
for (j, challenge) in challenges.iter().enumerate().skip(1) {
|
||||
let mut slots = (1 << (j + 1)) - 1;
|
||||
while slots > 0 {
|
||||
products[slots] = products[slots / 2] * challenge.0;
|
||||
products[slots - 1] = products[slots / 2] * challenge.1;
|
||||
|
||||
slots = slots.saturating_sub(2);
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check since if the above failed to populate, it'd be critical
|
||||
for product in &products {
|
||||
debug_assert!(!bool::from(product.is_zero()));
|
||||
}
|
||||
}
|
||||
|
||||
products
|
||||
}
|
||||
|
||||
/// Queue an Inner-Product proof for batch verification.
|
||||
///
|
||||
/// This will return Err if there is an error. This will return Ok if the proof was successfully
|
||||
/// queued for batch verification. The caller is required to verify the batch in order to ensure
|
||||
/// the proof is actually correct.
|
||||
pub(crate) fn verify(
|
||||
self,
|
||||
verifier: &mut BatchVerifier<C>,
|
||||
transcript: &mut VerifierTranscript,
|
||||
) -> Result<(), IpError> {
|
||||
let IpStatement { generators, h_bold_weights, u, P } = self;
|
||||
|
||||
// Calculate the discrete log w.r.t. 2 for the amount of generators present
|
||||
let mut lr_len = 0;
|
||||
while (1 << lr_len) < generators.g_bold_slice().len() {
|
||||
lr_len += 1;
|
||||
}
|
||||
|
||||
let weight = match P {
|
||||
P::Prover(_) => panic!("prove called with a P specification which was for the prover"),
|
||||
P::Verifier { verifier_weight } => verifier_weight,
|
||||
};
|
||||
|
||||
// Again, we start with the `else: (n > 1)` case
|
||||
|
||||
// We need x, x_inv per lines 25-27 for lines 28-31
|
||||
let mut L = Vec::with_capacity(lr_len);
|
||||
let mut R = Vec::with_capacity(lr_len);
|
||||
let mut xs: Vec<C::F> = Vec::with_capacity(lr_len);
|
||||
for _ in 0 .. lr_len {
|
||||
L.push(transcript.read_point::<C>().map_err(|_| IpError::IncompleteProof)?);
|
||||
R.push(transcript.read_point::<C>().map_err(|_| IpError::IncompleteProof)?);
|
||||
xs.push(transcript.challenge());
|
||||
}
|
||||
|
||||
// We calculate their inverse in batch
|
||||
let mut x_invs = xs.clone();
|
||||
{
|
||||
let mut scratch = vec![C::F::ZERO; x_invs.len()];
|
||||
ciphersuite::group::ff::BatchInverter::invert_with_external_scratch(
|
||||
&mut x_invs,
|
||||
&mut scratch,
|
||||
);
|
||||
}
|
||||
|
||||
// Now, with x and x_inv, we need to calculate g_bold', h_bold', P'
|
||||
//
|
||||
// For the sake of performance, we solely want to calculate all of these in terms of scalings
|
||||
// for g_bold, h_bold, P, and don't want to actually perform intermediary scalings of the
|
||||
// points
|
||||
//
|
||||
// L and R are easy, as it's simply x**2, x**-2
|
||||
//
|
||||
// For the series of g_bold, h_bold, we use the `challenge_products` function
|
||||
// For how that works, please see its own documentation
|
||||
let product_cache = {
|
||||
let mut challenges = Vec::with_capacity(lr_len);
|
||||
|
||||
let x_iter = xs.into_iter().zip(x_invs);
|
||||
let lr_iter = L.into_iter().zip(R);
|
||||
for ((x, x_inv), (L, R)) in x_iter.zip(lr_iter) {
|
||||
challenges.push((x, x_inv));
|
||||
verifier.additional.push((weight * x.square(), L));
|
||||
verifier.additional.push((weight * x_inv.square(), R));
|
||||
}
|
||||
|
||||
Self::challenge_products(&challenges)
|
||||
};
|
||||
|
||||
// And now for the `if n = 1` case
|
||||
let a = transcript.read_scalar::<C>().map_err(|_| IpError::IncompleteProof)?;
|
||||
let b = transcript.read_scalar::<C>().map_err(|_| IpError::IncompleteProof)?;
|
||||
let c = a * b;
|
||||
|
||||
// The multiexp of these terms equate to the final permutation of P
|
||||
// We now add terms for a * g_bold' + b * h_bold' b + c * u, with the scalars negative such
|
||||
// that the terms sum to 0 for an honest prover
|
||||
|
||||
// The g_bold * a term case from line 16
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0 .. generators.g_bold_slice().len() {
|
||||
verifier.g_bold[i] -= weight * product_cache[i] * a;
|
||||
}
|
||||
// The h_bold * b term case from line 16
|
||||
for i in 0 .. generators.h_bold_slice().len() {
|
||||
verifier.h_bold[i] -=
|
||||
weight * product_cache[product_cache.len() - 1 - i] * b * h_bold_weights[i];
|
||||
}
|
||||
// The c * u term case from line 16
|
||||
verifier.g -= weight * c * u;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
327
crypto/evrf/generalized-bulletproofs/src/lib.rs
Normal file
327
crypto/evrf/generalized-bulletproofs/src/lib.rs
Normal file
@@ -0,0 +1,327 @@
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use core::fmt;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use multiexp::{multiexp, multiexp_vartime};
|
||||
use ciphersuite::{
|
||||
group::{ff::Field, Group, GroupEncoding},
|
||||
Ciphersuite,
|
||||
};
|
||||
|
||||
mod scalar_vector;
|
||||
pub use scalar_vector::ScalarVector;
|
||||
mod point_vector;
|
||||
pub use point_vector::PointVector;
|
||||
|
||||
/// The transcript formats.
|
||||
pub mod transcript;
|
||||
|
||||
pub(crate) mod inner_product;
|
||||
|
||||
pub(crate) mod lincomb;
|
||||
|
||||
/// The arithmetic circuit proof.
|
||||
pub mod arithmetic_circuit_proof;
|
||||
|
||||
/// Functionlity useful when testing.
|
||||
#[cfg(any(test, feature = "tests"))]
|
||||
pub mod tests;
|
||||
|
||||
/// Calculate the nearest power of two greater than or equivalent to the argument.
|
||||
pub(crate) fn padded_pow_of_2(i: usize) -> usize {
|
||||
let mut next_pow_of_2 = 1;
|
||||
while next_pow_of_2 < i {
|
||||
next_pow_of_2 <<= 1;
|
||||
}
|
||||
next_pow_of_2
|
||||
}
|
||||
|
||||
/// An error from working with generators.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum GeneratorsError {
|
||||
/// The provided list of generators for `g` (bold) was empty.
|
||||
GBoldEmpty,
|
||||
/// The provided list of generators for `h` (bold) did not match `g` (bold) in length.
|
||||
DifferingGhBoldLengths,
|
||||
/// The amount of provided generators were not a power of two.
|
||||
NotPowerOfTwo,
|
||||
/// A generator was used multiple times.
|
||||
DuplicatedGenerator,
|
||||
}
|
||||
|
||||
/// A full set of generators.
|
||||
#[derive(Clone)]
|
||||
pub struct Generators<C: Ciphersuite> {
|
||||
g: C::G,
|
||||
h: C::G,
|
||||
|
||||
g_bold: Vec<C::G>,
|
||||
h_bold: Vec<C::G>,
|
||||
h_sum: Vec<C::G>,
|
||||
}
|
||||
|
||||
/// A batch verifier of proofs.
|
||||
#[must_use]
|
||||
pub struct BatchVerifier<C: Ciphersuite> {
|
||||
g: C::F,
|
||||
h: C::F,
|
||||
|
||||
g_bold: Vec<C::F>,
|
||||
h_bold: Vec<C::F>,
|
||||
h_sum: Vec<C::F>,
|
||||
|
||||
additional: Vec<(C::F, C::G)>,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> fmt::Debug for Generators<C> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let g = self.g.to_bytes();
|
||||
let g: &[u8] = g.as_ref();
|
||||
|
||||
let h = self.h.to_bytes();
|
||||
let h: &[u8] = h.as_ref();
|
||||
|
||||
fmt.debug_struct("Generators").field("g", &g).field("h", &h).finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// The generators for a specific proof.
|
||||
///
|
||||
/// This potentially have been reduced in size from the original set of generators, as beneficial
|
||||
/// to performance.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ProofGenerators<'a, C: Ciphersuite> {
|
||||
g: &'a C::G,
|
||||
h: &'a C::G,
|
||||
|
||||
g_bold: &'a [C::G],
|
||||
h_bold: &'a [C::G],
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> fmt::Debug for ProofGenerators<'_, C> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let g = self.g.to_bytes();
|
||||
let g: &[u8] = g.as_ref();
|
||||
|
||||
let h = self.h.to_bytes();
|
||||
let h: &[u8] = h.as_ref();
|
||||
|
||||
fmt.debug_struct("ProofGenerators").field("g", &g).field("h", &h).finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Generators<C> {
|
||||
/// Construct an instance of Generators for usage with Bulletproofs.
|
||||
pub fn new(
|
||||
g: C::G,
|
||||
h: C::G,
|
||||
g_bold: Vec<C::G>,
|
||||
h_bold: Vec<C::G>,
|
||||
) -> Result<Self, GeneratorsError> {
|
||||
if g_bold.is_empty() {
|
||||
Err(GeneratorsError::GBoldEmpty)?;
|
||||
}
|
||||
if g_bold.len() != h_bold.len() {
|
||||
Err(GeneratorsError::DifferingGhBoldLengths)?;
|
||||
}
|
||||
if padded_pow_of_2(g_bold.len()) != g_bold.len() {
|
||||
Err(GeneratorsError::NotPowerOfTwo)?;
|
||||
}
|
||||
|
||||
let mut set = HashSet::new();
|
||||
let mut add_generator = |generator: &C::G| {
|
||||
assert!(!bool::from(generator.is_identity()));
|
||||
let bytes = generator.to_bytes();
|
||||
!set.insert(bytes.as_ref().to_vec())
|
||||
};
|
||||
|
||||
assert!(!add_generator(&g), "g was prior present in empty set");
|
||||
if add_generator(&h) {
|
||||
Err(GeneratorsError::DuplicatedGenerator)?;
|
||||
}
|
||||
for g in &g_bold {
|
||||
if add_generator(g) {
|
||||
Err(GeneratorsError::DuplicatedGenerator)?;
|
||||
}
|
||||
}
|
||||
for h in &h_bold {
|
||||
if add_generator(h) {
|
||||
Err(GeneratorsError::DuplicatedGenerator)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut running_h_sum = C::G::identity();
|
||||
let mut h_sum = vec![];
|
||||
let mut next_pow_of_2 = 1;
|
||||
for (i, h) in h_bold.iter().enumerate() {
|
||||
running_h_sum += h;
|
||||
if (i + 1) == next_pow_of_2 {
|
||||
h_sum.push(running_h_sum);
|
||||
next_pow_of_2 *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Generators { g, h, g_bold, h_bold, h_sum })
|
||||
}
|
||||
|
||||
/// Create a BatchVerifier for proofs which use these generators.
|
||||
pub fn batch_verifier(&self) -> BatchVerifier<C> {
|
||||
BatchVerifier {
|
||||
g: C::F::ZERO,
|
||||
h: C::F::ZERO,
|
||||
|
||||
g_bold: vec![C::F::ZERO; self.g_bold.len()],
|
||||
h_bold: vec![C::F::ZERO; self.h_bold.len()],
|
||||
h_sum: vec![C::F::ZERO; self.h_sum.len()],
|
||||
|
||||
additional: Vec::with_capacity(128),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify all proofs queued for batch verification in this BatchVerifier.
|
||||
#[must_use]
|
||||
pub fn verify(&self, verifier: BatchVerifier<C>) -> bool {
|
||||
multiexp_vartime(
|
||||
&[(verifier.g, self.g), (verifier.h, self.h)]
|
||||
.into_iter()
|
||||
.chain(verifier.g_bold.into_iter().zip(self.g_bold.iter().cloned()))
|
||||
.chain(verifier.h_bold.into_iter().zip(self.h_bold.iter().cloned()))
|
||||
.chain(verifier.h_sum.into_iter().zip(self.h_sum.iter().cloned()))
|
||||
.chain(verifier.additional)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.is_identity()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// The `g` generator.
|
||||
pub fn g(&self) -> C::G {
|
||||
self.g
|
||||
}
|
||||
|
||||
/// The `h` generator.
|
||||
pub fn h(&self) -> C::G {
|
||||
self.h
|
||||
}
|
||||
|
||||
/// A slice to view the `g` (bold) generators.
|
||||
pub fn g_bold_slice(&self) -> &[C::G] {
|
||||
&self.g_bold
|
||||
}
|
||||
|
||||
/// A slice to view the `h` (bold) generators.
|
||||
pub fn h_bold_slice(&self) -> &[C::G] {
|
||||
&self.h_bold
|
||||
}
|
||||
|
||||
/// Reduce a set of generators to the quantity necessary to support a certain amount of
|
||||
/// in-circuit multiplications/terms in a Pedersen vector commitment.
|
||||
///
|
||||
/// Returns None if reducing to 0 or if the generators reduced are insufficient to provide this
|
||||
/// many generators.
|
||||
pub fn reduce(&self, generators: usize) -> Option<ProofGenerators<'_, C>> {
|
||||
if generators == 0 {
|
||||
None?;
|
||||
}
|
||||
|
||||
// Round to the nearest power of 2
|
||||
let generators = padded_pow_of_2(generators);
|
||||
if generators > self.g_bold.len() {
|
||||
None?;
|
||||
}
|
||||
|
||||
Some(ProofGenerators {
|
||||
g: &self.g,
|
||||
h: &self.h,
|
||||
|
||||
g_bold: &self.g_bold[.. generators],
|
||||
h_bold: &self.h_bold[.. generators],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: Ciphersuite> ProofGenerators<'a, C> {
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.g_bold.len()
|
||||
}
|
||||
|
||||
pub(crate) fn g(&self) -> C::G {
|
||||
*self.g
|
||||
}
|
||||
|
||||
pub(crate) fn h(&self) -> C::G {
|
||||
*self.h
|
||||
}
|
||||
|
||||
pub(crate) fn g_bold(&self, i: usize) -> C::G {
|
||||
self.g_bold[i]
|
||||
}
|
||||
|
||||
pub(crate) fn h_bold(&self, i: usize) -> C::G {
|
||||
self.h_bold[i]
|
||||
}
|
||||
|
||||
pub(crate) fn g_bold_slice(&self) -> &[C::G] {
|
||||
self.g_bold
|
||||
}
|
||||
|
||||
pub(crate) fn h_bold_slice(&self) -> &[C::G] {
|
||||
self.h_bold
|
||||
}
|
||||
}
|
||||
|
||||
/// The opening of a Pedersen commitment.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct PedersenCommitment<C: Ciphersuite> {
|
||||
/// The value committed to.
|
||||
pub value: C::F,
|
||||
/// The mask blinding the value committed to.
|
||||
pub mask: C::F,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> PedersenCommitment<C> {
|
||||
/// Commit to this value, yielding the Pedersen commitment.
|
||||
pub fn commit(&self, g: C::G, h: C::G) -> C::G {
|
||||
multiexp(&[(self.value, g), (self.mask, h)])
|
||||
}
|
||||
}
|
||||
|
||||
/// The opening of a Pedersen vector commitment.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct PedersenVectorCommitment<C: Ciphersuite> {
|
||||
/// The values committed to across the `g` (bold) generators.
|
||||
pub g_values: ScalarVector<C::F>,
|
||||
/// The values committed to across the `h` (bold) generators.
|
||||
pub h_values: ScalarVector<C::F>,
|
||||
/// The mask blinding the values committed to.
|
||||
pub mask: C::F,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> PedersenVectorCommitment<C> {
|
||||
/// Commit to the vectors of values.
|
||||
///
|
||||
/// This function returns None if the amount of generators is less than the amount of values
|
||||
/// within the relevant vector.
|
||||
pub fn commit(&self, g_bold: &[C::G], h_bold: &[C::G], h: C::G) -> Option<C::G> {
|
||||
if (g_bold.len() < self.g_values.len()) || (h_bold.len() < self.h_values.len()) {
|
||||
None?;
|
||||
};
|
||||
|
||||
let mut terms = vec![(self.mask, h)];
|
||||
for pair in self.g_values.0.iter().cloned().zip(g_bold.iter().cloned()) {
|
||||
terms.push(pair);
|
||||
}
|
||||
for pair in self.h_values.0.iter().cloned().zip(h_bold.iter().cloned()) {
|
||||
terms.push(pair);
|
||||
}
|
||||
let res = multiexp(&terms);
|
||||
terms.zeroize();
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
265
crypto/evrf/generalized-bulletproofs/src/lincomb.rs
Normal file
265
crypto/evrf/generalized-bulletproofs/src/lincomb.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
use core::ops::{Add, Sub, Mul};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use ciphersuite::group::ff::PrimeField;
|
||||
|
||||
use crate::ScalarVector;
|
||||
|
||||
/// A reference to a variable usable within linear combinations.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum Variable {
|
||||
/// A variable within the left vector of vectors multiplied against each other.
|
||||
aL(usize),
|
||||
/// A variable within the right vector of vectors multiplied against each other.
|
||||
aR(usize),
|
||||
/// A variable within the output vector of the left vector multiplied by the right vector.
|
||||
aO(usize),
|
||||
/// A variable within a Pedersen vector commitment, committed to with a generator from `g` (bold).
|
||||
CG {
|
||||
/// The commitment being indexed.
|
||||
commitment: usize,
|
||||
/// The index of the variable.
|
||||
index: usize,
|
||||
},
|
||||
/// A variable within a Pedersen vector commitment, committed to with a generator from `h` (bold).
|
||||
CH {
|
||||
/// The commitment being indexed.
|
||||
commitment: usize,
|
||||
/// The index of the variable.
|
||||
index: usize,
|
||||
},
|
||||
/// A variable within a Pedersen commitment.
|
||||
V(usize),
|
||||
}
|
||||
|
||||
// Does a NOP as there shouldn't be anything critical here
|
||||
impl Zeroize for Variable {
|
||||
fn zeroize(&mut self) {}
|
||||
}
|
||||
|
||||
/// A linear combination.
|
||||
///
|
||||
/// Specifically, `WL aL + WR aR + WO aO + WCG C_G + WCH C_H + WV V + c`.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
#[must_use]
|
||||
pub struct LinComb<F: PrimeField> {
|
||||
pub(crate) highest_a_index: Option<usize>,
|
||||
pub(crate) highest_c_index: Option<usize>,
|
||||
pub(crate) highest_v_index: Option<usize>,
|
||||
|
||||
// Sparse representation of WL/WR/WO
|
||||
pub(crate) WL: Vec<(usize, F)>,
|
||||
pub(crate) WR: Vec<(usize, F)>,
|
||||
pub(crate) WO: Vec<(usize, F)>,
|
||||
// Sparse representation once within a commitment
|
||||
pub(crate) WCG: Vec<Vec<(usize, F)>>,
|
||||
pub(crate) WCH: Vec<Vec<(usize, F)>>,
|
||||
// Sparse representation of WV
|
||||
pub(crate) WV: Vec<(usize, F)>,
|
||||
pub(crate) c: F,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> From<Variable> for LinComb<F> {
|
||||
fn from(constrainable: Variable) -> LinComb<F> {
|
||||
LinComb::empty().term(F::ONE, constrainable)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Add<&LinComb<F>> for LinComb<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, constraint: &Self) -> Self {
|
||||
self.highest_a_index = self.highest_a_index.max(constraint.highest_a_index);
|
||||
self.highest_c_index = self.highest_c_index.max(constraint.highest_c_index);
|
||||
self.highest_v_index = self.highest_v_index.max(constraint.highest_v_index);
|
||||
|
||||
self.WL.extend(&constraint.WL);
|
||||
self.WR.extend(&constraint.WR);
|
||||
self.WO.extend(&constraint.WO);
|
||||
while self.WCG.len() < constraint.WCG.len() {
|
||||
self.WCG.push(vec![]);
|
||||
}
|
||||
while self.WCH.len() < constraint.WCH.len() {
|
||||
self.WCH.push(vec![]);
|
||||
}
|
||||
for (sWC, cWC) in self.WCG.iter_mut().zip(&constraint.WCG) {
|
||||
sWC.extend(cWC);
|
||||
}
|
||||
for (sWC, cWC) in self.WCH.iter_mut().zip(&constraint.WCH) {
|
||||
sWC.extend(cWC);
|
||||
}
|
||||
self.WV.extend(&constraint.WV);
|
||||
self.c += constraint.c;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Sub<&LinComb<F>> for LinComb<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(mut self, constraint: &Self) -> Self {
|
||||
self.highest_a_index = self.highest_a_index.max(constraint.highest_a_index);
|
||||
self.highest_c_index = self.highest_c_index.max(constraint.highest_c_index);
|
||||
self.highest_v_index = self.highest_v_index.max(constraint.highest_v_index);
|
||||
|
||||
self.WL.extend(constraint.WL.iter().map(|(i, weight)| (*i, -*weight)));
|
||||
self.WR.extend(constraint.WR.iter().map(|(i, weight)| (*i, -*weight)));
|
||||
self.WO.extend(constraint.WO.iter().map(|(i, weight)| (*i, -*weight)));
|
||||
while self.WCG.len() < constraint.WCG.len() {
|
||||
self.WCG.push(vec![]);
|
||||
}
|
||||
while self.WCH.len() < constraint.WCH.len() {
|
||||
self.WCH.push(vec![]);
|
||||
}
|
||||
for (sWC, cWC) in self.WCG.iter_mut().zip(&constraint.WCG) {
|
||||
sWC.extend(cWC.iter().map(|(i, weight)| (*i, -*weight)));
|
||||
}
|
||||
for (sWC, cWC) in self.WCH.iter_mut().zip(&constraint.WCH) {
|
||||
sWC.extend(cWC.iter().map(|(i, weight)| (*i, -*weight)));
|
||||
}
|
||||
self.WV.extend(constraint.WV.iter().map(|(i, weight)| (*i, -*weight)));
|
||||
self.c -= constraint.c;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Mul<F> for LinComb<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(mut self, scalar: F) -> Self {
|
||||
for (_, weight) in self.WL.iter_mut() {
|
||||
*weight *= scalar;
|
||||
}
|
||||
for (_, weight) in self.WR.iter_mut() {
|
||||
*weight *= scalar;
|
||||
}
|
||||
for (_, weight) in self.WO.iter_mut() {
|
||||
*weight *= scalar;
|
||||
}
|
||||
for WC in self.WCG.iter_mut() {
|
||||
for (_, weight) in WC {
|
||||
*weight *= scalar;
|
||||
}
|
||||
}
|
||||
for WC in self.WCH.iter_mut() {
|
||||
for (_, weight) in WC {
|
||||
*weight *= scalar;
|
||||
}
|
||||
}
|
||||
for (_, weight) in self.WV.iter_mut() {
|
||||
*weight *= scalar;
|
||||
}
|
||||
self.c *= scalar;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> LinComb<F> {
|
||||
/// Create an empty linear combination.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
highest_a_index: None,
|
||||
highest_c_index: None,
|
||||
highest_v_index: None,
|
||||
WL: vec![],
|
||||
WR: vec![],
|
||||
WO: vec![],
|
||||
WCG: vec![],
|
||||
WCH: vec![],
|
||||
WV: vec![],
|
||||
c: F::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new instance of a term to this linear combination.
|
||||
pub fn term(mut self, scalar: F, constrainable: Variable) -> Self {
|
||||
match constrainable {
|
||||
Variable::aL(i) => {
|
||||
self.highest_a_index = self.highest_a_index.max(Some(i));
|
||||
self.WL.push((i, scalar))
|
||||
}
|
||||
Variable::aR(i) => {
|
||||
self.highest_a_index = self.highest_a_index.max(Some(i));
|
||||
self.WR.push((i, scalar))
|
||||
}
|
||||
Variable::aO(i) => {
|
||||
self.highest_a_index = self.highest_a_index.max(Some(i));
|
||||
self.WO.push((i, scalar))
|
||||
}
|
||||
Variable::CG { commitment: i, index: j } => {
|
||||
self.highest_c_index = self.highest_c_index.max(Some(i));
|
||||
self.highest_a_index = self.highest_a_index.max(Some(j));
|
||||
while self.WCG.len() <= i {
|
||||
self.WCG.push(vec![]);
|
||||
}
|
||||
self.WCG[i].push((j, scalar))
|
||||
}
|
||||
Variable::CH { commitment: i, index: j } => {
|
||||
self.highest_c_index = self.highest_c_index.max(Some(i));
|
||||
self.highest_a_index = self.highest_a_index.max(Some(j));
|
||||
while self.WCH.len() <= i {
|
||||
self.WCH.push(vec![]);
|
||||
}
|
||||
self.WCH[i].push((j, scalar))
|
||||
}
|
||||
Variable::V(i) => {
|
||||
self.highest_v_index = self.highest_v_index.max(Some(i));
|
||||
self.WV.push((i, scalar));
|
||||
}
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Add to the constant c.
|
||||
pub fn constant(mut self, scalar: F) -> Self {
|
||||
self.c += scalar;
|
||||
self
|
||||
}
|
||||
|
||||
/// View the current weights for aL.
|
||||
pub fn WL(&self) -> &[(usize, F)] {
|
||||
&self.WL
|
||||
}
|
||||
|
||||
/// View the current weights for aR.
|
||||
pub fn WR(&self) -> &[(usize, F)] {
|
||||
&self.WR
|
||||
}
|
||||
|
||||
/// View the current weights for aO.
|
||||
pub fn WO(&self) -> &[(usize, F)] {
|
||||
&self.WO
|
||||
}
|
||||
|
||||
/// View the current weights for CG.
|
||||
pub fn WCG(&self) -> &[Vec<(usize, F)>] {
|
||||
&self.WCG
|
||||
}
|
||||
|
||||
/// View the current weights for CH.
|
||||
pub fn WCH(&self) -> &[Vec<(usize, F)>] {
|
||||
&self.WCH
|
||||
}
|
||||
|
||||
/// View the current weights for V.
|
||||
pub fn WV(&self) -> &[(usize, F)] {
|
||||
&self.WV
|
||||
}
|
||||
|
||||
/// View the current constant.
|
||||
pub fn c(&self) -> F {
|
||||
self.c
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn accumulate_vector<F: PrimeField>(
|
||||
accumulator: &mut ScalarVector<F>,
|
||||
values: &[(usize, F)],
|
||||
weight: F,
|
||||
) {
|
||||
for (i, coeff) in values {
|
||||
accumulator[*i] += *coeff * weight;
|
||||
}
|
||||
}
|
||||
121
crypto/evrf/generalized-bulletproofs/src/point_vector.rs
Normal file
121
crypto/evrf/generalized-bulletproofs/src/point_vector.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use core::ops::{Index, IndexMut};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use ciphersuite::Ciphersuite;
|
||||
|
||||
#[cfg(test)]
|
||||
use multiexp::multiexp;
|
||||
|
||||
use crate::ScalarVector;
|
||||
|
||||
/// A point vector struct with the functionality necessary for Bulletproofs.
|
||||
///
|
||||
/// The math operations for this panic upon any invalid operation, such as if vectors of different
|
||||
/// lengths are added. The full extent of invalidity is not fully defined. Only field access is
|
||||
/// guaranteed to have a safe, public API.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct PointVector<C: Ciphersuite>(pub(crate) Vec<C::G>);
|
||||
|
||||
impl<C: Ciphersuite> Index<usize> for PointVector<C> {
|
||||
type Output = C::G;
|
||||
fn index(&self, index: usize) -> &C::G {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> IndexMut<usize> for PointVector<C> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut C::G {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> PointVector<C> {
|
||||
/*
|
||||
pub(crate) fn add(&self, point: impl AsRef<C::G>) -> Self {
|
||||
let mut res = self.clone();
|
||||
for val in res.0.iter_mut() {
|
||||
*val += point.as_ref();
|
||||
}
|
||||
res
|
||||
}
|
||||
pub(crate) fn sub(&self, point: impl AsRef<C::G>) -> Self {
|
||||
let mut res = self.clone();
|
||||
for val in res.0.iter_mut() {
|
||||
*val -= point.as_ref();
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) fn mul(&self, scalar: impl core::borrow::Borrow<C::F>) -> Self {
|
||||
let mut res = self.clone();
|
||||
for val in res.0.iter_mut() {
|
||||
*val *= scalar.borrow();
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) fn add_vec(&self, vector: &Self) -> Self {
|
||||
debug_assert_eq!(self.len(), vector.len());
|
||||
let mut res = self.clone();
|
||||
for (i, val) in res.0.iter_mut().enumerate() {
|
||||
*val += vector.0[i];
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) fn sub_vec(&self, vector: &Self) -> Self {
|
||||
debug_assert_eq!(self.len(), vector.len());
|
||||
let mut res = self.clone();
|
||||
for (i, val) in res.0.iter_mut().enumerate() {
|
||||
*val -= vector.0[i];
|
||||
}
|
||||
res
|
||||
}
|
||||
*/
|
||||
|
||||
pub(crate) fn mul_vec(&self, vector: &ScalarVector<C::F>) -> Self {
|
||||
debug_assert_eq!(self.len(), vector.len());
|
||||
let mut res = self.clone();
|
||||
for (i, val) in res.0.iter_mut().enumerate() {
|
||||
*val *= vector.0[i];
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn multiexp(&self, vector: &crate::ScalarVector<C::F>) -> C::G {
|
||||
debug_assert_eq!(self.len(), vector.len());
|
||||
let mut res = Vec::with_capacity(self.len());
|
||||
for (point, scalar) in self.0.iter().copied().zip(vector.0.iter().copied()) {
|
||||
res.push((scalar, point));
|
||||
}
|
||||
multiexp(&res)
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) fn multiexp_vartime(&self, vector: &ScalarVector<C::F>) -> C::G {
|
||||
debug_assert_eq!(self.len(), vector.len());
|
||||
let mut res = Vec::with_capacity(self.len());
|
||||
for (point, scalar) in self.0.iter().copied().zip(vector.0.iter().copied()) {
|
||||
res.push((scalar, point));
|
||||
}
|
||||
multiexp_vartime(&res)
|
||||
}
|
||||
|
||||
pub(crate) fn sum(&self) -> C::G {
|
||||
self.0.iter().sum()
|
||||
}
|
||||
*/
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub(crate) fn split(mut self) -> (Self, Self) {
|
||||
assert!(self.len() > 1);
|
||||
let r = self.0.split_off(self.0.len() / 2);
|
||||
debug_assert_eq!(self.len(), r.len());
|
||||
(self, PointVector(r))
|
||||
}
|
||||
}
|
||||
146
crypto/evrf/generalized-bulletproofs/src/scalar_vector.rs
Normal file
146
crypto/evrf/generalized-bulletproofs/src/scalar_vector.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
use core::ops::{Index, IndexMut, Add, Sub, Mul};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use ciphersuite::group::ff::PrimeField;
|
||||
|
||||
/// A scalar vector struct with the functionality necessary for Bulletproofs.
|
||||
///
|
||||
/// The math operations for this panic upon any invalid operation, such as if vectors of different
|
||||
/// lengths are added. The full extent of invalidity is not fully defined. Only `new`, `len`,
|
||||
/// and field access is guaranteed to have a safe, public API.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct ScalarVector<F: PrimeField>(pub(crate) Vec<F>);
|
||||
|
||||
impl<F: PrimeField + Zeroize> Zeroize for ScalarVector<F> {
|
||||
fn zeroize(&mut self) {
|
||||
self.0.zeroize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Index<usize> for ScalarVector<F> {
|
||||
type Output = F;
|
||||
fn index(&self, index: usize) -> &F {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
impl<F: PrimeField> IndexMut<usize> for ScalarVector<F> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut F {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Add<F> for ScalarVector<F> {
|
||||
type Output = ScalarVector<F>;
|
||||
fn add(mut self, scalar: F) -> Self {
|
||||
for s in &mut self.0 {
|
||||
*s += scalar;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<F: PrimeField> Sub<F> for ScalarVector<F> {
|
||||
type Output = ScalarVector<F>;
|
||||
fn sub(mut self, scalar: F) -> Self {
|
||||
for s in &mut self.0 {
|
||||
*s -= scalar;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<F: PrimeField> Mul<F> for ScalarVector<F> {
|
||||
type Output = ScalarVector<F>;
|
||||
fn mul(mut self, scalar: F) -> Self {
|
||||
for s in &mut self.0 {
|
||||
*s *= scalar;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Add<&ScalarVector<F>> for ScalarVector<F> {
|
||||
type Output = ScalarVector<F>;
|
||||
fn add(mut self, other: &ScalarVector<F>) -> Self {
|
||||
assert_eq!(self.len(), other.len());
|
||||
for (s, o) in self.0.iter_mut().zip(other.0.iter()) {
|
||||
*s += o;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<F: PrimeField> Sub<&ScalarVector<F>> for ScalarVector<F> {
|
||||
type Output = ScalarVector<F>;
|
||||
fn sub(mut self, other: &ScalarVector<F>) -> Self {
|
||||
assert_eq!(self.len(), other.len());
|
||||
for (s, o) in self.0.iter_mut().zip(other.0.iter()) {
|
||||
*s -= o;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<F: PrimeField> Mul<&ScalarVector<F>> for ScalarVector<F> {
|
||||
type Output = ScalarVector<F>;
|
||||
fn mul(mut self, other: &ScalarVector<F>) -> Self {
|
||||
assert_eq!(self.len(), other.len());
|
||||
for (s, o) in self.0.iter_mut().zip(other.0.iter()) {
|
||||
*s *= o;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> ScalarVector<F> {
|
||||
/// Create a new scalar vector, initialized with `len` zero scalars.
|
||||
pub fn new(len: usize) -> Self {
|
||||
ScalarVector(vec![F::ZERO; len])
|
||||
}
|
||||
|
||||
pub(crate) fn powers(x: F, len: usize) -> Self {
|
||||
assert!(len != 0);
|
||||
|
||||
let mut res = Vec::with_capacity(len);
|
||||
res.push(F::ONE);
|
||||
res.push(x);
|
||||
for i in 2 .. len {
|
||||
res.push(res[i - 1] * x);
|
||||
}
|
||||
res.truncate(len);
|
||||
ScalarVector(res)
|
||||
}
|
||||
|
||||
/// The length of this scalar vector.
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) fn sum(mut self) -> F {
|
||||
self.0.drain(..).sum()
|
||||
}
|
||||
*/
|
||||
|
||||
pub(crate) fn inner_product<'a, V: Iterator<Item = &'a F>>(&self, vector: V) -> F {
|
||||
let mut count = 0;
|
||||
let mut res = F::ZERO;
|
||||
for (a, b) in self.0.iter().zip(vector) {
|
||||
res += *a * b;
|
||||
count += 1;
|
||||
}
|
||||
debug_assert_eq!(self.len(), count);
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) fn split(mut self) -> (Self, Self) {
|
||||
assert!(self.len() > 1);
|
||||
let r = self.0.split_off(self.0.len() / 2);
|
||||
debug_assert_eq!(self.len(), r.len());
|
||||
(self, ScalarVector(r))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> From<Vec<F>> for ScalarVector<F> {
|
||||
fn from(vec: Vec<F>) -> Self {
|
||||
Self(vec)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
|
||||
|
||||
use crate::{
|
||||
ScalarVector, PedersenCommitment, PedersenVectorCommitment,
|
||||
transcript::*,
|
||||
arithmetic_circuit_proof::{
|
||||
Variable, LinComb, ArithmeticCircuitStatement, ArithmeticCircuitWitness,
|
||||
},
|
||||
tests::generators,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_zero_arithmetic_circuit() {
|
||||
let generators = generators(1);
|
||||
|
||||
let value = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
let gamma = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
let commitment = (generators.g() * value) + (generators.h() * gamma);
|
||||
let V = vec![commitment];
|
||||
|
||||
let aL = ScalarVector::<<Ristretto as Ciphersuite>::F>(vec![<Ristretto as Ciphersuite>::F::ZERO]);
|
||||
let aR = aL.clone();
|
||||
|
||||
let mut transcript = Transcript::new([0; 32]);
|
||||
let commitments = transcript.write_commitments(vec![], V);
|
||||
let statement = ArithmeticCircuitStatement::<Ristretto>::new(
|
||||
generators.reduce(1).unwrap(),
|
||||
vec![],
|
||||
commitments.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let witness = ArithmeticCircuitWitness::<Ristretto>::new(
|
||||
aL,
|
||||
aR,
|
||||
vec![],
|
||||
vec![PedersenCommitment { value, mask: gamma }],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let proof = {
|
||||
statement.clone().prove(&mut OsRng, &mut transcript, witness).unwrap();
|
||||
transcript.complete()
|
||||
};
|
||||
let mut verifier = generators.batch_verifier();
|
||||
|
||||
let mut transcript = VerifierTranscript::new([0; 32], &proof);
|
||||
let verifier_commmitments = transcript.read_commitments(0, 1);
|
||||
assert_eq!(commitments, verifier_commmitments.unwrap());
|
||||
statement.verify(&mut OsRng, &mut verifier, &mut transcript).unwrap();
|
||||
assert!(generators.verify(verifier));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vector_commitment_arithmetic_circuit() {
|
||||
let generators = generators(2);
|
||||
let reduced = generators.reduce(2).unwrap();
|
||||
|
||||
let v1 = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
let v2 = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
let v3 = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
let v4 = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
let gamma = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
let commitment = (reduced.g_bold(0) * v1) +
|
||||
(reduced.g_bold(1) * v2) +
|
||||
(reduced.h_bold(0) * v3) +
|
||||
(reduced.h_bold(1) * v4) +
|
||||
(generators.h() * gamma);
|
||||
let V = vec![];
|
||||
let C = vec![commitment];
|
||||
|
||||
let zero_vec =
|
||||
|| ScalarVector::<<Ristretto as Ciphersuite>::F>(vec![<Ristretto as Ciphersuite>::F::ZERO]);
|
||||
|
||||
let aL = zero_vec();
|
||||
let aR = zero_vec();
|
||||
|
||||
let mut transcript = Transcript::new([0; 32]);
|
||||
let commitments = transcript.write_commitments(C, V);
|
||||
let statement = ArithmeticCircuitStatement::<Ristretto>::new(
|
||||
reduced,
|
||||
vec![LinComb::empty()
|
||||
.term(<Ristretto as Ciphersuite>::F::ONE, Variable::CG { commitment: 0, index: 0 })
|
||||
.term(<Ristretto as Ciphersuite>::F::from(2u64), Variable::CG { commitment: 0, index: 1 })
|
||||
.term(<Ristretto as Ciphersuite>::F::from(3u64), Variable::CH { commitment: 0, index: 0 })
|
||||
.term(<Ristretto as Ciphersuite>::F::from(4u64), Variable::CH { commitment: 0, index: 1 })
|
||||
.constant(-(v1 + (v2 + v2) + (v3 + v3 + v3) + (v4 + v4 + v4 + v4)))],
|
||||
commitments.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let witness = ArithmeticCircuitWitness::<Ristretto>::new(
|
||||
aL,
|
||||
aR,
|
||||
vec![PedersenVectorCommitment {
|
||||
g_values: ScalarVector(vec![v1, v2]),
|
||||
h_values: ScalarVector(vec![v3, v4]),
|
||||
mask: gamma,
|
||||
}],
|
||||
vec![],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let proof = {
|
||||
statement.clone().prove(&mut OsRng, &mut transcript, witness).unwrap();
|
||||
transcript.complete()
|
||||
};
|
||||
let mut verifier = generators.batch_verifier();
|
||||
|
||||
let mut transcript = VerifierTranscript::new([0; 32], &proof);
|
||||
let verifier_commmitments = transcript.read_commitments(1, 0);
|
||||
assert_eq!(commitments, verifier_commmitments.unwrap());
|
||||
statement.verify(&mut OsRng, &mut verifier, &mut transcript).unwrap();
|
||||
assert!(generators.verify(verifier));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_test_arithmetic_circuit() {
|
||||
let generators = generators(32);
|
||||
|
||||
for i in 0 .. 100 {
|
||||
dbg!(i);
|
||||
|
||||
// Create aL, aR, aO
|
||||
let mut aL = ScalarVector(vec![]);
|
||||
let mut aR = ScalarVector(vec![]);
|
||||
while aL.len() < ((OsRng.next_u64() % 8) + 1).try_into().unwrap() {
|
||||
aL.0.push(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||
}
|
||||
while aR.len() < aL.len() {
|
||||
aR.0.push(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||
}
|
||||
let aO = aL.clone() * &aR;
|
||||
|
||||
// Create C
|
||||
let mut C = vec![];
|
||||
while C.len() < (OsRng.next_u64() % 16).try_into().unwrap() {
|
||||
let mut g_values = ScalarVector(vec![]);
|
||||
while g_values.0.len() < ((OsRng.next_u64() % 8) + 1).try_into().unwrap() {
|
||||
g_values.0.push(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||
}
|
||||
let mut h_values = ScalarVector(vec![]);
|
||||
while h_values.0.len() < ((OsRng.next_u64() % 8) + 1).try_into().unwrap() {
|
||||
h_values.0.push(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||
}
|
||||
C.push(PedersenVectorCommitment {
|
||||
g_values,
|
||||
h_values,
|
||||
mask: <Ristretto as Ciphersuite>::F::random(&mut OsRng),
|
||||
});
|
||||
}
|
||||
|
||||
// Create V
|
||||
let mut V = vec![];
|
||||
while V.len() < (OsRng.next_u64() % 4).try_into().unwrap() {
|
||||
V.push(PedersenCommitment {
|
||||
value: <Ristretto as Ciphersuite>::F::random(&mut OsRng),
|
||||
mask: <Ristretto as Ciphersuite>::F::random(&mut OsRng),
|
||||
});
|
||||
}
|
||||
|
||||
// Generate random constraints
|
||||
let mut constraints = vec![];
|
||||
for _ in 0 .. (OsRng.next_u64() % 8).try_into().unwrap() {
|
||||
let mut eval = <Ristretto as Ciphersuite>::F::ZERO;
|
||||
let mut constraint = LinComb::empty();
|
||||
|
||||
for _ in 0 .. (OsRng.next_u64() % 4) {
|
||||
let index = usize::try_from(OsRng.next_u64()).unwrap() % aL.len();
|
||||
let weight = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
constraint = constraint.term(weight, Variable::aL(index));
|
||||
eval += weight * aL[index];
|
||||
}
|
||||
|
||||
for _ in 0 .. (OsRng.next_u64() % 4) {
|
||||
let index = usize::try_from(OsRng.next_u64()).unwrap() % aR.len();
|
||||
let weight = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
constraint = constraint.term(weight, Variable::aR(index));
|
||||
eval += weight * aR[index];
|
||||
}
|
||||
|
||||
for _ in 0 .. (OsRng.next_u64() % 4) {
|
||||
let index = usize::try_from(OsRng.next_u64()).unwrap() % aO.len();
|
||||
let weight = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
constraint = constraint.term(weight, Variable::aO(index));
|
||||
eval += weight * aO[index];
|
||||
}
|
||||
|
||||
for (commitment, C) in C.iter().enumerate() {
|
||||
for _ in 0 .. (OsRng.next_u64() % 4) {
|
||||
let index = usize::try_from(OsRng.next_u64()).unwrap() % C.g_values.len();
|
||||
let weight = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
constraint = constraint.term(weight, Variable::CG { commitment, index });
|
||||
eval += weight * C.g_values[index];
|
||||
}
|
||||
|
||||
for _ in 0 .. (OsRng.next_u64() % 4) {
|
||||
let index = usize::try_from(OsRng.next_u64()).unwrap() % C.h_values.len();
|
||||
let weight = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
constraint = constraint.term(weight, Variable::CH { commitment, index });
|
||||
eval += weight * C.h_values[index];
|
||||
}
|
||||
}
|
||||
|
||||
if !V.is_empty() {
|
||||
for _ in 0 .. (OsRng.next_u64() % 4) {
|
||||
let index = usize::try_from(OsRng.next_u64()).unwrap() % V.len();
|
||||
let weight = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
constraint = constraint.term(weight, Variable::V(index));
|
||||
eval += weight * V[index].value;
|
||||
}
|
||||
}
|
||||
|
||||
constraint = constraint.constant(-eval);
|
||||
|
||||
constraints.push(constraint);
|
||||
}
|
||||
|
||||
let mut transcript = Transcript::new([0; 32]);
|
||||
let commitments = transcript.write_commitments(
|
||||
C.iter()
|
||||
.map(|C| {
|
||||
C.commit(generators.g_bold_slice(), generators.h_bold_slice(), generators.h()).unwrap()
|
||||
})
|
||||
.collect(),
|
||||
V.iter().map(|V| V.commit(generators.g(), generators.h())).collect(),
|
||||
);
|
||||
|
||||
let statement = ArithmeticCircuitStatement::<Ristretto>::new(
|
||||
generators.reduce(16).unwrap(),
|
||||
constraints,
|
||||
commitments.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness = ArithmeticCircuitWitness::<Ristretto>::new(aL, aR, C.clone(), V.clone()).unwrap();
|
||||
|
||||
let proof = {
|
||||
statement.clone().prove(&mut OsRng, &mut transcript, witness).unwrap();
|
||||
transcript.complete()
|
||||
};
|
||||
let mut verifier = generators.batch_verifier();
|
||||
|
||||
let mut transcript = VerifierTranscript::new([0; 32], &proof);
|
||||
let verifier_commmitments = transcript.read_commitments(C.len(), V.len());
|
||||
assert_eq!(commitments, verifier_commmitments.unwrap());
|
||||
statement.verify(&mut OsRng, &mut verifier, &mut transcript).unwrap();
|
||||
assert!(generators.verify(verifier));
|
||||
}
|
||||
}
|
||||
113
crypto/evrf/generalized-bulletproofs/src/tests/inner_product.rs
Normal file
113
crypto/evrf/generalized-bulletproofs/src/tests/inner_product.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
// The inner product relation is P = sum(g_bold * a, h_bold * b, g * (a * b))
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use ciphersuite::{
|
||||
group::{ff::Field, Group},
|
||||
Ciphersuite, Ristretto,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ScalarVector, PointVector,
|
||||
transcript::*,
|
||||
inner_product::{P, IpStatement, IpWitness},
|
||||
tests::generators,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_zero_inner_product() {
|
||||
let P = <Ristretto as Ciphersuite>::G::identity();
|
||||
|
||||
let generators = generators::<Ristretto>(1);
|
||||
let reduced = generators.reduce(1).unwrap();
|
||||
let witness = IpWitness::<Ristretto>::new(
|
||||
ScalarVector::<<Ristretto as Ciphersuite>::F>::new(1),
|
||||
ScalarVector::<<Ristretto as Ciphersuite>::F>::new(1),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let proof = {
|
||||
let mut transcript = Transcript::new([0; 32]);
|
||||
IpStatement::<Ristretto>::new(
|
||||
reduced,
|
||||
ScalarVector(vec![<Ristretto as Ciphersuite>::F::ONE; 1]),
|
||||
<Ristretto as Ciphersuite>::F::ONE,
|
||||
P::Prover(P),
|
||||
)
|
||||
.unwrap()
|
||||
.clone()
|
||||
.prove(&mut transcript, witness)
|
||||
.unwrap();
|
||||
transcript.complete()
|
||||
};
|
||||
|
||||
let mut verifier = generators.batch_verifier();
|
||||
IpStatement::<Ristretto>::new(
|
||||
reduced,
|
||||
ScalarVector(vec![<Ristretto as Ciphersuite>::F::ONE; 1]),
|
||||
<Ristretto as Ciphersuite>::F::ONE,
|
||||
P::Verifier { verifier_weight: <Ristretto as Ciphersuite>::F::ONE },
|
||||
)
|
||||
.unwrap()
|
||||
.verify(&mut verifier, &mut VerifierTranscript::new([0; 32], &proof))
|
||||
.unwrap();
|
||||
assert!(generators.verify(verifier));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inner_product() {
|
||||
// P = sum(g_bold * a, h_bold * b)
|
||||
let generators = generators::<Ristretto>(32);
|
||||
let mut verifier = generators.batch_verifier();
|
||||
for i in [1, 2, 4, 8, 16, 32] {
|
||||
let generators = generators.reduce(i).unwrap();
|
||||
let g = generators.g();
|
||||
assert_eq!(generators.len(), i);
|
||||
let mut g_bold = vec![];
|
||||
let mut h_bold = vec![];
|
||||
for i in 0 .. i {
|
||||
g_bold.push(generators.g_bold(i));
|
||||
h_bold.push(generators.h_bold(i));
|
||||
}
|
||||
let g_bold = PointVector::<Ristretto>(g_bold);
|
||||
let h_bold = PointVector::<Ristretto>(h_bold);
|
||||
|
||||
let mut a = ScalarVector::<<Ristretto as Ciphersuite>::F>::new(i);
|
||||
let mut b = ScalarVector::<<Ristretto as Ciphersuite>::F>::new(i);
|
||||
|
||||
for i in 0 .. i {
|
||||
a[i] = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
b[i] = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
|
||||
}
|
||||
|
||||
let P = g_bold.multiexp(&a) + h_bold.multiexp(&b) + (g * a.inner_product(b.0.iter()));
|
||||
|
||||
let witness = IpWitness::<Ristretto>::new(a, b).unwrap();
|
||||
|
||||
let proof = {
|
||||
let mut transcript = Transcript::new([0; 32]);
|
||||
IpStatement::<Ristretto>::new(
|
||||
generators,
|
||||
ScalarVector(vec![<Ristretto as Ciphersuite>::F::ONE; i]),
|
||||
<Ristretto as Ciphersuite>::F::ONE,
|
||||
P::Prover(P),
|
||||
)
|
||||
.unwrap()
|
||||
.prove(&mut transcript, witness)
|
||||
.unwrap();
|
||||
transcript.complete()
|
||||
};
|
||||
|
||||
verifier.additional.push((<Ristretto as Ciphersuite>::F::ONE, P));
|
||||
IpStatement::<Ristretto>::new(
|
||||
generators,
|
||||
ScalarVector(vec![<Ristretto as Ciphersuite>::F::ONE; i]),
|
||||
<Ristretto as Ciphersuite>::F::ONE,
|
||||
P::Verifier { verifier_weight: <Ristretto as Ciphersuite>::F::ONE },
|
||||
)
|
||||
.unwrap()
|
||||
.verify(&mut verifier, &mut VerifierTranscript::new([0; 32], &proof))
|
||||
.unwrap();
|
||||
}
|
||||
assert!(generators.verify(verifier));
|
||||
}
|
||||
27
crypto/evrf/generalized-bulletproofs/src/tests/mod.rs
Normal file
27
crypto/evrf/generalized-bulletproofs/src/tests/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use rand_core::OsRng;
|
||||
|
||||
use ciphersuite::{group::Group, Ciphersuite};
|
||||
|
||||
use crate::{Generators, padded_pow_of_2};
|
||||
|
||||
#[cfg(test)]
|
||||
mod inner_product;
|
||||
|
||||
#[cfg(test)]
|
||||
mod arithmetic_circuit_proof;
|
||||
|
||||
/// Generate a set of generators for testing purposes.
|
||||
///
|
||||
/// This should not be considered secure.
|
||||
pub fn generators<C: Ciphersuite>(n: usize) -> Generators<C> {
|
||||
assert_eq!(padded_pow_of_2(n), n, "amount of generators wasn't a power of 2");
|
||||
|
||||
let gens = || {
|
||||
let mut res = Vec::with_capacity(n);
|
||||
for _ in 0 .. n {
|
||||
res.push(C::G::random(&mut OsRng));
|
||||
}
|
||||
res
|
||||
};
|
||||
Generators::new(C::G::random(&mut OsRng), C::G::random(&mut OsRng), gens(), gens()).unwrap()
|
||||
}
|
||||
175
crypto/evrf/generalized-bulletproofs/src/transcript.rs
Normal file
175
crypto/evrf/generalized-bulletproofs/src/transcript.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use std::io;
|
||||
|
||||
use blake2::{Digest, Blake2b512};
|
||||
|
||||
use ciphersuite::{
|
||||
group::{ff::PrimeField, GroupEncoding},
|
||||
Ciphersuite,
|
||||
};
|
||||
|
||||
use crate::PointVector;
|
||||
|
||||
const SCALAR: u8 = 0;
|
||||
const POINT: u8 = 1;
|
||||
const CHALLENGE: u8 = 2;
|
||||
|
||||
fn challenge<F: PrimeField>(digest: &mut Blake2b512) -> F {
|
||||
digest.update([CHALLENGE]);
|
||||
let chl = digest.clone().finalize();
|
||||
|
||||
let mut res = F::ZERO;
|
||||
for (i, mut byte) in chl.iter().cloned().enumerate() {
|
||||
for j in 0 .. 8 {
|
||||
let lsb = byte & 1;
|
||||
let mut bit = F::from(u64::from(lsb));
|
||||
for _ in 0 .. ((i * 8) + j) {
|
||||
bit = bit.double();
|
||||
}
|
||||
res += bit;
|
||||
|
||||
byte >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Negligible probability
|
||||
if bool::from(res.is_zero()) {
|
||||
panic!("zero challenge");
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Commitments written to/read from a transcript.
|
||||
// We use a dedicated type for this to coerce the caller into transcripting the commitments as
|
||||
// expected.
|
||||
#[cfg_attr(test, derive(Clone, PartialEq, Debug))]
|
||||
pub struct Commitments<C: Ciphersuite> {
|
||||
pub(crate) C: PointVector<C>,
|
||||
pub(crate) V: PointVector<C>,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Commitments<C> {
|
||||
/// The vector commitments.
|
||||
pub fn C(&self) -> &[C::G] {
|
||||
&self.C.0
|
||||
}
|
||||
/// The non-vector commitments.
|
||||
pub fn V(&self) -> &[C::G] {
|
||||
&self.V.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A transcript for proving proofs.
|
||||
pub struct Transcript {
|
||||
digest: Blake2b512,
|
||||
transcript: Vec<u8>,
|
||||
}
|
||||
|
||||
/*
|
||||
We define our proofs as Vec<u8> and derive our transcripts from the values we deserialize from
|
||||
them. This format assumes the order of the values read, their size, and their quantity are
|
||||
constant to the context.
|
||||
*/
|
||||
impl Transcript {
|
||||
/// Create a new transcript off some context.
|
||||
pub fn new(context: [u8; 32]) -> Self {
|
||||
let mut digest = Blake2b512::new();
|
||||
digest.update(context);
|
||||
Self { digest, transcript: Vec::with_capacity(1024) }
|
||||
}
|
||||
|
||||
pub(crate) fn push_scalar(&mut self, scalar: impl PrimeField) {
|
||||
self.digest.update([SCALAR]);
|
||||
let bytes = scalar.to_repr();
|
||||
self.digest.update(bytes);
|
||||
self.transcript.extend(bytes.as_ref());
|
||||
}
|
||||
|
||||
pub(crate) fn push_point(&mut self, point: impl GroupEncoding) {
|
||||
self.digest.update([POINT]);
|
||||
let bytes = point.to_bytes();
|
||||
self.digest.update(bytes);
|
||||
self.transcript.extend(bytes.as_ref());
|
||||
}
|
||||
|
||||
/// Write the Pedersen (vector) commitments to this transcript.
|
||||
pub fn write_commitments<C: Ciphersuite>(
|
||||
&mut self,
|
||||
C: Vec<C::G>,
|
||||
V: Vec<C::G>,
|
||||
) -> Commitments<C> {
|
||||
for C in &C {
|
||||
self.push_point(*C);
|
||||
}
|
||||
for V in &V {
|
||||
self.push_point(*V);
|
||||
}
|
||||
Commitments { C: PointVector(C), V: PointVector(V) }
|
||||
}
|
||||
|
||||
/// Sample a challenge.
|
||||
pub fn challenge<F: PrimeField>(&mut self) -> F {
|
||||
challenge(&mut self.digest)
|
||||
}
|
||||
|
||||
/// Complete a transcript, yielding the fully serialized proof.
|
||||
pub fn complete(self) -> Vec<u8> {
|
||||
self.transcript
|
||||
}
|
||||
}
|
||||
|
||||
/// A transcript for verifying proofs.
|
||||
pub struct VerifierTranscript<'a> {
|
||||
digest: Blake2b512,
|
||||
transcript: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> VerifierTranscript<'a> {
|
||||
/// Create a new transcript to verify a proof with.
|
||||
pub fn new(context: [u8; 32], proof: &'a [u8]) -> Self {
|
||||
let mut digest = Blake2b512::new();
|
||||
digest.update(context);
|
||||
Self { digest, transcript: proof }
|
||||
}
|
||||
|
||||
pub(crate) fn read_scalar<C: Ciphersuite>(&mut self) -> io::Result<C::F> {
|
||||
let scalar = C::read_F(&mut self.transcript)?;
|
||||
self.digest.update([SCALAR]);
|
||||
let bytes = scalar.to_repr();
|
||||
self.digest.update(bytes);
|
||||
Ok(scalar)
|
||||
}
|
||||
|
||||
pub(crate) fn read_point<C: Ciphersuite>(&mut self) -> io::Result<C::G> {
|
||||
let point = C::read_G(&mut self.transcript)?;
|
||||
self.digest.update([POINT]);
|
||||
let bytes = point.to_bytes();
|
||||
self.digest.update(bytes);
|
||||
Ok(point)
|
||||
}
|
||||
|
||||
/// Read the Pedersen (Vector) Commitments from the transcript.
|
||||
///
|
||||
/// The lengths of the vectors are not transcripted.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn read_commitments<C: Ciphersuite>(
|
||||
&mut self,
|
||||
C: usize,
|
||||
V: usize,
|
||||
) -> io::Result<Commitments<C>> {
|
||||
let mut C_vec = Vec::with_capacity(C);
|
||||
for _ in 0 .. C {
|
||||
C_vec.push(self.read_point::<C>()?);
|
||||
}
|
||||
let mut V_vec = Vec::with_capacity(V);
|
||||
for _ in 0 .. V {
|
||||
V_vec.push(self.read_point::<C>()?);
|
||||
}
|
||||
Ok(Commitments { C: PointVector(C_vec), V: PointVector(V_vec) })
|
||||
}
|
||||
|
||||
/// Sample a challenge.
|
||||
pub fn challenge<F: PrimeField>(&mut self) -> F {
|
||||
challenge(&mut self.digest)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user