diff --git a/ecc/src/general_ecc/mul.rs b/ecc/src/general_ecc/mul.rs index 5c817307..115e9a07 100644 --- a/ecc/src/general_ecc/mul.rs +++ b/ecc/src/general_ecc/mul.rs @@ -198,4 +198,100 @@ impl< self.add(region, &acc, &aux.to_sub) } + + /// Computes multi-product variant for ecdsa + /// + /// Takes as input the generator point `gen_point` $G$ + /// and a vector of triplets formed by: + /// - Public Key point $P$ as `AssignedPoint` + /// - Scalar to mul with generator $u_1$ as `AssignedInteger` + /// - Scalar to mul with Public Key point $u_2$ as `AssignedInteger` + /// + /// Returns [G * u_1_i + P_i * u_2_i] for each i in the triplet Vec + pub fn mul_batch_ecdsa( + &self, + region: &mut RegionCtx<'_, '_, N>, + gen_point: &AssignedPoint, + triplets: Vec<( + AssignedPoint, + AssignedInteger, + AssignedInteger, + )>, + window_size: usize, + ) -> Result>, Error> { + assert!(window_size > 0); + assert!(triplets.len() > 0); + let aux = self.get_mul_aux(window_size, 2).unwrap(); + + let scalar_chip = self.scalar_field_chip(); + // 1. Decompose scalars in bits + let mut decomposed_scalars: Vec<(Vec>, Vec>)> = + triplets + .iter() + .map(|(_, u1, u2)| { + let decomposed_u1 = scalar_chip.decompose(region, u1)?; + let decomposed_u2 = scalar_chip.decompose(region, u2)?; + Ok((decomposed_u1, decomposed_u2)) + }) + .collect::>()?; + + // 2. Pad scalars bit representations + for (decomposed_u1, decomposed_u2) in decomposed_scalars.iter_mut() { + self.pad(region, decomposed_u1, window_size)?; + self.pad(region, decomposed_u2, window_size)?; + } + + // 3. Split scalar bits into windows + let windowed_scalars: Vec<(Windowed, Windowed)> = decomposed_scalars + .iter() + .map(|(decomposed_u1, decomposed_u2)| { + ( + Self::window(decomposed_u1.to_vec(), window_size), + Self::window(decomposed_u2.to_vec(), window_size), + ) + }) + .collect(); + let number_of_windows = windowed_scalars[0].0 .0.len(); + + // Table for the generator point + let gen_table = + self.make_incremental_table(region, &aux.to_add, &gen_point, window_size)?; + + // Tables for the public key points + let mut binary_aux = aux.to_add.clone(); + binary_aux = self.double(region, &binary_aux)?; + let pk_tables: Vec> = triplets + .iter() + .map(|(point, _, _)| { + self.make_incremental_table(region, &binary_aux, point, window_size) + }) + .collect::>()?; + + // Reorganize tables and scalars + let result = pk_tables + .iter() + .zip(windowed_scalars.iter()) + .map(|(pk_table, (w_u1, w_u2))| { + // First window + let mut acc = self.select_multi(region, &w_u1.0[0], &gen_table)?; + let to_add = self.select_multi(region, &w_u2.0[0], &pk_table)?; + acc = self.add(region, &acc, &to_add)?; + + // Rest of windows + for i in 1..number_of_windows { + acc = self.double_n(region, &acc, window_size)?; + let selector = &w_u1.0[i]; + let to_add = self.select_multi(region, selector, &gen_table)?; + acc = self.add(region, &acc, &to_add)?; + let selector = &w_u2.0[i]; + let to_add = self.select_multi(region, selector, &pk_table)?; + acc = self.add(region, &acc, &to_add)?; + } + + acc = self.add(region, &acc, &aux.to_sub)?; + Ok(acc) + }) + .collect(); + result + } } diff --git a/ecdsa/src/ecdsa.rs b/ecdsa/src/ecdsa.rs index 9bd754d0..8c2a3123 100644 --- a/ecdsa/src/ecdsa.rs +++ b/ecdsa/src/ecdsa.rs @@ -2,6 +2,7 @@ use super::integer::{IntegerChip, IntegerConfig}; use crate::halo2; use crate::integer; use crate::maingate; +use ecc::maingate::MainGateInstructions; use ecc::maingate::RegionCtx; use ecc::{AssignedPoint, EccConfig, GeneralEccChip}; use halo2::arithmetic::{CurveAffine, FieldExt}; @@ -63,6 +64,18 @@ pub struct AssignedPublicKey< pub point: AssignedPoint, } +pub struct AssignedEcdsaStarSig< + WB: FieldExt, + WS: FieldExt, + N: FieldExt, + const NUMBER_OF_LIMBS: usize, + const BIT_LEN_LIMB: usize, +> { + pub point: AssignedPoint, + pub r: AssignedInteger, + pub s: AssignedInteger, +} + pub struct EcdsaChip< E: CurveAffine, N: FieldExt, @@ -133,20 +146,163 @@ impl, + triplets: Vec<( + AssignedPublicKey, // signer pk + AssignedEcdsaSig, // signature + AssignedInteger, // msg_hash + )>, + ) -> Result<(), Error> { + let ecc_chip = self.ecc_chip(); + let scalar_chip = ecc_chip.scalar_field_chip(); + let base_chip = ecc_chip.base_field_chip(); + let main_gate = scalar_chip.main_gate(); + + let e_gen = ecc_chip.assign_point(ctx, Some(E::generator()))?; + let batch_mul_input: Vec<( + AssignedPoint, // pk + AssignedInteger, // u1 + AssignedInteger, // u2 + )> = triplets + .iter() + .map(|(pk, sig, msg_hash)| { + // 1. check 0 < r, s < n + // since `assert_not_zero` already includes a in-field check, we can just call + // `assert_not_zero` + scalar_chip.assert_not_zero(ctx, &sig.r)?; + scalar_chip.assert_not_zero(ctx, &sig.s)?; + // 2. w = s^(-1) (mod n) + let (s_inv, _) = scalar_chip.invert(ctx, &sig.s)?; + + // 3. u1 = m' * w (mod n) + let u1 = scalar_chip.mul(ctx, &msg_hash, &s_inv)?; + + // 4. u2 = r * w (mod n) + let u2 = scalar_chip.mul(ctx, &sig.r, &s_inv)?; + Ok((pk.point.clone(), u1, u2)) + }) + .collect::>()?; + + // 5. compute vector of Q = u1*G + u2*pk for each signature + let q_batch = ecc_chip.mul_batch_ecdsa(ctx, &e_gen, batch_mul_input, 4)?; + + for (q, (_, sig, _)) in q_batch.iter().zip(triplets.iter()) { + // 6. reduce q_x in E::ScalarExt + // assuming E::Base/E::ScalarExt have the same number of limbs + let q_x = q.get_x(); + let q_x_reduced_in_q = base_chip.reduce(ctx, &q_x)?; + let q_x_reduced_in_r = scalar_chip.reduce_external(ctx, &q_x_reduced_in_q)?; + // 7. check if Q.x == r (mod n) + scalar_chip.assert_strict_equal(ctx, &q_x_reduced_in_r, &sig.r)?; + } + + // main_gate.break_here(ctx)?; + Ok(()) + } + + /// Verify batch of signatures + /// The prover must provide the R point effectively following an ECDSA* + /// scheme + pub fn batch_verify_star( + &self, + ctx: &mut RegionCtx<'_, '_, N>, + pk_sig_msg_c: Vec<( + AssignedPublicKey, // signer pk + AssignedEcdsaStarSig, // signature + AssignedInteger, // msg_hash + AssignedInteger, // challenge power + )>, + ) -> Result<(), Error> { + let window_size = 4; + let ecc_chip = self.ecc_chip(); + let scalar_chip = ecc_chip.scalar_field_chip(); + let base_chip = ecc_chip.base_field_chip(); + + // 1. check 0 < r, s < n + // since `assert_not_zero` already includes a in-field check, we can just call + // `assert_not_zero` + scalar_chip.assert_not_zero(ctx, &pk_sig_msg_c[0].1.r)?; + scalar_chip.assert_not_zero(ctx, &pk_sig_msg_c[0].1.s)?; + // 2. w = s^(-1) (mod n) + let (w, _) = scalar_chip.div(ctx, &pk_sig_msg_c[0].3, &pk_sig_msg_c[0].1.s)?; + + // 3. u1 = m' * w (mod n) + let mut u1_acc = scalar_chip.mul(ctx, &pk_sig_msg_c[0].2, &w)?; + + // 4. u2 = r * w (mod n) + let u2 = scalar_chip.mul(ctx, &pk_sig_msg_c[0].1.r, &w)?; + + // Prepare batch multiplications: + // 1. sum{ u2 * pk } + sum_u1 * G + // 2. sum{ challenge * R} + let mut batch_mul_input_1 = vec![(pk_sig_msg_c[0].0.point.clone(), u2)]; + let mut batch_mul_input_2 = + vec![(pk_sig_msg_c[0].1.point.clone(), pk_sig_msg_c[0].3.clone())]; + + for (pk, sig, msg, c) in pk_sig_msg_c.into_iter().skip(1) { + // 1. check 0 < r, s < n + // since `assert_not_zero` already includes a in-field check, we can just call + // `assert_not_zero` + scalar_chip.assert_not_zero(ctx, &sig.r)?; + scalar_chip.assert_not_zero(ctx, &sig.s)?; + // 2. w = c * s^(-1) (mod n) + let (w, _) = scalar_chip.div(ctx, &c, &sig.s)?; + + // 3. u1 = m' * w (mod n) + let u1 = scalar_chip.mul(ctx, &msg, &w)?; + u1_acc = scalar_chip.add(ctx, &u1_acc, &u1)?; + + // 4. u2 = r * w (mod n) + let u2 = scalar_chip.mul(ctx, &sig.r, &w)?; + batch_mul_input_1.push((pk.point, u2)); + + batch_mul_input_2.push((sig.point, c)); + } + + u1_acc = scalar_chip.reduce(ctx, &u1_acc)?; + + // Add generator term to batch multiplication 1 + let e_gen = ecc_chip.assign_point(ctx, Some(E::generator()))?; + batch_mul_input_1.push((e_gen, u1_acc)); + + // 5. sum (u2_i * Q_i) + u1_acc * G + let sum_point_1 = ecc_chip.mul_batch_1d_horizontal(ctx, batch_mul_input_1, window_size)?; + + // 6. sum (challenge * R) + let sum_point_2 = ecc_chip.mul_batch_1d_horizontal(ctx, batch_mul_input_2, window_size)?; + + let sum_x = sum_point_1.get_x(); + let sum_x_reduced_in_q = base_chip.reduce(ctx, &sum_x)?; + let sum_x_reduced_in_r = scalar_chip.reduce_external(ctx, &sum_x_reduced_in_q)?; + let r_x = sum_point_2.get_x(); + let r_x_reduced_in_q = base_chip.reduce(ctx, &r_x)?; + let r_x_reduced_in_r = scalar_chip.reduce_external(ctx, &r_x_reduced_in_q)?; + scalar_chip.assert_equal(ctx, &r_x_reduced_in_r, &sum_x_reduced_in_r)?; + // let main_gate = scalar_chip.main_gate(); + // main_gate.break_here(ctx)?; + + Ok(()) + } } #[cfg(test)] mod tests { + use super::AssignedEcdsaStarSig; use super::{AssignedEcdsaSig, AssignedPublicKey, EcdsaChip}; use crate::halo2; use crate::integer; use crate::maingate; - use ecc::integer::Range; + use ecc::integer::{AssignedInteger, Range}; use ecc::maingate::big_to_fe; use ecc::maingate::fe_to_big; use ecc::maingate::RegionCtx; use ecc::{EccConfig, GeneralEccChip}; use group::ff::Field; + use group::prime::PrimeCurveAffine; use group::{Curve, Group}; use halo2::arithmetic::CurveAffine; use halo2::arithmetic::FieldExt; @@ -155,12 +311,18 @@ mod tests { use halo2::plonk::{Circuit, ConstraintSystem, Error}; use integer::{IntegerInstructions, NUMBER_OF_LOOKUP_LIMBS}; use maingate::{MainGate, MainGateConfig, RangeChip, RangeConfig, RangeInstructions}; + use rand::thread_rng; use rand_core::OsRng; use std::marker::PhantomData; const BIT_LEN_LIMB: usize = 68; const NUMBER_OF_LIMBS: usize = 4; + fn mod_n(x: C::Base) -> C::Scalar { + let x_big = fe_to_big(x); + big_to_fe(x_big) + } + // Single verification test #[derive(Clone, Debug)] struct TestCircuitEcdsaVerifyConfig { main_gate_config: MainGateConfig, @@ -283,13 +445,305 @@ mod tests { } } - #[test] - fn test_ecdsa_verifier() { - fn mod_n(x: C::Base) -> C::Scalar { - let x_big = fe_to_big(x); - big_to_fe(x_big) + // Batch verification test + #[derive(Default, Clone)] + struct BatchEcdsaVerifyInput { + pk: Vec, + m_hash: Vec, + s: Vec, + r: Vec, + } + + impl BatchEcdsaVerifyInput { + fn new() -> Self { + Self { + pk: vec![], + m_hash: vec![], + r: vec![], + s: vec![], + } } + } + + #[derive(Default, Clone)] + struct TestCircuitEcdsaBatchVerify { + aux_generator: E, + window_size: usize, + batch_size: usize, + _marker: PhantomData, + } + impl Circuit for TestCircuitEcdsaBatchVerify { + type Config = TestCircuitEcdsaVerifyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + TestCircuitEcdsaVerifyConfig::new::(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let mut ecc_chip = GeneralEccChip::::new( + config.ecc_chip_config(), + ); + let scalar_chip = ecc_chip.scalar_field_chip(); + + let mut rng = thread_rng(); + + let mut bevi = BatchEcdsaVerifyInput::new(); + // generate a batch of valid signatures + let generator = ::generator(); + for _ in 0..self.batch_size { + // Generate a key pair + let sk = ::ScalarExt::random(&mut rng); + let pk = (generator * sk).to_affine(); + + // Generate a valid_hash + let m_hash = ::ScalarExt::random(&mut rng); + + let randomness = ::ScalarExt::random(&mut rng); + let randomness_inv = randomness.invert().unwrap(); + + // Compute `r` + let r_point = (generator * randomness).to_affine().coordinates().unwrap(); + let x = r_point.x(); + let r = mod_n::(*x); + + // Compute `s` + let s = randomness_inv * (m_hash + r * sk); + bevi.pk.push(pk); + bevi.m_hash.push(m_hash); + bevi.r.push(r); + bevi.s.push(s); + } + + layouter.assign_region( + || "assign aux values", + |mut region| { + let offset = &mut 0; + let ctx = &mut RegionCtx::new(&mut region, offset); + + ecc_chip.assign_aux_generator(ctx, Some(self.aux_generator))?; + ecc_chip.assign_aux(ctx, self.window_size, 2)?; + Ok(()) + }, + )?; + + let ecdsa_chip = EcdsaChip::new(ecc_chip.clone()); + layouter + .assign_region( + || "region 0", + |mut region| { + let mut assigned_bevi: Vec<( + AssignedPublicKey, + AssignedEcdsaSig, + AssignedInteger, + )> = Vec::with_capacity(self.batch_size); + let offset = &mut 0; + let ctx = &mut RegionCtx::new(&mut region, offset); + + for i in 0..self.batch_size { + let integer_r = ecc_chip.new_unassigned_scalar(Some(bevi.r[i])); + let integer_s = ecc_chip.new_unassigned_scalar(Some(bevi.s[i])); + let msg_hash = ecc_chip.new_unassigned_scalar(Some(bevi.m_hash[i])); + + let r_assigned = + scalar_chip.assign_integer(ctx, integer_r, Range::Remainder)?; + let s_assigned = + scalar_chip.assign_integer(ctx, integer_s, Range::Remainder)?; + let sig = AssignedEcdsaSig { + r: r_assigned, + s: s_assigned, + }; + + let pk_in_circuit = + ecc_chip.assign_point(ctx, Some(bevi.pk[i].into()))?; + let pk_assigned = AssignedPublicKey { + point: pk_in_circuit, + }; + let msg_hash = + scalar_chip.assign_integer(ctx, msg_hash, Range::Remainder)?; + assigned_bevi.push((pk_assigned, sig, msg_hash)); + } + + ecdsa_chip.batch_verify(ctx, assigned_bevi).unwrap(); + Ok(()) + }, + ) + .unwrap(); + config.config_range(&mut layouter)?; + + Ok(()) + } + } + + // Batch ecdsa_star verification test + #[derive(Default, Clone)] + struct BatchEcdsaStarVerifyInput { + pk: Vec, + m_hash: Vec, + r: Vec, + s: Vec, + sig_point: Vec, + challenge: Vec, + } + + impl BatchEcdsaStarVerifyInput { + fn new() -> Self { + Self { + pk: vec![], + m_hash: vec![], + r: vec![], + s: vec![], + sig_point: vec![], + challenge: vec![], + } + } + } + #[derive(Default, Clone)] + struct TestCircuitEcdsaStarBatchVerify { + aux_generator: E, + window_size: usize, + batch_size: usize, + _marker: PhantomData, + } + + impl Circuit for TestCircuitEcdsaStarBatchVerify { + type Config = TestCircuitEcdsaVerifyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + TestCircuitEcdsaVerifyConfig::new::(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let mut ecc_chip = GeneralEccChip::::new( + config.ecc_chip_config(), + ); + let scalar_chip = ecc_chip.scalar_field_chip(); + + let mut rng = thread_rng(); + + let mut bevi = BatchEcdsaStarVerifyInput::new(); + // generate a batch of valid signatures and challenge powers + let generator = ::generator(); + for _ in 0..self.batch_size { + // Generate a key pair + let sk = ::ScalarExt::random(&mut rng); + let pk = (generator * sk).to_affine(); + + // Generate a valid_hash + let m_hash = ::ScalarExt::random(&mut rng); + + let randomness = ::ScalarExt::random(&mut rng); + let randomness_inv = randomness.invert().unwrap(); + + let sig_point = (generator * randomness).to_affine(); + let sig_point_coords = sig_point.coordinates().unwrap(); + let x = sig_point_coords.x(); + let r = mod_n::(*x); + + let s = randomness_inv * (m_hash + r * sk); + bevi.pk.push(pk); + bevi.m_hash.push(m_hash); + bevi.r.push(r); + bevi.s.push(s); + bevi.sig_point.push(sig_point); + } + + // Add challenge powers + let challenge = ::ScalarExt::random(&mut rng); + let mut challenge_power = ::ScalarExt::one(); + for _ in 0..self.batch_size - 1 { + bevi.challenge.push(challenge_power); + challenge_power = challenge_power * challenge; + } + bevi.challenge.push(challenge_power); + + layouter.assign_region( + || "assign aux values", + |mut region| { + let offset = &mut 0; + let ctx = &mut RegionCtx::new(&mut region, offset); + + ecc_chip.assign_aux_generator(ctx, Some(self.aux_generator))?; + ecc_chip.assign_aux(ctx, self.window_size, self.batch_size + 1)?; + ecc_chip.assign_aux(ctx, self.window_size, self.batch_size)?; + Ok(()) + }, + )?; + + let ecdsa_chip = EcdsaChip::new(ecc_chip.clone()); + layouter + .assign_region( + || "region 0", + |mut region| { + // bevi: batch ecdsa verifier input + let mut assigned_bevi: Vec<( + AssignedPublicKey, // Public Key + AssignedEcdsaStarSig, // ECDSA* signature + AssignedInteger, // Message hash + AssignedInteger, // Challenge power + )> = Vec::with_capacity(self.batch_size); + let offset = &mut 0; + let ctx = &mut RegionCtx::new(&mut region, offset); + + for i in 0..self.batch_size { + let integer_r = ecc_chip.new_unassigned_scalar(Some(bevi.r[i])); + let integer_s = ecc_chip.new_unassigned_scalar(Some(bevi.s[i])); + let msg_hash = ecc_chip.new_unassigned_scalar(Some(bevi.m_hash[i])); + let challenge_p = + ecc_chip.new_unassigned_scalar(Some(bevi.challenge[i])); + let sig_point_assigned = + ecc_chip.assign_point(ctx, Some(bevi.sig_point[i].into()))?; + + let r_assigned = scalar_chip.assign_integer(ctx, integer_r, Range::Remainder)?; + let s_assigned = scalar_chip.assign_integer(ctx, integer_s, Range::Remainder)?; + let sig = AssignedEcdsaStarSig { + point: sig_point_assigned, + r: r_assigned, + s: s_assigned, + }; + + let pk_in_circuit = + ecc_chip.assign_point(ctx, Some(bevi.pk[i].into()))?; + let pk_assigned = AssignedPublicKey { + point: pk_in_circuit, + }; + let msg_hash = scalar_chip.assign_integer(ctx, msg_hash, Range::Remainder)?; + let challenge_p = scalar_chip.assign_integer(ctx, challenge_p, Range::Remainder)?; + assigned_bevi.push((pk_assigned, sig, msg_hash, challenge_p)); + } + + ecdsa_chip.batch_verify_star(ctx, assigned_bevi).unwrap(); + Ok(()) + }, + ) + .unwrap(); + config.config_range(&mut layouter)?; + + Ok(()) + } + } + + // Run tests + #[test] + fn test_ecdsa_verifier() { fn run() { let g = C::generator(); @@ -354,4 +808,64 @@ mod tests { run::(); run::(); } + + #[test] + fn test_ecdsa_batch_verifier() { + fn run() { + use group::Group; + let k = 20; + let mut rng = thread_rng(); + let aux_generator = C::CurveExt::random(&mut rng).to_affine(); + let circuit = TestCircuitEcdsaBatchVerify:: { + aux_generator, + window_size: 4, + batch_size: 4, + _marker: PhantomData, + }; + + let public_inputs = vec![vec![]]; + let prover = match MockProver::run(k, &circuit, public_inputs) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + assert_eq!(prover.verify(), Ok(())); + } + + use crate::curves::bn256::Fr as BnScalar; + use crate::curves::pasta::{Fp as PastaFp, Fq as PastaFq}; + use crate::curves::secp256k1::Secp256k1Affine as Secp256k1; + run::(); + // run::(); + // run::(); + } + + #[test] + fn test_ecdsa_star_batch_verifier() { + fn run() { + use group::Group; + let k = 20; + let mut rng = thread_rng(); + let aux_generator = C::CurveExt::random(&mut rng).to_affine(); + let circuit = TestCircuitEcdsaStarBatchVerify:: { + aux_generator, + window_size: 4, + batch_size: 8, + _marker: PhantomData, + }; + + let public_inputs = vec![vec![]]; + let prover = match MockProver::run(k, &circuit, public_inputs) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + assert_eq!(prover.verify(), Ok(())); + } + + use crate::curves::bn256::Fr as BnScalar; + use crate::curves::pasta::{Fp as PastaFp, Fq as PastaFq}; + use crate::curves::secp256k1::Secp256k1Affine as Secp256k1; + run::(); + // run::(); + // run::(); + } } diff --git a/integer/Cargo.toml b/integer/Cargo.toml index 03dd971c..d5e4dc4e 100644 --- a/integer/Cargo.toml +++ b/integer/Cargo.toml @@ -15,6 +15,3 @@ subtle = { version = "2.3", default-features = false } [dev-dependencies] rand_core = { version = "0.6", default-features = false } - - -