@aztec/pxe 0.0.1-commit.a5db02d → 0.0.1-commit.aa0c64f

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.
Files changed (103) hide show
  1. package/dest/bin/check_oracle_version.js +3 -8
  2. package/dest/bin/index.d.ts +2 -2
  3. package/dest/bin/index.d.ts.map +1 -1
  4. package/dest/bin/index.js +1 -1
  5. package/dest/bin/oracle_version_helpers.d.ts +8 -25
  6. package/dest/bin/oracle_version_helpers.d.ts.map +1 -1
  7. package/dest/bin/oracle_version_helpers.js +11 -219
  8. package/dest/config/package_info.js +1 -1
  9. package/dest/contract/helpers.d.ts +1 -1
  10. package/dest/contract/helpers.d.ts.map +1 -1
  11. package/dest/contract/helpers.js +3 -3
  12. package/dest/contract/skip_sync_contracts.d.ts +11 -0
  13. package/dest/contract/skip_sync_contracts.d.ts.map +1 -0
  14. package/dest/contract/skip_sync_contracts.js +18 -0
  15. package/dest/contract_function_simulator/contract_function_simulator.d.ts +1 -1
  16. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  17. package/dest/contract_function_simulator/contract_function_simulator.js +5 -3
  18. package/dest/contract_function_simulator/index.d.ts +3 -3
  19. package/dest/contract_function_simulator/index.d.ts.map +1 -1
  20. package/dest/contract_function_simulator/index.js +1 -2
  21. package/dest/contract_function_simulator/noir-structs/resolved_tx.d.ts +7 -9
  22. package/dest/contract_function_simulator/noir-structs/resolved_tx.d.ts.map +1 -1
  23. package/dest/contract_function_simulator/noir-structs/resolved_tx.js +3 -44
  24. package/dest/contract_function_simulator/noir-structs/tx_effect_data.d.ts +5 -4
  25. package/dest/contract_function_simulator/noir-structs/tx_effect_data.d.ts.map +1 -1
  26. package/dest/contract_function_simulator/noir-structs/tx_effect_data.js +2 -2
  27. package/dest/contract_function_simulator/oracle/oracle_registry.d.ts +3 -3
  28. package/dest/contract_function_simulator/oracle/oracle_registry.d.ts.map +1 -1
  29. package/dest/contract_function_simulator/oracle/oracle_registry.js +6 -6
  30. package/dest/contract_function_simulator/oracle/oracle_type_mappings.d.ts +87 -35
  31. package/dest/contract_function_simulator/oracle/oracle_type_mappings.d.ts.map +1 -1
  32. package/dest/contract_function_simulator/oracle/oracle_type_mappings.js +286 -286
  33. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +1 -1
  34. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  35. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +5 -6
  36. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +2 -2
  37. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  38. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +2 -1
  39. package/dest/entrypoints/client/store.d.ts +1 -1
  40. package/dest/entrypoints/client/store.d.ts.map +1 -1
  41. package/dest/entrypoints/client/store.js +16 -2
  42. package/dest/logs/log_service.d.ts +1 -1
  43. package/dest/logs/log_service.d.ts.map +1 -1
  44. package/dest/logs/log_service.js +7 -2
  45. package/dest/messages/tx_resolver_service.d.ts +2 -2
  46. package/dest/messages/tx_resolver_service.d.ts.map +1 -1
  47. package/dest/messages/tx_resolver_service.js +7 -2
  48. package/dest/oracle_version.d.ts +1 -1
  49. package/dest/oracle_version.js +2 -2
  50. package/dest/private_kernel/batch_planner.d.ts +1 -1
  51. package/dest/private_kernel/batch_planner.d.ts.map +1 -1
  52. package/dest/private_kernel/batch_planner.js +1 -1
  53. package/dest/private_kernel/private_kernel_execution_prover.d.ts +1 -1
  54. package/dest/private_kernel/private_kernel_execution_prover.d.ts.map +1 -1
  55. package/dest/private_kernel/private_kernel_execution_prover.js +45 -5
  56. package/dest/storage/fact_store/fact_store.d.ts +1 -1
  57. package/dest/storage/fact_store/fact_store.js +1 -1
  58. package/dest/storage/fact_store/index.d.ts +2 -2
  59. package/dest/storage/fact_store/index.d.ts.map +1 -1
  60. package/dest/storage/fact_store/index.js +1 -1
  61. package/dest/storage/fact_store/origin_state.d.ts +3 -1
  62. package/dest/storage/fact_store/origin_state.d.ts.map +1 -1
  63. package/dest/storage/fact_store/origin_state.js +7 -0
  64. package/dest/tagging/constants.d.ts +2 -1
  65. package/dest/tagging/constants.d.ts.map +1 -1
  66. package/dest/tagging/constants.js +9 -0
  67. package/dest/tagging/index.d.ts +2 -2
  68. package/dest/tagging/index.d.ts.map +1 -1
  69. package/dest/tagging/index.js +1 -1
  70. package/dest/tagging/recipient_sync/sync_tagged_private_logs.d.ts +9 -4
  71. package/dest/tagging/recipient_sync/sync_tagged_private_logs.d.ts.map +1 -1
  72. package/dest/tagging/recipient_sync/sync_tagged_private_logs.js +60 -25
  73. package/dest/tagging/testing/tag_query_test_utils.d.ts +6 -0
  74. package/dest/tagging/testing/tag_query_test_utils.d.ts.map +1 -0
  75. package/dest/tagging/testing/tag_query_test_utils.js +10 -0
  76. package/package.json +17 -17
  77. package/src/bin/check_oracle_version.ts +3 -10
  78. package/src/bin/index.ts +1 -5
  79. package/src/bin/oracle_version_helpers.ts +11 -268
  80. package/src/config/package_info.ts +1 -1
  81. package/src/contract/helpers.ts +3 -3
  82. package/src/contract/skip_sync_contracts.ts +23 -0
  83. package/src/contract_function_simulator/contract_function_simulator.ts +5 -3
  84. package/src/contract_function_simulator/index.ts +23 -3
  85. package/src/contract_function_simulator/noir-structs/resolved_tx.ts +11 -45
  86. package/src/contract_function_simulator/noir-structs/tx_effect_data.ts +4 -3
  87. package/src/contract_function_simulator/oracle/oracle_registry.ts +7 -8
  88. package/src/contract_function_simulator/oracle/oracle_type_mappings.ts +285 -197
  89. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +12 -7
  90. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +8 -3
  91. package/src/entrypoints/client/store.ts +23 -7
  92. package/src/logs/log_service.ts +7 -2
  93. package/src/messages/tx_resolver_service.ts +8 -8
  94. package/src/oracle_version.ts +2 -2
  95. package/src/private_kernel/batch_planner.ts +0 -1
  96. package/src/private_kernel/private_kernel_execution_prover.ts +90 -4
  97. package/src/storage/fact_store/fact_store.ts +1 -1
  98. package/src/storage/fact_store/index.ts +1 -0
  99. package/src/storage/fact_store/origin_state.ts +9 -0
  100. package/src/tagging/constants.ts +10 -0
  101. package/src/tagging/index.ts +1 -1
  102. package/src/tagging/recipient_sync/sync_tagged_private_logs.ts +103 -46
  103. package/src/tagging/testing/tag_query_test_utils.ts +11 -0
@@ -36,6 +36,7 @@ import {
36
36
  type TaggingSecretStrategy,
37
37
  } from '../../hooks/resolve_tagging_secret_strategy.js';
38
38
  import { NoteService } from '../../notes/note_service.js';
39
+ import { assertAllowedScope } from '../../storage/allowed_scopes.js';
39
40
  import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js';
40
41
  import { syncSenderTaggingIndexes } from '../../tagging/index.js';
41
42
  import type { ExecutionNoteCache } from '../execution_note_cache.js';
@@ -321,7 +322,17 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
321
322
  * @returns The app tagging secret, or `None` if the recipient is invalid.
322
323
  */
323
324
  public async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise<Option<Fr>> {
324
- const extendedSecret = await this.#calculateAppTaggingSecret(this.contractAddress, sender, recipient);
325
+ assertAllowedScope(sender, this.scopes);
326
+
327
+ const senderCompleteAddress = await this.getCompleteAddressOrFail(sender);
328
+ const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
329
+ const extendedSecret = await AppTaggingSecret.computeViaEcdh(
330
+ senderCompleteAddress,
331
+ senderIvsk,
332
+ recipient,
333
+ this.contractAddress,
334
+ recipient,
335
+ );
325
336
 
326
337
  if (!extendedSecret) {
327
338
  this.logger.warn(`Computing a tagging secret for invalid recipient ${recipient} - returning no secret`, {
@@ -354,12 +365,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
354
365
  return index;
355
366
  }
356
367
 
357
- async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
358
- const senderCompleteAddress = await this.getCompleteAddressOrFail(sender);
359
- const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
360
- return AppTaggingSecret.computeViaEcdh(senderCompleteAddress, senderIvsk, recipient, contractAddress, recipient);
361
- }
362
-
363
368
  async #getIndexToUseForSecret(secret: AppTaggingSecret): Promise<number> {
364
369
  // If we have the tagging index in the cache, we use it. If not we obtain it from the execution data provider.
365
370
  const lastUsedIndexInTx = this.taggingIndexCache.getLastUsedIndex(secret);
@@ -859,13 +859,17 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
859
859
  // TODO(#11849): consider replacing this oracle with a pure Noir implementation of aes decryption.
860
860
  public async decryptAes128(
861
861
  ciphertext: BoundedVec<number>,
862
- iv: Buffer,
863
- symKey: Buffer,
862
+ iv: number[],
863
+ symKey: number[],
864
864
  ): Promise<Option<BoundedVec<number>>> {
865
865
  const capacity = ciphertext.maxLength;
866
866
  try {
867
867
  const aes128 = new Aes128();
868
- const plaintext = await aes128.decryptBufferCBC(Buffer.from(ciphertext.data), iv, symKey);
868
+ const plaintext = await aes128.decryptBufferCBC(
869
+ Buffer.from(ciphertext.data),
870
+ Buffer.from(iv),
871
+ Buffer.from(symKey),
872
+ );
869
873
  return Option.some(BoundedVec.from<number>({ data: [...plaintext], maxLength: capacity }));
870
874
  } catch {
871
875
  return Option.none({ maxLength: capacity });
@@ -1143,6 +1147,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
1143
1147
  #toTxEffectData(txEffect: TxEffect): TxEffectData {
1144
1148
  return {
1145
1149
  ...txEffect,
1150
+ revertCode: txEffect.revertCode.getCode(),
1146
1151
  publicLogs: FlatPublicLogs.fromLogs(txEffect.publicLogs),
1147
1152
  contractClassLogs: txEffect.contractClassLogs.map(log => ({
1148
1153
  contractAddress: log.contractAddress,
@@ -1,6 +1,11 @@
1
1
  import type { EthAddress } from '@aztec/foundation/eth-address';
2
2
  import { type Logger, createLogger } from '@aztec/foundation/log';
3
- import { AztecSQLiteOPFSStore, storePoolDirectory } from '@aztec/kv-store/sqlite-opfs';
3
+ import {
4
+ AztecSQLiteOPFSStore,
5
+ SqliteCorruptionError,
6
+ deleteStore,
7
+ storePoolDirectory,
8
+ } from '@aztec/kv-store/sqlite-opfs';
4
9
 
5
10
  import { assertStoreIdentity, effectiveStoreName } from '../../storage/store_identity.js';
6
11
 
@@ -21,12 +26,23 @@ export async function openBrowserStore(
21
26
  storeName,
22
27
  dataStoreMapSizeKb: config.dataStoreMapSizeKb,
23
28
  });
24
- const store = await AztecSQLiteOPFSStore.open(
25
- createLogger('kv-store:sqlite-opfs'),
26
- storeName,
27
- false,
28
- storePoolDirectory(storeName),
29
- );
29
+ const openStore = () =>
30
+ AztecSQLiteOPFSStore.open(createLogger('kv-store:sqlite-opfs'), storeName, false, storePoolDirectory(storeName));
31
+
32
+ let store: AztecSQLiteOPFSStore;
33
+ try {
34
+ store = await openStore();
35
+ } catch (err) {
36
+ if (!(err instanceof SqliteCorruptionError)) {
37
+ throw err;
38
+ }
39
+ // A corrupt image is unrecoverable — no retry against the same bytes helps. Wipe the store's OPFS directory
40
+ // (safe: the failed open left no SAH-pool lock behind) and reopen a fresh, empty one, so the browser self-heals
41
+ // into a normal first-run rather than dead-ending on "database disk image is malformed" every load.
42
+ log.warn(`${storeName} store is corrupt (${err.message}); wiping and reopening fresh`);
43
+ await deleteStore(storeName);
44
+ store = await openStore();
45
+ }
30
46
  try {
31
47
  await assertStoreIdentity(store, storeName, identity);
32
48
  } catch (err) {
@@ -22,7 +22,6 @@ import {
22
22
  } from '../contract_function_simulator/noir-structs/log_retrieval_request.js';
23
23
  import type { LogRetrievalResponse } from '../contract_function_simulator/noir-structs/log_retrieval_response.js';
24
24
  import type { PendingTaggedLog } from '../contract_function_simulator/noir-structs/pending_tagged_log.js';
25
- import { ResolvedTx } from '../contract_function_simulator/noir-structs/resolved_tx.js';
26
25
  import { AddressStore } from '../storage/address_store/address_store.js';
27
26
  import { assertAllowedScope } from '../storage/allowed_scopes.js';
28
27
  import type { RecipientTaggingStore } from '../storage/tagging_store/recipient_tagging_store.js';
@@ -225,7 +224,13 @@ export class LogService {
225
224
  }
226
225
  return {
227
226
  log: log.logData,
228
- context: new ResolvedTx(log.txHash, noteHashes, nullifiers[0], log.blockNumber, log.blockHash.toFr()),
227
+ context: {
228
+ txHash: log.txHash,
229
+ uniqueNoteHashesInTx: noteHashes,
230
+ firstNullifierInTx: nullifiers[0],
231
+ blockNumber: log.blockNumber,
232
+ blockHash: log.blockHash.toFr(),
233
+ },
229
234
  };
230
235
  });
231
236
  }
@@ -3,7 +3,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
3
3
  import type { AztecNode } from '@aztec/stdlib/interfaces/server';
4
4
  import { type IndexedTxEffect, TxHash } from '@aztec/stdlib/tx';
5
5
 
6
- import { ResolvedTx } from '../contract_function_simulator/noir-structs/resolved_tx.js';
6
+ import type { ResolvedTx } from '../contract_function_simulator/noir-structs/resolved_tx.js';
7
7
 
8
8
  /** Resolves transaction hashes into their on-chain context (note hashes, first nullifier, and mined block). */
9
9
  export class TxResolverService {
@@ -57,13 +57,13 @@ export class TxResolverService {
57
57
  throw new Error(`Tx effect for ${txHash} has no nullifiers`);
58
58
  }
59
59
 
60
- return new ResolvedTx(
61
- data.txHash,
62
- data.noteHashes,
63
- data.nullifiers[0],
64
- txEffect.l2BlockNumber,
65
- txEffect.l2BlockHash.toFr(),
66
- );
60
+ return {
61
+ txHash: data.txHash,
62
+ uniqueNoteHashesInTx: data.noteHashes,
63
+ firstNullifierInTx: data.nullifiers[0],
64
+ blockNumber: txEffect.l2BlockNumber,
65
+ blockHash: txEffect.l2BlockHash.toFr(),
66
+ };
67
67
  });
68
68
  }
69
69
  }
@@ -13,10 +13,10 @@
13
13
  export const ORACLE_VERSION_MAJOR = 30;
14
14
  export const ORACLE_VERSION_MINOR = 8;
15
15
 
16
- /// This hash is computed from the `ORACLE_REGISTRY` declaration (each oracle's name, ordered parameter names and
16
+ /// This hash is computed from `ORACLE_REGISTRY` (each oracle's name, ordered parameter names and
17
17
  /// types, and return type) and is used to detect when the oracle interface changes. When it does, you need to either:
18
18
  /// - increment `ORACLE_VERSION_MAJOR` and reset `ORACLE_VERSION_MINOR` to zero if the change is breaking, or
19
19
  /// - increment only `ORACLE_VERSION_MINOR` if the change is additive (a new oracle was added).
20
20
  ///
21
21
  /// These constants must be kept in sync between this file and `noir-projects/aztec-nr/aztec/src/oracle/version.nr`.
22
- export const ORACLE_INTERFACE_HASH = 'b302a8e65d37043c0a0dc08a39895603122dce334843f0442462edb3f56e32e2';
22
+ export const ORACLE_INTERFACE_HASH = 'fec4d3a95fc57a62a20bfeb659bb3bfd91b31cd93e07f8cf97b368f9f55cc67b';
@@ -150,7 +150,6 @@ export function appendApp(
150
150
  acc.feePayer,
151
151
  acc.expirationTimestamp,
152
152
  acc.isPrivateOnly,
153
- acc.claimedFirstNullifier,
154
153
  acc.claimedRevertibleCounter,
155
154
  );
156
155
  }
@@ -1,6 +1,7 @@
1
+ import { CircuitKind } from '@aztec/bb.js';
1
2
  import { MAX_APPS_PER_KERNEL } from '@aztec/constants';
2
3
  import { uniqueBy } from '@aztec/foundation/collection';
3
- import { vkAsFieldsMegaHonk } from '@aztec/foundation/crypto/keys';
4
+ import { vkAsFields } from '@aztec/foundation/crypto/keys';
4
5
  import { Fr } from '@aztec/foundation/curves/bn254';
5
6
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
6
7
  import { pushTestData } from '@aztec/foundation/testing';
@@ -20,9 +21,13 @@ import {
20
21
  type PrivateKernelExecutionProofOutput,
21
22
  PrivateKernelInit2CircuitPrivateInputs,
22
23
  PrivateKernelInit3CircuitPrivateInputs,
24
+ PrivateKernelInit4CircuitPrivateInputs,
25
+ PrivateKernelInit5CircuitPrivateInputs,
23
26
  PrivateKernelInitCircuitPrivateInputs,
24
27
  PrivateKernelInner2CircuitPrivateInputs,
25
28
  PrivateKernelInner3CircuitPrivateInputs,
29
+ PrivateKernelInner4CircuitPrivateInputs,
30
+ PrivateKernelInner5CircuitPrivateInputs,
26
31
  PrivateKernelInnerCircuitPrivateInputs,
27
32
  PrivateKernelResetTailCircuitPrivateInputs,
28
33
  type PrivateKernelSimulateOutput,
@@ -145,6 +150,7 @@ export class PrivateKernelExecutionProver {
145
150
  bytecode: output.bytecode,
146
151
  witness: output.outputWitness,
147
152
  vk: output.verificationKey.keyAsBytes,
153
+ kind: CircuitKind.Kernel,
148
154
  timings: {
149
155
  witgen: witgenTimer.ms(),
150
156
  },
@@ -230,6 +236,7 @@ export class PrivateKernelExecutionProver {
230
236
  bytecode: tailOutput.bytecode,
231
237
  witness: tailOutput.outputWitness,
232
238
  vk: tailOutput.verificationKey.keyAsBytes,
239
+ kind: CircuitKind.Kernel,
233
240
  timings: {
234
241
  witgen: witgenTimer.ms(),
235
242
  },
@@ -261,6 +268,7 @@ export class PrivateKernelExecutionProver {
261
268
  bytecode: hidingOutput.bytecode,
262
269
  witness: hidingOutput.outputWitness,
263
270
  vk: hidingOutput.verificationKey.keyAsBytes,
271
+ kind: CircuitKind.HidingKernel,
264
272
  timings: {
265
273
  witgen: witgenTimer.ms(),
266
274
  },
@@ -270,7 +278,11 @@ export class PrivateKernelExecutionProver {
270
278
  if (profileMode == 'gates' || profileMode == 'full') {
271
279
  for (const entry of executionSteps) {
272
280
  const gateCountTimer = new Timer();
273
- const gateCount = await this.proofCreator.computeGateCountForCircuit(entry.bytecode, entry.functionName);
281
+ const gateCount = await this.proofCreator.computeGateCountForCircuit(
282
+ entry.bytecode,
283
+ entry.functionName,
284
+ entry.kind,
285
+ );
274
286
  entry.gateCount = gateCount;
275
287
  entry.timings.gateCount = gateCountTimer.ms();
276
288
  }
@@ -366,6 +378,7 @@ export class PrivateKernelExecutionProver {
366
378
  bytecode: next.acir,
367
379
  witness: next.partialWitness,
368
380
  vk: next.vk,
381
+ kind: CircuitKind.App,
369
382
  timings: {
370
383
  witgen: next.profileResult?.timings.witgen ?? 0,
371
384
  oracles: next.profileResult?.timings.oracles,
@@ -399,8 +412,8 @@ export class PrivateKernelExecutionProver {
399
412
  ) {
400
413
  const { contractAddress, functionSelector } = publicInputs.callContext;
401
414
 
402
- const vkAsFields = await vkAsFieldsMegaHonk(vkAsBuffer);
403
- const vk = await VerificationKeyAsFields.fromKey(vkAsFields);
415
+ const vkFields = await vkAsFields(vkAsBuffer, CircuitKind.App);
416
+ const vk = await VerificationKeyAsFields.fromKey(vkFields);
404
417
 
405
418
  const { currentContractClassId, publicKeys, saltedInitializationHash } =
406
419
  await this.oracle.getContractAddressPreimage(contractAddress);
@@ -514,6 +527,47 @@ export class PrivateKernelExecutionProver {
514
527
  functionName = 'private_kernel_init_3';
515
528
  break;
516
529
  }
530
+ case 4: {
531
+ const proofInput = new PrivateKernelInit4CircuitPrivateInputs(
532
+ txRequest,
533
+ vkTreeRoot,
534
+ ProtocolContractsList,
535
+ apps[0],
536
+ apps[1],
537
+ apps[2],
538
+ apps[3],
539
+ isPrivateOnlyTx,
540
+ minRevertibleSideEffectCounter,
541
+ );
542
+ this.log.debug(`Calling private kernel init_4 with isPrivateOnly ${isPrivateOnlyTx}`);
543
+ pushTestData('private-kernel-inputs-init-4', proofInput);
544
+ output = generateWitnesses
545
+ ? await this.proofCreator.generateInit4Output(proofInput)
546
+ : await this.proofCreator.simulateInit4(proofInput);
547
+ functionName = 'private_kernel_init_4';
548
+ break;
549
+ }
550
+ case 5: {
551
+ const proofInput = new PrivateKernelInit5CircuitPrivateInputs(
552
+ txRequest,
553
+ vkTreeRoot,
554
+ ProtocolContractsList,
555
+ apps[0],
556
+ apps[1],
557
+ apps[2],
558
+ apps[3],
559
+ apps[4],
560
+ isPrivateOnlyTx,
561
+ minRevertibleSideEffectCounter,
562
+ );
563
+ this.log.debug(`Calling private kernel init_5 with isPrivateOnly ${isPrivateOnlyTx}`);
564
+ pushTestData('private-kernel-inputs-init-5', proofInput);
565
+ output = generateWitnesses
566
+ ? await this.proofCreator.generateInit5Output(proofInput)
567
+ : await this.proofCreator.simulateInit5(proofInput);
568
+ functionName = 'private_kernel_init_5';
569
+ break;
570
+ }
517
571
  default:
518
572
  throw new Error(`Unsupported init kernel batch size: ${apps.length}`);
519
573
  }
@@ -548,6 +602,37 @@ export class PrivateKernelExecutionProver {
548
602
  functionName = 'private_kernel_inner_3';
549
603
  break;
550
604
  }
605
+ case 4: {
606
+ const proofInput = new PrivateKernelInner4CircuitPrivateInputs(
607
+ previousKernelData,
608
+ apps[0],
609
+ apps[1],
610
+ apps[2],
611
+ apps[3],
612
+ );
613
+ pushTestData('private-kernel-inputs-inner-4', proofInput);
614
+ output = generateWitnesses
615
+ ? await this.proofCreator.generateInner4Output(proofInput)
616
+ : await this.proofCreator.simulateInner4(proofInput);
617
+ functionName = 'private_kernel_inner_4';
618
+ break;
619
+ }
620
+ case 5: {
621
+ const proofInput = new PrivateKernelInner5CircuitPrivateInputs(
622
+ previousKernelData,
623
+ apps[0],
624
+ apps[1],
625
+ apps[2],
626
+ apps[3],
627
+ apps[4],
628
+ );
629
+ pushTestData('private-kernel-inputs-inner-5', proofInput);
630
+ output = generateWitnesses
631
+ ? await this.proofCreator.generateInner5Output(proofInput)
632
+ : await this.proofCreator.simulateInner5(proofInput);
633
+ functionName = 'private_kernel_inner_5';
634
+ break;
635
+ }
551
636
  default:
552
637
  throw new Error(`Unsupported inner kernel batch size: ${apps.length}`);
553
638
  }
@@ -558,6 +643,7 @@ export class PrivateKernelExecutionProver {
558
643
  bytecode: output.bytecode,
559
644
  witness: output.outputWitness,
560
645
  vk: output.verificationKey.keyAsBytes,
646
+ kind: CircuitKind.Kernel,
561
647
  timings: {
562
648
  witgen: witgenTimer.ms(),
563
649
  },
@@ -82,7 +82,7 @@ export class FactStore implements StagedStore {
82
82
  }
83
83
 
84
84
  /**
85
- * Records a fact in a collection, visible under the given scope.
85
+ * Records a fact in a collection.
86
86
  *
87
87
  * The collection is created implicitly on the first fact recorded for its key: recording into an existing collection
88
88
  * just adds to it.
@@ -5,6 +5,7 @@ export type { Fact } from './stored_fact.js';
5
5
  export {
6
6
  OriginBlockState,
7
7
  classifyOriginBlockState,
8
+ originBlockStateFromNumber,
8
9
  anchoredTipBlockNumbers,
9
10
  toFactWithOriginState,
10
11
  type TipBlockNumbers,
@@ -21,6 +21,15 @@ export enum OriginBlockState {
21
21
  Finalized = 3,
22
22
  }
23
23
 
24
+ /** Parses a numeric origin-block-state discriminant, rejecting unknown values. */
25
+ export function originBlockStateFromNumber(value: number): OriginBlockState {
26
+ if (!(value in OriginBlockState)) {
27
+ const validNames = Object.keys(OriginBlockState).filter(k => isNaN(Number(k)));
28
+ throw new Error(`Invalid OriginBlockState value ${value}, expected one of ${validNames.join(', ')}`);
29
+ }
30
+ return value as OriginBlockState;
31
+ }
32
+
24
33
  /** The two chain-tip block numbers needed to classify an origin block (`finalized <= proven` always holds). */
25
34
  export type TipBlockNumbers = { provenBlockNumber: number; finalizedBlockNumber: number };
26
35
 
@@ -18,3 +18,13 @@ import { MAX_PRIVATE_LOGS_PER_TX } from '@aztec/constants';
18
18
  // reserved at log emission time, before squashing is decided, and the kernel's reset/squash loop is not bounded by
19
19
  // MAX_PRIVATE_LOGS_PER_TX. No fixed window value closes that gap.
20
20
  export const UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN = MAX_PRIVATE_LOGS_PER_TX + 20;
21
+
22
+ // The number of tags probed per constrained secret in the first round.
23
+ //
24
+ // The probe doubles each round (2, 4, 8, ..., capped at UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN) while every probed
25
+ // index is a hit, stopping at the first missing tag. Constrained delivery is gapless, so a single missing tag proves
26
+ // the stream has ended: at steady state this turns a full WINDOW_LEN probe into two tags. A secret K logs behind
27
+ // catches up in ~log2(K) round-trips while the probe is still doubling (2, 4, 8, 16, 32), but once it saturates the cap
28
+ // and advances WINDOW_LEN tags per round, deeper catch-up is linear at ~K/WINDOW_LEN rounds. Either way it beats both
29
+ // the full window every round and one round per log.
30
+ export const INITIAL_CONSTRAINED_PROBE_LEN = 2;
@@ -12,7 +12,7 @@
12
12
  export { syncTaggedPrivateLogs } from './recipient_sync/sync_tagged_private_logs.js';
13
13
  export { syncSenderTaggingIndexes } from './sender_sync/sync_sender_tagging_indexes.js';
14
14
  export { persistSenderTaggingIndexRangesForTx } from './persist_sender_tagging_index_ranges.js';
15
- export { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from './constants.js';
15
+ export { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN, INITIAL_CONSTRAINED_PROBE_LEN } from './constants.js';
16
16
  export { getAllPrivateLogsByTags, getAllPublicLogsByTagsFromContract } from './get_all_logs_by_tags.js';
17
17
 
18
18
  // Re-export tagging-related types from stdlib
@@ -7,7 +7,7 @@ import { AppTaggingSecretKind, SiloedTag } from '@aztec/stdlib/logs';
7
7
  import type { BlockHeader } from '@aztec/stdlib/tx';
8
8
 
9
9
  import type { RecipientTaggingStore } from '../../storage/tagging_store/recipient_tagging_store.js';
10
- import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../constants.js';
10
+ import { INITIAL_CONSTRAINED_PROBE_LEN, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../constants.js';
11
11
  import { getAllPrivateLogsByTags } from '../get_all_logs_by_tags.js';
12
12
  import { findHighestIndexes } from './utils/find_highest_indexes.js';
13
13
 
@@ -62,9 +62,14 @@ import { findHighestIndexes } from './utils/find_highest_indexes.js';
62
62
  * - The lower bound is `highestFinalizedIndex + 1`. The `highestAgedIndex` mechanism is unnecessary because
63
63
  * the nullifier chain prevents out-of-order index usage: no device can fill in a lower index after a higher
64
64
  * one is already on-chain.
65
- * - The scan stops at the first gap in the index sequence. If all indexes in the batch have logs, the window
66
- * advances. If a gap is found, the sequence has ended and no further logs exist.
67
- * - The upper bound is the same as unconstrained: `highestFinalizedIndex + WINDOW_LEN`.
65
+ * - Because the sequence is gapless, the scan stops at the first missing index: that gap proves the sequence has
66
+ * ended and no further logs exist. We exploit this with a doubling first-miss probe (see
67
+ * `INITIAL_CONSTRAINED_PROBE_LEN` in ../constants.ts), so a
68
+ * steady-state sync with no new logs costs two tags instead of the whole window. The probe length is in-memory
69
+ * state scoped to one sync call, so every sync restarts at the initial probe.
70
+ * - The upper bound is the same as unconstrained: `highestFinalizedIndex + WINDOW_LEN`. Advancing the probe is
71
+ * decoupled from persisting the finalized index, so unfinalized logs at the top of the run are still fetched
72
+ * while only the finalized prefix is persisted.
68
73
  *
69
74
  * # Batching across secrets
70
75
  *
@@ -109,19 +114,23 @@ export async function syncTaggedPrivateLogs(
109
114
 
110
115
  // Persist new indexes. If the finalized index moved forward, the window advances
111
116
  // and we need another round for this secret.
112
- const processForKind =
113
- pendingSecret.secret.kind === AppTaggingSecretKind.CONSTRAINED
114
- ? processConstrainedResults
115
- : processUnconstrainedResults;
116
-
117
- return await processForKind(
118
- pendingSecret,
119
- logsFoundWithSecret,
120
- taggingStore,
121
- currentTimestamp,
122
- finalizedBlockNumber,
123
- jobId,
124
- );
117
+ return pendingSecret.kind === AppTaggingSecretKind.CONSTRAINED
118
+ ? await processConstrainedResults(
119
+ pendingSecret,
120
+ logsFoundWithSecret,
121
+ taggingStore,
122
+ currentTimestamp,
123
+ finalizedBlockNumber,
124
+ jobId,
125
+ )
126
+ : await processUnconstrainedResults(
127
+ pendingSecret,
128
+ logsFoundWithSecret,
129
+ taggingStore,
130
+ currentTimestamp,
131
+ finalizedBlockNumber,
132
+ jobId,
133
+ );
125
134
  }),
126
135
  );
127
136
 
@@ -138,18 +147,29 @@ function getIndexRangesForSecrets(
138
147
  jobId: string,
139
148
  ): Promise<PendingSecret[]> {
140
149
  return Promise.all(
141
- secrets.map(async secret => {
150
+ secrets.map(async (secret): Promise<PendingSecret> => {
142
151
  const currentHighestFinalizedIndex = await taggingStore.getHighestFinalizedIndex(secret, jobId);
152
+ const boundEnd = (currentHighestFinalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
153
+
154
+ if (secret.kind === AppTaggingSecretKind.CONSTRAINED) {
155
+ // Constrained streams are gapless and resume at the finalized index, so probe a small initial window and stop
156
+ // at the first missing tag instead of always fetching the full window. The probe grows each round up to the
157
+ // window cap.
158
+ const start = currentHighestFinalizedIndex === undefined ? 0 : currentHighestFinalizedIndex + 1;
159
+ return {
160
+ kind: AppTaggingSecretKind.CONSTRAINED,
161
+ secret,
162
+ start,
163
+ end: Math.min(boundEnd, start + INITIAL_CONSTRAINED_PROBE_LEN),
164
+ boundEnd,
165
+ probeLen: INITIAL_CONSTRAINED_PROBE_LEN,
166
+ };
167
+ }
143
168
 
144
- const highestIndexBeforeStart =
145
- secret.kind === AppTaggingSecretKind.CONSTRAINED
146
- ? currentHighestFinalizedIndex
147
- : await taggingStore.getHighestAgedIndex(secret, jobId);
148
- const start = highestIndexBeforeStart === undefined ? 0 : highestIndexBeforeStart + 1;
149
-
150
- const end = (currentHighestFinalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
151
-
152
- return { secret, start, end };
169
+ // Unconstrained secrets can have gaps, so they scan the whole window starting past the highest aged index.
170
+ const highestAgedIndex = await taggingStore.getHighestAgedIndex(secret, jobId);
171
+ const start = highestAgedIndex === undefined ? 0 : highestAgedIndex + 1;
172
+ return { kind: AppTaggingSecretKind.UNCONSTRAINED, secret, start, end: boundEnd };
153
173
  }),
154
174
  );
155
175
  }
@@ -203,41 +223,58 @@ async function fetchLogsForSecrets(
203
223
  }
204
224
 
205
225
  /**
206
- * Processes fetched logs for a constrained secret. Constrained delivery guarantees contiguous index sequences,
207
- * so we scan from the start of the queried range and stop at the first missing index. Only `highestFinalizedIndex`
226
+ * Processes fetched logs for a constrained secret: persists the finalized index and advances the probe. See the
227
+ * "# Constrained secrets" section above for why the scan stops at the first missing index. Only `highestFinalizedIndex`
208
228
  * is tracked (no aged index).
209
229
  */
210
230
  async function processConstrainedResults(
211
- pending: PendingSecret,
231
+ pending: PendingConstrained,
212
232
  logsWithIndexes: LogWithIndex[],
213
233
  taggingStore: RecipientTaggingStore,
214
234
  currentTimestamp: bigint,
215
235
  finalizedBlockNumber: BlockNumber,
216
236
  jobId: string,
217
237
  ): Promise<PendingSecret | undefined> {
218
- // Find where the contiguous run of indexes ends. Constrained delivery guarantees there are no logs after the
219
- // first gap, so all logs in the batch are within this prefix.
238
+ // Find where the contiguous run of indexes ends; all logs in the batch fall within this prefix.
220
239
  const indexesWithLogs = new Set(logsWithIndexes.map(l => l.taggingIndex));
221
240
  let firstMissingIndex = pending.start;
222
241
  while (firstMissingIndex < pending.end && indexesWithLogs.has(firstMissingIndex)) {
223
242
  firstMissingIndex++;
224
243
  }
225
244
 
226
- // Finalize any logs before the gap. The nullifier for a constrained-delivery log is emitted in the same
227
- // transaction, guaranteeing the log cannot disappear once included, so we persist the highest finalized
228
- // index even when a gap stops the scan. This lets the next sync round skip already-finalized indexes.
245
+ // Persist the highest finalized index found. The nullifier for a constrained-delivery log is emitted in the same
246
+ // transaction, guaranteeing the log cannot disappear once included, so we persist even when a gap stops the scan.
247
+ // This lets the next sync round skip already-finalized indexes.
229
248
  const { highestFinalizedIndex } = findHighestIndexes(logsWithIndexes, currentTimestamp, finalizedBlockNumber);
230
-
231
249
  if (highestFinalizedIndex !== undefined) {
232
250
  await taggingStore.updateHighestFinalizedIndex(pending.secret, highestFinalizedIndex, jobId);
251
+ }
233
252
 
234
- if (firstMissingIndex >= pending.end) {
235
- return {
236
- secret: pending.secret,
237
- start: pending.end,
238
- end: highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1,
239
- };
240
- }
253
+ // Advancing the probe is decoupled from persisting the finalized index: keep scanning as long as the probe was
254
+ // entirely hits (no gap) and there is room before the protocol bound, even if none of those hits is finalized yet.
255
+ // Gating this on finalization would drop logs in unfinalized blocks, which sit at the top of the contiguous run.
256
+ // The bound re-anchors to any finalized index found this round.
257
+ const probeFullyConsumed = firstMissingIndex >= pending.end;
258
+ const boundEnd =
259
+ highestFinalizedIndex !== undefined
260
+ ? Math.max(pending.boundEnd, highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1)
261
+ : pending.boundEnd;
262
+
263
+ // Double the probe each round, capped at the window (see INITIAL_CONSTRAINED_PROBE_LEN in ../constants.ts).
264
+ // The queried range is already bounded by boundEnd (a probe can never reach
265
+ // more than WINDOW_LEN past the finalized frontier); this cap just keeps the stored length from growing unbounded
266
+ // once the probe saturates the window.
267
+ const nextProbeLen = Math.min(pending.probeLen * 2, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN);
268
+
269
+ if (probeFullyConsumed && pending.end < boundEnd) {
270
+ return {
271
+ kind: AppTaggingSecretKind.CONSTRAINED,
272
+ secret: pending.secret,
273
+ start: pending.end,
274
+ end: Math.min(boundEnd, pending.end + nextProbeLen),
275
+ boundEnd,
276
+ probeLen: nextProbeLen,
277
+ };
241
278
  }
242
279
 
243
280
  return undefined;
@@ -248,7 +285,7 @@ async function processConstrainedResults(
248
285
  * if the window needs to advance, or undefined if this secret is done.
249
286
  */
250
287
  async function processUnconstrainedResults(
251
- pending: PendingSecret,
288
+ pending: PendingUnconstrained,
252
289
  logsWithIndexes: LogWithIndex[],
253
290
  taggingStore: RecipientTaggingStore,
254
291
  currentTimestamp: bigint,
@@ -282,19 +319,39 @@ async function processUnconstrainedResults(
282
319
 
283
320
  // For the next iteration we want to look only at indexes for which we have not yet fetched logs while
284
321
  // ensuring that we do not look further than WINDOW_LEN ahead of the highest finalized index.
322
+ const end = highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
285
323
  return {
324
+ kind: AppTaggingSecretKind.UNCONSTRAINED,
286
325
  secret: pending.secret,
287
326
  start: pending.end,
288
- end: highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1,
327
+ end,
289
328
  };
290
329
  }
291
330
 
292
- type PendingSecret = {
331
+ /** Working scan state common to both secret kinds: the half-open [start, end) tag-index range to query this round. */
332
+ type PendingSecretBase = {
293
333
  secret: AppTaggingSecret;
294
334
  start: number;
295
335
  end: number;
296
336
  };
297
337
 
338
+ /** Constrained scan state, carrying the doubling-probe bookkeeping that only the gapless constrained scan uses. */
339
+ type PendingConstrained = PendingSecretBase & {
340
+ kind: typeof AppTaggingSecretKind.CONSTRAINED;
341
+ // Exclusive upper bound on the indexes this secret may probe this sync (highest finalized index + WINDOW_LEN + 1, the
342
+ // protocol bound on how far ahead of finalized a log can sit). `end` grows toward it each round.
343
+ boundEnd: number;
344
+ // Intended probe length for the round that produced `end`. The queried span can be smaller when boundEnd caps it.
345
+ probeLen: number;
346
+ };
347
+
348
+ /** Unconstrained scan state: a plain window with no probe bookkeeping, since the scan always covers the full window. */
349
+ type PendingUnconstrained = PendingSecretBase & {
350
+ kind: typeof AppTaggingSecretKind.UNCONSTRAINED;
351
+ };
352
+
353
+ type PendingSecret = PendingConstrained | PendingUnconstrained;
354
+
298
355
  type LogWithIndex = {
299
356
  log: LogResult;
300
357
  taggingIndex: number;