Skip to content

Commit 8465870

Browse files
committed
reduce proof size (trailing zeros in Merkle leaves)
1 parent 6288136 commit 8465870

File tree

1 file changed

+56
-53
lines changed

1 file changed

+56
-53
lines changed

crates/backend/fiat-shamir/src/merkle_pruning.rs

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ pub struct PrunedMerklePaths<Data, F> {
88
pub original_order: Vec<usize>,
99
pub leaf_data: Vec<Vec<Data>>,
1010
pub paths: Vec<(usize, Vec<[F; DIGEST_LEN_FE]>)>,
11+
pub n_trailing_zeros: usize,
1112
}
1213

1314
fn lca_level(a: usize, b: usize) -> usize {
1415
(usize::BITS - (a ^ b).leading_zeros()) as usize
1516
}
1617

1718
impl<Data: Clone, F: Clone> MerklePaths<Data, F> {
18-
pub fn prune(self) -> PrunedMerklePaths<Data, F> {
19+
pub fn prune(self) -> PrunedMerklePaths<Data, F>
20+
where
21+
Data: Default + PartialEq,
22+
{
1923
assert!(!self.0.is_empty());
2024
let merkle_height = self.0[0].sibling_hashes.len();
2125

@@ -34,6 +38,16 @@ impl<Data: Clone, F: Clone> MerklePaths<Data, F> {
3438
}
3539
}
3640

41+
let default = Data::default();
42+
let leaf_len = deduped[0].leaf_data.len();
43+
let mut n_trailing_zeros = 0;
44+
for offset in (0..leaf_len).rev() {
45+
if deduped.iter().any(|p| p.leaf_data[offset] != default) {
46+
break;
47+
}
48+
n_trailing_zeros += 1;
49+
}
50+
3751
let paths = deduped
3852
.iter()
3953
.enumerate()
@@ -56,21 +70,38 @@ impl<Data: Clone, F: Clone> MerklePaths<Data, F> {
5670
PrunedMerklePaths {
5771
merkle_height,
5872
original_order,
59-
leaf_data: deduped.into_iter().map(|p| p.leaf_data).collect(),
73+
leaf_data: deduped
74+
.into_iter()
75+
.map(|p| {
76+
let effective_len = p.leaf_data.len() - n_trailing_zeros;
77+
p.leaf_data[..effective_len].to_vec()
78+
})
79+
.collect(),
6080
paths,
81+
n_trailing_zeros,
6182
}
6283
}
6384
}
6485

6586
impl<Data: Clone, F: Clone> PrunedMerklePaths<Data, F> {
6687
pub fn restore(
67-
self,
88+
mut self,
6889
hash_leaf: &impl Fn(&[Data]) -> [F; DIGEST_LEN_FE],
6990
hash_combine: &impl Fn(&[F; DIGEST_LEN_FE], &[F; DIGEST_LEN_FE]) -> [F; DIGEST_LEN_FE],
70-
) -> Option<MerklePaths<Data, F>> {
91+
) -> Option<MerklePaths<Data, F>>
92+
where
93+
Data: Default,
94+
{
7195
let n = self.paths.len();
7296
let h = self.merkle_height;
7397

98+
if self.n_trailing_zeros > 1024 {
99+
return None; // prevent DoS with huge leaf data
100+
}
101+
self.leaf_data
102+
.iter_mut()
103+
.for_each(|d| d.resize(d.len() + self.n_trailing_zeros, Data::default()));
104+
74105
let levels = |i: usize| {
75106
i.checked_sub(1)
76107
.map_or(h, |j| lca_level(self.paths[j].0, self.paths[i].0))
@@ -388,58 +419,30 @@ mod tests {
388419
}
389420

390421
#[test]
391-
fn test_restore_returns_none_on_missing_leaf_data() {
392-
let pruned: PrunedMerklePaths<u8, u8> = PrunedMerklePaths {
393-
merkle_height: 2,
394-
original_order: vec![0],
395-
leaf_data: vec![], // Empty - should cause failure
396-
paths: vec![(0, vec![[1; DIGEST_LEN_FE], [2; DIGEST_LEN_FE]])],
397-
};
398-
assert!(pruned.restore(&simple_hash, &hash_combine).is_none());
399-
}
422+
fn test_trailing_zeros_stripped() {
423+
let leaves: Vec<Vec<u8>> = (0u8..8).map(|i| vec![i + 1, i + 10, 0, 0, 0]).collect();
424+
let tree = build_merkle_tree(&leaves);
400425

401-
#[test]
402-
fn test_restore_returns_none_on_invalid_original_order() {
403-
let pruned: PrunedMerklePaths<u8, u8> = PrunedMerklePaths {
404-
merkle_height: 2,
405-
original_order: vec![5], // Out of bounds index
406-
leaf_data: vec![vec![0]],
407-
paths: vec![(0, vec![[1; DIGEST_LEN_FE], [2; DIGEST_LEN_FE]])],
408-
};
409-
assert!(pruned.restore(&simple_hash, &hash_combine).is_none());
410-
}
426+
let indices = [2, 5, 7];
427+
let paths = MerklePaths(
428+
indices
429+
.iter()
430+
.map(|&i| generate_auth_path(leaves[i].clone(), i, &tree))
431+
.collect(),
432+
);
411433

412-
#[test]
413-
fn test_restore_returns_none_on_missing_siblings() {
414-
let pruned: PrunedMerklePaths<u8, u8> = PrunedMerklePaths {
415-
merkle_height: 3,
416-
original_order: vec![0],
417-
leaf_data: vec![vec![0]],
418-
paths: vec![(0, vec![[1; DIGEST_LEN_FE]])], // Only 1 sibling, need 3
419-
};
420-
assert!(pruned.restore(&simple_hash, &hash_combine).is_none());
421-
}
434+
let pruned = paths.clone().prune();
422435

423-
#[test]
424-
fn test_restore_returns_none_on_mismatched_paths_and_leaf_data() {
425-
let pruned: PrunedMerklePaths<u8, u8> = PrunedMerklePaths {
426-
merkle_height: 2,
427-
original_order: vec![0, 1],
428-
leaf_data: vec![vec![0]], // Only 1, but paths has 2
429-
paths: vec![(0, vec![[1; DIGEST_LEN_FE]]), (1, vec![[2; DIGEST_LEN_FE]])],
430-
};
431-
assert!(pruned.restore(&simple_hash, &hash_combine).is_none());
432-
}
436+
assert_eq!(pruned.n_trailing_zeros, 3);
437+
for leaf in &pruned.leaf_data {
438+
assert_eq!(leaf.len(), 2);
439+
}
433440

434-
#[test]
435-
fn test_restore_returns_none_on_empty_paths() {
436-
let pruned: PrunedMerklePaths<u8, u8> = PrunedMerklePaths {
437-
merkle_height: 2,
438-
original_order: vec![0],
439-
leaf_data: vec![],
440-
paths: vec![],
441-
};
442-
// original_order[0] = 0, but restored is empty
443-
assert!(pruned.restore(&simple_hash, &hash_combine).is_none());
441+
let restored = pruned.restore(&simple_hash, &hash_combine).unwrap();
442+
for (orig, rest) in paths.0.iter().zip(restored.0.iter()) {
443+
assert_eq!(orig.leaf_index, rest.leaf_index);
444+
assert_eq!(orig.leaf_data, rest.leaf_data);
445+
assert_eq!(orig.sibling_hashes, rest.sibling_hashes);
446+
}
444447
}
445448
}

0 commit comments

Comments
 (0)