1use alloc::vec;
2use alloc::vec::Vec;
3use anyhow::Result;
4
5use crate::{
6 HASH_SIZE, JellyfishMerkleTree, KeyHash, OwnedValue, SPARSE_MERKLE_PLACEHOLDER_HASH,
7 SimpleHasher, Version,
8 proof::{INTERNAL_DOMAIN_SEPARATOR, LEAF_DOMAIN_SEPARATOR, SparseMerkleProof},
9 storage::HasPreimage,
10 storage::TreeReader,
11 tree::ExclusionProof,
12};
13
14fn sparse_merkle_proof_to_ics23_existence_proof<H: SimpleHasher>(
15 key: Vec<u8>,
16 value: Vec<u8>,
17 proof: &SparseMerkleProof<H>,
18) -> ics23::ExistenceProof {
19 let key_hash: KeyHash = KeyHash::with::<H>(key.as_slice());
20 let mut path = Vec::new();
21 let mut skip = HASH_SIZE * 8 - proof.siblings().len();
22 let mut sibling_idx = 0;
23
24 for byte_idx in (0..HASH_SIZE).rev() {
25 for bit_idx in 0..8 {
27 if skip > 0 {
28 skip -= 1;
29 continue;
30 } else {
31 let bit = (key_hash.0[byte_idx] >> bit_idx) & 0x1;
32 let (prefix, suffix) = if bit == 1 {
37 let mut prefix =
41 Vec::with_capacity(INTERNAL_DOMAIN_SEPARATOR.len() + HASH_SIZE);
42 prefix.extend_from_slice(INTERNAL_DOMAIN_SEPARATOR);
43 prefix.extend_from_slice(&proof.siblings()[sibling_idx].hash::<H>());
44 (prefix, Vec::new())
45 } else {
46 let prefix = INTERNAL_DOMAIN_SEPARATOR.to_vec();
50 let suffix = proof.siblings()[sibling_idx].hash::<H>().to_vec();
51 (prefix, suffix)
52 };
53 path.push(ics23::InnerOp {
54 hash: ics23::HashOp::Sha512.into(),
55 prefix,
56 suffix,
57 });
58 sibling_idx += 1;
59 }
60 }
61 }
62
63 ics23::ExistenceProof {
64 key,
65 value,
66 path,
67 leaf: Some(ics23::LeafOp {
68 hash: ics23::HashOp::Sha512.into(),
69 prehash_key: ics23::HashOp::Sha512.into(),
70 prehash_value: ics23::HashOp::Sha512.into(),
71 length: ics23::LengthOp::NoPrefix.into(),
72 prefix: LEAF_DOMAIN_SEPARATOR.to_vec(),
73 }),
74 }
75}
76
77impl<'a, R, H> JellyfishMerkleTree<'a, R, H>
78where
79 R: 'a + TreeReader + HasPreimage,
80 H: SimpleHasher,
81{
82 fn exclusion_proof_to_ics23_nonexistence_proof(
83 &self,
84 key: Vec<u8>,
85 version: Version,
86 proof: &ExclusionProof<H>,
87 ) -> Result<ics23::NonExistenceProof> {
88 match proof {
89 ExclusionProof::Leftmost {
90 leftmost_right_proof,
91 } => {
92 let key_hash = leftmost_right_proof
93 .leaf()
94 .expect("must have leaf")
95 .key_hash();
96 let key_left_proof = self
97 .reader
98 .preimage(key_hash)?
99 .ok_or(anyhow::anyhow!("missing preimage for key hash"))?;
100
101 let value = self
102 .get(key_hash, version)?
103 .ok_or(anyhow::anyhow!("missing value for key hash"))?;
104
105 let leftmost_right_proof = sparse_merkle_proof_to_ics23_existence_proof(
106 key_left_proof.clone(),
107 value.clone(),
108 leftmost_right_proof,
109 );
110
111 Ok(ics23::NonExistenceProof {
112 key,
113 right: Some(leftmost_right_proof),
114 left: None,
115 })
116 }
117 ExclusionProof::Middle {
118 leftmost_right_proof,
119 rightmost_left_proof,
120 } => {
121 let leftmost_key_hash = leftmost_right_proof
122 .leaf()
123 .expect("must have leaf")
124 .key_hash();
125 let value_leftmost = self
126 .get(leftmost_key_hash, version)?
127 .ok_or(anyhow::anyhow!("missing value for key hash"))?;
128 let key_leftmost = self
129 .reader
130 .preimage(leftmost_key_hash)?
131 .ok_or(anyhow::anyhow!("missing preimage for key hash"))?;
132 let leftmost_right_proof = sparse_merkle_proof_to_ics23_existence_proof(
133 key_leftmost.clone(),
134 value_leftmost.clone(),
135 leftmost_right_proof,
136 );
137
138 let rightmost_key_hash = rightmost_left_proof
139 .leaf()
140 .expect("must have leaf")
141 .key_hash();
142 let value_rightmost = self
143 .get(rightmost_key_hash, version)?
144 .ok_or(anyhow::anyhow!("missing value for key hash"))?;
145 let key_rightmost = self
146 .reader
147 .preimage(rightmost_key_hash)?
148 .ok_or(anyhow::anyhow!("missing preimage for key hash"))?;
149 let rightmost_left_proof = sparse_merkle_proof_to_ics23_existence_proof(
150 key_rightmost.clone(),
151 value_rightmost.clone(),
152 rightmost_left_proof,
153 );
154
155 Ok(ics23::NonExistenceProof {
156 key,
157 right: Some(leftmost_right_proof),
158 left: Some(rightmost_left_proof),
159 })
160 }
161 ExclusionProof::Rightmost {
162 rightmost_left_proof,
163 } => {
164 let rightmost_key_hash = rightmost_left_proof
165 .leaf()
166 .expect("must have leaf")
167 .key_hash();
168 let value_rightmost = self
169 .get(rightmost_key_hash, version)?
170 .ok_or(anyhow::anyhow!("missing value for key hash"))?;
171 let key_rightmost = self
172 .reader
173 .preimage(rightmost_key_hash)?
174 .ok_or(anyhow::anyhow!("missing preimage for key hash"))?;
175 let rightmost_left_proof = sparse_merkle_proof_to_ics23_existence_proof(
176 key_rightmost.clone(),
177 value_rightmost.clone(),
178 rightmost_left_proof,
179 );
180
181 Ok(ics23::NonExistenceProof {
182 key,
183 right: None,
184 left: Some(rightmost_left_proof),
185 })
186 }
187 }
188 }
189
190 pub fn get_with_ics23_proof(
194 &self,
195 key: Vec<u8>,
196 version: Version,
197 ) -> Result<(Option<OwnedValue>, ics23::CommitmentProof)> {
198 let key_hash: KeyHash = KeyHash::with::<H>(key.as_slice());
199 let proof_or_exclusion = self.get_with_exclusion_proof(key_hash, version)?;
200
201 match proof_or_exclusion {
202 Ok((value, proof)) => {
203 let ics23_exist =
204 sparse_merkle_proof_to_ics23_existence_proof(key, value.clone(), &proof);
205
206 Ok((
207 Some(value),
208 ics23::CommitmentProof {
209 proof: Some(ics23::commitment_proof::Proof::Exist(ics23_exist)),
210 },
211 ))
212 }
213 Err(exclusion_proof) => {
214 let ics23_nonexist = self.exclusion_proof_to_ics23_nonexistence_proof(
215 key,
216 version,
217 &exclusion_proof,
218 )?;
219
220 Ok((
221 None,
222 ics23::CommitmentProof {
223 proof: Some(ics23::commitment_proof::Proof::Nonexist(ics23_nonexist)),
224 },
225 ))
226 }
227 }
228 }
229}
230
231pub fn ics23_spec() -> ics23::ProofSpec {
232 ics23::ProofSpec {
233 leaf_spec: Some(ics23::LeafOp {
234 hash: ics23::HashOp::Sha512.into(),
235 prehash_key: ics23::HashOp::Sha512.into(),
236 prehash_value: ics23::HashOp::Sha512.into(),
237 length: ics23::LengthOp::NoPrefix.into(),
238 prefix: LEAF_DOMAIN_SEPARATOR.to_vec(),
239 }),
240 inner_spec: Some(ics23::InnerSpec {
241 hash: ics23::HashOp::Sha512.into(),
242 child_order: vec![0, 1],
243 min_prefix_length: INTERNAL_DOMAIN_SEPARATOR.len() as i32,
244 max_prefix_length: INTERNAL_DOMAIN_SEPARATOR.len() as i32,
245 child_size: HASH_SIZE as i32,
246 empty_child: SPARSE_MERKLE_PLACEHOLDER_HASH.to_vec(),
247 }),
248 min_depth: 0,
249 max_depth: crate::types::nibble::ROOT_NIBBLE_HEIGHT as i32,
250 prehash_key_before_comparison: true,
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use alloc::format;
257 use ics23::{HostFunctionsManager, NonExistenceProof, commitment_proof::Proof};
258 use proptest::prelude::*;
259 use proptest::strategy::Strategy;
260 use sha2::Sha512;
261
262 use super::*;
263 use crate::{
264 HashBytes, KeyHash, SPARSE_MERKLE_PLACEHOLDER_HASH, TransparentHasher, mock::MockTreeStore,
265 };
266
267 proptest! {
268 #![proptest_config(ProptestConfig {
269 cases: 1000, .. ProptestConfig::default()
270 })]
271
272 #[test]
273 fn test_ics23_bounding_path_simple(key_seeds in key_strategy()) {
294 let mut preimages: Vec<String> = key_seeds.into_iter().filter(|k| *k!=0).map(|k| format!("{k:032x}")).collect();
295 preimages.sort();
296 preimages.dedup();
297
298 assert!(!preimages.is_empty());
299
300 let store = MockTreeStore::default();
301 let tree = JellyfishMerkleTree::<_, TransparentHasher>::new(&store);
302
303 for preimage in preimages.iter() {
306 store.put_key_preimage(
307 KeyHash::with::<TransparentHasher>(preimage.clone()),
308 preimage.as_bytes(),
309 );
310 }
311
312 let (_, write_batch) = tree.put_value_set(
313 preimages.iter().enumerate().map(|(i,k)| (KeyHash::with::<TransparentHasher>(k.as_bytes()), Some(i.to_be_bytes().to_vec()))),
314 1
315 ).unwrap();
316
317 store.write_tree_update_batch(write_batch).expect("can write to mock storage");
318
319 let len_preimages = preimages.len();
320
321 for (idx, existing_key) in preimages.iter().enumerate() {
322 let (smaller_key, bigger_key) = generate_adjacent_keys(existing_key);
326
327 let (v, proof) = tree.get_with_ics23_proof(smaller_key.as_bytes().to_vec(), 1).expect("can query tree");
328 assert!(v.is_none(), "the key should not exist!");
329 let proof = proof.proof.expect("a proof is present");
330 if let Proof::Nonexist(NonExistenceProof { key, left, right }) = proof {
331 assert_eq!(key, smaller_key.as_bytes().to_vec());
333
334 let expected_left_neighbor = if idx == 0 { None } else { preimages.get(idx-1) };
336 let expected_right_neighbor = Some(existing_key);
338
339 let reported_left_neighbor = left.clone().map(|existence_proof| String::from_utf8_lossy(&existence_proof.key).into_owned());
340 let reported_right_neighbor = right.clone().map(|existence_proof| String::from_utf8_lossy(&existence_proof.key).into_owned());
341
342 assert_eq!(expected_left_neighbor.cloned(), reported_left_neighbor);
343 assert_eq!(expected_right_neighbor.cloned(), reported_right_neighbor);
344 } else {
345 unreachable!("we have assessed that the value is `None`")
346 }
347
348 let (v, proof) = tree.get_with_ics23_proof(bigger_key.as_bytes().to_vec(), 1).expect("can query tree");
349 assert!(v.is_none(), "the key should not exist!");
350 let proof = proof.proof.expect("a proof is present");
351 if let Proof::Nonexist(NonExistenceProof { key, left, right }) = proof {
352 assert_eq!(key, bigger_key.as_bytes().to_vec());
354 let reported_left_neighbor = left.clone().map(|existence_proof| String::from_utf8_lossy(&existence_proof.key).into_owned());
355 let reported_right_neighbor = right.clone().map(|existence_proof| String::from_utf8_lossy(&existence_proof.key).into_owned());
356
357 let expected_left_neighbor = Some(existing_key);
359 let expected_right_neighbor = if idx == len_preimages - 1 { None } else { preimages.get(idx+1) };
361
362 assert_eq!(expected_left_neighbor.cloned(), reported_left_neighbor);
363 assert_eq!(expected_right_neighbor.cloned(), reported_right_neighbor);
364 } else {
365 unreachable!("we have assessed that the value is `None`")
366 }
367 }
368 }
369
370 #[test]
371 fn test_jmt_ics23_nonexistence(keys: Vec<Vec<u8>>) {
372 test_jmt_ics23_nonexistence_with_keys(keys.into_iter().filter(|k| !k.is_empty()));
373 }
374 }
375
376 fn key_strategy() -> impl Strategy<Value = Vec<u128>> {
377 proptest::collection::btree_set(u64::MAX as u128..=u128::MAX, 200)
378 .prop_map(|set| set.into_iter().collect())
379 }
380 fn generate_adjacent_keys(hex: &str) -> (String, String) {
381 let value = u128::from_str_radix(hex, 16).expect("valid hexstring");
382 let prev = value - 1;
383 let next = value + 1;
384 let p = format!("{prev:032x}");
385 let n = format!("{next:032x}");
386 (p, n)
387 }
388
389 fn test_jmt_ics23_nonexistence_with_keys(keys: impl Iterator<Item = Vec<u8>>) {
390 let db = MockTreeStore::default();
391 let tree = JellyfishMerkleTree::<_, Sha512>::new(&db);
392
393 let mut kvs = Vec::new();
394
395 kvs.push((KeyHash::with::<Sha512>(b"key"), Some(b"value1".to_vec())));
397 db.put_key_preimage(KeyHash::with::<Sha512>(b"key"), b"key");
398
399 for key_preimage in keys {
400 if key_preimage == b"notexist" {
402 continue;
403 }
404 assert!(
405 !key_preimage.is_empty(),
406 "ics23 proofs require non-empty key preimages"
407 );
408 let key_hash = KeyHash::with::<Sha512>(key_preimage.as_slice());
409 let value = vec![0u8; 32];
410 kvs.push((key_hash, Some(value)));
411 db.put_key_preimage(key_hash, key_preimage.as_slice());
412 }
413
414 let (new_root_hash, batch) = tree.put_value_set(kvs, 0).unwrap();
415 db.write_tree_update_batch(batch).unwrap();
416
417 let (value_retrieved, commitment_proof) =
418 tree.get_with_ics23_proof(b"notexist".to_vec(), 0).unwrap();
419
420 let key_hash = KeyHash::with::<Sha512>(b"notexist".as_slice());
421 let proof_or_exclusion = tree.get_with_exclusion_proof(key_hash, 0).unwrap();
422
423 use crate::tree::ExclusionProof::{Leftmost, Middle, Rightmost};
424 match proof_or_exclusion {
425 Ok(_) => panic!("expected nonexistence proof"),
426 Err(exclusion_proof) => match exclusion_proof {
427 Leftmost {
428 leftmost_right_proof,
429 } => {
430 if leftmost_right_proof.root_hash() != new_root_hash {
431 panic!(
432 "root hash mismatch. siblings: {:?}, smph: {:?}",
433 leftmost_right_proof.siblings(),
434 SPARSE_MERKLE_PLACEHOLDER_HASH
435 );
436 }
437
438 assert!(ics23::verify_non_membership::<HostFunctionsManager>(
439 &commitment_proof,
440 &ics23_spec(),
441 &new_root_hash.0.to_vec(),
442 b"notexist"
443 ));
444
445 assert_eq!(value_retrieved, None)
446 }
447 Rightmost {
448 rightmost_left_proof,
449 } => {
450 if rightmost_left_proof.root_hash() != new_root_hash {
451 panic!(
452 "root hash mismatch. siblings: {:?}, smph: {:?}",
453 rightmost_left_proof.siblings(),
454 SPARSE_MERKLE_PLACEHOLDER_HASH
455 );
456 }
457
458 assert!(ics23::verify_non_membership::<HostFunctionsManager>(
459 &commitment_proof,
460 &ics23_spec(),
461 &new_root_hash.0.to_vec(),
462 b"notexist"
463 ));
464
465 assert_eq!(value_retrieved, None)
466 }
467 Middle {
468 leftmost_right_proof,
469 rightmost_left_proof,
470 } => {
471 if leftmost_right_proof.root_hash() != new_root_hash {
472 let good_proof = tree
473 .get_with_proof(leftmost_right_proof.leaf().unwrap().key_hash(), 0)
474 .unwrap();
475 panic!(
476 "root hash mismatch. bad proof: {:?}, good proof: {:?}",
477 leftmost_right_proof, good_proof
478 );
479 }
480 if rightmost_left_proof.root_hash() != new_root_hash {
481 panic!(
482 "root hash mismatch. siblings: {:?}",
483 rightmost_left_proof.siblings()
484 );
485 }
486
487 assert!(ics23::verify_non_membership::<HostFunctionsManager>(
488 &commitment_proof,
489 &ics23_spec(),
490 &new_root_hash.0.to_vec(),
491 b"notexist"
492 ));
493
494 assert_eq!(value_retrieved, None)
495 }
496 },
497 }
498
499 assert!(!ics23::verify_non_membership::<HostFunctionsManager>(
500 &commitment_proof,
501 &ics23_spec(),
502 &new_root_hash.0.to_vec(),
503 b"key",
504 ));
505 }
506
507 #[test]
508 #[should_panic]
509 fn test_jmt_ics23_nonexistence_single_empty_key() {
510 test_jmt_ics23_nonexistence_with_keys(vec![vec![]].into_iter());
511 }
512
513 #[test]
514 fn test_jmt_ics23_existence() {
515 let db = MockTreeStore::default();
516 let tree = JellyfishMerkleTree::<_, Sha512>::new(&db);
517
518 let key = b"key";
519 let key_hash = KeyHash::with::<Sha512>(&key);
520
521 let mut kvs = Vec::new();
523 kvs.push((key_hash, Some(b"value".to_vec())));
524 for i in 1..4 {
526 let mut overlap_key = KeyHash([0; HASH_SIZE]);
527 overlap_key.0[0..i].copy_from_slice(&key_hash.0[0..i]);
528 kvs.push((overlap_key, Some(b"bogus value".to_vec())));
529 }
530
531 let (new_root_hash, batch) = tree.put_value_set(kvs, 0).unwrap();
532 db.write_tree_update_batch(batch).unwrap();
533
534 let (value_retrieved, commitment_proof) =
535 tree.get_with_ics23_proof(b"key".to_vec(), 0).unwrap();
536
537 assert!(ics23::verify_membership::<HostFunctionsManager>(
538 &commitment_proof,
539 &ics23_spec(),
540 &new_root_hash.0.to_vec(),
541 b"key",
542 b"value",
543 ));
544
545 assert_eq!(value_retrieved.unwrap(), b"value");
546 }
547
548 #[test]
549 fn test_jmt_ics23_existence_random_keys() {
550 let db = MockTreeStore::default();
551 let tree = JellyfishMerkleTree::<_, Sha512>::new(&db);
552
553 const MAX_VERSION: u64 = 1 << 14;
554
555 for version in 0..=MAX_VERSION {
556 let key = format!("key{}", version).into_bytes();
557 let value = format!("value{}", version).into_bytes();
558 let (_root, batch) = tree
559 .put_value_set(vec![(KeyHash::with::<Sha512>(key), Some(value))], version)
560 .unwrap();
561 db.write_tree_update_batch(batch).unwrap();
562 }
563
564 let value_maxversion = format!("value{}", MAX_VERSION).into_bytes();
565
566 let (value_retrieved, commitment_proof) = tree
567 .get_with_ics23_proof(format!("key{}", MAX_VERSION).into_bytes(), MAX_VERSION)
568 .unwrap();
569
570 let root_hash = tree.get_root_hash(MAX_VERSION).unwrap().0.to_vec();
571
572 assert!(ics23::verify_membership::<HostFunctionsManager>(
573 &commitment_proof,
574 &ics23_spec(),
575 &root_hash,
576 format!("key{}", MAX_VERSION).as_bytes(),
577 format!("value{}", MAX_VERSION).as_bytes(),
578 ));
579
580 assert_eq!(value_retrieved.unwrap(), value_maxversion);
581 }
582
583 #[test]
584 fn test_jmt_ics23_nonexistence_simple() {
587 use crate::Sha512Jmt;
588 let db = MockTreeStore::default();
589 let tree = Sha512Jmt::new(&db);
590
591 const MAX_VERSION: u64 = 3;
592
593 for version in 0..=MAX_VERSION {
594 let key_str = format!("key-{}", version);
595 let key = key_str.clone().into_bytes();
596 let value_str = format!("value-{}", version);
597 let value = value_str.clone().into_bytes();
598 let keys = vec![key.clone()];
599 let values = vec![value];
600 let value_set = keys
601 .into_iter()
602 .zip(values)
603 .map(|(k, v)| (KeyHash::with::<Sha512>(&k), Some(v)))
604 .collect::<Vec<_>>();
605 let key_hash = KeyHash::with::<Sha512>(&key);
606
607 db.put_key_preimage(key_hash, &key);
608 let (_root, batch) = tree.put_value_set(value_set, version).unwrap();
609 db.write_tree_update_batch(batch)
610 .expect("can insert node batch");
611 }
612 let (_value_retrieved, _commitment_proof) = tree
613 .get_with_ics23_proof(b"does_not_exist".to_vec(), MAX_VERSION)
614 .unwrap();
615 }
616
617 #[test]
618 fn test_jmt_ics23_nonexistence_simple_large() {
621 use crate::Sha512Jmt;
622 let db = MockTreeStore::default();
623 let tree = Sha512Jmt::new(&db);
624
625 const MAX_VERSION: u64 = 100;
626
627 for version in 0..=MAX_VERSION {
628 let key_str = format!("key-{}", version);
629 let key = key_str.clone().into_bytes();
630 let value_str = format!("value-{}", version);
631 let value = value_str.clone().into_bytes();
632 let keys = vec![key.clone()];
633 let values = vec![value];
634 let value_set = keys
635 .into_iter()
636 .zip(values)
637 .map(|(k, v)| (KeyHash::with::<Sha512>(&k), Some(v)))
638 .collect::<Vec<_>>();
639 let key_hash = KeyHash::with::<Sha512>(&key);
640
641 db.put_key_preimage(key_hash, &key);
642 let (_root, batch) = tree.put_value_set(value_set, version).unwrap();
643 db.write_tree_update_batch(batch)
644 .expect("can insert node batch");
645 }
646
647 for version in 0..=MAX_VERSION {
648 let (_value_retrieved, _commitment_proof) = tree
649 .get_with_ics23_proof(b"does_not_exist".to_vec(), version)
650 .unwrap();
651 }
652 }
653
654 #[test]
655 fn test_jmt_ics23_nonexistence_simple_transparent() {
659 let db = MockTreeStore::default();
660 let tree = JellyfishMerkleTree::<_, TransparentHasher>::new(&db);
661
662 const MAX_VERSION: u64 = 4;
663
664 let mock_keys_str = [
665 prefix_pad("a0"),
666 prefix_pad("b1"),
667 prefix_pad("c2"),
668 prefix_pad("d3"),
669 prefix_pad("e4"),
670 ];
671
672 for version in 0..=MAX_VERSION {
673 let key = mock_keys_str[version as usize];
674 let key_hash = KeyHash::with::<TransparentHasher>(&key);
675 let value_str = format!("value-{}", version);
676 let value = value_str.clone().into_bytes();
677 let keys = vec![key];
678 let values = vec![value];
679 let value_set = keys
680 .into_iter()
681 .zip(values)
682 .map(|(k, v)| (KeyHash::with::<TransparentHasher>(&k), Some(v)))
683 .collect::<Vec<_>>();
684 db.put_key_preimage(key_hash, key.as_ref());
685 let (_root, batch) = tree.put_value_set(value_set, version).unwrap();
686 db.write_tree_update_batch(batch)
687 .expect("can insert node batch");
688 }
689
690 let nonexisting_key = prefix_pad("c3");
691 let (_value_retrieved, _commitment_proof) = tree
692 .get_with_ics23_proof(nonexisting_key.to_vec(), MAX_VERSION)
693 .unwrap();
694 }
695
696 fn prefix_pad(hex_str: &str) -> HashBytes {
700 if hex_str.len() > HASH_SIZE * 2 {
701 panic!(
702 "hexadecimal string is longer than {} bytes when decoded",
703 HASH_SIZE
704 );
705 }
706
707 let mut bytes = Vec::with_capacity(hex_str.len() / 2);
708 for i in (0..hex_str.len()).step_by(2) {
709 let byte_str = &hex_str[i..i + 2];
710 let byte = u8::from_str_radix(byte_str, 16).expect("Invalid hex character");
711 bytes.push(byte);
712 }
713
714 let mut padded_bytes = [0u8; HASH_SIZE];
715 padded_bytes[..bytes.len()].copy_from_slice(&bytes);
716
717 padded_bytes
718 }
719}