use bellperson::{ gadgets::{boolean::AllocatedBit, num::AllocatedNum}, ConstraintSystem, SynthesisError, }; use ff::{PrimeField, PrimeFieldBits}; use nova_snark::{gadgets::ecc::AllocatedPoint, traits::circuit::StepCircuit}; use subtle::Choice; // An affine point coordinate that is on the curve. #[derive(Clone, Copy, Debug)] pub struct Coordinate where F: PrimeField, { pub x: F, pub y: F, pub is_infinity: bool, } impl Coordinate where F: PrimeField, { // New affine point coordiante on the curve so is_infinity = false. pub fn new(x: F, y: F) -> Self { Self { x, y, is_infinity: false, } } } // An ECDSA signature #[derive(Clone, Debug)] pub struct EcdsaSignature where Fb: PrimeField, Fs: PrimeField + PrimeFieldBits, { pk: Coordinate, // public key r: Coordinate, // (r, s) is the ECDSA signature s: Fs, c: Fs, // hash of the message g: Coordinate, // generator of the group; could be omitted if Nova's traits allow accessing the generator } impl EcdsaSignature where Fb: PrimeField, Fs: PrimeField + PrimeFieldBits, { pub fn new(pk: Coordinate, r: Coordinate, s: Fs, c: Fs, g: Coordinate) -> Self { Self { pk, r, s, c, g } } } // An ECDSA signature proof that we will use on the primary curve #[derive(Clone, Debug)] pub struct EcdsaCircuit where F: PrimeField, { pub r: Coordinate, pub g: Coordinate, pub pk: Coordinate, pub c: F, pub s: F, pub c_bits: Vec, pub s_bits: Vec, } impl EcdsaCircuit where F: PrimeField, { // Creates a new [`EcdsaCircuit`]. The base and scalar field elements from the curve // field used by the signature are converted to scalar field elements from the cyclic curve // field used by the circuit. pub fn new(num_steps: usize, signatures: &[EcdsaSignature]) -> (Vec, Vec) where Fb: PrimeField, Fs: PrimeField + PrimeFieldBits, { let mut z0 = Vec::new(); let mut circuits = Vec::new(); for (i, signature) in signatures.iter().enumerate().take(num_steps) { let r = Coordinate::new( F::from_repr(signature.r.x.to_repr()).unwrap(), F::from_repr(signature.r.y.to_repr()).unwrap(), ); let g = Coordinate::new( F::from_repr(signature.g.x.to_repr()).unwrap(), F::from_repr(signature.g.y.to_repr()).unwrap(), ); let pk = Coordinate::new( F::from_repr(signature.pk.x.to_repr()).unwrap(), F::from_repr(signature.pk.y.to_repr()).unwrap(), ); let c_bits = Self::to_le_bits(&signature.c); let s_bits = Self::to_le_bits(&signature.s); let c = F::from_repr(signature.c.to_repr()).unwrap(); let s = F::from_repr(signature.s.to_repr()).unwrap(); let circuit = EcdsaCircuit { r, g, pk, c, s, c_bits, s_bits, }; circuits.push(circuit); if i == 0 { z0 = vec![r.x, r.y, g.x, g.y, pk.x, pk.y, c, s]; } } (z0, circuits) } // Converts the scalar field element from the curve used by the signature to a bit represenation // for later use in scalar multiplication using the cyclic curve used by the circuit. fn to_le_bits(fs: &Fs) -> Vec where Fs: PrimeField + PrimeFieldBits, { let bits = fs .to_repr() .iter() .flat_map(|byte| (0..8).map(move |i| Choice::from((byte >> i) & 1u8))) .collect::>(); bits } // Synthesize a bit representation into circuit gadgets. fn synthesize_bits>( cs: &mut CS, bits: &[Choice], ) -> Result, SynthesisError> { let alloc_bits: Vec = bits .iter() .enumerate() .map(|(i, bit)| { AllocatedBit::alloc( cs.namespace(|| format!("bit {}", i)), Some(bit.unwrap_u8() == 1u8), ) }) .collect::, SynthesisError>>() .unwrap(); Ok(alloc_bits) } } impl StepCircuit for EcdsaCircuit where F: PrimeField + PrimeFieldBits, { fn arity(&self) -> usize { 8 } // Prove knowledge of the sk used to generate the Ecdsa signature (R,s) // with public key PK and message commitment c. // [s]G == R + [c]PK fn synthesize>( &self, cs: &mut CS, _z: &[AllocatedNum], ) -> Result>, SynthesisError> { let g = AllocatedPoint::alloc( cs.namespace(|| "G"), Some((self.g.x, self.g.y, self.g.is_infinity)), )?; let s_bits = Self::synthesize_bits(&mut cs.namespace(|| "s_bits"), &self.s_bits)?; let sg = g.scalar_mul(cs.namespace(|| "[s]G"), s_bits)?; let r = AllocatedPoint::alloc( cs.namespace(|| "R"), Some((self.r.x, self.r.y, self.r.is_infinity)), )?; let c_bits = Self::synthesize_bits(&mut cs.namespace(|| "c_bits"), &self.c_bits)?; let pk = AllocatedPoint::alloc( cs.namespace(|| "PK"), Some((self.pk.x, self.pk.y, self.pk.is_infinity)), )?; let cpk = pk.scalar_mul(&mut cs.namespace(|| "[c]PK"), c_bits)?; let rcpk = cpk.add(&mut cs.namespace(|| "R + [c]PK"), &r)?; let (rcpk_x, rcpk_y, _) = rcpk.get_coordinates(); let (sg_x, sg_y, _) = sg.get_coordinates(); cs.enforce( || "sg_x == rcpk_x", |lc| lc + sg_x.get_variable(), |lc| lc + CS::one(), |lc| lc + rcpk_x.get_variable(), ); cs.enforce( || "sg_y == rcpk_y", |lc| lc + sg_y.get_variable(), |lc| lc + CS::one(), |lc| lc + rcpk_y.get_variable(), ); let rx = AllocatedNum::alloc(cs.namespace(|| "rx"), || Ok(self.r.x))?; let ry = AllocatedNum::alloc(cs.namespace(|| "ry"), || Ok(self.r.y))?; let gx = AllocatedNum::alloc(cs.namespace(|| "gx"), || Ok(self.g.x))?; let gy = AllocatedNum::alloc(cs.namespace(|| "gy"), || Ok(self.g.y))?; let pkx = AllocatedNum::alloc(cs.namespace(|| "pkx"), || Ok(self.pk.x))?; let pky = AllocatedNum::alloc(cs.namespace(|| "pky"), || Ok(self.pk.y))?; let c = AllocatedNum::alloc(cs.namespace(|| "c"), || Ok(self.c))?; let s = AllocatedNum::alloc(cs.namespace(|| "s"), || Ok(self.s))?; Ok(vec![rx, ry, gx, gy, pkx, pky, c, s]) } fn output(&self, _z: &[F]) -> Vec { vec![ self.r.x, self.r.y, self.g.x, self.g.y, self.pk.x, self.pk.y, self.c, self.s, ] } }