@aztec/pxe 0.76.4 → 0.77.0-testnet-ignition.21
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/dest/bin/index.js +4 -6
- package/dest/config/index.d.ts +2 -2
- package/dest/config/index.d.ts.map +1 -1
- package/dest/config/index.js +20 -23
- package/dest/config/package_info.js +4 -2
- package/dest/contract_data_oracle/index.d.ts +10 -10
- package/dest/contract_data_oracle/index.d.ts.map +1 -1
- package/dest/contract_data_oracle/index.js +74 -89
- package/dest/contract_data_oracle/private_functions_tree.d.ts +5 -4
- package/dest/contract_data_oracle/private_functions_tree.d.ts.map +1 -1
- package/dest/contract_data_oracle/private_functions_tree.js +47 -51
- package/dest/database/contracts/contract_artifact_db.d.ts +2 -2
- package/dest/database/contracts/contract_artifact_db.d.ts.map +1 -1
- package/dest/database/contracts/contract_artifact_db.js +3 -2
- package/dest/database/contracts/contract_instance_db.d.ts +2 -1
- package/dest/database/contracts/contract_instance_db.d.ts.map +1 -1
- package/dest/database/contracts/contract_instance_db.js +3 -2
- package/dest/database/index.js +0 -1
- package/dest/database/kv_pxe_database.d.ts +9 -5
- package/dest/database/kv_pxe_database.d.ts.map +1 -1
- package/dest/database/kv_pxe_database.js +244 -257
- package/dest/database/note_dao.d.ts +13 -10
- package/dest/database/note_dao.d.ts.map +1 -1
- package/dest/database/note_dao.js +35 -48
- package/dest/database/outgoing_note_dao.d.ts +6 -3
- package/dest/database/outgoing_note_dao.d.ts.map +1 -1
- package/dest/database/outgoing_note_dao.js +25 -37
- package/dest/database/pxe_database.d.ts +12 -8
- package/dest/database/pxe_database.d.ts.map +1 -1
- package/dest/database/pxe_database.js +4 -2
- package/dest/database/pxe_database_test_suite.d.ts +1 -1
- package/dest/database/pxe_database_test_suite.d.ts.map +1 -1
- package/dest/database/pxe_database_test_suite.js +286 -147
- package/dest/index.d.ts +1 -6
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -7
- package/dest/kernel_oracle/index.d.ts +20 -10
- package/dest/kernel_oracle/index.d.ts.map +1 -1
- package/dest/kernel_oracle/index.js +32 -9
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.d.ts +3 -3
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.d.ts.map +1 -1
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.js +71 -67
- package/dest/kernel_prover/hints/index.js +0 -1
- package/dest/kernel_prover/index.js +0 -1
- package/dest/kernel_prover/kernel_prover.d.ts +6 -6
- package/dest/kernel_prover/kernel_prover.d.ts.map +1 -1
- package/dest/kernel_prover/kernel_prover.js +98 -81
- package/dest/kernel_prover/proving_data_oracle.d.ts +15 -7
- package/dest/kernel_prover/proving_data_oracle.d.ts.map +1 -1
- package/dest/kernel_prover/proving_data_oracle.js +4 -2
- package/dest/note_decryption_utils/add_public_values_to_payload.d.ts +3 -2
- package/dest/note_decryption_utils/add_public_values_to_payload.d.ts.map +1 -1
- package/dest/note_decryption_utils/add_public_values_to_payload.js +11 -12
- package/dest/pxe_http/index.js +0 -1
- package/dest/pxe_http/pxe_http_server.d.ts +1 -1
- package/dest/pxe_http/pxe_http_server.d.ts.map +1 -1
- package/dest/pxe_http/pxe_http_server.js +9 -7
- package/dest/pxe_service/error_enriching.d.ts +3 -3
- package/dest/pxe_service/error_enriching.d.ts.map +1 -1
- package/dest/pxe_service/error_enriching.js +14 -17
- package/dest/pxe_service/index.d.ts +0 -1
- package/dest/pxe_service/index.d.ts.map +1 -1
- package/dest/pxe_service/index.js +0 -2
- package/dest/pxe_service/pxe_service.d.ts +21 -12
- package/dest/pxe_service/pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/pxe_service.js +279 -333
- package/dest/pxe_service/test/pxe_test_suite.d.ts +1 -1
- package/dest/pxe_service/test/pxe_test_suite.d.ts.map +1 -1
- package/dest/pxe_service/test/pxe_test_suite.js +44 -28
- package/dest/simulator/index.d.ts +3 -3
- package/dest/simulator/index.d.ts.map +1 -1
- package/dest/simulator/index.js +1 -3
- package/dest/simulator_oracle/index.d.ts +18 -6
- package/dest/simulator_oracle/index.d.ts.map +1 -1
- package/dest/simulator_oracle/index.js +307 -235
- package/dest/simulator_oracle/tagging_utils.d.ts +2 -1
- package/dest/simulator_oracle/tagging_utils.d.ts.map +1 -1
- package/dest/simulator_oracle/tagging_utils.js +5 -7
- package/dest/synchronizer/index.js +0 -1
- package/dest/synchronizer/synchronizer.d.ts +7 -4
- package/dest/synchronizer/synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/synchronizer.js +64 -43
- package/dest/utils/create_pxe_service.d.ts +2 -2
- package/dest/utils/create_pxe_service.d.ts.map +1 -1
- package/dest/utils/create_pxe_service.js +13 -11
- package/package.json +18 -19
- package/src/bin/index.ts +1 -1
- package/src/config/index.ts +3 -3
- package/src/contract_data_oracle/index.ts +20 -20
- package/src/contract_data_oracle/private_functions_tree.ts +6 -7
- package/src/database/contracts/contract_artifact_db.ts +2 -2
- package/src/database/contracts/contract_instance_db.ts +2 -1
- package/src/database/kv_pxe_database.ts +33 -32
- package/src/database/note_dao.ts +11 -8
- package/src/database/outgoing_note_dao.ts +7 -4
- package/src/database/pxe_database.ts +13 -15
- package/src/database/pxe_database_test_suite.ts +8 -11
- package/src/index.ts +1 -7
- package/src/kernel_oracle/index.ts +55 -22
- package/src/kernel_prover/hints/build_private_kernel_reset_private_inputs.ts +14 -11
- package/src/kernel_prover/kernel_prover.ts +89 -69
- package/src/kernel_prover/proving_data_oracle.ts +20 -20
- package/src/note_decryption_utils/add_public_values_to_payload.ts +5 -4
- package/src/pxe_http/pxe_http_server.ts +1 -1
- package/src/pxe_service/error_enriching.ts +6 -6
- package/src/pxe_service/index.ts +0 -1
- package/src/pxe_service/pxe_service.ts +121 -224
- package/src/pxe_service/test/pxe_test_suite.ts +6 -3
- package/src/simulator/index.ts +3 -3
- package/src/simulator_oracle/index.ts +77 -47
- package/src/simulator_oracle/tagging_utils.ts +2 -1
- package/src/synchronizer/synchronizer.ts +31 -12
- package/src/utils/create_pxe_service.ts +16 -4
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
import { __classPrivateFieldGet } from "tslib";
|
|
3
|
-
import { L1NotePayload, MerkleTreeId, Note, TxHash, getNonNullifiedL1ToL2MessageWitness, } from '@aztec/circuit-types';
|
|
4
|
-
import { Fr, FunctionSelector, IndexedTaggingSecret, MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS, PrivateLog, PublicLog, computeAddressSecret, computeTaggingSecretPoint, } from '@aztec/circuits.js';
|
|
5
|
-
import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash';
|
|
6
|
-
import { FunctionType, NoteSelector, encodeArguments, getFunctionArtifact, } from '@aztec/foundation/abi';
|
|
1
|
+
import { MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS, PUBLIC_LOG_DATA_SIZE_IN_FIELDS } from '@aztec/constants';
|
|
7
2
|
import { timesParallel } from '@aztec/foundation/collection';
|
|
8
3
|
import { poseidon2Hash } from '@aztec/foundation/crypto';
|
|
4
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
9
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
10
|
-
import {
|
|
6
|
+
import { BufferReader } from '@aztec/foundation/serialize';
|
|
7
|
+
import { MessageLoadOracleInputs } from '@aztec/simulator/client';
|
|
8
|
+
import { FunctionSelector, FunctionType, NoteSelector, encodeArguments, getFunctionArtifact } from '@aztec/stdlib/abi';
|
|
9
|
+
import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash';
|
|
10
|
+
import { computeAddressSecret, computeTaggingSecretPoint } from '@aztec/stdlib/keys';
|
|
11
|
+
import { IndexedTaggingSecret, L1NotePayload, LogWithTxData, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
|
|
12
|
+
import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging';
|
|
13
|
+
import { Note } from '@aztec/stdlib/note';
|
|
14
|
+
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
15
|
+
import { TxHash } from '@aztec/stdlib/tx';
|
|
11
16
|
import { ContractDataOracle } from '../contract_data_oracle/index.js';
|
|
12
17
|
import { NoteDao } from '../database/note_dao.js';
|
|
13
18
|
import { getOrderedNoteItems } from '../note_decryption_utils/add_public_values_to_payload.js';
|
|
@@ -15,10 +20,14 @@ import { getAcirSimulator } from '../simulator/index.js';
|
|
|
15
20
|
import { WINDOW_HALF_SIZE, getIndexedTaggingSecretsForTheWindow, getInitialIndexesMap } from './tagging_utils.js';
|
|
16
21
|
/**
|
|
17
22
|
* A data oracle that provides information needed for simulating a transaction.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
*/ export class SimulatorOracle {
|
|
24
|
+
contractDataOracle;
|
|
25
|
+
db;
|
|
26
|
+
keyStore;
|
|
27
|
+
aztecNode;
|
|
28
|
+
simulationProvider;
|
|
29
|
+
log;
|
|
30
|
+
constructor(contractDataOracle, db, keyStore, aztecNode, simulationProvider, log = createLogger('pxe:simulator_oracle')){
|
|
22
31
|
this.contractDataOracle = contractDataOracle;
|
|
23
32
|
this.db = db;
|
|
24
33
|
this.keyStore = keyStore;
|
|
@@ -33,7 +42,7 @@ export class SimulatorOracle {
|
|
|
33
42
|
const completeAddress = await this.db.getCompleteAddress(account);
|
|
34
43
|
if (!completeAddress) {
|
|
35
44
|
throw new Error(`No public key registered for address ${account}.
|
|
36
|
-
Register it by calling pxe.registerAccount(...).\nSee docs for context: https://docs.aztec.network/reference/
|
|
45
|
+
Register it by calling pxe.registerAccount(...).\nSee docs for context: https://docs.aztec.network/developers/reference/debugging/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount`);
|
|
37
46
|
}
|
|
38
47
|
return completeAddress;
|
|
39
48
|
}
|
|
@@ -56,41 +65,40 @@ export class SimulatorOracle {
|
|
|
56
65
|
contractAddress,
|
|
57
66
|
storageSlot,
|
|
58
67
|
status,
|
|
59
|
-
scopes
|
|
68
|
+
scopes
|
|
60
69
|
});
|
|
61
|
-
return noteDaos.map(({ contractAddress, storageSlot, nonce, note, noteHash, siloedNullifier, index })
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
return noteDaos.map(({ contractAddress, storageSlot, nonce, note, noteHash, siloedNullifier, index })=>({
|
|
71
|
+
contractAddress,
|
|
72
|
+
storageSlot,
|
|
73
|
+
nonce,
|
|
74
|
+
note,
|
|
75
|
+
noteHash,
|
|
76
|
+
siloedNullifier,
|
|
77
|
+
// PXE can use this index to get full MembershipWitness
|
|
78
|
+
index
|
|
79
|
+
}));
|
|
71
80
|
}
|
|
72
81
|
async getFunctionArtifact(contractAddress, selector) {
|
|
73
82
|
const artifact = await this.contractDataOracle.getFunctionArtifact(contractAddress, selector);
|
|
74
83
|
const debug = await this.contractDataOracle.getFunctionDebugMetadata(contractAddress, selector);
|
|
75
84
|
return {
|
|
76
85
|
...artifact,
|
|
77
|
-
debug
|
|
86
|
+
debug
|
|
78
87
|
};
|
|
79
88
|
}
|
|
80
89
|
async getFunctionArtifactByName(contractAddress, functionName) {
|
|
81
90
|
const instance = await this.contractDataOracle.getContractInstance(contractAddress);
|
|
82
|
-
const artifact = await this.contractDataOracle.getContractArtifact(instance.
|
|
91
|
+
const artifact = await this.contractDataOracle.getContractArtifact(instance.currentContractClassId);
|
|
83
92
|
return artifact && getFunctionArtifact(artifact, functionName);
|
|
84
93
|
}
|
|
85
94
|
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
async getL1ToL2MembershipWitness(contractAddress, messageHash, secret) {
|
|
95
|
+
* Fetches a message from the db, given its key.
|
|
96
|
+
* @param contractAddress - Address of a contract by which the message was emitted.
|
|
97
|
+
* @param messageHash - Hash of the message.
|
|
98
|
+
* @param secret - Secret used to compute a nullifier.
|
|
99
|
+
* @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages
|
|
100
|
+
* @returns The l1 to l2 membership witness (index of message in the tree and sibling path).
|
|
101
|
+
*/ async getL1ToL2MembershipWitness(contractAddress, messageHash, secret) {
|
|
94
102
|
const [messageIndex, siblingPath] = await getNonNullifiedL1ToL2MessageWitness(this.aztecNode, contractAddress, messageHash, secret);
|
|
95
103
|
// Assuming messageIndex is what you intended to use for the index in MessageLoadOracleInputs
|
|
96
104
|
return new MessageLoadOracleInputs(messageIndex, siblingPath);
|
|
@@ -100,27 +108,49 @@ export class SimulatorOracle {
|
|
|
100
108
|
throw new Error('Unimplemented in private!');
|
|
101
109
|
}
|
|
102
110
|
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return await __classPrivateFieldGet(this, _SimulatorOracle_instances, "m", _SimulatorOracle_findLeafIndex).call(this, 'latest', MerkleTreeId.NOTE_HASH_TREE, commitment);
|
|
111
|
+
* Gets the index of a commitment in the note hash tree.
|
|
112
|
+
* @param commitment - The commitment.
|
|
113
|
+
* @returns - The index of the commitment. Undefined if it does not exist in the tree.
|
|
114
|
+
*/ async getCommitmentIndex(commitment) {
|
|
115
|
+
return await this.#findLeafIndex('latest', MerkleTreeId.NOTE_HASH_TREE, commitment);
|
|
109
116
|
}
|
|
110
117
|
// We need this in public as part of the EXISTS calls - but isn't used in private
|
|
111
118
|
getCommitmentValue(_leafIndex) {
|
|
112
119
|
throw new Error('Unimplemented in private!');
|
|
113
120
|
}
|
|
114
121
|
async getNullifierIndex(nullifier) {
|
|
115
|
-
return await
|
|
122
|
+
return await this.#findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier);
|
|
123
|
+
}
|
|
124
|
+
async #findLeafIndex(blockNumber, treeId, leafValue) {
|
|
125
|
+
const [leafIndex] = await this.aztecNode.findLeavesIndexes(blockNumber, treeId, [
|
|
126
|
+
leafValue
|
|
127
|
+
]);
|
|
128
|
+
return leafIndex;
|
|
116
129
|
}
|
|
117
130
|
async getMembershipWitness(blockNumber, treeId, leafValue) {
|
|
118
|
-
const leafIndex = await
|
|
131
|
+
const leafIndex = await this.#findLeafIndex(blockNumber, treeId, leafValue);
|
|
119
132
|
if (!leafIndex) {
|
|
120
133
|
throw new Error(`Leaf value: ${leafValue} not found in ${MerkleTreeId[treeId]}`);
|
|
121
134
|
}
|
|
122
|
-
const siblingPath = await
|
|
123
|
-
return [
|
|
135
|
+
const siblingPath = await this.#getSiblingPath(blockNumber, treeId, leafIndex);
|
|
136
|
+
return [
|
|
137
|
+
new Fr(leafIndex),
|
|
138
|
+
...siblingPath
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
async #getSiblingPath(blockNumber, treeId, leafIndex) {
|
|
142
|
+
switch(treeId){
|
|
143
|
+
case MerkleTreeId.NULLIFIER_TREE:
|
|
144
|
+
return (await this.aztecNode.getNullifierSiblingPath(blockNumber, leafIndex)).toFields();
|
|
145
|
+
case MerkleTreeId.NOTE_HASH_TREE:
|
|
146
|
+
return (await this.aztecNode.getNoteHashSiblingPath(blockNumber, leafIndex)).toFields();
|
|
147
|
+
case MerkleTreeId.PUBLIC_DATA_TREE:
|
|
148
|
+
return (await this.aztecNode.getPublicDataSiblingPath(blockNumber, leafIndex)).toFields();
|
|
149
|
+
case MerkleTreeId.ARCHIVE:
|
|
150
|
+
return (await this.aztecNode.getArchiveSiblingPath(blockNumber, leafIndex)).toFields();
|
|
151
|
+
default:
|
|
152
|
+
throw new Error('Not implemented');
|
|
153
|
+
}
|
|
124
154
|
}
|
|
125
155
|
async getNullifierMembershipWitnessAtLatestBlock(nullifier) {
|
|
126
156
|
return this.getNullifierMembershipWitness(await this.getBlockNumber(), nullifier);
|
|
@@ -138,76 +168,118 @@ export class SimulatorOracle {
|
|
|
138
168
|
return await this.aztecNode.getPublicDataTreeWitness(blockNumber, leafSlot);
|
|
139
169
|
}
|
|
140
170
|
/**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
getBlockHeader() {
|
|
171
|
+
* Retrieve the databases view of the Block Header object.
|
|
172
|
+
* This structure is fed into the circuits simulator and is used to prove against certain historical roots.
|
|
173
|
+
*
|
|
174
|
+
* @returns A Promise that resolves to a BlockHeader object.
|
|
175
|
+
*/ getBlockHeader() {
|
|
147
176
|
return this.db.getBlockHeader();
|
|
148
177
|
}
|
|
149
178
|
/**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
async getBlockNumber() {
|
|
179
|
+
* Fetches the current block number.
|
|
180
|
+
* @returns The block number.
|
|
181
|
+
*/ async getBlockNumber() {
|
|
154
182
|
return await this.aztecNode.getBlockNumber();
|
|
155
183
|
}
|
|
156
184
|
getDebugFunctionName(contractAddress, selector) {
|
|
157
185
|
return this.contractDataOracle.getDebugFunctionName(contractAddress, selector);
|
|
158
186
|
}
|
|
159
187
|
/**
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
getSenders() {
|
|
188
|
+
* Returns the full contents of your address book.
|
|
189
|
+
* This is used when calculating tags for incoming notes by deriving the shared secret, the contract-siloed tagging secret, and
|
|
190
|
+
* finally the index specified tag. We will then query the node with this tag for each address in the address book.
|
|
191
|
+
* @returns The full list of the users contact addresses.
|
|
192
|
+
*/ getSenders() {
|
|
166
193
|
return this.db.getSenderAddresses();
|
|
167
194
|
}
|
|
168
195
|
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
async getIndexedTaggingSecretAsSender(contractAddress, sender, recipient) {
|
|
196
|
+
* Returns the tagging secret for a given sender and recipient pair. For this to work, the ivsk_m of the sender must be known.
|
|
197
|
+
* Includes the next index to be used used for tagging with this secret.
|
|
198
|
+
* @param contractAddress - The contract address to silo the secret for
|
|
199
|
+
* @param sender - The address sending the note
|
|
200
|
+
* @param recipient - The address receiving the note
|
|
201
|
+
* @returns An indexed tagging secret that can be used to tag notes.
|
|
202
|
+
*/ async getIndexedTaggingSecretAsSender(contractAddress, sender, recipient) {
|
|
177
203
|
await this.syncTaggedLogsAsSender(contractAddress, sender, recipient);
|
|
178
|
-
const appTaggingSecret = await
|
|
179
|
-
const [index] = await this.db.getTaggingSecretsIndexesAsSender([
|
|
204
|
+
const appTaggingSecret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient);
|
|
205
|
+
const [index] = await this.db.getTaggingSecretsIndexesAsSender([
|
|
206
|
+
appTaggingSecret
|
|
207
|
+
]);
|
|
180
208
|
return new IndexedTaggingSecret(appTaggingSecret, index);
|
|
181
209
|
}
|
|
182
210
|
/**
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const secret = await __classPrivateFieldGet(this, _SimulatorOracle_instances, "m", _SimulatorOracle_calculateAppTaggingSecret).call(this, contractAddress, sender, recipient);
|
|
211
|
+
* Increments the tagging secret for a given sender and recipient pair. For this to work, the ivsk_m of the sender must be known.
|
|
212
|
+
* @param contractAddress - The contract address to silo the secret for
|
|
213
|
+
* @param sender - The address sending the note
|
|
214
|
+
* @param recipient - The address receiving the note
|
|
215
|
+
*/ async incrementAppTaggingSecretIndexAsSender(contractAddress, sender, recipient) {
|
|
216
|
+
const secret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient);
|
|
190
217
|
const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
|
|
191
218
|
this.log.debug(`Incrementing app tagging secret at ${contractName}(${contractAddress})`, {
|
|
192
219
|
secret,
|
|
193
220
|
sender,
|
|
194
221
|
recipient,
|
|
195
222
|
contractName,
|
|
196
|
-
contractAddress
|
|
223
|
+
contractAddress
|
|
197
224
|
});
|
|
198
|
-
const [index] = await this.db.getTaggingSecretsIndexesAsSender([
|
|
199
|
-
|
|
225
|
+
const [index] = await this.db.getTaggingSecretsIndexesAsSender([
|
|
226
|
+
secret
|
|
227
|
+
]);
|
|
228
|
+
await this.db.setTaggingSecretsIndexesAsSender([
|
|
229
|
+
new IndexedTaggingSecret(secret, index + 1)
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
async #calculateAppTaggingSecret(contractAddress, sender, recipient) {
|
|
233
|
+
const senderCompleteAddress = await this.getCompleteAddress(sender);
|
|
234
|
+
const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
|
|
235
|
+
const secretPoint = await computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
|
|
236
|
+
// Silo the secret so it can't be used to track other app's notes
|
|
237
|
+
const appSecret = poseidon2Hash([
|
|
238
|
+
secretPoint.x,
|
|
239
|
+
secretPoint.y,
|
|
240
|
+
contractAddress
|
|
241
|
+
]);
|
|
242
|
+
return appSecret;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Returns the indexed tagging secrets for a given recipient and all the senders in the address book
|
|
246
|
+
* This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration
|
|
247
|
+
* of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment,
|
|
248
|
+
* so we're keeping it private for now.
|
|
249
|
+
* @param contractAddress - The contract address to silo the secret for
|
|
250
|
+
* @param recipient - The address receiving the notes
|
|
251
|
+
* @returns A list of indexed tagging secrets
|
|
252
|
+
*/ async #getIndexedTaggingSecretsForSenders(contractAddress, recipient) {
|
|
253
|
+
const recipientCompleteAddress = await this.getCompleteAddress(recipient);
|
|
254
|
+
const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient);
|
|
255
|
+
// We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves
|
|
256
|
+
// (recipient = us, sender = us)
|
|
257
|
+
const senders = [
|
|
258
|
+
...await this.db.getSenderAddresses(),
|
|
259
|
+
...await this.keyStore.getAccounts()
|
|
260
|
+
].filter((address, index, self)=>index === self.findIndex((otherAddress)=>otherAddress.equals(address)));
|
|
261
|
+
const appTaggingSecrets = await Promise.all(senders.map(async (contact)=>{
|
|
262
|
+
const sharedSecret = await computeTaggingSecretPoint(recipientCompleteAddress, recipientIvsk, contact);
|
|
263
|
+
return poseidon2Hash([
|
|
264
|
+
sharedSecret.x,
|
|
265
|
+
sharedSecret.y,
|
|
266
|
+
contractAddress
|
|
267
|
+
]);
|
|
268
|
+
}));
|
|
269
|
+
const indexes = await this.db.getTaggingSecretsIndexesAsRecipient(appTaggingSecrets);
|
|
270
|
+
return appTaggingSecrets.map((secret, i)=>new IndexedTaggingSecret(secret, indexes[i]));
|
|
200
271
|
}
|
|
201
272
|
/**
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
273
|
+
* Updates the local index of the shared tagging secret of a sender / recipient pair
|
|
274
|
+
* if a log with a larger index is found from the node.
|
|
275
|
+
* @param contractAddress - The address of the contract that the logs are tagged for
|
|
276
|
+
* @param sender - The address of the sender, we must know the sender's ivsk_m.
|
|
277
|
+
* @param recipient - The address of the recipient.
|
|
278
|
+
*/ async syncTaggedLogsAsSender(contractAddress, sender, recipient) {
|
|
279
|
+
const appTaggingSecret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient);
|
|
280
|
+
const [oldIndex] = await this.db.getTaggingSecretsIndexesAsSender([
|
|
281
|
+
appTaggingSecret
|
|
282
|
+
]);
|
|
211
283
|
// This algorithm works such that:
|
|
212
284
|
// 1. If we find minimum consecutive empty logs in a window of logs we set the index to the index of the last log
|
|
213
285
|
// we found and quit.
|
|
@@ -215,51 +287,55 @@ export class SimulatorOracle {
|
|
|
215
287
|
// and repeat the process.
|
|
216
288
|
const MIN_CONSECUTIVE_EMPTY_LOGS = 10;
|
|
217
289
|
const WINDOW_SIZE = MIN_CONSECUTIVE_EMPTY_LOGS * 2;
|
|
218
|
-
let [numConsecutiveEmptyLogs, currentIndex] = [
|
|
290
|
+
let [numConsecutiveEmptyLogs, currentIndex] = [
|
|
291
|
+
0,
|
|
292
|
+
oldIndex
|
|
293
|
+
];
|
|
219
294
|
do {
|
|
220
295
|
// We compute the tags for the current window of indexes
|
|
221
|
-
const currentTags = await timesParallel(WINDOW_SIZE, i
|
|
296
|
+
const currentTags = await timesParallel(WINDOW_SIZE, (i)=>{
|
|
222
297
|
const indexedAppTaggingSecret = new IndexedTaggingSecret(appTaggingSecret, currentIndex + i);
|
|
223
298
|
return indexedAppTaggingSecret.computeSiloedTag(recipient, contractAddress);
|
|
224
299
|
});
|
|
225
300
|
// We fetch the logs for the tags
|
|
226
301
|
const possibleLogs = await this.aztecNode.getLogsByTags(currentTags);
|
|
227
302
|
// We find the index of the last log in the window that is not empty
|
|
228
|
-
const indexOfLastLog = possibleLogs.findLastIndex(possibleLog
|
|
303
|
+
const indexOfLastLog = possibleLogs.findLastIndex((possibleLog)=>possibleLog.length !== 0);
|
|
229
304
|
if (indexOfLastLog === -1) {
|
|
230
|
-
// We haven't found any logs in the current window so we stop looking
|
|
231
305
|
break;
|
|
232
306
|
}
|
|
233
307
|
// We move the current index to that of the last log we found
|
|
234
308
|
currentIndex += indexOfLastLog + 1;
|
|
235
309
|
// We compute the number of consecutive empty logs we found and repeat the process if we haven't found enough.
|
|
236
310
|
numConsecutiveEmptyLogs = WINDOW_SIZE - indexOfLastLog - 1;
|
|
237
|
-
}
|
|
311
|
+
}while (numConsecutiveEmptyLogs < MIN_CONSECUTIVE_EMPTY_LOGS)
|
|
238
312
|
const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
|
|
239
313
|
if (currentIndex !== oldIndex) {
|
|
240
|
-
await this.db.setTaggingSecretsIndexesAsSender([
|
|
314
|
+
await this.db.setTaggingSecretsIndexesAsSender([
|
|
315
|
+
new IndexedTaggingSecret(appTaggingSecret, currentIndex)
|
|
316
|
+
]);
|
|
241
317
|
this.log.debug(`Syncing logs for sender ${sender} at contract ${contractName}(${contractAddress})`, {
|
|
242
318
|
sender,
|
|
243
319
|
secret: appTaggingSecret,
|
|
244
320
|
index: currentIndex,
|
|
245
321
|
contractName,
|
|
246
|
-
contractAddress
|
|
322
|
+
contractAddress
|
|
247
323
|
});
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
324
|
+
} else {
|
|
250
325
|
this.log.debug(`No new logs found for sender ${sender} at contract ${contractName}(${contractAddress})`);
|
|
251
326
|
}
|
|
252
327
|
}
|
|
253
328
|
/**
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
329
|
+
* Synchronizes the logs tagged with scoped addresses and all the senders in the address book.
|
|
330
|
+
* Returns the unsynched logs and updates the indexes of the secrets used to tag them until there are no more logs
|
|
331
|
+
* to sync.
|
|
332
|
+
* @param contractAddress - The address of the contract that the logs are tagged for
|
|
333
|
+
* @param recipient - The address of the recipient
|
|
334
|
+
* @returns A list of encrypted logs tagged with the recipient's address
|
|
335
|
+
*/ async syncTaggedLogs(contractAddress, maxBlockNumber, scopes) {
|
|
336
|
+
this.log.verbose('Searching for tagged logs', {
|
|
337
|
+
contract: contractAddress
|
|
338
|
+
});
|
|
263
339
|
// Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles.
|
|
264
340
|
// However it is impossible at the moment due to the language not supporting nested slices.
|
|
265
341
|
// This nesting is necessary because for a given set of tags we don't
|
|
@@ -271,10 +347,10 @@ export class SimulatorOracle {
|
|
|
271
347
|
// that a logs will be received ordered by a given tax index and that the tags won't be reused).
|
|
272
348
|
const logsMap = new Map();
|
|
273
349
|
const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
|
|
274
|
-
for (const recipient of recipients)
|
|
350
|
+
for (const recipient of recipients){
|
|
275
351
|
const logsForRecipient = [];
|
|
276
352
|
// Get all the secrets for the recipient and sender pairs (#9365)
|
|
277
|
-
const secrets = await
|
|
353
|
+
const secrets = await this.#getIndexedTaggingSecretsForSenders(contractAddress, recipient);
|
|
278
354
|
// We fetch logs for a window of indexes in a range:
|
|
279
355
|
// <latest_log_index - WINDOW_HALF_SIZE, latest_log_index + WINDOW_HALF_SIZE>.
|
|
280
356
|
//
|
|
@@ -283,32 +359,32 @@ export class SimulatorOracle {
|
|
|
283
359
|
// for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again.
|
|
284
360
|
// Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed
|
|
285
361
|
// some logs. For these reasons, we have to look both back and ahead of the stored index.
|
|
286
|
-
let secretsAndWindows = secrets.map(secret
|
|
362
|
+
let secretsAndWindows = secrets.map((secret)=>{
|
|
287
363
|
return {
|
|
288
364
|
appTaggingSecret: secret.appTaggingSecret,
|
|
289
365
|
leftMostIndex: Math.max(0, secret.index - WINDOW_HALF_SIZE),
|
|
290
|
-
rightMostIndex: secret.index + WINDOW_HALF_SIZE
|
|
366
|
+
rightMostIndex: secret.index + WINDOW_HALF_SIZE
|
|
291
367
|
};
|
|
292
368
|
});
|
|
293
369
|
// As we iterate we store the largest index we have seen for a given secret to later on store it in the db.
|
|
294
370
|
const newLargestIndexMapToStore = {};
|
|
295
371
|
// The initial/unmodified indexes of the secrets stored in a key-value map where key is the app tagging secret.
|
|
296
372
|
const initialIndexesMap = getInitialIndexesMap(secrets);
|
|
297
|
-
while
|
|
373
|
+
while(secretsAndWindows.length > 0){
|
|
298
374
|
const secretsForTheWholeWindow = getIndexedTaggingSecretsForTheWindow(secretsAndWindows);
|
|
299
|
-
const tagsForTheWholeWindow = await Promise.all(secretsForTheWholeWindow.map(secret
|
|
375
|
+
const tagsForTheWholeWindow = await Promise.all(secretsForTheWholeWindow.map((secret)=>secret.computeSiloedTag(recipient, contractAddress)));
|
|
300
376
|
// We store the new largest indexes we find in the iteration in the following map to later on construct
|
|
301
377
|
// a new set of secrets and windows to fetch logs for.
|
|
302
378
|
const newLargestIndexMapForIteration = {};
|
|
303
379
|
// Fetch the logs for the tags and iterate over them
|
|
304
380
|
const logsByTags = await this.aztecNode.getLogsByTags(tagsForTheWholeWindow);
|
|
305
|
-
logsByTags.forEach((logsByTag, logIndex)
|
|
381
|
+
logsByTags.forEach((logsByTag, logIndex)=>{
|
|
306
382
|
if (logsByTag.length > 0) {
|
|
307
383
|
// Check that public logs have the correct contract address
|
|
308
|
-
const checkedLogsbyTag = logsByTag.filter(l
|
|
384
|
+
const checkedLogsbyTag = logsByTag.filter((l)=>!l.isFromPublic || PublicLog.fromBuffer(l.logData).contractAddress.equals(contractAddress));
|
|
309
385
|
if (checkedLogsbyTag.length < logsByTag.length) {
|
|
310
|
-
const discarded = logsByTag.filter(log
|
|
311
|
-
this.log.warn(`Discarded ${logsByTag.length - checkedLogsbyTag.length} public logs with mismatched contract address ${contractAddress}:`, discarded.map(l
|
|
386
|
+
const discarded = logsByTag.filter((log)=>checkedLogsbyTag.find((filteredLog)=>filteredLog.equals(log)) === undefined);
|
|
387
|
+
this.log.warn(`Discarded ${logsByTag.length - checkedLogsbyTag.length} public logs with mismatched contract address ${contractAddress}:`, discarded.map((l)=>PublicLog.fromBuffer(l.logData)));
|
|
312
388
|
}
|
|
313
389
|
// The logs for the given tag exist so we store them for later processing
|
|
314
390
|
logsForRecipient.push(...checkedLogsbyTag);
|
|
@@ -320,16 +396,12 @@ export class SimulatorOracle {
|
|
|
320
396
|
recipient,
|
|
321
397
|
secret: secretCorrespondingToLog.appTaggingSecret,
|
|
322
398
|
contractName,
|
|
323
|
-
contractAddress
|
|
399
|
+
contractAddress
|
|
324
400
|
});
|
|
325
|
-
if (secretCorrespondingToLog.index >= initialIndex &&
|
|
326
|
-
(newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] === undefined ||
|
|
327
|
-
secretCorrespondingToLog.index >=
|
|
328
|
-
newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()])) {
|
|
401
|
+
if (secretCorrespondingToLog.index >= initialIndex && (newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] === undefined || secretCorrespondingToLog.index >= newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()])) {
|
|
329
402
|
// We have found a new largest index so we store it for later processing (storing it in the db + fetching
|
|
330
403
|
// the difference of the window sets of current and the next iteration)
|
|
331
|
-
newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] =
|
|
332
|
-
secretCorrespondingToLog.index + 1;
|
|
404
|
+
newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] = secretCorrespondingToLog.index + 1;
|
|
333
405
|
this.log.debug(`Incrementing index to ${secretCorrespondingToLog.index + 1} at contract ${contractName}(${contractAddress})`);
|
|
334
406
|
}
|
|
335
407
|
}
|
|
@@ -338,19 +410,18 @@ export class SimulatorOracle {
|
|
|
338
410
|
// for. Note that it's very unlikely that a new log from the current window would appear between the iterations
|
|
339
411
|
// so we fetch the logs only for the difference of the window sets.
|
|
340
412
|
const newSecretsAndWindows = [];
|
|
341
|
-
for (const [appTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration))
|
|
342
|
-
const secret = secrets.find(secret
|
|
413
|
+
for (const [appTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)){
|
|
414
|
+
const secret = secrets.find((secret)=>secret.appTaggingSecret.toString() === appTaggingSecret);
|
|
343
415
|
if (secret) {
|
|
344
416
|
newSecretsAndWindows.push({
|
|
345
417
|
appTaggingSecret: secret.appTaggingSecret,
|
|
346
418
|
// We set the left most index to the new index to avoid fetching the same logs again
|
|
347
419
|
leftMostIndex: newIndex,
|
|
348
|
-
rightMostIndex: newIndex + WINDOW_HALF_SIZE
|
|
420
|
+
rightMostIndex: newIndex + WINDOW_HALF_SIZE
|
|
349
421
|
});
|
|
350
422
|
// We store the new largest index in the map to later store it in the db.
|
|
351
423
|
newLargestIndexMapToStore[appTaggingSecret] = newIndex;
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
424
|
+
} else {
|
|
354
425
|
throw new Error(`Secret not found for appTaggingSecret ${appTaggingSecret}. This is a bug as it should never happen!`);
|
|
355
426
|
}
|
|
356
427
|
}
|
|
@@ -358,24 +429,59 @@ export class SimulatorOracle {
|
|
|
358
429
|
secretsAndWindows = newSecretsAndWindows;
|
|
359
430
|
}
|
|
360
431
|
// We filter the logs by block number and store them in the map.
|
|
361
|
-
logsMap.set(recipient.toString(), logsForRecipient.filter(log
|
|
432
|
+
logsMap.set(recipient.toString(), logsForRecipient.filter((log)=>log.blockNumber <= maxBlockNumber));
|
|
362
433
|
// At this point we have processed all the logs for the recipient so we store the new largest indexes in the db.
|
|
363
|
-
await this.db.setTaggingSecretsIndexesAsRecipient(Object.entries(newLargestIndexMapToStore).map(([appTaggingSecret, index])
|
|
434
|
+
await this.db.setTaggingSecretsIndexesAsRecipient(Object.entries(newLargestIndexMapToStore).map(([appTaggingSecret, index])=>new IndexedTaggingSecret(Fr.fromHexString(appTaggingSecret), index)));
|
|
364
435
|
}
|
|
365
436
|
return logsMap;
|
|
366
437
|
}
|
|
367
438
|
/**
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
439
|
+
* Decrypts logs tagged for a recipient and returns them.
|
|
440
|
+
* @param scopedLogs - The logs to decrypt.
|
|
441
|
+
* @param recipient - The recipient of the logs.
|
|
442
|
+
* @returns The decrypted notes.
|
|
443
|
+
*/ async #decryptTaggedLogs(scopedLogs, recipient) {
|
|
444
|
+
const recipientCompleteAddress = await this.getCompleteAddress(recipient);
|
|
445
|
+
const ivskM = await this.keyStore.getMasterSecretKey(recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey);
|
|
446
|
+
const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM);
|
|
447
|
+
// Since we could have notes with the same index for different txs, we need
|
|
448
|
+
// to keep track of them scoping by txHash
|
|
449
|
+
const excludedIndices = new Map();
|
|
450
|
+
const decrypted = [];
|
|
451
|
+
for (const scopedLog of scopedLogs){
|
|
452
|
+
const payload = scopedLog.isFromPublic ? await L1NotePayload.decryptAsIncomingFromPublic(PublicLog.fromBuffer(scopedLog.logData), addressSecret) : await L1NotePayload.decryptAsIncoming(PrivateLog.fromBuffer(scopedLog.logData), addressSecret);
|
|
453
|
+
if (!payload) {
|
|
454
|
+
this.log.verbose('Unable to decrypt log');
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (!excludedIndices.has(scopedLog.txHash.toString())) {
|
|
458
|
+
excludedIndices.set(scopedLog.txHash.toString(), new Set());
|
|
459
|
+
}
|
|
460
|
+
const note = await getOrderedNoteItems(this.db, payload);
|
|
461
|
+
const plaintext = [
|
|
462
|
+
payload.storageSlot,
|
|
463
|
+
payload.noteTypeId.toField(),
|
|
464
|
+
...note.items
|
|
465
|
+
];
|
|
466
|
+
decrypted.push({
|
|
467
|
+
plaintext,
|
|
468
|
+
txHash: scopedLog.txHash,
|
|
469
|
+
contractAddress: payload.contractAddress
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return decrypted;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Processes the tagged logs returned by syncTaggedLogs by decrypting them and storing them in the database.
|
|
476
|
+
* @param logs - The logs to process.
|
|
477
|
+
* @param recipient - The recipient of the logs.
|
|
478
|
+
*/ async processTaggedLogs(logs, recipient, simulator) {
|
|
479
|
+
const decryptedLogs = await this.#decryptTaggedLogs(logs, recipient);
|
|
374
480
|
// We've produced the full NoteDao, which we'd be able to simply insert into the database. However, this is
|
|
375
481
|
// only a temporary measure as we migrate from the PXE-driven discovery into the new contract-driven approach. We
|
|
376
482
|
// discard most of the work done up to this point and reconstruct the note plaintext to then hand over to the
|
|
377
483
|
// contract for further processing.
|
|
378
|
-
for (const decryptedLog of decryptedLogs)
|
|
484
|
+
for (const decryptedLog of decryptedLogs){
|
|
379
485
|
// Log processing requires the note hashes in the tx in which the note was created. We are now assuming that the
|
|
380
486
|
// note was included in the same block in which the log was delivered - note that partial notes will not work this
|
|
381
487
|
// way.
|
|
@@ -391,32 +497,71 @@ export class SimulatorOracle {
|
|
|
391
497
|
// Called when notes are delivered, usually as a result to a call to the process_log contract function
|
|
392
498
|
async deliverNote(contractAddress, storageSlot, nonce, content, noteHash, nullifier, txHash, recipient) {
|
|
393
499
|
const noteDao = await this.produceNoteDao(contractAddress, storageSlot, nonce, content, noteHash, nullifier, txHash, recipient);
|
|
394
|
-
await this.db.addNotes([
|
|
500
|
+
await this.db.addNotes([
|
|
501
|
+
noteDao
|
|
502
|
+
], recipient);
|
|
395
503
|
this.log.verbose('Added note', {
|
|
396
504
|
contract: noteDao.contractAddress,
|
|
397
505
|
slot: noteDao.storageSlot,
|
|
398
|
-
nullifier: noteDao.siloedNullifier.toString
|
|
506
|
+
nullifier: noteDao.siloedNullifier.toString
|
|
399
507
|
});
|
|
400
508
|
}
|
|
509
|
+
async getLogByTag(tag) {
|
|
510
|
+
const logs = await this.aztecNode.getLogsByTags([
|
|
511
|
+
tag
|
|
512
|
+
]);
|
|
513
|
+
const logsForTag = logs[0];
|
|
514
|
+
this.log.debug(`Got ${logsForTag.length} logs for tag ${tag}`);
|
|
515
|
+
if (logsForTag.length == 0) {
|
|
516
|
+
return null;
|
|
517
|
+
} else if (logsForTag.length > 1) {
|
|
518
|
+
// TODO(#11627): handle this case
|
|
519
|
+
throw new Error(`Got ${logsForTag.length} logs for tag ${tag}. getLogByTag currently only supports a single log per tag`);
|
|
520
|
+
}
|
|
521
|
+
const log = logsForTag[0];
|
|
522
|
+
// getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so
|
|
523
|
+
// we need to make a second call to the node for `getTxEffect`.
|
|
524
|
+
// TODO(#9789): bundle this information in the `getLogsByTag` call.
|
|
525
|
+
const txEffect = await this.aztecNode.getTxEffect(log.txHash);
|
|
526
|
+
if (txEffect == undefined) {
|
|
527
|
+
throw new Error(`Unexpected: failed to retrieve tx effects for tx ${log.txHash} which is known to exist`);
|
|
528
|
+
}
|
|
529
|
+
const reader = BufferReader.asReader(log.logData);
|
|
530
|
+
const logArray = reader.readArray(PUBLIC_LOG_DATA_SIZE_IN_FIELDS, Fr);
|
|
531
|
+
// Public logs always take up all available fields by padding with zeroes, and the length of the originally emitted
|
|
532
|
+
// log is lost. Until this is improved, we simply remove all of the zero elements (which are expected to be at the
|
|
533
|
+
// end).
|
|
534
|
+
// TODO(#11636): use the actual log length.
|
|
535
|
+
const trimmedLog = logArray.filter((x)=>!x.isZero());
|
|
536
|
+
return new LogWithTxData(trimmedLog, log.txHash.hash, txEffect.data.noteHashes, txEffect.data.nullifiers[0]);
|
|
537
|
+
}
|
|
401
538
|
async removeNullifiedNotes(contractAddress) {
|
|
402
|
-
this.log.verbose('
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
539
|
+
this.log.verbose('Searching for nullifiers of known notes', {
|
|
540
|
+
contract: contractAddress
|
|
541
|
+
});
|
|
542
|
+
for (const recipient of (await this.keyStore.getAccounts())){
|
|
543
|
+
const currentNotesForRecipient = await this.db.getNotes({
|
|
544
|
+
contractAddress,
|
|
545
|
+
owner: recipient
|
|
546
|
+
});
|
|
547
|
+
const nullifiersToCheck = currentNotesForRecipient.map((note)=>note.siloedNullifier);
|
|
406
548
|
const nullifierIndexes = await this.aztecNode.findNullifiersIndexesWithBlock('latest', nullifiersToCheck);
|
|
407
|
-
const foundNullifiers = nullifiersToCheck
|
|
408
|
-
.map((nullifier, i) => {
|
|
549
|
+
const foundNullifiers = nullifiersToCheck.map((nullifier, i)=>{
|
|
409
550
|
if (nullifierIndexes[i] !== undefined) {
|
|
410
|
-
return {
|
|
551
|
+
return {
|
|
552
|
+
...nullifierIndexes[i],
|
|
553
|
+
...{
|
|
554
|
+
data: nullifier
|
|
555
|
+
}
|
|
556
|
+
};
|
|
411
557
|
}
|
|
412
|
-
})
|
|
413
|
-
.filter(nullifier => nullifier !== undefined);
|
|
558
|
+
}).filter((nullifier)=>nullifier !== undefined);
|
|
414
559
|
const nullifiedNotes = await this.db.removeNullifiedNotes(foundNullifiers, await recipient.toAddressPoint());
|
|
415
|
-
nullifiedNotes.forEach(noteDao
|
|
560
|
+
nullifiedNotes.forEach((noteDao)=>{
|
|
416
561
|
this.log.verbose(`Removed note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`, {
|
|
417
562
|
contract: noteDao.contractAddress,
|
|
418
563
|
slot: noteDao.storageSlot,
|
|
419
|
-
nullifier: noteDao.siloedNullifier.toString()
|
|
564
|
+
nullifier: noteDao.siloedNullifier.toString()
|
|
420
565
|
});
|
|
421
566
|
});
|
|
422
567
|
}
|
|
@@ -437,7 +582,9 @@ export class SimulatorOracle {
|
|
|
437
582
|
// locally synced block number which *should* be recent enough to be available. We avoid querying at 'latest' since
|
|
438
583
|
// we want to avoid accidentally processing notes that only exist ahead in time of the locally synced state.
|
|
439
584
|
const syncedBlockNumber = await this.db.getBlockNumber();
|
|
440
|
-
const uniqueNoteHashTreeIndex = (await this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NOTE_HASH_TREE, [
|
|
585
|
+
const uniqueNoteHashTreeIndex = (await this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NOTE_HASH_TREE, [
|
|
586
|
+
uniqueNoteHash
|
|
587
|
+
]))[0];
|
|
441
588
|
if (uniqueNoteHashTreeIndex === undefined) {
|
|
442
589
|
throw new Error(`Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present on the tree at block ${syncedBlockNumber} (from tx ${txHash})`);
|
|
443
590
|
}
|
|
@@ -448,10 +595,11 @@ export class SimulatorOracle {
|
|
|
448
595
|
if (!artifact) {
|
|
449
596
|
throw new Error(`Mandatory implementation of "process_log" missing in noir contract ${contractAddress.toString()}.`);
|
|
450
597
|
}
|
|
598
|
+
const selector = await FunctionSelector.fromNameAndParameters(artifact);
|
|
451
599
|
const execRequest = {
|
|
452
600
|
name: artifact.name,
|
|
453
601
|
to: contractAddress,
|
|
454
|
-
selector
|
|
602
|
+
selector,
|
|
455
603
|
type: FunctionType.UNCONSTRAINED,
|
|
456
604
|
isStatic: artifact.isStatic,
|
|
457
605
|
args: encodeArguments(artifact, [
|
|
@@ -459,12 +607,11 @@ export class SimulatorOracle {
|
|
|
459
607
|
txHash.toString(),
|
|
460
608
|
toBoundedVec(noteHashes, MAX_NOTE_HASHES_PER_TX),
|
|
461
609
|
firstNullifier,
|
|
462
|
-
recipient
|
|
610
|
+
recipient
|
|
463
611
|
]),
|
|
464
|
-
returnTypes: artifact.returnTypes
|
|
612
|
+
returnTypes: artifact.returnTypes
|
|
465
613
|
};
|
|
466
|
-
await (simulator ??
|
|
467
|
-
getAcirSimulator(this.db, this.aztecNode, this.keyStore, this.simulationProvider, this.contractDataOracle)).runUnconstrained(execRequest, artifact, contractAddress, []);
|
|
614
|
+
await (simulator ?? getAcirSimulator(this.db, this.aztecNode, this.keyStore, this.simulationProvider, this.contractDataOracle)).runUnconstrained(execRequest, contractAddress, selector, []);
|
|
468
615
|
}
|
|
469
616
|
storeCapsule(contractAddress, slot, capsule) {
|
|
470
617
|
return this.db.storeCapsule(contractAddress, slot, capsule);
|
|
@@ -479,84 +626,9 @@ export class SimulatorOracle {
|
|
|
479
626
|
return this.db.copyCapsule(contractAddress, srcSlot, dstSlot, numEntries);
|
|
480
627
|
}
|
|
481
628
|
}
|
|
482
|
-
_SimulatorOracle_instances = new WeakSet(), _SimulatorOracle_findLeafIndex = async function _SimulatorOracle_findLeafIndex(blockNumber, treeId, leafValue) {
|
|
483
|
-
const [leafIndex] = await this.aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]);
|
|
484
|
-
return leafIndex;
|
|
485
|
-
}, _SimulatorOracle_getSiblingPath = async function _SimulatorOracle_getSiblingPath(blockNumber, treeId, leafIndex) {
|
|
486
|
-
switch (treeId) {
|
|
487
|
-
case MerkleTreeId.NULLIFIER_TREE:
|
|
488
|
-
return (await this.aztecNode.getNullifierSiblingPath(blockNumber, leafIndex)).toFields();
|
|
489
|
-
case MerkleTreeId.NOTE_HASH_TREE:
|
|
490
|
-
return (await this.aztecNode.getNoteHashSiblingPath(blockNumber, leafIndex)).toFields();
|
|
491
|
-
case MerkleTreeId.PUBLIC_DATA_TREE:
|
|
492
|
-
return (await this.aztecNode.getPublicDataSiblingPath(blockNumber, leafIndex)).toFields();
|
|
493
|
-
case MerkleTreeId.ARCHIVE:
|
|
494
|
-
return (await this.aztecNode.getArchiveSiblingPath(blockNumber, leafIndex)).toFields();
|
|
495
|
-
default:
|
|
496
|
-
throw new Error('Not implemented');
|
|
497
|
-
}
|
|
498
|
-
}, _SimulatorOracle_calculateAppTaggingSecret = async function _SimulatorOracle_calculateAppTaggingSecret(contractAddress, sender, recipient) {
|
|
499
|
-
const senderCompleteAddress = await this.getCompleteAddress(sender);
|
|
500
|
-
const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
|
|
501
|
-
const secretPoint = await computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
|
|
502
|
-
// Silo the secret so it can't be used to track other app's notes
|
|
503
|
-
const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]);
|
|
504
|
-
return appSecret;
|
|
505
|
-
}, _SimulatorOracle_getIndexedTaggingSecretsForSenders =
|
|
506
|
-
/**
|
|
507
|
-
* Returns the indexed tagging secrets for a given recipient and all the senders in the address book
|
|
508
|
-
* This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration
|
|
509
|
-
* of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment,
|
|
510
|
-
* so we're keeping it private for now.
|
|
511
|
-
* @param contractAddress - The contract address to silo the secret for
|
|
512
|
-
* @param recipient - The address receiving the notes
|
|
513
|
-
* @returns A list of indexed tagging secrets
|
|
514
|
-
*/
|
|
515
|
-
async function _SimulatorOracle_getIndexedTaggingSecretsForSenders(contractAddress, recipient) {
|
|
516
|
-
const recipientCompleteAddress = await this.getCompleteAddress(recipient);
|
|
517
|
-
const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient);
|
|
518
|
-
// We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves
|
|
519
|
-
// (recipient = us, sender = us)
|
|
520
|
-
const senders = [...(await this.db.getSenderAddresses()), ...(await this.keyStore.getAccounts())].filter((address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address)));
|
|
521
|
-
const appTaggingSecrets = await Promise.all(senders.map(async (contact) => {
|
|
522
|
-
const sharedSecret = await computeTaggingSecretPoint(recipientCompleteAddress, recipientIvsk, contact);
|
|
523
|
-
return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
|
|
524
|
-
}));
|
|
525
|
-
const indexes = await this.db.getTaggingSecretsIndexesAsRecipient(appTaggingSecrets);
|
|
526
|
-
return appTaggingSecrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i]));
|
|
527
|
-
}, _SimulatorOracle_decryptTaggedLogs =
|
|
528
|
-
/**
|
|
529
|
-
* Decrypts logs tagged for a recipient and returns them.
|
|
530
|
-
* @param scopedLogs - The logs to decrypt.
|
|
531
|
-
* @param recipient - The recipient of the logs.
|
|
532
|
-
* @returns The decrypted notes.
|
|
533
|
-
*/
|
|
534
|
-
async function _SimulatorOracle_decryptTaggedLogs(scopedLogs, recipient) {
|
|
535
|
-
const recipientCompleteAddress = await this.getCompleteAddress(recipient);
|
|
536
|
-
const ivskM = await this.keyStore.getMasterSecretKey(recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey);
|
|
537
|
-
const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM);
|
|
538
|
-
// Since we could have notes with the same index for different txs, we need
|
|
539
|
-
// to keep track of them scoping by txHash
|
|
540
|
-
const excludedIndices = new Map();
|
|
541
|
-
const decrypted = [];
|
|
542
|
-
for (const scopedLog of scopedLogs) {
|
|
543
|
-
const payload = scopedLog.isFromPublic
|
|
544
|
-
? await L1NotePayload.decryptAsIncomingFromPublic(PublicLog.fromBuffer(scopedLog.logData), addressSecret)
|
|
545
|
-
: await L1NotePayload.decryptAsIncoming(PrivateLog.fromBuffer(scopedLog.logData), addressSecret);
|
|
546
|
-
if (!payload) {
|
|
547
|
-
this.log.verbose('Unable to decrypt log');
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
if (!excludedIndices.has(scopedLog.txHash.toString())) {
|
|
551
|
-
excludedIndices.set(scopedLog.txHash.toString(), new Set());
|
|
552
|
-
}
|
|
553
|
-
const note = await getOrderedNoteItems(this.db, payload);
|
|
554
|
-
const plaintext = [payload.storageSlot, payload.noteTypeId.toField(), ...note.items];
|
|
555
|
-
decrypted.push({ plaintext, txHash: scopedLog.txHash, contractAddress: payload.contractAddress });
|
|
556
|
-
}
|
|
557
|
-
return decrypted;
|
|
558
|
-
};
|
|
559
629
|
function toBoundedVec(array, maxLength) {
|
|
560
|
-
return {
|
|
630
|
+
return {
|
|
631
|
+
storage: array.concat(Array(maxLength - array.length).fill(new Fr(0))),
|
|
632
|
+
len: array.length
|
|
633
|
+
};
|
|
561
634
|
}
|
|
562
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2ltdWxhdG9yX29yYWNsZS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLE9BQU8sRUFJTCxhQUFhLEVBR2IsWUFBWSxFQUNaLElBQUksRUFJSixNQUFNLEVBRU4sbUNBQW1DLEdBQ3BDLE1BQU0sc0JBQXNCLENBQUM7QUFDOUIsT0FBTyxFQUtMLEVBQUUsRUFDRixnQkFBZ0IsRUFDaEIsb0JBQW9CLEVBR3BCLHNCQUFzQixFQUN0QiwwQkFBMEIsRUFDMUIsVUFBVSxFQUNWLFNBQVMsRUFDVCxvQkFBb0IsRUFDcEIseUJBQXlCLEdBQzFCLE1BQU0sb0JBQW9CLENBQUM7QUFDNUIsT0FBTyxFQUFFLHFCQUFxQixFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM3RixPQUFPLEVBRUwsWUFBWSxFQUNaLFlBQVksRUFDWixlQUFlLEVBQ2YsbUJBQW1CLEdBQ3BCLE1BQU0sdUJBQXVCLENBQUM7QUFDL0IsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzdELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFckQsT0FBTyxFQUdMLHVCQUF1QixHQUV4QixNQUFNLHlCQUF5QixDQUFDO0FBRWpDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBRXRFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNsRCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSwwREFBMEQsQ0FBQztBQUMvRixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsb0NBQW9DLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxv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
|