binius_spartan_verifier/wrapper/
mod.rs1pub mod builder_channel;
13pub mod circuit_elem;
14pub mod gadgets;
15pub mod zk_wrapped_channel;
16
17pub use builder_channel::IronSpartanBuilderChannel;
18pub use zk_wrapped_channel::ZKWrappedVerifierChannel;
19
20#[cfg(test)]
21mod tests {
22 use std::rc::Rc;
23
24 use binius_core::word::Word;
25 use binius_field::{
26 BinaryField1b as B1, ExtensionField, Field, Ghash128b as B128, Random,
27 arithmetic_traits::InvertOrZero, field::FieldOps,
28 };
29 use binius_ip::channel::{IPVerifierChannel, WordIPVerifierChannel};
30 use binius_spartan_frontend::circuit_builder::ConstraintBuilder;
31 use rand::{SeedableRng, rngs::StdRng};
32
33 use super::*;
34 use crate::wrapper::circuit_elem::CircuitElem;
35
36 type BuildElem = CircuitElem<B128, ConstraintBuilder<B128>>;
37
38 fn alloc_private_wire(rc: &Rc<std::cell::RefCell<ConstraintBuilder<B128>>>) -> BuildElem {
44 let wire = rc.borrow_mut().alloc_precommit();
45 BuildElem::wire(rc, wire)
46 }
47
48 #[test]
49 fn test_constant_arithmetic() {
50 let a = BuildElem::Constant(B128::new(3));
51 let b = BuildElem::Constant(B128::new(5));
52
53 let sum = a.clone() + b.clone();
55 assert!(matches!(sum, BuildElem::Constant(c) if c == B128::new(3) + B128::new(5)));
56
57 let product = a.clone() * b.clone();
59 assert!(matches!(product, BuildElem::Constant(c) if c == B128::new(3) * B128::new(5)));
60
61 let diff = a - b;
63 assert!(matches!(diff, BuildElem::Constant(c) if c == B128::new(3) + B128::new(5)));
64 }
65
66 #[test]
67 fn test_constant_identity_shortcuts() {
68 let rc = Rc::new(std::cell::RefCell::new(ConstraintBuilder::<B128>::new()));
69 let elem = alloc_private_wire(&rc);
70
71 let result = elem.clone() + BuildElem::Constant(B128::ZERO);
73 assert!(matches!(result, BuildElem::Wire { .. }));
74
75 let result = elem.clone() * BuildElem::Constant(B128::ONE);
77 assert!(matches!(result, BuildElem::Wire { .. }));
78
79 let result = elem * BuildElem::Constant(B128::ZERO);
81 assert!(matches!(result, BuildElem::Constant(c) if c == B128::ZERO));
82 }
83
84 #[test]
85 fn test_wire_addition_creates_constraint() {
86 let rc = Rc::new(std::cell::RefCell::new(ConstraintBuilder::<B128>::new()));
87 let a = alloc_private_wire(&rc);
88 let b = alloc_private_wire(&rc);
89
90 let _sum = a + b;
91 let (cs, _layout) = Rc::try_unwrap(rc).unwrap().into_inner().build().finalize();
94 assert!(!cs.mul_constraints().is_empty());
95 }
96
97 #[test]
98 fn test_wire_multiplication_creates_constraint() {
99 let rc = Rc::new(std::cell::RefCell::new(ConstraintBuilder::<B128>::new()));
100 let a = alloc_private_wire(&rc);
101 let b = alloc_private_wire(&rc);
102
103 let _product = a * b;
104 let (cs, _layout) = Rc::try_unwrap(rc).unwrap().into_inner().build().finalize();
105 assert!(!cs.mul_constraints().is_empty());
107 }
108
109 #[test]
110 fn test_invert_creates_constraints() {
111 let rc = Rc::new(std::cell::RefCell::new(ConstraintBuilder::<B128>::new()));
112 let elem = alloc_private_wire(&rc);
113
114 let _inv = unsafe { elem.invert() };
117 let (cs, _layout) = Rc::try_unwrap(rc).unwrap().into_inner().build().finalize();
118 assert!(cs.mul_constraints().len() >= 2);
121 }
122
123 #[test]
124 #[should_panic(expected = "the wrapper inverts only values argued non-zero")]
125 fn test_invert_or_zero_is_unimplemented() {
126 let rc = Rc::new(std::cell::RefCell::new(ConstraintBuilder::<B128>::new()));
127 let elem = alloc_private_wire(&rc);
128
129 let _ = elem.invert_or_zero();
130 }
131
132 #[test]
133 fn test_channel_recv_and_sample() {
134 let mut channel = IronSpartanBuilderChannel::<B128>::new();
135
136 let a = channel.recv_one().unwrap();
137 let b = channel.sample();
138 let c = channel.recv_array::<3>().unwrap();
139
140 assert!(matches!(a, BuildElem::Wire { .. }));
142 assert!(matches!(b, BuildElem::Wire { .. }));
143 for elem in &c {
144 assert!(matches!(elem, BuildElem::Wire { .. }));
145 }
146 }
147
148 #[test]
154 fn test_pack_words_allocates_inout_wires() {
155 let mut channel = IronSpartanBuilderChannel::<B128>::new();
156
157 let words = [Word::from_u64(7), Word::ZERO, Word::from_u64(9)];
160 let elems = channel.pack_words(&words);
161
162 assert_eq!(elems.len(), 2);
163 assert!(
164 elems
165 .iter()
166 .all(|elem| matches!(elem, BuildElem::Wire { .. }))
167 );
168 }
169
170 #[test]
171 fn test_channel_assert_zero() {
172 let mut channel = IronSpartanBuilderChannel::<B128>::new();
173
174 assert!(channel.assert_zero(BuildElem::Constant(B128::ZERO)).is_ok());
176
177 assert!(channel.assert_zero(BuildElem::Constant(B128::ONE)).is_err());
179
180 let wire_elem = channel.recv_one().unwrap();
182 assert!(channel.assert_zero(wire_elem).is_ok());
183 }
184
185 #[test]
186 fn test_verify_iop_builds_constraint_system() {
187 use binius_spartan_frontend::{
188 circuit_builder::CircuitBuilder, circuits::powers, compiler::compile,
189 };
190
191 use crate::{
192 IOPVerifier,
193 constraint_system::{BlindingInfo, ConstraintSystemPadded},
194 };
195
196 fn power7_circuit<Builder: CircuitBuilder>(
198 builder: &mut Builder,
199 x_wire: Builder::Wire,
200 y_wire: Builder::Wire,
201 ) {
202 let powers_vec = powers(builder, x_wire, 7);
203 let x7 = powers_vec[6];
204 builder.assert_eq(x7, y_wire);
205 }
206
207 let mut constraint_builder = ConstraintBuilder::<B128>::new();
208 let x_wire = constraint_builder.alloc_inout();
209 let y_wire = constraint_builder.alloc_inout();
210 power7_circuit(&mut constraint_builder, x_wire, y_wire);
211 let (cs, _layout) = compile(constraint_builder);
212
213 let blinding_info = BlindingInfo {
215 n_dummy_wires: 10,
216 n_dummy_constraints: 2,
217 };
218 let cs = ConstraintSystemPadded::new(cs, blinding_info);
219 let iop_verifier = IOPVerifier::new(cs);
220 let cs = iop_verifier.constraint_system();
221 let public_size = 1 << cs.log_public();
222
223 let mut channel = IronSpartanBuilderChannel::<B128>::new();
225
226 let public = vec![B128::ZERO; public_size];
228 let public_elems = channel.observe_many(&public);
229 iop_verifier
231 .verify((), &public_elems, &mut channel)
232 .expect("symbolic verify failed");
233
234 let builder = channel.finish();
235
236 let (wrapper_cs, _layout) = builder.build().finalize();
238
239 assert!(wrapper_cs.n_inout() > 0);
241 assert!(!wrapper_cs.mul_constraints().is_empty());
242 }
243
244 #[test]
245 fn test_square_transpose_constants() {
246 type FSub = B1;
247 let degree = <B128 as ExtensionField<FSub>>::DEGREE;
248 let mut rng = StdRng::seed_from_u64(0);
249
250 let values = (0..degree)
252 .map(|_| B128::random(&mut rng))
253 .collect::<Vec<_>>();
254
255 let mut expected = values.clone();
256 <B128 as ExtensionField<FSub>>::square_transpose(&mut expected);
257
258 let mut elems = values
259 .iter()
260 .map(|&v| BuildElem::Constant(v))
261 .collect::<Vec<_>>();
262 <BuildElem as FieldOps>::square_transpose::<FSub>(&mut elems);
263
264 for (i, (elem, &exp)) in elems.iter().zip(&expected).enumerate() {
265 match elem {
266 CircuitElem::Constant(c) => assert_eq!(*c, exp, "mismatch at index {i}"),
267 CircuitElem::Wire { .. } => {
268 panic!("expected constant after all-constants transpose")
269 }
270 }
271 }
272 }
273
274 #[test]
275 fn test_channel_integration_simple_circuit() {
276 let mut channel = IronSpartanBuilderChannel::<B128>::new();
279 let a = channel.recv_one().unwrap();
280 let b = channel.recv_one().unwrap();
281 let c = channel.recv_one().unwrap();
282 let product = a * b;
283 let diff = product + c;
285 channel.assert_zero(diff).unwrap();
286
287 let builder = channel.finish();
289 let (cs, _layout) = builder.build().finalize();
290
291 assert!(cs.n_inout() >= 3);
293 assert!(!cs.mul_constraints().is_empty());
296 }
297}