From 35c815d36ed271d0a6542a664465590f2362fa8f Mon Sep 17 00:00:00 2001 From: Constance Date: Tue, 21 Mar 2023 17:04:19 +0100 Subject: [PATCH] Add variable-base sign-scalar multiplication --- halo2_gadgets/src/ecc.rs | 34 ++- halo2_gadgets/src/ecc/chip.rs | 18 ++ halo2_gadgets/src/ecc/chip/mul_fixed/short.rs | 279 +++++++++++++++++- 3 files changed, 328 insertions(+), 3 deletions(-) diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index 38fab0a4..4bf58d3e 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use halo2_proofs::{ arithmetic::CurveAffine, - circuit::{Chip, Layouter, Value}, + circuit::{AssignedCell, Chip, Layouter, Value}, plonk::Error, }; @@ -111,6 +111,15 @@ pub trait EccInstructions: b: &B, ) -> Result; + /// Performs variable-base sign-scalar multiplication, returning `[sign] point` + /// `sign` must be in {-1, 1}. + fn mul_sign( + &self, + layouter: &mut impl Layouter, + sign: &AssignedCell, + point: &Self::Point, + ) -> Result; + /// Performs variable-base scalar multiplication, returning `[scalar] base`. fn mul( &self, @@ -432,6 +441,21 @@ impl + Clone + Debug + Eq> Point, + sign: &AssignedCell, + ) -> Result, Error> { + self.chip + .mul_sign(&mut layouter, sign, &self.inner) + .map(|point| Point { + chip: self.chip.clone(), + inner: point, + }) + } } /// The affine short Weierstrass x-coordinate of a point on a specific elliptic curve. @@ -865,6 +889,14 @@ pub(crate) mod tests { )?; } + // Test variable-base sign-scalar multiplication + { + super::chip::mul_fixed::short::tests::test_mul_sign( + chip.clone(), + layouter.namespace(|| "variable-base sign-scalar mul"), + )?; + } + // Test full-width fixed-base scalar multiplication { super::chip::mul_fixed::full_width::tests::test_mul_fixed( diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index 4d12057a..3766b454 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -532,6 +532,24 @@ where ) } + /// Performs variable-base sign-scalar multiplication, returning `[sign] point` + /// `sign` must be in {-1, 1}. + fn mul_sign( + &self, + layouter: &mut impl Layouter, + sign: &AssignedCell, + point: &Self::Point, + ) -> Result { + // Multiply point by sign, using the same gate as mul_fixed::short. + // This also constrains sign to be in {-1, 1}. + let config_short = self.config().mul_fixed_short.clone(); + config_short.assign_scalar_sign( + layouter.namespace(|| "variable-base sign-scalar mul"), + sign, + point, + ) + } + fn mul( &self, layouter: &mut impl Layouter, diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs index bfdc735f..94d61de5 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs @@ -4,7 +4,7 @@ use super::super::{EccPoint, EccScalarFixedShort, FixedPoints, L_SCALAR_SHORT, N use crate::{ecc::chip::MagnitudeSign, utilities::bool_check}; use halo2_proofs::{ - circuit::{Layouter, Region}, + circuit::{AssignedCell, Layouter, Region}, plonk::{ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; @@ -241,11 +241,73 @@ impl> Config { Ok((result, scalar)) } + + /// Multiply the point by sign, using the q_mul_fixed_short gate. + /// Constraints `sign` in {-1, 1} + pub fn assign_scalar_sign( + &self, + mut layouter: impl Layouter, + sign: &AssignedCell, + point: &EccPoint, + ) -> Result { + let signed_point = layouter.assign_region( + || "Signed point", + |mut region| { + let offset = 0; + + // Enable mul_fixed_short selector to check the sign logic. + self.q_mul_fixed_short.enable(&mut region, offset)?; + + // Set "last window" to 0 (this field is irrelevant here). + region.assign_advice_from_constant( + || "u=0", + self.super_config.u, + offset, + pallas::Base::zero(), + )?; + + // Copy sign to `window` column + sign.copy_advice(|| "sign", &mut region, self.super_config.window, offset)?; + + // Assign the input y-coordinate. + point.y.copy_advice( + || "unsigned y", + &mut region, + self.super_config.add_config.y_qr, + offset, + )?; + + // Conditionally negate y-coordinate according to the value of sign + let signed_y_val = sign.value().and_then(|sign| { + if sign == &-pallas::Base::one() { + -point.y.value() + } else { + point.y.value().cloned() + } + }); + + // Assign the output signed y-coordinate. + let signed_y = region.assign_advice( + || "signed y", + self.super_config.add_config.y_p, + offset, + || signed_y_val, + )?; + + Ok(EccPoint { + x: point.x.clone(), + y: signed_y, + }) + }, + )?; + + Ok(signed_point) + } } #[cfg(test)] pub mod tests { - use group::{ff::PrimeField, Curve}; + use group::{ff::PrimeField, Curve, Group}; use halo2_proofs::{ arithmetic::CurveAffine, circuit::{AssignedCell, Chip, Layouter, Value}, @@ -657,4 +719,217 @@ pub mod tests { ); } } + + pub(crate) fn test_mul_sign( + chip: EccChip, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Generate a random non-identity point P + let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); + let p = Point::new( + chip.clone(), + layouter.namespace(|| "P"), + Value::known(p_val), + )?; + + // Create -P + let p_neg_val = -p_val; + let p_neg = Point::new( + chip.clone(), + layouter.namespace(|| "-P"), + Value::known(p_neg_val), + )?; + + // Create the identity point + let identity = Point::new( + chip.clone(), + layouter.namespace(|| "identity"), + Value::known(pallas::Point::identity().to_affine()), + )?; + + // Create -1 and 1 scalars + let pos_sign = chip.load_private( + layouter.namespace(|| "positive sign"), + chip.config().advices[0], + Value::known(pallas::Base::one()), + )?; + let neg_sign = chip.load_private( + layouter.namespace(|| "negative sign"), + chip.config().advices[1], + Value::known(-pallas::Base::one()), + )?; + + // [1] P == P + { + let result = p.mul_sign(layouter.namespace(|| "[1] P"), &pos_sign)?; + result.constrain_equal(layouter.namespace(|| "constrain [1] P"), &p)?; + } + + // [-1] P == -P + { + let result = p.mul_sign(layouter.namespace(|| "[1] P"), &neg_sign)?; + result.constrain_equal(layouter.namespace(|| "constrain [1] P"), &p_neg)?; + } + + // [1] 0 == 0 + { + let result = identity.mul_sign(layouter.namespace(|| "[1] O"), &pos_sign)?; + result.constrain_equal(layouter.namespace(|| "constrain [1] 0"), &identity)?; + } + + // [-1] 0 == 0 + { + let result = identity.mul_sign(layouter.namespace(|| "[-1] O"), &neg_sign)?; + result.constrain_equal(layouter.namespace(|| "constrain [1] 0"), &identity)?; + } + + Ok(()) + } + + #[test] + fn invalid_sign_in_mul_sign() { + use crate::{ecc::chip::EccConfig, utilities::UtilitiesInstructions}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::{FailureLocation, MockProver, VerifyFailure}, + plonk::{Circuit, ConstraintSystem, Error}, + }; + + #[derive(Default)] + struct MyCircuit { + base: Value, + sign: Value, + } + + impl UtilitiesInstructions for MyCircuit { + type Var = AssignedCell; + } + + impl Circuit for MyCircuit { + type Config = EccConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + let lookup_table = meta.lookup_table_column(); + let lagrange_coeffs = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + + // Shared fixed column for loading constants + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup_table); + EccChip::::configure(meta, advices, lagrange_coeffs, range_check) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = EccChip::construct(config.clone()); + + let column = config.advices[0]; + + //let short_config = config.mul_fixed_short.clone(); + let base = Point::new(chip, layouter.namespace(|| "load base"), self.base)?; + + let sign = + self.load_private(layouter.namespace(|| "load sign"), column, self.sign)?; + + base.mul_sign(layouter.namespace(|| "[sign] base"), &sign)?; + + Ok(()) + } + } + + // Copied from halo2_proofs::dev::util + fn format_value(v: pallas::Base) -> String { + use ff::Field; + if v.is_zero_vartime() { + "0".into() + } else if v == pallas::Base::one() { + "1".into() + } else if v == -pallas::Base::one() { + "-1".into() + } else { + // Format value as hex. + let s = format!("{:?}", v); + // Remove leading zeroes. + let s = s.strip_prefix("0x").unwrap(); + let s = s.trim_start_matches('0'); + format!("0x{}", s) + } + } + + // Sign that is not +/- 1 should fail + // Generate a random non-identity point + let point = pallas::Point::random(rand::rngs::OsRng); + let circuit = MyCircuit { + base: Value::known(point.to_affine()), + sign: Value::known(pallas::Base::zero()), + }; + + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ((17, "Short fixed-base mul gate").into(), 1, "sign_check").into(), + location: FailureLocation::InRegion { + region: (2, "Signed point").into(), + offset: 0, + }, + cell_values: vec![(((Any::Advice, 4).into(), 0).into(), "0".to_string())], + }, + VerifyFailure::ConstraintNotSatisfied { + constraint: ( + (17, "Short fixed-base mul gate").into(), + 3, + "negation_check" + ) + .into(), + location: FailureLocation::InRegion { + region: (2, "Signed point").into(), + offset: 0, + }, + cell_values: vec![ + ( + ((Any::Advice, 1).into(), 0).into(), + format_value(*point.to_affine().coordinates().unwrap().y()), + ), + ( + ((Any::Advice, 3).into(), 0).into(), + format_value(*point.to_affine().coordinates().unwrap().y()), + ), + (((Any::Advice, 4).into(), 0).into(), "0".to_string()), + ], + } + ]) + ); + } }