From d44d2779044b66c240d548445ac80e9ff1e1dffb Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 4 May 2026 14:46:02 +0200 Subject: [PATCH 1/3] Add NonIdentityPoint::new_from_constant (#55) This PR adds `NonIdentityPoint::new_from_constant` to enable creating non-identity points that are properly pinned to constants, which is required by the Orchard ZSA circuit ([Orchard PR](https://github.com/QED-it/orchard/pull/246)). More precisely, in the Orchard ZSA circuit, `q_init_zec` / `q_init_zsa` must be constrained to fixed constants, as they define the initial point `Q` of the Sinsemilla hash. If constructed via `NonIdentityPoint::new`, they remain unconstrained witnesses, allowing a prover to inject an arbitrary on-curve point and break commitment soundness. --- halo2_gadgets/src/ecc.rs | 21 ++++++++++++- halo2_gadgets/src/ecc/chip.rs | 12 ++++++++ halo2_gadgets/src/ecc/chip/witness_point.rs | 34 ++++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index 326b6e4f5e..1070aad187 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -77,6 +77,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, @@ -280,7 +288,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, @@ -290,6 +299,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, diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index 469c19ebec..12ba4a7d72 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -492,6 +492,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 98f865a6dc..d7d279d2e4 100644 --- a/halo2_gadgets/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets/src/ecc/chip/witness_point.rs @@ -184,6 +184,30 @@ 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)] @@ -202,10 +226,18 @@ pub mod tests { ) { // Witnessing the identity should return an error. NonIdentityPoint::new( - chip, + chip.clone(), layouter.namespace(|| "witness identity"), Value::known(pallas::Affine::identity()), ) .expect_err("witnessing 𝒪 should return an error"); + + // Witnessing the identity from a constant should return an error. + NonIdentityPoint::new_from_constant( + chip, + layouter.namespace(|| "witness identity"), + pallas::Affine::identity(), + ) + .expect_err("witnessing 𝒪 should return an error"); } } From 11eb2bf63c8c92fd3d7aa003abb5db57c9c7f844 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 1 Jun 2026 08:46:23 +0200 Subject: [PATCH 2/3] Test NonIdentityPoint::new_from_constant and Point::new_from_constant (#57) Add tests for `NonIdentityPoint::new_from_constant` and `Point::new_from_constant`. --- halo2_gadgets/src/ecc/chip/witness_point.rs | 59 ++++++++++++++++-- .../circuit_data/proof_ecc_chip.bin | Bin 3872 -> 3872 bytes .../circuit_data/proof_ecc_chip_4_5b.bin | Bin 3968 -> 3968 bytes .../circuit_data/vk_ecc_chip.rdata | 58 ++++++++--------- .../circuit_data/vk_ecc_chip_4_5b.rdata | 58 ++++++++--------- 5 files changed, 112 insertions(+), 63 deletions(-) diff --git a/halo2_gadgets/src/ecc/chip/witness_point.rs b/halo2_gadgets/src/ecc/chip/witness_point.rs index d7d279d2e4..dfb9f63cda 100644 --- a/halo2_gadgets/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets/src/ecc/chip/witness_point.rs @@ -212,11 +212,12 @@ impl Config { #[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, @@ -224,7 +225,7 @@ pub mod tests { chip: EccChip, mut layouter: impl Layouter, ) { - // Witnessing the identity should return an error. + // `NonIdentityPoint::new` must reject the identity point. NonIdentityPoint::new( chip.clone(), layouter.namespace(|| "witness identity"), @@ -232,12 +233,60 @@ pub mod tests { ) .expect_err("witnessing 𝒪 should return an error"); - // Witnessing the identity from a constant should return an error. + // `NonIdentityPoint::new` must accept a non-identity point. + let _ = NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "witness a non-identity point"), + Value::known(pallas::Point::generator().to_affine()), + ) + .unwrap(); + + // `NonIdentityPoint::new_from_constant` must reject the identity point. NonIdentityPoint::new_from_constant( - chip, - layouter.namespace(|| "witness identity"), + 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 constant non-identity point"), + pallas::Point::generator().to_affine(), + ) + .unwrap(); + + // `Point::new` must accept the identity point. + let _ = Point::new( + chip.clone(), + layouter.namespace(|| "witness identity"), + Value::known(pallas::Affine::identity()), + ) + .unwrap(); + + // `Point::new` must accept a non-identity point. + let _ = Point::new( + chip.clone(), + layouter.namespace(|| "witness a non-identity point"), + Value::known(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 constant non-identity point"), + pallas::Point::generator().to_affine(), + ) + .unwrap(); } } diff --git a/halo2_gadgets/src/test_circuits/circuit_data/proof_ecc_chip.bin b/halo2_gadgets/src/test_circuits/circuit_data/proof_ecc_chip.bin index e30d156d18c7303f0f7ed7e7b2f91b89522fcbbd..28c19d824f518067da919b30f82521d8c31f7a30 100644 GIT binary patch literal 3872 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 literal 3872 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 U#Yof-=`sHpn@*oOR2n}1A4u0i1ONa4 diff --git a/halo2_gadgets/src/test_circuits/circuit_data/proof_ecc_chip_4_5b.bin b/halo2_gadgets/src/test_circuits/circuit_data/proof_ecc_chip_4_5b.bin index b05982b14cefd625abd2410d7b1eb2f4b9e171f6..e017f010438ddf90af95842b27552ac617d3d8cc 100644 GIT binary patch literal 3968 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 z Date: Tue, 9 Jun 2026 10:17:02 +0200 Subject: [PATCH 3/3] Update new_from_constant tests to not change the existing stability checks (#58) This PR introduces a circuit version flag in MyEccCircuit to preserve backward compatibility with the existing serialized circuit fixtures. --- halo2_gadgets/src/ecc.rs | 34 +++++++--- halo2_gadgets/src/ecc/chip/witness_point.rs | 39 ++++-------- .../circuit_data/proof_ecc_chip.bin | Bin 3872 -> 3872 bytes .../circuit_data/proof_ecc_chip_4_5b.bin | Bin 3968 -> 3968 bytes .../circuit_data/vk_ecc_chip.rdata | 58 +++++++++--------- .../circuit_data/vk_ecc_chip_4_5b.rdata | 58 +++++++++--------- 6 files changed, 95 insertions(+), 94 deletions(-) diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index 1070aad187..7fe4efa31e 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -794,13 +794,15 @@ pub(crate) mod tests { struct MyEccCircuit { test_errors: bool, + test_zsa_additions: bool, _lookup_marker: PhantomData, } impl MyEccCircuit { - fn new(test_errors: bool) -> Self { + fn new(test_errors: bool, test_zsa_additions: bool) -> Self { Self { test_errors, + test_zsa_additions, _lookup_marker: PhantomData, } } @@ -812,7 +814,7 @@ pub(crate) mod tests { type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { - MyEccCircuit::new(false) + MyEccCircuit::new(false, false) } fn configure(meta: &mut ConstraintSystem) -> Self::Config { @@ -912,6 +914,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( @@ -988,17 +998,25 @@ 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(())) } #[test] fn test_ecc_chip_against_stored_circuit() { - let circuit = MyEccCircuit::::new(false); + let circuit = MyEccCircuit::::new(false, false); test_against_stored_circuit(circuit, "ecc_chip", 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() { @@ -1008,7 +1026,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(); @@ -1017,7 +1035,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(())) @@ -1025,7 +1043,7 @@ pub(crate) mod tests { #[test] fn test_against_stored_ecc_chip_4_5b() { - let circuit = MyEccCircuit::::new(false); + let circuit = MyEccCircuit::::new(false, false); test_against_stored_circuit(circuit, "ecc_chip_4_5b", 3968); } @@ -1038,7 +1056,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/witness_point.rs b/halo2_gadgets/src/ecc/chip/witness_point.rs index dfb9f63cda..d136f014a4 100644 --- a/halo2_gadgets/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets/src/ecc/chip/witness_point.rs @@ -225,22 +225,21 @@ pub mod tests { chip: EccChip, mut layouter: impl Layouter, ) { - // `NonIdentityPoint::new` must reject the identity point. + // Witnessing the identity should return an error. NonIdentityPoint::new( - chip.clone(), + chip, layouter.namespace(|| "witness identity"), Value::known(pallas::Affine::identity()), ) .expect_err("witnessing 𝒪 should return an error"); + } - // `NonIdentityPoint::new` must accept a non-identity point. - let _ = NonIdentityPoint::new( - chip.clone(), - layouter.namespace(|| "witness a non-identity point"), - Value::known(pallas::Point::generator().to_affine()), - ) - .unwrap(); - + 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(), @@ -252,27 +251,11 @@ pub mod tests { // `NonIdentityPoint::new_from_constant` must accept a non-identity point. let _ = NonIdentityPoint::new_from_constant( chip.clone(), - layouter.namespace(|| "witness constant non-identity point"), + layouter.namespace(|| "witness a constant non-identity point"), pallas::Point::generator().to_affine(), ) .unwrap(); - // `Point::new` must accept the identity point. - let _ = Point::new( - chip.clone(), - layouter.namespace(|| "witness identity"), - Value::known(pallas::Affine::identity()), - ) - .unwrap(); - - // `Point::new` must accept a non-identity point. - let _ = Point::new( - chip.clone(), - layouter.namespace(|| "witness a non-identity point"), - Value::known(pallas::Point::generator().to_affine()), - ) - .unwrap(); - // `Point::new_from_constant` must accept the identity point. let _ = Point::new_from_constant( chip.clone(), @@ -284,7 +267,7 @@ pub mod tests { // `Point::new_from_constant` must accept a non-identity point. let _ = Point::new_from_constant( chip, - layouter.namespace(|| "witness constant non-identity point"), + layouter.namespace(|| "witness a constant non-identity point"), pallas::Point::generator().to_affine(), ) .unwrap(); diff --git a/halo2_gadgets/src/test_circuits/circuit_data/proof_ecc_chip.bin b/halo2_gadgets/src/test_circuits/circuit_data/proof_ecc_chip.bin index 28c19d824f518067da919b30f82521d8c31f7a30..e30d156d18c7303f0f7ed7e7b2f91b89522fcbbd 100644 GIT binary patch literal 3872 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 U#Yof-=`sHpn@*oOR2n}1A4u0i1ONa4 literal 3872 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 diff --git a/halo2_gadgets/src/test_circuits/circuit_data/proof_ecc_chip_4_5b.bin b/halo2_gadgets/src/test_circuits/circuit_data/proof_ecc_chip_4_5b.bin index e017f010438ddf90af95842b27552ac617d3d8cc..b05982b14cefd625abd2410d7b1eb2f4b9e171f6 100644 GIT binary patch literal 3968 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