feat(backend): Use Schnorr signatures for signed PODs (#236)
* Implement non-native extension field arithmetic * Schnorr signature verification (#221) * Use Schnorr signatures for signed PODs * add custom gates (#237) * Clippy * Formatting * Apply suggestions from code review Co-authored-by: Eduard S. <eduardsanou@posteo.net> * Fix typo * Fix tests * Point -> PublicKey * Remove default nnf_div implementation for clarity * Code review & edits for clarity * Remove suspicious mutation * Simplify computation * Fix division * Fix * Update src/backends/plonky2/primitives/ec/curve.rs Co-authored-by: Eduard S. <eduardsanou@posteo.net> * Update src/backends/plonky2/primitives/ec/curve.rs Co-authored-by: Eduard S. <eduardsanou@posteo.net> * Fixes * Add public key to signed POD struct * Style * Elaborate on in-circuit field->biguint conversion * Add missing gates * Comments * Add bits to biguint struct * Comments * Comment --------- Co-authored-by: Daniel Gulotta <dgulotta@alum.mit.edu> Co-authored-by: Eduard S. <eduardsanou@posteo.net>
This commit is contained in:
parent
541c264586
commit
c66506c048
22 changed files with 2995 additions and 456 deletions
402
src/backends/plonky2/primitives/ec/field.rs
Normal file
402
src/backends/plonky2/primitives/ec/field.rs
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use num::BigUint;
|
||||
use plonky2::{
|
||||
field::{
|
||||
extension::{Extendable, FieldExtension, OEF},
|
||||
types::Field,
|
||||
},
|
||||
hash::hash_types::RichField,
|
||||
iop::{
|
||||
generator::{GeneratedValues, SimpleGenerator},
|
||||
target::{BoolTarget, Target},
|
||||
witness::{PartitionWitness, Witness, WitnessWrite},
|
||||
},
|
||||
plonk::{circuit_builder::CircuitBuilder, circuit_data::CommonCircuitData},
|
||||
util::serialization::{Buffer, IoError, Read, Write},
|
||||
};
|
||||
|
||||
//use super::gates::field::NNFMulGate;
|
||||
use crate::{
|
||||
backends::plonky2::{
|
||||
basetypes::D,
|
||||
primitives::ec::gates::{field::NNFMulSimple, generic::SimpleGate},
|
||||
},
|
||||
middleware::F,
|
||||
};
|
||||
|
||||
/// Trait for incorporating non-native field (NNF) arithmetic into a
|
||||
/// circuit. For our purposes, this is a field extension generated by
|
||||
/// a single element.
|
||||
pub trait CircuitBuilderNNF<
|
||||
F: RichField + Extendable<D>,
|
||||
const D: usize,
|
||||
NNF: Field,
|
||||
NNFTarget: Clone,
|
||||
>
|
||||
{
|
||||
// Target 'adder'
|
||||
fn add_virtual_nnf_target(&mut self) -> NNFTarget;
|
||||
// Constant introducers.
|
||||
fn nnf_constant(&mut self, x: &NNF) -> NNFTarget;
|
||||
fn nnf_zero(&mut self) -> NNFTarget {
|
||||
self.nnf_constant(&NNF::ZERO)
|
||||
}
|
||||
fn nnf_one(&mut self) -> NNFTarget {
|
||||
self.nnf_constant(&NNF::ONE)
|
||||
}
|
||||
|
||||
// Field ops
|
||||
fn nnf_add(&mut self, x: &NNFTarget, y: &NNFTarget) -> NNFTarget;
|
||||
fn nnf_sub(&mut self, x: &NNFTarget, y: &NNFTarget) -> NNFTarget;
|
||||
fn nnf_mul(&mut self, x: &NNFTarget, y: &NNFTarget) -> NNFTarget;
|
||||
fn nnf_div(&mut self, x: &NNFTarget, y: &NNFTarget) -> NNFTarget;
|
||||
fn nnf_inverse(&mut self, x: &NNFTarget) -> NNFTarget {
|
||||
let one = self.nnf_one();
|
||||
self.nnf_div(&one, x)
|
||||
}
|
||||
|
||||
/// Multiplies an extension field element by the generator of the
|
||||
/// extension field.
|
||||
fn nnf_mul_generator(&mut self, x: &NNFTarget) -> NNFTarget;
|
||||
|
||||
/// Multiplies an extension field element by a base field element.
|
||||
fn nnf_mul_scalar(&mut self, x: Target, y: &NNFTarget) -> NNFTarget;
|
||||
|
||||
/// Multiplies an extension field element by a base field element
|
||||
/// times the generator of the extension field to a given power.
|
||||
fn nnf_add_scalar_times_generator_power(
|
||||
&mut self,
|
||||
x: Target,
|
||||
gen_power: usize,
|
||||
y: &NNFTarget,
|
||||
) -> NNFTarget;
|
||||
fn nnf_if(&mut self, b: BoolTarget, x_true: &NNFTarget, x_false: &NNFTarget) -> NNFTarget;
|
||||
|
||||
/// Computes an extension field element to a given (biguint)
|
||||
/// power.
|
||||
fn nnf_exp_biguint(&mut self, base: &NNFTarget, exponent: &BigUint) -> NNFTarget;
|
||||
|
||||
// Equality check and connection
|
||||
fn nnf_eq(&mut self, x: &NNFTarget, y: &NNFTarget) -> BoolTarget;
|
||||
fn nnf_connect(&mut self, x: &NNFTarget, y: &NNFTarget);
|
||||
}
|
||||
|
||||
/// Target type modelled on OEF.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OEFTarget<const DEG: usize, NNF: OEF<DEG>> {
|
||||
pub components: [Target; DEG],
|
||||
_phantom_data: PhantomData<NNF>,
|
||||
}
|
||||
|
||||
impl<const DEG: usize, NNF: OEF<DEG>> OEFTarget<DEG, NNF> {
|
||||
pub fn new(components: [Target; DEG]) -> Self {
|
||||
Self {
|
||||
components,
|
||||
_phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const DEG: usize, NNF: OEF<DEG>> Default for OEFTarget<DEG, NNF> {
|
||||
fn default() -> Self {
|
||||
Self::new([Target::default(); DEG])
|
||||
}
|
||||
}
|
||||
|
||||
/// Quotient generator for OEF targets. Allows us to automagically
|
||||
/// generate quotients as witnesses.
|
||||
#[derive(Debug, Default)]
|
||||
struct QuotientGeneratorOEF<const DEG: usize, NNF: OEF<DEG>> {
|
||||
numerator: OEFTarget<DEG, NNF>,
|
||||
denominator: OEFTarget<DEG, NNF>,
|
||||
quotient: OEFTarget<DEG, NNF>,
|
||||
}
|
||||
|
||||
impl<
|
||||
const DEG: usize,
|
||||
NNF: OEF<DEG> + FieldExtension<DEG, BaseField = F>,
|
||||
F: RichField + Extendable<D>,
|
||||
const D: usize,
|
||||
> SimpleGenerator<F, D> for QuotientGeneratorOEF<DEG, NNF>
|
||||
{
|
||||
fn id(&self) -> String {
|
||||
"QuotientGeneratorOEF".to_string()
|
||||
}
|
||||
|
||||
fn dependencies(&self) -> Vec<Target> {
|
||||
self.numerator
|
||||
.components
|
||||
.iter()
|
||||
.chain(self.denominator.components.iter())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn run_once(
|
||||
&self,
|
||||
witness: &PartitionWitness<F>,
|
||||
out_buffer: &mut GeneratedValues<F>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// Dereference numerator & denominator targets to vectors
|
||||
// and construct field elements.
|
||||
let num_components = self
|
||||
.numerator
|
||||
.components
|
||||
.iter()
|
||||
.map(|t| witness.get_target(*t))
|
||||
.collect::<Vec<_>>();
|
||||
let den_components = self
|
||||
.denominator
|
||||
.components
|
||||
.iter()
|
||||
.map(|t| witness.get_target(*t))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let num = NNF::from_basefield_array(std::array::from_fn(|i| num_components[i]));
|
||||
let den = NNF::from_basefield_array(std::array::from_fn(|i| den_components[i]));
|
||||
|
||||
let quotient = num / den;
|
||||
|
||||
out_buffer.set_target_arr(&self.quotient.components, "ient.to_basefield_array())
|
||||
}
|
||||
|
||||
fn serialize(
|
||||
&self,
|
||||
dst: &mut Vec<u8>,
|
||||
_common_data: &CommonCircuitData<F, D>,
|
||||
) -> Result<(), IoError> {
|
||||
dst.write_target_array(&self.numerator.components)?;
|
||||
dst.write_target_array(&self.denominator.components)?;
|
||||
dst.write_target_array(&self.quotient.components)
|
||||
}
|
||||
|
||||
fn deserialize(
|
||||
src: &mut Buffer,
|
||||
_common_data: &CommonCircuitData<F, D>,
|
||||
) -> Result<Self, IoError> {
|
||||
let numerator = OEFTarget::new(src.read_target_array()?);
|
||||
let denominator = OEFTarget::new(src.read_target_array()?);
|
||||
let quotient = OEFTarget::new(src.read_target_array()?);
|
||||
Ok(Self {
|
||||
numerator,
|
||||
denominator,
|
||||
quotient,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<const DEG: usize, NNF: OEF<DEG> + FieldExtension<DEG, BaseField = F>>
|
||||
CircuitBuilderNNF<F, D, NNF, OEFTarget<DEG, NNF>> for CircuitBuilder<F, D>
|
||||
{
|
||||
fn add_virtual_nnf_target(&mut self) -> OEFTarget<DEG, NNF> {
|
||||
OEFTarget::new(self.add_virtual_target_arr())
|
||||
}
|
||||
fn nnf_constant(&mut self, x: &NNF) -> OEFTarget<DEG, NNF> {
|
||||
let targets = x
|
||||
.to_basefield_array()
|
||||
.iter()
|
||||
.map(|c| self.constant(*c))
|
||||
.collect::<Vec<_>>();
|
||||
OEFTarget::new(std::array::from_fn(|i| targets[i]))
|
||||
}
|
||||
fn nnf_add(&mut self, x: &OEFTarget<DEG, NNF>, y: &OEFTarget<DEG, NNF>) -> OEFTarget<DEG, NNF> {
|
||||
let sum_targets = std::iter::zip(&x.components, &y.components)
|
||||
.map(|(a, b)| self.add(*a, *b))
|
||||
.collect::<Vec<_>>();
|
||||
OEFTarget::new(std::array::from_fn(|i| sum_targets[i]))
|
||||
}
|
||||
fn nnf_sub(&mut self, x: &OEFTarget<DEG, NNF>, y: &OEFTarget<DEG, NNF>) -> OEFTarget<DEG, NNF> {
|
||||
let sub_targets = std::iter::zip(&x.components, &y.components)
|
||||
.map(|(a, b)| self.sub(*a, *b))
|
||||
.collect::<Vec<_>>();
|
||||
OEFTarget::new(std::array::from_fn(|i| sub_targets[i]))
|
||||
}
|
||||
fn nnf_mul(&mut self, x: &OEFTarget<DEG, NNF>, y: &OEFTarget<DEG, NNF>) -> OEFTarget<DEG, NNF> {
|
||||
let mut inputs = Vec::with_capacity(10);
|
||||
inputs.extend_from_slice(&x.components);
|
||||
inputs.extend_from_slice(&y.components);
|
||||
let outputs = NNFMulSimple::<DEG, NNF>::apply(self, &inputs);
|
||||
OEFTarget::new(outputs.try_into().unwrap())
|
||||
}
|
||||
fn nnf_div(&mut self, x: &OEFTarget<DEG, NNF>, y: &OEFTarget<DEG, NNF>) -> OEFTarget<DEG, NNF> {
|
||||
let one = self.nnf_one();
|
||||
// Determine denominator inverse witness.
|
||||
let y_inv = self.add_virtual_nnf_target();
|
||||
self.add_simple_generator(QuotientGeneratorOEF {
|
||||
numerator: one.clone(),
|
||||
denominator: y.clone(),
|
||||
quotient: y_inv.clone(),
|
||||
});
|
||||
|
||||
// Add constraints and generate quotient.
|
||||
let maybe_one = self.nnf_mul(&y_inv, y);
|
||||
self.nnf_connect(&one, &maybe_one);
|
||||
self.nnf_mul(x, &y_inv)
|
||||
}
|
||||
fn nnf_mul_generator(&mut self, x: &OEFTarget<DEG, NNF>) -> OEFTarget<DEG, NNF> {
|
||||
OEFTarget::new(std::array::from_fn(|i| {
|
||||
if i == 0 {
|
||||
self.mul_const(NNF::W, x.components[DEG - 1])
|
||||
} else {
|
||||
x.components[i - 1]
|
||||
}
|
||||
}))
|
||||
}
|
||||
fn nnf_mul_scalar(&mut self, x: Target, y: &OEFTarget<DEG, NNF>) -> OEFTarget<DEG, NNF> {
|
||||
OEFTarget::new(std::array::from_fn(|i| self.mul(x, y.components[i])))
|
||||
}
|
||||
fn nnf_add_scalar_times_generator_power(
|
||||
&mut self,
|
||||
x: Target,
|
||||
gen_power: usize,
|
||||
y: &OEFTarget<DEG, NNF>,
|
||||
) -> OEFTarget<DEG, NNF> {
|
||||
OEFTarget::new(std::array::from_fn(|i| {
|
||||
if i == gen_power {
|
||||
self.add(x, y.components[i])
|
||||
} else {
|
||||
y.components[i]
|
||||
}
|
||||
}))
|
||||
}
|
||||
fn nnf_if(
|
||||
&mut self,
|
||||
b: BoolTarget,
|
||||
x_true: &OEFTarget<DEG, NNF>,
|
||||
x_false: &OEFTarget<DEG, NNF>,
|
||||
) -> OEFTarget<DEG, NNF> {
|
||||
OEFTarget::new(std::array::from_fn(|i| {
|
||||
self._if(b, x_true.components[i], x_false.components[i])
|
||||
}))
|
||||
}
|
||||
fn nnf_exp_biguint(
|
||||
&mut self,
|
||||
base: &OEFTarget<DEG, NNF>,
|
||||
exponent: &BigUint,
|
||||
) -> OEFTarget<DEG, NNF> {
|
||||
let mut ans = self.nnf_one();
|
||||
for i in (0..exponent.bits()).rev() {
|
||||
ans = self.nnf_mul(&ans, &ans);
|
||||
if exponent.bit(i) {
|
||||
ans = self.nnf_mul(&ans, base);
|
||||
}
|
||||
}
|
||||
ans
|
||||
}
|
||||
fn nnf_eq(&mut self, x: &OEFTarget<DEG, NNF>, y: &OEFTarget<DEG, NNF>) -> BoolTarget {
|
||||
let eq_checks = std::iter::zip(&x.components, &y.components)
|
||||
.map(|(a, b)| self.is_equal(*a, *b))
|
||||
.collect::<Vec<_>>();
|
||||
eq_checks
|
||||
.into_iter()
|
||||
.reduce(|check, c| self.and(check, c))
|
||||
.expect("Missing equality checks")
|
||||
}
|
||||
fn nnf_connect(&mut self, x: &OEFTarget<DEG, NNF>, y: &OEFTarget<DEG, NNF>) {
|
||||
std::iter::zip(&x.components, &y.components).for_each(|(a, b)| self.connect(*a, *b))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_nnf_target<const DEG: usize, NNF: OEF<DEG>>(
|
||||
witness: &impl Witness<NNF::BaseField>,
|
||||
tgt: &OEFTarget<DEG, NNF>,
|
||||
) -> NNF {
|
||||
let values = tgt.components.map(|x| witness.get_target(x));
|
||||
NNF::from_basefield_array(values)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use plonky2::{
|
||||
field::{
|
||||
extension::quintic::QuinticExtension,
|
||||
goldilocks_field::GoldilocksField,
|
||||
types::{Field, Sample},
|
||||
},
|
||||
iop::witness::{PartialWitness, WitnessWrite},
|
||||
plonk::{
|
||||
circuit_builder::CircuitBuilder, circuit_data::CircuitConfig,
|
||||
config::PoseidonGoldilocksConfig,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{CircuitBuilderNNF, OEFTarget};
|
||||
|
||||
#[test]
|
||||
fn quintic_arithmetic_check() -> Result<(), anyhow::Error> {
|
||||
type QuinticGoldilocks = QuinticExtension<GoldilocksField>;
|
||||
|
||||
// Circuit declaration
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<GoldilocksField, 2>::new(config);
|
||||
|
||||
let zero = builder.nnf_zero();
|
||||
let one = builder.nnf_one();
|
||||
|
||||
// Let c = a * b.
|
||||
let a_target: OEFTarget<5, QuinticGoldilocks> = builder.add_virtual_nnf_target();
|
||||
let b_target: OEFTarget<5, QuinticGoldilocks> = builder.add_virtual_nnf_target();
|
||||
let c_target = builder.nnf_mul(&a_target, &b_target);
|
||||
|
||||
// Pick some values.
|
||||
let a_value = QuinticExtension(std::array::from_fn(|_| GoldilocksField::rand()));
|
||||
let b_value = {
|
||||
let rand_value = QuinticExtension(std::array::from_fn(|_| GoldilocksField::rand()));
|
||||
if rand_value == QuinticGoldilocks::ZERO {
|
||||
QuinticGoldilocks::ONE
|
||||
} else {
|
||||
rand_value
|
||||
}
|
||||
};
|
||||
let c_value = a_value * b_value;
|
||||
|
||||
// How about d = a/b?
|
||||
let d_target = builder.nnf_div(&a_target, &b_target);
|
||||
let d_value = a_value / b_value;
|
||||
|
||||
// Also e = a - b.
|
||||
let e_target = builder.nnf_sub(&a_target, &b_target);
|
||||
let e_value = a_value - b_value;
|
||||
|
||||
// a +- 0 == a, a * 1 == a, etc.
|
||||
let a_plus_zero = builder.nnf_add(&a_target, &zero);
|
||||
let a_minus_zero = builder.nnf_sub(&a_target, &zero);
|
||||
let a_times_one = builder.nnf_mul(&a_target, &one);
|
||||
let a_div_one = builder.nnf_div(&a_target, &one);
|
||||
|
||||
builder.nnf_connect(&a_target, &a_plus_zero);
|
||||
builder.nnf_connect(&a_target, &a_minus_zero);
|
||||
builder.nnf_connect(&a_target, &a_times_one);
|
||||
builder.nnf_connect(&a_target, &a_div_one);
|
||||
|
||||
// a == a, a != a + 1
|
||||
let a_plus_one = builder.nnf_add(&a_target, &one);
|
||||
let a_eq_a = builder.nnf_eq(&a_target, &a_target);
|
||||
let a_eq_a_plus_one = builder.nnf_eq(&a_target, &a_plus_one);
|
||||
|
||||
builder.assert_one(a_eq_a.target);
|
||||
builder.assert_zero(a_eq_a_plus_one.target);
|
||||
|
||||
// b * (1/b) == 1
|
||||
let one_on_b = builder.nnf_inverse(&b_target);
|
||||
let b_times_one_on_b = builder.nnf_mul(&b_target, &one_on_b);
|
||||
|
||||
builder.nnf_connect(&one, &b_times_one_on_b);
|
||||
|
||||
// Prove
|
||||
let mut pw = PartialWitness::<GoldilocksField>::new();
|
||||
|
||||
pw.set_target_arr(&a_target.components, &a_value.0)?;
|
||||
pw.set_target_arr(&b_target.components, &b_value.0)?;
|
||||
pw.set_target_arr(&c_target.components, &c_value.0)?;
|
||||
pw.set_target_arr(&d_target.components, &d_value.0)?;
|
||||
pw.set_target_arr(&e_target.components, &e_value.0)?;
|
||||
|
||||
let data = builder.build::<PoseidonGoldilocksConfig>();
|
||||
let proof = data.prove(pw)?;
|
||||
data.verify(proof)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue