@agirails/sdk 2.5.2 → 2.5.4
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 +67 -22
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +12 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +30 -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 +45 -11
- 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/cli/utils/client.d.ts.map +1 -1
- package/dist/cli/utils/client.js +1 -0
- package/dist/cli/utils/client.js.map +1 -1
- package/dist/config/networks.d.ts +2 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +27 -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/runtime/MockStateManager.d.ts.map +1 -1
- package/dist/runtime/MockStateManager.js +2 -1
- package/dist/runtime/MockStateManager.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.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +52 -18
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +116 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +212 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +19 -0
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +100 -5
- 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 -251
- 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 -576
- 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,591 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nonce Manager Implementation
|
|
3
|
-
* Tracks nonces per DID + message type for AIP-4 delivery proofs
|
|
4
|
-
* Reference: AIP-4 §3.2 (nonce field requirement)
|
|
5
|
-
*
|
|
6
|
-
* SECURITY FIXES:
|
|
7
|
-
* - C-2: Added atomic nonce allocation with locking
|
|
8
|
-
* - H-1: Added persistent nonce storage option
|
|
9
|
-
* - H-5: Added nonce upper bound validation
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { assertSafeFileForRead, ensureSafeDir, ensureSafeFile } from './fsSafe';
|
|
13
|
-
import { sdkLogger } from './Logger';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Maximum allowed nonce value.
|
|
17
|
-
* SECURITY FIX (H-5): Prevents nonce overflow attacks.
|
|
18
|
-
* Using Number.MAX_SAFE_INTEGER (2^53 - 1) to ensure safe JavaScript integer operations.
|
|
19
|
-
*/
|
|
20
|
-
export const MAX_NONCE_VALUE = Number.MAX_SAFE_INTEGER;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Nonce Manager Interface (from DeliveryProofBuilder)
|
|
24
|
-
*/
|
|
25
|
-
export interface NonceManager {
|
|
26
|
-
/**
|
|
27
|
-
* Get next nonce for message type
|
|
28
|
-
* @param messageType - Message type identifier (e.g., "agirails.delivery.v1")
|
|
29
|
-
* @returns Monotonically increasing nonce
|
|
30
|
-
*/
|
|
31
|
-
getNextNonce(messageType: string): number;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Record nonce usage
|
|
35
|
-
* @param messageType - Message type identifier
|
|
36
|
-
* @param nonce - Nonce used
|
|
37
|
-
*/
|
|
38
|
-
recordNonce(messageType: string, nonce: number): void;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get current nonce (last used)
|
|
42
|
-
* @param messageType - Message type identifier
|
|
43
|
-
* @returns Current nonce or 0 if none used
|
|
44
|
-
*/
|
|
45
|
-
getCurrentNonce(messageType: string): number;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Reset nonce for message type
|
|
49
|
-
* @param messageType - Message type identifier
|
|
50
|
-
*/
|
|
51
|
-
resetNonce(messageType: string): void;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* In-Memory Nonce Manager
|
|
56
|
-
* Simple implementation using Map for per-message-type nonce tracking
|
|
57
|
-
*
|
|
58
|
-
* SECURITY FIXES:
|
|
59
|
-
* - C-2: Added atomic getAndIncrementNonce() to prevent race conditions
|
|
60
|
-
* - H-5: Added nonce upper bound validation
|
|
61
|
-
*
|
|
62
|
-
* ⚠️ WARNING: Nonces are lost on process restart. For production:
|
|
63
|
-
* - Use persistent storage (Redis, PostgreSQL, etc.)
|
|
64
|
-
* - Implement nonce recovery from blockchain events
|
|
65
|
-
* - Add DID-scoped nonce tracking
|
|
66
|
-
*/
|
|
67
|
-
export class InMemoryNonceManager implements NonceManager {
|
|
68
|
-
private nonces: Map<string, number> = new Map();
|
|
69
|
-
// SECURITY FIX (C-2): Mutex for atomic nonce operations
|
|
70
|
-
// Store both the promise and its resolver for proper lock release
|
|
71
|
-
private locks: Map<string, { promise: Promise<void>; resolve: () => void }> = new Map();
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Create in-memory nonce manager
|
|
75
|
-
* @param initialNonces - Optional initial nonce values (for recovery)
|
|
76
|
-
*/
|
|
77
|
-
constructor(initialNonces?: Record<string, number>) {
|
|
78
|
-
if (initialNonces) {
|
|
79
|
-
Object.entries(initialNonces).forEach(([messageType, nonce]) => {
|
|
80
|
-
// SECURITY FIX (H-5): Validate initial nonces
|
|
81
|
-
if (nonce > MAX_NONCE_VALUE) {
|
|
82
|
-
throw new Error(
|
|
83
|
-
`Initial nonce ${nonce} for ${messageType} exceeds maximum allowed value ${MAX_NONCE_VALUE}`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
this.nonces.set(messageType, nonce);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* SECURITY FIX (C-2 + DEADLOCK-FIX): Acquire lock for message type
|
|
93
|
-
* Ensures atomic nonce operations.
|
|
94
|
-
*
|
|
95
|
-
* FIXED: Previous implementation had a deadlock bug where:
|
|
96
|
-
* - The resolver was stored in a closure but never accessible to releaseLock()
|
|
97
|
-
* - releaseLock() just deleted the entry without resolving waiting Promises
|
|
98
|
-
*
|
|
99
|
-
* New implementation stores both promise AND resolver together.
|
|
100
|
-
*/
|
|
101
|
-
private async acquireLock(messageType: string): Promise<void> {
|
|
102
|
-
// Wait for any existing lock to be released
|
|
103
|
-
while (this.locks.has(messageType)) {
|
|
104
|
-
const existingLock = this.locks.get(messageType);
|
|
105
|
-
if (existingLock) {
|
|
106
|
-
await existingLock.promise;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Create new lock with stored resolver
|
|
111
|
-
let resolver: () => void = () => {};
|
|
112
|
-
const lockPromise = new Promise<void>((resolve) => {
|
|
113
|
-
resolver = resolve;
|
|
114
|
-
});
|
|
115
|
-
this.locks.set(messageType, { promise: lockPromise, resolve: resolver });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* SECURITY FIX (C-2 + DEADLOCK-FIX): Release lock for message type
|
|
120
|
-
*
|
|
121
|
-
* FIXED: Now properly resolves the Promise before deleting,
|
|
122
|
-
* so any waiting acquireLock() calls can proceed.
|
|
123
|
-
*/
|
|
124
|
-
private releaseLock(messageType: string): void {
|
|
125
|
-
const lock = this.locks.get(messageType);
|
|
126
|
-
if (lock) {
|
|
127
|
-
lock.resolve(); // Resolve the promise first
|
|
128
|
-
this.locks.delete(messageType); // Then delete the entry
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Get next nonce for message type
|
|
134
|
-
* @param messageType - Message type identifier
|
|
135
|
-
* @returns Monotonically increasing nonce
|
|
136
|
-
*/
|
|
137
|
-
getNextNonce(messageType: string): number {
|
|
138
|
-
const current = this.nonces.get(messageType) || 0;
|
|
139
|
-
const next = current + 1;
|
|
140
|
-
|
|
141
|
-
// SECURITY FIX (H-5): Check upper bound
|
|
142
|
-
if (next > MAX_NONCE_VALUE) {
|
|
143
|
-
throw new Error(
|
|
144
|
-
`Nonce overflow: next nonce ${next} exceeds maximum allowed value ${MAX_NONCE_VALUE}. ` +
|
|
145
|
-
`Consider resetting nonces or using a larger storage type.`
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return next;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* SECURITY FIX (C-2): Atomic get-and-increment nonce
|
|
154
|
-
* Returns the next nonce and records it atomically to prevent race conditions.
|
|
155
|
-
*
|
|
156
|
-
* @param messageType - Message type identifier
|
|
157
|
-
* @returns Atomically allocated nonce
|
|
158
|
-
*/
|
|
159
|
-
async getAndIncrementNonce(messageType: string): Promise<number> {
|
|
160
|
-
await this.acquireLock(messageType);
|
|
161
|
-
try {
|
|
162
|
-
const current = this.nonces.get(messageType) || 0;
|
|
163
|
-
const next = current + 1;
|
|
164
|
-
|
|
165
|
-
// SECURITY FIX (H-5): Check upper bound
|
|
166
|
-
if (next > MAX_NONCE_VALUE) {
|
|
167
|
-
throw new Error(
|
|
168
|
-
`Nonce overflow: next nonce ${next} exceeds maximum allowed value ${MAX_NONCE_VALUE}`
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
this.nonces.set(messageType, next);
|
|
173
|
-
return next;
|
|
174
|
-
} finally {
|
|
175
|
-
this.releaseLock(messageType);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Record nonce usage
|
|
181
|
-
* @param messageType - Message type identifier
|
|
182
|
-
* @param nonce - Nonce used
|
|
183
|
-
*/
|
|
184
|
-
recordNonce(messageType: string, nonce: number): void {
|
|
185
|
-
const current = this.nonces.get(messageType) || 0;
|
|
186
|
-
|
|
187
|
-
// SECURITY FIX (H-5): Check upper bound
|
|
188
|
-
if (nonce > MAX_NONCE_VALUE) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
`Nonce ${nonce} exceeds maximum allowed value ${MAX_NONCE_VALUE}`
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Ensure monotonic increase
|
|
195
|
-
if (nonce <= current) {
|
|
196
|
-
throw new Error(
|
|
197
|
-
`Nonce must be strictly increasing: attempted ${nonce}, current is ${current}`
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
this.nonces.set(messageType, nonce);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Get current nonce (last used)
|
|
206
|
-
* @param messageType - Message type identifier
|
|
207
|
-
* @returns Current nonce or 0 if none used
|
|
208
|
-
*/
|
|
209
|
-
getCurrentNonce(messageType: string): number {
|
|
210
|
-
return this.nonces.get(messageType) || 0;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Reset nonce for message type
|
|
215
|
-
* @param messageType - Message type identifier
|
|
216
|
-
*/
|
|
217
|
-
resetNonce(messageType: string): void {
|
|
218
|
-
this.nonces.delete(messageType);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get all nonces (for persistence)
|
|
223
|
-
* @returns Record of all message type nonces
|
|
224
|
-
*/
|
|
225
|
-
getAllNonces(): Record<string, number> {
|
|
226
|
-
return Object.fromEntries(this.nonces.entries());
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Clear all nonces
|
|
231
|
-
*/
|
|
232
|
-
clearAll(): void {
|
|
233
|
-
this.nonces.clear();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* DID-Scoped Nonce Manager
|
|
239
|
-
* Tracks nonces per DID + message type combination
|
|
240
|
-
* Recommended for multi-agent scenarios
|
|
241
|
-
*/
|
|
242
|
-
export class DIDScopedNonceManager implements NonceManager {
|
|
243
|
-
private nonces: Map<string, Map<string, number>> = new Map();
|
|
244
|
-
private currentDID: string;
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Create DID-scoped nonce manager
|
|
248
|
-
* @param did - DID to track nonces for
|
|
249
|
-
* @param initialNonces - Optional initial nonce values
|
|
250
|
-
*/
|
|
251
|
-
constructor(did: string, initialNonces?: Record<string, number>) {
|
|
252
|
-
this.currentDID = did;
|
|
253
|
-
|
|
254
|
-
if (initialNonces) {
|
|
255
|
-
const didNonces = new Map<string, number>();
|
|
256
|
-
Object.entries(initialNonces).forEach(([messageType, nonce]) => {
|
|
257
|
-
didNonces.set(messageType, nonce);
|
|
258
|
-
});
|
|
259
|
-
this.nonces.set(did, didNonces);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Get next nonce for message type (current DID)
|
|
265
|
-
* @param messageType - Message type identifier
|
|
266
|
-
* @returns Monotonically increasing nonce
|
|
267
|
-
*/
|
|
268
|
-
getNextNonce(messageType: string): number {
|
|
269
|
-
return this.getNextNonceForDID(this.currentDID, messageType);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Record nonce usage (current DID)
|
|
274
|
-
* @param messageType - Message type identifier
|
|
275
|
-
* @param nonce - Nonce used
|
|
276
|
-
*/
|
|
277
|
-
recordNonce(messageType: string, nonce: number): void {
|
|
278
|
-
this.recordNonceForDID(this.currentDID, messageType, nonce);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Get current nonce (current DID)
|
|
283
|
-
* @param messageType - Message type identifier
|
|
284
|
-
* @returns Current nonce or 0 if none used
|
|
285
|
-
*/
|
|
286
|
-
getCurrentNonce(messageType: string): number {
|
|
287
|
-
return this.getCurrentNonceForDID(this.currentDID, messageType);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Reset nonce for message type (current DID)
|
|
292
|
-
* @param messageType - Message type identifier
|
|
293
|
-
*/
|
|
294
|
-
resetNonce(messageType: string): void {
|
|
295
|
-
this.resetNonceForDID(this.currentDID, messageType);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Get next nonce for specific DID + message type
|
|
300
|
-
* @param did - DID identifier
|
|
301
|
-
* @param messageType - Message type identifier
|
|
302
|
-
* @returns Monotonically increasing nonce
|
|
303
|
-
*/
|
|
304
|
-
getNextNonceForDID(did: string, messageType: string): number {
|
|
305
|
-
const didNonces = this.nonces.get(did);
|
|
306
|
-
if (!didNonces) {
|
|
307
|
-
return 1;
|
|
308
|
-
}
|
|
309
|
-
const current = didNonces.get(messageType) || 0;
|
|
310
|
-
return current + 1;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Record nonce usage for specific DID + message type
|
|
315
|
-
* @param did - DID identifier
|
|
316
|
-
* @param messageType - Message type identifier
|
|
317
|
-
* @param nonce - Nonce used
|
|
318
|
-
*/
|
|
319
|
-
recordNonceForDID(did: string, messageType: string, nonce: number): void {
|
|
320
|
-
let didNonces = this.nonces.get(did);
|
|
321
|
-
|
|
322
|
-
if (!didNonces) {
|
|
323
|
-
didNonces = new Map<string, number>();
|
|
324
|
-
this.nonces.set(did, didNonces);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const current = didNonces.get(messageType) || 0;
|
|
328
|
-
|
|
329
|
-
// Ensure monotonic increase
|
|
330
|
-
if (nonce <= current) {
|
|
331
|
-
throw new Error(
|
|
332
|
-
`Nonce must be strictly increasing for ${did}: attempted ${nonce}, current is ${current}`
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
didNonces.set(messageType, nonce);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Get current nonce for specific DID + message type
|
|
341
|
-
* @param did - DID identifier
|
|
342
|
-
* @param messageType - Message type identifier
|
|
343
|
-
* @returns Current nonce or 0 if none used
|
|
344
|
-
*/
|
|
345
|
-
getCurrentNonceForDID(did: string, messageType: string): number {
|
|
346
|
-
const didNonces = this.nonces.get(did);
|
|
347
|
-
return didNonces?.get(messageType) || 0;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Reset nonce for specific DID + message type
|
|
352
|
-
* @param did - DID identifier
|
|
353
|
-
* @param messageType - Message type identifier
|
|
354
|
-
*/
|
|
355
|
-
resetNonceForDID(did: string, messageType: string): void {
|
|
356
|
-
const didNonces = this.nonces.get(did);
|
|
357
|
-
if (didNonces) {
|
|
358
|
-
didNonces.delete(messageType);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Switch current DID context
|
|
364
|
-
* @param did - New DID to track
|
|
365
|
-
*/
|
|
366
|
-
switchDID(did: string): void {
|
|
367
|
-
this.currentDID = did;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Get all nonces for all DIDs (for persistence)
|
|
372
|
-
* @returns Nested record of DID → message type → nonce
|
|
373
|
-
*/
|
|
374
|
-
getAllNonces(): Record<string, Record<string, number>> {
|
|
375
|
-
const result: Record<string, Record<string, number>> = {};
|
|
376
|
-
|
|
377
|
-
this.nonces.forEach((didNonces, did) => {
|
|
378
|
-
result[did] = Object.fromEntries(didNonces.entries());
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
return result;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Clear all nonces for all DIDs
|
|
386
|
-
*/
|
|
387
|
-
clearAll(): void {
|
|
388
|
-
this.nonces.clear();
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* File-based Nonce Manager for Persistent Storage
|
|
394
|
-
*
|
|
395
|
-
* SECURITY FIX (H-1): Persists nonces to disk to survive process restarts.
|
|
396
|
-
* SECURITY FIX (NEW-H-4): File locking to prevent concurrent write corruption.
|
|
397
|
-
* Uses atomic file writes (temp file + rename) for crash safety.
|
|
398
|
-
*
|
|
399
|
-
* @module utils/NonceManager
|
|
400
|
-
*/
|
|
401
|
-
export class FileBasedNonceManager implements NonceManager {
|
|
402
|
-
private inMemory: InMemoryNonceManager;
|
|
403
|
-
private filePath: string;
|
|
404
|
-
private fs: typeof import('fs');
|
|
405
|
-
private path: typeof import('path');
|
|
406
|
-
private lockfile: typeof import('proper-lockfile');
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Create file-based nonce manager
|
|
410
|
-
* @param stateDirectory - Directory to store nonces file
|
|
411
|
-
*/
|
|
412
|
-
constructor(stateDirectory: string) {
|
|
413
|
-
this.fs = require('fs');
|
|
414
|
-
this.path = require('path');
|
|
415
|
-
// SECURITY FIX (NEW-H-4): File locking to prevent race conditions
|
|
416
|
-
this.lockfile = require('proper-lockfile');
|
|
417
|
-
|
|
418
|
-
// Ensure .actp directory exists
|
|
419
|
-
const actpDir = this.path.join(stateDirectory, '.actp');
|
|
420
|
-
ensureSafeDir(actpDir, 0o755);
|
|
421
|
-
|
|
422
|
-
this.filePath = this.path.join(actpDir, 'nonces.json');
|
|
423
|
-
|
|
424
|
-
// Load existing nonces
|
|
425
|
-
const initialNonces = this.loadFromFile();
|
|
426
|
-
this.inMemory = new InMemoryNonceManager(initialNonces);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Load nonces from file
|
|
431
|
-
*/
|
|
432
|
-
private loadFromFile(): Record<string, number> | undefined {
|
|
433
|
-
if (!this.fs.existsSync(this.filePath)) {
|
|
434
|
-
return undefined;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
try {
|
|
438
|
-
// SECURITY: Refuse to read from symlinked nonce files
|
|
439
|
-
assertSafeFileForRead(this.filePath);
|
|
440
|
-
|
|
441
|
-
const MAX_NONCE_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
442
|
-
const st = this.fs.statSync(this.filePath);
|
|
443
|
-
if (st.size > MAX_NONCE_FILE_SIZE) {
|
|
444
|
-
throw new Error(
|
|
445
|
-
`nonces.json exceeds ${MAX_NONCE_FILE_SIZE / 1024 / 1024}MB limit: ${this.filePath}`
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const data = JSON.parse(this.fs.readFileSync(this.filePath, 'utf-8'));
|
|
450
|
-
return data as Record<string, number>;
|
|
451
|
-
} catch (e: any) {
|
|
452
|
-
// Fail closed: nonce resets can enable replay.
|
|
453
|
-
throw new Error(
|
|
454
|
-
`Failed to parse nonces.json (replay protection would be weakened). ` +
|
|
455
|
-
`Fix/delete the file: ${this.filePath}. Error: ${e?.message || String(e)}`
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Save nonces to file atomically with file locking
|
|
462
|
-
*
|
|
463
|
-
* SECURITY FIX (NEW-H-4): File locking prevents concurrent write corruption
|
|
464
|
-
*/
|
|
465
|
-
private async saveToFile(): Promise<void> {
|
|
466
|
-
const data = this.inMemory.getAllNonces();
|
|
467
|
-
const tempPath = `${this.filePath}.tmp`;
|
|
468
|
-
|
|
469
|
-
// SECURITY FIX: Ensure file exists before locking (proper-lockfile requirement)
|
|
470
|
-
ensureSafeFile(this.filePath, '{}', 0o644);
|
|
471
|
-
|
|
472
|
-
// SECURITY FIX (NEW-H-4): Acquire file lock before writing
|
|
473
|
-
let release: (() => Promise<void>) | null = null;
|
|
474
|
-
try {
|
|
475
|
-
release = await this.lockfile.lock(this.filePath, {
|
|
476
|
-
stale: 10000, // Lock expires after 10 seconds if process crashes
|
|
477
|
-
retries: {
|
|
478
|
-
retries: 5,
|
|
479
|
-
minTimeout: 100,
|
|
480
|
-
maxTimeout: 500
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
// Atomic write: temp file + rename
|
|
485
|
-
if (this.fs.existsSync(tempPath)) {
|
|
486
|
-
this.fs.unlinkSync(tempPath);
|
|
487
|
-
}
|
|
488
|
-
this.fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), {
|
|
489
|
-
encoding: 'utf-8',
|
|
490
|
-
mode: 0o644,
|
|
491
|
-
flag: 'wx'
|
|
492
|
-
});
|
|
493
|
-
this.fs.renameSync(tempPath, this.filePath);
|
|
494
|
-
} catch (error) {
|
|
495
|
-
// Clean up temp file on error
|
|
496
|
-
if (this.fs.existsSync(tempPath)) {
|
|
497
|
-
try {
|
|
498
|
-
this.fs.unlinkSync(tempPath);
|
|
499
|
-
} catch {
|
|
500
|
-
// Ignore cleanup errors
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
throw error;
|
|
504
|
-
} finally {
|
|
505
|
-
if (release) {
|
|
506
|
-
await release();
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
getNextNonce(messageType: string): number {
|
|
512
|
-
return this.inMemory.getNextNonce(messageType);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Atomic get and increment with persistence
|
|
517
|
-
*/
|
|
518
|
-
async getAndIncrementNonce(messageType: string): Promise<number> {
|
|
519
|
-
const nonce = await this.inMemory.getAndIncrementNonce(messageType);
|
|
520
|
-
// SECURITY FIX (NEW-H-4): saveToFile is now async
|
|
521
|
-
await this.saveToFile();
|
|
522
|
-
return nonce;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
recordNonce(messageType: string, nonce: number): void {
|
|
526
|
-
this.inMemory.recordNonce(messageType, nonce);
|
|
527
|
-
// Fire-and-forget to maintain sync interface
|
|
528
|
-
this.saveToFile().catch((err) => {
|
|
529
|
-
sdkLogger.error('Failed to save nonce manager state', { error: err instanceof Error ? err.message : String(err) });
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
getCurrentNonce(messageType: string): number {
|
|
534
|
-
return this.inMemory.getCurrentNonce(messageType);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
resetNonce(messageType: string): void {
|
|
538
|
-
this.inMemory.resetNonce(messageType);
|
|
539
|
-
// Fire-and-forget to maintain sync interface
|
|
540
|
-
this.saveToFile().catch((err) => {
|
|
541
|
-
sdkLogger.error('Failed to save nonce manager state', { error: err instanceof Error ? err.message : String(err) });
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
getAllNonces(): Record<string, number> {
|
|
546
|
-
return this.inMemory.getAllNonces();
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
clearAll(): void {
|
|
550
|
-
this.inMemory.clearAll();
|
|
551
|
-
if (this.fs.existsSync(this.filePath)) {
|
|
552
|
-
this.fs.unlinkSync(this.filePath);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* Create nonce manager based on environment
|
|
559
|
-
* @param options - Configuration options
|
|
560
|
-
* @returns NonceManager instance
|
|
561
|
-
*
|
|
562
|
-
* @example
|
|
563
|
-
* ```typescript
|
|
564
|
-
* // In-memory (default)
|
|
565
|
-
* const manager = createNonceManager();
|
|
566
|
-
*
|
|
567
|
-
* // DID-scoped
|
|
568
|
-
* const manager = createNonceManager({ did: 'did:ethr:0x...' });
|
|
569
|
-
*
|
|
570
|
-
* // Persistent (survives restarts)
|
|
571
|
-
* const manager = createNonceManager({ stateDirectory: '/path/to/project' });
|
|
572
|
-
* ```
|
|
573
|
-
*/
|
|
574
|
-
export function createNonceManager(
|
|
575
|
-
options?: {
|
|
576
|
-
did?: string;
|
|
577
|
-
initialNonces?: Record<string, number>;
|
|
578
|
-
stateDirectory?: string;
|
|
579
|
-
}
|
|
580
|
-
): NonceManager {
|
|
581
|
-
// SECURITY FIX (H-1): Support persistent storage
|
|
582
|
-
if (options?.stateDirectory) {
|
|
583
|
-
return new FileBasedNonceManager(options.stateDirectory);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (options?.did) {
|
|
587
|
-
return new DIDScopedNonceManager(options.did, options?.initialNonces);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
return new InMemoryNonceManager(options?.initialNonces);
|
|
591
|
-
}
|