@aztec/pxe 0.23.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/config/index.js +2 -2
- package/dest/database/deferred_note_dao.d.ts +8 -4
- package/dest/database/deferred_note_dao.d.ts.map +1 -1
- package/dest/database/deferred_note_dao.js +9 -6
- package/dest/database/note_dao.d.ts +4 -0
- package/dest/database/note_dao.d.ts.map +1 -1
- package/dest/database/note_dao.js +7 -2
- package/dest/database/pxe_database_test_suite.js +5 -5
- package/dest/kernel_oracle/index.d.ts +1 -0
- package/dest/kernel_oracle/index.d.ts.map +1 -1
- package/dest/kernel_oracle/index.js +4 -1
- package/dest/kernel_prover/hints_builder.d.ts +36 -0
- package/dest/kernel_prover/hints_builder.d.ts.map +1 -0
- package/dest/kernel_prover/hints_builder.js +115 -0
- package/dest/kernel_prover/kernel_prover.d.ts +2 -24
- package/dest/kernel_prover/kernel_prover.d.ts.map +1 -1
- package/dest/kernel_prover/kernel_prover.js +32 -103
- package/dest/kernel_prover/proof_creator.d.ts +11 -11
- package/dest/kernel_prover/proof_creator.d.ts.map +1 -1
- package/dest/kernel_prover/proof_creator.js +6 -6
- package/dest/kernel_prover/proving_data_oracle.d.ts +2 -0
- package/dest/kernel_prover/proving_data_oracle.d.ts.map +1 -1
- package/dest/note_processor/note_processor.d.ts.map +1 -1
- package/dest/note_processor/note_processor.js +15 -14
- package/dest/note_processor/produce_note_dao.d.ts +2 -2
- package/dest/note_processor/produce_note_dao.d.ts.map +1 -1
- package/dest/note_processor/produce_note_dao.js +8 -8
- package/dest/pxe_http/pxe_http_server.d.ts.map +1 -1
- package/dest/pxe_http/pxe_http_server.js +7 -6
- package/dest/pxe_service/create_pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/create_pxe_service.js +5 -1
- package/dest/pxe_service/pxe_service.d.ts +5 -3
- package/dest/pxe_service/pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/pxe_service.js +51 -44
- package/dest/pxe_service/test/pxe_test_suite.d.ts.map +1 -1
- package/dest/pxe_service/test/pxe_test_suite.js +3 -3
- package/dest/simulator_oracle/index.d.ts +7 -4
- package/dest/simulator_oracle/index.d.ts.map +1 -1
- package/dest/simulator_oracle/index.js +17 -8
- package/dest/synchronizer/synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/synchronizer.js +14 -42
- package/package.json +14 -13
- package/src/bin/index.ts +42 -0
- package/src/config/index.ts +40 -0
- package/src/contract_data_oracle/index.ts +185 -0
- package/src/contract_data_oracle/private_functions_tree.ts +127 -0
- package/src/contract_database/index.ts +1 -0
- package/src/contract_database/memory_contract_database.ts +58 -0
- package/src/database/contracts/contract_artifact_db.ts +19 -0
- package/src/database/contracts/contract_instance_db.ts +18 -0
- package/src/database/deferred_note_dao.ts +55 -0
- package/src/database/index.ts +1 -0
- package/src/database/kv_pxe_database.ts +409 -0
- package/src/database/note_dao.ts +97 -0
- package/src/database/pxe_database.ts +162 -0
- package/src/database/pxe_database_test_suite.ts +258 -0
- package/src/index.ts +11 -0
- package/src/kernel_oracle/index.ts +65 -0
- package/src/kernel_prover/hints_builder.ts +170 -0
- package/src/kernel_prover/index.ts +2 -0
- package/src/kernel_prover/kernel_prover.ts +309 -0
- package/src/kernel_prover/proof_creator.ts +157 -0
- package/src/kernel_prover/proving_data_oracle.ts +79 -0
- package/src/note_processor/index.ts +1 -0
- package/src/note_processor/note_processor.ts +275 -0
- package/src/note_processor/produce_note_dao.ts +132 -0
- package/src/pxe_http/index.ts +1 -0
- package/src/pxe_http/pxe_http_server.ts +75 -0
- package/src/pxe_service/create_pxe_service.ts +53 -0
- package/src/pxe_service/index.ts +3 -0
- package/src/pxe_service/pxe_service.ts +769 -0
- package/src/pxe_service/test/pxe_test_suite.ts +140 -0
- package/src/simulator/index.ts +24 -0
- package/src/simulator_oracle/index.ts +222 -0
- package/src/synchronizer/index.ts +1 -0
- package/src/synchronizer/synchronizer.ts +385 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { NoteFilter, NoteStatus, randomTxHash } from '@aztec/circuit-types';
|
|
2
|
+
import { AztecAddress, CompleteAddress, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js';
|
|
3
|
+
import { makeHeader } from '@aztec/circuits.js/testing';
|
|
4
|
+
import { Fr, Point } from '@aztec/foundation/fields';
|
|
5
|
+
import { BenchmarkingContractArtifact } from '@aztec/noir-contracts.js/Benchmarking';
|
|
6
|
+
import { SerializableContractInstance } from '@aztec/types/contracts';
|
|
7
|
+
|
|
8
|
+
import { NoteDao } from './note_dao.js';
|
|
9
|
+
import { randomNoteDao } from './note_dao.test.js';
|
|
10
|
+
import { PxeDatabase } from './pxe_database.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A common test suite for a PXE database.
|
|
14
|
+
* @param getDatabase - A function that returns a database instance.
|
|
15
|
+
*/
|
|
16
|
+
export function describePxeDatabase(getDatabase: () => PxeDatabase) {
|
|
17
|
+
let database: PxeDatabase;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
database = getDatabase();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Database', () => {
|
|
24
|
+
describe('auth witnesses', () => {
|
|
25
|
+
it('stores and retrieves auth witnesses', async () => {
|
|
26
|
+
const messageHash = Fr.random();
|
|
27
|
+
const witness = [Fr.random(), Fr.random()];
|
|
28
|
+
|
|
29
|
+
await database.addAuthWitness(messageHash, witness);
|
|
30
|
+
await expect(database.getAuthWitness(messageHash)).resolves.toEqual(witness);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns undefined if it doesn't have auth witnesses for the message", async () => {
|
|
34
|
+
const messageHash = Fr.random();
|
|
35
|
+
await expect(database.getAuthWitness(messageHash)).resolves.toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it.skip('refuses to overwrite auth witnesses for the same message', async () => {
|
|
39
|
+
const messageHash = Fr.random();
|
|
40
|
+
const witness = [Fr.random(), Fr.random()];
|
|
41
|
+
|
|
42
|
+
await database.addAuthWitness(messageHash, witness);
|
|
43
|
+
await expect(database.addAuthWitness(messageHash, witness)).rejects.toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('capsules', () => {
|
|
48
|
+
it('stores and retrieves capsules', async () => {
|
|
49
|
+
const capsule = [Fr.random(), Fr.random()];
|
|
50
|
+
|
|
51
|
+
await database.addCapsule(capsule);
|
|
52
|
+
await expect(database.popCapsule()).resolves.toEqual(capsule);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns undefined if it doesn't have capsules", async () => {
|
|
56
|
+
await expect(database.popCapsule()).resolves.toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('behaves like a stack when storing capsules', async () => {
|
|
60
|
+
const capsule1 = [Fr.random(), Fr.random()];
|
|
61
|
+
const capsule2 = [Fr.random(), Fr.random()];
|
|
62
|
+
|
|
63
|
+
await database.addCapsule(capsule1);
|
|
64
|
+
await database.addCapsule(capsule2);
|
|
65
|
+
await expect(database.popCapsule()).resolves.toEqual(capsule2);
|
|
66
|
+
await expect(database.popCapsule()).resolves.toEqual(capsule1);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('notes', () => {
|
|
71
|
+
let owners: CompleteAddress[];
|
|
72
|
+
let contractAddresses: AztecAddress[];
|
|
73
|
+
let storageSlots: Fr[];
|
|
74
|
+
let notes: NoteDao[];
|
|
75
|
+
|
|
76
|
+
const filteringTests: [() => NoteFilter, () => NoteDao[]][] = [
|
|
77
|
+
[() => ({}), () => notes],
|
|
78
|
+
|
|
79
|
+
[
|
|
80
|
+
() => ({ contractAddress: contractAddresses[0] }),
|
|
81
|
+
() => notes.filter(note => note.contractAddress.equals(contractAddresses[0])),
|
|
82
|
+
],
|
|
83
|
+
[() => ({ contractAddress: AztecAddress.random() }), () => []],
|
|
84
|
+
|
|
85
|
+
[
|
|
86
|
+
() => ({ storageSlot: storageSlots[0] }),
|
|
87
|
+
() => notes.filter(note => note.storageSlot.equals(storageSlots[0])),
|
|
88
|
+
],
|
|
89
|
+
[() => ({ storageSlot: Fr.random() }), () => []],
|
|
90
|
+
|
|
91
|
+
[() => ({ txHash: notes[0].txHash }), () => [notes[0]]],
|
|
92
|
+
[() => ({ txHash: randomTxHash() }), () => []],
|
|
93
|
+
|
|
94
|
+
[() => ({ owner: owners[0].address }), () => notes.filter(note => note.publicKey.equals(owners[0].publicKey))],
|
|
95
|
+
|
|
96
|
+
[
|
|
97
|
+
() => ({ contractAddress: contractAddresses[0], storageSlot: storageSlots[0] }),
|
|
98
|
+
() =>
|
|
99
|
+
notes.filter(
|
|
100
|
+
note => note.contractAddress.equals(contractAddresses[0]) && note.storageSlot.equals(storageSlots[0]),
|
|
101
|
+
),
|
|
102
|
+
],
|
|
103
|
+
[() => ({ contractAddress: contractAddresses[0], storageSlot: storageSlots[1] }), () => []],
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
owners = Array.from({ length: 2 }).map(() => CompleteAddress.random());
|
|
108
|
+
contractAddresses = Array.from({ length: 2 }).map(() => AztecAddress.random());
|
|
109
|
+
storageSlots = Array.from({ length: 2 }).map(() => Fr.random());
|
|
110
|
+
|
|
111
|
+
notes = Array.from({ length: 10 }).map((_, i) =>
|
|
112
|
+
randomNoteDao({
|
|
113
|
+
contractAddress: contractAddresses[i % contractAddresses.length],
|
|
114
|
+
storageSlot: storageSlots[i % storageSlots.length],
|
|
115
|
+
publicKey: owners[i % owners.length].publicKey,
|
|
116
|
+
index: BigInt(i),
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
beforeEach(async () => {
|
|
122
|
+
for (const owner of owners) {
|
|
123
|
+
await database.addCompleteAddress(owner);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it.each(filteringTests)('stores notes in bulk and retrieves notes', async (getFilter, getExpected) => {
|
|
128
|
+
await database.addNotes(notes);
|
|
129
|
+
await expect(database.getNotes(getFilter())).resolves.toEqual(getExpected());
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it.each(filteringTests)('stores notes one by one and retrieves notes', async (getFilter, getExpected) => {
|
|
133
|
+
for (const note of notes) {
|
|
134
|
+
await database.addNote(note);
|
|
135
|
+
}
|
|
136
|
+
await expect(database.getNotes(getFilter())).resolves.toEqual(getExpected());
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it.each(filteringTests)('retrieves nullified notes', async (getFilter, getExpected) => {
|
|
140
|
+
await database.addNotes(notes);
|
|
141
|
+
|
|
142
|
+
// Nullify all notes and use the same filter as other test cases
|
|
143
|
+
for (const owner of owners) {
|
|
144
|
+
const notesToNullify = notes.filter(note => note.publicKey.equals(owner.publicKey));
|
|
145
|
+
const nullifiers = notesToNullify.map(note => note.siloedNullifier);
|
|
146
|
+
await expect(database.removeNullifiedNotes(nullifiers, owner.publicKey)).resolves.toEqual(notesToNullify);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await expect(database.getNotes({ ...getFilter(), status: NoteStatus.ACTIVE_OR_NULLIFIED })).resolves.toEqual(
|
|
150
|
+
getExpected(),
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('skips nullified notes by default or when requesting active', async () => {
|
|
155
|
+
await database.addNotes(notes);
|
|
156
|
+
|
|
157
|
+
const notesToNullify = notes.filter(note => note.publicKey.equals(owners[0].publicKey));
|
|
158
|
+
const nullifiers = notesToNullify.map(note => note.siloedNullifier);
|
|
159
|
+
await expect(database.removeNullifiedNotes(nullifiers, notesToNullify[0].publicKey)).resolves.toEqual(
|
|
160
|
+
notesToNullify,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const actualNotesWithDefault = await database.getNotes({});
|
|
164
|
+
const actualNotesWithActive = await database.getNotes({ status: NoteStatus.ACTIVE });
|
|
165
|
+
|
|
166
|
+
expect(actualNotesWithDefault).toEqual(actualNotesWithActive);
|
|
167
|
+
expect(actualNotesWithActive).toEqual(notes.filter(note => !notesToNullify.includes(note)));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('returns active and nullified notes when requesting either', async () => {
|
|
171
|
+
await database.addNotes(notes);
|
|
172
|
+
|
|
173
|
+
const notesToNullify = notes.filter(note => note.publicKey.equals(owners[0].publicKey));
|
|
174
|
+
const nullifiers = notesToNullify.map(note => note.siloedNullifier);
|
|
175
|
+
await expect(database.removeNullifiedNotes(nullifiers, notesToNullify[0].publicKey)).resolves.toEqual(
|
|
176
|
+
notesToNullify,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const result = await database.getNotes({
|
|
180
|
+
status: NoteStatus.ACTIVE_OR_NULLIFIED,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// We have to compare the sorted arrays since the database does not return the same order as when originally
|
|
184
|
+
// inserted combining active and nullified results.
|
|
185
|
+
expect(result.sort()).toEqual([...notes].sort());
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('block header', () => {
|
|
190
|
+
it('stores and retrieves the block header', async () => {
|
|
191
|
+
const header = makeHeader(Math.floor(Math.random() * 1000), INITIAL_L2_BLOCK_NUM);
|
|
192
|
+
|
|
193
|
+
await database.setHeader(header);
|
|
194
|
+
expect(database.getHeader()).toEqual(header);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('rejects getting header if no block set', () => {
|
|
198
|
+
expect(() => database.getHeader()).toThrow();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('addresses', () => {
|
|
203
|
+
it('stores and retrieves addresses', async () => {
|
|
204
|
+
const address = CompleteAddress.random();
|
|
205
|
+
await expect(database.addCompleteAddress(address)).resolves.toBe(true);
|
|
206
|
+
await expect(database.getCompleteAddress(address.address)).resolves.toEqual(address);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('silently ignores an address it already knows about', async () => {
|
|
210
|
+
const address = CompleteAddress.random();
|
|
211
|
+
await expect(database.addCompleteAddress(address)).resolves.toBe(true);
|
|
212
|
+
await expect(database.addCompleteAddress(address)).resolves.toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it.skip('refuses to overwrite an address with a different public key', async () => {
|
|
216
|
+
const address = CompleteAddress.random();
|
|
217
|
+
const otherAddress = new CompleteAddress(address.address, Point.random(), address.partialAddress);
|
|
218
|
+
|
|
219
|
+
await database.addCompleteAddress(address);
|
|
220
|
+
await expect(database.addCompleteAddress(otherAddress)).rejects.toThrow();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('returns all addresses', async () => {
|
|
224
|
+
const addresses = Array.from({ length: 10 }).map(() => CompleteAddress.random());
|
|
225
|
+
for (const address of addresses) {
|
|
226
|
+
await database.addCompleteAddress(address);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const result = await database.getCompleteAddresses();
|
|
230
|
+
expect(result).toEqual(expect.arrayContaining(addresses));
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("returns an empty array if it doesn't have addresses", async () => {
|
|
234
|
+
expect(await database.getCompleteAddresses()).toEqual([]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("returns undefined if it doesn't have an address", async () => {
|
|
238
|
+
expect(await database.getCompleteAddress(CompleteAddress.random().address)).toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('contracts', () => {
|
|
243
|
+
it('stores a contract artifact', async () => {
|
|
244
|
+
const artifact = BenchmarkingContractArtifact;
|
|
245
|
+
const id = Fr.random();
|
|
246
|
+
await database.addContractArtifact(id, artifact);
|
|
247
|
+
await expect(database.getContractArtifact(id)).resolves.toEqual(artifact);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('stores a contract instance', async () => {
|
|
251
|
+
const address = AztecAddress.random();
|
|
252
|
+
const instance = SerializableContractInstance.random().withAddress(address);
|
|
253
|
+
await database.addContractInstance(instance);
|
|
254
|
+
await expect(database.getContractInstance(address)).resolves.toEqual(instance);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './pxe_service/index.js';
|
|
2
|
+
export * from './pxe_http/index.js';
|
|
3
|
+
export * from './config/index.js';
|
|
4
|
+
|
|
5
|
+
export { Tx, TxHash } from '@aztec/circuit-types';
|
|
6
|
+
|
|
7
|
+
export { TxRequest, PartialAddress } from '@aztec/circuits.js';
|
|
8
|
+
export * from '@aztec/foundation/fields';
|
|
9
|
+
export * from '@aztec/foundation/eth-address';
|
|
10
|
+
export * from '@aztec/foundation/aztec-address';
|
|
11
|
+
export * from '@aztec/key-store';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AztecNode, KeyStore } from '@aztec/circuit-types';
|
|
2
|
+
import {
|
|
3
|
+
AztecAddress,
|
|
4
|
+
Fr,
|
|
5
|
+
FunctionSelector,
|
|
6
|
+
MembershipWitness,
|
|
7
|
+
NOTE_HASH_TREE_HEIGHT,
|
|
8
|
+
Point,
|
|
9
|
+
computeContractClassIdPreimage,
|
|
10
|
+
computeSaltedInitializationHash,
|
|
11
|
+
} from '@aztec/circuits.js';
|
|
12
|
+
import { Tuple } from '@aztec/foundation/serialize';
|
|
13
|
+
|
|
14
|
+
import { ContractDataOracle } from '../contract_data_oracle/index.js';
|
|
15
|
+
import { ProvingDataOracle } from './../kernel_prover/proving_data_oracle.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A data oracle that provides information needed for simulating a transaction.
|
|
19
|
+
*/
|
|
20
|
+
export class KernelOracle implements ProvingDataOracle {
|
|
21
|
+
constructor(private contractDataOracle: ContractDataOracle, private keyStore: KeyStore, private node: AztecNode) {}
|
|
22
|
+
|
|
23
|
+
public async getContractAddressPreimage(address: AztecAddress) {
|
|
24
|
+
const instance = await this.contractDataOracle.getContractInstance(address);
|
|
25
|
+
return {
|
|
26
|
+
saltedInitializationHash: computeSaltedInitializationHash(instance),
|
|
27
|
+
...instance,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async getContractClassIdPreimage(contractClassId: Fr) {
|
|
32
|
+
const contractClass = await this.contractDataOracle.getContractClass(contractClassId);
|
|
33
|
+
return computeContractClassIdPreimage(contractClass);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async getFunctionMembershipWitness(contractAddress: AztecAddress, selector: FunctionSelector) {
|
|
37
|
+
return await this.contractDataOracle.getFunctionMembershipWitness(contractAddress, selector);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async getVkMembershipWitness() {
|
|
41
|
+
return await this.contractDataOracle.getVkMembershipWitness();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getNoteMembershipWitness(leafIndex: bigint): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT>> {
|
|
45
|
+
const path = await this.node.getNoteHashSiblingPath('latest', leafIndex);
|
|
46
|
+
return new MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT>(
|
|
47
|
+
path.pathSize,
|
|
48
|
+
leafIndex,
|
|
49
|
+
path.toFields() as Tuple<Fr, typeof NOTE_HASH_TREE_HEIGHT>,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getNullifierMembershipWitness(blockNumber: number, nullifier: Fr) {
|
|
54
|
+
return this.node.getNullifierMembershipWitness(blockNumber, nullifier);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getNoteHashTreeRoot(): Promise<Fr> {
|
|
58
|
+
const header = await this.node.getHeader();
|
|
59
|
+
return header.state.partial.noteHashTree.root;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public getMasterNullifierSecretKey(nullifierPublicKey: Point) {
|
|
63
|
+
return this.keyStore.getNullifierSecretKeyFromPublicKey(nullifierPublicKey);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Fr,
|
|
3
|
+
GrumpkinScalar,
|
|
4
|
+
MAX_NEW_NOTE_HASHES_PER_TX,
|
|
5
|
+
MAX_NEW_NULLIFIERS_PER_TX,
|
|
6
|
+
MAX_NOTE_HASH_READ_REQUESTS_PER_TX,
|
|
7
|
+
MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX,
|
|
8
|
+
MAX_NULLIFIER_READ_REQUESTS_PER_TX,
|
|
9
|
+
MembershipWitness,
|
|
10
|
+
NULLIFIER_TREE_HEIGHT,
|
|
11
|
+
NullifierKeyValidationRequestContext,
|
|
12
|
+
NullifierReadRequestResetHintsBuilder,
|
|
13
|
+
ReadRequestContext,
|
|
14
|
+
SideEffect,
|
|
15
|
+
SideEffectLinkedToNoteHash,
|
|
16
|
+
SideEffectType,
|
|
17
|
+
} from '@aztec/circuits.js';
|
|
18
|
+
import { siloNullifier } from '@aztec/circuits.js/hash';
|
|
19
|
+
import { makeTuple } from '@aztec/foundation/array';
|
|
20
|
+
import { Tuple } from '@aztec/foundation/serialize';
|
|
21
|
+
|
|
22
|
+
import { ProvingDataOracle } from './proving_data_oracle.js';
|
|
23
|
+
|
|
24
|
+
export class HintsBuilder {
|
|
25
|
+
constructor(private oracle: ProvingDataOracle) {}
|
|
26
|
+
|
|
27
|
+
sortSideEffects<T extends SideEffectType, K extends number>(
|
|
28
|
+
sideEffects: Tuple<T, K>,
|
|
29
|
+
): [Tuple<T, K>, Tuple<number, K>] {
|
|
30
|
+
const sorted = sideEffects
|
|
31
|
+
.map((sideEffect, index) => ({ sideEffect, index }))
|
|
32
|
+
.sort((a, b) => {
|
|
33
|
+
// Empty ones go to the right
|
|
34
|
+
if (a.sideEffect.isEmpty()) {
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
return Number(a.sideEffect.counter.toBigInt() - b.sideEffect.counter.toBigInt());
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const originalToSorted = sorted.map(() => 0);
|
|
41
|
+
sorted.forEach(({ index }, i) => {
|
|
42
|
+
originalToSorted[index] = i;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return [sorted.map(({ sideEffect }) => sideEffect) as Tuple<T, K>, originalToSorted as Tuple<number, K>];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Performs the matching between an array of read request and an array of note hashes. This produces
|
|
50
|
+
* hints for the private kernel tail circuit to efficiently match a read request with the corresponding
|
|
51
|
+
* note hash. Several read requests might be pointing to the same note hash. It is therefore valid
|
|
52
|
+
* to return more than one hint with the same index (contrary to getNullifierHints).
|
|
53
|
+
*
|
|
54
|
+
* @param noteHashReadRequests - The array of read requests.
|
|
55
|
+
* @param noteHashes - The array of note hashes.
|
|
56
|
+
* @returns An array of hints where each element is the index of the note hash in note hashes array
|
|
57
|
+
* corresponding to the read request. In other words we have readRequests[i] == noteHashes[hints[i]].
|
|
58
|
+
*/
|
|
59
|
+
getNoteHashReadRequestHints(
|
|
60
|
+
noteHashReadRequests: Tuple<SideEffect, typeof MAX_NOTE_HASH_READ_REQUESTS_PER_TX>,
|
|
61
|
+
noteHashes: Tuple<SideEffect, typeof MAX_NEW_NOTE_HASHES_PER_TX>,
|
|
62
|
+
): Tuple<Fr, typeof MAX_NOTE_HASH_READ_REQUESTS_PER_TX> {
|
|
63
|
+
const hints = makeTuple(MAX_NOTE_HASH_READ_REQUESTS_PER_TX, Fr.zero);
|
|
64
|
+
for (let i = 0; i < MAX_NOTE_HASH_READ_REQUESTS_PER_TX && !noteHashReadRequests[i].isEmpty(); i++) {
|
|
65
|
+
const equalToRR = (cmt: SideEffect) => cmt.value.equals(noteHashReadRequests[i].value);
|
|
66
|
+
const result = noteHashes.findIndex(equalToRR);
|
|
67
|
+
if (result == -1) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`The read request at index ${i} ${noteHashReadRequests[i].toString()} does not match to any note hash.`,
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
hints[i] = new Fr(result);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return hints;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getNullifierReadRequestResetHints(
|
|
79
|
+
nullifierReadRequests: Tuple<ReadRequestContext, typeof MAX_NULLIFIER_READ_REQUESTS_PER_TX>,
|
|
80
|
+
nullifiers: Tuple<SideEffectLinkedToNoteHash, typeof MAX_NEW_NULLIFIERS_PER_TX>,
|
|
81
|
+
) {
|
|
82
|
+
// TODO - Should be comparing un-siloed values and contract addresses.
|
|
83
|
+
const builder = new NullifierReadRequestResetHintsBuilder();
|
|
84
|
+
const nullifierIndexMap: Map<bigint, number> = new Map();
|
|
85
|
+
nullifiers.forEach((n, i) => nullifierIndexMap.set(n.value.toBigInt(), i));
|
|
86
|
+
const siloedReadRequestValues = nullifierReadRequests.map(r =>
|
|
87
|
+
r.isEmpty() ? Fr.ZERO : siloNullifier(r.contractAddress, r.value),
|
|
88
|
+
);
|
|
89
|
+
for (let i = 0; i < nullifierReadRequests.length; ++i) {
|
|
90
|
+
const value = siloedReadRequestValues[i];
|
|
91
|
+
if (value.isZero()) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
const pendingValueIndex = nullifierIndexMap.get(value.toBigInt());
|
|
95
|
+
if (pendingValueIndex !== undefined) {
|
|
96
|
+
builder.addPendingReadRequest(i, pendingValueIndex);
|
|
97
|
+
} else {
|
|
98
|
+
const membershipWitness = await this.oracle.getNullifierMembershipWitness(0, value);
|
|
99
|
+
if (!membershipWitness) {
|
|
100
|
+
throw new Error('Read request is reading an unknown nullifier value.');
|
|
101
|
+
}
|
|
102
|
+
builder.addSettledReadRequest(
|
|
103
|
+
i,
|
|
104
|
+
new MembershipWitness(
|
|
105
|
+
NULLIFIER_TREE_HEIGHT,
|
|
106
|
+
membershipWitness.index,
|
|
107
|
+
membershipWitness.siblingPath.toTuple<typeof NULLIFIER_TREE_HEIGHT>(),
|
|
108
|
+
),
|
|
109
|
+
membershipWitness.leafPreimage,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return builder.toHints();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Performs the matching between an array of nullified note hashes and an array of note hashes. This produces
|
|
118
|
+
* hints for the private kernel tail circuit to efficiently match a nullifier with the corresponding
|
|
119
|
+
* note hash. Note that the same note hash value might appear more than once in the note hashes
|
|
120
|
+
* (resp. nullified note hashes) array. It is crucial in this case that each hint points to a different index
|
|
121
|
+
* of the nullified note hashes array. Otherwise, the private kernel will fail to validate.
|
|
122
|
+
*
|
|
123
|
+
* @param nullifiedNoteHashes - The array of nullified note hashes.
|
|
124
|
+
* @param noteHashes - The array of note hashes.
|
|
125
|
+
* @returns An array of hints where each element is the index of the note hash in note hashes array
|
|
126
|
+
* corresponding to the nullified note hash. In other words we have nullifiedNoteHashes[i] == noteHashes[hints[i]].
|
|
127
|
+
*/
|
|
128
|
+
getNullifierHints(
|
|
129
|
+
nullifiedNoteHashes: Tuple<Fr, typeof MAX_NEW_NULLIFIERS_PER_TX>,
|
|
130
|
+
noteHashes: Tuple<SideEffect, typeof MAX_NEW_NOTE_HASHES_PER_TX>,
|
|
131
|
+
): Tuple<Fr, typeof MAX_NEW_NULLIFIERS_PER_TX> {
|
|
132
|
+
const hints = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, Fr.zero);
|
|
133
|
+
const alreadyUsed = new Set<number>();
|
|
134
|
+
for (let i = 0; i < MAX_NEW_NULLIFIERS_PER_TX; i++) {
|
|
135
|
+
if (!nullifiedNoteHashes[i].isZero()) {
|
|
136
|
+
const result = noteHashes.findIndex(
|
|
137
|
+
(cmt: SideEffect, index: number) => cmt.value.equals(nullifiedNoteHashes[i]) && !alreadyUsed.has(index),
|
|
138
|
+
);
|
|
139
|
+
alreadyUsed.add(result);
|
|
140
|
+
if (result == -1) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`The nullified note hash at index ${i} with value ${nullifiedNoteHashes[
|
|
143
|
+
i
|
|
144
|
+
].toString()} does not match to any note hash.`,
|
|
145
|
+
);
|
|
146
|
+
} else {
|
|
147
|
+
hints[i] = new Fr(result);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return hints;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async getMasterNullifierSecretKeys(
|
|
155
|
+
nullifierKeyValidationRequests: Tuple<
|
|
156
|
+
NullifierKeyValidationRequestContext,
|
|
157
|
+
typeof MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX
|
|
158
|
+
>,
|
|
159
|
+
) {
|
|
160
|
+
const keys = makeTuple(MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, GrumpkinScalar.zero);
|
|
161
|
+
for (let i = 0; i < nullifierKeyValidationRequests.length; ++i) {
|
|
162
|
+
const request = nullifierKeyValidationRequests[i];
|
|
163
|
+
if (request.isEmpty()) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
keys[i] = await this.oracle.getMasterNullifierSecretKey(request.publicKey);
|
|
167
|
+
}
|
|
168
|
+
return keys;
|
|
169
|
+
}
|
|
170
|
+
}
|