@corpus-core/colibri-stateless 2.0.6 → 3.0.0

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.
@@ -23,7 +23,466 @@
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.TransactionUtils = exports.PrototypeProtection = exports.TransactionVerifier = void 0;
26
+ exports.compareTransactionParameters = compareTransactionParameters;
26
27
  const types_js_1 = require("./types.js");
28
+ function getOwn(obj, key) {
29
+ if (obj == null || typeof obj !== 'object')
30
+ return { present: false, value: undefined };
31
+ if (!Object.prototype.hasOwnProperty.call(obj, key))
32
+ return { present: false, value: undefined };
33
+ return { present: true, value: obj[key] };
34
+ }
35
+ /** Resolve RPC aliases. If several names are present they must parse to the same value. */
36
+ function resolveAliased(obj, names, parse) {
37
+ let provided = false;
38
+ let canonical;
39
+ for (const name of names) {
40
+ const field = getOwn(obj, name);
41
+ if (!field.present || field.value === undefined)
42
+ continue;
43
+ const parsed = parse(field.value);
44
+ if (!parsed.ok)
45
+ return { ok: false };
46
+ if (!provided) {
47
+ provided = true;
48
+ canonical = parsed.value;
49
+ }
50
+ else if (canonical !== parsed.value) {
51
+ return { ok: false };
52
+ }
53
+ }
54
+ return { ok: true, value: { provided, value: canonical } };
55
+ }
56
+ function parseQuantity(value) {
57
+ if (value === undefined || value === null || value === '')
58
+ return { ok: true, value: 0n };
59
+ if (typeof value === 'boolean' || typeof value === 'object')
60
+ return { ok: false };
61
+ if (typeof value === 'bigint')
62
+ return value < 0n ? { ok: false } : { ok: true, value };
63
+ if (typeof value === 'number') {
64
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0)
65
+ return { ok: false };
66
+ return { ok: true, value: BigInt(value) };
67
+ }
68
+ const str = String(value).trim();
69
+ if (str === '' || str === '0x' || str === '0X')
70
+ return { ok: true, value: 0n };
71
+ if (/^0x[0-9a-fA-F]+$/.test(str))
72
+ return { ok: true, value: BigInt(str) };
73
+ if (/^[0-9]+$/.test(str))
74
+ return { ok: true, value: BigInt(str) };
75
+ return { ok: false };
76
+ }
77
+ /** 20-byte address, left-padded. `null`/`''`/`0x` → contract creation (`null`). */
78
+ function parseAddress(value) {
79
+ if (value === undefined || value === null || value === '')
80
+ return { ok: true, value: null };
81
+ if (typeof value === 'boolean' || typeof value === 'object')
82
+ return { ok: false };
83
+ let str = String(value).trim();
84
+ if (str === '0x' || str === '0X')
85
+ return { ok: true, value: null };
86
+ if (str.startsWith('0x') || str.startsWith('0X'))
87
+ str = str.slice(2);
88
+ if (!/^[0-9a-fA-F]+$/.test(str) || str.length > 40)
89
+ return { ok: false };
90
+ if (str.length % 2 === 1)
91
+ str = '0' + str;
92
+ return { ok: true, value: '0x' + str.toLowerCase().padStart(40, '0') };
93
+ }
94
+ function parseHexPayload(value) {
95
+ if (value === undefined || value === null || value === '')
96
+ return { ok: true, value: '' };
97
+ if (value instanceof Uint8Array) {
98
+ let hex = '';
99
+ for (let i = 0; i < value.length; i++)
100
+ hex += value[i].toString(16).padStart(2, '0');
101
+ return { ok: true, value: hex };
102
+ }
103
+ if (typeof value === 'boolean' || typeof value === 'object')
104
+ return { ok: false };
105
+ let str = String(value).trim();
106
+ if (str.startsWith('0x') || str.startsWith('0X'))
107
+ str = str.slice(2);
108
+ if (str === '')
109
+ return { ok: true, value: '' };
110
+ if (!/^[0-9a-fA-F]+$/.test(str))
111
+ return { ok: false };
112
+ return { ok: true, value: str.toLowerCase() };
113
+ }
114
+ /** Exact calldata bytes. Leading zeros are significant (`0x` ≠ `0x00`). Odd nibble length is invalid. */
115
+ function parseBytes(value) {
116
+ const hex = parseHexPayload(value);
117
+ if (!hex.ok)
118
+ return hex;
119
+ if (hex.value.length % 2 === 1)
120
+ return { ok: false };
121
+ return { ok: true, value: '0x' + hex.value };
122
+ }
123
+ function parseHash32(value) {
124
+ const hex = parseHexPayload(value);
125
+ if (!hex.ok)
126
+ return hex;
127
+ if (hex.value.length > 64)
128
+ return { ok: false };
129
+ return { ok: true, value: '0x' + hex.value.padStart(64, '0') };
130
+ }
131
+ function parseYParity(value) {
132
+ const q = parseQuantity(value);
133
+ if (!q.ok)
134
+ return q;
135
+ // Accept v = 27/28 as well as yParity = 0/1.
136
+ if (q.value === 27n || q.value === 28n)
137
+ return { ok: true, value: q.value - 27n };
138
+ if (q.value !== 0n && q.value !== 1n)
139
+ return { ok: false };
140
+ return q;
141
+ }
142
+ function warn(config, message) {
143
+ if (config.debug)
144
+ console.warn(`[TransactionVerifier] ${message}`);
145
+ }
146
+ function quantitiesEqual(originalVal, decodedVal, defaultIfOriginalOmitted) {
147
+ const decoded = decodedVal === undefined ? { ok: true, value: 0n } : parseQuantity(decodedVal);
148
+ if (!decoded.ok)
149
+ return 'invalid';
150
+ if (originalVal === undefined) {
151
+ if (defaultIfOriginalOmitted === 'skip')
152
+ return true;
153
+ return decoded.value === 0n;
154
+ }
155
+ const original = parseQuantity(originalVal);
156
+ if (!original.ok)
157
+ return 'invalid';
158
+ return original.value === decoded.value;
159
+ }
160
+ function compareAccessList(original, decoded, config) {
161
+ const origField = getOwn(original, 'accessList');
162
+ const decField = getOwn(decoded, 'accessList');
163
+ const origList = origField.present && origField.value != null ? origField.value : [];
164
+ const decList = decField.present && decField.value != null ? decField.value : [];
165
+ if (!Array.isArray(origList) || !Array.isArray(decList)) {
166
+ warn(config, 'accessList is not an array');
167
+ return false;
168
+ }
169
+ if (origList.length !== decList.length) {
170
+ warn(config, `accessList length mismatch: original=${origList.length}, decoded=${decList.length}`);
171
+ return false;
172
+ }
173
+ for (let i = 0; i < origList.length; i++) {
174
+ const oEntry = origList[i];
175
+ const dEntry = decList[i];
176
+ if (oEntry == null || typeof oEntry !== 'object' || dEntry == null || typeof dEntry !== 'object') {
177
+ warn(config, `accessList[${i}] is not an object`);
178
+ return false;
179
+ }
180
+ const oa = parseAddress(getOwn(oEntry, 'address').value);
181
+ const da = parseAddress(getOwn(dEntry, 'address').value);
182
+ if (!oa.ok || !da.ok || oa.value === null || da.value === null || oa.value !== da.value) {
183
+ warn(config, `accessList[${i}].address mismatch`);
184
+ return false;
185
+ }
186
+ const oKeysField = getOwn(oEntry, 'storageKeys');
187
+ const dKeysField = getOwn(dEntry, 'storageKeys');
188
+ const oKeys = oKeysField.present && oKeysField.value != null ? oKeysField.value : [];
189
+ const dKeys = dKeysField.present && dKeysField.value != null ? dKeysField.value : [];
190
+ if (!Array.isArray(oKeys) || !Array.isArray(dKeys) || oKeys.length !== dKeys.length) {
191
+ warn(config, `accessList[${i}].storageKeys mismatch`);
192
+ return false;
193
+ }
194
+ for (let k = 0; k < oKeys.length; k++) {
195
+ const okh = parseHash32(oKeys[k]);
196
+ const dkh = parseHash32(dKeys[k]);
197
+ if (!okh.ok || !dkh.ok || okh.value !== dkh.value) {
198
+ warn(config, `accessList[${i}].storageKeys[${k}] mismatch`);
199
+ return false;
200
+ }
201
+ }
202
+ }
203
+ return true;
204
+ }
205
+ function compareAuthorizationList(original, decoded, config) {
206
+ const origField = getOwn(original, 'authorizationList');
207
+ const decField = getOwn(decoded, 'authorizationList');
208
+ const origList = origField.present && origField.value != null ? origField.value : [];
209
+ const decList = decField.present && decField.value != null ? decField.value : [];
210
+ if (!Array.isArray(origList) || !Array.isArray(decList)) {
211
+ warn(config, 'authorizationList is not an array');
212
+ return false;
213
+ }
214
+ if (origList.length !== decList.length) {
215
+ warn(config, `authorizationList length mismatch: original=${origList.length}, decoded=${decList.length}`);
216
+ return false;
217
+ }
218
+ for (let i = 0; i < origList.length; i++) {
219
+ const o = origList[i];
220
+ const d = decList[i];
221
+ if (o == null || typeof o !== 'object' || d == null || typeof d !== 'object') {
222
+ warn(config, `authorizationList[${i}] is not an object`);
223
+ return false;
224
+ }
225
+ const oa = parseAddress(getOwn(o, 'address').value);
226
+ const da = parseAddress(getOwn(d, 'address').value);
227
+ if (!oa.ok || !da.ok || oa.value === null || da.value === null || oa.value !== da.value) {
228
+ warn(config, `authorizationList[${i}].address mismatch`);
229
+ return false;
230
+ }
231
+ const origChain = getOwn(o, 'chainId');
232
+ const expectedChain = origChain.present && origChain.value !== undefined ? origChain.value : config.chainId;
233
+ const chainEq = quantitiesEqual(expectedChain, getOwn(d, 'chainId').value, 'skip');
234
+ if (chainEq !== true) {
235
+ warn(config, `authorizationList[${i}].chainId mismatch`);
236
+ return false;
237
+ }
238
+ const origNonce = getOwn(o, 'nonce');
239
+ if (origNonce.present && origNonce.value !== undefined) {
240
+ const nonceEq = quantitiesEqual(origNonce.value, getOwn(d, 'nonce').value, 'skip');
241
+ if (nonceEq !== true) {
242
+ warn(config, `authorizationList[${i}].nonce mismatch`);
243
+ return false;
244
+ }
245
+ }
246
+ const origHasSig = (getOwn(o, 'r').present && getOwn(o, 'r').value !== undefined) ||
247
+ (getOwn(o, 's').present && getOwn(o, 's').value !== undefined) ||
248
+ (getOwn(o, 'yParity').present && getOwn(o, 'yParity').value !== undefined) ||
249
+ (getOwn(o, 'v').present && getOwn(o, 'v').value !== undefined);
250
+ if (origHasSig) {
251
+ const or_ = parseHash32(getOwn(o, 'r').value);
252
+ const dr = parseHash32(getOwn(d, 'r').value);
253
+ const os = parseHash32(getOwn(o, 's').value);
254
+ const ds = parseHash32(getOwn(d, 's').value);
255
+ if (!or_.ok || !dr.ok || or_.value !== dr.value || !os.ok || !ds.ok || os.value !== ds.value) {
256
+ warn(config, `authorizationList[${i}] signature mismatch`);
257
+ return false;
258
+ }
259
+ const origY = getOwn(o, 'yParity').present ? getOwn(o, 'yParity').value : getOwn(o, 'v').value;
260
+ const decY = getOwn(d, 'yParity').present ? getOwn(d, 'yParity').value : getOwn(d, 'v').value;
261
+ const oy = parseYParity(origY);
262
+ const dy = parseYParity(decY);
263
+ if (!oy.ok || !dy.ok || oy.value !== dy.value) {
264
+ warn(config, `authorizationList[${i}].yParity mismatch`);
265
+ return false;
266
+ }
267
+ }
268
+ }
269
+ return true;
270
+ }
271
+ function compareHashList(originalVal, decodedVal, name, config) {
272
+ const origList = originalVal === undefined || originalVal === null ? [] : originalVal;
273
+ const decList = decodedVal === undefined || decodedVal === null ? [] : decodedVal;
274
+ if (!Array.isArray(origList) || !Array.isArray(decList)) {
275
+ warn(config, `${name} is not an array`);
276
+ return false;
277
+ }
278
+ if (origList.length !== decList.length) {
279
+ warn(config, `${name} length mismatch: original=${origList.length}, decoded=${decList.length}`);
280
+ return false;
281
+ }
282
+ for (let i = 0; i < origList.length; i++) {
283
+ const oh = parseHash32(origList[i]);
284
+ const dh = parseHash32(decList[i]);
285
+ if (!oh.ok || !dh.ok || oh.value !== dh.value) {
286
+ warn(config, `${name}[${i}] mismatch`);
287
+ return false;
288
+ }
289
+ }
290
+ return true;
291
+ }
292
+ /**
293
+ * Compare an `eth_sendTransaction` object with the JSON from `colibri_decodeTransaction`.
294
+ *
295
+ * @param original Intended tx object from the dapp
296
+ * @param decoded Decoded signed tx (`EthTxData`: `input`, hex quantities, `to` null if create)
297
+ * @param config Must include the expected `chainId`
298
+ * @return `true` only if the signed tx matches the intent
299
+ */
300
+ function compareTransactionParameters(original, decoded, config) {
301
+ if (config.debug) {
302
+ console.log('[TransactionVerifier] Comparing transaction parameters:');
303
+ console.log('Original:', original);
304
+ console.log('Decoded:', decoded);
305
+ }
306
+ if (original == null || typeof original !== 'object' || decoded == null || typeof decoded !== 'object') {
307
+ warn(config, 'original and decoded must be objects');
308
+ return false;
309
+ }
310
+ const chainId = parseQuantity(config.chainId);
311
+ if (!chainId.ok) {
312
+ warn(config, 'config.chainId is invalid');
313
+ return false;
314
+ }
315
+ const decodedChainIdField = getOwn(decoded, 'chainId');
316
+ if (!decodedChainIdField.present) {
317
+ warn(config, 'signed transaction has no chainId (pre-EIP-155 replay risk)');
318
+ return false;
319
+ }
320
+ const decodedChainId = parseQuantity(decodedChainIdField.value);
321
+ if (!decodedChainId.ok || decodedChainId.value !== chainId.value) {
322
+ warn(config, `ChainId mismatch: expected=${config.chainId}, decoded=${decodedChainIdField.value}`);
323
+ return false;
324
+ }
325
+ const originalChainId = getOwn(original, 'chainId');
326
+ if (originalChainId.present && originalChainId.value !== undefined) {
327
+ const parsed = parseQuantity(originalChainId.value);
328
+ if (!parsed.ok || parsed.value !== chainId.value) {
329
+ warn(config, 'original.chainId does not match config.chainId');
330
+ return false;
331
+ }
332
+ }
333
+ // Decoder maps pre-EIP-155 legacy v=27/28 to chainId 1, so a missing-chainId
334
+ // check is not enough on mainnet. EIP-155 requires v >= 35.
335
+ const decodedType = getOwn(decoded, 'type');
336
+ const typeVal = decodedType.present ? parseQuantity(decodedType.value) : { ok: true, value: 0n };
337
+ if (!typeVal.ok) {
338
+ warn(config, 'invalid decoded.type');
339
+ return false;
340
+ }
341
+ if (typeVal.value === 0n) {
342
+ const vField = getOwn(decoded, 'v');
343
+ const v = vField.present ? parseQuantity(vField.value) : { ok: false };
344
+ if (!v.ok || v.value < 35n) {
345
+ warn(config, 'unprotected pre-EIP-155 signature');
346
+ return false;
347
+ }
348
+ }
349
+ const origTo = resolveAliased(original, ['to'], parseAddress);
350
+ const decTo = resolveAliased(decoded, ['to'], parseAddress);
351
+ if (!origTo.ok || !decTo.ok) {
352
+ warn(config, 'invalid to address');
353
+ return false;
354
+ }
355
+ const expectedTo = origTo.value.provided ? origTo.value.value : null;
356
+ const actualTo = decTo.value.provided ? decTo.value.value : null;
357
+ if (expectedTo !== actualTo) {
358
+ warn(config, `to mismatch: original='${expectedTo}', decoded='${actualTo}'`);
359
+ return false;
360
+ }
361
+ const origFrom = resolveAliased(original, ['from'], parseAddress);
362
+ const decFrom = resolveAliased(decoded, ['from'], parseAddress);
363
+ if (!origFrom.ok || !decFrom.ok) {
364
+ warn(config, 'invalid from address');
365
+ return false;
366
+ }
367
+ if (origFrom.value.provided && origFrom.value.value !== (decFrom.value.provided ? decFrom.value.value : null)) {
368
+ warn(config, `from mismatch: original='${origFrom.value.value}', decoded='${decFrom.value.value}'`);
369
+ return false;
370
+ }
371
+ const origValue = getOwn(original, 'value');
372
+ const valueEq = quantitiesEqual(origValue.present ? origValue.value : undefined, getOwn(decoded, 'value').value, 'zero');
373
+ if (valueEq !== true) {
374
+ warn(config, `value mismatch: original='${origValue.value}', decoded='${decoded.value}'`);
375
+ return false;
376
+ }
377
+ const origData = resolveAliased(original, ['data', 'input'], parseBytes);
378
+ const decData = resolveAliased(decoded, ['input', 'data'], parseBytes);
379
+ if (!origData.ok || !decData.ok) {
380
+ warn(config, 'invalid calldata (data/input)');
381
+ return false;
382
+ }
383
+ const expectedData = origData.value.provided ? origData.value.value : '0x';
384
+ const actualData = decData.value.provided ? decData.value.value : '0x';
385
+ if (expectedData !== actualData) {
386
+ warn(config, `data/input mismatch: original='${expectedData}', decoded='${actualData}'`);
387
+ return false;
388
+ }
389
+ const origType = getOwn(original, 'type');
390
+ if (origType.present && origType.value !== undefined) {
391
+ const typeEq = quantitiesEqual(origType.value, decoded.type, 'skip');
392
+ if (typeEq !== true) {
393
+ warn(config, `type mismatch: original='${origType.value}', decoded='${decoded.type}'`);
394
+ return false;
395
+ }
396
+ }
397
+ const origNonce = getOwn(original, 'nonce');
398
+ if (origNonce.present && origNonce.value !== undefined) {
399
+ const nonceEq = quantitiesEqual(origNonce.value, decoded.nonce, 'skip');
400
+ if (nonceEq !== true) {
401
+ warn(config, `nonce mismatch: original='${origNonce.value}', decoded='${decoded.nonce}'`);
402
+ return false;
403
+ }
404
+ }
405
+ const origGas = resolveAliased(original, ['gas', 'gasLimit'], parseQuantity);
406
+ if (!origGas.ok) {
407
+ warn(config, 'invalid gas/gasLimit');
408
+ return false;
409
+ }
410
+ if (origGas.value.provided) {
411
+ const gasEq = quantitiesEqual(origGas.value.value, decoded.gas, 'skip');
412
+ if (gasEq !== true) {
413
+ warn(config, `gas mismatch: original='${origGas.value.value}', decoded='${decoded.gas}'`);
414
+ return false;
415
+ }
416
+ }
417
+ const origMaxFee = getOwn(original, 'maxFeePerGas');
418
+ const origPrio = getOwn(original, 'maxPriorityFeePerGas');
419
+ const origGasPrice = getOwn(original, 'gasPrice');
420
+ const isEip1559 = origMaxFee.present || origPrio.present;
421
+ if (origMaxFee.present && origMaxFee.value !== undefined) {
422
+ const eq = quantitiesEqual(origMaxFee.value, decoded.maxFeePerGas, 'skip');
423
+ if (eq !== true) {
424
+ warn(config, `maxFeePerGas mismatch`);
425
+ return false;
426
+ }
427
+ }
428
+ if (origPrio.present && origPrio.value !== undefined) {
429
+ const eq = quantitiesEqual(origPrio.value, decoded.maxPriorityFeePerGas, 'skip');
430
+ if (eq !== true) {
431
+ warn(config, `maxPriorityFeePerGas mismatch`);
432
+ return false;
433
+ }
434
+ }
435
+ // Decoded EIP-1559 txs still carry an *effective* gasPrice; do not compare it.
436
+ if (origGasPrice.present && origGasPrice.value !== undefined && !isEip1559) {
437
+ const eq = quantitiesEqual(origGasPrice.value, decoded.gasPrice, 'skip');
438
+ if (eq !== true) {
439
+ warn(config, `gasPrice mismatch`);
440
+ return false;
441
+ }
442
+ }
443
+ if (!compareAccessList(original, decoded, config))
444
+ return false;
445
+ if (!compareAuthorizationList(original, decoded, config))
446
+ return false;
447
+ const origBlobs = getOwn(original, 'blobVersionedHashes');
448
+ const decBlobs = getOwn(decoded, 'blobVersionedHashes');
449
+ if (!compareHashList(origBlobs.present ? origBlobs.value : [], decBlobs.present ? decBlobs.value : [], 'blobVersionedHashes', config))
450
+ return false;
451
+ const origBlobFee = getOwn(original, 'maxFeePerBlobGas');
452
+ if (origBlobFee.present && origBlobFee.value !== undefined) {
453
+ const eq = quantitiesEqual(origBlobFee.value, decoded.maxFeePerBlobGas, 'skip');
454
+ if (eq !== true) {
455
+ warn(config, `maxFeePerBlobGas mismatch`);
456
+ return false;
457
+ }
458
+ }
459
+ const origMint = getOwn(original, 'mint');
460
+ const mintEq = quantitiesEqual(origMint.present ? origMint.value : undefined, getOwn(decoded, 'mint').present ? decoded.mint : 0n, 'zero');
461
+ if (mintEq !== true) {
462
+ warn(config, 'unexpected mint field on signed transaction');
463
+ return false;
464
+ }
465
+ const origSource = getOwn(original, 'sourceHash');
466
+ const decSource = getOwn(decoded, 'sourceHash');
467
+ if (decSource.present) {
468
+ const expected = origSource.present ? origSource.value : '0x' + '00'.repeat(32);
469
+ const oh = parseHash32(expected);
470
+ const dh = parseHash32(decSource.value);
471
+ if (!oh.ok || !dh.ok || oh.value !== dh.value) {
472
+ warn(config, 'unexpected sourceHash on signed transaction');
473
+ return false;
474
+ }
475
+ }
476
+ const origSys = getOwn(original, 'isSystemTx');
477
+ const decSys = getOwn(decoded, 'isSystemTx');
478
+ const expectedSys = origSys.present ? Boolean(origSys.value) : false;
479
+ const actualSys = decSys.present ? Boolean(decSys.value) : false;
480
+ if (expectedSys !== actualSys) {
481
+ warn(config, 'unexpected isSystemTx on signed transaction');
482
+ return false;
483
+ }
484
+ return true;
485
+ }
27
486
  class TransactionVerifier {
28
487
  /**
29
488
  * Verifizierte Ausführung von eth_sendTransaction
@@ -47,7 +506,7 @@ class TransactionVerifier {
47
506
  // 2. Signierte Transaction dekodieren
48
507
  const decodedTx = await rpcMethod('colibri_decodeTransaction', [signedRawTx], types_js_1.MethodType.LOCAL);
49
508
  // 3. Dekodierte Parameter mit Original vergleichen
50
- const isValid = this.compareTransactionParameters(txObject, decodedTx, config);
509
+ const isValid = compareTransactionParameters(txObject, decodedTx, config);
51
510
  if (!isValid) {
52
511
  throw new types_js_1.ProviderRpcError(4201, 'Signed transaction does not match original parameters');
53
512
  }
@@ -63,55 +522,6 @@ class TransactionVerifier {
63
522
  throw new types_js_1.ProviderRpcError(4202, `Transaction verification failed: ${error.message}`);
64
523
  }
65
524
  }
66
- /**
67
- * Vergleicht die Parameter einer ursprünglichen Transaction mit einer dekodierten signierten Transaction
68
- */
69
- static compareTransactionParameters(original, decoded, config) {
70
- if (config.debug) {
71
- console.log('[TransactionVerifier] Comparing transaction parameters:');
72
- console.log('Original:', original);
73
- console.log('Decoded:', decoded);
74
- }
75
- // Kritische Parameter vergleichen
76
- const criticalFields = ['to', 'value', 'data', 'gas', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas', 'nonce'];
77
- for (const field of criticalFields) {
78
- if (original[field] !== undefined) {
79
- // Normalisierung für Hex-Werte (0x-Prefix, Leading Zeros)
80
- const originalValue = this.normalizeHexValue(original[field]);
81
- const decodedValue = this.normalizeHexValue(decoded[field]);
82
- if (originalValue !== decodedValue) {
83
- if (config.debug) {
84
- console.warn(`[TransactionVerifier] Transaction parameter mismatch in '${field}': original='${originalValue}', decoded='${decodedValue}'`);
85
- }
86
- return false;
87
- }
88
- }
89
- }
90
- // ChainId prüfen
91
- if (decoded.chainId && parseInt(decoded.chainId, 16) !== parseInt(config.chainId)) {
92
- if (config.debug) {
93
- console.warn(`[TransactionVerifier] ChainId mismatch: expected=${config.chainId}, decoded=${parseInt(decoded.chainId, 16)}`);
94
- }
95
- return false;
96
- }
97
- return true;
98
- }
99
- /**
100
- * Normalisiert Hex-Werte für Vergleiche (entfernt führende Nullen, stellt 0x-Prefix sicher)
101
- */
102
- static normalizeHexValue(value) {
103
- if (value === undefined || value === null)
104
- return '';
105
- if (typeof value === 'number')
106
- return '0x' + value.toString(16);
107
- const str = value.toString();
108
- if (!str.startsWith('0x'))
109
- return '0x' + str;
110
- // Führende Nullen entfernen, aber mindestens eine Ziffer behalten
111
- const withoutPrefix = str.slice(2);
112
- const normalized = withoutPrefix.replace(/^0+/, '') || '0';
113
- return '0x' + normalized;
114
- }
115
525
  }
116
526
  exports.TransactionVerifier = TransactionVerifier;
117
527
  /**
@@ -198,5 +608,6 @@ exports.TransactionUtils = TransactionUtils;
198
608
  exports.default = {
199
609
  TransactionVerifier,
200
610
  PrototypeProtection,
201
- TransactionUtils
611
+ TransactionUtils,
612
+ compareTransactionParameters
202
613
  };
package/cjs/wasm.js CHANGED
@@ -22,7 +22,7 @@
22
22
  * SPDX-License-Identifier: MIT
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.decode_proof = exports.set_trusted_checkpoint = exports.get_prover_config_hex = exports.getC4w = exports.loadC4WModule = exports.set_wasm_url = exports.get_default_storage = exports.copy_to_c = exports.as_json = exports.as_char_ptr = exports.as_bytes = void 0;
25
+ exports.decode_proof = exports.reset_caches = exports.set_trusted_checkpoint = exports.get_prover_config_hex = exports.getC4w = exports.loadC4WModule = exports.set_wasm_url = exports.get_default_storage = exports.copy_to_c = exports.as_json = exports.as_char_ptr = exports.as_bytes = void 0;
26
26
  exports.module_dir = module_dir;
27
27
  // This file is used only for the CommonJS build output. It must not use
28
28
  // `import.meta` because that is a parse error in CommonJS environments (e.g. Jest).
@@ -64,6 +64,7 @@ exports.loadC4WModule = api.loadC4WModule;
64
64
  exports.getC4w = api.getC4w;
65
65
  exports.get_prover_config_hex = api.get_prover_config_hex;
66
66
  exports.set_trusted_checkpoint = api.set_trusted_checkpoint;
67
+ exports.reset_caches = api.reset_caches;
67
68
  exports.decode_proof = api.decode_proof;
68
69
  /**
69
70
  * Returns the directory of the compiled module. Used in Node to locate
package/cjs/wasm_cjs.js CHANGED
@@ -22,7 +22,7 @@
22
22
  * SPDX-License-Identifier: MIT
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.decode_proof = exports.set_trusted_checkpoint = exports.get_prover_config_hex = exports.getC4w = exports.loadC4WModule = exports.set_wasm_url = exports.get_default_storage = exports.copy_to_c = exports.as_json = exports.as_char_ptr = exports.as_bytes = void 0;
25
+ exports.decode_proof = exports.reset_caches = exports.set_trusted_checkpoint = exports.get_prover_config_hex = exports.getC4w = exports.loadC4WModule = exports.set_wasm_url = exports.get_default_storage = exports.copy_to_c = exports.as_json = exports.as_char_ptr = exports.as_bytes = void 0;
26
26
  exports.module_dir = module_dir;
27
27
  // This file is used only for the CommonJS build output. It must not use
28
28
  // `import.meta` because that is a parse error in CommonJS environments (e.g. Jest).
@@ -64,6 +64,7 @@ exports.loadC4WModule = api.loadC4WModule;
64
64
  exports.getC4w = api.getC4w;
65
65
  exports.get_prover_config_hex = api.get_prover_config_hex;
66
66
  exports.set_trusted_checkpoint = api.set_trusted_checkpoint;
67
+ exports.reset_caches = api.reset_caches;
67
68
  exports.decode_proof = api.decode_proof;
68
69
  /**
69
70
  * Returns the directory of the compiled module. Used in Node to locate
@@ -256,6 +256,14 @@ function createC4wApi(options) {
256
256
  c4w._c4w_set_checkpoint(BigInt(chainId), as_char_ptr(checkpoint, c4w, free_buffers));
257
257
  free_buffers.forEach(ptr => c4w._free(ptr));
258
258
  }
259
+ /**
260
+ * Clears in-process prover/verifier caches (header tags, EL headers, PAP
261
+ * tx cache, prover cache). Persistent storage is left to the host.
262
+ */
263
+ async function reset_caches() {
264
+ const c4w = await getC4w();
265
+ c4w._c4w_reset_caches();
266
+ }
259
267
  async function decode_proof(proof) {
260
268
  const c4w = await getC4w();
261
269
  const ptr = copy_to_c(proof, c4w);
@@ -271,6 +279,7 @@ function createC4wApi(options) {
271
279
  getC4w,
272
280
  get_prover_config_hex,
273
281
  set_trusted_checkpoint,
282
+ reset_caches,
274
283
  decode_proof,
275
284
  };
276
285
  }
package/index.d.ts CHANGED
@@ -34,7 +34,7 @@ export { getRuntime, type C4Runtime, type RuntimeStatus } from './runtime.js';
34
34
  */
35
35
  export declare function decode_proof(proof: Uint8Array): Promise<any>;
36
36
  export { ProviderRpcError, RequestArguments, Cache, C4Config, DataRequest, C4MethodType as MethodType, ProviderMessage, EthSubscribeSubscriptionType, EthNewFilterType, ProverMode };
37
- export { TransactionVerifier, PrototypeProtection } from './transactionVerifier.js';
37
+ export { TransactionVerifier, PrototypeProtection, compareTransactionParameters } from './transactionVerifier.js';
38
38
  export default class C4Client {
39
39
  config: C4Config;
40
40
  private eventEmitter;
@@ -100,6 +100,11 @@ export default class C4Client {
100
100
  */
101
101
  rpc(method: string, args: any[], method_type?: C4MethodType): Promise<any>;
102
102
  static register_storage(storage: C4Storage): Promise<void>;
103
+ /**
104
+ * Clears in-process prover/verifier caches. Call between fixture-backed
105
+ * tests that share one WASM/native process.
106
+ */
107
+ static reset_caches(): Promise<void>;
103
108
  request(args: RequestArguments): Promise<unknown>;
104
109
  /**
105
110
  * Registers an event listener on the Colibri client.
package/index.js CHANGED
@@ -46,7 +46,7 @@ export async function decode_proof(proof) {
46
46
  // Re-export types needed by consumers of the C4Client module
47
47
  export { ProviderRpcError, C4MethodType as MethodType };
48
48
  // Re-export transaction verification utilities
49
- export { TransactionVerifier, PrototypeProtection } from './transactionVerifier.js';
49
+ export { TransactionVerifier, PrototypeProtection, compareTransactionParameters } from './transactionVerifier.js';
50
50
  // EIP-3668 / CCIP-Read: the EVM ran to completion but reverted. Map the
51
51
  // verifier's `revert` status to a structured EIP-1193 error (code 3 + data)
52
52
  // so clients like ethers can decode OffchainLookup payloads from
@@ -102,6 +102,8 @@ class C4Client {
102
102
  this.flags |= (1 << 12);
103
103
  this.verify_flags |= (1 << 9);
104
104
  }
105
+ if (this.config.persist_header_cache)
106
+ this.verify_flags |= (1 << 10);
105
107
  if (!this.config.warningHandler)
106
108
  this.config.warningHandler = async (req, message) => console.warn(message);
107
109
  if (!this.config.proofStrategy)
@@ -267,6 +269,13 @@ class C4Client {
267
269
  static async register_storage(storage) {
268
270
  (await getRuntime()).registerStorage(storage);
269
271
  }
272
+ /**
273
+ * Clears in-process prover/verifier caches. Call between fixture-backed
274
+ * tests that share one WASM/native process.
275
+ */
276
+ static async reset_caches() {
277
+ (await getRuntime()).resetCaches();
278
+ }
270
279
  async request(args) {
271
280
  if (!this.connectionState.initialConnectionAttempted) {
272
281
  await this.connectionState.attemptInitialConnection();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corpus-core/colibri-stateless",
3
- "version": "2.0.6",
3
+ "version": "3.0.0",
4
4
  "description": "Colibri is a stateless and trustless ethereum client, which is optimized for the mobile apps or embedded devices, because it does not hols the state, but verifies on demand.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -20,9 +20,10 @@
20
20
  },
21
21
  "scripts": {
22
22
  "test": "npm run test:node && npm run test:webpack",
23
- "test:node": "node --test test/rpc.test.mjs test/cjs_bridge.test.mjs test/runtime_selection.test.mjs",
23
+ "test:node": "node --test test/rpc.test.mjs test/cjs_bridge.test.mjs test/runtime_selection.test.mjs test/transactionVerifier.test.mjs",
24
24
  "test:node:native": "node --test test/native.test.mjs",
25
25
  "test:integration": "C4_RUN_INTEGRATION=1 node --test test/integration.test.mjs",
26
+ "test:rpc-integration": "C4_RUN_RPC_INTEGRATION=1 node --test --test-concurrency=1 --test-timeout=3600000 test/rpc_integration.test.mjs",
26
27
  "test:ccip": "C4_RUN_INTEGRATION=1 node --test test/ens_ccip.test.mjs",
27
28
  "test:webpack": "npm run webpack:install && npm run webpack:build",
28
29
  "webpack:install": "cd test/webpack && npm install --legacy-peer-deps",