@@ -33,6 +33,26 @@ interface DelegatedUserDecryptRequest {
3333 defaultOptions ?: FhevmInstanceOptions ;
3434}
3535
36+ /**
37+ * Validates that every response item's extraData matches the request's extraData.
38+ * Returns the sanitized (no 0x prefix) request extraData for use in signature verification.
39+ */
40+ function validateAndSanitizeRequestExtraData (
41+ requestExtraData : BytesHex ,
42+ relayerUserDecryptResults : RelayerUserDecryptResult ,
43+ ) : string {
44+ const sanitized = requestExtraData . replace ( / ^ 0 x / , '' ) ;
45+ for ( const result of relayerUserDecryptResults ) {
46+ const responseExtraData = result . extraData . replace ( / ^ 0 x / , '' ) ;
47+ if ( responseExtraData . toLowerCase ( ) !== sanitized . toLowerCase ( ) ) {
48+ throw new Error (
49+ `Response extraData does not match request extraData: expected ${ requestExtraData } , got ${ result . extraData } ` ,
50+ ) ;
51+ }
52+ }
53+ return sanitized ;
54+ }
55+
3656// Add type checking
3757const getAddress = ( value : string ) : `0x${string } ` =>
3858 ethersGetAddress ( value ) as `0x${string } `;
@@ -285,6 +305,12 @@ export const userDecryptRequest =
285305 ...options ,
286306 } ) ;
287307
308+ // Validate that response extraData matches request to prevent server tampering
309+ const requestExtraDataSanitized = validateAndSanitizeRequestExtraData (
310+ extraData ,
311+ relayerUserDecryptResults ,
312+ ) ;
313+
288314 // Response side: resolve context-aware signers
289315 const effectiveSigners = await resolveEffectiveSigners (
290316 relayerUserDecryptResults ,
@@ -323,14 +349,16 @@ export const userDecryptRequest =
323349 h . handle . replace ( / ^ 0 x / , '' ) ,
324350 ) ,
325351 eip712_verifying_contract : verifyingContractAddressDecryption ,
352+ extra_data : requestExtraDataSanitized ,
326353 } ;
327354
328355 // Transform response to match node-tkms expected format
356+ // Use request extraData (not response) to bind signature verification to the original request
329357 const tkmsUserDecryptResults = relayerUserDecryptResults . map (
330358 ( result ) => ( {
331359 payload : result . payload ,
332360 signature : result . signature ,
333- extra_data : result . extraData . replace ( / ^ 0 x / , '' ) ,
361+ extra_data : requestExtraDataSanitized ,
334362 } ) ,
335363 ) ;
336364
@@ -426,6 +454,12 @@ export const delegatedUserDecryptRequest =
426454 } ,
427455 ) ;
428456
457+ // Validate that response extraData matches request to prevent server tampering
458+ const requestExtraDataSanitized = validateAndSanitizeRequestExtraData (
459+ extraData ,
460+ relayerUserDecryptResults ,
461+ ) ;
462+
429463 // Response side: resolve context-aware signers
430464 const effectiveSigners = await resolveEffectiveSigners (
431465 relayerUserDecryptResults ,
@@ -464,14 +498,16 @@ export const delegatedUserDecryptRequest =
464498 h . handle . replace ( / ^ 0 x / , '' ) ,
465499 ) ,
466500 eip712_verifying_contract : verifyingContractAddressDecryption ,
501+ extra_data : requestExtraDataSanitized ,
467502 } ;
468503
469504 // Transform response to match node-tkms expected format
505+ // Use request extraData (not response) to bind signature verification to the original request
470506 const tkmsUserDecryptResults = relayerUserDecryptResults . map (
471507 ( result ) => ( {
472508 payload : result . payload ,
473509 signature : result . signature ,
474- extra_data : result . extraData . replace ( / ^ 0 x / , '' ) ,
510+ extra_data : requestExtraDataSanitized ,
475511 } ) ,
476512 ) ;
477513
0 commit comments