@@ -10,9 +10,10 @@ const repoRoot = path.resolve(__dirname, '..');
1010// ZK-041: Accept version parameter for versioned artifact layout
1111const zkVersion = process . argv [ 2 ] || '1' ;
1212const artifactsDir = path . join ( repoRoot , 'artifacts' , 'zk' , `v${ zkVersion } ` ) ;
13- const manifestPath = path . join ( artifactsDir , 'manifests' , 'manifest.json' ) ;
13+ const versionedManifestPath = path . join ( artifactsDir , 'manifests' , 'manifest.json' ) ;
14+ const legacyManifestPath = path . join ( repoRoot , 'artifacts' , 'zk' , 'manifest.json' ) ;
15+ const releaseBundlePath = path . join ( artifactsDir , 'bundles' , 'release-bundle.json' ) ;
1416const PRODUCTION_MERKLE_ROOT_DEPTH = 20 ;
15- const CIRCUIT_ORDER = [ 'withdraw' , 'commitment' ] ;
1617const WITHDRAW_PUBLIC_INPUT_SCHEMA = [
1718 'pool_id' ,
1819 'root' ,
@@ -22,6 +23,7 @@ const WITHDRAW_PUBLIC_INPUT_SCHEMA = [
2223 'relayer' ,
2324 'fee' ,
2425] ;
26+ const CONTRACT_PUBLIC_INPUT_SCHEMA = WITHDRAW_PUBLIC_INPUT_SCHEMA . slice ( 1 ) ;
2527const EXTRA_FILES = {
2628 commitment_vectors : {
2729 path : 'commitment_vectors.json' ,
@@ -66,10 +68,36 @@ function commandOutput(command, args = ['--version']) {
6668 return result . stdout ;
6769}
6870
69- function readJson ( filePath ) {
70- return JSON . parse ( fs . readFileSync ( filePath , 'utf8' ) ) ;
71+ function detectBackendVersions ( ) {
72+ try {
73+ return {
74+ nargo_version : commandOutput ( 'nargo' , [ '--version' ] ) . trim ( ) ,
75+ noirc_version : commandOutput ( 'noirc' , [ '--version' ] ) . trim ( ) ,
76+ } ;
77+ } catch {
78+ return {
79+ nargo_version : 'unknown' ,
80+ noirc_version : 'unknown' ,
81+ } ;
82+ }
7183}
7284
85+ function normalizeBackend ( backend ) {
86+ const versions = detectBackendVersions ( ) ;
87+ if ( backend && typeof backend === 'object' ) {
88+ return {
89+ name : backend . name ?? 'nargo/noir' ,
90+ nargo_version : backend . nargo_version ?? versions . nargo_version ,
91+ noirc_version : backend . noirc_version ?? versions . noirc_version ,
92+ } ;
93+ }
94+
95+ return {
96+ name : typeof backend === 'string' && backend . length > 0 ? backend : 'nargo/noir' ,
97+ nargo_version : versions . nargo_version ,
98+ noirc_version : versions . noirc_version ,
99+ } ;
100+ }
73101
74102
75103function buildExtraFileEntries ( ) {
@@ -92,6 +120,36 @@ function buildExtraFileEntries() {
92120 ) ;
93121}
94122
123+ function buildReleaseBundle ( manifest ) {
124+ const withdraw = manifest . circuits . withdraw ;
125+ if ( ! withdraw || ! withdraw . public_input_schema ) {
126+ throw new Error ( 'Withdrawal circuit manifest entry is required to build the release bundle' ) ;
127+ }
128+
129+ const manifestSha256 = sha256Hex ( stableStringify ( manifest ) ) ;
130+ const verifierSchema = {
131+ circuit_id : withdraw . circuit_id ,
132+ public_input_schema : withdraw . public_input_schema ,
133+ public_input_arity : withdraw . public_input_schema . length ,
134+ contract_public_input_schema : CONTRACT_PUBLIC_INPUT_SCHEMA ,
135+ contract_public_input_arity : CONTRACT_PUBLIC_INPUT_SCHEMA . length ,
136+ schema_version : 1 ,
137+ } ;
138+
139+ return {
140+ version : 1 ,
141+ artifact_version : zkVersion ,
142+ manifest_sha256 : manifestSha256 ,
143+ manifest,
144+ verifier_schema,
145+ contract_metadata : {
146+ contract_name : 'privacy_pool' ,
147+ target_circuit_id : withdraw . circuit_id ,
148+ manifest_sha256 : manifestSha256 ,
149+ public_input_arity : CONTRACT_PUBLIC_INPUT_SCHEMA . length ,
150+ schema_version : 1 ,
151+ verifier_key_storage : 'DataKey::VerifyingKey' ,
152+ } ,
95153function computeChecksums ( raw , artifact ) {
96154 return {
97155 artifact_sha256 : sha256Hex ( raw ) ,
@@ -103,16 +161,20 @@ function computeChecksums(raw, artifact) {
103161function main ( ) {
104162 console . log ( `Refreshing ZK manifest for version ${zkVersion } . . . `);
105163
106- // ZK-041: Create manifests directory if it doesn't exist
107- if ( ! fs . existsSync ( path . dirname ( manifestPath ) ) ) {
108- fs . mkdirSync ( path . dirname ( manifestPath ) , { recursive : true } ) ;
164+ if (!fs.existsSync(path.dirname(versionedManifestPath))) {
165+ fs.mkdirSync(path.dirname(versionedManifestPath), { recursive: true });
166+ }
167+ if (!fs.existsSync(path.dirname(releaseBundlePath))) {
168+ fs.mkdirSync(path.dirname(releaseBundlePath), { recursive: true });
109169 }
110170
111- // ZK-041: Initialize manifest structure if it doesn't exist
112171 let manifest;
113- if ( fs . existsSync ( manifestPath ) ) {
114- manifest = JSON . parse ( fs . readFileSync ( manifestPath , 'utf8' ) ) ;
172+ if (fs.existsSync(versionedManifestPath)) {
173+ manifest = JSON.parse(fs.readFileSync(versionedManifestPath, 'utf8'));
174+ } else if (fs.existsSync(legacyManifestPath)) {
175+ manifest = JSON.parse(fs.readFileSync(legacyManifestPath, 'utf8'));
115176 } else {
177+ throw new Error('No manifest found to refresh. Run the rebuild pipeline first.');
116178 manifest = {
117179 version: zkVersion,
118180 backend: {
@@ -124,10 +186,13 @@ function main() {
124186 };
125187 }
126188
127- // ZK-041: Update circuits list to include merkle
189+ manifest.version = Number.parseInt(zkVersion, 10);
190+ manifest.backend = normalizeBackend(manifest.backend);
191+
128192 const circuits = ['withdraw', 'commitment', 'merkle'];
129193
130194 for (const name of circuits) {
195+ const filePath = path.join(artifactsDir, 'circuits', name, ` $ { name } . json `);
131196 // ZK-041: Look for circuits in versioned directory structure
132197 const circuitFile = ` circuits / $ { name} / $ { name} . json `;
133198 const filePath = path.join(artifactsDir, circuitFile);
@@ -139,6 +204,35 @@ function main() {
139204
140205 const raw = fs.readFileSync(filePath);
141206 const artifact = JSON.parse(raw.toString('utf8'));
207+ const circuitEntry = manifest.circuits[name] ?? (manifest.circuits[name] = {});
208+ circuitEntry.circuit_id = name;
209+ circuitEntry.path = ` circuits / $ { name} / $ { name} . json `;
210+ circuitEntry.artifact_sha256 = sha256Hex(raw);
211+ circuitEntry.bytecode_sha256 = sha256Hex(String(artifact.bytecode ?? ''));
212+ circuitEntry.abi_sha256 = sha256Hex(stableStringify(artifact.abi ?? null));
213+ circuitEntry.name = artifact.name ?? name;
214+ circuitEntry.backend = 'nargo/noir';
215+
216+ if (name === 'withdraw') {
217+ circuitEntry.root_depth = PRODUCTION_MERKLE_ROOT_DEPTH;
218+ circuitEntry.public_input_schema = WITHDRAW_PUBLIC_INPUT_SCHEMA;
219+ }
220+ }
221+
222+ const extraFiles = buildExtraFileEntries();
223+ manifest.files = extraFiles;
224+
225+ const manifestText = JSON.stringify(manifest, null, 2) + '\n';
226+ const releaseBundle = buildReleaseBundle(manifest);
227+ const releaseBundleText = JSON.stringify(releaseBundle, null, 2) + '\n';
228+
229+ fs.writeFileSync(versionedManifestPath, manifestText);
230+ fs.writeFileSync(legacyManifestPath, manifestText);
231+ fs.writeFileSync(releaseBundlePath, releaseBundleText);
232+
233+ console.log(` Manifest updated at ${versionedManifestPath } `);
234+ console.log(` Legacy manifest updated at ${legacyManifestPath } `);
235+ console.log(` Release bundle updated at ${releaseBundlePath } `);
142236 const checksums = computeChecksums(raw, artifact);
143237
144238 if (!manifest.circuits[name]) {
0 commit comments