@@ -110,3 +110,114 @@ impl<M: SolStruct> Eip712SignedMessage<M> {
110110 MessageId ( self . message . eip712_hash_struct ( ) . into ( ) )
111111 }
112112}
113+
114+ #[ cfg( test) ]
115+ mod tests {
116+ use super :: * ;
117+ use thegraph_core:: alloy:: {
118+ primitives:: { Address , Signature , U256 } ,
119+ signers:: local:: PrivateKeySigner ,
120+ sol_types:: eip712_domain,
121+ } ;
122+
123+ #[ test]
124+ fn test_signature_malleability_resistance ( ) {
125+ // Create a domain separator for testing
126+ let domain_separator = eip712_domain ! {
127+ name: "TAP" ,
128+ version: "1" ,
129+ chain_id: 1 ,
130+ verifying_contract: Address :: from( [ 0x11u8 ; 20 ] ) ,
131+ } ;
132+
133+ let test_value = 100u128 ;
134+ let test_address = Address :: from ( [ 0x11u8 ; 20 ] ) ;
135+ let message = msg:: Receipt :: new ( test_address, test_value) . unwrap ( ) ;
136+
137+ // Create a new Ethereum signer
138+ let signer = PrivateKeySigner :: random ( ) ;
139+
140+ // Create signed message using the original signature
141+ let signed_message =
142+ Eip712SignedMessage :: new ( & domain_separator, message. clone ( ) , & signer) . unwrap ( ) ;
143+
144+ // Get the original signature components
145+ let r = signed_message. signature . r ( ) ;
146+ let s = signed_message. signature . s ( ) ;
147+ let v = signed_message. signature . v ( ) ;
148+
149+ // Create a malleated signature by changing the s value
150+ // Get the Secp256k1 curve order
151+ let n = U256 :: from_str_radix (
152+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" ,
153+ 16 ,
154+ )
155+ . unwrap ( ) ;
156+ let s_malleated = n - s;
157+ let v_malleated = !v; // Flip the parity bit
158+
159+ // Create a new signature with the malleated components
160+ let signature_malleated = Signature :: new ( r, s_malleated, v_malleated) ;
161+
162+ // Create a new signed message with the malleated signature
163+ let signed_message_malleated = Eip712SignedMessage {
164+ message : message. clone ( ) ,
165+ signature : signature_malleated,
166+ } ;
167+
168+ // Verify both signatures recover to the same signer
169+ let signer_address = signer. address ( ) ;
170+ let recovered_address = signed_message. recover_signer ( & domain_separator) . unwrap ( ) ;
171+ let recovered_address_malleated = signed_message_malleated
172+ . recover_signer ( & domain_separator)
173+ . unwrap ( ) ;
174+
175+ assert_eq ! (
176+ recovered_address, signer_address,
177+ "Original signature should recover to the correct address"
178+ ) ;
179+ assert_eq ! (
180+ recovered_address_malleated, signer_address,
181+ "Malleated signature should recover to the same address"
182+ ) ;
183+
184+ // Verify that the raw signatures are different
185+ assert_ne ! (
186+ signed_message. signature. as_bytes( ) ,
187+ signed_message_malleated. signature. as_bytes( ) ,
188+ "Raw signature bytes should be different"
189+ ) ;
190+
191+ // WITHOUT our fix, these would generate different IDs:
192+ let raw_sig_id_1 = signed_message. signature . get_signature_bytes ( ) ;
193+ let raw_sig_id_2 = signed_message_malleated. signature . get_signature_bytes ( ) ;
194+ assert_ne ! (
195+ raw_sig_id_1, raw_sig_id_2,
196+ "Using only raw signatures would fail to detect duplicates"
197+ ) ;
198+
199+ // WITH our fix, these should generate the same unique ID:
200+ let unique_id_1 = signed_message. unique_hash ( & domain_separator) . unwrap ( ) ;
201+ let unique_id_2 = signed_message_malleated
202+ . unique_hash ( & domain_separator)
203+ . unwrap ( ) ;
204+
205+ assert_eq ! (
206+ unique_id_1, unique_id_2,
207+ "Our fix should generate the same ID for both original and malleated signatures"
208+ ) ;
209+
210+ // Bonus: Verify that different messages generate different IDs
211+ let different_message = msg:: Receipt :: new ( test_address, test_value + 1 ) . unwrap ( ) ;
212+ let different_signed_message =
213+ Eip712SignedMessage :: new ( & domain_separator, different_message, & signer) . unwrap ( ) ;
214+ let different_unique_id = different_signed_message
215+ . unique_hash ( & domain_separator)
216+ . unwrap ( ) ;
217+
218+ assert_ne ! (
219+ unique_id_1, different_unique_id,
220+ "Different messages should generate different unique IDs"
221+ ) ;
222+ }
223+ }
0 commit comments