diff --git a/halo2_gadgets/src/endoscale/chip/alg_1.rs b/halo2_gadgets/src/endoscale/chip/alg_1.rs index d936edf0c..b68b48bb1 100644 --- a/halo2_gadgets/src/endoscale/chip/alg_1.rs +++ b/halo2_gadgets/src/endoscale/chip/alg_1.rs @@ -581,6 +581,7 @@ impl Bitstring { .collect() } + /// Produces an even-length bool array, the out-of-circuit equivalent to the `Bitstring`. #[cfg(test)] fn bitstring(&self) -> Value> { let num_bits = self.num_bits(); @@ -589,7 +590,7 @@ impl Bitstring { .iter() .by_vals() .take(num_bits) - // Padding for odd bits + // If there are an odd number of meaningful bits, add padding .chain((num_bits % 2 == 1).then_some(false)) .collect() }) @@ -871,4 +872,70 @@ mod tests { let proof = MockProver::run(9, &circuit, vec![]).unwrap(); proof.assert_satisfied(); } + + #[test] + #[should_panic(expected = "assertion failed: f(value)")] + fn endoscaling_depends_on_base_point() { + let mut rng = thread_rng(); + + // Use the same challenge + let challenge = pallas::Base::random(&mut rng); + + // Use different base points for in- and out-of-circuit + let in_circuit_base_point = pallas::Point::random(&mut rng); + let out_of_circuit_base_point = pallas::Point::random(&mut rng); + assert_ne!(in_circuit_base_point, out_of_circuit_base_point); + + // Convert challenge to bits / an endoscalar (out-of-circuit "alg 2")... + let out_of_circuit_endoscalar: pallas::Scalar = + EndoscalarChallenge::::new(&challenge).get_scalar(); + // ...then compute the relevant endoscaled value using scalar multiplication + let out_of_circuit_result = out_of_circuit_base_point * out_of_circuit_endoscalar; + + // In-circuit, compute the endoscaled result ("alg 1") + let circuit = CompareDecompAndEndoscalingCircuit { + challenge: Value::known(challenge), + base_point: Value::known(in_circuit_base_point.to_affine()), + out_of_circuit_result: Value::known(out_of_circuit_result.to_affine()), + }; + + let proof = MockProver::run(9, &circuit, vec![]).unwrap(); + + // Actually, this will panic instead of being handled nicely because we use an assert in the + // circuit to check equality. + assert!(proof.verify().is_err()); + } + + #[test] + #[should_panic(expected = "assertion failed: f(value)")] + fn endoscaling_depends_on_scalar() { + let mut rng = thread_rng(); + + // Use different challenges + let in_circuit_challenge = pallas::Base::random(&mut rng); + let out_of_circuit_challenge = pallas::Base::random(&mut rng); + assert_ne!(in_circuit_challenge, out_of_circuit_challenge); + + // Use the same base point + let base_point = pallas::Point::random(&mut rng); + + // Convert challenge to bits / an endoscalar (out-of-circuit "alg 2")... + let out_of_circuit_endoscalar: pallas::Scalar = + EndoscalarChallenge::::new(&out_of_circuit_challenge).get_scalar(); + // ...then compute the relevant endoscaled value using scalar multiplication + let out_of_circuit_result = base_point * out_of_circuit_endoscalar; + + // In-circuit, compute the endoscaled result ("alg 1") + let circuit = CompareDecompAndEndoscalingCircuit { + challenge: Value::known(in_circuit_challenge), + base_point: Value::known(base_point.to_affine()), + out_of_circuit_result: Value::known(out_of_circuit_result.to_affine()), + }; + + let proof = MockProver::run(9, &circuit, vec![]).unwrap(); + + // Actually, this will panic instead of being handled nicely because we use an assert in the + // circuit to check equality. + assert!(proof.verify().is_err()); + } }