@@ -236,7 +236,13 @@ contract FilecoinWarmStorageService is
236236 mapping (uint256 dataSetId = > bool ) private provenThisPeriod;
237237
238238 mapping (uint256 dataSetId = > DataSetInfo) private dataSetInfo;
239- mapping (address payer = > mapping (uint256 clientDataSetId = > uint256 )) private clientDataSetIds;
239+
240+ // Replay protection: tracks used nonces for both CreateDataSet and AddPieces operations.
241+ // Stores packed data: upper 128 bits = cumulative piece count after AddPieces or 0 for CreateDataSet,
242+ // lower 128 bits = dataSetId. For AddPieces, stores (firstAdded + pieceData.length) which is the
243+ // next piece ID that would be assigned, providing historical data about dataset state after the operation.
244+ mapping (address payer = > mapping (uint256 nonce = > uint256 )) private clientNonces;
245+
240246 mapping (address payer = > uint256 []) private clientDataSets;
241247 mapping (uint256 pdpRailId = > uint256 ) private railToDataSet;
242248
@@ -524,10 +530,10 @@ contract FilecoinWarmStorageService is
524530 address payee = serviceProviderRegistry.getProviderPayee (providerId);
525531
526532 require (
527- clientDataSetIds [createData.payer][createData.clientDataSetId] == 0 ,
533+ clientNonces [createData.payer][createData.clientDataSetId] == 0 ,
528534 Errors.ClientDataSetAlreadyRegistered (createData.clientDataSetId)
529535 );
530- clientDataSetIds [createData.payer][createData.clientDataSetId] = dataSetId;
536+ clientNonces [createData.payer][createData.clientDataSetId] = dataSetId;
531537 clientDataSets[createData.payer].push (dataSetId);
532538
533539 // Verify the client's signature
@@ -668,7 +674,7 @@ contract FilecoinWarmStorageService is
668674 Errors.PaymentRailsNotFinalized (dataSetId, info.pdpEndEpoch)
669675 );
670676
671- // NOTE keep clientDataSetIds [payer][clientDataSetId] to prevent replay
677+ // NOTE keep clientNonces [payer][clientDataSetId] to prevent replay
672678
673679 // Remove from client's dataset list
674680 uint256 [] storage clientDataSetList = clientDataSets[payer];
@@ -704,11 +710,11 @@ contract FilecoinWarmStorageService is
704710
705711 /**
706712 * @notice Handles pieces being added to a data set and stores associated metadata
707- * @dev Called by the PDPVerifier contract when pieces are added to a data set
713+ * @dev Called by the PDPVerifier contract when pieces are added to a data set.
708714 * @param dataSetId The ID of the data set
709- * @param firstAdded The ID of the first piece added
715+ * @param firstAdded The ID of the first piece added (from PDPVerifier, used for piece ID assignment)
710716 * @param pieceData Array of piece data objects
711- * @param extraData Encoded metadata, and signature
717+ * @param extraData Encoded (nonce, metadata keys, metadata values, signature)
712718 */
713719 function piecesAdded (uint256 dataSetId , uint256 firstAdded , Cids.Cid[] memory pieceData , bytes calldata extraData )
714720 external
@@ -723,8 +729,13 @@ contract FilecoinWarmStorageService is
723729 address payer = info.payer;
724730 require (extraData.length > 0 , Errors.ExtraDataRequired ());
725731 // Decode the extra data
726- (bytes memory signature , string [][] memory metadataKeys , string [][] memory metadataValues ) =
727- abi.decode (extraData, (bytes , string [][], string [][]));
732+ (uint256 nonce , string [][] memory metadataKeys , string [][] memory metadataValues , bytes memory signature ) =
733+ abi.decode (extraData, (uint256 , string [][], string [][], bytes ));
734+
735+ // Validate nonce hasn't been used (replay protection)
736+ require (clientNonces[payer][nonce] == 0 , Errors.ClientDataSetAlreadyRegistered (nonce));
737+ // Mark nonce as used, storing cumulative piece count (next piece ID) in upper bits
738+ clientNonces[payer][nonce] = ((firstAdded + pieceData.length ) << 128 ) | dataSetId;
728739
729740 // Check that we have metadata arrays for each piece
730741 require (
@@ -737,9 +748,7 @@ contract FilecoinWarmStorageService is
737748 );
738749
739750 // Verify the signature
740- verifyAddPiecesSignature (
741- payer, info.clientDataSetId, pieceData, firstAdded, metadataKeys, metadataValues, signature
742- );
751+ verifyAddPiecesSignature (payer, info.clientDataSetId, pieceData, nonce, metadataKeys, metadataValues, signature);
743752
744753 // Store metadata for each new piece
745754 for (uint256 i = 0 ; i < pieceData.length ; i++ ) {
@@ -1354,7 +1363,7 @@ contract FilecoinWarmStorageService is
13541363 * @param payer The address of the payer who should have signed the message
13551364 * @param clientDataSetId The ID of the data set
13561365 * @param pieceDataArray Array of piece CID structures
1357- * @param firstAdded The first piece ID being added
1366+ * @param nonce Client-chosen nonce for replay protection
13581367 * @param allKeys 2D array where allKeys[i] contains metadata keys for piece i
13591368 * @param allValues 2D array where allValues[i] contains metadata values for piece i
13601369 * @param signature The signature bytes (v, r, s)
@@ -1363,16 +1372,14 @@ contract FilecoinWarmStorageService is
13631372 address payer ,
13641373 uint256 clientDataSetId ,
13651374 Cids.Cid[] memory pieceDataArray ,
1366- uint256 firstAdded ,
1375+ uint256 nonce ,
13671376 string [][] memory allKeys ,
13681377 string [][] memory allValues ,
13691378 bytes memory signature
13701379 ) internal view {
13711380 // Compute the EIP-712 digest
13721381 bytes32 digest = _hashTypedDataV4 (
1373- SignatureVerificationLib.addPiecesStructHash (
1374- clientDataSetId, firstAdded, pieceDataArray, allKeys, allValues
1375- )
1382+ SignatureVerificationLib.addPiecesStructHash (clientDataSetId, nonce, pieceDataArray, allKeys, allValues)
13761383 );
13771384
13781385 // Delegate to library for verification
0 commit comments