1use std::collections::BTreeMap;
15
16use ed25519_dalek::{Signature, VerifyingKey};
17use serde::{Deserialize, Serialize};
18use sha2::{Digest as _, Sha256};
19
20use crate::circuit::{GateIndex, Slice};
21
22pub const MAX_EXPR_DEPTH: usize = 8;
25
26pub const MAX_EXPR_NODES: usize = 64;
28
29#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
34#[repr(C)]
35pub enum DigestAlgorithm {
36 Sha256,
37}
38
39impl DigestAlgorithm {
40 pub const fn output_bits(&self) -> u32 {
41 match self {
42 DigestAlgorithm::Sha256 => 256,
43 }
44 }
45
46 fn hash(&self, bytes: &[u8]) -> Vec<u8> {
47 match self {
48 DigestAlgorithm::Sha256 => Sha256::digest(bytes).to_vec(),
49 }
50 }
51}
52
53#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
58#[repr(C)]
59pub enum Encoding {
60 Base64UrlNoPad,
63}
64
65impl Encoding {
66 pub const fn decoded_bits(&self, encoded_bits: u32) -> Option<u32> {
73 match self {
74 Encoding::Base64UrlNoPad => {
75 if !encoded_bits.is_multiple_of(8) {
76 return None;
77 }
78 let chars = encoded_bits / 8;
79 if chars % 4 == 1 {
80 return None;
81 }
82 Some(8 * (3 * chars / 4))
85 }
86 }
87 }
88
89 fn decode(&self, bytes: &[u8]) -> Option<Vec<u8>> {
97 match self {
98 Encoding::Base64UrlNoPad => {
99 let mut out = Vec::with_capacity(3 * bytes.len() / 4);
100 for group in bytes.chunks(4) {
101 if group.len() == 1 {
102 return None;
103 }
104 let mut acc = 0u32;
105 for byte in group {
106 acc = (acc << 6) | u32::from(base64url_digit(*byte)?);
107 }
108 let whole_bytes = group.len() - 1;
111 let spare = 6 * group.len() - 8 * whole_bytes;
112 if acc & ((1 << spare) - 1) != 0 {
113 return None;
114 }
115 acc >>= spare;
116 for i in (0..whole_bytes).rev() {
117 out.push((acc >> (8 * i)) as u8);
118 }
119 }
120 Some(out)
121 }
122 }
123 }
124}
125
126const fn base64url_digit(byte: u8) -> Option<u8> {
129 match byte {
130 b'A'..=b'Z' => Some(byte - b'A'),
131 b'a'..=b'z' => Some(byte - b'a' + 26),
132 b'0'..=b'9' => Some(byte - b'0' + 52),
133 b'-' => Some(62),
134 b'_' => Some(63),
135 _ => None,
136 }
137}
138
139#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
140#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
148#[repr(C)]
149pub enum OnAmbiguity {
150 Fail,
165 TakeSmallestBits,
174}
175
176#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
177#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
192#[repr(C)]
193pub enum ConstraintExpr {
194 Slice(Slice),
196 Constant(Vec<u8>),
198 Concat(Vec<ConstraintExpr>),
200 Digest {
202 algorithm: DigestAlgorithm,
203 of: Box<ConstraintExpr>,
204 },
205 Wire(GateIndex),
207 Decode {
214 encoding: Encoding,
215 of: Box<ConstraintExpr>,
216 },
217}
218
219impl ConstraintExpr {
220 pub fn slices(&self) -> Vec<&Slice> {
222 match self {
223 ConstraintExpr::Slice(slice) => vec![slice],
224 ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => Vec::new(),
225 ConstraintExpr::Concat(parts) => {
226 parts.iter().flat_map(ConstraintExpr::slices).collect()
227 }
228 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.slices(),
229 }
230 }
231
232 pub fn wires(&self) -> Vec<GateIndex> {
234 match self {
235 ConstraintExpr::Wire(wire) => vec![*wire],
236 ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) => Vec::new(),
237 ConstraintExpr::Concat(parts) => parts.iter().flat_map(ConstraintExpr::wires).collect(),
238 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.wires(),
239 }
240 }
241
242 pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
244 match self {
245 ConstraintExpr::Wire(wire) => vec![wire],
246 ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) => Vec::new(),
247 ConstraintExpr::Concat(parts) => parts
248 .iter_mut()
249 .flat_map(ConstraintExpr::wires_mut)
250 .collect(),
251 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.wires_mut(),
252 }
253 }
254
255 pub fn depth(&self) -> usize {
256 match self {
257 ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => 1,
258 ConstraintExpr::Concat(parts) => {
259 1 + parts.iter().map(ConstraintExpr::depth).max().unwrap_or(0)
260 }
261 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => 1 + of.depth(),
262 }
263 }
264
265 pub fn node_count(&self) -> usize {
266 match self {
267 ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => 1,
268 ConstraintExpr::Concat(parts) => {
269 1 + parts.iter().map(ConstraintExpr::node_count).sum::<usize>()
270 }
271 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => {
272 1 + of.node_count()
273 }
274 }
275 }
276
277 pub fn static_len<F>(&self, wire_bits: &F) -> Option<u32>
283 where
284 F: Fn(GateIndex) -> Option<u32>,
285 {
286 match self {
287 ConstraintExpr::Slice(slice) => Some(slice.len()),
288 ConstraintExpr::Constant(bytes) => u32::try_from(8 * bytes.len()).ok(),
289 ConstraintExpr::Wire(wire) => wire_bits(*wire),
290 ConstraintExpr::Concat(parts) => parts.iter().try_fold(0u32, |acc, part| {
291 part.static_len(wire_bits)
292 .and_then(|len| acc.checked_add(len))
293 }),
294 ConstraintExpr::Digest { algorithm, .. } => Some(algorithm.output_bits()),
295 ConstraintExpr::Decode { encoding, of } => of
296 .static_len(wire_bits)
297 .and_then(|bits| encoding.decoded_bits(bits)),
298 }
299 }
300
301 pub fn eval(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> Option<Vec<bool>> {
314 match self {
315 ConstraintExpr::Slice(slice) => slice
316 .get_indices()
317 .into_iter()
318 .map(|i| bits.get(i as usize).copied())
319 .collect(),
320 ConstraintExpr::Constant(bytes) => Some(bytes_to_bits(bytes)),
321 ConstraintExpr::Wire(wire) => wires.get(wire).cloned(),
322 ConstraintExpr::Concat(parts) => {
323 let mut out = Vec::new();
324 for part in parts {
325 out.extend(part.eval(bits, wires)?);
326 }
327 Some(out)
328 }
329 ConstraintExpr::Digest { algorithm, of } => {
330 let inner = of.eval(bits, wires)?;
331 Some(bytes_to_bits(&algorithm.hash(&bits_to_bytes(&inner)?)))
332 }
333 ConstraintExpr::Decode { encoding, of } => {
334 let inner = of.eval(bits, wires)?;
335 Some(bytes_to_bits(&encoding.decode(&bits_to_bytes(&inner)?)?))
336 }
337 }
338 }
339}
340
341#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
346#[repr(C)]
347pub enum Relation {
348 AtMost,
350 AtLeast,
352}
353
354#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
359#[repr(C)]
360pub enum SignatureScheme {
361 Ed25519,
362}
363
364impl SignatureScheme {
365 pub const fn signature_bits(&self) -> u32 {
367 match self {
368 SignatureScheme::Ed25519 => 512,
369 }
370 }
371
372 pub const fn public_key_bits(&self) -> u32 {
374 match self {
375 SignatureScheme::Ed25519 => 256,
376 }
377 }
378}
379
380#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
381#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
391#[repr(C)]
392pub enum PlaintextBitConstraint {
393 Signature {
395 scheme: SignatureScheme,
396 signature: ConstraintExpr,
397 message: ConstraintExpr,
398 public_key: ConstraintExpr,
399 },
400 Equality {
402 bits: ConstraintExpr,
403 expected: ConstraintExpr,
404 },
405 Comparison {
423 relation: Relation,
424 lhs: ConstraintExpr,
425 rhs: ConstraintExpr,
426 },
427}
428
429impl PlaintextBitConstraint {
430 pub fn operands(&self) -> Vec<&ConstraintExpr> {
432 match self {
433 PlaintextBitConstraint::Signature {
434 signature,
435 message,
436 public_key,
437 ..
438 } => vec![signature, message, public_key],
439 PlaintextBitConstraint::Equality { bits, expected } => vec![bits, expected],
440 PlaintextBitConstraint::Comparison { lhs, rhs, .. } => vec![lhs, rhs],
441 }
442 }
443
444 fn operands_mut(&mut self) -> Vec<&mut ConstraintExpr> {
445 match self {
446 PlaintextBitConstraint::Signature {
447 signature,
448 message,
449 public_key,
450 ..
451 } => vec![signature, message, public_key],
452 PlaintextBitConstraint::Equality { bits, expected } => vec![bits, expected],
453 PlaintextBitConstraint::Comparison { lhs, rhs, .. } => vec![lhs, rhs],
454 }
455 }
456
457 pub fn covering_operands(&self) -> Vec<&ConstraintExpr> {
484 match self {
485 PlaintextBitConstraint::Signature {
486 signature, message, ..
487 } => vec![signature, message],
488 PlaintextBitConstraint::Equality { bits, expected } => {
489 match (bits.slices().is_empty(), expected.slices().is_empty()) {
490 (true, _) => vec![expected],
491 (_, true) => vec![bits],
492 _ => Vec::new(),
493 }
494 }
495 PlaintextBitConstraint::Comparison { .. } => Vec::new(),
497 }
498 }
499
500 pub fn slices(&self) -> Vec<&Slice> {
502 self.operands()
503 .into_iter()
504 .flat_map(ConstraintExpr::slices)
505 .collect()
506 }
507
508 pub fn wires(&self) -> Vec<GateIndex> {
510 self.operands()
511 .into_iter()
512 .flat_map(ConstraintExpr::wires)
513 .collect()
514 }
515
516 pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
518 self.operands_mut()
519 .into_iter()
520 .flat_map(ConstraintExpr::wires_mut)
521 .collect()
522 }
523
524 pub fn is_satisfied(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> bool {
530 match self {
531 PlaintextBitConstraint::Signature {
532 scheme,
533 signature,
534 message,
535 public_key,
536 } => {
537 let (Some(signature), Some(message), Some(public_key)) = (
538 signature.eval(bits, wires),
539 message.eval(bits, wires),
540 public_key.eval(bits, wires),
541 ) else {
542 return false;
543 };
544 let (Some(signature), Some(message), Some(public_key)) = (
545 bits_to_bytes(&signature),
546 bits_to_bytes(&message),
547 bits_to_bytes(&public_key),
548 ) else {
549 return false;
550 };
551 match scheme {
552 SignatureScheme::Ed25519 => {
553 let (Ok(public_key), Ok(signature)) = (
554 <[u8; 32]>::try_from(public_key),
555 <[u8; 64]>::try_from(signature),
556 ) else {
557 return false;
558 };
559 match VerifyingKey::from_bytes(&public_key) {
560 Ok(key) => key
561 .verify_strict(&message, &Signature::from_bytes(&signature))
562 .is_ok(),
563 Err(_) => false,
564 }
565 }
566 }
567 }
568 PlaintextBitConstraint::Equality {
569 bits: lhs,
570 expected,
571 } => match (lhs.eval(bits, wires), expected.eval(bits, wires)) {
572 (Some(lhs), Some(rhs)) => lhs == rhs,
573 _ => false,
574 },
575 PlaintextBitConstraint::Comparison { relation, lhs, rhs } => {
576 let (Some(lhs), Some(rhs)) = (lhs.eval(bits, wires), rhs.eval(bits, wires)) else {
577 return false;
578 };
579 let (Some(lhs), Some(rhs)) = (bits_to_bytes(&lhs), bits_to_bytes(&rhs)) else {
583 return false;
584 };
585 if lhs.len() != rhs.len() {
586 return false;
587 }
588 match relation {
589 Relation::AtMost => lhs <= rhs,
590 Relation::AtLeast => lhs >= rhs,
591 }
592 }
593 }
594 }
595}
596
597#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
605pub struct ConstraintClause(Vec<PlaintextBitConstraint>);
606
607impl ConstraintClause {
608 pub fn new(constraints: Vec<PlaintextBitConstraint>) -> Self {
609 Self(constraints)
610 }
611
612 pub fn constraints(&self) -> &[PlaintextBitConstraint] {
613 &self.0
614 }
615
616 pub fn is_empty(&self) -> bool {
617 self.0.is_empty()
618 }
619
620 pub fn wires(&self) -> Vec<GateIndex> {
622 self.0
623 .iter()
624 .flat_map(PlaintextBitConstraint::wires)
625 .collect()
626 }
627
628 pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
631 self.0
632 .iter_mut()
633 .flat_map(PlaintextBitConstraint::wires_mut)
634 .collect()
635 }
636
637 pub fn is_satisfied(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> bool {
641 self.0
642 .iter()
643 .all(|constraint| constraint.is_satisfied(bits, wires))
644 }
645}
646
647fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
649 bytes
650 .iter()
651 .flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
652 .collect()
653}
654
655fn bits_to_bytes(bits: &[bool]) -> Option<Vec<u8>> {
658 if !bits.len().is_multiple_of(8) {
659 return None;
660 }
661 Some(
662 bits.chunks(8)
663 .map(|chunk| {
664 chunk
665 .iter()
666 .enumerate()
667 .fold(0u8, |acc, (i, bit)| acc | (u8::from(*bit) << i))
668 })
669 .collect(),
670 )
671}
672
673#[cfg(test)]
674mod tests {
675 use ed25519_dalek::{Signer, SigningKey};
676
677 use super::*;
678
679 fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
680 bytes
681 .iter()
682 .flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
683 .collect()
684 }
685
686 fn signed_batch(key: &SigningKey, message: &[u8]) -> (Vec<bool>, Vec<bool>) {
688 let signature = key.sign(message);
689 let mut bits = bytes_to_bits(message);
690 bits.extend(bytes_to_bits(&signature.to_bytes()));
691 (bits, bytes_to_bits(key.verifying_key().as_bytes()))
692 }
693
694 fn slice(start: u32, size: u32) -> ConstraintExpr {
695 ConstraintExpr::Slice(Slice::range(start, size, 1).unwrap())
696 }
697
698 fn wires(public_key: &[bool]) -> BTreeMap<GateIndex, Vec<bool>> {
700 BTreeMap::from([(0, public_key.to_vec())])
701 }
702
703 fn signature_constraint(message_bytes: u32) -> PlaintextBitConstraint {
704 PlaintextBitConstraint::Signature {
705 scheme: SignatureScheme::Ed25519,
706 signature: slice(8 * message_bytes, 512),
707 message: slice(0, 8 * message_bytes),
708 public_key: ConstraintExpr::Wire(0),
709 }
710 }
711
712 #[test]
713 fn test_signature_constraint() {
714 let key = SigningKey::from_bytes(&[7u8; 32]);
715 let message = b"{\"price\":42}";
716 let (bits, public_key) = signed_batch(&key, message);
717 let constraint = signature_constraint(message.len() as u32);
718
719 assert!(constraint.is_satisfied(&bits, &wires(&public_key)));
720
721 let mut tampered = bits.clone();
723 tampered[3] = !tampered[3];
724 assert!(!constraint.is_satisfied(&tampered, &wires(&public_key)));
725
726 let other = SigningKey::from_bytes(&[9u8; 32]);
728 let other_key = bytes_to_bits(other.verifying_key().as_bytes());
729 assert!(!constraint.is_satisfied(&bits, &wires(&other_key)));
730 }
731
732 #[test]
733 fn test_signature_constraint_rejects_malformed_key() {
734 let key = SigningKey::from_bytes(&[7u8; 32]);
735 let message = b"{\"price\":42}";
736 let (bits, _) = signed_batch(&key, message);
737 let public_key = vec![true; 256];
739 assert!(
740 !signature_constraint(message.len() as u32).is_satisfied(&bits, &wires(&public_key))
741 );
742 }
743
744 #[test]
745 fn test_equality_constraint() {
746 let bits = bytes_to_bits(b"header:body");
747 let constraint = PlaintextBitConstraint::Equality {
748 bits: slice(0, 48),
749 expected: ConstraintExpr::Wire(0),
750 };
751 assert!(constraint.is_satisfied(&bits, &wires(&bytes_to_bits(b"header"))));
752 assert!(!constraint.is_satisfied(&bits, &wires(&bytes_to_bits(b"HEADER"))));
753 }
754
755 #[test]
756 fn test_bits_to_bytes_is_lsb_first() {
757 assert_eq!(
758 bits_to_bytes(&bytes_to_bits(&[0x01, 0x80, 0xa5])).unwrap(),
759 [0x01, 0x80, 0xa5]
760 );
761 assert_eq!(bits_to_bytes(&[true; 4]), None);
763 }
764
765 #[test]
769 fn test_signature_over_a_composed_message() {
770 let key = SigningKey::from_bytes(&[7u8; 32]);
771 let payload = b"{\"price\":42}";
772
773 let signed = {
775 let mut signed = vec![0x31u8];
776 signed.extend(Sha256::digest(payload));
777 signed
778 };
779 let mut bits = bytes_to_bits(payload);
780 bits.extend(bytes_to_bits(&key.sign(&signed).to_bytes()));
781
782 let constraint = PlaintextBitConstraint::Signature {
783 scheme: SignatureScheme::Ed25519,
784 signature: slice(8 * payload.len() as u32, 512),
785 message: ConstraintExpr::Concat(vec![
786 ConstraintExpr::Constant(vec![0x31]),
787 ConstraintExpr::Digest {
788 algorithm: DigestAlgorithm::Sha256,
789 of: Box::new(slice(0, 8 * payload.len() as u32)),
790 },
791 ]),
792 public_key: ConstraintExpr::Wire(0),
793 };
794 let public_key = bytes_to_bits(key.verifying_key().as_bytes());
795 assert!(constraint.is_satisfied(&bits, &wires(&public_key)));
796
797 let mut tampered = bits.clone();
800 tampered[3] = !tampered[3];
801 assert!(!constraint.is_satisfied(&tampered, &wires(&public_key)));
802 }
803
804 #[test]
807 fn test_equality_against_a_digest() {
808 let content = b"the content";
809 let mut bits = bytes_to_bits(content);
810 bits.extend(bytes_to_bits(&Sha256::digest(content)));
811
812 let constraint = PlaintextBitConstraint::Equality {
813 bits: slice(8 * content.len() as u32, 256),
814 expected: ConstraintExpr::Digest {
815 algorithm: DigestAlgorithm::Sha256,
816 of: Box::new(slice(0, 8 * content.len() as u32)),
817 },
818 };
819 assert!(constraint.is_satisfied(&bits, &BTreeMap::new()));
820
821 let mut tampered = bits.clone();
822 tampered[0] = !tampered[0];
823 assert!(!constraint.is_satisfied(&tampered, &BTreeMap::new()));
824 }
825
826 #[test]
827 fn test_static_len_adds_up() {
828 let expr = ConstraintExpr::Concat(vec![
829 ConstraintExpr::Constant(vec![0u8; 3]),
830 slice(0, 5),
831 ConstraintExpr::Digest {
832 algorithm: DigestAlgorithm::Sha256,
833 of: Box::new(ConstraintExpr::Wire(0)),
834 },
835 ConstraintExpr::Wire(1),
836 ]);
837 assert_eq!(
839 expr.static_len(&|wire| Some(wire + 7)),
840 Some(24 + 5 + 256 + 8)
841 );
842 assert_eq!(expr.static_len(&|_| None), None);
844 }
845
846 #[test]
847 fn test_eval_refuses_a_slice_past_the_batch() {
848 assert_eq!(slice(0, 16).eval(&[true; 8], &BTreeMap::new()), None);
849 }
850
851 #[test]
852 fn test_eval_refuses_a_digest_over_a_partial_byte() {
853 let expr = ConstraintExpr::Digest {
854 algorithm: DigestAlgorithm::Sha256,
855 of: Box::new(slice(0, 4)),
856 };
857 assert_eq!(expr.eval(&[true; 8], &BTreeMap::new()), None);
858 }
859
860 #[test]
861 fn test_depth_and_node_count() {
862 let expr = ConstraintExpr::Concat(vec![
863 slice(0, 1),
864 ConstraintExpr::Digest {
865 algorithm: DigestAlgorithm::Sha256,
866 of: Box::new(ConstraintExpr::Concat(vec![slice(1, 1), slice(2, 1)])),
867 },
868 ]);
869 assert_eq!(expr.depth(), 4);
870 assert_eq!(expr.node_count(), 6);
871 }
872
873 const JWS: &[u8] = b"eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3NTYxMDAwMDAsInB4IjoiMDAwMDAwNDI0MiJ9.\
884 ZdO1q9RcSfUrdq8UhqZYHVNBHp1OsDLgKG16bQDd-txuigbHkeuG-Bqbu335MrjoPL5Ssq6e\
885 3mJiJpXOTW6nCw";
886
887 const JWS_SIGNING_INPUT_BYTES: u32 = 20 + 1 + 48;
890 const JWS_SIGNATURE_BYTES: u32 = 86;
891
892 fn jws_constraint() -> PlaintextBitConstraint {
895 PlaintextBitConstraint::Signature {
896 scheme: SignatureScheme::Ed25519,
897 signature: ConstraintExpr::Decode {
898 encoding: Encoding::Base64UrlNoPad,
899 of: Box::new(slice(
900 8 * (JWS_SIGNING_INPUT_BYTES + 1),
901 8 * JWS_SIGNATURE_BYTES,
902 )),
903 },
904 message: slice(0, 8 * JWS_SIGNING_INPUT_BYTES),
905 public_key: ConstraintExpr::Wire(0),
906 }
907 }
908
909 #[test]
910 fn test_jws_verifies_through_a_decoded_signature() {
911 let key = SigningKey::from_bytes(&[7u8; 32]);
912 let public_key = bytes_to_bits(key.verifying_key().as_bytes());
913 assert_eq!(JWS.len(), 156);
914 let bits = bytes_to_bits(JWS);
915
916 assert!(jws_constraint().is_satisfied(&bits, &wires(&public_key)));
917
918 let mut tampered = JWS.to_vec();
921 tampered[60] ^= 0x01;
922 assert!(!jws_constraint().is_satisfied(&bytes_to_bits(&tampered), &wires(&public_key)));
923 }
924
925 #[test]
929 fn test_a_signature_that_does_not_decode_fails_the_constraint() {
930 let key = SigningKey::from_bytes(&[7u8; 32]);
931 let public_key = bytes_to_bits(key.verifying_key().as_bytes());
932
933 for (what, byte) in [
934 ("padding", b'='),
935 ("standard alphabet", b'+'),
936 ("junk", b'!'),
937 ] {
938 let mut body = JWS.to_vec();
939 body[80] = byte;
940 assert!(
941 !jws_constraint().is_satisfied(&bytes_to_bits(&body), &wires(&public_key)),
942 "a {what} character should not decode"
943 );
944 }
945 }
946
947 const JWS_STALE: &[u8] = b"eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3MDAwMDAwMDAsInB4IjoiMDAwMDAwNDI0\
949 MiJ9.Sp-OEJIDpCKuVEuTyxKkMZyNP-2pI86wCfWxN59KPONfJBNC4ILVEMSOdLhl\
950 kjPEu4XYEgzIyHNoHFbyHQ8cCg";
951
952 fn iat_window() -> ConstraintExpr {
962 ConstraintExpr::Decode {
963 encoding: Encoding::Base64UrlNoPad,
964 of: Box::new(slice(8 * 29, 8 * 16)),
965 }
966 }
967
968 fn iat_bound(relation: Relation, bound: &[u8]) -> PlaintextBitConstraint {
969 PlaintextBitConstraint::Comparison {
970 relation,
971 lhs: iat_window(),
972 rhs: ConstraintExpr::Constant(bound.to_vec()),
973 }
974 }
975
976 #[test]
980 fn test_a_comparison_bounds_a_text_timestamp() {
981 let not_before = iat_bound(Relation::AtLeast, b":1756000000,");
982 let not_after = iat_bound(Relation::AtMost, b":1757000000,");
983 let no_wires = BTreeMap::new();
985
986 let fresh = bytes_to_bits(JWS);
987 assert!(not_before.is_satisfied(&fresh, &no_wires));
988 assert!(not_after.is_satisfied(&fresh, &no_wires));
989
990 let key = SigningKey::from_bytes(&[7u8; 32]);
993 let stale = bytes_to_bits(JWS_STALE);
994 assert!(jws_constraint().is_satisfied(
995 &stale,
996 &wires(&bytes_to_bits(key.verifying_key().as_bytes()))
997 ));
998 assert!(!not_before.is_satisfied(&stale, &no_wires));
999 assert!(not_after.is_satisfied(&stale, &no_wires));
1000 }
1001
1002 #[test]
1009 fn test_a_comparison_is_over_bytes_not_the_bit_vector() {
1010 let lower = b"1756100001";
1011 let higher = b"1756100002";
1012 assert!(lower < higher, "as byte strings");
1013 assert!(
1014 bytes_to_bits(lower) > bytes_to_bits(higher),
1015 "and the other way as LSB-first bit vectors, which is the trap"
1016 );
1017
1018 let no_wires = BTreeMap::new();
1019 let bits = bytes_to_bits(lower);
1020 let at_most = PlaintextBitConstraint::Comparison {
1021 relation: Relation::AtMost,
1022 lhs: slice(0, 8 * 10),
1023 rhs: ConstraintExpr::Constant(higher.to_vec()),
1024 };
1025 let at_least = PlaintextBitConstraint::Comparison {
1026 relation: Relation::AtLeast,
1027 lhs: slice(0, 8 * 10),
1028 rhs: ConstraintExpr::Constant(higher.to_vec()),
1029 };
1030 assert!(
1031 at_most.is_satisfied(&bits, &no_wires),
1032 "1756100001 <= 1756100002"
1033 );
1034 assert!(!at_least.is_satisfied(&bits, &no_wires));
1035 }
1036
1037 #[test]
1041 fn test_a_comparison_refuses_unequal_widths() {
1042 let constraint = PlaintextBitConstraint::Comparison {
1043 relation: Relation::AtMost,
1044 lhs: slice(0, 8 * 4),
1045 rhs: ConstraintExpr::Constant(b"12345".to_vec()),
1046 };
1047 assert!(!constraint.is_satisfied(&bytes_to_bits(b"1234"), &BTreeMap::new()));
1048 }
1049
1050 #[test]
1052 fn test_only_pinned_operands_cover() {
1053 let n = |c: PlaintextBitConstraint| c.covering_operands().len();
1054
1055 assert_eq!(n(iat_bound(Relation::AtLeast, b":1756000000,")), 0);
1057
1058 assert_eq!(n(jws_constraint()), 2);
1061 assert!(jws_constraint()
1062 .covering_operands()
1063 .iter()
1064 .all(|operand| !matches!(operand, ConstraintExpr::Wire(_))));
1065
1066 assert_eq!(n(self_signed_constraint()), 2);
1070 let key_bits = 512 + 8 * 12;
1071 assert!(
1072 self_signed_constraint()
1073 .covering_operands()
1074 .iter()
1075 .flat_map(|operand| operand.slices())
1076 .flat_map(|slice| slice.get_indices())
1077 .all(|index| index < key_bits),
1078 "the key's own bytes must not be covered by its signature"
1079 );
1080
1081 assert_eq!(
1084 n(PlaintextBitConstraint::Equality {
1085 bits: slice(0, 8),
1086 expected: ConstraintExpr::Constant(vec![b'.']),
1087 }),
1088 1
1089 );
1090 assert_eq!(
1091 n(PlaintextBitConstraint::Equality {
1092 bits: ConstraintExpr::Constant(vec![b'.']),
1093 expected: slice(0, 8),
1094 }),
1095 1
1096 );
1097 assert_eq!(
1099 n(PlaintextBitConstraint::Equality {
1100 bits: slice(0, 8),
1101 expected: slice(8, 8),
1102 }),
1103 0
1104 );
1105 }
1106
1107 fn self_signed_constraint() -> PlaintextBitConstraint {
1112 PlaintextBitConstraint::Signature {
1113 scheme: SignatureScheme::Ed25519,
1114 signature: slice(0, 512),
1115 message: slice(512, 8 * 12),
1116 public_key: slice(512 + 8 * 12, 256),
1117 }
1118 }
1119
1120 #[test]
1122 fn test_a_self_signed_batch_satisfies_its_own_constraint() {
1123 let attacker = SigningKey::from_bytes(&[42u8; 32]);
1124 let message = b"whatever it li";
1125 let message = &message[..12];
1126 let signature = attacker.sign(message);
1127
1128 let mut bits = bytes_to_bits(&signature.to_bytes());
1129 bits.extend(bytes_to_bits(message));
1130 bits.extend(bytes_to_bits(attacker.verifying_key().as_bytes()));
1131
1132 assert!(
1133 self_signed_constraint().is_satisfied(&bits, &BTreeMap::new()),
1134 "a peer can always satisfy a clause whose key it supplies"
1135 );
1136 }
1137
1138 #[test]
1139 fn test_decoded_bits_is_exact_for_unpadded_base64() {
1140 let b64 = Encoding::Base64UrlNoPad;
1141 assert_eq!(b64.decoded_bits(8 * 86), Some(8 * 64));
1143 assert_eq!(b64.decoded_bits(8 * 4), Some(8 * 3));
1144 assert_eq!(b64.decoded_bits(8 * 2), Some(8));
1145 assert_eq!(b64.decoded_bits(8 * 3), Some(8 * 2));
1146 assert_eq!(b64.decoded_bits(8 * 5), None);
1149 assert_eq!(b64.decoded_bits(4), None);
1151 }
1152
1153 #[test]
1156 fn test_decode_rejects_non_canonical_encodings() {
1157 let b64 = Encoding::Base64UrlNoPad;
1158 assert_eq!(b64.decode(b"QQ"), Some(vec![b'A']));
1161 assert_eq!(b64.decode(b"QR"), None);
1162 assert_eq!(b64.decode(b"QQ=="), None);
1164 assert_eq!(b64.decode(b"QUJDRA"), Some(b"ABCD".to_vec()));
1168 assert_eq!(
1169 b64.decode(b"QUJDRAA"),
1170 Some(vec![b'A', b'B', b'C', b'D', 0])
1171 );
1172 assert_eq!(b64.decode(b"QUJDR"), None);
1173 assert_eq!(b64.decode(b"-_-_"), Some(vec![0xfb, 0xff, 0xbf]));
1175 assert_eq!(b64.decode(b"+/+/"), None);
1176 }
1177
1178 #[test]
1179 fn test_static_len_of_a_decode() {
1180 let expr = ConstraintExpr::Decode {
1181 encoding: Encoding::Base64UrlNoPad,
1182 of: Box::new(slice(0, 8 * 86)),
1183 };
1184 assert_eq!(expr.static_len(&|_| None), Some(512));
1185 assert_eq!(expr.depth(), 2);
1187 assert_eq!(expr.node_count(), 2);
1188 assert_eq!(expr.slices().len(), 1);
1189 }
1190
1191 mod circuit {
1192 use num_bigint::BigUint;
1193 use primitives::random::rng::test_rng;
1194
1195 use super::*;
1196 use crate::{
1197 circuit::{AlgebraicType, Circuit, Gate, Input},
1198 config::DefaultConfig as C,
1199 };
1200
1201 const MESSAGE_BYTES: u32 = 12;
1202 const BATCH_SIZE: u32 = 8 * MESSAGE_BYTES + 512;
1204
1205 fn plaintext_bits(circuit: &mut Circuit<C>, batch_size: u32) -> u32 {
1206 circuit
1207 .add_gate(Gate::Input(Input::Plaintext {
1208 algebraic_type: AlgebraicType::Bit,
1209 batch_size,
1210 }))
1211 .unwrap()
1212 }
1213
1214 fn build(clauses: Vec<ConstraintClause>) -> Result<Circuit<C>, String> {
1217 build_sized(BATCH_SIZE, clauses)
1218 }
1219
1220 fn build_sized(
1221 batch_size: u32,
1222 clauses: Vec<ConstraintClause>,
1223 ) -> Result<Circuit<C>, String> {
1224 let mut circuit = Circuit::<C>::new();
1225 plaintext_bits(&mut circuit, 256);
1226 let x = plaintext_bits(&mut circuit, batch_size);
1227 let gate = circuit
1228 .add_gate(Gate::ConstrainPlaintextBits {
1229 x,
1230 clauses,
1231 on_ambiguity: OnAmbiguity::Fail,
1232 })
1233 .map_err(|e| e.to_string())?;
1234 circuit.add_output(gate).unwrap();
1235 Ok(circuit)
1236 }
1237
1238 fn one(constraint: PlaintextBitConstraint) -> Vec<ConstraintClause> {
1240 vec![ConstraintClause::new(vec![constraint])]
1241 }
1242
1243 fn expect(data: &[bool], ok: bool) -> Vec<BigUint> {
1245 data.iter()
1246 .chain(std::iter::once(&ok))
1247 .map(|b| BigUint::from(*b))
1248 .collect()
1249 }
1250
1251 #[test]
1253 fn test_gate_output_is_the_input_plus_a_success_bit() {
1254 let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1255 let output = circuit.gate_output_unchecked(2);
1256 assert_eq!(output.get_batch_size(), BATCH_SIZE + 1);
1257 assert_eq!(output.get_type(), AlgebraicType::Bit);
1258 assert_eq!(
1259 output.get_form(),
1260 crate::circuit::ShareOrPlaintext::Plaintext
1261 );
1262 }
1263
1264 #[test]
1267 fn test_a_comparison_alone_does_not_cover_the_batch() {
1268 let err = build(one(PlaintextBitConstraint::Comparison {
1269 relation: Relation::AtLeast,
1270 lhs: slice(0, 8 * 4),
1271 rhs: ConstraintExpr::Constant(vec![0u8; 4]),
1272 }))
1273 .expect_err("a comparison covers nothing");
1274 assert!(err.contains("must be covered"), "{err}");
1275 }
1276
1277 #[test]
1280 fn test_a_comparison_beside_a_pinning_constraint_validates() {
1281 build(vec![ConstraintClause::new(vec![
1282 signature_constraint(MESSAGE_BYTES),
1283 PlaintextBitConstraint::Comparison {
1284 relation: Relation::AtLeast,
1285 lhs: slice(0, 8 * 4),
1286 rhs: ConstraintExpr::Constant(vec![0u8; 4]),
1287 },
1288 ])])
1289 .expect("pinned by the signature");
1290 }
1291
1292 #[test]
1295 fn test_validation_refuses_a_comparison_of_unequal_widths() {
1296 let err = build(vec![ConstraintClause::new(vec![
1297 signature_constraint(MESSAGE_BYTES),
1298 PlaintextBitConstraint::Comparison {
1299 relation: Relation::AtMost,
1300 lhs: slice(0, 8 * 4),
1301 rhs: ConstraintExpr::Constant(vec![0u8; 5]),
1302 },
1303 ])])
1304 .expect_err("four bytes against five");
1305 assert!(err.contains("same length"), "{err}");
1306 }
1307
1308 const KEY_IN_RESPONSE_BITS: u32 = 512 + 8 * MESSAGE_BYTES + 256;
1311
1312 #[test]
1316 fn test_a_self_signed_clause_is_refused() {
1317 let err = build_sized(KEY_IN_RESPONSE_BITS, one(self_signed_constraint()))
1318 .expect_err("a key sliced from the batch is anchored by nothing");
1319 assert!(err.contains("must be covered"), "{err}");
1320 }
1321
1322 #[test]
1327 fn test_a_key_from_the_response_pinned_by_another_constraint_validates() {
1328 let key_at = 512 + 8 * MESSAGE_BYTES;
1329 build_sized(
1330 KEY_IN_RESPONSE_BITS,
1331 vec![ConstraintClause::new(vec![
1332 self_signed_constraint(),
1333 PlaintextBitConstraint::Equality {
1334 bits: slice(key_at, 256),
1335 expected: ConstraintExpr::Constant(vec![0u8; 32]),
1336 },
1337 ])],
1338 )
1339 .expect("the key is pinned by the equality");
1340 }
1341
1342 #[test]
1343 fn test_mock_eval_passes_the_bits_through() {
1344 let key = SigningKey::from_bytes(&[7u8; 32]);
1345 let message = b"{\"price\":42}";
1346 assert_eq!(message.len() as u32, MESSAGE_BYTES);
1347 let (bits, public_key) = signed_batch(&key, message);
1348
1349 let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1350 let inputs = public_key
1351 .iter()
1352 .chain(bits.iter())
1353 .map(|b| BigUint::from(*b))
1354 .collect::<Vec<BigUint>>();
1355 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1356
1357 assert_eq!(output, expect(&bits, true));
1358 }
1359
1360 #[test]
1364 fn test_mock_eval_reports_an_unsatisfied_constraint() {
1365 let key = SigningKey::from_bytes(&[7u8; 32]);
1366 let (mut bits, public_key) = signed_batch(&key, b"{\"price\":42}");
1367 bits[0] = !bits[0];
1368
1369 let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1370 let inputs = public_key
1371 .iter()
1372 .chain(bits.iter())
1373 .map(|b| BigUint::from(*b))
1374 .collect::<Vec<BigUint>>();
1375 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1376
1377 assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1378 }
1379
1380 #[test]
1381 fn test_validation_rejects_uncovered_bits() {
1382 let err = build(one(PlaintextBitConstraint::Signature {
1384 scheme: SignatureScheme::Ed25519,
1385 signature: slice(8 * MESSAGE_BYTES, 512),
1386 message: slice(0, 8 * (MESSAGE_BYTES - 1)),
1387 public_key: ConstraintExpr::Wire(0),
1388 }))
1389 .unwrap_err();
1390 assert!(
1391 err.contains("clause 0") && err.contains("8 are not"),
1392 "{err}"
1393 );
1394 }
1395
1396 #[test]
1397 fn test_validation_rejects_no_clauses() {
1398 let err = build(vec![]).unwrap_err();
1399 assert!(err.contains("expected at least one clause"), "{err}");
1400 }
1401
1402 #[test]
1403 fn test_validation_rejects_an_empty_clause() {
1404 let err = build(vec![
1405 ConstraintClause::new(vec![signature_constraint(MESSAGE_BYTES)]),
1406 ConstraintClause::new(vec![]),
1407 ])
1408 .unwrap_err();
1409 assert!(err.contains("clause 1 is empty"), "{err}");
1410 }
1411
1412 #[test]
1413 fn test_validation_rejects_out_of_range_slice() {
1414 let err = build(one(signature_constraint(MESSAGE_BYTES + 1))).unwrap_err();
1415 assert!(err.contains("out-of-range"), "{err}");
1416 }
1417
1418 #[test]
1419 fn test_validation_rejects_mis_sized_signature() {
1420 let err = build(one(PlaintextBitConstraint::Signature {
1421 scheme: SignatureScheme::Ed25519,
1422 signature: slice(8 * MESSAGE_BYTES, 256),
1423 message: slice(0, 8 * MESSAGE_BYTES),
1424 public_key: ConstraintExpr::Wire(0),
1425 }))
1426 .unwrap_err();
1427 assert!(
1428 err.contains("expected a 512-bit Ed25519 signature"),
1429 "{err}"
1430 );
1431 }
1432
1433 #[test]
1434 fn test_validation_rejects_mis_sized_public_key() {
1435 let mut circuit = Circuit::<C>::new();
1436 plaintext_bits(&mut circuit, 128);
1438 let x = plaintext_bits(&mut circuit, BATCH_SIZE);
1439 let err = circuit
1440 .add_gate(Gate::ConstrainPlaintextBits {
1441 x,
1442 on_ambiguity: OnAmbiguity::Fail,
1443 clauses: one(signature_constraint(MESSAGE_BYTES)),
1444 })
1445 .unwrap_err()
1446 .to_string();
1447 assert!(
1448 err.contains("expected a 256-bit Ed25519 public key"),
1449 "{err}"
1450 );
1451 }
1452
1453 #[test]
1454 fn test_validation_rejects_mis_sized_equality_value() {
1455 let err = build(one(PlaintextBitConstraint::Equality {
1456 bits: slice(0, BATCH_SIZE),
1458 expected: ConstraintExpr::Wire(0),
1459 }))
1460 .unwrap_err();
1461 assert!(err.contains("must be the same length"), "{err}");
1462 }
1463
1464 fn build_two_keys() -> Circuit<C> {
1467 let mut circuit = Circuit::<C>::new();
1468 plaintext_bits(&mut circuit, 256);
1469 plaintext_bits(&mut circuit, 256);
1470 let x = plaintext_bits(&mut circuit, BATCH_SIZE);
1471 let clause = |public_key| {
1472 ConstraintClause::new(vec![PlaintextBitConstraint::Signature {
1473 scheme: SignatureScheme::Ed25519,
1474 signature: slice(8 * MESSAGE_BYTES, 512),
1475 message: slice(0, 8 * MESSAGE_BYTES),
1476 public_key: ConstraintExpr::Wire(public_key),
1477 }])
1478 };
1479 let gate = circuit
1480 .add_gate(Gate::ConstrainPlaintextBits {
1481 x,
1482 on_ambiguity: OnAmbiguity::Fail,
1483 clauses: vec![clause(0), clause(1)],
1484 })
1485 .unwrap();
1486 circuit.add_output(gate).unwrap();
1487 circuit
1488 }
1489
1490 #[test]
1492 fn test_a_later_clause_can_satisfy_the_gate() {
1493 let circuit = build_two_keys();
1494 let first = SigningKey::from_bytes(&[7u8; 32]);
1495 let second = SigningKey::from_bytes(&[9u8; 32]);
1496 let first_key = bytes_to_bits(first.verifying_key().as_bytes());
1497
1498 let (bits, second_key) = signed_batch(&second, b"{\"price\":42}");
1500 let inputs = first_key
1501 .iter()
1502 .chain(second_key.iter())
1503 .chain(bits.iter())
1504 .map(|b| BigUint::from(*b))
1505 .collect::<Vec<BigUint>>();
1506
1507 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1508 assert_eq!(output, expect(&bits, true));
1509 }
1510
1511 #[test]
1513 fn test_the_gate_reports_failure_only_when_all_clauses_fail() {
1514 let circuit = build_two_keys();
1515 let third = SigningKey::from_bytes(&[11u8; 32]);
1516 let (bits, _) = signed_batch(&third, b"{\"price\":42}");
1517 let first = bytes_to_bits(
1518 SigningKey::from_bytes(&[7u8; 32])
1519 .verifying_key()
1520 .as_bytes(),
1521 );
1522 let second = bytes_to_bits(
1523 SigningKey::from_bytes(&[9u8; 32])
1524 .verifying_key()
1525 .as_bytes(),
1526 );
1527 let inputs = first
1528 .iter()
1529 .chain(second.iter())
1530 .chain(bits.iter())
1531 .map(|b| BigUint::from(*b))
1532 .collect::<Vec<BigUint>>();
1533 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1534 assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1535 }
1536
1537 #[test]
1541 fn test_random_bits_evaluate_to_a_clean_failure() {
1542 use rand::Rng;
1543
1544 let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1545 let mut rng = test_rng();
1546 let inputs = (0..256 + BATCH_SIZE)
1547 .map(|_| BigUint::from(rng.gen::<bool>()))
1548 .collect::<Vec<BigUint>>();
1549
1550 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1553 assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1554 }
1555
1556 #[test]
1559 fn test_validation_requires_coverage_from_every_clause() {
1560 let err = build(vec![
1561 ConstraintClause::new(vec![signature_constraint(MESSAGE_BYTES)]),
1562 ConstraintClause::new(vec![PlaintextBitConstraint::Equality {
1563 bits: slice(0, 256),
1564 expected: ConstraintExpr::Wire(0),
1565 }]),
1566 ])
1567 .unwrap_err();
1568 assert!(err.contains("clause 1"), "{err}");
1569 assert!(err.contains("must be covered"), "{err}");
1570 }
1571
1572 #[test]
1573 fn test_gate_inputs_are_listed_clause_by_clause() {
1574 let circuit = build_two_keys();
1575 assert_eq!(circuit.gate_unchecked(3).get_inputs(), vec![2, 0, 1]);
1576 }
1577
1578 #[test]
1581 fn test_validation_rejects_an_over_deep_expression() {
1582 let mut expected = slice(0, 256);
1583 for _ in 0..MAX_EXPR_DEPTH {
1584 expected = ConstraintExpr::Digest {
1585 algorithm: DigestAlgorithm::Sha256,
1586 of: Box::new(expected),
1587 };
1588 }
1589 let err = build(one(PlaintextBitConstraint::Equality {
1590 bits: slice(0, 256),
1591 expected,
1592 }))
1593 .unwrap_err();
1594 assert!(err.contains("nests deeper than"), "{err}");
1595 }
1596
1597 #[test]
1598 fn test_validation_rejects_an_over_wide_expression() {
1599 let err = build(one(PlaintextBitConstraint::Equality {
1600 bits: ConstraintExpr::Concat(
1601 (0..MAX_EXPR_NODES as u32 + 1)
1602 .map(|i| slice(i, 1))
1603 .collect(),
1604 ),
1605 expected: ConstraintExpr::Wire(0),
1606 }))
1607 .unwrap_err();
1608 assert!(err.contains("more than"), "{err}");
1609 }
1610
1611 #[test]
1612 fn test_validation_rejects_shared_input() {
1613 let mut circuit = Circuit::<C>::new();
1614 plaintext_bits(&mut circuit, 256);
1615 let x = circuit
1616 .add_gate(Gate::Input(Input::Share {
1617 algebraic_type: AlgebraicType::Bit,
1618 batch_size: BATCH_SIZE,
1619 }))
1620 .unwrap();
1621 let err = circuit
1622 .add_gate(Gate::ConstrainPlaintextBits {
1623 x,
1624 on_ambiguity: OnAmbiguity::Fail,
1625 clauses: one(signature_constraint(MESSAGE_BYTES)),
1626 })
1627 .unwrap_err()
1628 .to_string();
1629 assert!(err.contains("is_plaintext"), "{err}");
1630 }
1631 }
1632}