-
Notifications
You must be signed in to change notification settings - Fork 100
Expand file tree
/
Copy pathartifacts.ts
More file actions
340 lines (300 loc) · 10.2 KB
/
Copy pathartifacts.ts
File metadata and controls
340 lines (300 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
import { join } from 'path';
/**
* ZK Artifact Path Configuration (ZK-041)
*
* Centralized configuration for locating compiled circuit artifacts, manifests,
* fixtures, and proving assets. This ensures the SDK can locate artifacts without
* hard-coded ad hoc paths and makes layout changes testable.
*
* Directory Structure:
* artifacts/
* zk/
* v{version}/
* circuits/
* {circuit_name}/
* circuit.json # Compiled circuit (ACIR + ABI)
* manifests/
* manifest.json # Circuit metadata and checksums
* fixtures/
* {circuit_name}/
* test_vectors.json # Test vectors and golden inputs
* proving_keys/
* {circuit_name}/
* vk # Verification key
* pk # Proving key
*/
import { posix as pathPosix } from 'path';
import { Buffer } from 'buffer';
import { NoirArtifacts, ZkArtifactManifest, ArtifactManifestError } from './types';
import { sha256Hex } from './hash';
/**
* Join URL parts ensuring single slashes.
*/
function joinUrl(base: string, ...parts: string[]): string {
const normalizedBase = base.endsWith('/') ? base : base + '/';
const normalizedParts = parts.map((p) => (p.startsWith('/') ? p.slice(1) : p));
return normalizedBase + normalizedParts.join('/');
}
/**
* Current ZK artifact version.
* Increment this when circuit definitions change incompatibly.
*/
export const ZK_ARTIFACT_VERSION = '1';
/**
* Base directory for all ZK artifacts relative to repository root.
*/
export const ZK_ARTIFACTS_BASE_DIR = 'artifacts/zk';
/**
* Get the versioned artifacts directory.
*/
export function getVersionedArtifactsDir(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(ZK_ARTIFACTS_BASE_DIR, `v${version}`);
}
/**
* Get the circuits directory for a specific version.
*/
export function getCircuitsDir(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getVersionedArtifactsDir(version), 'circuits');
}
/**
* Get the circuit directory for a specific circuit and version.
*/
export function getCircuitDir(circuitName: string, version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getCircuitsDir(version), circuitName);
}
/**
* Get the compiled circuit JSON path for a specific circuit.
*/
export function getCircuitPath(circuitName: string, version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getCircuitDir(circuitName, version), `${circuitName}.json`);
}
/**
* Get the manifests directory for a specific version.
*/
export function getManifestsDir(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getVersionedArtifactsDir(version), 'manifests');
}
/**
* Get the manifest file path for a specific version.
*/
export function getManifestPath(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getManifestsDir(version), 'manifest.json');
}
/**
* Get the fixtures directory for a specific version.
*/
export function getFixturesDir(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getVersionedArtifactsDir(version), 'fixtures');
}
/**
* Get the fixtures directory for a specific circuit.
*/
export function getCircuitFixturesDir(circuitName: string, version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getFixturesDir(version), circuitName);
}
/**
* Get the proving keys directory for a specific version.
*/
export function getProvingKeysDir(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getVersionedArtifactsDir(version), 'proving_keys');
}
/**
* Get the proving keys directory for a specific circuit.
*/
export function getCircuitProvingKeysDir(circuitName: string, version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getProvingKeysDir(version), circuitName);
}
/**
* Get the verification key path for a specific circuit.
*/
export function getVerificationKeyPath(circuitName: string, version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getCircuitProvingKeysDir(circuitName, version), 'vk');
}
/**
* Get the proving key path for a specific circuit.
*/
export function getProvingKeyPath(circuitName: string, version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getCircuitProvingKeysDir(circuitName, version), 'pk');
}
/**
* Get the release bundle directory for a specific version.
*/
export function getReleaseBundleDir(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getVersionedArtifactsDir(version), 'bundles');
}
/**
* Get the release bundle file path for a specific version.
*/
export function getReleaseBundlePath(version: string = ZK_ARTIFACT_VERSION): string {
return join(getReleaseBundleDir(version), 'release-bundle.json');
return pathPosix.join(getReleaseBundleDir(version), 'release-bundle.json');
}
/**
* Get the benchmark baselines file path for a specific version.
*/
export function getBenchmarkBaselinesPath(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getReleaseBundleDir(version), 'benchmark-baselines.json');
}
/**
* Get the root directory for VK rotation evidence for a specific version.
*/
export function getRotationEvidenceDir(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getReleaseBundleDir(version), 'rotation-evidence');
}
/**
* Get the VK rotation evidence bundle path for a specific pool and version.
*/
export function getRotationEvidenceBundlePath(
poolId: string,
version: string = ZK_ARTIFACT_VERSION
): string {
return pathPosix.join(getRotationEvidenceDir(version), poolId, 'rotation-bundle.json');
}
/**
* Get the human-readable VK rotation log path for a specific pool and version.
*/
export function getRotationEvidenceLogPath(
poolId: string,
version: string = ZK_ARTIFACT_VERSION
): string {
return pathPosix.join(getRotationEvidenceDir(version), poolId, 'rotation-log.md');
}
/**
* Filename for the verifier schema artifact.
*/
export const VERIFIER_SCHEMA_FILENAME = 'verifier_schema.json';
/**
* Get the verifier schema path for a specific version.
*/
export function getVerifierSchemaPath(version: string = ZK_ARTIFACT_VERSION): string {
return pathPosix.join(getVersionedArtifactsDir(version), VERIFIER_SCHEMA_FILENAME);
}
/**
* Known circuit names in the PrivacyLayer system.
*/
export const CIRCUIT_NAMES = {
COMMITMENT: 'commitment',
WITHDRAW: 'withdraw',
MERKLE: 'merkle',
} as const;
export type CircuitName = typeof CIRCUIT_NAMES[keyof typeof CIRCUIT_NAMES];
/**
* Get the circuit path for a known circuit.
*/
export function getKnownCircuitPath(circuit: CircuitName, version?: string): string {
return getCircuitPath(circuit, version);
}
/**
* Artifact layout configuration object.
* Exported for testing and validation.
*/
export const ARTIFACT_LAYOUT = {
version: ZK_ARTIFACT_VERSION,
baseDir: ZK_ARTIFACTS_BASE_DIR,
getCircuitsDir,
getCircuitDir,
getCircuitPath,
getManifestsDir,
getManifestPath,
getFixturesDir,
getCircuitFixturesDir,
getProvingKeysDir,
getCircuitProvingKeysDir,
getVerificationKeyPath,
getProvingKeyPath,
getReleaseBundleDir,
getReleaseBundlePath,
getBenchmarkBaselinesPath,
getRotationEvidenceDir,
getRotationEvidenceBundlePath,
getRotationEvidenceLogPath,
CIRCUIT_NAMES,
getKnownCircuitPath,
VERIFIER_SCHEMA_FILENAME,
getVerifierSchemaPath,
} as const;
/**
* Browser-compatible artifact loader that fetches versioned artifacts from URLs
* and validates their integrity against a manifest.
*/
export class BrowserArtifactLoader {
constructor(private readonly baseUrl: string) {}
/**
* Fetches the manifest for a specific version.
*/
async fetchManifest(
version: string = ZK_ARTIFACT_VERSION
): Promise<ZkArtifactManifest> {
const manifestPath = `manifests/manifest.json`;
const url = joinUrl(
this.baseUrl,
ZK_ARTIFACTS_BASE_DIR,
`v${version}`,
manifestPath
);
const response = await fetch(url);
if (!response.ok) {
throw new ArtifactManifestError(
`Failed to fetch manifest from ${url}: ${response.statusText}`
);
}
return await response.json();
}
/**
* Computes the SHA-256 hash of a manifest for VK metadata (ZK-074).
* This hash is used to track which artifact set a VK corresponds to.
*/
async computeManifestHash(manifest: ZkArtifactManifest): Promise<string> {
const manifestJson = JSON.stringify(manifest);
return await sha256Hex(manifestJson);
}
/**
* Loads and validates artifacts for a specific circuit and version.
*/
async loadArtifacts(
circuitName: string,
version: string = ZK_ARTIFACT_VERSION
): Promise<NoirArtifacts> {
const manifest = await this.fetchManifest(version);
const entry = manifest.circuits[circuitName];
if (!entry) {
throw new ArtifactManifestError(
`Circuit "${circuitName}" not found in manifest for version "${version}"`
);
}
const artifactUrl = joinUrl(
this.baseUrl,
ZK_ARTIFACTS_BASE_DIR,
`v${version}`,
entry.path
);
const response = await fetch(artifactUrl);
if (!response.ok) {
throw new ArtifactManifestError(
`Failed to fetch artifact for "${circuitName}" from ${artifactUrl}: ${response.statusText}`
);
}
const raw = await response.arrayBuffer();
const bytes = new Uint8Array(raw);
// Integrity check (ZK-085)
// Check 'artifact_sha256' first, then 'checksum' for compatibility
const expectedHash = entry.artifact_sha256 ?? entry.checksum;
if (expectedHash) {
const actualHash = await sha256Hex(bytes);
if (actualHash !== expectedHash) {
throw new ArtifactManifestError(
`Integrity check failed for "${circuitName}": expected ${expectedHash}, got ${actualHash}`
);
}
}
const artifact = JSON.parse(new TextDecoder().decode(bytes));
return {
acir: typeof artifact.bytecode === 'string'
? new Uint8Array(Buffer.from(artifact.bytecode, 'base64')) // If it's base64 encoded
: new Uint8Array(artifact.acir || []), // Fallback to raw acir array if present
abi: artifact.abi,
name: artifact.name || circuitName,
bytecode: artifact.bytecode,
};
}
}