Skip to content

Commit 5f9f3ed

Browse files
authored
feat: use random nonces for AddPieces (#317)
Allows for non-sequential piece additions. PDPVerifier allocates its own pieceIds according to order added, we receive that as firstAdded and use it to refer to the same pieceIds as PDPVerifier. Now, instead of using firstAdded as a nonce, we allow for random nonces that the user now supplies separately in extraData. * Adds a new "nonce" into extraData * Repurposes clientDataSetIds as clientNonces * Moves signature to the end of extraData to be consistent with CreateDataSet Closes: #305
1 parent c036c20 commit 5f9f3ed

12 files changed

+320
-91
lines changed

service_contracts/abi/FilecoinWarmStorageServiceStateLibrary.abi.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
},
2121
{
2222
"type": "function",
23-
"name": "clientDataSetIds",
23+
"name": "clientDataSets",
2424
"inputs": [
2525
{
2626
"name": "service",
@@ -31,25 +31,20 @@
3131
"name": "payer",
3232
"type": "address",
3333
"internalType": "address"
34-
},
35-
{
36-
"name": "clientDataSetId",
37-
"type": "uint256",
38-
"internalType": "uint256"
3934
}
4035
],
4136
"outputs": [
4237
{
43-
"name": "",
44-
"type": "uint256",
45-
"internalType": "uint256"
38+
"name": "dataSetIds",
39+
"type": "uint256[]",
40+
"internalType": "uint256[]"
4641
}
4742
],
4843
"stateMutability": "view"
4944
},
5045
{
5146
"type": "function",
52-
"name": "clientDataSets",
47+
"name": "clientNonces",
5348
"inputs": [
5449
{
5550
"name": "service",
@@ -60,13 +55,18 @@
6055
"name": "payer",
6156
"type": "address",
6257
"internalType": "address"
58+
},
59+
{
60+
"name": "nonce",
61+
"type": "uint256",
62+
"internalType": "uint256"
6363
}
6464
],
6565
"outputs": [
6666
{
67-
"name": "dataSetIds",
68-
"type": "uint256[]",
69-
"internalType": "uint256[]"
67+
"name": "",
68+
"type": "uint256",
69+
"internalType": "uint256"
7070
}
7171
],
7272
"stateMutability": "view"

service_contracts/abi/FilecoinWarmStorageServiceStateView.abi.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,43 +25,43 @@
2525
},
2626
{
2727
"type": "function",
28-
"name": "clientDataSetIds",
28+
"name": "clientDataSets",
2929
"inputs": [
3030
{
3131
"name": "payer",
3232
"type": "address",
3333
"internalType": "address"
34-
},
35-
{
36-
"name": "clientDataSetId",
37-
"type": "uint256",
38-
"internalType": "uint256"
3934
}
4035
],
4136
"outputs": [
4237
{
43-
"name": "",
44-
"type": "uint256",
45-
"internalType": "uint256"
38+
"name": "dataSetIds",
39+
"type": "uint256[]",
40+
"internalType": "uint256[]"
4641
}
4742
],
4843
"stateMutability": "view"
4944
},
5045
{
5146
"type": "function",
52-
"name": "clientDataSets",
47+
"name": "clientNonces",
5348
"inputs": [
5449
{
5550
"name": "payer",
5651
"type": "address",
5752
"internalType": "address"
53+
},
54+
{
55+
"name": "nonce",
56+
"type": "uint256",
57+
"internalType": "uint256"
5858
}
5959
],
6060
"outputs": [
6161
{
62-
"name": "dataSetIds",
63-
"type": "uint256[]",
64-
"internalType": "uint256[]"
62+
"name": "",
63+
"type": "uint256",
64+
"internalType": "uint256"
6565
}
6666
],
6767
"stateMutability": "view"

service_contracts/src/FilecoinWarmStorageService.sol

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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

service_contracts/src/FilecoinWarmStorageServiceStateView.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ contract FilecoinWarmStorageServiceStateView is IPDPProvingSchedule {
2222
return service.challengeWindow();
2323
}
2424

25-
function clientDataSetIds(address payer, uint256 clientDataSetId) external view returns (uint256) {
26-
return service.clientDataSetIds(payer, clientDataSetId);
27-
}
28-
2925
function clientDataSets(address payer) external view returns (uint256[] memory dataSetIds) {
3026
return service.clientDataSets(payer);
3127
}
3228

29+
function clientNonces(address payer, uint256 nonce) external view returns (uint256) {
30+
return service.clientNonces(payer, nonce);
31+
}
32+
3333
function filBeamControllerAddress() external view returns (address) {
3434
return service.filBeamControllerAddress();
3535
}

service_contracts/src/lib/FilecoinWarmStorageServiceLayout.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ bytes32 constant PROVING_ACTIVATION_EPOCH_SLOT = bytes32(uint256(4));
1313
bytes32 constant PROVING_DEADLINES_SLOT = bytes32(uint256(5));
1414
bytes32 constant PROVEN_THIS_PERIOD_SLOT = bytes32(uint256(6));
1515
bytes32 constant DATA_SET_INFO_SLOT = bytes32(uint256(7));
16-
bytes32 constant CLIENT_DATA_SET_IDS_SLOT = bytes32(uint256(8));
16+
bytes32 constant CLIENT_NONCES_SLOT = bytes32(uint256(8));
1717
bytes32 constant CLIENT_DATA_SETS_SLOT = bytes32(uint256(9));
1818
bytes32 constant RAIL_TO_DATA_SET_SLOT = bytes32(uint256(10));
1919
bytes32 constant DATA_SET_METADATA_SLOT = bytes32(uint256(11));

service_contracts/src/lib/FilecoinWarmStorageServiceStateInternalLibrary.sol

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,14 @@ library FilecoinWarmStorageServiceStateInternalLibrary {
7878
return CHALLENGES_PER_PROOF;
7979
}
8080

81-
function clientDataSetIds(FilecoinWarmStorageService service, address payer, uint256 clientDataSetId)
81+
function clientNonces(FilecoinWarmStorageService service, address payer, uint256 nonce)
8282
internal
8383
view
8484
returns (uint256)
8585
{
8686
return uint256(
8787
service.extsload(
88-
keccak256(
89-
abi.encode(clientDataSetId, keccak256(abi.encode(payer, StorageLayout.CLIENT_DATA_SET_IDS_SLOT)))
90-
)
88+
keccak256(abi.encode(nonce, keccak256(abi.encode(payer, StorageLayout.CLIENT_NONCES_SLOT))))
9189
)
9290
);
9391
}

service_contracts/src/lib/FilecoinWarmStorageServiceStateLibrary.sol

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,14 @@ library FilecoinWarmStorageServiceStateLibrary {
7474
return CHALLENGES_PER_PROOF;
7575
}
7676

77-
function clientDataSetIds(FilecoinWarmStorageService service, address payer, uint256 clientDataSetId)
77+
function clientNonces(FilecoinWarmStorageService service, address payer, uint256 nonce)
7878
public
7979
view
8080
returns (uint256)
8181
{
8282
return uint256(
8383
service.extsload(
84-
keccak256(
85-
abi.encode(clientDataSetId, keccak256(abi.encode(payer, StorageLayout.CLIENT_DATA_SET_IDS_SLOT)))
86-
)
84+
keccak256(abi.encode(nonce, keccak256(abi.encode(payer, StorageLayout.CLIENT_NONCES_SLOT))))
8785
)
8886
);
8987
}

service_contracts/src/lib/SignatureVerificationLib.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ library SignatureVerificationLib {
2626
keccak256("PieceMetadata(uint256 pieceIndex,MetadataEntry[] metadata)MetadataEntry(string key,string value)");
2727

2828
bytes32 private constant ADD_PIECES_TYPEHASH = keccak256(
29-
"AddPieces(uint256 clientDataSetId,uint256 firstAdded,Cid[] pieceData,PieceMetadata[] pieceMetadata)"
29+
"AddPieces(uint256 clientDataSetId,uint256 nonce,Cid[] pieceData,PieceMetadata[] pieceMetadata)"
3030
"Cid(bytes data)" "MetadataEntry(string key,string value)"
3131
"PieceMetadata(uint256 pieceIndex,MetadataEntry[] metadata)"
3232
);
@@ -84,7 +84,7 @@ library SignatureVerificationLib {
8484

8585
function addPiecesStructHash(
8686
uint256 clientDataSetId,
87-
uint256 firstAdded,
87+
uint256 nonce,
8888
Cids.Cid[] calldata pieceDataArray,
8989
string[][] calldata allKeys,
9090
string[][] calldata allValues
@@ -93,7 +93,7 @@ library SignatureVerificationLib {
9393
abi.encode(
9494
ADD_PIECES_TYPEHASH,
9595
clientDataSetId,
96-
firstAdded,
96+
nonce,
9797
hashAllCids(pieceDataArray),
9898
hashAllPieceMetadata(allKeys, allValues)
9999
)

0 commit comments

Comments
 (0)