Upstream GBP, divisor, circuit abstraction, and EC gadgets from FCMP++

This commit is contained in:
Luke Parker
2024-07-21 21:48:54 -04:00
parent d5205ce231
commit dcc26ecf33
33 changed files with 4663 additions and 1 deletions

View File

@@ -0,0 +1,20 @@
[package]
name = "generalized-bulletproofs-circuit-abstraction"
version = "0.1.0"
description = "An abstraction for arithmetic circuits over Generalized Bulletproofs"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/circuit-abstraction"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["bulletproofs", "circuit"]
edition = "2021"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
generalized-bulletproofs = { path = "../generalized-bulletproofs" }

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 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.

View File

@@ -0,0 +1,3 @@
# Generalized Bulletproofs Circuit Abstraction
A circuit abstraction around `generalized-bulletproofs`.

View File

@@ -0,0 +1,39 @@
use ciphersuite::{group::ff::Field, Ciphersuite};
use crate::*;
impl<C: Ciphersuite> Circuit<C> {
/// Constrain two linear combinations to be equal.
pub fn equality(&mut self, a: LinComb<C::F>, b: &LinComb<C::F>) {
self.constrain_equal_to_zero(a - b);
}
/// Calculate (and constrain) the inverse of a value.
///
/// A linear combination may optionally be passed as a constraint for the value being inverted.
/// A reference to the inverted value and its inverse is returned.
///
/// May panic if any linear combinations reference non-existent terms, the witness isn't provided
/// when proving/is provided when verifying, or if the witness is 0 (and accordingly doesn't have
/// an inverse).
pub fn inverse(
&mut self,
lincomb: Option<LinComb<C::F>>,
witness: Option<C::F>,
) -> (Variable, Variable) {
let (l, r, o) = self.mul(lincomb, None, witness.map(|f| (f, f.invert().unwrap())));
// The output of a value multiplied by its inverse is 1
// Constrain `1 o - 1 = 0`
self.constrain_equal_to_zero(LinComb::from(o).constant(-C::F::ONE));
(l, r)
}
/// Constrain two linear combinations as inequal.
///
/// May panic if any linear combinations reference non-existent terms.
pub fn inequality(&mut self, a: LinComb<C::F>, b: &LinComb<C::F>, witness: Option<(C::F, C::F)>) {
let l_constraint = a - b;
// The existence of a multiplicative inverse means a-b != 0, which means a != b
self.inverse(Some(l_constraint), witness.map(|(a, b)| a - b));
}
}

View File

@@ -0,0 +1,192 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![allow(non_snake_case)]
use zeroize::{Zeroize, ZeroizeOnDrop};
use ciphersuite::{
group::ff::{Field, PrimeField},
Ciphersuite,
};
use generalized_bulletproofs::{
ScalarVector, PedersenCommitment, PedersenVectorCommitment, ProofGenerators,
transcript::{Transcript as ProverTranscript, VerifierTranscript, Commitments},
arithmetic_circuit_proof::{AcError, ArithmeticCircuitStatement, ArithmeticCircuitWitness},
};
pub use generalized_bulletproofs::arithmetic_circuit_proof::{Variable, LinComb};
mod gadgets;
/// A trait for the transcript, whether proving for verifying, as necessary for sampling
/// challenges.
pub trait Transcript {
/// Sample a challenge from the transacript.
///
/// It is the caller's responsibility to have properly transcripted all variables prior to
/// sampling this challenge.
fn challenge<F: PrimeField>(&mut self) -> F;
}
impl Transcript for ProverTranscript {
fn challenge<F: PrimeField>(&mut self) -> F {
self.challenge()
}
}
impl Transcript for VerifierTranscript<'_> {
fn challenge<F: PrimeField>(&mut self) -> F {
self.challenge()
}
}
/// The witness for the satisfaction of this circuit.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
struct ProverData<C: Ciphersuite> {
aL: Vec<C::F>,
aR: Vec<C::F>,
C: Vec<PedersenVectorCommitment<C>>,
V: Vec<PedersenCommitment<C>>,
}
/// A struct representing a circuit.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Circuit<C: Ciphersuite> {
muls: usize,
// A series of linear combinations which must evaluate to 0.
constraints: Vec<LinComb<C::F>>,
prover: Option<ProverData<C>>,
}
impl<C: Ciphersuite> Circuit<C> {
/// Returns the amount of multiplications used by this circuit.
pub fn muls(&self) -> usize {
self.muls
}
/// Create an instance to prove satisfaction of a circuit with.
// TODO: Take the transcript here
#[allow(clippy::type_complexity)]
pub fn prove(
vector_commitments: Vec<PedersenVectorCommitment<C>>,
commitments: Vec<PedersenCommitment<C>>,
) -> Self {
Self {
muls: 0,
constraints: vec![],
prover: Some(ProverData { aL: vec![], aR: vec![], C: vector_commitments, V: commitments }),
}
}
/// Create an instance to verify a proof with.
// TODO: Take the transcript here
pub fn verify() -> Self {
Self { muls: 0, constraints: vec![], prover: None }
}
/// Evaluate a linear combination.
///
/// Yields WL aL + WR aR + WO aO + WCG CG + WCH CH + WV V + c.
///
/// May panic if the linear combination references non-existent terms.
///
/// Returns None if not a prover.
pub fn eval(&self, lincomb: &LinComb<C::F>) -> Option<C::F> {
self.prover.as_ref().map(|prover| {
let mut res = lincomb.c();
for (index, weight) in lincomb.WL() {
res += prover.aL[*index] * weight;
}
for (index, weight) in lincomb.WR() {
res += prover.aR[*index] * weight;
}
for (index, weight) in lincomb.WO() {
res += prover.aL[*index] * prover.aR[*index] * weight;
}
for (WCG, C) in lincomb.WCG().iter().zip(&prover.C) {
for (j, weight) in WCG {
res += C.g_values[*j] * weight;
}
}
for (WCH, C) in lincomb.WCH().iter().zip(&prover.C) {
for (j, weight) in WCH {
res += C.h_values[*j] * weight;
}
}
for (index, weight) in lincomb.WV() {
res += prover.V[*index].value * weight;
}
res
})
}
/// Multiply two values, optionally constrained, returning the constrainable left/right/out
/// terms.
///
/// May panic if any linear combinations reference non-existent terms or if the witness isn't
/// provided when proving/is provided when verifying.
pub fn mul(
&mut self,
a: Option<LinComb<C::F>>,
b: Option<LinComb<C::F>>,
witness: Option<(C::F, C::F)>,
) -> (Variable, Variable, Variable) {
let l = Variable::aL(self.muls);
let r = Variable::aR(self.muls);
let o = Variable::aO(self.muls);
self.muls += 1;
debug_assert_eq!(self.prover.is_some(), witness.is_some());
if let Some(witness) = witness {
let prover = self.prover.as_mut().unwrap();
prover.aL.push(witness.0);
prover.aR.push(witness.1);
}
if let Some(a) = a {
self.constrain_equal_to_zero(a.term(-C::F::ONE, l));
}
if let Some(b) = b {
self.constrain_equal_to_zero(b.term(-C::F::ONE, r));
}
(l, r, o)
}
/// Constrain a linear combination to be equal to 0.
///
/// May panic if the linear combination references non-existent terms.
pub fn constrain_equal_to_zero(&mut self, lincomb: LinComb<C::F>) {
self.constraints.push(lincomb);
}
/// Obtain the statement for this circuit.
///
/// If configured as the prover, the witness to use is also returned.
#[allow(clippy::type_complexity)]
pub fn statement(
self,
generators: ProofGenerators<'_, C>,
commitments: Commitments<C>,
) -> Result<(ArithmeticCircuitStatement<'_, C>, Option<ArithmeticCircuitWitness<C>>), AcError> {
let statement = ArithmeticCircuitStatement::new(generators, self.constraints, commitments)?;
let witness = self
.prover
.map(|mut prover| {
// We can't deconstruct the witness as it implements Drop (per ZeroizeOnDrop)
// Accordingly, we take the values within it and move forward with those
let mut aL = vec![];
std::mem::swap(&mut prover.aL, &mut aL);
let mut aR = vec![];
std::mem::swap(&mut prover.aR, &mut aR);
let mut C = vec![];
std::mem::swap(&mut prover.C, &mut C);
let mut V = vec![];
std::mem::swap(&mut prover.V, &mut V);
ArithmeticCircuitWitness::new(ScalarVector::from(aL), ScalarVector::from(aR), C, V)
})
.transpose()?;
Ok((statement, witness))
}
}