1use std::fmt;
4
5use binius_field::BinaryField128bGhash as B128;
6use binius_utils::serialization::{DeserializeBytes, SerializationError, SerializeBytes};
7use bytes::{Buf, BufMut};
8
9use super::{ShiftedValueIndex, ValueIndex, ValueVec};
10use crate::{error::ConstraintViolation, word::Word};
11
12pub type Operand = Vec<ShiftedValueIndex>;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum ConstraintKind {
33 Zero,
35 And,
37 Imul,
39 Bmul,
41}
42
43impl fmt::Display for ConstraintKind {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 let name = match self {
46 Self::Zero => "zero",
47 Self::And => "and",
48 Self::Imul => "imul",
49 Self::Bmul => "bmul",
50 };
51 f.write_str(name)
52 }
53}
54
55#[derive(Debug, Clone, Default)]
64pub struct ZeroConstraint(pub [Operand; ZeroConstraint::ARITY]);
65
66impl ZeroConstraint {
67 pub const ARITY: usize = 1;
69 pub const KIND: ConstraintKind = ConstraintKind::Zero;
71 pub const OPERAND_NAMES: [&'static str; Self::ARITY] = ["val"];
73
74 pub fn plain(val: impl IntoIterator<Item = ValueIndex>) -> ZeroConstraint {
76 ZeroConstraint::new(val.into_iter().map(ShiftedValueIndex::plain))
77 }
78
79 pub fn new(val: impl IntoIterator<Item = ShiftedValueIndex>) -> ZeroConstraint {
81 ZeroConstraint([val.into_iter().collect()])
82 }
83
84 pub const fn val(&self) -> &Operand {
86 &self.0[0]
87 }
88
89 pub fn verify(&self, values: &ValueVec) -> Result<(), ConstraintViolation> {
95 let Word(val) = values.eval_operand(self.val());
96
97 if val != 0 {
98 return Err(ConstraintViolation::Zero { val });
99 }
100 Ok(())
101 }
102}
103
104impl AsRef<[Operand; ZeroConstraint::ARITY]> for ZeroConstraint {
105 fn as_ref(&self) -> &[Operand; Self::ARITY] {
106 &self.0
107 }
108}
109
110impl SerializeBytes for ZeroConstraint {
111 fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
112 self.0.serialize(write_buf)
113 }
114}
115
116impl DeserializeBytes for ZeroConstraint {
117 fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
118 where
119 Self: Sized,
120 {
121 <[Operand; Self::ARITY]>::deserialize(read_buf).map(Self)
122 }
123}
124
125#[derive(Debug, Clone, Default)]
132pub struct AndConstraint(pub [Operand; AndConstraint::ARITY]);
133
134impl AndConstraint {
135 pub const ARITY: usize = 3;
137 pub const KIND: ConstraintKind = ConstraintKind::And;
139 pub const OPERAND_NAMES: [&'static str; Self::ARITY] = ["a", "b", "c"];
141
142 pub fn plain_abc(
144 a: impl IntoIterator<Item = ValueIndex>,
145 b: impl IntoIterator<Item = ValueIndex>,
146 c: impl IntoIterator<Item = ValueIndex>,
147 ) -> AndConstraint {
148 AndConstraint::abc(
149 a.into_iter().map(ShiftedValueIndex::plain),
150 b.into_iter().map(ShiftedValueIndex::plain),
151 c.into_iter().map(ShiftedValueIndex::plain),
152 )
153 }
154
155 pub fn abc(
157 a: impl IntoIterator<Item = ShiftedValueIndex>,
158 b: impl IntoIterator<Item = ShiftedValueIndex>,
159 c: impl IntoIterator<Item = ShiftedValueIndex>,
160 ) -> AndConstraint {
161 AndConstraint([
162 a.into_iter().collect(),
163 b.into_iter().collect(),
164 c.into_iter().collect(),
165 ])
166 }
167
168 pub const fn a(&self) -> &Operand {
170 &self.0[0]
171 }
172
173 pub const fn b(&self) -> &Operand {
175 &self.0[1]
176 }
177
178 pub const fn c(&self) -> &Operand {
180 &self.0[2]
181 }
182
183 pub fn verify(&self, values: &ValueVec) -> Result<(), ConstraintViolation> {
189 let Word(a) = values.eval_operand(self.a());
190 let Word(b) = values.eval_operand(self.b());
191 let Word(c) = values.eval_operand(self.c());
192
193 let residue = (a & b) ^ c;
194 if residue != 0 {
195 return Err(ConstraintViolation::And { a, b, c, residue });
196 }
197 Ok(())
198 }
199}
200
201impl AsRef<[Operand; AndConstraint::ARITY]> for AndConstraint {
202 fn as_ref(&self) -> &[Operand; Self::ARITY] {
203 &self.0
204 }
205}
206
207impl SerializeBytes for AndConstraint {
208 fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
209 self.0.serialize(write_buf)
210 }
211}
212
213impl DeserializeBytes for AndConstraint {
214 fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
215 where
216 Self: Sized,
217 {
218 <[Operand; Self::ARITY]>::deserialize(read_buf).map(Self)
219 }
220}
221
222#[derive(Debug, Clone, Default)]
229pub struct ImulConstraint(pub [Operand; ImulConstraint::ARITY]);
230
231impl ImulConstraint {
232 pub const ARITY: usize = 4;
234 pub const KIND: ConstraintKind = ConstraintKind::Imul;
236 pub const OPERAND_NAMES: [&'static str; Self::ARITY] = ["a", "b", "lo", "hi"];
238
239 pub const fn a(&self) -> &Operand {
241 &self.0[0]
242 }
243
244 pub const fn b(&self) -> &Operand {
246 &self.0[1]
247 }
248
249 pub const fn lo(&self) -> &Operand {
253 &self.0[2]
254 }
255
256 pub const fn hi(&self) -> &Operand {
260 &self.0[3]
261 }
262
263 pub fn verify(&self, values: &ValueVec) -> Result<(), ConstraintViolation> {
272 let Word(a) = values.eval_operand(self.a());
273 let Word(b) = values.eval_operand(self.b());
274 let Word(lo) = values.eval_operand(self.lo());
275 let Word(hi) = values.eval_operand(self.hi());
276
277 let product = a as u128 * b as u128;
278 let expected_lo = product as u64;
279 let expected_hi = (product >> 64) as u64;
280
281 if lo != expected_lo || hi != expected_hi {
282 return Err(ConstraintViolation::Imul {
283 a,
284 b,
285 lo,
286 hi,
287 expected_lo,
288 expected_hi,
289 });
290 }
291 Ok(())
292 }
293}
294
295impl AsRef<[Operand; ImulConstraint::ARITY]> for ImulConstraint {
296 fn as_ref(&self) -> &[Operand; Self::ARITY] {
297 &self.0
298 }
299}
300
301impl SerializeBytes for ImulConstraint {
302 fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
303 self.0.serialize(write_buf)
304 }
305}
306
307impl DeserializeBytes for ImulConstraint {
308 fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
309 where
310 Self: Sized,
311 {
312 <[Operand; Self::ARITY]>::deserialize(read_buf).map(Self)
313 }
314}
315
316#[derive(Debug, Clone, Default)]
324pub struct BmulConstraint(pub [Operand; BmulConstraint::ARITY]);
325
326impl BmulConstraint {
327 pub const ARITY: usize = 6;
329 pub const KIND: ConstraintKind = ConstraintKind::Bmul;
331 pub const OPERAND_NAMES: [&'static str; Self::ARITY] =
333 ["a_lo", "a_hi", "b_lo", "b_hi", "c_lo", "c_hi"];
334
335 pub const fn a_lo(&self) -> &Operand {
337 &self.0[0]
338 }
339
340 pub const fn a_hi(&self) -> &Operand {
342 &self.0[1]
343 }
344
345 pub const fn b_lo(&self) -> &Operand {
347 &self.0[2]
348 }
349
350 pub const fn b_hi(&self) -> &Operand {
352 &self.0[3]
353 }
354
355 pub const fn c_lo(&self) -> &Operand {
357 &self.0[4]
358 }
359
360 pub const fn c_hi(&self) -> &Operand {
362 &self.0[5]
363 }
364
365 pub fn verify(&self, values: &ValueVec) -> Result<(), ConstraintViolation> {
374 let a = eval_element(values, self.a_lo(), self.a_hi());
375 let b = eval_element(values, self.b_lo(), self.b_hi());
376 let c = eval_element(values, self.c_lo(), self.c_hi());
377
378 let expected = u128::from(B128::new(a) * B128::new(b));
379 if c != expected {
380 return Err(ConstraintViolation::Bmul { a, b, c, expected });
381 }
382 Ok(())
383 }
384}
385
386fn eval_element(values: &ValueVec, lo: &Operand, hi: &Operand) -> u128 {
393 let Word(lo) = values.eval_operand(lo);
394 let Word(hi) = values.eval_operand(hi);
395 lo as u128 | ((hi as u128) << 64)
396}
397
398impl AsRef<[Operand; BmulConstraint::ARITY]> for BmulConstraint {
399 fn as_ref(&self) -> &[Operand; Self::ARITY] {
400 &self.0
401 }
402}
403
404impl SerializeBytes for BmulConstraint {
405 fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
406 self.0.serialize(write_buf)
407 }
408}
409
410impl DeserializeBytes for BmulConstraint {
411 fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
412 where
413 Self: Sized,
414 {
415 <[Operand; Self::ARITY]>::deserialize(read_buf).map(Self)
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use std::iter;
422
423 use super::*;
424 use crate::constraint_system::ConstraintKind;
425
426 #[test]
427 fn test_zero_constraint_serialization_round_trip() {
428 let constraint = ZeroConstraint::new([
431 ShiftedValueIndex::sll(ValueIndex::constant(1), 5),
432 ShiftedValueIndex::srl(ValueIndex::inout(2), 10),
433 ShiftedValueIndex::plain(ValueIndex::private(3)),
434 ]);
435
436 let mut buf = Vec::new();
437 constraint.serialize(&mut buf).unwrap();
438
439 let deserialized = ZeroConstraint::deserialize(&mut buf.as_slice()).unwrap();
440 assert_eq!(constraint.val().len(), deserialized.val().len());
441
442 for (orig, deser) in constraint.val().iter().zip(deserialized.val().iter()) {
443 assert_eq!(orig.value_index, deser.value_index);
444 assert_eq!(orig.shift_seq, deser.shift_seq);
445 }
446 }
447
448 #[test]
449 fn test_and_constraint_serialization_round_trip() {
450 let constraint = AndConstraint::abc(
451 vec![ShiftedValueIndex::sll(ValueIndex::constant(1), 5)],
452 vec![ShiftedValueIndex::srl(ValueIndex::constant(2), 10)],
453 vec![
454 ShiftedValueIndex::sar(ValueIndex::constant(3), 15),
455 ShiftedValueIndex::plain(ValueIndex::constant(4)),
456 ],
457 );
458
459 let mut buf = Vec::new();
460 constraint.serialize(&mut buf).unwrap();
461
462 let deserialized = AndConstraint::deserialize(&mut buf.as_slice()).unwrap();
463 assert_eq!(constraint.a().len(), deserialized.a().len());
464 assert_eq!(constraint.b().len(), deserialized.b().len());
465 assert_eq!(constraint.c().len(), deserialized.c().len());
466
467 for (orig, deser) in constraint.a().iter().zip(deserialized.a().iter()) {
468 assert_eq!(orig.value_index, deser.value_index);
469 assert_eq!(orig.shift_seq, deser.shift_seq);
470 }
471 }
472
473 #[test]
474 fn test_imul_constraint_serialization_round_trip() {
475 let constraint = ImulConstraint([
476 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))],
477 vec![ShiftedValueIndex::srl(ValueIndex::constant(1), 32)],
478 vec![ShiftedValueIndex::plain(ValueIndex::constant(2))],
479 vec![ShiftedValueIndex::plain(ValueIndex::constant(3))],
480 ]);
481
482 let mut buf = Vec::new();
483 constraint.serialize(&mut buf).unwrap();
484
485 let deserialized = ImulConstraint::deserialize(&mut buf.as_slice()).unwrap();
486 assert_eq!(constraint.a().len(), deserialized.a().len());
487 assert_eq!(constraint.b().len(), deserialized.b().len());
488 assert_eq!(constraint.lo().len(), deserialized.lo().len());
489 assert_eq!(constraint.hi().len(), deserialized.hi().len());
490 }
491
492 #[test]
493 fn test_bmul_constraint_serialization_round_trip() {
494 let constraint = BmulConstraint([
495 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))],
496 vec![ShiftedValueIndex::srl(ValueIndex::constant(1), 32)],
497 vec![ShiftedValueIndex::plain(ValueIndex::constant(2))],
498 vec![ShiftedValueIndex::sll(ValueIndex::constant(3), 5)],
499 vec![ShiftedValueIndex::plain(ValueIndex::constant(4))],
500 vec![
501 ShiftedValueIndex::sar(ValueIndex::constant(5), 15),
502 ShiftedValueIndex::plain(ValueIndex::constant(6)),
503 ],
504 ]);
505
506 let mut buf = Vec::new();
507 constraint.serialize(&mut buf).unwrap();
508
509 let deserialized = BmulConstraint::deserialize(&mut buf.as_slice()).unwrap();
510 assert_eq!(constraint.a_lo().len(), deserialized.a_lo().len());
511 assert_eq!(constraint.a_hi().len(), deserialized.a_hi().len());
512 assert_eq!(constraint.b_lo().len(), deserialized.b_lo().len());
513 assert_eq!(constraint.b_hi().len(), deserialized.b_hi().len());
514 assert_eq!(constraint.c_lo().len(), deserialized.c_lo().len());
515 assert_eq!(constraint.c_hi().len(), deserialized.c_hi().len());
516 }
517
518 const A: u128 = 0x0123456789abcdef_fedcba9876543210;
523 const B: u128 = 0x0f1e2d3c4b5a6978_8796a5b4c3d2e1f0;
524 const A_TIMES_B: u128 = 0x7f2984f784967f5a_7b881bf2b700d768;
525
526 fn values(words: &[u64]) -> ValueVec {
534 let mut public = [Word::ZERO; 8];
535 for (slot, &word) in iter::zip(&mut public, words) {
536 *slot = Word::from_u64(word);
537 }
538 ValueVec::new_from_data(public.len(), &public, &[Word::ZERO; 8])
539 }
540
541 fn at(index: u32) -> Operand {
543 vec![ShiftedValueIndex::plain(ValueIndex::constant(index))]
544 }
545
546 fn split(x: u128) -> [u64; 2] {
548 [x as u64, (x >> 64) as u64]
549 }
550
551 #[test]
552 fn zero_constraint_accepts_operand_whose_terms_cancel() {
553 let values = values(&[0xfeed_face, 0xfeed_face]);
556 let constraint = ZeroConstraint::plain([ValueIndex::constant(0), ValueIndex::constant(1)]);
557
558 assert!(constraint.verify(&values).is_ok());
559 }
560
561 #[test]
562 fn zero_constraint_rejects_operand_that_survives() {
563 let values = values(&[0xfeed_face, 0x0bad_cafe]);
564 let constraint = ZeroConstraint::plain([ValueIndex::constant(0), ValueIndex::constant(1)]);
565
566 match constraint.verify(&values).unwrap_err() {
567 ConstraintViolation::Zero { val } => assert_eq!(val, 0xfeed_face ^ 0x0bad_cafe),
568 other => panic!("wrong violation: {other:?}"),
569 }
570 }
571
572 #[test]
573 fn and_constraint_accepts_matching_conjunction() {
574 let values = values(&[0b1100, 0b1010, 0b1000]);
575 let constraint = AndConstraint::plain_abc(
576 [ValueIndex::constant(0)],
577 [ValueIndex::constant(1)],
578 [ValueIndex::constant(2)],
579 );
580
581 assert!(constraint.verify(&values).is_ok());
582 }
583
584 #[test]
585 fn and_constraint_rejects_mismatched_conjunction() {
586 let values = values(&[0b1100, 0b1010, 0b1001]);
588 let constraint = AndConstraint::plain_abc(
589 [ValueIndex::constant(0)],
590 [ValueIndex::constant(1)],
591 [ValueIndex::constant(2)],
592 );
593
594 match constraint.verify(&values).unwrap_err() {
595 ConstraintViolation::And { a, b, c, residue } => {
596 assert_eq!(a, 0b1100);
597 assert_eq!(b, 0b1010);
598 assert_eq!(c, 0b1001);
599 assert_eq!(residue, 0b0001);
600 }
601 other => panic!("wrong violation: {other:?}"),
602 }
603 }
604
605 #[test]
606 fn imul_constraint_accepts_both_halves_of_the_product() {
607 let a = 0x1234_5678_9abc_def0u64;
609 let b = 0x0fed_cba9_8765_4321u64;
610 let product = a as u128 * b as u128;
611 let values = values(&[a, b, product as u64, (product >> 64) as u64]);
612 let constraint = ImulConstraint([at(0), at(1), at(2), at(3)]);
613
614 assert!(constraint.verify(&values).is_ok());
615 }
616
617 #[test]
618 fn imul_constraint_rejects_a_dropped_high_word() {
619 let a = 0x1234_5678_9abc_def0u64;
621 let b = 0x0fed_cba9_8765_4321u64;
622 let product = a as u128 * b as u128;
623 let values = values(&[a, b, product as u64, 0]);
624 let constraint = ImulConstraint([at(0), at(1), at(2), at(3)]);
625
626 match constraint.verify(&values).unwrap_err() {
627 ConstraintViolation::Imul {
628 a: got_a,
629 b: got_b,
630 lo,
631 hi,
632 expected_lo,
633 expected_hi,
634 } => {
635 assert_eq!(got_a, a);
636 assert_eq!(got_b, b);
637 assert_eq!(lo, product as u64);
638 assert_eq!(hi, 0);
639 assert_eq!(expected_lo, product as u64);
640 assert_eq!(expected_hi, (product >> 64) as u64);
641 }
642 other => panic!("wrong violation: {other:?}"),
643 }
644 }
645
646 #[test]
647 fn bmul_constraint_accepts_the_field_product() {
648 let [a_lo, a_hi] = split(A);
649 let [b_lo, b_hi] = split(B);
650 let [c_lo, c_hi] = split(A_TIMES_B);
651 let values = values(&[a_lo, a_hi, b_lo, b_hi, c_lo, c_hi]);
652 let constraint = BmulConstraint([at(0), at(1), at(2), at(3), at(4), at(5)]);
653
654 assert!(constraint.verify(&values).is_ok());
655 }
656
657 #[test]
658 fn bmul_constraint_rejects_a_product_off_by_one_coefficient() {
659 let [a_lo, a_hi] = split(A);
660 let [b_lo, b_hi] = split(B);
661 let [c_lo, c_hi] = split(A_TIMES_B ^ 1);
662 let values = values(&[a_lo, a_hi, b_lo, b_hi, c_lo, c_hi]);
663 let constraint = BmulConstraint([at(0), at(1), at(2), at(3), at(4), at(5)]);
664
665 match constraint.verify(&values).unwrap_err() {
666 ConstraintViolation::Bmul { a, b, c, expected } => {
667 assert_eq!(a, A);
668 assert_eq!(b, B);
669 assert_eq!(c, A_TIMES_B ^ 1);
670 assert_eq!(expected, A_TIMES_B);
671 }
672 other => panic!("wrong violation: {other:?}"),
673 }
674 }
675
676 #[test]
677 fn violation_reports_the_kind_of_constraint_that_failed() {
678 assert_eq!(ConstraintViolation::Zero { val: 1 }.kind(), ConstraintKind::Zero);
681 assert_eq!(
682 ConstraintViolation::And {
683 a: 1,
684 b: 1,
685 c: 0,
686 residue: 1
687 }
688 .kind(),
689 ConstraintKind::And
690 );
691 assert_eq!(
692 ConstraintViolation::Imul {
693 a: 1,
694 b: 1,
695 lo: 0,
696 hi: 0,
697 expected_lo: 1,
698 expected_hi: 0
699 }
700 .kind(),
701 ConstraintKind::Imul
702 );
703 assert_eq!(
704 ConstraintViolation::Bmul {
705 a: 1,
706 b: 1,
707 c: 0,
708 expected: 1
709 }
710 .kind(),
711 ConstraintKind::Bmul
712 );
713 }
714}