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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
33#[repr(C)]
34pub enum DigestAlgorithm {
35 Sha256,
36}
37
38impl DigestAlgorithm {
39 pub const fn output_bits(&self) -> u32 {
40 match self {
41 DigestAlgorithm::Sha256 => 256,
42 }
43 }
44
45 fn hash(&self, bytes: &[u8]) -> Vec<u8> {
46 match self {
47 DigestAlgorithm::Sha256 => Sha256::digest(bytes).to_vec(),
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
56#[repr(C)]
57pub enum Encoding {
58 Base64UrlNoPad,
61}
62
63impl Encoding {
64 pub const fn decoded_bits(&self, encoded_bits: u32) -> Option<u32> {
71 match self {
72 Encoding::Base64UrlNoPad => {
73 if !encoded_bits.is_multiple_of(8) {
74 return None;
75 }
76 let chars = encoded_bits / 8;
77 if chars % 4 == 1 {
78 return None;
79 }
80 Some(8 * (3 * chars / 4))
83 }
84 }
85 }
86
87 fn decode(&self, bytes: &[u8]) -> Option<Vec<u8>> {
95 match self {
96 Encoding::Base64UrlNoPad => {
97 let mut out = Vec::with_capacity(3 * bytes.len() / 4);
98 for group in bytes.chunks(4) {
99 if group.len() == 1 {
100 return None;
101 }
102 let mut acc = 0u32;
103 for byte in group {
104 acc = (acc << 6) | u32::from(base64url_digit(*byte)?);
105 }
106 let whole_bytes = group.len() - 1;
109 let spare = 6 * group.len() - 8 * whole_bytes;
110 if acc & ((1 << spare) - 1) != 0 {
111 return None;
112 }
113 acc >>= spare;
114 for i in (0..whole_bytes).rev() {
115 out.push((acc >> (8 * i)) as u8);
116 }
117 }
118 Some(out)
119 }
120 }
121 }
122}
123
124const fn base64url_digit(byte: u8) -> Option<u8> {
127 match byte {
128 b'A'..=b'Z' => Some(byte - b'A'),
129 b'a'..=b'z' => Some(byte - b'a' + 26),
130 b'0'..=b'9' => Some(byte - b'0' + 52),
131 b'-' => Some(62),
132 b'_' => Some(63),
133 _ => None,
134 }
135}
136
137#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
145#[repr(C)]
146pub enum OnAmbiguity {
147 Fail,
162 TakeSmallestBits,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
188#[repr(C)]
189pub enum ConstraintExpr {
190 Slice(Slice),
192 Constant(Vec<u8>),
194 Concat(Vec<ConstraintExpr>),
196 Digest {
198 algorithm: DigestAlgorithm,
199 of: Box<ConstraintExpr>,
200 },
201 Wire(GateIndex),
203 Decode {
210 encoding: Encoding,
211 of: Box<ConstraintExpr>,
212 },
213}
214
215impl ConstraintExpr {
216 pub fn slices(&self) -> Vec<&Slice> {
218 match self {
219 ConstraintExpr::Slice(slice) => vec![slice],
220 ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => Vec::new(),
221 ConstraintExpr::Concat(parts) => {
222 parts.iter().flat_map(ConstraintExpr::slices).collect()
223 }
224 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.slices(),
225 }
226 }
227
228 pub fn wires(&self) -> Vec<GateIndex> {
230 match self {
231 ConstraintExpr::Wire(wire) => vec![*wire],
232 ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) => Vec::new(),
233 ConstraintExpr::Concat(parts) => parts.iter().flat_map(ConstraintExpr::wires).collect(),
234 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.wires(),
235 }
236 }
237
238 pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
240 match self {
241 ConstraintExpr::Wire(wire) => vec![wire],
242 ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) => Vec::new(),
243 ConstraintExpr::Concat(parts) => parts
244 .iter_mut()
245 .flat_map(ConstraintExpr::wires_mut)
246 .collect(),
247 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.wires_mut(),
248 }
249 }
250
251 pub fn depth(&self) -> usize {
252 match self {
253 ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => 1,
254 ConstraintExpr::Concat(parts) => {
255 1 + parts.iter().map(ConstraintExpr::depth).max().unwrap_or(0)
256 }
257 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => 1 + of.depth(),
258 }
259 }
260
261 pub fn node_count(&self) -> usize {
262 match self {
263 ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => 1,
264 ConstraintExpr::Concat(parts) => {
265 1 + parts.iter().map(ConstraintExpr::node_count).sum::<usize>()
266 }
267 ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => {
268 1 + of.node_count()
269 }
270 }
271 }
272
273 pub fn static_len<F>(&self, wire_bits: &F) -> Option<u32>
279 where
280 F: Fn(GateIndex) -> Option<u32>,
281 {
282 match self {
283 ConstraintExpr::Slice(slice) => Some(slice.len()),
284 ConstraintExpr::Constant(bytes) => u32::try_from(8 * bytes.len()).ok(),
285 ConstraintExpr::Wire(wire) => wire_bits(*wire),
286 ConstraintExpr::Concat(parts) => parts.iter().try_fold(0u32, |acc, part| {
287 part.static_len(wire_bits)
288 .and_then(|len| acc.checked_add(len))
289 }),
290 ConstraintExpr::Digest { algorithm, .. } => Some(algorithm.output_bits()),
291 ConstraintExpr::Decode { encoding, of } => of
292 .static_len(wire_bits)
293 .and_then(|bits| encoding.decoded_bits(bits)),
294 }
295 }
296
297 pub fn eval(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> Option<Vec<bool>> {
310 match self {
311 ConstraintExpr::Slice(slice) => slice
312 .get_indices()
313 .into_iter()
314 .map(|i| bits.get(i as usize).copied())
315 .collect(),
316 ConstraintExpr::Constant(bytes) => Some(bytes_to_bits(bytes)),
317 ConstraintExpr::Wire(wire) => wires.get(wire).cloned(),
318 ConstraintExpr::Concat(parts) => {
319 let mut out = Vec::new();
320 for part in parts {
321 out.extend(part.eval(bits, wires)?);
322 }
323 Some(out)
324 }
325 ConstraintExpr::Digest { algorithm, of } => {
326 let inner = of.eval(bits, wires)?;
327 Some(bytes_to_bits(&algorithm.hash(&bits_to_bytes(&inner)?)))
328 }
329 ConstraintExpr::Decode { encoding, of } => {
330 let inner = of.eval(bits, wires)?;
331 Some(bytes_to_bits(&encoding.decode(&bits_to_bytes(&inner)?)?))
332 }
333 }
334 }
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
341#[repr(C)]
342pub enum Relation {
343 AtMost,
345 AtLeast,
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
353#[repr(C)]
354pub enum SignatureScheme {
355 Ed25519,
356}
357
358impl SignatureScheme {
359 pub const fn signature_bits(&self) -> u32 {
361 match self {
362 SignatureScheme::Ed25519 => 512,
363 }
364 }
365
366 pub const fn public_key_bits(&self) -> u32 {
368 match self {
369 SignatureScheme::Ed25519 => 256,
370 }
371 }
372}
373
374#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
384#[repr(C)]
385pub enum PlaintextBitConstraint {
386 Signature {
388 scheme: SignatureScheme,
389 signature: ConstraintExpr,
390 message: ConstraintExpr,
391 public_key: ConstraintExpr,
392 },
393 Equality {
395 bits: ConstraintExpr,
396 expected: ConstraintExpr,
397 },
398 Comparison {
416 relation: Relation,
417 lhs: ConstraintExpr,
418 rhs: ConstraintExpr,
419 },
420}
421
422impl PlaintextBitConstraint {
423 pub fn operands(&self) -> Vec<&ConstraintExpr> {
425 match self {
426 PlaintextBitConstraint::Signature {
427 signature,
428 message,
429 public_key,
430 ..
431 } => vec![signature, message, public_key],
432 PlaintextBitConstraint::Equality { bits, expected } => vec![bits, expected],
433 PlaintextBitConstraint::Comparison { lhs, rhs, .. } => vec![lhs, rhs],
434 }
435 }
436
437 fn operands_mut(&mut self) -> Vec<&mut ConstraintExpr> {
438 match self {
439 PlaintextBitConstraint::Signature {
440 signature,
441 message,
442 public_key,
443 ..
444 } => vec![signature, message, public_key],
445 PlaintextBitConstraint::Equality { bits, expected } => vec![bits, expected],
446 PlaintextBitConstraint::Comparison { lhs, rhs, .. } => vec![lhs, rhs],
447 }
448 }
449
450 pub fn covering_operands(&self) -> Vec<&ConstraintExpr> {
477 match self {
478 PlaintextBitConstraint::Signature {
479 signature, message, ..
480 } => vec![signature, message],
481 PlaintextBitConstraint::Equality { bits, expected } => {
482 match (bits.slices().is_empty(), expected.slices().is_empty()) {
483 (true, _) => vec![expected],
484 (_, true) => vec![bits],
485 _ => Vec::new(),
486 }
487 }
488 PlaintextBitConstraint::Comparison { .. } => Vec::new(),
490 }
491 }
492
493 pub fn slices(&self) -> Vec<&Slice> {
495 self.operands()
496 .into_iter()
497 .flat_map(ConstraintExpr::slices)
498 .collect()
499 }
500
501 pub fn wires(&self) -> Vec<GateIndex> {
503 self.operands()
504 .into_iter()
505 .flat_map(ConstraintExpr::wires)
506 .collect()
507 }
508
509 pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
511 self.operands_mut()
512 .into_iter()
513 .flat_map(ConstraintExpr::wires_mut)
514 .collect()
515 }
516
517 pub fn is_satisfied(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> bool {
523 match self {
524 PlaintextBitConstraint::Signature {
525 scheme,
526 signature,
527 message,
528 public_key,
529 } => {
530 let (Some(signature), Some(message), Some(public_key)) = (
531 signature.eval(bits, wires),
532 message.eval(bits, wires),
533 public_key.eval(bits, wires),
534 ) else {
535 return false;
536 };
537 let (Some(signature), Some(message), Some(public_key)) = (
538 bits_to_bytes(&signature),
539 bits_to_bytes(&message),
540 bits_to_bytes(&public_key),
541 ) else {
542 return false;
543 };
544 match scheme {
545 SignatureScheme::Ed25519 => {
546 let (Ok(public_key), Ok(signature)) = (
547 <[u8; 32]>::try_from(public_key),
548 <[u8; 64]>::try_from(signature),
549 ) else {
550 return false;
551 };
552 match VerifyingKey::from_bytes(&public_key) {
553 Ok(key) => key
554 .verify_strict(&message, &Signature::from_bytes(&signature))
555 .is_ok(),
556 Err(_) => false,
557 }
558 }
559 }
560 }
561 PlaintextBitConstraint::Equality {
562 bits: lhs,
563 expected,
564 } => match (lhs.eval(bits, wires), expected.eval(bits, wires)) {
565 (Some(lhs), Some(rhs)) => lhs == rhs,
566 _ => false,
567 },
568 PlaintextBitConstraint::Comparison { relation, lhs, rhs } => {
569 let (Some(lhs), Some(rhs)) = (lhs.eval(bits, wires), rhs.eval(bits, wires)) else {
570 return false;
571 };
572 let (Some(lhs), Some(rhs)) = (bits_to_bytes(&lhs), bits_to_bytes(&rhs)) else {
576 return false;
577 };
578 if lhs.len() != rhs.len() {
579 return false;
580 }
581 match relation {
582 Relation::AtMost => lhs <= rhs,
583 Relation::AtLeast => lhs >= rhs,
584 }
585 }
586 }
587 }
588}
589
590#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
598pub struct ConstraintClause(Vec<PlaintextBitConstraint>);
599
600impl ConstraintClause {
601 pub fn new(constraints: Vec<PlaintextBitConstraint>) -> Self {
602 Self(constraints)
603 }
604
605 pub fn constraints(&self) -> &[PlaintextBitConstraint] {
606 &self.0
607 }
608
609 pub fn is_empty(&self) -> bool {
610 self.0.is_empty()
611 }
612
613 pub fn wires(&self) -> Vec<GateIndex> {
615 self.0
616 .iter()
617 .flat_map(PlaintextBitConstraint::wires)
618 .collect()
619 }
620
621 pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
624 self.0
625 .iter_mut()
626 .flat_map(PlaintextBitConstraint::wires_mut)
627 .collect()
628 }
629
630 pub fn is_satisfied(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> bool {
634 self.0
635 .iter()
636 .all(|constraint| constraint.is_satisfied(bits, wires))
637 }
638}
639
640fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
642 bytes
643 .iter()
644 .flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
645 .collect()
646}
647
648fn bits_to_bytes(bits: &[bool]) -> Option<Vec<u8>> {
651 if !bits.len().is_multiple_of(8) {
652 return None;
653 }
654 Some(
655 bits.chunks(8)
656 .map(|chunk| {
657 chunk
658 .iter()
659 .enumerate()
660 .fold(0u8, |acc, (i, bit)| acc | (u8::from(*bit) << i))
661 })
662 .collect(),
663 )
664}
665
666#[cfg(test)]
667mod tests {
668 use ed25519_dalek::{Signer, SigningKey};
669
670 use super::*;
671
672 fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
673 bytes
674 .iter()
675 .flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
676 .collect()
677 }
678
679 fn signed_batch(key: &SigningKey, message: &[u8]) -> (Vec<bool>, Vec<bool>) {
681 let signature = key.sign(message);
682 let mut bits = bytes_to_bits(message);
683 bits.extend(bytes_to_bits(&signature.to_bytes()));
684 (bits, bytes_to_bits(key.verifying_key().as_bytes()))
685 }
686
687 fn slice(start: u32, size: u32) -> ConstraintExpr {
688 ConstraintExpr::Slice(Slice::range(start, size, 1).unwrap())
689 }
690
691 fn wires(public_key: &[bool]) -> BTreeMap<GateIndex, Vec<bool>> {
693 BTreeMap::from([(0, public_key.to_vec())])
694 }
695
696 fn signature_constraint(message_bytes: u32) -> PlaintextBitConstraint {
697 PlaintextBitConstraint::Signature {
698 scheme: SignatureScheme::Ed25519,
699 signature: slice(8 * message_bytes, 512),
700 message: slice(0, 8 * message_bytes),
701 public_key: ConstraintExpr::Wire(0),
702 }
703 }
704
705 #[test]
706 fn test_signature_constraint() {
707 let key = SigningKey::from_bytes(&[7u8; 32]);
708 let message = b"{\"price\":42}";
709 let (bits, public_key) = signed_batch(&key, message);
710 let constraint = signature_constraint(message.len() as u32);
711
712 assert!(constraint.is_satisfied(&bits, &wires(&public_key)));
713
714 let mut tampered = bits.clone();
716 tampered[3] = !tampered[3];
717 assert!(!constraint.is_satisfied(&tampered, &wires(&public_key)));
718
719 let other = SigningKey::from_bytes(&[9u8; 32]);
721 let other_key = bytes_to_bits(other.verifying_key().as_bytes());
722 assert!(!constraint.is_satisfied(&bits, &wires(&other_key)));
723 }
724
725 #[test]
726 fn test_signature_constraint_rejects_malformed_key() {
727 let key = SigningKey::from_bytes(&[7u8; 32]);
728 let message = b"{\"price\":42}";
729 let (bits, _) = signed_batch(&key, message);
730 let public_key = vec![true; 256];
732 assert!(
733 !signature_constraint(message.len() as u32).is_satisfied(&bits, &wires(&public_key))
734 );
735 }
736
737 #[test]
738 fn test_equality_constraint() {
739 let bits = bytes_to_bits(b"header:body");
740 let constraint = PlaintextBitConstraint::Equality {
741 bits: slice(0, 48),
742 expected: ConstraintExpr::Wire(0),
743 };
744 assert!(constraint.is_satisfied(&bits, &wires(&bytes_to_bits(b"header"))));
745 assert!(!constraint.is_satisfied(&bits, &wires(&bytes_to_bits(b"HEADER"))));
746 }
747
748 #[test]
749 fn test_bits_to_bytes_is_lsb_first() {
750 assert_eq!(
751 bits_to_bytes(&bytes_to_bits(&[0x01, 0x80, 0xa5])).unwrap(),
752 [0x01, 0x80, 0xa5]
753 );
754 assert_eq!(bits_to_bytes(&[true; 4]), None);
756 }
757
758 #[test]
762 fn test_signature_over_a_composed_message() {
763 let key = SigningKey::from_bytes(&[7u8; 32]);
764 let payload = b"{\"price\":42}";
765
766 let signed = {
768 let mut signed = vec![0x31u8];
769 signed.extend(Sha256::digest(payload));
770 signed
771 };
772 let mut bits = bytes_to_bits(payload);
773 bits.extend(bytes_to_bits(&key.sign(&signed).to_bytes()));
774
775 let constraint = PlaintextBitConstraint::Signature {
776 scheme: SignatureScheme::Ed25519,
777 signature: slice(8 * payload.len() as u32, 512),
778 message: ConstraintExpr::Concat(vec![
779 ConstraintExpr::Constant(vec![0x31]),
780 ConstraintExpr::Digest {
781 algorithm: DigestAlgorithm::Sha256,
782 of: Box::new(slice(0, 8 * payload.len() as u32)),
783 },
784 ]),
785 public_key: ConstraintExpr::Wire(0),
786 };
787 let public_key = bytes_to_bits(key.verifying_key().as_bytes());
788 assert!(constraint.is_satisfied(&bits, &wires(&public_key)));
789
790 let mut tampered = bits.clone();
793 tampered[3] = !tampered[3];
794 assert!(!constraint.is_satisfied(&tampered, &wires(&public_key)));
795 }
796
797 #[test]
800 fn test_equality_against_a_digest() {
801 let content = b"the content";
802 let mut bits = bytes_to_bits(content);
803 bits.extend(bytes_to_bits(&Sha256::digest(content)));
804
805 let constraint = PlaintextBitConstraint::Equality {
806 bits: slice(8 * content.len() as u32, 256),
807 expected: ConstraintExpr::Digest {
808 algorithm: DigestAlgorithm::Sha256,
809 of: Box::new(slice(0, 8 * content.len() as u32)),
810 },
811 };
812 assert!(constraint.is_satisfied(&bits, &BTreeMap::new()));
813
814 let mut tampered = bits.clone();
815 tampered[0] = !tampered[0];
816 assert!(!constraint.is_satisfied(&tampered, &BTreeMap::new()));
817 }
818
819 #[test]
820 fn test_static_len_adds_up() {
821 let expr = ConstraintExpr::Concat(vec![
822 ConstraintExpr::Constant(vec![0u8; 3]),
823 slice(0, 5),
824 ConstraintExpr::Digest {
825 algorithm: DigestAlgorithm::Sha256,
826 of: Box::new(ConstraintExpr::Wire(0)),
827 },
828 ConstraintExpr::Wire(1),
829 ]);
830 assert_eq!(
832 expr.static_len(&|wire| Some(wire + 7)),
833 Some(24 + 5 + 256 + 8)
834 );
835 assert_eq!(expr.static_len(&|_| None), None);
837 }
838
839 #[test]
840 fn test_eval_refuses_a_slice_past_the_batch() {
841 assert_eq!(slice(0, 16).eval(&[true; 8], &BTreeMap::new()), None);
842 }
843
844 #[test]
845 fn test_eval_refuses_a_digest_over_a_partial_byte() {
846 let expr = ConstraintExpr::Digest {
847 algorithm: DigestAlgorithm::Sha256,
848 of: Box::new(slice(0, 4)),
849 };
850 assert_eq!(expr.eval(&[true; 8], &BTreeMap::new()), None);
851 }
852
853 #[test]
854 fn test_depth_and_node_count() {
855 let expr = ConstraintExpr::Concat(vec![
856 slice(0, 1),
857 ConstraintExpr::Digest {
858 algorithm: DigestAlgorithm::Sha256,
859 of: Box::new(ConstraintExpr::Concat(vec![slice(1, 1), slice(2, 1)])),
860 },
861 ]);
862 assert_eq!(expr.depth(), 4);
863 assert_eq!(expr.node_count(), 6);
864 }
865
866 const JWS: &[u8] = b"eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3NTYxMDAwMDAsInB4IjoiMDAwMDAwNDI0MiJ9.\
877 ZdO1q9RcSfUrdq8UhqZYHVNBHp1OsDLgKG16bQDd-txuigbHkeuG-Bqbu335MrjoPL5Ssq6e\
878 3mJiJpXOTW6nCw";
879
880 const JWS_SIGNING_INPUT_BYTES: u32 = 20 + 1 + 48;
883 const JWS_SIGNATURE_BYTES: u32 = 86;
884
885 fn jws_constraint() -> PlaintextBitConstraint {
888 PlaintextBitConstraint::Signature {
889 scheme: SignatureScheme::Ed25519,
890 signature: ConstraintExpr::Decode {
891 encoding: Encoding::Base64UrlNoPad,
892 of: Box::new(slice(
893 8 * (JWS_SIGNING_INPUT_BYTES + 1),
894 8 * JWS_SIGNATURE_BYTES,
895 )),
896 },
897 message: slice(0, 8 * JWS_SIGNING_INPUT_BYTES),
898 public_key: ConstraintExpr::Wire(0),
899 }
900 }
901
902 #[test]
903 fn test_jws_verifies_through_a_decoded_signature() {
904 let key = SigningKey::from_bytes(&[7u8; 32]);
905 let public_key = bytes_to_bits(key.verifying_key().as_bytes());
906 assert_eq!(JWS.len(), 156);
907 let bits = bytes_to_bits(JWS);
908
909 assert!(jws_constraint().is_satisfied(&bits, &wires(&public_key)));
910
911 let mut tampered = JWS.to_vec();
914 tampered[60] ^= 0x01;
915 assert!(!jws_constraint().is_satisfied(&bytes_to_bits(&tampered), &wires(&public_key)));
916 }
917
918 #[test]
922 fn test_a_signature_that_does_not_decode_fails_the_constraint() {
923 let key = SigningKey::from_bytes(&[7u8; 32]);
924 let public_key = bytes_to_bits(key.verifying_key().as_bytes());
925
926 for (what, byte) in [
927 ("padding", b'='),
928 ("standard alphabet", b'+'),
929 ("junk", b'!'),
930 ] {
931 let mut body = JWS.to_vec();
932 body[80] = byte;
933 assert!(
934 !jws_constraint().is_satisfied(&bytes_to_bits(&body), &wires(&public_key)),
935 "a {what} character should not decode"
936 );
937 }
938 }
939
940 const JWS_STALE: &[u8] = b"eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3MDAwMDAwMDAsInB4IjoiMDAwMDAwNDI0\
942 MiJ9.Sp-OEJIDpCKuVEuTyxKkMZyNP-2pI86wCfWxN59KPONfJBNC4ILVEMSOdLhl\
943 kjPEu4XYEgzIyHNoHFbyHQ8cCg";
944
945 fn iat_window() -> ConstraintExpr {
955 ConstraintExpr::Decode {
956 encoding: Encoding::Base64UrlNoPad,
957 of: Box::new(slice(8 * 29, 8 * 16)),
958 }
959 }
960
961 fn iat_bound(relation: Relation, bound: &[u8]) -> PlaintextBitConstraint {
962 PlaintextBitConstraint::Comparison {
963 relation,
964 lhs: iat_window(),
965 rhs: ConstraintExpr::Constant(bound.to_vec()),
966 }
967 }
968
969 #[test]
973 fn test_a_comparison_bounds_a_text_timestamp() {
974 let not_before = iat_bound(Relation::AtLeast, b":1756000000,");
975 let not_after = iat_bound(Relation::AtMost, b":1757000000,");
976 let no_wires = BTreeMap::new();
978
979 let fresh = bytes_to_bits(JWS);
980 assert!(not_before.is_satisfied(&fresh, &no_wires));
981 assert!(not_after.is_satisfied(&fresh, &no_wires));
982
983 let key = SigningKey::from_bytes(&[7u8; 32]);
986 let stale = bytes_to_bits(JWS_STALE);
987 assert!(jws_constraint().is_satisfied(
988 &stale,
989 &wires(&bytes_to_bits(key.verifying_key().as_bytes()))
990 ));
991 assert!(!not_before.is_satisfied(&stale, &no_wires));
992 assert!(not_after.is_satisfied(&stale, &no_wires));
993 }
994
995 #[test]
1002 fn test_a_comparison_is_over_bytes_not_the_bit_vector() {
1003 let lower = b"1756100001";
1004 let higher = b"1756100002";
1005 assert!(lower < higher, "as byte strings");
1006 assert!(
1007 bytes_to_bits(lower) > bytes_to_bits(higher),
1008 "and the other way as LSB-first bit vectors, which is the trap"
1009 );
1010
1011 let no_wires = BTreeMap::new();
1012 let bits = bytes_to_bits(lower);
1013 let at_most = PlaintextBitConstraint::Comparison {
1014 relation: Relation::AtMost,
1015 lhs: slice(0, 8 * 10),
1016 rhs: ConstraintExpr::Constant(higher.to_vec()),
1017 };
1018 let at_least = PlaintextBitConstraint::Comparison {
1019 relation: Relation::AtLeast,
1020 lhs: slice(0, 8 * 10),
1021 rhs: ConstraintExpr::Constant(higher.to_vec()),
1022 };
1023 assert!(
1024 at_most.is_satisfied(&bits, &no_wires),
1025 "1756100001 <= 1756100002"
1026 );
1027 assert!(!at_least.is_satisfied(&bits, &no_wires));
1028 }
1029
1030 #[test]
1034 fn test_a_comparison_refuses_unequal_widths() {
1035 let constraint = PlaintextBitConstraint::Comparison {
1036 relation: Relation::AtMost,
1037 lhs: slice(0, 8 * 4),
1038 rhs: ConstraintExpr::Constant(b"12345".to_vec()),
1039 };
1040 assert!(!constraint.is_satisfied(&bytes_to_bits(b"1234"), &BTreeMap::new()));
1041 }
1042
1043 #[test]
1045 fn test_only_pinned_operands_cover() {
1046 let n = |c: PlaintextBitConstraint| c.covering_operands().len();
1047
1048 assert_eq!(n(iat_bound(Relation::AtLeast, b":1756000000,")), 0);
1050
1051 assert_eq!(n(jws_constraint()), 2);
1054 assert!(jws_constraint()
1055 .covering_operands()
1056 .iter()
1057 .all(|operand| !matches!(operand, ConstraintExpr::Wire(_))));
1058
1059 assert_eq!(n(self_signed_constraint()), 2);
1063 let key_bits = 512 + 8 * 12;
1064 assert!(
1065 self_signed_constraint()
1066 .covering_operands()
1067 .iter()
1068 .flat_map(|operand| operand.slices())
1069 .flat_map(|slice| slice.get_indices())
1070 .all(|index| index < key_bits),
1071 "the key's own bytes must not be covered by its signature"
1072 );
1073
1074 assert_eq!(
1077 n(PlaintextBitConstraint::Equality {
1078 bits: slice(0, 8),
1079 expected: ConstraintExpr::Constant(vec![b'.']),
1080 }),
1081 1
1082 );
1083 assert_eq!(
1084 n(PlaintextBitConstraint::Equality {
1085 bits: ConstraintExpr::Constant(vec![b'.']),
1086 expected: slice(0, 8),
1087 }),
1088 1
1089 );
1090 assert_eq!(
1092 n(PlaintextBitConstraint::Equality {
1093 bits: slice(0, 8),
1094 expected: slice(8, 8),
1095 }),
1096 0
1097 );
1098 }
1099
1100 fn self_signed_constraint() -> PlaintextBitConstraint {
1105 PlaintextBitConstraint::Signature {
1106 scheme: SignatureScheme::Ed25519,
1107 signature: slice(0, 512),
1108 message: slice(512, 8 * 12),
1109 public_key: slice(512 + 8 * 12, 256),
1110 }
1111 }
1112
1113 #[test]
1115 fn test_a_self_signed_batch_satisfies_its_own_constraint() {
1116 let attacker = SigningKey::from_bytes(&[42u8; 32]);
1117 let message = b"whatever it li";
1118 let message = &message[..12];
1119 let signature = attacker.sign(message);
1120
1121 let mut bits = bytes_to_bits(&signature.to_bytes());
1122 bits.extend(bytes_to_bits(message));
1123 bits.extend(bytes_to_bits(attacker.verifying_key().as_bytes()));
1124
1125 assert!(
1126 self_signed_constraint().is_satisfied(&bits, &BTreeMap::new()),
1127 "a peer can always satisfy a clause whose key it supplies"
1128 );
1129 }
1130
1131 #[test]
1132 fn test_decoded_bits_is_exact_for_unpadded_base64() {
1133 let b64 = Encoding::Base64UrlNoPad;
1134 assert_eq!(b64.decoded_bits(8 * 86), Some(8 * 64));
1136 assert_eq!(b64.decoded_bits(8 * 4), Some(8 * 3));
1137 assert_eq!(b64.decoded_bits(8 * 2), Some(8));
1138 assert_eq!(b64.decoded_bits(8 * 3), Some(8 * 2));
1139 assert_eq!(b64.decoded_bits(8 * 5), None);
1142 assert_eq!(b64.decoded_bits(4), None);
1144 }
1145
1146 #[test]
1149 fn test_decode_rejects_non_canonical_encodings() {
1150 let b64 = Encoding::Base64UrlNoPad;
1151 assert_eq!(b64.decode(b"QQ"), Some(vec![b'A']));
1154 assert_eq!(b64.decode(b"QR"), None);
1155 assert_eq!(b64.decode(b"QQ=="), None);
1157 assert_eq!(b64.decode(b"QUJDRA"), Some(b"ABCD".to_vec()));
1161 assert_eq!(
1162 b64.decode(b"QUJDRAA"),
1163 Some(vec![b'A', b'B', b'C', b'D', 0])
1164 );
1165 assert_eq!(b64.decode(b"QUJDR"), None);
1166 assert_eq!(b64.decode(b"-_-_"), Some(vec![0xfb, 0xff, 0xbf]));
1168 assert_eq!(b64.decode(b"+/+/"), None);
1169 }
1170
1171 #[test]
1172 fn test_static_len_of_a_decode() {
1173 let expr = ConstraintExpr::Decode {
1174 encoding: Encoding::Base64UrlNoPad,
1175 of: Box::new(slice(0, 8 * 86)),
1176 };
1177 assert_eq!(expr.static_len(&|_| None), Some(512));
1178 assert_eq!(expr.depth(), 2);
1180 assert_eq!(expr.node_count(), 2);
1181 assert_eq!(expr.slices().len(), 1);
1182 }
1183
1184 mod circuit {
1185 use num_bigint::BigUint;
1186 use primitives::random::rng::test_rng;
1187
1188 use super::*;
1189 use crate::{
1190 circuit::{AlgebraicType, Circuit, Gate, Input},
1191 config::DefaultConfig as C,
1192 };
1193
1194 const MESSAGE_BYTES: u32 = 12;
1195 const BATCH_SIZE: u32 = 8 * MESSAGE_BYTES + 512;
1197
1198 fn plaintext_bits(circuit: &mut Circuit<C>, batch_size: u32) -> u32 {
1199 circuit
1200 .add_gate(Gate::Input(Input::Plaintext {
1201 algebraic_type: AlgebraicType::Bit,
1202 batch_size,
1203 }))
1204 .unwrap()
1205 }
1206
1207 fn build(clauses: Vec<ConstraintClause>) -> Result<Circuit<C>, String> {
1210 build_sized(BATCH_SIZE, clauses)
1211 }
1212
1213 fn build_sized(
1214 batch_size: u32,
1215 clauses: Vec<ConstraintClause>,
1216 ) -> Result<Circuit<C>, String> {
1217 let mut circuit = Circuit::<C>::new();
1218 plaintext_bits(&mut circuit, 256);
1219 let x = plaintext_bits(&mut circuit, batch_size);
1220 let gate = circuit
1221 .add_gate(Gate::ConstrainPlaintextBits {
1222 x,
1223 clauses,
1224 on_ambiguity: OnAmbiguity::Fail,
1225 })
1226 .map_err(|e| e.to_string())?;
1227 circuit.add_output(gate).unwrap();
1228 Ok(circuit)
1229 }
1230
1231 fn one(constraint: PlaintextBitConstraint) -> Vec<ConstraintClause> {
1233 vec![ConstraintClause::new(vec![constraint])]
1234 }
1235
1236 fn expect(data: &[bool], ok: bool) -> Vec<BigUint> {
1238 data.iter()
1239 .chain(std::iter::once(&ok))
1240 .map(|b| BigUint::from(*b))
1241 .collect()
1242 }
1243
1244 #[test]
1246 fn test_gate_output_is_the_input_plus_a_success_bit() {
1247 let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1248 let output = circuit.gate_output_unchecked(2);
1249 assert_eq!(output.get_batch_size(), BATCH_SIZE + 1);
1250 assert_eq!(output.get_type(), AlgebraicType::Bit);
1251 assert_eq!(
1252 output.get_form(),
1253 crate::circuit::ShareOrPlaintext::Plaintext
1254 );
1255 }
1256
1257 #[test]
1260 fn test_a_comparison_alone_does_not_cover_the_batch() {
1261 let err = build(one(PlaintextBitConstraint::Comparison {
1262 relation: Relation::AtLeast,
1263 lhs: slice(0, 8 * 4),
1264 rhs: ConstraintExpr::Constant(vec![0u8; 4]),
1265 }))
1266 .expect_err("a comparison covers nothing");
1267 assert!(err.contains("must be covered"), "{err}");
1268 }
1269
1270 #[test]
1273 fn test_a_comparison_beside_a_pinning_constraint_validates() {
1274 build(vec![ConstraintClause::new(vec![
1275 signature_constraint(MESSAGE_BYTES),
1276 PlaintextBitConstraint::Comparison {
1277 relation: Relation::AtLeast,
1278 lhs: slice(0, 8 * 4),
1279 rhs: ConstraintExpr::Constant(vec![0u8; 4]),
1280 },
1281 ])])
1282 .expect("pinned by the signature");
1283 }
1284
1285 #[test]
1288 fn test_validation_refuses_a_comparison_of_unequal_widths() {
1289 let err = build(vec![ConstraintClause::new(vec![
1290 signature_constraint(MESSAGE_BYTES),
1291 PlaintextBitConstraint::Comparison {
1292 relation: Relation::AtMost,
1293 lhs: slice(0, 8 * 4),
1294 rhs: ConstraintExpr::Constant(vec![0u8; 5]),
1295 },
1296 ])])
1297 .expect_err("four bytes against five");
1298 assert!(err.contains("same length"), "{err}");
1299 }
1300
1301 const KEY_IN_RESPONSE_BITS: u32 = 512 + 8 * MESSAGE_BYTES + 256;
1304
1305 #[test]
1309 fn test_a_self_signed_clause_is_refused() {
1310 let err = build_sized(KEY_IN_RESPONSE_BITS, one(self_signed_constraint()))
1311 .expect_err("a key sliced from the batch is anchored by nothing");
1312 assert!(err.contains("must be covered"), "{err}");
1313 }
1314
1315 #[test]
1320 fn test_a_key_from_the_response_pinned_by_another_constraint_validates() {
1321 let key_at = 512 + 8 * MESSAGE_BYTES;
1322 build_sized(
1323 KEY_IN_RESPONSE_BITS,
1324 vec![ConstraintClause::new(vec![
1325 self_signed_constraint(),
1326 PlaintextBitConstraint::Equality {
1327 bits: slice(key_at, 256),
1328 expected: ConstraintExpr::Constant(vec![0u8; 32]),
1329 },
1330 ])],
1331 )
1332 .expect("the key is pinned by the equality");
1333 }
1334
1335 #[test]
1336 fn test_mock_eval_passes_the_bits_through() {
1337 let key = SigningKey::from_bytes(&[7u8; 32]);
1338 let message = b"{\"price\":42}";
1339 assert_eq!(message.len() as u32, MESSAGE_BYTES);
1340 let (bits, public_key) = signed_batch(&key, message);
1341
1342 let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1343 let inputs = public_key
1344 .iter()
1345 .chain(bits.iter())
1346 .map(|b| BigUint::from(*b))
1347 .collect::<Vec<BigUint>>();
1348 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1349
1350 assert_eq!(output, expect(&bits, true));
1351 }
1352
1353 #[test]
1357 fn test_mock_eval_reports_an_unsatisfied_constraint() {
1358 let key = SigningKey::from_bytes(&[7u8; 32]);
1359 let (mut bits, public_key) = signed_batch(&key, b"{\"price\":42}");
1360 bits[0] = !bits[0];
1361
1362 let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1363 let inputs = public_key
1364 .iter()
1365 .chain(bits.iter())
1366 .map(|b| BigUint::from(*b))
1367 .collect::<Vec<BigUint>>();
1368 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1369
1370 assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1371 }
1372
1373 #[test]
1374 fn test_validation_rejects_uncovered_bits() {
1375 let err = build(one(PlaintextBitConstraint::Signature {
1377 scheme: SignatureScheme::Ed25519,
1378 signature: slice(8 * MESSAGE_BYTES, 512),
1379 message: slice(0, 8 * (MESSAGE_BYTES - 1)),
1380 public_key: ConstraintExpr::Wire(0),
1381 }))
1382 .unwrap_err();
1383 assert!(
1384 err.contains("clause 0") && err.contains("8 are not"),
1385 "{err}"
1386 );
1387 }
1388
1389 #[test]
1390 fn test_validation_rejects_no_clauses() {
1391 let err = build(vec![]).unwrap_err();
1392 assert!(err.contains("expected at least one clause"), "{err}");
1393 }
1394
1395 #[test]
1396 fn test_validation_rejects_an_empty_clause() {
1397 let err = build(vec![
1398 ConstraintClause::new(vec![signature_constraint(MESSAGE_BYTES)]),
1399 ConstraintClause::new(vec![]),
1400 ])
1401 .unwrap_err();
1402 assert!(err.contains("clause 1 is empty"), "{err}");
1403 }
1404
1405 #[test]
1406 fn test_validation_rejects_out_of_range_slice() {
1407 let err = build(one(signature_constraint(MESSAGE_BYTES + 1))).unwrap_err();
1408 assert!(err.contains("out-of-range"), "{err}");
1409 }
1410
1411 #[test]
1412 fn test_validation_rejects_mis_sized_signature() {
1413 let err = build(one(PlaintextBitConstraint::Signature {
1414 scheme: SignatureScheme::Ed25519,
1415 signature: slice(8 * MESSAGE_BYTES, 256),
1416 message: slice(0, 8 * MESSAGE_BYTES),
1417 public_key: ConstraintExpr::Wire(0),
1418 }))
1419 .unwrap_err();
1420 assert!(
1421 err.contains("expected a 512-bit Ed25519 signature"),
1422 "{err}"
1423 );
1424 }
1425
1426 #[test]
1427 fn test_validation_rejects_mis_sized_public_key() {
1428 let mut circuit = Circuit::<C>::new();
1429 plaintext_bits(&mut circuit, 128);
1431 let x = plaintext_bits(&mut circuit, BATCH_SIZE);
1432 let err = circuit
1433 .add_gate(Gate::ConstrainPlaintextBits {
1434 x,
1435 on_ambiguity: OnAmbiguity::Fail,
1436 clauses: one(signature_constraint(MESSAGE_BYTES)),
1437 })
1438 .unwrap_err()
1439 .to_string();
1440 assert!(
1441 err.contains("expected a 256-bit Ed25519 public key"),
1442 "{err}"
1443 );
1444 }
1445
1446 #[test]
1447 fn test_validation_rejects_mis_sized_equality_value() {
1448 let err = build(one(PlaintextBitConstraint::Equality {
1449 bits: slice(0, BATCH_SIZE),
1451 expected: ConstraintExpr::Wire(0),
1452 }))
1453 .unwrap_err();
1454 assert!(err.contains("must be the same length"), "{err}");
1455 }
1456
1457 fn build_two_keys() -> Circuit<C> {
1460 let mut circuit = Circuit::<C>::new();
1461 plaintext_bits(&mut circuit, 256);
1462 plaintext_bits(&mut circuit, 256);
1463 let x = plaintext_bits(&mut circuit, BATCH_SIZE);
1464 let clause = |public_key| {
1465 ConstraintClause::new(vec![PlaintextBitConstraint::Signature {
1466 scheme: SignatureScheme::Ed25519,
1467 signature: slice(8 * MESSAGE_BYTES, 512),
1468 message: slice(0, 8 * MESSAGE_BYTES),
1469 public_key: ConstraintExpr::Wire(public_key),
1470 }])
1471 };
1472 let gate = circuit
1473 .add_gate(Gate::ConstrainPlaintextBits {
1474 x,
1475 on_ambiguity: OnAmbiguity::Fail,
1476 clauses: vec![clause(0), clause(1)],
1477 })
1478 .unwrap();
1479 circuit.add_output(gate).unwrap();
1480 circuit
1481 }
1482
1483 #[test]
1485 fn test_a_later_clause_can_satisfy_the_gate() {
1486 let circuit = build_two_keys();
1487 let first = SigningKey::from_bytes(&[7u8; 32]);
1488 let second = SigningKey::from_bytes(&[9u8; 32]);
1489 let first_key = bytes_to_bits(first.verifying_key().as_bytes());
1490
1491 let (bits, second_key) = signed_batch(&second, b"{\"price\":42}");
1493 let inputs = first_key
1494 .iter()
1495 .chain(second_key.iter())
1496 .chain(bits.iter())
1497 .map(|b| BigUint::from(*b))
1498 .collect::<Vec<BigUint>>();
1499
1500 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1501 assert_eq!(output, expect(&bits, true));
1502 }
1503
1504 #[test]
1506 fn test_the_gate_reports_failure_only_when_all_clauses_fail() {
1507 let circuit = build_two_keys();
1508 let third = SigningKey::from_bytes(&[11u8; 32]);
1509 let (bits, _) = signed_batch(&third, b"{\"price\":42}");
1510 let first = bytes_to_bits(
1511 SigningKey::from_bytes(&[7u8; 32])
1512 .verifying_key()
1513 .as_bytes(),
1514 );
1515 let second = bytes_to_bits(
1516 SigningKey::from_bytes(&[9u8; 32])
1517 .verifying_key()
1518 .as_bytes(),
1519 );
1520 let inputs = first
1521 .iter()
1522 .chain(second.iter())
1523 .chain(bits.iter())
1524 .map(|b| BigUint::from(*b))
1525 .collect::<Vec<BigUint>>();
1526 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1527 assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1528 }
1529
1530 #[test]
1534 fn test_random_bits_evaluate_to_a_clean_failure() {
1535 use rand::Rng;
1536
1537 let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1538 let mut rng = test_rng();
1539 let inputs = (0..256 + BATCH_SIZE)
1540 .map(|_| BigUint::from(rng.gen::<bool>()))
1541 .collect::<Vec<BigUint>>();
1542
1543 let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1546 assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1547 }
1548
1549 #[test]
1552 fn test_validation_requires_coverage_from_every_clause() {
1553 let err = build(vec![
1554 ConstraintClause::new(vec![signature_constraint(MESSAGE_BYTES)]),
1555 ConstraintClause::new(vec![PlaintextBitConstraint::Equality {
1556 bits: slice(0, 256),
1557 expected: ConstraintExpr::Wire(0),
1558 }]),
1559 ])
1560 .unwrap_err();
1561 assert!(err.contains("clause 1"), "{err}");
1562 assert!(err.contains("must be covered"), "{err}");
1563 }
1564
1565 #[test]
1566 fn test_gate_inputs_are_listed_clause_by_clause() {
1567 let circuit = build_two_keys();
1568 assert_eq!(circuit.gate_unchecked(3).get_inputs(), vec![2, 0, 1]);
1569 }
1570
1571 #[test]
1574 fn test_validation_rejects_an_over_deep_expression() {
1575 let mut expected = slice(0, 256);
1576 for _ in 0..MAX_EXPR_DEPTH {
1577 expected = ConstraintExpr::Digest {
1578 algorithm: DigestAlgorithm::Sha256,
1579 of: Box::new(expected),
1580 };
1581 }
1582 let err = build(one(PlaintextBitConstraint::Equality {
1583 bits: slice(0, 256),
1584 expected,
1585 }))
1586 .unwrap_err();
1587 assert!(err.contains("nests deeper than"), "{err}");
1588 }
1589
1590 #[test]
1591 fn test_validation_rejects_an_over_wide_expression() {
1592 let err = build(one(PlaintextBitConstraint::Equality {
1593 bits: ConstraintExpr::Concat(
1594 (0..MAX_EXPR_NODES as u32 + 1)
1595 .map(|i| slice(i, 1))
1596 .collect(),
1597 ),
1598 expected: ConstraintExpr::Wire(0),
1599 }))
1600 .unwrap_err();
1601 assert!(err.contains("more than"), "{err}");
1602 }
1603
1604 #[test]
1605 fn test_validation_rejects_shared_input() {
1606 let mut circuit = Circuit::<C>::new();
1607 plaintext_bits(&mut circuit, 256);
1608 let x = circuit
1609 .add_gate(Gate::Input(Input::Share {
1610 algebraic_type: AlgebraicType::Bit,
1611 batch_size: BATCH_SIZE,
1612 }))
1613 .unwrap();
1614 let err = circuit
1615 .add_gate(Gate::ConstrainPlaintextBits {
1616 x,
1617 on_ambiguity: OnAmbiguity::Fail,
1618 clauses: one(signature_constraint(MESSAGE_BYTES)),
1619 })
1620 .unwrap_err()
1621 .to_string();
1622 assert!(err.contains("is_plaintext"), "{err}");
1623 }
1624 }
1625}