Skip to content

Commit c4fb05f

Browse files
authored
test(proofs): 5/6 enable ignored range proof tests and add negative tests (#1834)
## Why this should be merged Un-ignores all 14 range proof tests that were blocked on issue #738 (full trie reconstruction). These tests now pass with the verification logic from the previous commit. Adds three negative tests that construct tampered RangeProofs with valid edge proofs and assert that verify_range_proof rejects them: - test_bad_range_proof_modified_key: flips a bit in a middle key - test_bad_range_proof_modified_value: flips a bit in a middle value - test_bad_range_proof_gapped_entries: removes a middle entry Fuzz testing is in a later commit. ## How this works new tests ## How this was tested CI ## Breaking Changes None
1 parent ba1f789 commit c4fb05f

File tree

1 file changed

+123
-18
lines changed

1 file changed

+123
-18
lines changed

firewood/src/merkle/tests/range.rs

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,129 @@ fn test_bad_range_proof_out_of_order() {
119119
}
120120

121121
#[test]
122-
// Tests malformed proof scenarios that require full trie reconstruction to detect:
123-
// modified keys, modified values, gapped entries, empty keys, and nil values.
124-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
125-
fn test_bad_range_proof_malformed() {
126-
// TODO: Re-enable once full range proof verification (trie reconstruction
127-
// and root hash comparison) is implemented.
122+
// Detects a modified key via trie reconstruction and root hash mismatch.
123+
fn test_bad_range_proof_modified_key() {
124+
let rng = firewood_storage::SeededRng::from_env_or_random();
125+
126+
let set = fixed_and_pseudorandom_data(&rng, 4096);
127+
let mut items = set.iter().collect::<Vec<_>>();
128+
items.sort_unstable();
129+
let merkle = init_merkle(items.clone());
130+
let root_hash = merkle.nodestore().root_hash().unwrap();
131+
132+
let start = 100;
133+
let end = 200;
134+
135+
let start_proof = merkle.prove(items[start].0).unwrap();
136+
let end_proof = merkle.prove(items[end - 1].0).unwrap();
137+
138+
let mut kvs: KeyValuePairs = items[start..end]
139+
.iter()
140+
.map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice()))
141+
.collect();
142+
143+
let mid = kvs.len() / 2;
144+
let mut key = kvs[mid].0.to_vec();
145+
key[0] ^= 0x01;
146+
kvs[mid].0 = key.into_boxed_slice();
147+
kvs.sort_by(|(a, _), (b, _)| a.cmp(b));
148+
149+
let range_proof = RangeProof::new(start_proof, end_proof, kvs.into_boxed_slice());
150+
assert!(
151+
verify_range_proof(
152+
Some(items[start].0),
153+
Some(items[end - 1].0),
154+
&root_hash,
155+
&range_proof,
156+
)
157+
.is_err(),
158+
"modified key should be detected"
159+
);
160+
}
161+
162+
#[test]
163+
// Detects a modified value via trie reconstruction and root hash mismatch.
164+
// In ethhash mode, account values at depth 32 have their storageRoot field
165+
// replaced with a computed hash during hashing. A blind XOR on the raw value
166+
// may only affect the storageRoot (or the RLP header that wraps it), making
167+
// the modification invisible to the hash. This test is not meaningful under
168+
// ethhash because the hashing intentionally ignores part of the value.
169+
#[cfg(not(feature = "ethhash"))]
170+
fn test_bad_range_proof_modified_value() {
171+
let rng = firewood_storage::SeededRng::from_env_or_random();
172+
173+
let set = fixed_and_pseudorandom_data(&rng, 4096);
174+
let mut items = set.iter().collect::<Vec<_>>();
175+
items.sort_unstable();
176+
let merkle = init_merkle(items.clone());
177+
let root_hash = merkle.nodestore().root_hash().unwrap();
178+
179+
let start = 100;
180+
let end = 200;
181+
182+
let start_proof = merkle.prove(items[start].0).unwrap();
183+
let end_proof = merkle.prove(items[end - 1].0).unwrap();
184+
185+
let mut kvs: KeyValuePairs = items[start..end]
186+
.iter()
187+
.map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice()))
188+
.collect();
189+
190+
let mid = kvs.len() / 2;
191+
let mut val = kvs[mid].1.to_vec();
192+
val[0] ^= 0x01;
193+
kvs[mid].1 = val.into_boxed_slice();
194+
195+
let range_proof = RangeProof::new(start_proof, end_proof, kvs.into_boxed_slice());
196+
assert!(
197+
verify_range_proof(
198+
Some(items[start].0),
199+
Some(items[end - 1].0),
200+
&root_hash,
201+
&range_proof,
202+
)
203+
.is_err(),
204+
"modified value should be detected"
205+
);
206+
}
207+
208+
#[test]
209+
// Detects gapped entries (missing middle element) via trie reconstruction
210+
// and root hash mismatch.
211+
fn test_bad_range_proof_gapped_entries() {
212+
let rng = firewood_storage::SeededRng::from_env_or_random();
213+
214+
let set = fixed_and_pseudorandom_data(&rng, 4096);
215+
let mut items = set.iter().collect::<Vec<_>>();
216+
items.sort_unstable();
217+
let merkle = init_merkle(items.clone());
218+
let root_hash = merkle.nodestore().root_hash().unwrap();
219+
220+
let start = 100;
221+
let end = 200;
222+
223+
let start_proof = merkle.prove(items[start].0).unwrap();
224+
let end_proof = merkle.prove(items[end - 1].0).unwrap();
225+
226+
let mut kvs: KeyValuePairs = items[start..end]
227+
.iter()
228+
.map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice()))
229+
.collect();
230+
231+
let mid = kvs.len() / 2;
232+
kvs.remove(mid);
233+
234+
let range_proof = RangeProof::new(start_proof, end_proof, kvs.into_boxed_slice());
235+
assert!(
236+
verify_range_proof(
237+
Some(items[start].0),
238+
Some(items[end - 1].0),
239+
&root_hash,
240+
&range_proof,
241+
)
242+
.is_err(),
243+
"gapped entries should be detected"
244+
);
128245
}
129246

130247
#[test]
@@ -204,7 +321,6 @@ fn test_range_proof_with_non_existent_proof() {
204321
// - There exists a gap between the first element and the left edge proof
205322
// - There exists a gap between the last element and the right edge proof
206323
// Detecting gaps requires full trie reconstruction, not yet implemented.
207-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
208324
fn test_range_proof_with_invalid_non_existent_proof() {
209325
let rng = firewood_storage::SeededRng::from_env_or_random();
210326

@@ -274,7 +390,6 @@ fn test_range_proof_with_invalid_non_existent_proof() {
274390
#[test]
275391
// Tests the proof with only one element. The first edge proof can be existent one or
276392
// non-existent one.
277-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
278393
fn test_one_element_range_proof() {
279394
let rng = firewood_storage::SeededRng::from_env_or_random();
280395

@@ -391,7 +506,6 @@ fn test_one_element_range_proof() {
391506
#[test]
392507
// Tests the range proof with all elements.
393508
// The edge proofs can be nil.
394-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
395509
fn test_all_elements_proof() {
396510
let rng = firewood_storage::SeededRng::from_env_or_random();
397511

@@ -444,7 +558,6 @@ fn test_all_elements_proof() {
444558
#[test]
445559
// Tests the range proof with "no" element. The first edge proof must
446560
// be a non-existent proof.
447-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
448561
fn test_empty_range_proof() {
449562
let rng = firewood_storage::SeededRng::from_env_or_random();
450563

@@ -479,7 +592,6 @@ fn test_empty_range_proof() {
479592
#[test]
480593
// Focuses on the small trie with embedded nodes. If the gapped
481594
// node is embedded in the trie, it should be detected too.
482-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
483595
fn test_gapped_range_proof() {
484596
let mut items = Vec::new();
485597
// Sorted entries
@@ -527,7 +639,6 @@ fn test_gapped_range_proof() {
527639

528640
#[test]
529641
// Tests the element is not in the range covered by proofs.
530-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
531642
fn test_same_side_proof() {
532643
let rng = firewood_storage::SeededRng::from_env_or_random();
533644

@@ -575,7 +686,6 @@ fn test_same_side_proof() {
575686

576687
#[test]
577688
// Tests the range starts from zero.
578-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
579689
fn test_single_side_range_proof() {
580690
let rng = firewood_storage::SeededRng::from_env_or_random();
581691

@@ -614,7 +724,6 @@ fn test_single_side_range_proof() {
614724

615725
#[test]
616726
// Tests the range ends with 0xffff...fff.
617-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
618727
fn test_reverse_single_side_range_proof() {
619728
let rng = firewood_storage::SeededRng::from_env_or_random();
620729

@@ -654,7 +763,6 @@ fn test_reverse_single_side_range_proof() {
654763

655764
#[test]
656765
// Tests the range starts with zero and ends with 0xffff...fff.
657-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
658766
fn test_both_sides_range_proof() {
659767
let rng = firewood_storage::SeededRng::from_env_or_random();
660768

@@ -691,7 +799,6 @@ fn test_both_sides_range_proof() {
691799
// Tests normal range proof with both edge proofs
692800
// as the existent proof, but with an extra empty value included, which is a
693801
// noop technically, but practically should be rejected.
694-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
695802
fn test_empty_value_range_proof() {
696803
let rng = firewood_storage::SeededRng::from_env_or_random();
697804

@@ -738,7 +845,6 @@ fn test_empty_value_range_proof() {
738845
// Tests the range proof with all elements,
739846
// but with an extra empty value included, which is a noop technically, but
740847
// practically should be rejected.
741-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
742848
fn test_all_elements_empty_value_range_proof() {
743849
let rng = firewood_storage::SeededRng::from_env_or_random();
744850

@@ -780,7 +886,6 @@ fn test_all_elements_empty_value_range_proof() {
780886
}
781887

782888
#[test]
783-
#[ignore = "https://github.com/ava-labs/firewood/issues/738"]
784889
fn test_range_proof_keys_with_shared_prefix() {
785890
let items = vec![
786891
(

0 commit comments

Comments
 (0)