@agirails/sdk 2.5.3 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ACTPClient.d.ts +18 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +72 -23
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +15 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +33 -4
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/StandardAdapter.d.ts +20 -3
- package/dist/adapters/StandardAdapter.d.ts.map +1 -1
- package/dist/adapters/StandardAdapter.js +90 -12
- package/dist/adapters/StandardAdapter.js.map +1 -1
- package/dist/cli/commands/publish.js +16 -4
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/register.js +16 -4
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/tx.js +31 -3
- package/dist/cli/commands/tx.js.map +1 -1
- package/dist/config/networks.d.ts +10 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +31 -22
- package/dist/config/networks.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +2 -1
- package/dist/level0/request.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +11 -5
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/utils/IPFSClient.d.ts +3 -1
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +27 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts +11 -1
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +84 -19
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/IWalletProvider.d.ts +34 -0
- package/dist/wallet/IWalletProvider.d.ts.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +128 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +248 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +26 -1
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +140 -6
- package/dist/wallet/aa/DualNonceManager.js.map +1 -1
- package/package.json +3 -6
- package/src/ACTPClient.ts +0 -1579
- package/src/abi/ACTPKernel.json +0 -1356
- package/src/abi/AgentRegistry.json +0 -915
- package/src/abi/ERC20.json +0 -40
- package/src/abi/EscrowVault.json +0 -134
- package/src/abi/IdentityRegistry.json +0 -316
- package/src/adapters/AdapterRegistry.ts +0 -173
- package/src/adapters/AdapterRouter.ts +0 -416
- package/src/adapters/BaseAdapter.ts +0 -498
- package/src/adapters/BasicAdapter.ts +0 -514
- package/src/adapters/IAdapter.ts +0 -292
- package/src/adapters/StandardAdapter.ts +0 -555
- package/src/adapters/X402Adapter.ts +0 -731
- package/src/adapters/index.ts +0 -60
- package/src/builders/DeliveryProofBuilder.ts +0 -327
- package/src/builders/QuoteBuilder.ts +0 -483
- package/src/builders/index.ts +0 -17
- package/src/cli/commands/balance.ts +0 -110
- package/src/cli/commands/batch.ts +0 -487
- package/src/cli/commands/config.ts +0 -231
- package/src/cli/commands/deploy-check.ts +0 -364
- package/src/cli/commands/deploy-env.ts +0 -120
- package/src/cli/commands/diff.ts +0 -141
- package/src/cli/commands/init.ts +0 -469
- package/src/cli/commands/mint.ts +0 -116
- package/src/cli/commands/pay.ts +0 -113
- package/src/cli/commands/publish.ts +0 -475
- package/src/cli/commands/pull.ts +0 -124
- package/src/cli/commands/register.ts +0 -247
- package/src/cli/commands/simulate.ts +0 -345
- package/src/cli/commands/time.ts +0 -302
- package/src/cli/commands/tx.ts +0 -448
- package/src/cli/commands/watch.ts +0 -211
- package/src/cli/index.ts +0 -134
- package/src/cli/utils/client.ts +0 -252
- package/src/cli/utils/config.ts +0 -389
- package/src/cli/utils/output.ts +0 -465
- package/src/cli/utils/wallet.ts +0 -109
- package/src/config/agirailsmd.ts +0 -262
- package/src/config/networks.ts +0 -275
- package/src/config/pendingPublish.ts +0 -237
- package/src/config/publishPipeline.ts +0 -359
- package/src/config/syncOperations.ts +0 -279
- package/src/erc8004/ERC8004Bridge.ts +0 -462
- package/src/erc8004/ReputationReporter.ts +0 -468
- package/src/erc8004/index.ts +0 -61
- package/src/errors/index.ts +0 -427
- package/src/index.ts +0 -364
- package/src/level0/Provider.ts +0 -117
- package/src/level0/ServiceDirectory.ts +0 -131
- package/src/level0/index.ts +0 -10
- package/src/level0/provide.ts +0 -132
- package/src/level0/request.ts +0 -432
- package/src/level1/Agent.ts +0 -1426
- package/src/level1/index.ts +0 -10
- package/src/level1/pricing/PriceCalculator.ts +0 -255
- package/src/level1/pricing/PricingStrategy.ts +0 -198
- package/src/level1/types/Job.ts +0 -179
- package/src/level1/types/Options.ts +0 -291
- package/src/level1/types/index.ts +0 -8
- package/src/protocol/ACTPKernel.ts +0 -808
- package/src/protocol/AgentRegistry.ts +0 -559
- package/src/protocol/DIDManager.ts +0 -629
- package/src/protocol/DIDResolver.ts +0 -554
- package/src/protocol/EASHelper.ts +0 -378
- package/src/protocol/EscrowVault.ts +0 -255
- package/src/protocol/EventMonitor.ts +0 -204
- package/src/protocol/MessageSigner.ts +0 -510
- package/src/protocol/ProofGenerator.ts +0 -339
- package/src/protocol/QuoteBuilder.ts +0 -15
- package/src/registry/AgentRegistryClient.ts +0 -202
- package/src/runtime/BlockchainRuntime.ts +0 -1015
- package/src/runtime/IACTPRuntime.ts +0 -306
- package/src/runtime/MockRuntime.ts +0 -1298
- package/src/runtime/MockStateManager.ts +0 -577
- package/src/runtime/index.ts +0 -25
- package/src/runtime/types/MockState.ts +0 -237
- package/src/storage/ArchiveBundleBuilder.ts +0 -561
- package/src/storage/ArweaveClient.ts +0 -946
- package/src/storage/FilebaseClient.ts +0 -790
- package/src/storage/index.ts +0 -96
- package/src/storage/types.ts +0 -348
- package/src/types/adapter.ts +0 -310
- package/src/types/agent.ts +0 -79
- package/src/types/did.ts +0 -223
- package/src/types/eip712.ts +0 -175
- package/src/types/erc8004.ts +0 -293
- package/src/types/escrow.ts +0 -27
- package/src/types/index.ts +0 -17
- package/src/types/message.ts +0 -145
- package/src/types/state.ts +0 -87
- package/src/types/transaction.ts +0 -69
- package/src/types/x402.ts +0 -251
- package/src/utils/ErrorRecoveryGuide.ts +0 -676
- package/src/utils/Helpers.ts +0 -688
- package/src/utils/IPFSClient.ts +0 -368
- package/src/utils/Logger.ts +0 -484
- package/src/utils/NonceManager.ts +0 -591
- package/src/utils/RateLimiter.ts +0 -534
- package/src/utils/ReceivedNonceTracker.ts +0 -567
- package/src/utils/SDKLifecycle.ts +0 -416
- package/src/utils/SecureNonce.ts +0 -78
- package/src/utils/Semaphore.ts +0 -276
- package/src/utils/UsedAttestationTracker.ts +0 -385
- package/src/utils/canonicalJson.ts +0 -38
- package/src/utils/circuitBreaker.ts +0 -324
- package/src/utils/computeTypeHash.ts +0 -48
- package/src/utils/fsSafe.ts +0 -80
- package/src/utils/index.ts +0 -80
- package/src/utils/retry.ts +0 -364
- package/src/utils/security.ts +0 -418
- package/src/utils/validation.ts +0 -540
- package/src/wallet/AutoWalletProvider.ts +0 -299
- package/src/wallet/EOAWalletProvider.ts +0 -69
- package/src/wallet/IWalletProvider.ts +0 -135
- package/src/wallet/aa/BundlerClient.ts +0 -274
- package/src/wallet/aa/DualNonceManager.ts +0 -173
- package/src/wallet/aa/PaymasterClient.ts +0 -174
- package/src/wallet/aa/TransactionBatcher.ts +0 -353
- package/src/wallet/aa/UserOpBuilder.ts +0 -246
- package/src/wallet/aa/constants.ts +0 -60
- package/src/wallet/keystore.ts +0 -240
|
@@ -1,567 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ReceivedNonceTracker - Replay Attack Prevention for Message Receivers
|
|
3
|
-
*
|
|
4
|
-
* This utility tracks nonces of received messages to prevent replay attacks.
|
|
5
|
-
* It works in conjunction with NonceManager (for senders) but serves the receiver side.
|
|
6
|
-
*
|
|
7
|
-
* Reference: V4 Security Vulnerability (EIP-712 Replay Attack)
|
|
8
|
-
*
|
|
9
|
-
* Usage Pattern:
|
|
10
|
-
* - Sender: Uses NonceManager to generate monotonically increasing nonces
|
|
11
|
-
* - Receiver: Uses ReceivedNonceTracker to validate and track received nonces
|
|
12
|
-
*
|
|
13
|
-
* Security Properties:
|
|
14
|
-
* 1. Nonces must be monotonically increasing per sender + message type
|
|
15
|
-
* 2. Duplicate nonces are rejected (replay attack prevention)
|
|
16
|
-
* 3. Nonces that are lower than the highest seen are rejected (old replay prevention)
|
|
17
|
-
*
|
|
18
|
-
* ⚠️ WARNING: In-memory tracking only. For production:
|
|
19
|
-
* - Use persistent storage (Redis, PostgreSQL, etc.)
|
|
20
|
-
* - Implement nonce recovery from transaction history
|
|
21
|
-
* - Consider nonce expiry for long-running processes
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Nonce validation result
|
|
26
|
-
*/
|
|
27
|
-
export interface NonceValidationResult {
|
|
28
|
-
valid: boolean;
|
|
29
|
-
reason?: string;
|
|
30
|
-
expectedMinimum?: string; // bytes32 format
|
|
31
|
-
receivedNonce?: string; // bytes32 format
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Interface for tracking received nonces
|
|
36
|
-
*/
|
|
37
|
-
export interface IReceivedNonceTracker {
|
|
38
|
-
/**
|
|
39
|
-
* Validate and record a received nonce
|
|
40
|
-
* @param sender - Sender DID (e.g., "did:ethr:0x...")
|
|
41
|
-
* @param messageType - Message type (e.g., "agirails.delivery.v1")
|
|
42
|
-
* @param nonce - Nonce value (bytes32 format: "0x...")
|
|
43
|
-
* @returns Validation result
|
|
44
|
-
*/
|
|
45
|
-
validateAndRecord(sender: string, messageType: string, nonce: string): NonceValidationResult;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Check if a nonce has been used (without recording)
|
|
49
|
-
* @param sender - Sender DID
|
|
50
|
-
* @param messageType - Message type
|
|
51
|
-
* @param nonce - Nonce value (bytes32 format)
|
|
52
|
-
* @returns true if nonce was already used
|
|
53
|
-
*/
|
|
54
|
-
hasBeenUsed(sender: string, messageType: string, nonce: string): boolean;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get highest nonce seen for sender + message type
|
|
58
|
-
* @param sender - Sender DID
|
|
59
|
-
* @param messageType - Message type
|
|
60
|
-
* @returns Highest nonce (bytes32 format) or null if none seen
|
|
61
|
-
*/
|
|
62
|
-
getHighestNonce(sender: string, messageType: string): string | null;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Reset tracking for a specific sender + message type
|
|
66
|
-
* @param sender - Sender DID
|
|
67
|
-
* @param messageType - Message type
|
|
68
|
-
*/
|
|
69
|
-
reset(sender: string, messageType: string): void;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Clear all tracked nonces
|
|
73
|
-
*/
|
|
74
|
-
clearAll(): void;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* In-Memory Received Nonce Tracker
|
|
79
|
-
*
|
|
80
|
-
* Strategy: Track highest nonce seen per sender + message type
|
|
81
|
-
* - Accept nonces that are strictly greater than the highest seen
|
|
82
|
-
* - Reject nonces that are <= highest seen (replay attack)
|
|
83
|
-
*
|
|
84
|
-
* Trade-off:
|
|
85
|
-
* - Memory efficient (one value per sender + type)
|
|
86
|
-
* - Requires ordered nonce sequences
|
|
87
|
-
* - Cannot skip nonces (nonce gaps are rejected)
|
|
88
|
-
*/
|
|
89
|
-
export class InMemoryReceivedNonceTracker implements IReceivedNonceTracker {
|
|
90
|
-
// Map: sender -> messageType -> highest nonce (as BigInt for comparison)
|
|
91
|
-
private highestNonces: Map<string, Map<string, bigint>> = new Map();
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Validate and record a received nonce
|
|
95
|
-
*/
|
|
96
|
-
validateAndRecord(sender: string, messageType: string, nonce: string): NonceValidationResult {
|
|
97
|
-
// Validate nonce format (must be bytes32: 0x + 64 hex chars)
|
|
98
|
-
if (!/^0x[a-fA-F0-9]{64}$/.test(nonce)) {
|
|
99
|
-
return {
|
|
100
|
-
valid: false,
|
|
101
|
-
reason: 'Invalid nonce format (must be bytes32)',
|
|
102
|
-
receivedNonce: nonce
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Convert nonce to BigInt for comparison
|
|
107
|
-
const nonceValue = BigInt(nonce);
|
|
108
|
-
|
|
109
|
-
// Get sender's nonce map
|
|
110
|
-
let senderNonces = this.highestNonces.get(sender);
|
|
111
|
-
if (!senderNonces) {
|
|
112
|
-
senderNonces = new Map<string, bigint>();
|
|
113
|
-
this.highestNonces.set(sender, senderNonces);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Get highest nonce for this message type
|
|
117
|
-
const highestNonce = senderNonces.get(messageType);
|
|
118
|
-
|
|
119
|
-
if (highestNonce === undefined) {
|
|
120
|
-
// First message from this sender for this type
|
|
121
|
-
senderNonces.set(messageType, nonceValue);
|
|
122
|
-
return { valid: true };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Nonce must be strictly greater than highest seen
|
|
126
|
-
if (nonceValue <= highestNonce) {
|
|
127
|
-
const expectedMinimum = '0x' + (highestNonce + BigInt(1)).toString(16).padStart(64, '0');
|
|
128
|
-
return {
|
|
129
|
-
valid: false,
|
|
130
|
-
reason: `Nonce replay detected: nonce must be > ${this.bigintToBytes32(highestNonce)}`,
|
|
131
|
-
expectedMinimum,
|
|
132
|
-
receivedNonce: nonce
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Valid nonce - record it
|
|
137
|
-
senderNonces.set(messageType, nonceValue);
|
|
138
|
-
return { valid: true };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Check if a nonce has been used (non-mutating)
|
|
143
|
-
*/
|
|
144
|
-
hasBeenUsed(sender: string, messageType: string, nonce: string): boolean {
|
|
145
|
-
const nonceValue = BigInt(nonce);
|
|
146
|
-
const senderNonces = this.highestNonces.get(sender);
|
|
147
|
-
|
|
148
|
-
if (!senderNonces) {
|
|
149
|
-
return false; // No nonces seen from this sender
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const highestNonce = senderNonces.get(messageType);
|
|
153
|
-
if (highestNonce === undefined) {
|
|
154
|
-
return false; // No nonces seen for this message type
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// If the provided nonce is <= highest seen, it's been "used"
|
|
158
|
-
return nonceValue <= highestNonce;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Get highest nonce seen
|
|
163
|
-
*/
|
|
164
|
-
getHighestNonce(sender: string, messageType: string): string | null {
|
|
165
|
-
const senderNonces = this.highestNonces.get(sender);
|
|
166
|
-
if (!senderNonces) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const highestNonce = senderNonces.get(messageType);
|
|
171
|
-
if (highestNonce === undefined) {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return this.bigintToBytes32(highestNonce);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Reset tracking for sender + message type
|
|
180
|
-
*/
|
|
181
|
-
reset(sender: string, messageType: string): void {
|
|
182
|
-
const senderNonces = this.highestNonces.get(sender);
|
|
183
|
-
if (senderNonces) {
|
|
184
|
-
senderNonces.delete(messageType);
|
|
185
|
-
// Clean up sender map if empty
|
|
186
|
-
if (senderNonces.size === 0) {
|
|
187
|
-
this.highestNonces.delete(sender);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Clear all tracked nonces
|
|
194
|
-
*/
|
|
195
|
-
clearAll(): void {
|
|
196
|
-
this.highestNonces.clear();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Convert BigInt to bytes32 hex string
|
|
201
|
-
*/
|
|
202
|
-
private bigintToBytes32(value: bigint): string {
|
|
203
|
-
return '0x' + value.toString(16).padStart(64, '0');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Get all nonces (for debugging/persistence)
|
|
208
|
-
*/
|
|
209
|
-
getAllNonces(): Record<string, Record<string, string>> {
|
|
210
|
-
const result: Record<string, Record<string, string>> = {};
|
|
211
|
-
|
|
212
|
-
this.highestNonces.forEach((senderNonces, sender) => {
|
|
213
|
-
result[sender] = {};
|
|
214
|
-
senderNonces.forEach((nonce, messageType) => {
|
|
215
|
-
result[sender][messageType] = this.bigintToBytes32(nonce);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
return result;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Set-Based Received Nonce Tracker
|
|
225
|
-
*
|
|
226
|
-
* Strategy: Track exact set of used nonces per sender + message type
|
|
227
|
-
* - Accept nonces that haven't been seen before
|
|
228
|
-
* - Reject duplicate nonces (replay attack)
|
|
229
|
-
* - Allows non-sequential nonces (nonce gaps are OK)
|
|
230
|
-
*
|
|
231
|
-
* SECURITY FIX (NEW-H-2): Max size enforcement to prevent memory exhaustion
|
|
232
|
-
* SECURITY FIX (HIGH-2): Global total entries limit to prevent DoS via many sender combinations
|
|
233
|
-
* SECURITY FIX (H-2): Rate limiting per sender to prevent flood attacks
|
|
234
|
-
*
|
|
235
|
-
* Trade-off:
|
|
236
|
-
* - Higher memory usage (stores every nonce)
|
|
237
|
-
* - More flexible (allows out-of-order delivery)
|
|
238
|
-
* - Requires periodic cleanup to prevent unbounded growth
|
|
239
|
-
*/
|
|
240
|
-
export class SetBasedReceivedNonceTracker implements IReceivedNonceTracker {
|
|
241
|
-
// Map: sender -> messageType -> Set of used nonces
|
|
242
|
-
private usedNonces: Map<string, Map<string, Set<string>>> = new Map();
|
|
243
|
-
|
|
244
|
-
// SECURITY FIX (NEW-H-2): Maximum entries per sender+messageType
|
|
245
|
-
private readonly maxSizePerType: number;
|
|
246
|
-
|
|
247
|
-
// SECURITY FIX (HIGH-2): Global limit across ALL sender+messageType combinations
|
|
248
|
-
private readonly maxTotalEntries: number;
|
|
249
|
-
private totalEntries: number = 0;
|
|
250
|
-
|
|
251
|
-
// SECURITY FIX (H-2): Rate limiting per sender
|
|
252
|
-
// Map: sender -> { count: number, windowStart: number }
|
|
253
|
-
private rateLimitState: Map<string, { count: number; windowStart: number }> = new Map();
|
|
254
|
-
private readonly maxNoncesPerMinute: number;
|
|
255
|
-
private readonly rateLimitWindowMs: number = 60000; // 1 minute window
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Create set-based tracker with optional max size
|
|
259
|
-
* @param maxSizePerType - Maximum nonces per sender+messageType (default: 10,000)
|
|
260
|
-
* @param maxTotalEntries - Maximum total nonces across all combinations (default: 100,000)
|
|
261
|
-
* @param maxNoncesPerMinute - Maximum nonces per sender per minute (default: 100)
|
|
262
|
-
*/
|
|
263
|
-
constructor(
|
|
264
|
-
maxSizePerType: number = 10000,
|
|
265
|
-
maxTotalEntries: number = 100000,
|
|
266
|
-
maxNoncesPerMinute: number = 100
|
|
267
|
-
) {
|
|
268
|
-
if (maxSizePerType <= 0) {
|
|
269
|
-
throw new Error('maxSizePerType must be positive');
|
|
270
|
-
}
|
|
271
|
-
if (maxTotalEntries <= 0) {
|
|
272
|
-
throw new Error('maxTotalEntries must be positive');
|
|
273
|
-
}
|
|
274
|
-
if (maxNoncesPerMinute <= 0) {
|
|
275
|
-
throw new Error('maxNoncesPerMinute must be positive');
|
|
276
|
-
}
|
|
277
|
-
this.maxSizePerType = maxSizePerType;
|
|
278
|
-
this.maxTotalEntries = maxTotalEntries;
|
|
279
|
-
this.maxNoncesPerMinute = maxNoncesPerMinute;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* SECURITY FIX (H-2): Check rate limit for sender
|
|
284
|
-
* @param sender - Sender DID
|
|
285
|
-
* @returns true if rate limit exceeded
|
|
286
|
-
*/
|
|
287
|
-
private checkRateLimit(sender: string): boolean {
|
|
288
|
-
const now = Date.now();
|
|
289
|
-
const state = this.rateLimitState.get(sender);
|
|
290
|
-
|
|
291
|
-
if (!state) {
|
|
292
|
-
// First nonce from this sender
|
|
293
|
-
this.rateLimitState.set(sender, { count: 1, windowStart: now });
|
|
294
|
-
return false; // Not rate limited
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Check if window expired (reset counter)
|
|
298
|
-
if (now - state.windowStart >= this.rateLimitWindowMs) {
|
|
299
|
-
state.count = 1;
|
|
300
|
-
state.windowStart = now;
|
|
301
|
-
return false; // Not rate limited
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Increment counter
|
|
305
|
-
state.count++;
|
|
306
|
-
|
|
307
|
-
// Check if rate limit exceeded
|
|
308
|
-
return state.count > this.maxNoncesPerMinute;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* SECURITY FIX (H-2): Periodic cleanup of rate limit state
|
|
313
|
-
* Removes expired rate limit entries (older than 5 minutes)
|
|
314
|
-
*/
|
|
315
|
-
private cleanupRateLimitState(): void {
|
|
316
|
-
const now = Date.now();
|
|
317
|
-
const expiryThreshold = 5 * 60 * 1000; // 5 minutes
|
|
318
|
-
|
|
319
|
-
for (const [sender, state] of this.rateLimitState.entries()) {
|
|
320
|
-
if (now - state.windowStart > expiryThreshold) {
|
|
321
|
-
this.rateLimitState.delete(sender);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Validate and record a received nonce
|
|
328
|
-
*
|
|
329
|
-
* SECURITY FIX (NEW-H-2): Automatic cleanup when max size reached
|
|
330
|
-
* SECURITY FIX (HIGH-2): Global limit check to prevent DoS
|
|
331
|
-
* SECURITY FIX (H-2): Rate limiting per sender (max 100 nonces/minute)
|
|
332
|
-
*/
|
|
333
|
-
validateAndRecord(sender: string, messageType: string, nonce: string): NonceValidationResult {
|
|
334
|
-
// Validate nonce format
|
|
335
|
-
if (!/^0x[a-fA-F0-9]{64}$/.test(nonce)) {
|
|
336
|
-
return {
|
|
337
|
-
valid: false,
|
|
338
|
-
reason: 'Invalid nonce format (must be bytes32)',
|
|
339
|
-
receivedNonce: nonce
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// SECURITY FIX (H-2): Rate limit check (BEFORE global limit to avoid unnecessary work)
|
|
344
|
-
if (this.checkRateLimit(sender)) {
|
|
345
|
-
return {
|
|
346
|
-
valid: false,
|
|
347
|
-
reason: `Rate limit exceeded for sender ${sender}: ` +
|
|
348
|
-
`Maximum ${this.maxNoncesPerMinute} nonces per minute allowed. ` +
|
|
349
|
-
`This may indicate a flood attack or misconfigured client. ` +
|
|
350
|
-
`Wait 1 minute before retrying.`,
|
|
351
|
-
receivedNonce: nonce
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// SECURITY FIX (H-2): Periodic cleanup every 100 validations (amortized cost)
|
|
356
|
-
if (this.totalEntries % 100 === 0) {
|
|
357
|
-
this.cleanupRateLimitState();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// SECURITY FIX (HIGH-2): Check global limit BEFORE adding
|
|
361
|
-
if (this.totalEntries >= this.maxTotalEntries) {
|
|
362
|
-
return {
|
|
363
|
-
valid: false,
|
|
364
|
-
reason: `Global nonce tracker limit reached (${this.maxTotalEntries} entries). ` +
|
|
365
|
-
`This may indicate a DoS attack or need for cleanup. ` +
|
|
366
|
-
`Current usage: ${this.totalEntries} entries across ${this.getCombinationCount()} sender+type combinations.`,
|
|
367
|
-
receivedNonce: nonce
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Get sender's nonce map
|
|
372
|
-
let senderNonces = this.usedNonces.get(sender);
|
|
373
|
-
if (!senderNonces) {
|
|
374
|
-
senderNonces = new Map<string, Set<string>>();
|
|
375
|
-
this.usedNonces.set(sender, senderNonces);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Get set of used nonces for this message type
|
|
379
|
-
let usedSet = senderNonces.get(messageType);
|
|
380
|
-
if (!usedSet) {
|
|
381
|
-
usedSet = new Set<string>();
|
|
382
|
-
senderNonces.set(messageType, usedSet);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Check if nonce was already used
|
|
386
|
-
if (usedSet.has(nonce)) {
|
|
387
|
-
return {
|
|
388
|
-
valid: false,
|
|
389
|
-
reason: 'Nonce replay detected: this nonce has already been used',
|
|
390
|
-
receivedNonce: nonce
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// SECURITY FIX (NEW-H-2): Auto-cleanup if max size per type reached
|
|
395
|
-
if (usedSet.size >= this.maxSizePerType) {
|
|
396
|
-
// Keep only last 80% of entries (sorted by nonce value)
|
|
397
|
-
const keepCount = Math.floor(this.maxSizePerType * 0.8);
|
|
398
|
-
const sortedNonces = Array.from(usedSet).sort((a, b) => {
|
|
399
|
-
const aVal = BigInt(a);
|
|
400
|
-
const bVal = BigInt(b);
|
|
401
|
-
return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
402
|
-
});
|
|
403
|
-
const removedCount = usedSet.size - keepCount;
|
|
404
|
-
usedSet = new Set(sortedNonces.slice(-keepCount));
|
|
405
|
-
senderNonces.set(messageType, usedSet);
|
|
406
|
-
// SECURITY FIX (HIGH-2): Update global counter
|
|
407
|
-
this.totalEntries -= removedCount;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Valid nonce - record it
|
|
411
|
-
usedSet.add(nonce);
|
|
412
|
-
// SECURITY FIX (HIGH-2): Update global counter
|
|
413
|
-
this.totalEntries++;
|
|
414
|
-
return { valid: true };
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Get number of sender+messageType combinations (for monitoring)
|
|
419
|
-
* SECURITY FIX (HIGH-2): Monitoring method
|
|
420
|
-
*/
|
|
421
|
-
private getCombinationCount(): number {
|
|
422
|
-
let count = 0;
|
|
423
|
-
this.usedNonces.forEach(senderMap => {
|
|
424
|
-
count += senderMap.size;
|
|
425
|
-
});
|
|
426
|
-
return count;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Get memory usage statistics
|
|
431
|
-
* SECURITY FIX (HIGH-2): Monitoring method for DoS detection
|
|
432
|
-
*/
|
|
433
|
-
getMemoryUsage(): { totalEntries: number; combinations: number; maxTotalEntries: number } {
|
|
434
|
-
return {
|
|
435
|
-
totalEntries: this.totalEntries,
|
|
436
|
-
combinations: this.getCombinationCount(),
|
|
437
|
-
maxTotalEntries: this.maxTotalEntries
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Check if a nonce has been used
|
|
443
|
-
*/
|
|
444
|
-
hasBeenUsed(sender: string, messageType: string, nonce: string): boolean {
|
|
445
|
-
const senderNonces = this.usedNonces.get(sender);
|
|
446
|
-
if (!senderNonces) {
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const usedSet = senderNonces.get(messageType);
|
|
451
|
-
if (!usedSet) {
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return usedSet.has(nonce);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Get highest nonce seen (for this strategy, compute from set)
|
|
460
|
-
*/
|
|
461
|
-
getHighestNonce(sender: string, messageType: string): string | null {
|
|
462
|
-
const senderNonces = this.usedNonces.get(sender);
|
|
463
|
-
if (!senderNonces) {
|
|
464
|
-
return null;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const usedSet = senderNonces.get(messageType);
|
|
468
|
-
if (!usedSet || usedSet.size === 0) {
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Find maximum nonce in set
|
|
473
|
-
let maxNonce = BigInt(0);
|
|
474
|
-
usedSet.forEach(nonce => {
|
|
475
|
-
const value = BigInt(nonce);
|
|
476
|
-
if (value > maxNonce) {
|
|
477
|
-
maxNonce = value;
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
return '0x' + maxNonce.toString(16).padStart(64, '0');
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Reset tracking for sender + message type
|
|
486
|
-
*/
|
|
487
|
-
reset(sender: string, messageType: string): void {
|
|
488
|
-
const senderNonces = this.usedNonces.get(sender);
|
|
489
|
-
if (senderNonces) {
|
|
490
|
-
const usedSet = senderNonces.get(messageType);
|
|
491
|
-
if (usedSet) {
|
|
492
|
-
// SECURITY FIX (HIGH-2): Update global counter
|
|
493
|
-
this.totalEntries -= usedSet.size;
|
|
494
|
-
}
|
|
495
|
-
senderNonces.delete(messageType);
|
|
496
|
-
if (senderNonces.size === 0) {
|
|
497
|
-
this.usedNonces.delete(sender);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Clear all tracked nonces
|
|
504
|
-
*/
|
|
505
|
-
clearAll(): void {
|
|
506
|
-
this.usedNonces.clear();
|
|
507
|
-
// SECURITY FIX (HIGH-2): Reset global counter
|
|
508
|
-
this.totalEntries = 0;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Get nonce count for sender + message type (for monitoring)
|
|
513
|
-
*/
|
|
514
|
-
getNonceCount(sender: string, messageType: string): number {
|
|
515
|
-
const senderNonces = this.usedNonces.get(sender);
|
|
516
|
-
if (!senderNonces) {
|
|
517
|
-
return 0;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const usedSet = senderNonces.get(messageType);
|
|
521
|
-
return usedSet ? usedSet.size : 0;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Cleanup old nonces (keep only last N)
|
|
526
|
-
* This prevents unbounded memory growth
|
|
527
|
-
*/
|
|
528
|
-
cleanup(sender: string, messageType: string, keepLast: number = 1000): void {
|
|
529
|
-
const senderNonces = this.usedNonces.get(sender);
|
|
530
|
-
if (!senderNonces) {
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const usedSet = senderNonces.get(messageType);
|
|
535
|
-
if (!usedSet || usedSet.size <= keepLast) {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// Convert to array and sort by nonce value
|
|
540
|
-
const sortedNonces = Array.from(usedSet).sort((a, b) => {
|
|
541
|
-
const aVal = BigInt(a);
|
|
542
|
-
const bVal = BigInt(b);
|
|
543
|
-
return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
// Keep only the last N nonces
|
|
547
|
-
const removedCount = usedSet.size - keepLast;
|
|
548
|
-
const toKeep = new Set(sortedNonces.slice(-keepLast));
|
|
549
|
-
senderNonces.set(messageType, toKeep);
|
|
550
|
-
// SECURITY FIX (HIGH-2): Update global counter
|
|
551
|
-
this.totalEntries -= removedCount;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* Factory function to create a nonce tracker
|
|
557
|
-
* @param strategy - 'memory-efficient' (highest nonce) or 'set-based' (all nonces)
|
|
558
|
-
* @returns IReceivedNonceTracker instance
|
|
559
|
-
*/
|
|
560
|
-
export function createReceivedNonceTracker(
|
|
561
|
-
strategy: 'memory-efficient' | 'set-based' = 'memory-efficient'
|
|
562
|
-
): IReceivedNonceTracker {
|
|
563
|
-
if (strategy === 'set-based') {
|
|
564
|
-
return new SetBasedReceivedNonceTracker();
|
|
565
|
-
}
|
|
566
|
-
return new InMemoryReceivedNonceTracker();
|
|
567
|
-
}
|