1use serde::Serialize;
19
20use crate::Digest;
21use crate::hash::Hasher;
22
23pub const DIGEST_LEN: usize = 32;
25
26pub const LEAF_PREFIX: [u8; 1] = [0x00];
28
29pub const INNER_PREFIX: [u8; 1] = [0x01];
31
32pub const EMPTY_NODE: [u8; DIGEST_LEN] = [0u8; DIGEST_LEN];
34
35#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
41pub enum Node {
42 Empty,
44 Digest([u8; DIGEST_LEN]),
46}
47
48impl Node {
49 pub fn bytes(&self) -> [u8; DIGEST_LEN] {
52 match self {
53 Self::Empty => EMPTY_NODE,
54 Self::Digest(value) => *value,
55 }
56 }
57}
58
59impl AsRef<[u8]> for Node {
60 fn as_ref(&self) -> &[u8] {
61 match self {
62 Self::Empty => &EMPTY_NODE,
63 Self::Digest(value) => value.as_ref(),
64 }
65 }
66}
67
68impl From<[u8; DIGEST_LEN]> for Node {
69 fn from(value: [u8; DIGEST_LEN]) -> Self {
70 Self::Digest(value)
71 }
72}
73
74impl From<Digest> for Node {
75 fn from(value: Digest) -> Self {
76 Self::Digest(value.into_inner())
77 }
78}
79
80#[derive(Debug, PartialEq, Eq)]
82pub enum MerkleError {
83 InvalidProof,
86 InvalidInput,
89}
90
91impl std::fmt::Display for MerkleError {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 match self {
94 Self::InvalidProof => f.write_str("invalid merkle proof"),
95 Self::InvalidInput => f.write_str("invalid merkle input"),
96 }
97 }
98}
99
100impl std::error::Error for MerkleError {}
101
102#[derive(Clone, Debug, PartialEq, Eq)]
106pub struct MerkleProof {
107 path: Vec<Node>,
108}
109
110impl MerkleProof {
111 pub fn new(path: Vec<Node>) -> Self {
115 Self { path }
116 }
117
118 pub fn path(&self) -> &[Node] {
121 &self.path
122 }
123
124 pub fn verify_proof_with_leaf_bytes(
136 &self,
137 root: &Node,
138 leaf_bytes: &[u8],
139 leaf_index: usize,
140 ) -> Result<(), MerkleError> {
141 match self.compute_root(leaf_bytes, leaf_index) {
142 Some(computed) if &computed == root => Ok(()),
143 _ => Err(MerkleError::InvalidProof),
144 }
145 }
146
147 pub fn verify_proof<L: Serialize>(
157 &self,
158 root: &Node,
159 leaf: &L,
160 leaf_index: usize,
161 ) -> Result<(), MerkleError> {
162 let bytes = bcs::to_bytes(leaf).map_err(|_| MerkleError::InvalidInput)?;
163 self.verify_proof_with_leaf_bytes(root, &bytes, leaf_index)
164 }
165
166 pub fn compute_root(&self, leaf: &[u8], leaf_index: usize) -> Option<Node> {
172 if leaf_index >> self.path.len() != 0 {
173 return None;
174 }
175 let mut current = leaf_hash(leaf);
176 let mut level_index = leaf_index;
177 for sibling in &self.path {
178 current = if level_index.is_multiple_of(2) {
179 inner_hash(¤t, sibling)
180 } else {
181 inner_hash(sibling, ¤t)
182 };
183 level_index /= 2;
184 }
185 Some(current)
186 }
187
188 pub fn is_right_most(&self, leaf_index: usize) -> bool {
194 let mut level_index = leaf_index;
195 for sibling in &self.path {
196 if level_index.is_multiple_of(2) && sibling.as_ref() != EMPTY_NODE.as_ref() {
197 return false;
198 }
199 level_index /= 2;
200 }
201 true
202 }
203}
204
205#[derive(Debug)]
212pub struct MerkleTree {
213 nodes: Vec<Node>,
216 n_leaves: usize,
217}
218
219impl MerkleTree {
220 pub fn build_from_serialized<I>(iter: I) -> Self
222 where
223 I: IntoIterator,
224 I::IntoIter: ExactSizeIterator,
225 I::Item: AsRef<[u8]>,
226 {
227 let leaf_hashes = iter
228 .into_iter()
229 .map(|leaf| leaf_hash(leaf.as_ref()))
230 .collect::<Vec<_>>();
231 Self::build_from_leaf_hashes(leaf_hashes)
232 }
233
234 pub fn build_from_unserialized<I>(iter: I) -> Result<Self, MerkleError>
240 where
241 I: IntoIterator,
242 I::IntoIter: ExactSizeIterator,
243 I::Item: Serialize,
244 {
245 let leaf_hashes = iter
246 .into_iter()
247 .map(|leaf| {
248 bcs::to_bytes(&leaf)
249 .map_err(|_| MerkleError::InvalidInput)
250 .map(|bytes| leaf_hash(&bytes))
251 })
252 .collect::<Result<Vec<_>, _>>()?;
253 Ok(Self::build_from_leaf_hashes(leaf_hashes))
254 }
255
256 pub fn build_from_leaf_hashes<I>(iter: I) -> Self
261 where
262 I: IntoIterator,
263 I::IntoIter: ExactSizeIterator<Item = Node>,
264 {
265 let iter = iter.into_iter();
266 let mut nodes = Vec::with_capacity(n_nodes(iter.len()));
267 nodes.extend(iter);
268
269 let n_leaves = nodes.len();
270 let mut level_nodes = n_leaves;
271 let mut prev_level_index = 0;
272
273 while level_nodes > 1 {
274 if level_nodes.is_multiple_of(2) {
275 } else {
277 nodes.push(Node::Empty);
278 level_nodes += 1;
279 }
280 let new_level_index = prev_level_index + level_nodes;
281 (prev_level_index..new_level_index)
282 .step_by(2)
283 .for_each(|index| nodes.push(inner_hash(&nodes[index], &nodes[index + 1])));
284 prev_level_index = new_level_index;
285 level_nodes /= 2;
286 }
287
288 Self { nodes, n_leaves }
289 }
290
291 pub fn root(&self) -> Node {
295 self.nodes.last().copied().unwrap_or(Node::Empty)
296 }
297
298 pub fn n_leaves(&self) -> usize {
300 self.n_leaves
301 }
302
303 pub fn get_proof(&self, leaf_index: usize) -> Result<MerkleProof, MerkleError> {
307 if leaf_index >= self.n_leaves {
308 return Err(MerkleError::InvalidInput);
309 }
310 let path_capacity = self
311 .n_leaves
312 .checked_ilog2()
313 .map(|log| log as usize + 1)
314 .unwrap_or(0);
315 let mut path = Vec::with_capacity(path_capacity);
316 let mut level_index = leaf_index;
317 let mut n_level = self.n_leaves;
318 let mut level_base_index = 0;
319 while n_level > 1 {
320 n_level = n_level.next_multiple_of(2);
322 let sibling_index = if level_index.is_multiple_of(2) {
323 level_base_index + level_index + 1
324 } else {
325 level_base_index + level_index - 1
326 };
327 path.push(self.nodes[sibling_index]);
328 level_index /= 2;
329 level_base_index += n_level;
330 n_level /= 2;
331 }
332 Ok(MerkleProof { path })
333 }
334
335 pub fn compute_non_inclusion_proof<L>(
343 &self,
344 sorted_leaves: &[L],
345 target: &L,
346 ) -> Result<MerkleNonInclusionProof<L>, MerkleError>
347 where
348 L: Ord + Clone,
349 {
350 let position = sorted_leaves.partition_point(|leaf| leaf <= target);
351 if position > 0 && &sorted_leaves[position - 1] == target {
352 return Err(MerkleError::InvalidInput);
353 }
354
355 let left_leaf = if position > 0 {
356 Some((
357 sorted_leaves[position - 1].clone(),
358 self.get_proof(position - 1)?,
359 ))
360 } else {
361 None
362 };
363
364 let right_leaf = if position < sorted_leaves.len() {
365 Some((sorted_leaves[position].clone(), self.get_proof(position)?))
366 } else {
367 None
368 };
369
370 Ok(MerkleNonInclusionProof {
371 index: position,
372 left_leaf,
373 right_leaf,
374 })
375 }
376}
377
378#[derive(Clone, Debug, PartialEq, Eq)]
393pub struct MerkleNonInclusionProof<L> {
394 index: usize,
398 left_leaf: Option<(L, MerkleProof)>,
401 right_leaf: Option<(L, MerkleProof)>,
404}
405
406impl<L> MerkleNonInclusionProof<L> {
407 pub fn new(
411 index: usize,
412 left_leaf: Option<(L, MerkleProof)>,
413 right_leaf: Option<(L, MerkleProof)>,
414 ) -> Self {
415 Self {
416 index,
417 left_leaf,
418 right_leaf,
419 }
420 }
421
422 pub fn index(&self) -> usize {
425 self.index
426 }
427
428 pub fn left_leaf(&self) -> Option<&(L, MerkleProof)> {
430 self.left_leaf.as_ref()
431 }
432
433 pub fn right_leaf(&self) -> Option<&(L, MerkleProof)> {
435 self.right_leaf.as_ref()
436 }
437}
438
439impl<L> MerkleNonInclusionProof<L>
440where
441 L: Serialize,
442{
443 pub fn verify_proof_by_key<K, F>(
460 &self,
461 root: &Node,
462 target_key: &K,
463 key_of: F,
464 ) -> Result<(), MerkleError>
465 where
466 K: Ord + ?Sized,
467 F: Fn(&L) -> &K,
468 {
469 if root.as_ref() == EMPTY_NODE.as_ref() {
471 return Ok(());
472 }
473
474 let right_leaf_index = self.index;
475 let left_leaf_with_index = self.left_leaf.as_ref().zip(self.index.checked_sub(1));
476
477 if let Some(((left_leaf, left_proof), left_leaf_index)) = left_leaf_with_index {
478 left_proof.verify_proof(root, left_leaf, left_leaf_index)?;
479 if key_of(left_leaf) >= target_key {
480 return Err(MerkleError::InvalidProof);
481 }
482 } else if right_leaf_index != 0 || self.right_leaf.is_none() {
483 return Err(MerkleError::InvalidProof);
487 }
488
489 if let Some((right_leaf, right_proof)) = &self.right_leaf {
490 right_proof.verify_proof(root, right_leaf, right_leaf_index)?;
491 if key_of(right_leaf) <= target_key {
492 return Err(MerkleError::InvalidProof);
493 }
494 } else if let Some(((_, left_proof), left_leaf_index)) = left_leaf_with_index {
495 if !left_proof.is_right_most(left_leaf_index) {
498 return Err(MerkleError::InvalidProof);
499 }
500 } else {
501 return Err(MerkleError::InvalidProof);
502 }
503
504 Ok(())
505 }
506}
507
508impl<L> MerkleNonInclusionProof<L>
509where
510 L: Ord + Serialize,
511{
512 pub fn verify_proof(&self, root: &Node, target: &L) -> Result<(), MerkleError> {
517 self.verify_proof_by_key(root, target, |leaf| leaf)
518 }
519}
520
521pub(crate) fn leaf_hash(input: &[u8]) -> Node {
523 let mut hasher = Hasher::new();
524 hasher.update(LEAF_PREFIX);
525 hasher.update(input);
526 Node::Digest(hasher.finalize().into_inner())
527}
528
529fn inner_hash(left: &Node, right: &Node) -> Node {
531 let mut hasher = Hasher::new();
532 hasher.update(INNER_PREFIX);
533 hasher.update(left.bytes());
534 hasher.update(right.bytes());
535 Node::Digest(hasher.finalize().into_inner())
536}
537
538pub(crate) fn n_nodes(n_leaves: usize) -> usize {
541 let mut lvl_nodes = n_leaves;
542 let mut tot_nodes = 0;
543 while lvl_nodes > 1 {
544 lvl_nodes += lvl_nodes % 2;
545 tot_nodes += lvl_nodes;
546 lvl_nodes /= 2;
547 }
548 tot_nodes + lvl_nodes
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554
555 #[cfg(target_arch = "wasm32")]
556 use wasm_bindgen_test::wasm_bindgen_test as test;
557
558 const TEST_INPUT: [&[u8]; 9] = [
559 b"foo", b"bar", b"fizz", b"baz", b"buzz", b"fizz", b"foobar", b"walrus", b"fizz",
560 ];
561
562 #[test]
563 fn n_nodes_formula() {
564 assert_eq!(n_nodes(0), 0);
565 assert_eq!(n_nodes(1), 1);
566 assert_eq!(n_nodes(2), 3);
567 assert_eq!(n_nodes(3), 7);
568 assert_eq!(n_nodes(4), 7);
569 assert_eq!(n_nodes(5), 13);
570 assert_eq!(n_nodes(6), 13);
571 assert_eq!(n_nodes(7), 15);
572 assert_eq!(n_nodes(8), 15);
573 assert_eq!(n_nodes(9), 23);
574 }
575
576 #[test]
577 fn empty_tree_root_is_empty_node() {
578 let tree = MerkleTree::build_from_serialized::<[&[u8]; 0]>([]);
579 assert_eq!(tree.root().bytes(), EMPTY_NODE);
580 }
581
582 #[test]
583 fn single_element_tree_root_is_leaf_hash() {
584 let leaf = b"Test";
585 let tree = MerkleTree::build_from_serialized([leaf.as_ref()]);
586 let mut hasher = Hasher::new();
588 hasher.update(LEAF_PREFIX);
589 hasher.update(leaf);
590 assert_eq!(tree.root().bytes(), hasher.finalize().into_inner());
591 }
592
593 #[test]
594 fn empty_element_tree_root_is_hash_of_empty_leaf() {
595 let tree = MerkleTree::build_from_serialized([&[][..]]);
596 let mut hasher = Hasher::new();
597 hasher.update(LEAF_PREFIX);
598 hasher.update::<&[u8]>(&[]);
599 assert_eq!(tree.root().bytes(), hasher.finalize().into_inner());
600 }
601
602 #[test]
603 fn get_proof_out_of_bounds() {
604 for i in 0..TEST_INPUT.len() {
605 let tree = MerkleTree::build_from_serialized(&TEST_INPUT[..i]);
606 assert_eq!(
607 tree.get_proof(i.next_power_of_two()),
608 Err(MerkleError::InvalidInput),
609 );
610 }
611 }
612
613 #[test]
614 fn every_proof_round_trips() {
615 for i in 0..TEST_INPUT.len() {
616 let tree = MerkleTree::build_from_serialized(&TEST_INPUT[..i]);
617 for (index, leaf) in TEST_INPUT[..i].iter().enumerate() {
618 let proof = tree.get_proof(index).unwrap();
619 proof
620 .verify_proof_with_leaf_bytes(&tree.root(), leaf, index)
621 .unwrap();
622 }
623 }
624 }
625
626 #[test]
627 fn proof_fails_for_wrong_index() {
628 for i in 1..TEST_INPUT.len() {
629 let tree = MerkleTree::build_from_serialized(&TEST_INPUT[..i]);
630 for (index, leaf) in TEST_INPUT[..i].iter().enumerate() {
631 let proof = tree.get_proof(index).unwrap();
632 assert_eq!(
633 proof.verify_proof_with_leaf_bytes(&tree.root(), leaf, index + 1),
634 Err(MerkleError::InvalidProof),
635 );
636 }
637 }
638 }
639
640 #[test]
641 fn proof_fails_for_tampered_leaf() {
642 let tree = MerkleTree::build_from_serialized(TEST_INPUT);
643 let proof = tree.get_proof(3).unwrap();
644 let tampered = b"not-the-real-leaf";
645 assert_eq!(
646 proof.verify_proof_with_leaf_bytes(&tree.root(), tampered, 3),
647 Err(MerkleError::InvalidProof),
648 );
649 }
650
651 #[test]
652 fn proof_fails_against_wrong_root() {
653 let tree = MerkleTree::build_from_serialized(TEST_INPUT);
654 let proof = tree.get_proof(2).unwrap();
655 let wrong_root = Node::Digest([0xab; DIGEST_LEN]);
656 assert_eq!(
657 proof.verify_proof_with_leaf_bytes(&wrong_root, TEST_INPUT[2], 2),
658 Err(MerkleError::InvalidProof),
659 );
660 }
661
662 #[test]
663 fn is_right_most_detects_last_leaf() {
664 for i in 1..TEST_INPUT.len() {
665 let tree = MerkleTree::build_from_serialized(&TEST_INPUT[..i]);
666 for j in 0..i {
667 let proof = tree.get_proof(j).unwrap();
668 let expected = j == i - 1;
669 assert_eq!(proof.is_right_most(j), expected);
670 }
671 }
672 }
673
674 #[test]
677 fn non_inclusion_empty_tree() {
678 let tree = MerkleTree::build_from_unserialized::<[&[u8]; 0]>([]).unwrap();
679 let leaves: [&[u8]; 0] = [];
680 let proof = tree
681 .compute_non_inclusion_proof(&leaves, &b"foo".as_ref())
682 .unwrap();
683 assert!(proof.left_leaf().is_none());
684 assert!(proof.right_leaf().is_none());
685 assert_eq!(proof.index(), 0);
686 proof.verify_proof(&tree.root(), &b"foo".as_ref()).unwrap();
687 proof.verify_proof(&tree.root(), &b"bar".as_ref()).unwrap();
688 }
689
690 #[test]
693 fn non_inclusion_single_leaf() {
694 let leaves: [&[u8]; 1] = [b"foo"];
695 let tree = MerkleTree::build_from_unserialized(&leaves).unwrap();
696
697 let proof = tree
698 .compute_non_inclusion_proof(&leaves, &b"bar".as_ref())
699 .unwrap();
700 proof.verify_proof(&tree.root(), &b"bar".as_ref()).unwrap();
701
702 assert_eq!(
704 proof.verify_proof(&tree.root(), &b"foo".as_ref()),
705 Err(MerkleError::InvalidProof),
706 );
707
708 assert_eq!(
711 tree.compute_non_inclusion_proof(&leaves, &b"foo".as_ref()),
712 Err(MerkleError::InvalidInput),
713 );
714 }
715
716 #[test]
719 fn non_inclusion_multiple_leaves() {
720 const RAW: [&str; 9] = [
721 "foo", "bar", "fizz", "baz", "buzz", "fizz", "foobar", "walrus", "fizz",
722 ];
723 let mut sorted: Vec<&str> = RAW.to_vec();
724 sorted.sort();
725 sorted.dedup();
726 let tree = MerkleTree::build_from_unserialized(&sorted).unwrap();
727
728 let probes = ["fuzz", "yankee", "aloha", "foo", "bar", "fizz", "walrus"];
730 for probe in probes {
731 let result = tree.compute_non_inclusion_proof(&sorted, &probe);
732 if sorted.contains(&probe) {
733 assert_eq!(result, Err(MerkleError::InvalidInput));
734 } else {
735 let proof = result.unwrap();
736 proof.verify_proof(&tree.root(), &probe).unwrap();
737 }
738 }
739 }
740
741 #[test]
745 fn non_inclusion_at_extremes() {
746 let sorted: [&str; 3] = ["bar", "foo", "qux"];
747 let tree = MerkleTree::build_from_unserialized(&sorted).unwrap();
748
749 let before = tree.compute_non_inclusion_proof(&sorted, &"aaa").unwrap();
751 assert!(before.left_leaf().is_none());
752 assert!(before.right_leaf().is_some());
753 assert_eq!(before.index(), 0);
754 before.verify_proof(&tree.root(), &"aaa").unwrap();
755
756 let after = tree.compute_non_inclusion_proof(&sorted, &"zzz").unwrap();
758 assert!(after.left_leaf().is_some());
759 assert!(after.right_leaf().is_none());
760 assert_eq!(after.index(), sorted.len());
761 after.verify_proof(&tree.root(), &"zzz").unwrap();
762 }
763
764 #[test]
768 fn non_inclusion_forged_zero_index_with_left_leaf() {
769 let leaves: [&[u8]; 1] = [b"foo"];
770 let tree = MerkleTree::build_from_unserialized(&leaves).unwrap();
771 let fake: &[u8] = b"fake";
772
773 let forged = MerkleNonInclusionProof::new(
774 0,
775 Some((fake, tree.get_proof(0).unwrap())),
776 Some((fake, tree.get_proof(0).unwrap())),
777 );
778 assert_eq!(
779 forged.verify_proof(&tree.root(), &fake),
780 Err(MerkleError::InvalidProof),
781 );
782 }
783
784 #[test]
791 fn root_matches_upstream_for_known_input() {
792 const EXPECTED_ROOT: [u8; DIGEST_LEN] = [
793 0x8d, 0x01, 0x06, 0x76, 0xde, 0x3d, 0x66, 0x08, 0x77, 0xcc, 0x8c, 0x27, 0xa4, 0x2d,
794 0xcf, 0xf9, 0xc1, 0x15, 0x97, 0x20, 0x36, 0x1a, 0x82, 0x36, 0xd2, 0xd2, 0x07, 0xb6,
795 0x8b, 0x72, 0x9b, 0x0c,
796 ];
797
798 let tree = MerkleTree::build_from_serialized(TEST_INPUT);
799 assert_eq!(tree.root().bytes(), EXPECTED_ROOT);
800 }
801
802 #[cfg(feature = "proptest")]
808 mod proptests {
809 use super::*;
810
811 use proptest::collection::vec;
812 use proptest::prelude::*;
813 use test_strategy::proptest;
814
815 #[cfg(target_arch = "wasm32")]
822 use wasm_bindgen_test::wasm_bindgen_test as test;
823
824 fn small_leaves() -> impl Strategy<Value = Vec<u32>> {
829 vec(any::<u32>(), 1..=32)
830 }
831
832 fn sorted_unique_leaves() -> impl Strategy<Value = Vec<u32>> {
836 vec(any::<u32>(), 0..=32).prop_map(|mut v| {
837 v.sort();
838 v.dedup();
839 v
840 })
841 }
842
843 #[proptest]
847 fn inclusion_proof_round_trips(#[strategy(small_leaves())] leaves: Vec<u32>) {
848 let tree = MerkleTree::build_from_unserialized(leaves.iter()).unwrap();
849 let root = tree.root();
850 for (index, leaf) in leaves.iter().enumerate() {
851 let proof = tree.get_proof(index).unwrap();
852 proof
853 .verify_proof(&root, leaf, index)
854 .expect("freshly-built proof must verify");
855 }
856 }
857
858 #[proptest]
865 fn inclusion_proof_rejects_wrong_leaf(#[strategy(small_leaves())] leaves: Vec<u32>) {
866 prop_assume!(leaves.len() >= 2);
867 let tree = MerkleTree::build_from_unserialized(leaves.iter()).unwrap();
868 let root = tree.root();
869 for (index, leaf) in leaves.iter().enumerate() {
870 let other_index = (index + 1) % leaves.len();
871 if leaves[other_index] == *leaf {
872 continue;
873 }
874 let proof = tree.get_proof(index).unwrap();
875 prop_assert!(
876 proof
877 .verify_proof(&root, &leaves[other_index], index)
878 .is_err(),
879 "proof for index {index} must not verify a different leaf at the same index",
880 );
881 }
882 }
883
884 #[proptest]
888 fn non_inclusion_proof_round_trips(
889 #[strategy(sorted_unique_leaves())] leaves: Vec<u32>,
890 target: u32,
891 ) {
892 prop_assume!(leaves.binary_search(&target).is_err());
895 let tree = MerkleTree::build_from_unserialized(leaves.iter()).unwrap();
896 let root = tree.root();
897 let proof = tree.compute_non_inclusion_proof(&leaves, &target).unwrap();
898 proof
899 .verify_proof(&root, &target)
900 .expect("freshly-built non-inclusion proof must verify");
901 }
902
903 #[proptest]
906 fn non_inclusion_rejects_present_target(
907 #[strategy(sorted_unique_leaves())] leaves: Vec<u32>,
908 ) {
909 prop_assume!(!leaves.is_empty());
910 let tree = MerkleTree::build_from_unserialized(leaves.iter()).unwrap();
911 for present in &leaves {
912 prop_assert_eq!(
913 tree.compute_non_inclusion_proof(&leaves, present),
914 Err(MerkleError::InvalidInput),
915 );
916 }
917 }
918
919 #[proptest]
924 fn is_right_most_only_for_last_leaf(#[strategy(small_leaves())] leaves: Vec<u32>) {
925 let tree = MerkleTree::build_from_unserialized(leaves.iter()).unwrap();
926 let last = leaves.len() - 1;
927 for j in 0..leaves.len() {
928 let proof = tree.get_proof(j).unwrap();
929 prop_assert_eq!(proof.is_right_most(j), j == last);
930 }
931 }
932 }
933}