@aztec/bb.js 0.0.1-alpha.5 → 0.0.1-alpha.7

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.
Files changed (36) hide show
  1. package/README.md +3 -4
  2. package/dest/barretenberg-threads.wasm +0 -0
  3. package/dest/barretenberg.wasm +0 -0
  4. package/dest/barretenberg_api/index.d.ts +6 -4
  5. package/dest/barretenberg_api/index.d.ts.map +1 -1
  6. package/dest/barretenberg_api/index.js +36 -28
  7. package/dest/barretenberg_wasm/barretenberg_wasm.js +5 -5
  8. package/dest/barretenberg_wasm/barretenberg_wasm.test.js +2 -2
  9. package/dest/barretenberg_wasm/index.d.ts +1 -1
  10. package/dest/barretenberg_wasm/index.js +1 -1
  11. package/dest/barretenberg_wasm.js +1 -1
  12. package/dest/crs/browser/cached_net_crs.d.ts.map +1 -1
  13. package/dest/crs/browser/cached_net_crs.js +1 -1
  14. package/dest/crs/node/{file_crs.d.ts → ignition_files_crs.d.ts} +2 -2
  15. package/dest/crs/node/ignition_files_crs.d.ts.map +1 -0
  16. package/dest/crs/node/ignition_files_crs.js +51 -0
  17. package/dest/crs/node/index.d.ts +2 -12
  18. package/dest/crs/node/index.d.ts.map +1 -1
  19. package/dest/crs/node/index.js +28 -15
  20. package/dest/examples/simple.test.js +1 -1
  21. package/dest/main.d.ts +4 -4
  22. package/dest/main.d.ts.map +1 -1
  23. package/dest/main.js +48 -63
  24. package/dest/simple_test.js +1 -1
  25. package/package.json +9 -4
  26. package/src/barretenberg_api/index.ts +252 -64
  27. package/src/barretenberg_wasm/barretenberg_wasm.test.ts +1 -1
  28. package/src/barretenberg_wasm/barretenberg_wasm.ts +4 -4
  29. package/src/barretenberg_wasm/index.ts +1 -1
  30. package/src/crs/browser/cached_net_crs.ts +0 -1
  31. package/src/crs/node/{file_crs.ts → ignition_files_crs.ts} +2 -2
  32. package/src/crs/node/index.ts +27 -19
  33. package/src/examples/simple.test.ts +1 -1
  34. package/src/main.ts +49 -77
  35. package/dest/crs/node/file_crs.d.ts.map +0 -1
  36. package/dest/crs/node/file_crs.js +0 -51
@@ -1,33 +1,41 @@
1
- import { concatenateUint8Arrays, numToUInt32BE } from '../../serialize/serialize.js';
2
1
  import { NetCrs } from '../net_crs.js';
3
- import { FileCrs } from './file_crs.js';
2
+ import { IgnitionFilesCrs } from './ignition_files_crs.js';
3
+ import { mkdirSync, readFileSync, writeFileSync } from 'fs';
4
+ import { readFile } from 'fs/promises';
5
+ import createDebug from 'debug';
6
+
7
+ const debug = createDebug('bb.js:crs');
4
8
 
5
9
  /**
6
10
  * Generic CRS finder utility class.
7
11
  */
8
12
  export class Crs {
9
- private crs: FileCrs | NetCrs;
10
-
11
- constructor(
12
- /**
13
- * The number of circuit gates.
14
- */
15
- public readonly numPoints: number,
16
- ) {
17
- this.crs = FileCrs.defaultExists() ? new FileCrs(numPoints) : new NetCrs(numPoints);
18
- }
13
+ constructor(public readonly numPoints: number, public readonly path: string) {}
19
14
 
20
15
  static async new(numPoints: number) {
21
- const crs = new Crs(numPoints);
16
+ const crs = new Crs(numPoints, './crs');
22
17
  await crs.init();
23
18
  return crs;
24
19
  }
25
20
 
26
- /**
27
- * Read CRS from our chosen source.
28
- */
29
21
  async init() {
30
- await this.crs.init();
22
+ mkdirSync(this.path, { recursive: true });
23
+ const size = await readFile(this.path + '/size', 'ascii').catch(() => undefined);
24
+ if (size && +size >= this.numPoints) {
25
+ debug(`using cached crs of size: ${size}`);
26
+ return;
27
+ }
28
+
29
+ const crs = IgnitionFilesCrs.defaultExists() ? new IgnitionFilesCrs(this.numPoints) : new NetCrs(this.numPoints);
30
+ if (crs instanceof NetCrs) {
31
+ debug(`downloading crs of size: ${this.numPoints}`);
32
+ } else {
33
+ debug(`loading igntion file crs of size: ${this.numPoints}`);
34
+ }
35
+ await crs.init();
36
+ writeFileSync(this.path + '/size', this.numPoints.toString());
37
+ writeFileSync(this.path + '/g1.dat', crs.getG1Data());
38
+ writeFileSync(this.path + '/g2.dat', crs.getG2Data());
31
39
  }
32
40
 
33
41
  /**
@@ -35,7 +43,7 @@ export class Crs {
35
43
  * @returns The points data.
36
44
  */
37
45
  getG1Data(): Uint8Array {
38
- return this.crs.getG1Data();
46
+ return readFileSync(this.path + '/g1.dat');
39
47
  }
40
48
 
41
49
  /**
@@ -43,6 +51,6 @@ export class Crs {
43
51
  * @returns The points data.
44
52
  */
45
53
  getG2Data(): Uint8Array {
46
- return this.crs.getG2Data();
54
+ return readFileSync(this.path + '/g2.dat');
47
55
  }
48
56
  }
@@ -23,5 +23,5 @@ describe('simple', () => {
23
23
  it('should construct 512k gate proof', async () => {
24
24
  const valid = await api.examplesSimpleCreateAndVerifyProof();
25
25
  expect(valid).toBe(true);
26
- }, 60000);
26
+ }, 90000);
27
27
  });
package/src/main.ts CHANGED
@@ -12,46 +12,53 @@ const debug = createDebug('bb.js');
12
12
  // Maximum we support.
13
13
  const MAX_CIRCUIT_SIZE = 2 ** 19;
14
14
 
15
- function getBytecode(jsonPath: string) {
15
+ function getJsonData(jsonPath: string) {
16
16
  const json = readFileSync(jsonPath, 'utf-8');
17
17
  const parsed = JSON.parse(json);
18
+ return parsed;
19
+ }
20
+
21
+ function getBytecode(jsonPath: string) {
22
+ const parsed = getJsonData(jsonPath);
18
23
  const buffer = Buffer.from(parsed.bytecode, 'base64');
19
24
  const decompressed = gunzipSync(buffer);
20
25
  return decompressed;
21
26
  }
22
27
 
28
+ async function getGates(jsonPath: string, api: BarretenbergApiAsync) {
29
+ const parsed = getJsonData(jsonPath);
30
+ if (parsed.gates) {
31
+ return +parsed.gates;
32
+ }
33
+ const { total } = await computeCircuitSize(jsonPath, api);
34
+ const jsonData = getJsonData(jsonPath);
35
+ jsonData.gates = total;
36
+ writeFileSync(jsonPath, JSON.stringify(jsonData));
37
+ return total;
38
+ }
39
+
23
40
  function getWitness(witnessPath: string) {
24
41
  const data = readFileSync(witnessPath);
25
42
  return Buffer.concat([numToUInt32BE(data.length / 32), data]);
26
43
  }
27
44
 
28
- async function getCircuitSize(jsonPath: string, api: BarretenbergApiAsync) {
45
+ async function computeCircuitSize(jsonPath: string, api: BarretenbergApiAsync) {
46
+ debug(`computing circuit size...`);
29
47
  const bytecode = getBytecode(jsonPath);
30
48
  const [exact, total, subgroup] = await api.acirGetCircuitSizes(new RawBuffer(bytecode));
31
49
  return { exact, total, subgroup };
32
50
  }
33
51
 
34
- async function init(jsonPath: string, sizeHint?: number) {
52
+ async function init(jsonPath: string) {
35
53
  const api = await newBarretenbergApiAsync();
36
54
 
37
- const subgroupSize = await (async () => {
38
- if (sizeHint) {
39
- // Round to subgroup size.
40
- return Math.pow(2, Math.ceil(Math.log2(sizeHint)));
41
- }
42
-
43
- // First compute circuit size.
44
- debug(`computing circuit size (use size hint to skip)...`);
45
- const circuitSizes = await getCircuitSize(jsonPath, api);
46
- debug(`circuit size: ${circuitSizes.total}`);
47
-
48
- if (circuitSizes.subgroup > MAX_CIRCUIT_SIZE) {
49
- throw new Error(`Circuit size of ${circuitSizes.subgroup} exceeds max supported of ${MAX_CIRCUIT_SIZE}`);
50
- }
51
-
52
- return circuitSizes.subgroup;
53
- })();
55
+ const circuitSize = await getGates(jsonPath, api);
56
+ const subgroupSize = Math.pow(2, Math.ceil(Math.log2(circuitSize)));
57
+ if (subgroupSize > MAX_CIRCUIT_SIZE) {
58
+ throw new Error(`Circuit size of ${subgroupSize} exceeds max supported of ${MAX_CIRCUIT_SIZE}`);
59
+ }
54
60
 
61
+ debug(`circuit size: ${circuitSize}`);
55
62
  debug(`subgroup size: ${subgroupSize}`);
56
63
  debug('loading crs...');
57
64
  // Plus 1 needed! (Move +1 into Crs?)
@@ -64,7 +71,7 @@ async function init(jsonPath: string, sizeHint?: number) {
64
71
  // TODO: Make RawBuffer be default behaviour, and have a specific Vector type for when wanting length prefixed.
65
72
  await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data()));
66
73
 
67
- const acirComposer = await api.acirNewAcirComposer();
74
+ const acirComposer = await api.acirNewAcirComposer(subgroupSize);
68
75
  return { api, acirComposer, circuitSize: subgroupSize };
69
76
  }
70
77
 
@@ -77,24 +84,15 @@ async function initLite() {
77
84
  // Load CRS into wasm global CRS state.
78
85
  await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data()));
79
86
 
80
- const acirComposer = await api.acirNewAcirComposer();
87
+ const acirComposer = await api.acirNewAcirComposer(0);
81
88
  return { api, acirComposer };
82
89
  }
83
90
 
84
- export async function proveAndVerify(jsonPath: string, witnessPath: string, isRecursive: boolean, sizeHint?: number) {
85
- const { api, acirComposer } = await init(jsonPath, sizeHint);
91
+ export async function proveAndVerify(jsonPath: string, witnessPath: string, isRecursive: boolean) {
92
+ const { api, acirComposer } = await init(jsonPath);
86
93
  try {
87
- // debug('initing proving key...');
88
- const bytecode = getBytecode(jsonPath);
89
- // await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), circuitSize);
90
-
91
- // const circuitSize = await api.acirGetExactCircuitSize(acirComposer);
92
- // debug(`circuit size: ${circuitSize}`);
93
-
94
- // debug('initing verification key...');
95
- // await api.acirInitVerificationKey(acirComposer);
96
-
97
94
  debug(`creating proof...`);
95
+ const bytecode = getBytecode(jsonPath);
98
96
  const witness = getWitness(witnessPath);
99
97
  const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), isRecursive);
100
98
 
@@ -107,23 +105,11 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, isRe
107
105
  }
108
106
  }
109
107
 
110
- export async function prove(
111
- jsonPath: string,
112
- witnessPath: string,
113
- isRecursive: boolean,
114
- outputPath: string,
115
- sizeHint?: number,
116
- ) {
117
- const { api, acirComposer } = await init(jsonPath, sizeHint);
108
+ export async function prove(jsonPath: string, witnessPath: string, isRecursive: boolean, outputPath: string) {
109
+ const { api, acirComposer } = await init(jsonPath);
118
110
  try {
119
- // debug('initing proving key...');
120
- const bytecode = getBytecode(jsonPath);
121
- // await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), circuitSize);
122
-
123
- // const circuitSize = await api.acirGetExactCircuitSize(acirComposer);
124
- // debug(`circuit size: ${circuitSize}`);
125
-
126
111
  debug(`creating proof...`);
112
+ const bytecode = getBytecode(jsonPath);
127
113
  const witness = getWitness(witnessPath);
128
114
  const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), isRecursive);
129
115
  debug(`done.`);
@@ -138,14 +124,13 @@ export async function prove(
138
124
  export async function gateCount(jsonPath: string) {
139
125
  const api = await newBarretenbergApiAsync(1);
140
126
  try {
141
- const circuitSizes = await getCircuitSize(jsonPath, api);
142
- console.log(`${circuitSizes.exact}`);
127
+ console.log(`gates: ${await getGates(jsonPath, api)}`);
143
128
  } finally {
144
129
  await api.destroy();
145
130
  }
146
131
  }
147
132
 
148
- export async function verify(jsonPath: string, proofPath: string, isRecursive: boolean, vkPath: string) {
133
+ export async function verify(proofPath: string, isRecursive: boolean, vkPath: string) {
149
134
  const { api, acirComposer } = await initLite();
150
135
  try {
151
136
  await api.acirLoadVerificationKey(acirComposer, new RawBuffer(readFileSync(vkPath)));
@@ -173,12 +158,12 @@ export async function contract(outputPath: string, vkPath: string) {
173
158
  }
174
159
  }
175
160
 
176
- export async function writeVk(jsonPath: string, outputPath: string, sizeHint?: number) {
177
- const { api, acirComposer, circuitSize } = await init(jsonPath, sizeHint);
161
+ export async function writeVk(jsonPath: string, outputPath: string) {
162
+ const { api, acirComposer } = await init(jsonPath);
178
163
  try {
179
164
  debug('initing proving key...');
180
165
  const bytecode = getBytecode(jsonPath);
181
- await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), circuitSize);
166
+ await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode));
182
167
 
183
168
  debug('initing verification key...');
184
169
  const vk = await api.acirGetVerificationKey(acirComposer);
@@ -226,16 +211,6 @@ export async function vkAsFields(vkPath: string, vkeyOutputPath: string) {
226
211
  }
227
212
  }
228
213
 
229
- // nargo use bb.js: backend -> bb.js
230
- // backend prove --data-dir data --witness /foo/bar/witness.tr --json /foo/bar/main.json
231
- // backend verify ...
232
- // backend get_total_num_gates --data-dir data --json /foo/bar/main.json
233
- // backend get_sol_contract --data-dir data --json /foo/bar/main.json --output
234
- // backend get_features
235
- // OPTIONAL stateful backend:
236
- // backend start
237
- // backend stop
238
-
239
214
  const program = new Command();
240
215
 
241
216
  program.option('-v, --verbose', 'enable verbose logging', false);
@@ -252,10 +227,9 @@ program
252
227
  .option('-j, --json-path <path>', 'Specify the JSON path', './target/main.json')
253
228
  .option('-w, --witness-path <path>', 'Specify the witness path', './target/witness.tr')
254
229
  .option('-r, --recursive', 'prove and verify using recursive prover and verifier', false)
255
- .option('-s, --size-hint <gates>', 'provide a circuit size hint to skip calculation phase')
256
- .action(async ({ jsonPath, witnessPath, recursive, sizeHint }) => {
230
+ .action(async ({ jsonPath, witnessPath, recursive }) => {
257
231
  handleGlobalOptions();
258
- const result = await proveAndVerify(jsonPath, witnessPath, recursive, sizeHint);
232
+ const result = await proveAndVerify(jsonPath, witnessPath, recursive);
259
233
  process.exit(result ? 0 : 1);
260
234
  });
261
235
 
@@ -266,10 +240,9 @@ program
266
240
  .option('-w, --witness-path <path>', 'Specify the witness path', './target/witness.tr')
267
241
  .option('-r, --recursive', 'prove using recursive prover', false)
268
242
  .option('-o, --output-path <path>', 'Specify the proof output path', './proofs/proof')
269
- .option('-s, --size-hint <gates>', 'provide a circuit size hint to skip calculation phase')
270
- .action(async ({ jsonPath, witnessPath, recursive, outputPath, sizeHint }) => {
243
+ .action(async ({ jsonPath, witnessPath, recursive, outputPath }) => {
271
244
  handleGlobalOptions();
272
- await prove(jsonPath, witnessPath, recursive, outputPath, sizeHint);
245
+ await prove(jsonPath, witnessPath, recursive, outputPath);
273
246
  });
274
247
 
275
248
  program
@@ -284,13 +257,13 @@ program
284
257
  program
285
258
  .command('verify')
286
259
  .description('Verify a proof. Process exists with success or failure code.')
287
- .option('-j, --json-path <path>', 'Specify the JSON path', './target/main.json')
288
260
  .requiredOption('-p, --proof-path <path>', 'Specify the path to the proof')
289
261
  .option('-r, --recursive', 'prove using recursive prover', false)
290
262
  .requiredOption('-k, --vk <path>', 'path to a verification key. avoids recomputation.')
291
- .action(async ({ jsonPath, proofPath, recursive, vk }) => {
263
+ .action(async ({ proofPath, recursive, vk }) => {
292
264
  handleGlobalOptions();
293
- await verify(jsonPath, proofPath, recursive, vk);
265
+ const result = await verify(proofPath, recursive, vk);
266
+ process.exit(result ? 0 : 1);
294
267
  });
295
268
 
296
269
  program
@@ -309,10 +282,9 @@ program
309
282
  .description('Output verification key.')
310
283
  .option('-j, --json-path <path>', 'Specify the JSON path', './target/main.json')
311
284
  .requiredOption('-o, --output-path <path>', 'Specify the path to write the key')
312
- .option('-s, --size-hint <gates>', 'provide a circuit size hint to skip calculation phase')
313
- .action(async ({ jsonPath, outputPath, sizeHint }) => {
285
+ .action(async ({ jsonPath, outputPath }) => {
314
286
  handleGlobalOptions();
315
- await writeVk(jsonPath, outputPath, sizeHint);
287
+ await writeVk(jsonPath, outputPath);
316
288
  });
317
289
 
318
290
  program
@@ -1 +0,0 @@
1
- {"version":3,"file":"file_crs.d.ts","sourceRoot":"","sources":["../../../src/crs/node/file_crs.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,eAAO,MAAM,YAAY,QAAqF,CAAC;AAE/G;;GAEG;AACH,qBAAa,OAAO;IAKhB;;OAEG;aACa,SAAS,EAAE,MAAM;IACjC,OAAO,CAAC,IAAI;IARd,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,MAAM,CAAc;;IAG1B;;OAEG;IACa,SAAS,EAAE,MAAM,EACzB,IAAI,SAAe;IAG7B,MAAM,CAAC,aAAa;IAIpB;;OAEG;IACG,IAAI;IAYV;;;OAGG;IACH,SAAS,IAAI,UAAU;IAIvB;;;OAGG;IACH,SAAS,IAAI,UAAU;CAGxB"}
@@ -1,51 +0,0 @@
1
- import { existsSync } from 'fs';
2
- import { readFile } from 'fs/promises';
3
- import { dirname } from 'path';
4
- import { fileURLToPath } from 'url';
5
- /**
6
- * The path to our SRS object, assuming that we are in barretenberg/ts folder.
7
- */
8
- export const SRS_DEV_PATH = dirname(fileURLToPath(import.meta.url)) + '/../../../cpp/srs_db/ignition/monomial';
9
- /**
10
- * Downloader for CRS from a local file (for Node).
11
- */
12
- export class FileCrs {
13
- constructor(
14
- /**
15
- * The number of circuit gates.
16
- */
17
- numPoints, path = SRS_DEV_PATH) {
18
- this.numPoints = numPoints;
19
- this.path = path;
20
- }
21
- static defaultExists() {
22
- return existsSync(SRS_DEV_PATH);
23
- }
24
- /**
25
- * Read the data file.
26
- */
27
- async init() {
28
- // We need this.numPoints number of g1 points.
29
- // numPoints should be circuitSize + 1.
30
- const g1Start = 28;
31
- const g1End = g1Start + this.numPoints * 64;
32
- const data = await readFile(this.path + '/transcript00.dat');
33
- this.data = data.subarray(g1Start, g1End);
34
- this.g2Data = await readFile(this.path + '/g2.dat');
35
- }
36
- /**
37
- * G1 points data for prover key.
38
- * @returns The points data.
39
- */
40
- getG1Data() {
41
- return this.data;
42
- }
43
- /**
44
- * G2 points data for verification key.
45
- * @returns The points data.
46
- */
47
- getG2Data() {
48
- return this.g2Data;
49
- }
50
- }
51
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsZV9jcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY3JzL25vZGUvZmlsZV9jcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLElBQUksQ0FBQztBQUNoQyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDL0IsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUVwQzs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxPQUFPLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyx3Q0FBd0MsQ0FBQztBQUUvRzs7R0FFRztBQUNILE1BQU0sT0FBTyxPQUFPO0lBSWxCO0lBQ0U7O09BRUc7SUFDYSxTQUFpQixFQUN6QixPQUFPLFlBQVk7UUFEWCxjQUFTLEdBQVQsU0FBUyxDQUFRO1FBQ3pCLFNBQUksR0FBSixJQUFJLENBQWU7SUFDMUIsQ0FBQztJQUVKLE1BQU0sQ0FBQyxhQUFhO1FBQ2xCLE9BQU8sVUFBVSxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ2xDLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxJQUFJO1FBQ1IsOENBQThDO1FBQzlDLHVDQUF1QztRQUN2QyxNQUFNLE9BQU8sR0FBRyxFQUFFLENBQUM7UUFDbkIsTUFBTSxLQUFLLEdBQUcsT0FBTyxHQUFHLElBQUksQ0FBQyxTQUFTLEdBQUcsRUFBRSxDQUFDO1FBRTVDLE1BQU0sSUFBSSxHQUFHLE1BQU0sUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLEdBQUcsbUJBQW1CLENBQUMsQ0FBQztRQUM3RCxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBRTFDLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxTQUFTLENBQUMsQ0FBQztJQUN0RCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsU0FBUztRQUNQLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQztJQUNuQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsU0FBUztRQUNQLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQztJQUNyQixDQUFDO0NBQ0YifQ==