@aztec/pxe 0.74.0 → 0.75.0-commit.c03ba01a2a4122e43e90d5133ba017e54b90e9d2
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 +3 -5
- package/dest/config/index.js +18 -21
- package/dest/config/package_info.js +4 -2
- package/dest/contract_data_oracle/index.js +69 -79
- package/dest/contract_data_oracle/private_functions_tree.js +44 -50
- package/dest/database/contracts/contract_artifact_db.js +3 -2
- package/dest/database/contracts/contract_instance_db.js +3 -2
- package/dest/database/index.js +0 -1
- package/dest/database/kv_pxe_database.js +243 -259
- package/dest/database/note_dao.js +28 -43
- package/dest/database/outgoing_note_dao.js +20 -34
- package/dest/database/pxe_database.js +4 -2
- package/dest/database/pxe_database_test_suite.js +296 -151
- package/dest/index.js +0 -1
- package/dest/kernel_oracle/index.js +9 -6
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.js +68 -66
- package/dest/kernel_prover/hints/index.js +0 -1
- package/dest/kernel_prover/index.js +0 -1
- package/dest/kernel_prover/kernel_prover.js +60 -65
- package/dest/kernel_prover/proving_data_oracle.js +4 -2
- package/dest/note_decryption_utils/add_public_values_to_payload.js +8 -9
- package/dest/pxe_http/index.js +0 -1
- package/dest/pxe_http/pxe_http_server.js +8 -6
- package/dest/pxe_service/error_enriching.js +10 -13
- package/dest/pxe_service/index.js +0 -1
- package/dest/pxe_service/pxe_service.js +282 -290
- package/dest/pxe_service/test/pxe_test_suite.js +40 -27
- package/dest/simulator/index.js +1 -3
- package/dest/simulator_oracle/index.js +266 -231
- package/dest/simulator_oracle/tagging_utils.js +4 -6
- package/dest/synchronizer/index.js +0 -1
- package/dest/synchronizer/synchronizer.js +42 -42
- package/dest/utils/create_pxe_service.js +9 -9
- package/package.json +15 -15
- package/src/pxe_service/pxe_service.ts +6 -11
- package/dest/bin/index.d.ts +0 -3
- package/dest/bin/index.d.ts.map +0 -1
- package/dest/config/index.d.ts +0 -46
- package/dest/config/index.d.ts.map +0 -1
- package/dest/config/package_info.d.ts +0 -5
- package/dest/config/package_info.d.ts.map +0 -1
- package/dest/contract_data_oracle/index.d.ts +0 -104
- package/dest/contract_data_oracle/index.d.ts.map +0 -1
- package/dest/contract_data_oracle/private_functions_tree.d.ts +0 -65
- package/dest/contract_data_oracle/private_functions_tree.d.ts.map +0 -1
- package/dest/database/contracts/contract_artifact_db.d.ts +0 -20
- package/dest/database/contracts/contract_artifact_db.d.ts.map +0 -1
- package/dest/database/contracts/contract_instance_db.d.ts +0 -19
- package/dest/database/contracts/contract_instance_db.d.ts.map +0 -1
- package/dest/database/index.d.ts +0 -3
- package/dest/database/index.d.ts.map +0 -1
- package/dest/database/kv_pxe_database.d.ts +0 -55
- package/dest/database/kv_pxe_database.d.ts.map +0 -1
- package/dest/database/note_dao.d.ts +0 -103
- package/dest/database/note_dao.d.ts.map +0 -1
- package/dest/database/outgoing_note_dao.d.ts +0 -73
- package/dest/database/outgoing_note_dao.d.ts.map +0 -1
- package/dest/database/pxe_database.d.ts +0 -216
- package/dest/database/pxe_database.d.ts.map +0 -1
- package/dest/database/pxe_database_test_suite.d.ts +0 -7
- package/dest/database/pxe_database_test_suite.d.ts.map +0 -1
- package/dest/index.d.ts +0 -15
- package/dest/index.d.ts.map +0 -1
- package/dest/kernel_oracle/index.d.ts +0 -34
- package/dest/kernel_oracle/index.d.ts.map +0 -1
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.d.ts +0 -28
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.d.ts.map +0 -1
- package/dest/kernel_prover/hints/index.d.ts +0 -2
- package/dest/kernel_prover/hints/index.d.ts.map +0 -1
- package/dest/kernel_prover/index.d.ts +0 -3
- package/dest/kernel_prover/index.d.ts.map +0 -1
- package/dest/kernel_prover/kernel_prover.d.ts +0 -38
- package/dest/kernel_prover/kernel_prover.d.ts.map +0 -1
- package/dest/kernel_prover/proving_data_oracle.d.ts +0 -65
- package/dest/kernel_prover/proving_data_oracle.d.ts.map +0 -1
- package/dest/note_decryption_utils/add_public_values_to_payload.d.ts +0 -10
- package/dest/note_decryption_utils/add_public_values_to_payload.d.ts.map +0 -1
- package/dest/pxe_http/index.d.ts +0 -2
- package/dest/pxe_http/index.d.ts.map +0 -1
- package/dest/pxe_http/pxe_http_server.d.ts +0 -16
- package/dest/pxe_http/pxe_http_server.d.ts.map +0 -1
- package/dest/pxe_service/error_enriching.d.ts +0 -11
- package/dest/pxe_service/error_enriching.d.ts.map +0 -1
- package/dest/pxe_service/index.d.ts +0 -4
- package/dest/pxe_service/index.d.ts.map +0 -1
- package/dest/pxe_service/pxe_service.d.ts +0 -98
- package/dest/pxe_service/pxe_service.d.ts.map +0 -1
- package/dest/pxe_service/test/pxe_test_suite.d.ts +0 -3
- package/dest/pxe_service/test/pxe_test_suite.d.ts.map +0 -1
- package/dest/simulator/index.d.ts +0 -10
- package/dest/simulator/index.d.ts.map +0 -1
- package/dest/simulator_oracle/index.d.ts +0 -129
- package/dest/simulator_oracle/index.d.ts.map +0 -1
- package/dest/simulator_oracle/tagging_utils.d.ts +0 -16
- package/dest/simulator_oracle/tagging_utils.d.ts.map +0 -1
- package/dest/synchronizer/index.d.ts +0 -2
- package/dest/synchronizer/index.d.ts.map +0 -1
- package/dest/synchronizer/synchronizer.d.ts +0 -30
- package/dest/synchronizer/synchronizer.d.ts.map +0 -1
- package/dest/utils/create_pxe_service.d.ts +0 -16
- package/dest/utils/create_pxe_service.d.ts.map +0 -1
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
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';
|
|
1
|
+
import { L1NotePayload, MerkleTreeId, Note, TxHash, getNonNullifiedL1ToL2MessageWitness } from '@aztec/circuit-types';
|
|
2
|
+
import { Fr, FunctionSelector, IndexedTaggingSecret, MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS, PrivateLog, PublicLog, computeAddressSecret, computeTaggingSecretPoint } from '@aztec/circuits.js';
|
|
5
3
|
import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash';
|
|
6
|
-
import { FunctionType, NoteSelector, encodeArguments, getFunctionArtifact
|
|
4
|
+
import { FunctionType, NoteSelector, encodeArguments, getFunctionArtifact } from '@aztec/foundation/abi';
|
|
7
5
|
import { timesParallel } from '@aztec/foundation/collection';
|
|
8
6
|
import { poseidon2Hash } from '@aztec/foundation/crypto';
|
|
9
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
10
|
-
import { MessageLoadOracleInputs
|
|
8
|
+
import { MessageLoadOracleInputs } from '@aztec/simulator/client';
|
|
11
9
|
import { ContractDataOracle } from '../contract_data_oracle/index.js';
|
|
12
10
|
import { NoteDao } from '../database/note_dao.js';
|
|
13
11
|
import { getOrderedNoteItems } from '../note_decryption_utils/add_public_values_to_payload.js';
|
|
@@ -15,10 +13,14 @@ import { getAcirSimulator } from '../simulator/index.js';
|
|
|
15
13
|
import { WINDOW_HALF_SIZE, getIndexedTaggingSecretsForTheWindow, getInitialIndexesMap } from './tagging_utils.js';
|
|
16
14
|
/**
|
|
17
15
|
* A data oracle that provides information needed for simulating a transaction.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
*/ export class SimulatorOracle {
|
|
17
|
+
contractDataOracle;
|
|
18
|
+
db;
|
|
19
|
+
keyStore;
|
|
20
|
+
aztecNode;
|
|
21
|
+
simulationProvider;
|
|
22
|
+
log;
|
|
23
|
+
constructor(contractDataOracle, db, keyStore, aztecNode, simulationProvider, log = createLogger('pxe:simulator_oracle')){
|
|
22
24
|
this.contractDataOracle = contractDataOracle;
|
|
23
25
|
this.db = db;
|
|
24
26
|
this.keyStore = keyStore;
|
|
@@ -63,25 +65,25 @@ export class SimulatorOracle {
|
|
|
63
65
|
contractAddress,
|
|
64
66
|
storageSlot,
|
|
65
67
|
status,
|
|
66
|
-
scopes
|
|
68
|
+
scopes
|
|
67
69
|
});
|
|
68
|
-
return noteDaos.map(({ contractAddress, storageSlot, nonce, note, noteHash, siloedNullifier, index })
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
}));
|
|
78
80
|
}
|
|
79
81
|
async getFunctionArtifact(contractAddress, selector) {
|
|
80
82
|
const artifact = await this.contractDataOracle.getFunctionArtifact(contractAddress, selector);
|
|
81
83
|
const debug = await this.contractDataOracle.getFunctionDebugMetadata(contractAddress, selector);
|
|
82
84
|
return {
|
|
83
85
|
...artifact,
|
|
84
|
-
debug
|
|
86
|
+
debug
|
|
85
87
|
};
|
|
86
88
|
}
|
|
87
89
|
async getFunctionArtifactByName(contractAddress, functionName) {
|
|
@@ -90,14 +92,13 @@ export class SimulatorOracle {
|
|
|
90
92
|
return artifact && getFunctionArtifact(artifact, functionName);
|
|
91
93
|
}
|
|
92
94
|
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
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) {
|
|
101
102
|
const [messageIndex, siblingPath] = await getNonNullifiedL1ToL2MessageWitness(this.aztecNode, contractAddress, messageHash, secret);
|
|
102
103
|
// Assuming messageIndex is what you intended to use for the index in MessageLoadOracleInputs
|
|
103
104
|
return new MessageLoadOracleInputs(messageIndex, siblingPath);
|
|
@@ -107,27 +108,49 @@ export class SimulatorOracle {
|
|
|
107
108
|
throw new Error('Unimplemented in private!');
|
|
108
109
|
}
|
|
109
110
|
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
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);
|
|
116
116
|
}
|
|
117
117
|
// We need this in public as part of the EXISTS calls - but isn't used in private
|
|
118
118
|
getCommitmentValue(_leafIndex) {
|
|
119
119
|
throw new Error('Unimplemented in private!');
|
|
120
120
|
}
|
|
121
121
|
async getNullifierIndex(nullifier) {
|
|
122
|
-
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;
|
|
123
129
|
}
|
|
124
130
|
async getMembershipWitness(blockNumber, treeId, leafValue) {
|
|
125
|
-
const leafIndex = await
|
|
131
|
+
const leafIndex = await this.#findLeafIndex(blockNumber, treeId, leafValue);
|
|
126
132
|
if (!leafIndex) {
|
|
127
133
|
throw new Error(`Leaf value: ${leafValue} not found in ${MerkleTreeId[treeId]}`);
|
|
128
134
|
}
|
|
129
|
-
const siblingPath = await
|
|
130
|
-
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
|
+
}
|
|
131
154
|
}
|
|
132
155
|
async getNullifierMembershipWitnessAtLatestBlock(nullifier) {
|
|
133
156
|
return this.getNullifierMembershipWitness(await this.getBlockNumber(), nullifier);
|
|
@@ -145,76 +168,118 @@ export class SimulatorOracle {
|
|
|
145
168
|
return await this.aztecNode.getPublicDataTreeWitness(blockNumber, leafSlot);
|
|
146
169
|
}
|
|
147
170
|
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
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() {
|
|
154
176
|
return this.db.getBlockHeader();
|
|
155
177
|
}
|
|
156
178
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
async getBlockNumber() {
|
|
179
|
+
* Fetches the current block number.
|
|
180
|
+
* @returns The block number.
|
|
181
|
+
*/ async getBlockNumber() {
|
|
161
182
|
return await this.aztecNode.getBlockNumber();
|
|
162
183
|
}
|
|
163
184
|
getDebugFunctionName(contractAddress, selector) {
|
|
164
185
|
return this.contractDataOracle.getDebugFunctionName(contractAddress, selector);
|
|
165
186
|
}
|
|
166
187
|
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
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() {
|
|
173
193
|
return this.db.getSenderAddresses();
|
|
174
194
|
}
|
|
175
195
|
/**
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
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) {
|
|
184
203
|
await this.syncTaggedLogsAsSender(contractAddress, sender, recipient);
|
|
185
|
-
const appTaggingSecret = await
|
|
186
|
-
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
|
+
]);
|
|
187
208
|
return new IndexedTaggingSecret(appTaggingSecret, index);
|
|
188
209
|
}
|
|
189
210
|
/**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
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);
|
|
197
217
|
const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
|
|
198
218
|
this.log.debug(`Incrementing app tagging secret at ${contractName}(${contractAddress})`, {
|
|
199
219
|
secret,
|
|
200
220
|
sender,
|
|
201
221
|
recipient,
|
|
202
222
|
contractName,
|
|
203
|
-
contractAddress
|
|
223
|
+
contractAddress
|
|
204
224
|
});
|
|
205
|
-
const [index] = await this.db.getTaggingSecretsIndexesAsSender([
|
|
206
|
-
|
|
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;
|
|
207
243
|
}
|
|
208
244
|
/**
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
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]));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
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
|
+
]);
|
|
218
283
|
// This algorithm works such that:
|
|
219
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
|
|
220
285
|
// we found and quit.
|
|
@@ -222,51 +287,55 @@ export class SimulatorOracle {
|
|
|
222
287
|
// and repeat the process.
|
|
223
288
|
const MIN_CONSECUTIVE_EMPTY_LOGS = 10;
|
|
224
289
|
const WINDOW_SIZE = MIN_CONSECUTIVE_EMPTY_LOGS * 2;
|
|
225
|
-
let [numConsecutiveEmptyLogs, currentIndex] = [
|
|
290
|
+
let [numConsecutiveEmptyLogs, currentIndex] = [
|
|
291
|
+
0,
|
|
292
|
+
oldIndex
|
|
293
|
+
];
|
|
226
294
|
do {
|
|
227
295
|
// We compute the tags for the current window of indexes
|
|
228
|
-
const currentTags = await timesParallel(WINDOW_SIZE, i
|
|
296
|
+
const currentTags = await timesParallel(WINDOW_SIZE, (i)=>{
|
|
229
297
|
const indexedAppTaggingSecret = new IndexedTaggingSecret(appTaggingSecret, currentIndex + i);
|
|
230
298
|
return indexedAppTaggingSecret.computeSiloedTag(recipient, contractAddress);
|
|
231
299
|
});
|
|
232
300
|
// We fetch the logs for the tags
|
|
233
301
|
const possibleLogs = await this.aztecNode.getLogsByTags(currentTags);
|
|
234
302
|
// We find the index of the last log in the window that is not empty
|
|
235
|
-
const indexOfLastLog = possibleLogs.findLastIndex(possibleLog
|
|
303
|
+
const indexOfLastLog = possibleLogs.findLastIndex((possibleLog)=>possibleLog.length !== 0);
|
|
236
304
|
if (indexOfLastLog === -1) {
|
|
237
|
-
// We haven't found any logs in the current window so we stop looking
|
|
238
305
|
break;
|
|
239
306
|
}
|
|
240
307
|
// We move the current index to that of the last log we found
|
|
241
308
|
currentIndex += indexOfLastLog + 1;
|
|
242
309
|
// We compute the number of consecutive empty logs we found and repeat the process if we haven't found enough.
|
|
243
310
|
numConsecutiveEmptyLogs = WINDOW_SIZE - indexOfLastLog - 1;
|
|
244
|
-
}
|
|
311
|
+
}while (numConsecutiveEmptyLogs < MIN_CONSECUTIVE_EMPTY_LOGS)
|
|
245
312
|
const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
|
|
246
313
|
if (currentIndex !== oldIndex) {
|
|
247
|
-
await this.db.setTaggingSecretsIndexesAsSender([
|
|
314
|
+
await this.db.setTaggingSecretsIndexesAsSender([
|
|
315
|
+
new IndexedTaggingSecret(appTaggingSecret, currentIndex)
|
|
316
|
+
]);
|
|
248
317
|
this.log.debug(`Syncing logs for sender ${sender} at contract ${contractName}(${contractAddress})`, {
|
|
249
318
|
sender,
|
|
250
319
|
secret: appTaggingSecret,
|
|
251
320
|
index: currentIndex,
|
|
252
321
|
contractName,
|
|
253
|
-
contractAddress
|
|
322
|
+
contractAddress
|
|
254
323
|
});
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
324
|
+
} else {
|
|
257
325
|
this.log.debug(`No new logs found for sender ${sender} at contract ${contractName}(${contractAddress})`);
|
|
258
326
|
}
|
|
259
327
|
}
|
|
260
328
|
/**
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
+
});
|
|
270
339
|
// Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles.
|
|
271
340
|
// However it is impossible at the moment due to the language not supporting nested slices.
|
|
272
341
|
// This nesting is necessary because for a given set of tags we don't
|
|
@@ -278,10 +347,10 @@ export class SimulatorOracle {
|
|
|
278
347
|
// that a logs will be received ordered by a given tax index and that the tags won't be reused).
|
|
279
348
|
const logsMap = new Map();
|
|
280
349
|
const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
|
|
281
|
-
for (const recipient of recipients)
|
|
350
|
+
for (const recipient of recipients){
|
|
282
351
|
const logsForRecipient = [];
|
|
283
352
|
// Get all the secrets for the recipient and sender pairs (#9365)
|
|
284
|
-
const secrets = await
|
|
353
|
+
const secrets = await this.#getIndexedTaggingSecretsForSenders(contractAddress, recipient);
|
|
285
354
|
// We fetch logs for a window of indexes in a range:
|
|
286
355
|
// <latest_log_index - WINDOW_HALF_SIZE, latest_log_index + WINDOW_HALF_SIZE>.
|
|
287
356
|
//
|
|
@@ -290,32 +359,32 @@ export class SimulatorOracle {
|
|
|
290
359
|
// for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again.
|
|
291
360
|
// Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed
|
|
292
361
|
// some logs. For these reasons, we have to look both back and ahead of the stored index.
|
|
293
|
-
let secretsAndWindows = secrets.map(secret
|
|
362
|
+
let secretsAndWindows = secrets.map((secret)=>{
|
|
294
363
|
return {
|
|
295
364
|
appTaggingSecret: secret.appTaggingSecret,
|
|
296
365
|
leftMostIndex: Math.max(0, secret.index - WINDOW_HALF_SIZE),
|
|
297
|
-
rightMostIndex: secret.index + WINDOW_HALF_SIZE
|
|
366
|
+
rightMostIndex: secret.index + WINDOW_HALF_SIZE
|
|
298
367
|
};
|
|
299
368
|
});
|
|
300
369
|
// As we iterate we store the largest index we have seen for a given secret to later on store it in the db.
|
|
301
370
|
const newLargestIndexMapToStore = {};
|
|
302
371
|
// The initial/unmodified indexes of the secrets stored in a key-value map where key is the app tagging secret.
|
|
303
372
|
const initialIndexesMap = getInitialIndexesMap(secrets);
|
|
304
|
-
while
|
|
373
|
+
while(secretsAndWindows.length > 0){
|
|
305
374
|
const secretsForTheWholeWindow = getIndexedTaggingSecretsForTheWindow(secretsAndWindows);
|
|
306
|
-
const tagsForTheWholeWindow = await Promise.all(secretsForTheWholeWindow.map(secret
|
|
375
|
+
const tagsForTheWholeWindow = await Promise.all(secretsForTheWholeWindow.map((secret)=>secret.computeSiloedTag(recipient, contractAddress)));
|
|
307
376
|
// We store the new largest indexes we find in the iteration in the following map to later on construct
|
|
308
377
|
// a new set of secrets and windows to fetch logs for.
|
|
309
378
|
const newLargestIndexMapForIteration = {};
|
|
310
379
|
// Fetch the logs for the tags and iterate over them
|
|
311
380
|
const logsByTags = await this.aztecNode.getLogsByTags(tagsForTheWholeWindow);
|
|
312
|
-
logsByTags.forEach((logsByTag, logIndex)
|
|
381
|
+
logsByTags.forEach((logsByTag, logIndex)=>{
|
|
313
382
|
if (logsByTag.length > 0) {
|
|
314
383
|
// Check that public logs have the correct contract address
|
|
315
|
-
const checkedLogsbyTag = logsByTag.filter(l
|
|
384
|
+
const checkedLogsbyTag = logsByTag.filter((l)=>!l.isFromPublic || PublicLog.fromBuffer(l.logData).contractAddress.equals(contractAddress));
|
|
316
385
|
if (checkedLogsbyTag.length < logsByTag.length) {
|
|
317
|
-
const discarded = logsByTag.filter(log
|
|
318
|
-
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)));
|
|
319
388
|
}
|
|
320
389
|
// The logs for the given tag exist so we store them for later processing
|
|
321
390
|
logsForRecipient.push(...checkedLogsbyTag);
|
|
@@ -327,16 +396,12 @@ export class SimulatorOracle {
|
|
|
327
396
|
recipient,
|
|
328
397
|
secret: secretCorrespondingToLog.appTaggingSecret,
|
|
329
398
|
contractName,
|
|
330
|
-
contractAddress
|
|
399
|
+
contractAddress
|
|
331
400
|
});
|
|
332
|
-
if (secretCorrespondingToLog.index >= initialIndex &&
|
|
333
|
-
(newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] === undefined ||
|
|
334
|
-
secretCorrespondingToLog.index >=
|
|
335
|
-
newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()])) {
|
|
401
|
+
if (secretCorrespondingToLog.index >= initialIndex && (newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] === undefined || secretCorrespondingToLog.index >= newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()])) {
|
|
336
402
|
// We have found a new largest index so we store it for later processing (storing it in the db + fetching
|
|
337
403
|
// the difference of the window sets of current and the next iteration)
|
|
338
|
-
newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] =
|
|
339
|
-
secretCorrespondingToLog.index + 1;
|
|
404
|
+
newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] = secretCorrespondingToLog.index + 1;
|
|
340
405
|
this.log.debug(`Incrementing index to ${secretCorrespondingToLog.index + 1} at contract ${contractName}(${contractAddress})`);
|
|
341
406
|
}
|
|
342
407
|
}
|
|
@@ -345,19 +410,18 @@ export class SimulatorOracle {
|
|
|
345
410
|
// for. Note that it's very unlikely that a new log from the current window would appear between the iterations
|
|
346
411
|
// so we fetch the logs only for the difference of the window sets.
|
|
347
412
|
const newSecretsAndWindows = [];
|
|
348
|
-
for (const [appTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration))
|
|
349
|
-
const secret = secrets.find(secret
|
|
413
|
+
for (const [appTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)){
|
|
414
|
+
const secret = secrets.find((secret)=>secret.appTaggingSecret.toString() === appTaggingSecret);
|
|
350
415
|
if (secret) {
|
|
351
416
|
newSecretsAndWindows.push({
|
|
352
417
|
appTaggingSecret: secret.appTaggingSecret,
|
|
353
418
|
// We set the left most index to the new index to avoid fetching the same logs again
|
|
354
419
|
leftMostIndex: newIndex,
|
|
355
|
-
rightMostIndex: newIndex + WINDOW_HALF_SIZE
|
|
420
|
+
rightMostIndex: newIndex + WINDOW_HALF_SIZE
|
|
356
421
|
});
|
|
357
422
|
// We store the new largest index in the map to later store it in the db.
|
|
358
423
|
newLargestIndexMapToStore[appTaggingSecret] = newIndex;
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
424
|
+
} else {
|
|
361
425
|
throw new Error(`Secret not found for appTaggingSecret ${appTaggingSecret}. This is a bug as it should never happen!`);
|
|
362
426
|
}
|
|
363
427
|
}
|
|
@@ -365,24 +429,59 @@ export class SimulatorOracle {
|
|
|
365
429
|
secretsAndWindows = newSecretsAndWindows;
|
|
366
430
|
}
|
|
367
431
|
// We filter the logs by block number and store them in the map.
|
|
368
|
-
logsMap.set(recipient.toString(), logsForRecipient.filter(log
|
|
432
|
+
logsMap.set(recipient.toString(), logsForRecipient.filter((log)=>log.blockNumber <= maxBlockNumber));
|
|
369
433
|
// At this point we have processed all the logs for the recipient so we store the new largest indexes in the db.
|
|
370
|
-
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)));
|
|
371
435
|
}
|
|
372
436
|
return logsMap;
|
|
373
437
|
}
|
|
374
438
|
/**
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
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);
|
|
381
480
|
// We've produced the full NoteDao, which we'd be able to simply insert into the database. However, this is
|
|
382
481
|
// only a temporary measure as we migrate from the PXE-driven discovery into the new contract-driven approach. We
|
|
383
482
|
// discard most of the work done up to this point and reconstruct the note plaintext to then hand over to the
|
|
384
483
|
// contract for further processing.
|
|
385
|
-
for (const decryptedLog of decryptedLogs)
|
|
484
|
+
for (const decryptedLog of decryptedLogs){
|
|
386
485
|
// Log processing requires the note hashes in the tx in which the note was created. We are now assuming that the
|
|
387
486
|
// note was included in the same block in which the log was delivered - note that partial notes will not work this
|
|
388
487
|
// way.
|
|
@@ -398,32 +497,42 @@ export class SimulatorOracle {
|
|
|
398
497
|
// Called when notes are delivered, usually as a result to a call to the process_log contract function
|
|
399
498
|
async deliverNote(contractAddress, storageSlot, nonce, content, noteHash, nullifier, txHash, recipient) {
|
|
400
499
|
const noteDao = await this.produceNoteDao(contractAddress, storageSlot, nonce, content, noteHash, nullifier, txHash, recipient);
|
|
401
|
-
await this.db.addNotes([
|
|
500
|
+
await this.db.addNotes([
|
|
501
|
+
noteDao
|
|
502
|
+
], recipient);
|
|
402
503
|
this.log.verbose('Added note', {
|
|
403
504
|
contract: noteDao.contractAddress,
|
|
404
505
|
slot: noteDao.storageSlot,
|
|
405
|
-
nullifier: noteDao.siloedNullifier.toString
|
|
506
|
+
nullifier: noteDao.siloedNullifier.toString
|
|
406
507
|
});
|
|
407
508
|
}
|
|
408
509
|
async removeNullifiedNotes(contractAddress) {
|
|
409
|
-
this.log.verbose('Removing nullified notes', {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
510
|
+
this.log.verbose('Removing nullified notes', {
|
|
511
|
+
contract: contractAddress
|
|
512
|
+
});
|
|
513
|
+
for (const recipient of (await this.keyStore.getAccounts())){
|
|
514
|
+
const currentNotesForRecipient = await this.db.getNotes({
|
|
515
|
+
contractAddress,
|
|
516
|
+
owner: recipient
|
|
517
|
+
});
|
|
518
|
+
const nullifiersToCheck = currentNotesForRecipient.map((note)=>note.siloedNullifier);
|
|
413
519
|
const nullifierIndexes = await this.aztecNode.findNullifiersIndexesWithBlock('latest', nullifiersToCheck);
|
|
414
|
-
const foundNullifiers = nullifiersToCheck
|
|
415
|
-
.map((nullifier, i) => {
|
|
520
|
+
const foundNullifiers = nullifiersToCheck.map((nullifier, i)=>{
|
|
416
521
|
if (nullifierIndexes[i] !== undefined) {
|
|
417
|
-
return {
|
|
522
|
+
return {
|
|
523
|
+
...nullifierIndexes[i],
|
|
524
|
+
...{
|
|
525
|
+
data: nullifier
|
|
526
|
+
}
|
|
527
|
+
};
|
|
418
528
|
}
|
|
419
|
-
})
|
|
420
|
-
.filter(nullifier => nullifier !== undefined);
|
|
529
|
+
}).filter((nullifier)=>nullifier !== undefined);
|
|
421
530
|
const nullifiedNotes = await this.db.removeNullifiedNotes(foundNullifiers, await recipient.toAddressPoint());
|
|
422
|
-
nullifiedNotes.forEach(noteDao
|
|
531
|
+
nullifiedNotes.forEach((noteDao)=>{
|
|
423
532
|
this.log.verbose(`Removed note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`, {
|
|
424
533
|
contract: noteDao.contractAddress,
|
|
425
534
|
slot: noteDao.storageSlot,
|
|
426
|
-
nullifier: noteDao.siloedNullifier.toString()
|
|
535
|
+
nullifier: noteDao.siloedNullifier.toString()
|
|
427
536
|
});
|
|
428
537
|
});
|
|
429
538
|
}
|
|
@@ -444,7 +553,9 @@ export class SimulatorOracle {
|
|
|
444
553
|
// locally synced block number which *should* be recent enough to be available. We avoid querying at 'latest' since
|
|
445
554
|
// we want to avoid accidentally processing notes that only exist ahead in time of the locally synced state.
|
|
446
555
|
const syncedBlockNumber = await this.db.getBlockNumber();
|
|
447
|
-
const uniqueNoteHashTreeIndex = (await this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NOTE_HASH_TREE, [
|
|
556
|
+
const uniqueNoteHashTreeIndex = (await this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NOTE_HASH_TREE, [
|
|
557
|
+
uniqueNoteHash
|
|
558
|
+
]))[0];
|
|
448
559
|
if (uniqueNoteHashTreeIndex === undefined) {
|
|
449
560
|
throw new Error(`Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present on the tree at block ${syncedBlockNumber} (from tx ${txHash})`);
|
|
450
561
|
}
|
|
@@ -466,12 +577,11 @@ export class SimulatorOracle {
|
|
|
466
577
|
txHash.toString(),
|
|
467
578
|
toBoundedVec(noteHashes, MAX_NOTE_HASHES_PER_TX),
|
|
468
579
|
firstNullifier,
|
|
469
|
-
recipient
|
|
580
|
+
recipient
|
|
470
581
|
]),
|
|
471
|
-
returnTypes: artifact.returnTypes
|
|
582
|
+
returnTypes: artifact.returnTypes
|
|
472
583
|
};
|
|
473
|
-
await (simulator ??
|
|
474
|
-
getAcirSimulator(this.db, this.aztecNode, this.keyStore, this.simulationProvider, this.contractDataOracle)).runUnconstrained(execRequest, artifact, contractAddress, []);
|
|
584
|
+
await (simulator ?? getAcirSimulator(this.db, this.aztecNode, this.keyStore, this.simulationProvider, this.contractDataOracle)).runUnconstrained(execRequest, artifact, contractAddress, []);
|
|
475
585
|
}
|
|
476
586
|
dbStore(contractAddress, slot, values) {
|
|
477
587
|
return this.db.dbStore(contractAddress, slot, values);
|
|
@@ -486,84 +596,9 @@ export class SimulatorOracle {
|
|
|
486
596
|
return this.db.dbCopy(contractAddress, srcSlot, dstSlot, numEntries);
|
|
487
597
|
}
|
|
488
598
|
}
|
|
489
|
-
_SimulatorOracle_instances = new WeakSet(), _SimulatorOracle_findLeafIndex = async function _SimulatorOracle_findLeafIndex(blockNumber, treeId, leafValue) {
|
|
490
|
-
const [leafIndex] = await this.aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]);
|
|
491
|
-
return leafIndex;
|
|
492
|
-
}, _SimulatorOracle_getSiblingPath = async function _SimulatorOracle_getSiblingPath(blockNumber, treeId, leafIndex) {
|
|
493
|
-
switch (treeId) {
|
|
494
|
-
case MerkleTreeId.NULLIFIER_TREE:
|
|
495
|
-
return (await this.aztecNode.getNullifierSiblingPath(blockNumber, leafIndex)).toFields();
|
|
496
|
-
case MerkleTreeId.NOTE_HASH_TREE:
|
|
497
|
-
return (await this.aztecNode.getNoteHashSiblingPath(blockNumber, leafIndex)).toFields();
|
|
498
|
-
case MerkleTreeId.PUBLIC_DATA_TREE:
|
|
499
|
-
return (await this.aztecNode.getPublicDataSiblingPath(blockNumber, leafIndex)).toFields();
|
|
500
|
-
case MerkleTreeId.ARCHIVE:
|
|
501
|
-
return (await this.aztecNode.getArchiveSiblingPath(blockNumber, leafIndex)).toFields();
|
|
502
|
-
default:
|
|
503
|
-
throw new Error('Not implemented');
|
|
504
|
-
}
|
|
505
|
-
}, _SimulatorOracle_calculateAppTaggingSecret = async function _SimulatorOracle_calculateAppTaggingSecret(contractAddress, sender, recipient) {
|
|
506
|
-
const senderCompleteAddress = await this.getCompleteAddress(sender);
|
|
507
|
-
const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
|
|
508
|
-
const secretPoint = await computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
|
|
509
|
-
// Silo the secret so it can't be used to track other app's notes
|
|
510
|
-
const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]);
|
|
511
|
-
return appSecret;
|
|
512
|
-
}, _SimulatorOracle_getIndexedTaggingSecretsForSenders =
|
|
513
|
-
/**
|
|
514
|
-
* Returns the indexed tagging secrets for a given recipient and all the senders in the address book
|
|
515
|
-
* This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration
|
|
516
|
-
* of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment,
|
|
517
|
-
* so we're keeping it private for now.
|
|
518
|
-
* @param contractAddress - The contract address to silo the secret for
|
|
519
|
-
* @param recipient - The address receiving the notes
|
|
520
|
-
* @returns A list of indexed tagging secrets
|
|
521
|
-
*/
|
|
522
|
-
async function _SimulatorOracle_getIndexedTaggingSecretsForSenders(contractAddress, recipient) {
|
|
523
|
-
const recipientCompleteAddress = await this.getCompleteAddress(recipient);
|
|
524
|
-
const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient);
|
|
525
|
-
// We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves
|
|
526
|
-
// (recipient = us, sender = us)
|
|
527
|
-
const senders = [...(await this.db.getSenderAddresses()), ...(await this.keyStore.getAccounts())].filter((address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address)));
|
|
528
|
-
const appTaggingSecrets = await Promise.all(senders.map(async (contact) => {
|
|
529
|
-
const sharedSecret = await computeTaggingSecretPoint(recipientCompleteAddress, recipientIvsk, contact);
|
|
530
|
-
return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
|
|
531
|
-
}));
|
|
532
|
-
const indexes = await this.db.getTaggingSecretsIndexesAsRecipient(appTaggingSecrets);
|
|
533
|
-
return appTaggingSecrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i]));
|
|
534
|
-
}, _SimulatorOracle_decryptTaggedLogs =
|
|
535
|
-
/**
|
|
536
|
-
* Decrypts logs tagged for a recipient and returns them.
|
|
537
|
-
* @param scopedLogs - The logs to decrypt.
|
|
538
|
-
* @param recipient - The recipient of the logs.
|
|
539
|
-
* @returns The decrypted notes.
|
|
540
|
-
*/
|
|
541
|
-
async function _SimulatorOracle_decryptTaggedLogs(scopedLogs, recipient) {
|
|
542
|
-
const recipientCompleteAddress = await this.getCompleteAddress(recipient);
|
|
543
|
-
const ivskM = await this.keyStore.getMasterSecretKey(recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey);
|
|
544
|
-
const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM);
|
|
545
|
-
// Since we could have notes with the same index for different txs, we need
|
|
546
|
-
// to keep track of them scoping by txHash
|
|
547
|
-
const excludedIndices = new Map();
|
|
548
|
-
const decrypted = [];
|
|
549
|
-
for (const scopedLog of scopedLogs) {
|
|
550
|
-
const payload = scopedLog.isFromPublic
|
|
551
|
-
? await L1NotePayload.decryptAsIncomingFromPublic(PublicLog.fromBuffer(scopedLog.logData), addressSecret)
|
|
552
|
-
: await L1NotePayload.decryptAsIncoming(PrivateLog.fromBuffer(scopedLog.logData), addressSecret);
|
|
553
|
-
if (!payload) {
|
|
554
|
-
this.log.verbose('Unable to decrypt log');
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
if (!excludedIndices.has(scopedLog.txHash.toString())) {
|
|
558
|
-
excludedIndices.set(scopedLog.txHash.toString(), new Set());
|
|
559
|
-
}
|
|
560
|
-
const note = await getOrderedNoteItems(this.db, payload);
|
|
561
|
-
const plaintext = [payload.storageSlot, payload.noteTypeId.toField(), ...note.items];
|
|
562
|
-
decrypted.push({ plaintext, txHash: scopedLog.txHash, contractAddress: payload.contractAddress });
|
|
563
|
-
}
|
|
564
|
-
return decrypted;
|
|
565
|
-
};
|
|
566
599
|
function toBoundedVec(array, maxLength) {
|
|
567
|
-
return {
|
|
600
|
+
return {
|
|
601
|
+
storage: array.concat(Array(maxLength - array.length).fill(new Fr(0))),
|
|
602
|
+
len: array.length
|
|
603
|
+
};
|
|
568
604
|
}
|
|
569
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2ltdWxhdG9yX29yYWNsZS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLE9BQU8sRUFJTCxhQUFhLEVBR2IsWUFBWSxFQUNaLElBQUksRUFJSixNQUFNLEVBRU4sbUNBQW1DLEdBQ3BDLE1BQU0sc0JBQXNCLENBQUM7QUFDOUIsT0FBTyxFQUtMLEVBQUUsRUFDRixnQkFBZ0IsRUFDaEIsb0JBQW9CLEVBR3BCLHNCQUFzQixFQUN0QiwwQkFBMEIsRUFDMUIsVUFBVSxFQUNWLFNBQVMsRUFDVCxvQkFBb0IsRUFDcEIseUJBQXlCLEdBQzFCLE1BQU0sb0JBQW9CLENBQUM7QUFDNUIsT0FBTyxFQUFFLHFCQUFxQixFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM3RixPQUFPLEVBRUwsWUFBWSxFQUNaLFlBQVksRUFDWixlQUFlLEVBQ2YsbUJBQW1CLEdBQ3BCLE1BQU0sdUJBQXVCLENBQUM7QUFDL0IsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzdELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFckQsT0FBTyxFQUdMLHVCQUF1QixHQUV4QixNQUFNLHlCQUF5QixDQUFDO0FBRWpDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBRXRFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNsRCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSwwREFBMEQsQ0FBQztBQUMvRixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsb0NBQW9DLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxv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
|