diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index fcf5f9b68..96b726e5e 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -80,6 +80,14 @@ pub trait EccInstructions: value: Value, ) -> Result; + /// Witnesses the given constant point with both coordinates pinned via fixed columns. + /// Returns an error if the point is the identity. + fn witness_point_non_id_from_constant( + &self, + layouter: &mut impl Layouter, + value: C, + ) -> Result; + /// Witnesses a full-width scalar to be used in variable-base multiplication. fn witness_scalar_var( &self, @@ -283,7 +291,8 @@ pub struct NonIdentityPoint> { } impl> NonIdentityPoint { - /// Constructs a new point with the given value. + /// Witnesses the given point with only on-curve / non-identity constraints. + /// For known-constant points use [`NonIdentityPoint::new_from_constant`]. pub fn new( chip: EccChip, mut layouter: impl Layouter, @@ -293,6 +302,16 @@ impl> NonIdentityPoint { point.map(|inner| NonIdentityPoint { chip, inner }) } + /// Witnesses the given constant point with both coordinates pinned via fixed columns. + pub fn new_from_constant( + chip: EccChip, + mut layouter: impl Layouter, + value: C, + ) -> Result { + let point = chip.witness_point_non_id_from_constant(&mut layouter, value); + point.map(|inner| NonIdentityPoint { chip, inner }) + } + /// Constrains this point to be equal in value to another point. pub fn constrain_equal> + Clone>( &self, @@ -778,18 +797,28 @@ pub(crate) mod tests { struct MyEccCircuit { test_errors: bool, + test_zsa_additions: bool, circuit_version: CircuitVersion, _lookup_marker: PhantomData, } impl MyEccCircuit { - fn new(test_errors: bool) -> Self { - Self::with_version(test_errors, CircuitVersion::AnchoredBase) + fn new(test_errors: bool, test_zsa_additions: bool) -> Self { + Self::with_version( + test_errors, + test_zsa_additions, + CircuitVersion::AnchoredBase, + ) } - fn with_version(test_errors: bool, circuit_version: CircuitVersion) -> Self { + fn with_version( + test_errors: bool, + test_zsa_additions: bool, + circuit_version: CircuitVersion, + ) -> Self { Self { test_errors, + test_zsa_additions, circuit_version, _lookup_marker: PhantomData, } @@ -802,7 +831,7 @@ pub(crate) mod tests { type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { - MyEccCircuit::with_version(false, self.circuit_version) + MyEccCircuit::with_version(false, false, self.circuit_version) } fn configure(meta: &mut ConstraintSystem) -> Self::Config { @@ -902,6 +931,14 @@ pub(crate) mod tests { ) } + // Test constant witness + if self.test_zsa_additions { + super::chip::witness_point::tests::test_witness_constant( + chip.clone(), + layouter.namespace(|| "witness constant"), + ) + } + // Test complete addition { super::chip::add::tests::test_add( @@ -978,7 +1015,7 @@ pub(crate) mod tests { #[test] fn ecc_chip() { let k = 13; - let circuit = MyEccCircuit::::new(true); + let circuit = MyEccCircuit::::new(true, false); let prover = MockProver::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())) } @@ -986,6 +1023,7 @@ pub(crate) mod tests { #[test] fn test_ecc_chip_fixed_against_stored_circuit() { let circuit = MyEccCircuit::::with_version( + false, false, CircuitVersion::AnchoredBase, ); @@ -998,12 +1036,21 @@ pub(crate) mod tests { #[test] fn test_ecc_chip_insecure_against_stored_circuit() { let circuit = MyEccCircuit::::with_version( + false, false, CircuitVersion::InsecureUnanchoredBase, ); test_against_stored_circuit(circuit, "ecc_chip_insecure", 3872); } + #[test] + fn ecc_chip_with_zsa_additions() { + let k = 13; + let circuit = MyEccCircuit::::new(true, true); + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + #[cfg(feature = "test-dev-graph")] #[test] fn print_ecc_chip() { @@ -1013,7 +1060,7 @@ pub(crate) mod tests { root.fill(&WHITE).unwrap(); let root = root.titled("Ecc Chip Layout", ("sans-serif", 60)).unwrap(); - let circuit = MyEccCircuit::::new(false); + let circuit = MyEccCircuit::::new(false, false); halo2_proofs::dev::CircuitLayout::default() .render(13, &circuit, &root) .unwrap(); @@ -1022,7 +1069,7 @@ pub(crate) mod tests { #[test] fn ecc_chip_4_5b() { let k = 13; - let circuit = MyEccCircuit::::new(true); + let circuit = MyEccCircuit::::new(true, false); let prover = MockProver::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())) @@ -1031,6 +1078,7 @@ pub(crate) mod tests { #[test] fn test_against_stored_ecc_chip_4_5b_fixed() { let circuit = MyEccCircuit::::with_version( + false, false, CircuitVersion::AnchoredBase, ); @@ -1040,6 +1088,7 @@ pub(crate) mod tests { #[test] fn test_against_stored_ecc_chip_4_5b_insecure() { let circuit = MyEccCircuit::::with_version( + false, false, CircuitVersion::InsecureUnanchoredBase, ); @@ -1055,7 +1104,7 @@ pub(crate) mod tests { root.fill(&WHITE).unwrap(); let root = root.titled("Ecc Chip Layout", ("sans-serif", 60)).unwrap(); - let circuit = MyEccCircuit::::new(false); + let circuit = MyEccCircuit::::new(false, false); halo2_proofs::dev::CircuitLayout::default() .render(13, &circuit, &root) .unwrap(); diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index 2303c0925..bae023de0 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -525,6 +525,18 @@ where ) } + fn witness_point_non_id_from_constant( + &self, + layouter: &mut impl Layouter, + value: pallas::Affine, + ) -> Result { + let config = self.config().witness_point; + layouter.assign_region( + || "witness constant non-identity point", + |mut region| config.constant_point_non_id(value, 0, &mut region), + ) + } + fn witness_scalar_var( &self, _layouter: &mut impl Layouter, diff --git a/halo2_gadgets/src/ecc/chip/witness_point.rs b/halo2_gadgets/src/ecc/chip/witness_point.rs index 98f865a6d..d136f014a 100644 --- a/halo2_gadgets/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets/src/ecc/chip/witness_point.rs @@ -184,15 +184,40 @@ impl Config { self.assign_xy(value, offset, region) .map(|(x, y)| NonIdentityEccPoint::from_coordinates_unchecked(x, y)) } + + /// Assigns a constant non-identity point with both coordinates pinned via fixed columns. + pub(super) fn constant_point_non_id( + &self, + value: pallas::Affine, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Enable `q_point_non_id` selector + self.q_point_non_id.enable(region, offset)?; + + // Return an error if the point is the identity. + if value == pallas::Affine::identity() { + return Err(Error::Synthesis); + } + + let value = { + let value = value.coordinates().unwrap(); + (value.x().into(), value.y().into()) + }; + + self.assign_xy_from_constant(value, offset, region) + .map(|(x, y)| NonIdentityEccPoint::from_coordinates_unchecked(x, y)) + } } #[cfg(test)] pub mod tests { + use group::{Curve, Group}; use halo2_proofs::circuit::Layouter; use pasta_curves::pallas; use super::*; - use crate::ecc::{EccInstructions, NonIdentityPoint}; + use crate::ecc::{EccInstructions, NonIdentityPoint, Point}; pub fn test_witness_non_id< EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, @@ -208,4 +233,43 @@ pub mod tests { ) .expect_err("witnessing 𝒪 should return an error"); } + + pub fn test_witness_constant< + EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, + >( + chip: EccChip, + mut layouter: impl Layouter, + ) { + // `NonIdentityPoint::new_from_constant` must reject the identity point. + NonIdentityPoint::new_from_constant( + chip.clone(), + layouter.namespace(|| "witness constant identity"), + pallas::Affine::identity(), + ) + .expect_err("witnessing 𝒪 should return an error"); + + // `NonIdentityPoint::new_from_constant` must accept a non-identity point. + let _ = NonIdentityPoint::new_from_constant( + chip.clone(), + layouter.namespace(|| "witness a constant non-identity point"), + pallas::Point::generator().to_affine(), + ) + .unwrap(); + + // `Point::new_from_constant` must accept the identity point. + let _ = Point::new_from_constant( + chip.clone(), + layouter.namespace(|| "witness constant identity"), + pallas::Affine::identity(), + ) + .unwrap(); + + // `Point::new_from_constant` must accept a non-identity point. + let _ = Point::new_from_constant( + chip, + layouter.namespace(|| "witness a constant non-identity point"), + pallas::Point::generator().to_affine(), + ) + .unwrap(); + } }