Skip to content

Commit d47b17a

Browse files
committed
test(aggregator): test receipt deduplication with malleated signature
Signed-off-by: Joseph Livesey <[email protected]>
1 parent 2badfe0 commit d47b17a

File tree

1 file changed

+64
-1
lines changed
  • tap_aggregator/src/aggregator

1 file changed

+64
-1
lines changed

tap_aggregator/src/aggregator/v1.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)