@aztec/txe 0.71.0 → 0.72.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/txe",
3
- "version": "0.71.0",
3
+ "version": "0.72.1",
4
4
  "type": "module",
5
5
  "exports": "./dest/index.js",
6
6
  "bin": "./dest/bin/index.js",
@@ -59,19 +59,19 @@
59
59
  ]
60
60
  },
61
61
  "dependencies": {
62
- "@aztec/accounts": "0.71.0",
63
- "@aztec/aztec.js": "0.71.0",
64
- "@aztec/circuit-types": "0.71.0",
65
- "@aztec/circuits.js": "0.71.0",
66
- "@aztec/foundation": "0.71.0",
67
- "@aztec/key-store": "0.71.0",
68
- "@aztec/kv-store": "0.71.0",
69
- "@aztec/protocol-contracts": "0.71.0",
70
- "@aztec/pxe": "0.71.0",
71
- "@aztec/simulator": "0.71.0",
72
- "@aztec/telemetry-client": "0.71.0",
73
- "@aztec/types": "0.71.0",
74
- "@aztec/world-state": "0.71.0",
62
+ "@aztec/accounts": "0.72.1",
63
+ "@aztec/aztec.js": "0.72.1",
64
+ "@aztec/circuit-types": "0.72.1",
65
+ "@aztec/circuits.js": "0.72.1",
66
+ "@aztec/foundation": "0.72.1",
67
+ "@aztec/key-store": "0.72.1",
68
+ "@aztec/kv-store": "0.72.1",
69
+ "@aztec/protocol-contracts": "0.72.1",
70
+ "@aztec/pxe": "0.72.1",
71
+ "@aztec/simulator": "0.72.1",
72
+ "@aztec/telemetry-client": "0.72.1",
73
+ "@aztec/types": "0.72.1",
74
+ "@aztec/world-state": "0.72.1",
75
75
  "zod": "^3.23.8"
76
76
  },
77
77
  "devDependencies": {
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { loadContractArtifact } from '@aztec/aztec.js';
2
- import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server';
3
2
  import { type Logger } from '@aztec/foundation/log';
4
3
  import { type ApiSchemaFor, type ZodFor } from '@aztec/foundation/schemas';
4
+ import { createTracedJsonRpcServer } from '@aztec/telemetry-client';
5
5
 
6
6
  import { readFile, readdir } from 'fs/promises';
7
7
  import { join } from 'path';
@@ -120,5 +120,7 @@ const TXEDispatcherApiSchema: ApiSchemaFor<TXEDispatcher> = {
120
120
  * @returns A TXE RPC server.
121
121
  */
122
122
  export function createTXERpcServer(logger: Logger) {
123
- return createSafeJsonRpcServer(new TXEDispatcher(logger), TXEDispatcherApiSchema, { http200OnError: true });
123
+ return createTracedJsonRpcServer(new TXEDispatcher(logger), TXEDispatcherApiSchema, {
124
+ http200OnError: true,
125
+ });
124
126
  }
@@ -2,7 +2,8 @@ import { createLogger } from '@aztec/aztec.js';
2
2
  import {
3
3
  type AztecNode,
4
4
  type EpochProofQuote,
5
- type GetUnencryptedLogsResponse,
5
+ type GetContractClassLogsResponse,
6
+ type GetPublicLogsResponse,
6
7
  type InBlock,
7
8
  type L2Block,
8
9
  L2BlockHash,
@@ -22,7 +23,6 @@ import {
22
23
  TxReceipt,
23
24
  TxScopedL2Log,
24
25
  type TxValidationResult,
25
- type UnencryptedL2Log,
26
26
  } from '@aztec/circuit-types';
27
27
  import {
28
28
  type ARCHIVE_HEIGHT,
@@ -36,8 +36,10 @@ import {
36
36
  type NULLIFIER_TREE_HEIGHT,
37
37
  type NodeInfo,
38
38
  type PUBLIC_DATA_TREE_HEIGHT,
39
+ PUBLIC_LOG_DATA_SIZE_IN_FIELDS,
39
40
  type PrivateLog,
40
41
  type ProtocolContractAddresses,
42
+ type PublicLog,
41
43
  } from '@aztec/circuits.js';
42
44
  import { type L1ContractAddresses } from '@aztec/ethereum';
43
45
  import { poseidon2Hash } from '@aztec/foundation/crypto';
@@ -192,45 +194,48 @@ export class TXENode implements AztecNode {
192
194
  /**
193
195
  * Adds public logs to the txe node, given a block
194
196
  * @param blockNumber - The block number at which to add the public logs.
195
- * @param privateLogs - The unencrypted logs to be added.
196
- */
197
- addPublicLogsByTags(blockNumber: number, unencryptedLogs: UnencryptedL2Log[]) {
198
- unencryptedLogs.forEach(log => {
199
- if (log.data.length < 32 * 33) {
200
- // TODO remove when #9835 and #9836 are fixed
201
- this.#logger.warn(`Skipping unencrypted log with insufficient data length: ${log.data.length}`);
197
+ * @param publicLogs - The public logs to be added.
198
+ */
199
+ addPublicLogsByTags(blockNumber: number, publicLogs: PublicLog[]) {
200
+ publicLogs.forEach(log => {
201
+ // Check that each log stores 3 lengths in its first field. If not, it's not a tagged log:
202
+ const firstFieldBuf = log.log[0].toBuffer();
203
+ if (
204
+ !firstFieldBuf.subarray(0, 24).equals(Buffer.alloc(24)) ||
205
+ firstFieldBuf[26] !== 0 ||
206
+ firstFieldBuf[29] !== 0
207
+ ) {
208
+ // See parseLogFromPublic - the first field of a tagged log is 8 bytes structured:
209
+ // [ publicLen[0], publicLen[1], 0, privateLen[0], privateLen[1], 0, ciphertextLen[0], ciphertextLen[1]]
210
+ this.#logger.warn(`Skipping public log with invalid first field: ${log.log[0]}`);
202
211
  return;
203
212
  }
204
- try {
205
- // TODO remove when #9835 and #9836 are fixed. The partial note logs are emitted as bytes, but encoded as Fields.
206
- // This means that for every 32 bytes of payload, we only have 1 byte of data.
207
- // Also, the tag is not stored in the first 32 bytes of the log, (that's the length of public fields now) but in the next 32.
208
- const correctedBuffer = Buffer.alloc(32);
209
- const initialOffset = 32;
210
- for (let i = 0; i < 32; i++) {
211
- const byte = Fr.fromBuffer(log.data.subarray(i * 32 + initialOffset, i * 32 + 32 + initialOffset)).toNumber();
212
- correctedBuffer.writeUInt8(byte, i);
213
- }
214
- const tag = new Fr(correctedBuffer);
215
-
216
- this.#logger.verbose(
217
- `Found tagged unencrypted log with tag ${tag.toString()} in block ${this.getBlockNumber()}`,
218
- );
219
-
220
- const currentLogs = this.#logsByTags.get(tag.toString()) ?? [];
221
- const scopedLog = new TxScopedL2Log(
222
- new TxHash(new Fr(blockNumber)),
223
- this.#noteIndex,
224
- blockNumber,
225
- true,
226
- log.toBuffer(),
227
- );
228
-
229
- currentLogs.push(scopedLog);
230
- this.#logsByTags.set(tag.toString(), currentLogs);
231
- } catch (err) {
232
- this.#logger.warn(`Failed to add tagged log to store: ${err}`);
213
+ // Check that the length values line up with the log contents
214
+ const publicValuesLength = firstFieldBuf.subarray(-8).readUint16BE();
215
+ const privateValuesLength = firstFieldBuf.subarray(-8).readUint16BE(3);
216
+ // Add 1 for the first field holding lengths
217
+ const totalLogLength = 1 + publicValuesLength + privateValuesLength;
218
+ // Note that zeroes can be valid log values, so we can only assert that we do not go over the given length
219
+ if (totalLogLength > PUBLIC_LOG_DATA_SIZE_IN_FIELDS || log.log.slice(totalLogLength).find(f => !f.isZero())) {
220
+ this.#logger.warn(`Skipping invalid tagged public log with first field: ${log.log[0]}`);
221
+ return;
233
222
  }
223
+ // The first elt stores lengths => tag is in fields[1]
224
+ const tag = log.log[1];
225
+
226
+ this.#logger.verbose(`Found tagged public log with tag ${tag.toString()} in block ${this.getBlockNumber()}`);
227
+
228
+ const currentLogs = this.#logsByTags.get(tag.toString()) ?? [];
229
+ const scopedLog = new TxScopedL2Log(
230
+ new TxHash(new Fr(blockNumber)),
231
+ this.#noteIndex,
232
+ blockNumber,
233
+ true,
234
+ log.toBuffer(),
235
+ );
236
+
237
+ currentLogs.push(scopedLog);
238
+ this.#logsByTags.set(tag.toString(), currentLogs);
234
239
  });
235
240
  }
236
241
  /**
@@ -268,7 +273,8 @@ export class TXENode implements AztecNode {
268
273
  // hold a reference to them.
269
274
  // We should likely migrate this so that the trees are owned by the node.
270
275
 
271
- if (blockNumber == 'latest') {
276
+ // TODO: blockNumber is being passed as undefined, figure out why
277
+ if (blockNumber === 'latest' || blockNumber === undefined) {
272
278
  blockNumber = await this.getBlockNumber();
273
279
  }
274
280
 
@@ -497,12 +503,12 @@ export class TXENode implements AztecNode {
497
503
  }
498
504
 
499
505
  /**
500
- * Gets unencrypted logs based on the provided filter.
506
+ * Gets public logs based on the provided filter.
501
507
  * @param filter - The filter to apply to the logs.
502
508
  * @returns The requested logs.
503
509
  */
504
- getUnencryptedLogs(_filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
505
- throw new Error('TXE Node method getUnencryptedLogs not implemented');
510
+ getPublicLogs(_filter: LogFilter): Promise<GetPublicLogsResponse> {
511
+ throw new Error('TXE Node method getPublicLogs not implemented');
506
512
  }
507
513
 
508
514
  /**
@@ -510,7 +516,7 @@ export class TXENode implements AztecNode {
510
516
  * @param filter - The filter to apply to the logs.
511
517
  * @returns The requested logs.
512
518
  */
513
- getContractClassLogs(_filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
519
+ getContractClassLogs(_filter: LogFilter): Promise<GetContractClassLogsResponse> {
514
520
  throw new Error('TXE Node method getContractClassLogs not implemented');
515
521
  }
516
522
 
@@ -35,6 +35,7 @@ import {
35
35
  PublicDataTreeLeaf,
36
36
  type PublicDataTreeLeafPreimage,
37
37
  type PublicDataWrite,
38
+ type PublicLog,
38
39
  computeContractClassId,
39
40
  computeTaggingSecretPoint,
40
41
  deriveKeys,
@@ -94,7 +95,6 @@ import { TXEWorldStateDB } from '../util/txe_world_state_db.js';
94
95
  export class TXE implements TypedOracle {
95
96
  private blockNumber = 0;
96
97
  private sideEffectCounter = 0;
97
- private contractAddress: AztecAddress;
98
98
  private msgSender: AztecAddress;
99
99
  private functionSelector = FunctionSelector.fromField(new Fr(0));
100
100
  private isStaticCall = false;
@@ -107,7 +107,7 @@ export class TXE implements TypedOracle {
107
107
  private uniqueNoteHashesFromPublic: Fr[] = [];
108
108
  private siloedNullifiersFromPublic: Fr[] = [];
109
109
  private privateLogs: PrivateLog[] = [];
110
- private publicLogs: UnencryptedL2Log[] = [];
110
+ private publicLogs: PublicLog[] = [];
111
111
 
112
112
  private committedBlocks = new Set<number>();
113
113
 
@@ -122,16 +122,16 @@ export class TXE implements TypedOracle {
122
122
 
123
123
  debug: LogFn;
124
124
 
125
- constructor(
125
+ private constructor(
126
126
  private logger: Logger,
127
127
  private trees: MerkleTrees,
128
128
  private executionCache: HashedValuesCache,
129
129
  private keyStore: KeyStore,
130
130
  private txeDatabase: TXEDatabase,
131
+ private contractAddress: AztecAddress,
131
132
  ) {
132
133
  this.noteCache = new ExecutionNoteCache(this.getTxRequestHash());
133
134
  this.contractDataOracle = new ContractDataOracle(txeDatabase);
134
- this.contractAddress = AztecAddress.random();
135
135
 
136
136
  this.node = new TXENode(this.blockNumber, this.VERSION, this.CHAIN_ID, this.trees);
137
137
 
@@ -148,6 +148,16 @@ export class TXE implements TypedOracle {
148
148
  this.debug = createDebugOnlyLogger('aztec:kv-pxe-database');
149
149
  }
150
150
 
151
+ static async create(
152
+ logger: Logger,
153
+ trees: MerkleTrees,
154
+ executionCache: HashedValuesCache,
155
+ keyStore: KeyStore,
156
+ txeDatabase: TXEDatabase,
157
+ ) {
158
+ return new TXE(logger, trees, executionCache, keyStore, txeDatabase, await AztecAddress.random());
159
+ }
160
+
151
161
  // Utils
152
162
 
153
163
  async #getTreesAt(blockNumber: number) {
@@ -254,8 +264,8 @@ export class TXE implements TypedOracle {
254
264
  const account = await this.txeDatabase.getAccount(address);
255
265
  const privateKey = await this.keyStore.getMasterSecretKey(account.publicKeys.masterIncomingViewingPublicKey);
256
266
  const schnorr = new Schnorr();
257
- const signature = schnorr.constructSignature(messageHash.toBuffer(), privateKey).toBuffer();
258
- const authWitness = new AuthWitness(messageHash, [...signature]);
267
+ const signature = await schnorr.constructSignature(messageHash.toBuffer(), privateKey);
268
+ const authWitness = new AuthWitness(messageHash, [...signature.toBuffer()]);
259
269
  return this.txeDatabase.addAuthWitness(authWitness.requestHash, authWitness.witness);
260
270
  }
261
271
 
@@ -324,26 +334,13 @@ export class TXE implements TypedOracle {
324
334
  this.privateLogs.push(...privateLogs);
325
335
  }
326
336
 
327
- addPublicLogs(logs: UnencryptedL2Log[]) {
337
+ addPublicLogs(logs: PublicLog[]) {
328
338
  logs.forEach(log => {
329
- if (log.data.length < 32 * 33) {
330
- // TODO remove when #9835 and #9836 are fixed
331
- this.logger.warn(`Skipping unencrypted log with insufficient data length: ${log.data.length}`);
332
- return;
333
- }
334
339
  try {
335
- // TODO remove when #9835 and #9836 are fixed. The partial note logs are emitted as bytes, but encoded as Fields.
336
- // This means that for every 32 bytes of payload, we only have 1 byte of data.
337
- // Also, the tag is not stored in the first 32 bytes of the log, (that's the length of public fields now) but in the next 32.
338
- const correctedBuffer = Buffer.alloc(32);
339
- const initialOffset = 32;
340
- for (let i = 0; i < 32; i++) {
341
- const byte = Fr.fromBuffer(log.data.subarray(i * 32 + initialOffset, i * 32 + 32 + initialOffset)).toNumber();
342
- correctedBuffer.writeUInt8(byte, i);
343
- }
344
- const tag = new Fr(correctedBuffer);
345
-
346
- this.logger.verbose(`Found tagged unencrypted log with tag ${tag.toString()} in block ${this.blockNumber}`);
340
+ // The first elt stores lengths => tag is in fields[1]
341
+ const tag = log.log[1];
342
+
343
+ this.logger.verbose(`Found tagged public log with tag ${tag.toString()} in block ${this.blockNumber}`);
347
344
  this.publicLogs.push(log);
348
345
  } catch (err) {
349
346
  this.logger.warn(`Failed to add tagged log to store: ${err}`);
@@ -373,10 +370,6 @@ export class TXE implements TypedOracle {
373
370
  return Fr.random();
374
371
  }
375
372
 
376
- storeArrayInExecutionCache(values: Fr[]) {
377
- return Promise.resolve(this.executionCache.store(values));
378
- }
379
-
380
373
  storeInExecutionCache(values: Fr[]) {
381
374
  return Promise.resolve(this.executionCache.store(values));
382
375
  }
@@ -835,19 +828,30 @@ export class TXE implements TypedOracle {
835
828
  globalVariables,
836
829
  );
837
830
 
831
+ const { usedTxRequestHashForNonces } = this.noteCache.finish();
832
+ const firstNullifier = usedTxRequestHashForNonces ? this.getTxRequestHash() : this.noteCache.getAllNullifiers()[0];
833
+
838
834
  // When setting up a teardown call, we tell it that
839
835
  // private execution used Gas(1, 1) so it can compute a tx fee.
840
836
  const gasUsedByPrivate = isTeardown ? new Gas(1, 1) : Gas.empty();
841
837
  const tx = createTxForPublicCalls(
842
838
  /*setupExecutionRequests=*/ [],
843
839
  /*appExecutionRequests=*/ isTeardown ? [] : [executionRequest],
840
+ firstNullifier,
844
841
  /*teardownExecutionRequests=*/ isTeardown ? executionRequest : undefined,
845
842
  gasUsedByPrivate,
846
843
  );
847
844
 
848
845
  const result = await simulator.simulate(tx);
846
+ const noteHashes = result.avmProvingRequest.inputs.output.accumulatedData.noteHashes.filter(s => !s.isEmpty());
847
+
848
+ await this.addUniqueNoteHashesFromPublic(noteHashes);
849
849
 
850
- this.addPublicLogs(tx.unencryptedLogs.unrollLogs());
850
+ this.addPublicLogs(
851
+ result.avmProvingRequest.inputs.output.accumulatedData.publicLogs.filter(
852
+ log => !log.contractAddress.equals(AztecAddress.ZERO),
853
+ ),
854
+ );
851
855
 
852
856
  return Promise.resolve(result);
853
857
  }
@@ -899,7 +903,11 @@ export class TXE implements TypedOracle {
899
903
  const sideEffects = executionResult.avmProvingRequest.inputs.output.accumulatedData;
900
904
  const publicDataWrites = sideEffects.publicDataWrites.filter(s => !s.isEmpty());
901
905
  const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
902
- const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
906
+
907
+ const { usedTxRequestHashForNonces } = this.noteCache.finish();
908
+ const firstNullifier = usedTxRequestHashForNonces ? this.getTxRequestHash() : this.noteCache.getAllNullifiers()[0];
909
+ const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty()).filter(s => !s.equals(firstNullifier));
910
+
903
911
  await this.addPublicDataWrites(publicDataWrites);
904
912
  await this.addUniqueNoteHashesFromPublic(noteHashes);
905
913
  await this.addSiloedNullifiers(nullifiers);
@@ -953,7 +961,7 @@ export class TXE implements TypedOracle {
953
961
  async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
954
962
  const senderCompleteAddress = await this.getCompleteAddress(sender);
955
963
  const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
956
- const secretPoint = computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
964
+ const secretPoint = await computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
957
965
  // Silo the secret to the app so it can't be used to track other app's notes
958
966
  const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]);
959
967
  return appSecret;
@@ -1013,7 +1021,11 @@ export class TXE implements TypedOracle {
1013
1021
  const sideEffects = executionResult.avmProvingRequest.inputs.output.accumulatedData;
1014
1022
  const publicDataWrites = sideEffects.publicDataWrites.filter(s => !s.isEmpty());
1015
1023
  const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
1016
- const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
1024
+ const { usedTxRequestHashForNonces } = this.noteCache.finish();
1025
+ const firstNullifier = usedTxRequestHashForNonces
1026
+ ? this.getTxRequestHash()
1027
+ : this.noteCache.getAllNullifiers()[0];
1028
+ const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty()).filter(s => !s.equals(firstNullifier));
1017
1029
  await this.addPublicDataWrites(publicDataWrites);
1018
1030
  await this.addUniqueNoteHashes(noteHashes);
1019
1031
  await this.addSiloedNullifiers(nullifiers);
@@ -48,12 +48,12 @@ export class TXEService {
48
48
  const txeDatabase = new TXEDatabase(store);
49
49
  // Register protocol contracts.
50
50
  for (const name of protocolContractNames) {
51
- const { contractClass, instance, artifact } = getCanonicalProtocolContract(name);
51
+ const { contractClass, instance, artifact } = await getCanonicalProtocolContract(name);
52
52
  await txeDatabase.addContractArtifact(contractClass.id, artifact);
53
53
  await txeDatabase.addContractInstance(instance);
54
54
  }
55
55
  logger.debug(`TXE service initialized`);
56
- const txe = new TXE(logger, trees, executionCache, keyStore, txeDatabase);
56
+ const txe = await TXE.create(logger, trees, executionCache, keyStore, txeDatabase);
57
57
  const service = new TXEService(logger, txe);
58
58
  await service.advanceBlocksBy(toSingle(new Fr(1n)));
59
59
  return service;
@@ -95,8 +95,8 @@ export class TXEService {
95
95
  return toForeignCallResult([]);
96
96
  }
97
97
 
98
- deriveKeys(secret: ForeignCallSingle) {
99
- const keys = (this.typedOracle as TXE).deriveKeys(fromSingle(secret));
98
+ async deriveKeys(secret: ForeignCallSingle) {
99
+ const keys = await (this.typedOracle as TXE).deriveKeys(fromSingle(secret));
100
100
  return toForeignCallResult(keys.publicKeys.toFields().map(toSingle));
101
101
  }
102
102
 
@@ -116,7 +116,7 @@ export class TXEService {
116
116
  `Deploy ${artifact.name} with initializer ${initializerStr}(${decodedArgs}) and public keys hash ${publicKeysHashFr}`,
117
117
  );
118
118
 
119
- const instance = getContractInstanceFromDeployParams(artifact, {
119
+ const instance = await getContractInstanceFromDeployParams(artifact, {
120
120
  constructorArgs: decodedArgs,
121
121
  skipArgsDecoding: true,
122
122
  salt: Fr.ONE,
@@ -177,10 +177,10 @@ export class TXEService {
177
177
  }
178
178
 
179
179
  async addAccount(secret: ForeignCallSingle) {
180
- const keys = (this.typedOracle as TXE).deriveKeys(fromSingle(secret));
180
+ const keys = await (this.typedOracle as TXE).deriveKeys(fromSingle(secret));
181
181
  const args = [keys.publicKeys.masterIncomingViewingPublicKey.x, keys.publicKeys.masterIncomingViewingPublicKey.y];
182
182
  const artifact = SchnorrAccountContractArtifact;
183
- const instance = getContractInstanceFromDeployParams(artifact, {
183
+ const instance = await getContractInstanceFromDeployParams(artifact, {
184
184
  constructorArgs: args,
185
185
  skipArgsDecoding: true,
186
186
  salt: Fr.ONE,
@@ -271,11 +271,6 @@ export class TXEService {
271
271
  return toForeignCallResult([toSingle(new Fr(blockNumber))]);
272
272
  }
273
273
 
274
- async storeArrayInExecutionCache(args: ForeignCallArray) {
275
- const hash = await this.typedOracle.storeArrayInExecutionCache(fromArray(args));
276
- return toForeignCallResult([toSingle(hash)]);
277
- }
278
-
279
274
  // Since the argument is a slice, noir automatically adds a length field to oracle call.
280
275
  async storeInExecutionCache(_length: ForeignCallSingle, values: ForeignCallArray) {
281
276
  const returnsHash = await this.typedOracle.storeInExecutionCache(fromArray(values));