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