@@ -136,7 +136,9 @@ mod tests {
136136 use tap_core:: { signed_message:: Eip712SignedMessage , tap_eip712_domain} ;
137137 use tap_graph:: { Receipt , ReceiptAggregateVoucher } ;
138138 use thegraph_core:: alloy:: {
139- dyn_abi:: Eip712Domain , primitives:: Address , signers:: local:: PrivateKeySigner ,
139+ dyn_abi:: Eip712Domain ,
140+ primitives:: { Address , U256 } ,
141+ signers:: { local:: PrivateKeySigner , Signature } ,
140142 } ;
141143
142144 use super :: * ;
@@ -163,6 +165,67 @@ mod tests {
163165 tap_eip712_domain ( 1 , Address :: from ( [ 0x11u8 ; 20 ] ) )
164166 }
165167
168+ #[ rstest]
169+ #[ test]
170+ #[ should_panic]
171+ fn test_signature_malleability_vulnerability (
172+ keys : ( PrivateKeySigner , Address ) ,
173+ allocation_ids : Vec < Address > ,
174+ domain_separator : Eip712Domain ,
175+ ) {
176+ // Create a test receipt
177+ let receipt = Eip712SignedMessage :: new (
178+ & domain_separator,
179+ Receipt :: new ( allocation_ids[ 0 ] , 42 ) . unwrap ( ) ,
180+ & keys. 0 ,
181+ )
182+ . unwrap ( ) ;
183+
184+ // Get the original signature components
185+ let r = receipt. signature . r ( ) ;
186+ let s = receipt. signature . s ( ) ;
187+ let v = receipt. signature . v ( ) ;
188+
189+ // Create a malleated signature by changing the s value and flipping v
190+ // Get the Secp256k1 curve order
191+ let n = U256 :: from_str_radix (
192+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" ,
193+ 16 ,
194+ )
195+ . unwrap ( ) ;
196+ let s_malleated = n - s;
197+ let v_malleated = !v; // Flip the parity bit
198+
199+ // Create a new signature with the malleated components
200+ let signature_malleated = Signature :: new ( r, s_malleated, v_malleated) ;
201+
202+ // Create a new signed message with the malleated signature but same message
203+ let receipt_malleated = Eip712SignedMessage {
204+ message : receipt. message . clone ( ) ,
205+ signature : signature_malleated,
206+ } ;
207+
208+ // Verify that both signatures recover to the same signer
209+ let original_signer = receipt. recover_signer ( & domain_separator) . unwrap ( ) ;
210+ let malleated_signer = receipt_malleated. recover_signer ( & domain_separator) . unwrap ( ) ;
211+
212+ assert_eq ! (
213+ original_signer, malleated_signer,
214+ "Both signatures should recover to the same signer"
215+ ) ;
216+
217+ // Try to check if signatures are unique using the current implementation
218+ let receipts = vec ! [ receipt, receipt_malleated] ;
219+
220+ // This should return an error because the signatures are different
221+ // but the messages are the same, which if allowed would present a security vulnerability
222+ let result = check_signatures_unique ( & receipts) ;
223+
224+ // The result should be an error because the malleated signature is not treated as unique
225+ // and is detected as a duplicate
226+ assert ! ( result. is_err( ) ) ;
227+ }
228+
166229 #[ rstest]
167230 #[ test]
168231 fn check_signatures_unique_fail (
0 commit comments