Skip to content
30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@orbinum/proof-generator",
"version": "3.0.0",
"version": "3.1.0",
"description": "ZK-SNARK proof generator for Orbinum. Combines snarkjs (witness) with arkworks WASM (proof generation) to produce 128-byte Groth16 proofs.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -57,7 +57,7 @@
"lint-staged": "15.2.11",
"prettier": "3.4.2",
"ts-jest": "29.2.5",
"typescript": "5.7.3"
"typescript": "^5.7.3"
},
"lint-staged": {
"*.ts": [
Expand Down
170 changes: 91 additions & 79 deletions src/circuits.ts
Original file line number Diff line number Diff line change
@@ -1,116 +1,128 @@
/**
* Circuit Configuration
* Circuit Configuration and Artifact Providers
*
* Defines paths and metadata for all supported circuits using versioned npm packages.
* Defines paths and metadata for all supported circuits.
* Implements ArtifactProvider for Node.js (fs) and Browser (fetch).
*/

import { CircuitType, CircuitConfig } from './types';
import * as fs from 'fs';
import { ArtifactProvider } from './provider';
import * as path from 'path';

/**
* Get circuit configuration from local workspace artifacts
*/
// ============================================================================
// Defaults & Configuration
// ============================================================================

const DEFAULT_CIRCUIT_URL = 'https://circuits.orbinum.io/v1';

export function getCircuitConfig(circuitType: CircuitType): CircuitConfig {
const circuitName = circuitType.toLowerCase();
const paths = getPackageCircuitPaths(circuitName);

return {
name: circuitName,
wasmPath: paths.wasm,
zkeyPath: paths.zkey,
provingKeyPath: paths.ark,
wasmPath: `${circuitName}.wasm`,
zkeyPath: `${circuitName}_pk.zkey`,
provingKeyPath: `${circuitName}_pk.ark`,
expectedPublicSignals: getExpectedPublicSignals(circuitType),
};
}

/**
* Resolve circuit artifact paths from the @orbinum/circuits npm package
*
* Searches for circuit artifacts (.wasm, _pk.ark, _pk.zkey) in common
* package layouts: root, artifacts/, pkg/ directories.
*
* @param circuitName - Circuit name (e.g., 'unshield', 'transfer')
* @returns Paths to circuit artifacts
* @throws Error if package or artifacts not found
*/
function getPackageCircuitPaths(circuitName: string): { wasm: string; ark: string; zkey: string } {
// Try to resolve @orbinum/circuits or orbinum-circuits package
const packageCandidates = ['@orbinum/circuits/package.json', 'orbinum-circuits/package.json'];
function getExpectedPublicSignals(circuitType: CircuitType): number {
switch (circuitType) {
case CircuitType.Unshield:
return 5;
case CircuitType.Transfer:
return 5;
case CircuitType.Disclosure:
return 4;
default:
throw new Error(`Unknown circuit type: ${circuitType}`);
}
}

let packageRoot: string | null = null;
let packageName: string | null = null;
// ============================================================================
// Node.js Provider (FS)
// ============================================================================

for (const candidate of packageCandidates) {
export class NodeArtifactProvider implements ArtifactProvider {
private fs: any;
private packageRoot: string;

constructor(packageRoot?: string) {
// Dynamic require to avoid bundling fs in browser
try {
packageRoot = path.dirname(require.resolve(candidate));
packageName = candidate.replace('/package.json', '');
break;
} catch {
continue;
this.fs = eval('require')('fs');
} catch (e) {
throw new Error('NodeArtifactProvider requires Node.js environment');
}
this.packageRoot = packageRoot || this.resolvePackageRoot();
}

if (!packageRoot || !packageName) {
throw new Error(
'Cannot resolve @orbinum/circuits package. Install with: npm install @orbinum/circuits'
);
private resolvePackageRoot(): string {
const packageCandidates = ['@orbinum/circuits/package.json', 'orbinum-circuits/package.json'];
for (const candidate of packageCandidates) {
try {
return path.dirname(eval('require').resolve(candidate));
} catch {
continue;
}
}
throw new Error('Cannot resolve @orbinum/circuits package');
}

// Search in common artifact locations
const searchDirs = [
packageRoot,
path.join(packageRoot, 'artifacts'),
path.join(packageRoot, 'pkg'),
];
async getCircuitWasm(type: CircuitType): Promise<Uint8Array> {
const config = getCircuitConfig(type);
const filePath = this.findArtifactPath(config.wasmPath);
return this.fs.readFileSync(filePath);
}

for (const dir of searchDirs) {
const wasmPath = path.join(dir, `${circuitName}.wasm`);
const arkPath = path.join(dir, `${circuitName}_pk.ark`);
const zkeyPath = path.join(dir, `${circuitName}_pk.zkey`);
async getCircuitZkey(type: CircuitType): Promise<Uint8Array> {
const config = getCircuitConfig(type);
const filePath = this.findArtifactPath(config.zkeyPath);
return this.fs.readFileSync(filePath);
}

if (fs.existsSync(wasmPath) && fs.existsSync(arkPath) && fs.existsSync(zkeyPath)) {
return { wasm: wasmPath, ark: arkPath, zkey: zkeyPath };
private findArtifactPath(filename: string): string {
const searchDirs = [
this.packageRoot,
path.join(this.packageRoot, 'artifacts'),
path.join(this.packageRoot, 'pkg'),
];
for (const dir of searchDirs) {
const p = path.join(dir, filename);
if (this.fs.existsSync(p)) return p;
}
throw new Error(`Artifact ${filename} not found in ${this.packageRoot}`);
}

throw new Error(
`Circuit artifacts for "${circuitName}" not found in ${packageName}. ` +
`Searched directories: ${searchDirs.join(', ')}`
);
}

/**
* Get expected number of public signals for each circuit
*/
function getExpectedPublicSignals(circuitType: CircuitType): number {
switch (circuitType) {
case CircuitType.Unshield:
return 5; // merkle_root, nullifier, amount, recipient, asset_id
case CircuitType.Transfer:
return 5; // merkle_root, input_nullifiers(2), output_commitments(2)
case CircuitType.Disclosure:
return 4; // commitment, vk_hash, mask, revealed_owner_hash
default:
throw new Error(`Unknown circuit type: ${circuitType}`);
}
}
// ============================================================================
// Web Provider (Fetch)
// ============================================================================

/**
* Validate that circuit artifacts exist
*/
export function validateCircuitArtifacts(config: CircuitConfig): void {
const fs = require('fs');
export class WebArtifactProvider implements ArtifactProvider {
private baseUrl: string;

constructor(baseUrl: string = DEFAULT_CIRCUIT_URL) {
this.baseUrl = baseUrl.replace(/\/$/, '');
}

if (!fs.existsSync(config.wasmPath)) {
throw new Error(`WASM not found: ${config.wasmPath}`);
async getCircuitWasm(type: CircuitType): Promise<Uint8Array> {
const config = getCircuitConfig(type);
return this.fetchArtifact(config.wasmPath);
}

if (!fs.existsSync(config.provingKeyPath)) {
throw new Error(`Proving key not found: ${config.provingKeyPath}`);
async getCircuitZkey(type: CircuitType): Promise<Uint8Array> {
const config = getCircuitConfig(type);
return this.fetchArtifact(config.zkeyPath);
}

if (!fs.existsSync(config.zkeyPath)) {
throw new Error(`zkey not found: ${config.zkeyPath}`);
private async fetchArtifact(filename: string): Promise<Uint8Array> {
const url = `${this.baseUrl}/${filename}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch circuit artifact: ${url} (${response.status})`);
}
const buffer = await response.arrayBuffer();
return new Uint8Array(buffer);
}
}
Loading
Loading