@@ -489,9 +489,7 @@ fn test_prospective_insertion() {
489489
490490 let mutations = smt. compute_mutations ( vec ! [ ( key_2, value_2) ] ) . unwrap ( ) ;
491491 assert_eq ! ( mutations. root( ) , root_2, "prospective root 2 did not match actual root 2" ) ;
492- let mutations = smt
493- . compute_mutations ( vec ! [ ( key_3, EMPTY_WORD ) , ( key_2, value_2) , ( key_3, value_3) ] )
494- . unwrap ( ) ;
492+ let mutations = smt. compute_mutations ( vec ! [ ( key_2, value_2) , ( key_3, value_3) ] ) . unwrap ( ) ;
495493 assert_eq ! ( mutations. root( ) , root_3, "mutations before and after apply did not match" ) ;
496494 let old_root = smt. root ( ) ;
497495 let revert = apply_mutations ( & mut smt, mutations) ;
@@ -503,23 +501,8 @@ fn test_prospective_insertion() {
503501 "reverse mutations pairs did not match"
504502 ) ;
505503
506- // Edge case: multiple values at the same key, where a later pair restores the original value.
507- let mutations = smt. compute_mutations ( vec ! [ ( key_3, EMPTY_WORD ) , ( key_3, value_3) ] ) . unwrap ( ) ;
508- assert_eq ! ( mutations. root( ) , root_3) ;
509- let old_root = smt. root ( ) ;
510- let revert = apply_mutations ( & mut smt, mutations) ;
511- assert_eq ! ( smt. root( ) , root_3) ;
512- assert_eq ! ( revert. old_root, smt. root( ) , "reverse mutations old root did not match" ) ;
513- assert_eq ! ( revert. root( ) , old_root, "reverse mutations new root did not match" ) ;
514- assert_eq ! (
515- revert. new_pairs,
516- Map :: from_iter( [ ( key_3, value_3) ] ) ,
517- "reverse mutations pairs did not match"
518- ) ;
519-
520504 // Test batch updates, and that the order doesn't matter.
521- let pairs =
522- vec ! [ ( key_3, value_2) , ( key_2, EMPTY_WORD ) , ( key_1, EMPTY_WORD ) , ( key_3, EMPTY_WORD ) ] ;
505+ let pairs = vec ! [ ( key_3, EMPTY_WORD ) , ( key_2, EMPTY_WORD ) , ( key_1, EMPTY_WORD ) ] ;
523506 let mutations = smt. compute_mutations ( pairs) . unwrap ( ) ;
524507 assert_eq ! (
525508 mutations. root( ) ,
@@ -1064,6 +1047,116 @@ fn test_smt_leaf_try_from_elements_invalid_length() {
10641047 assert_matches ! ( result, Err ( SmtLeafError :: DecodingError ( _) ) ) ;
10651048}
10661049
1050+ // DUPLICATE KEY DETECTION
1051+ // --------------------------------------------------------------------------------------------
1052+
1053+ /// Tests that `compute_mutations` rejects duplicate keys (same key, same value).
1054+ #[ test]
1055+ fn test_compute_mutations_rejects_duplicate_keys ( ) {
1056+ use crate :: merkle:: MerkleError ;
1057+
1058+ let smt = Smt :: default ( ) ;
1059+ let key = Word :: from ( [ ONE , ONE , ONE , Felt :: new ( 42 ) ] ) ;
1060+ let value = Word :: new ( [ ONE ; WORD_SIZE ] ) ;
1061+
1062+ let result = smt. compute_mutations ( vec ! [ ( key, value) , ( key, value) ] ) ;
1063+
1064+ let expected_pos = Smt :: key_to_leaf_index ( & key) . position ( ) ;
1065+ assert_matches ! ( result, Err ( MerkleError :: DuplicateValuesForIndex ( pos) ) if pos == expected_pos) ;
1066+ }
1067+
1068+ /// Tests that `compute_mutations` rejects duplicate keys even with different values.
1069+ #[ test]
1070+ fn test_compute_mutations_rejects_duplicate_keys_different_values ( ) {
1071+ use crate :: merkle:: MerkleError ;
1072+
1073+ let smt = Smt :: default ( ) ;
1074+ let key = Word :: from ( [ ONE , ONE , ONE , Felt :: new ( 42 ) ] ) ;
1075+ let value_1 = Word :: new ( [ ONE ; WORD_SIZE ] ) ;
1076+ let value_2 = Word :: new ( [ Felt :: from_u32 ( 2_u32 ) ; WORD_SIZE ] ) ;
1077+
1078+ let result = smt. compute_mutations ( vec ! [ ( key, value_1) , ( key, value_2) ] ) ;
1079+
1080+ let expected_pos = Smt :: key_to_leaf_index ( & key) . position ( ) ;
1081+ assert_matches ! ( result, Err ( MerkleError :: DuplicateValuesForIndex ( pos) ) if pos == expected_pos) ;
1082+ }
1083+
1084+ /// Tests that `compute_mutations` rejects duplicate keys even when interleaved with another key
1085+ /// that shares the same leaf index: `[(k1, v1), (k2, v2), (k1, v3)]`.
1086+ #[ test]
1087+ fn test_compute_mutations_rejects_interleaved_duplicate_keys ( ) {
1088+ use crate :: merkle:: MerkleError ;
1089+
1090+ let smt = Smt :: default ( ) ;
1091+
1092+ // Two different keys that map to the same leaf (same most significant felt)
1093+ let key_1 = Word :: from ( [ ONE , ONE , ONE , Felt :: new ( 42 ) ] ) ;
1094+ let key_2 = Word :: from ( [ Felt :: new ( 2 ) , Felt :: new ( 2 ) , Felt :: new ( 2 ) , Felt :: new ( 42 ) ] ) ;
1095+
1096+ let value_1 = Word :: new ( [ ONE ; WORD_SIZE ] ) ;
1097+ let value_2 = Word :: new ( [ Felt :: from_u32 ( 2_u32 ) ; WORD_SIZE ] ) ;
1098+ let value_3 = Word :: new ( [ Felt :: from_u32 ( 3_u32 ) ; WORD_SIZE ] ) ;
1099+
1100+ // k1 appears at positions 0 and 2, interleaved with k2
1101+ let result = smt. compute_mutations ( vec ! [ ( key_1, value_1) , ( key_2, value_2) , ( key_1, value_3) ] ) ;
1102+
1103+ let expected_pos = Smt :: key_to_leaf_index ( & key_1) . position ( ) ;
1104+ assert_matches ! ( result, Err ( MerkleError :: DuplicateValuesForIndex ( pos) ) if pos == expected_pos) ;
1105+ }
1106+
1107+ /// Tests that different keys mapping to the same leaf index do NOT trigger the duplicate error.
1108+ #[ test]
1109+ fn test_compute_mutations_no_false_positives ( ) {
1110+ let smt = Smt :: default ( ) ;
1111+
1112+ // Two different keys that map to the same leaf (same most significant felt)
1113+ let key_1 = Word :: from ( [ ONE , ONE , ONE , Felt :: new ( 42 ) ] ) ;
1114+ let key_2 = Word :: from ( [ Felt :: new ( 2 ) , Felt :: new ( 2 ) , Felt :: new ( 2 ) , Felt :: new ( 42 ) ] ) ;
1115+
1116+ let value_1 = Word :: new ( [ ONE ; WORD_SIZE ] ) ;
1117+ let value_2 = Word :: new ( [ Felt :: from_u32 ( 2_u32 ) ; WORD_SIZE ] ) ;
1118+
1119+ // These are different keys (despite sharing a leaf index), so this should succeed.
1120+ let result = smt. compute_mutations ( vec ! [ ( key_1, value_1) , ( key_2, value_2) ] ) ;
1121+
1122+ assert ! ( result. is_ok( ) , "Different keys at the same leaf index should not be rejected" ) ;
1123+ }
1124+
1125+ /// Tests that `Smt::with_entries` rejects duplicate keys.
1126+ #[ test]
1127+ fn test_with_entries_rejects_duplicate_keys ( ) {
1128+ use crate :: merkle:: MerkleError ;
1129+
1130+ let key = Word :: from ( [ ONE , ONE , ONE , Felt :: new ( 42 ) ] ) ;
1131+ let value_1 = Word :: new ( [ ONE ; WORD_SIZE ] ) ;
1132+ let value_2 = Word :: new ( [ Felt :: from_u32 ( 2_u32 ) ; WORD_SIZE ] ) ;
1133+
1134+ let result = Smt :: with_entries ( vec ! [ ( key, value_1) , ( key, value_2) ] ) ;
1135+
1136+ let expected_pos = Smt :: key_to_leaf_index ( & key) . position ( ) ;
1137+ assert_matches ! ( result, Err ( MerkleError :: DuplicateValuesForIndex ( pos) ) if pos == expected_pos) ;
1138+ }
1139+
1140+ /// Tests that `Smt::with_entries` rejects interleaved duplicate keys.
1141+ #[ test]
1142+ fn test_with_entries_rejects_interleaved_duplicate_keys ( ) {
1143+ use crate :: merkle:: MerkleError ;
1144+
1145+ // Two different keys that map to the same leaf (same most significant felt)
1146+ let key_1 = Word :: from ( [ ONE , ONE , ONE , Felt :: new ( 42 ) ] ) ;
1147+ let key_2 = Word :: from ( [ Felt :: new ( 2 ) , Felt :: new ( 2 ) , Felt :: new ( 2 ) , Felt :: new ( 42 ) ] ) ;
1148+
1149+ let value_1 = Word :: new ( [ ONE ; WORD_SIZE ] ) ;
1150+ let value_2 = Word :: new ( [ Felt :: from_u32 ( 2_u32 ) ; WORD_SIZE ] ) ;
1151+ let value_3 = Word :: new ( [ Felt :: from_u32 ( 3_u32 ) ; WORD_SIZE ] ) ;
1152+
1153+ // k1 appears at positions 0 and 2, interleaved with k2
1154+ let result = Smt :: with_entries ( vec ! [ ( key_1, value_1) , ( key_2, value_2) , ( key_1, value_3) ] ) ;
1155+
1156+ let expected_pos = Smt :: key_to_leaf_index ( & key_1) . position ( ) ;
1157+ assert_matches ! ( result, Err ( MerkleError :: DuplicateValuesForIndex ( pos) ) if pos == expected_pos) ;
1158+ }
1159+
10671160// HELPERS
10681161// --------------------------------------------------------------------------------------------
10691162
0 commit comments