micro-zk-proofs
Version:
Create & verify zero-knowledge SNARK proofs in parallel, using noble cryptography
757 lines • 31.7 kB
JavaScript
/**
* The code is only used if you plan to run **legacy circom-js programs**. It is unused in WASM.
* Minimal witness program executor for circom programs, based on websnark/wasmsnark/snarkjs.
* Unsafe: it uses eval, better to be used inside worker threads.
* Depends on **monkey-patched BigInt** prototypes due to how circom programs are serialized.
* We only patch prototypes before execution. After finishing, patches are reverted.
* @module
*/
import { invert, pow } from '@noble/curves/abstract/modular.js';
import { bn254 as nobleBn254 } from '@noble/curves/bn254.js';
import { bitMask } from '@noble/curves/utils.js';
import { abytes } from '@noble/hashes/utils.js';
import * as P from 'micro-packed';
import {} from "./index.js";
/**
* Checks if the provided value is object-like for option/schema bags.
* This intentionally matches noble-curves and noble-hashes by using the
* `[object Object]` tag instead of rejecting class/proxy/env objects by prototype;
* stricter checks caused compatibility reports in proxied environments.
* Array, Uint8Array and others are not plain objects.
* @param obj - The value to be checked.
*/
function isPlainObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
function monkeyPatchBigInt() {
const methods = {
// Equality
eq: (a, b) => a === b,
neq: (a, b) => a !== b,
greaterOrEquals: (a, b) => a >= b,
greater: (a, b) => a > b,
gt: (a, b) => a > b,
lesserOrEquals: (a, b) => a <= b,
lesser: (a, b) => a < b,
lt: (a, b) => a < b,
// Basic math
sub: (a, b) => a - b,
add: (a, b) => a + b,
mul: (a, b) => a * b,
div: (a, b) => a / b,
mod: (a, b) => a % b,
// Fields
inverse: (n, modulo) => invert(n, modulo),
modPow: (a, power, modulo) => pow(a, power, modulo),
// Binary
and: (a, b) => a & b,
// Old circom serializes `<<` as `.shl(...)`, matching snarkjs v0.2.0's bigint shim.
shl: (a, b) => a << BigInt(b),
shr: (a, b) => a >> BigInt(b),
};
let patched = false;
let orig = {};
const proto = BigInt.prototype;
const restoreOne = (name, desc) => {
if (!desc)
delete proto[name];
else
Object.defineProperty(proto, name, desc);
};
return {
patch() {
if (patched)
throw new Error('bigint: already patched');
const snap = {};
for (const name in methods) {
const desc = Object.getOwnPropertyDescriptor(proto, name);
if (desc && !desc.configurable)
throw new Error(`bigint: cannot patch non-configurable BigInt.prototype.${name}`);
// Preserve descriptors: callers may have accessors or own undefined-valued properties here.
snap[name] = desc;
}
try {
for (const name in methods) {
Object.defineProperty(proto, name, {
configurable: true,
enumerable: snap[name]?.enumerable || false,
value: function (...args) {
return methods[name](this, ...args);
},
writable: true,
});
}
}
catch (err) {
for (const name in snap)
restoreOne(name, snap[name]);
throw err;
}
orig = snap;
patched = true;
},
restore() {
if (!patched)
throw new Error('bigint: not patched');
for (const name in methods)
restoreOne(name, orig[name]);
orig = {};
patched = false;
},
};
}
const selectorStr = (lst) => lst.map((i) => `[${i}]`).join('');
const signalStr = (name, selectors) => name + selectorStr(selectors);
// Apply selectors
const select = (a, selectors) => {
for (const s of selectors)
a = a[s];
return a;
};
// Old circom can emit setPin/setSignal from templates and functions, including
// expression positions such as for headers. It can also emit nested function calls
// that trigger child components. Yielding those calls lets the scheduler
// trampoline recursive triggers before the caller continues.
const ctxYieldCall = /(^|[^\w$.])(ctx\.(?:callFunction|setPin|setSignal)\()/g;
const jsIgnored = /("(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|`(?:\\[\s\S]|[^`\\])*`|\/\/[^\n\r]*(?:\r\n?|\n|$)|\/\*[\s\S]*?\*\/)/g;
const yieldCtxCalls = (src) => {
const code = (chunk) => chunk.replace(ctxYieldCall, '$1yield $2');
let out = '';
let last = 0;
for (const match of src.matchAll(jsIgnored)) {
out += code(src.slice(last, match.index)) + match[0];
last = match.index + match[0].length;
}
return out + code(src.slice(last));
};
const codeToGenerator = (src) => yieldCtxCalls(src.replace(/function\s*\(\s*ctx\s*\)\s*\{/, 'function*(ctx) {'));
export const __TESTS =
/* @__PURE__ */ Object.freeze({
yieldCtxCalls: yieldCtxCalls,
});
/**
* Builds a witness generator for a legacy circom-js circuit JSON.
* @param circJson - Circom circuit JSON artifact.
* @returns Function that executes the circuit and returns the witness.
* @example
* Build a witness runner from a circom JSON circuit artifact.
* ```ts
* import { generateWitness } from 'micro-zk-proofs/witness.js';
* // Addition circuit: witness output is one, a + b, b, a.
* const circuitJson = {
* nVars: 4,
* nInputs: 2,
* nOutputs: 1,
* nSignals: 4,
* templates: {
* Main: `function(ctx) {
* ctx.setSignal(
* "out",
* [],
* bigInt(ctx.getSignal("a", [])).add(bigInt(ctx.getSignal("b", []))).mod(__P__)
* );
* }`,
* },
* functions: {},
* components: [{ name: 'main', params: {}, template: 'Main', inputSignals: 2 }],
* signals: [
* { names: ['one'], triggerComponents: [] },
* { names: ['main.out'], triggerComponents: [] },
* { names: ['main.b'], triggerComponents: [0] },
* { names: ['main.a'], triggerComponents: [0] },
* ],
* signalName2Idx: { one: 0, 'main.out': 1, 'main.b': 2, 'main.a': 3 },
* };
* const witness = generateWitness(circuitJson)({ a: '33', b: '34' });
* // [1n, 67n, 34n, 33n]
* ```
*/
export function generateWitness(circJson) {
if (!isPlainObject(circJson))
throw new TypeError('"circJson" expected object, got type=' + typeof circJson);
if (!Array.isArray(circJson.signals))
throw new TypeError('"circJson.signals" expected array, got type=' + typeof circJson.signals);
if (!Array.isArray(circJson.components))
throw new TypeError('"circJson.components" expected array, got type=' + typeof circJson.components);
if (!isPlainObject(circJson.templates))
throw new TypeError('"circJson.templates" expected object, got type=' + typeof circJson.templates);
if (!isPlainObject(circJson.functions))
throw new TypeError('"circJson.functions" expected object, got type=' + typeof circJson.functions);
const P = nobleBn254.fields.Fr.ORDER;
const MASK = bitMask(nobleBn254.fields.Fr.BITS);
const signals = circJson.signals;
const components = circJson.components;
const templates = {};
// Bind P & MASK directly into templates/functions, so we see dependency
for (let t in circJson.templates) {
templates[t] = new Function('bigInt', '__P__', '__MASK__', 'return ' + codeToGenerator(circJson.templates[t]))(BigInt, P, MASK);
}
const functions = {};
for (let f in circJson.functions) {
functions[f] = {
params: circJson.functions[f].params,
func: new Function('bigInt', '__P__', '__MASK__', 'return ' + codeToGenerator(circJson.functions[f].func))(BigInt, P, MASK),
};
}
function inputIdx(i) {
if (i >= circJson.nInputs)
throw new Error('Accessing an invalid input: ' + i);
// Witness slot 0 is the constant one, so declared inputs start after the output slots.
return circJson.nOutputs + 1 + i;
}
function getSignalIdx(name) {
if (circJson.signalName2Idx[name] !== undefined)
return circJson.signalName2Idx[name];
// signalNames() also queries raw witness indices when building error messages.
if (!isNaN(name))
return Number(name);
throw new Error('Invalid signal identifier: ' + name);
}
const signalNames = (i) => signals[getSignalIdx(i)].names.join(', ');
const patcher = monkeyPatchBigInt();
return function (input) {
const witness = new Array(circJson.nSignals);
let currentComponent;
let scopes = []; // scope stack
const notInitSignals = {};
const callFrameTag = Symbol('callFrame');
// Ready component frames preserve old depth-first trigger order without growing
// the JS call stack on long generated chains.
const pendingComponents = [];
const execFrames = [];
let draining = false;
let stepping = false;
const isCallFrame = (value) => value !== undefined && value.tag === callFrameTag;
function triggerComponent(c) {
notInitSignals[c]--;
const oldComponent = currentComponent;
currentComponent = components[c].name;
const template = components[c].template;
const newScope = {};
for (let p in components[c].params)
newScope[p] = components[c].params[p];
execFrames.push({
name: components[c].name,
scope: newScope,
iter: templates[template](ctx),
ready: [],
resume: undefined,
hasResume: false,
done: false,
value: undefined,
});
currentComponent = oldComponent;
}
function activeReady() {
return execFrames.length > 0 ? execFrames[execFrames.length - 1].ready : pendingComponents;
}
function removeQueued(c) {
let pendingIdx = pendingComponents.indexOf(c);
while (pendingIdx >= 0) {
pendingComponents.splice(pendingIdx, 1);
pendingIdx = pendingComponents.indexOf(c);
}
for (const frame of execFrames) {
let idx = frame.ready.indexOf(c);
while (idx >= 0) {
frame.ready.splice(idx, 1);
idx = frame.ready.indexOf(c);
}
}
}
function queueReady(c) {
// A generated parent can re-trigger a component already queued by an input
// alias. Old recursion runs it at the later trigger point, so move it there.
removeQueued(c);
activeReady().push(c);
}
function stepFrame() {
const frame = execFrames[execFrames.length - 1];
const oldComponent = currentComponent;
const oldScope = scopes;
const wasStepping = stepping;
currentComponent = frame.name;
scopes = [scopes[0], frame.scope];
stepping = true;
try {
const res = frame.hasResume ? frame.iter.next(frame.resume) : frame.iter.next();
frame.hasResume = false;
if (!res.done) {
if (isCallFrame(res.value)) {
res.value.frame.returnTo = frame;
execFrames.push(res.value.frame);
return;
}
frame.resume = res.value;
frame.hasResume = true;
return;
}
frame.done = true;
frame.value = res.value;
execFrames.pop();
if (frame.returnTo !== undefined) {
frame.returnTo.resume = frame.value;
frame.returnTo.hasResume = true;
}
if (frame.ready.length > 0)
activeReady().unshift(...frame.ready);
}
finally {
stepping = wasStepping;
scopes = oldScope;
currentComponent = oldComponent;
}
}
function drainStep() {
const ready = activeReady();
if (ready.length == 0)
return stepFrame();
const c = ready.shift();
if (notInitSignals[c] >= 0)
triggerComponent(c);
}
function drainFrame(frame) {
while (!frame.done)
drainStep();
return frame.value;
}
// Process ready components until none remain in the active frame. The notInitSignals guard
// (>= 0) skips duplicates: triggerComponent decrements to -1 on first call, so
// any component pushed twice will be a no-op on the second dequeue.
function drainPendingComponents(force = false) {
if (stepping)
return;
if (draining && !force)
return;
const wasDraining = draining;
draining = true;
try {
while (activeReady().length > 0 || execFrames.length > 0)
drainStep();
}
finally {
draining = wasDraining;
}
}
function setSignalFullName(fullName, value) {
const sId = getSignalIdx(fullName);
let firstInit = false;
if (witness[sId] === undefined)
firstInit = true;
witness[sId] = BigInt(value);
const triggers = signals[sId].triggerComponents;
for (let i = 0; i < signals[sId].triggerComponents.length; i++) {
const idCmp = triggers[i];
if (firstInit)
notInitSignals[idCmp]--;
}
const seen = new Set();
for (let i = 0; i < triggers.length; i++) {
const c = triggers[i];
// Old circom can alias many inputs of the same component to one signal,
// producing duplicate trigger ids. Decrement every alias first, then
// queue each ready component once in old first-trigger order; repeated
// queueReady calls repeatedly scan queues and can move duplicates.
if (notInitSignals[c] == 0 && !seen.has(c)) {
seen.add(c);
queueReady(c);
}
}
if (!stepping)
drainPendingComponents();
return witness[sId];
}
function getSignalFullName(name) {
const id = getSignalIdx(name);
// Circom-generated code can make several components ready at once through
// aliasing. Drain only when this read actually needs pending work; eager
// drains can run a child before the current parent reaches an earlier write.
if (witness[id] === undefined && activeReady().length > 0)
drainPendingComponents(true);
if (witness[id] === undefined)
throw new Error('Signal not initialized: ' + name);
return witness[id];
}
const cName = (name) => (name == 'one' ? 'one' : currentComponent + '.' + name);
// Minimal API that used inside evaluated code
const ctx = {
// Pins
setPin(compName, compSel, sigName, sigSel, value) {
const name = signalStr(cName(compName), compSel) + '.' + signalStr(sigName, sigSel);
setSignalFullName(name, value);
},
getPin(compName, componentSels, sigName, sigSel) {
const name = signalStr(cName(compName), componentSels) + '.' + signalStr(sigName, sigSel);
return getSignalFullName(name);
},
// Vars
setVar(name, sels, value) {
const scope = scopes[scopes.length - 1];
if (sels.length == 0) {
scope[name] = value;
}
else {
if (scope[name] === undefined)
scope[name] = [];
let cur = scope[name];
for (let i = 0; i < sels.length - 1; i++) {
if (cur[sels[i]] === undefined)
cur[sels[i]] = [];
cur = cur[sels[i]];
}
cur[sels[sels.length - 1]] = value;
}
return value;
},
getVar(name, sels) {
for (let i = scopes.length - 1; i >= 0; i--)
if (scopes[i][name] !== undefined)
return select(scopes[i][name], sels);
throw new Error('Variable not defined: ' + name);
},
// Signals
setSignal(name, sels, value) {
setSignalFullName(signalStr(currentComponent ? currentComponent + '.' + name : name, sels), value);
},
getSignal(name, sels) {
return getSignalFullName(signalStr(cName(name), sels));
},
// Utils
callFunction(name, params) {
const newScope = {};
for (let p = 0; p < functions[name].params.length; p++)
newScope[functions[name].params[p]] = params[p];
const frame = {
name: currentComponent,
scope: newScope,
iter: functions[name].func(ctx),
ready: [],
resume: undefined,
hasResume: false,
done: false,
value: undefined,
};
if (stepping)
return { tag: callFrameTag, frame };
execFrames.push(frame);
return drainFrame(frame);
},
assert(a, b, errStr = '') {
a = BigInt(a);
b = BigInt(b);
if (a === b)
return;
throw new Error(`Constraint doesn't match ${currentComponent}: ${errStr} -> ${a} != ${b}`);
},
};
patcher.patch();
try {
// Processing
for (const c in components)
notInitSignals[c] = components[c].inputSignals;
ctx.setSignal('one', [], BigInt(1));
// Queue all components that are already fully initialised (zero remaining inputs)
// and drain the queue iteratively rather than triggering them inline. This avoids
// a call-stack overflow when complex circuits have long inter-template chains.
for (let c in notInitSignals)
if (notInitSignals[c] == 0)
queueReady(c);
drainPendingComponents();
// Circuit JSON inputs are own fields; prototypes may carry unrelated app metadata.
for (const s of Object.keys(input)) {
currentComponent = 'main';
const stack = [{ selectors: [], values: input[s] }];
while (stack.length) {
const { selectors, values } = stack.pop();
if (!Array.isArray(values)) {
if (values === undefined)
throw new Error('Signal not defined:' + s);
ctx.setSignal(s, selectors, BigInt(values));
continue;
}
for (let j = values.length - 1; j >= 0; j--) {
stack.push({ selectors: [...selectors, `${j}`], values: values[j] });
}
}
}
for (let i = 0; i < circJson.nInputs; i++) {
const idx = inputIdx(i);
if (witness[idx] === undefined)
throw new Error('Input Signal not assigned: ' + signalNames(idx));
}
for (let i = 0; i < witness.length; i++)
if (witness[i] === undefined)
throw new Error('Signal not assigned: ' + signalNames(i));
return witness.slice(0, circJson.nVars);
}
finally {
patcher.restore();
}
};
}
/**
* Binary coders and parsers for Circom2 artifacts.
* @param curve - Curve pair used for field sizing and point decoding.
* @returns R1CS, witness, and zkey coders plus parse helpers.
* @example
* Build the coders once, then use them to parse and encode Circom2 artifacts.
* ```ts
* const { bn254 } = await import('@noble/curves/bn254.js');
* const coders = getCoders(bn254);
* const bytes = coders.binWitness.encode([1n, 2n]);
* coders.binWitness.decode(bytes);
* ```
*/
export const getCoders = (curve) => {
if (!isPlainObject(curve))
throw new TypeError('"curve" expected curve object, got type=' + typeof curve);
if (!isPlainObject(curve.fields))
throw new TypeError('"curve.fields" expected object, got type=' + typeof curve.fields);
if (!isPlainObject(curve.fields.Fr))
throw new TypeError('"curve.fields.Fr" expected object, got type=' + typeof curve.fields.Fr);
const field = curve.fields.Fr;
// NOTE: we need to pass field here, even if bigints are variable size, they are fixed to field bytes!
const fieldBytes = field.BYTES;
const fieldCoder = P.bigint(fieldBytes, true, false);
const Header = P.struct({
prime: P.prefix(P.U32LE, fieldCoder), // TODO: verify that exactly same as field.ORDER?
nWires: P.U32LE, // Total Number of wires including ONE signal (Index 0).
nPubOut: P.U32LE, // Total Number of wires public output wires. They should be starting at idx 1
nPubIn: P.U32LE, // Total Number of wires public input wires. They should be starting just after the public output
nPrvIn: P.U32LE, // Total Number of wires private input wires. They should be starting just after the public inputs
nLables: P.U64LE, // Total Number of wires private input wires. They should be starting just after the public inputs
mConstraints: P.U32LE, // Total Number of constraints
});
const constraintDict = {
encode: (from) => {
if (!Array.isArray(from))
throw new Error('array expected');
const to = {};
for (const item of from) {
if (!Array.isArray(item) || item.length !== 2)
throw new Error(`array of two elements expected`);
const [key, value] = item;
if (Object.prototype.hasOwnProperty.call(to, key))
throw new Error(`key(${key}) appears twice in constraint`);
to[key] = value;
}
return to;
},
decode: (to) => {
if (to === null || typeof to !== 'object' || Array.isArray(to))
throw new Error(`expected constraint object, got ${to}`);
return Object.entries(to).map(([key, value]) => {
// Object.entries() stringifies numeric R1CS signal ids; U32LE needs the number back.
if (!/^(0|[1-9][0-9]*)$/.test(key))
throw new Error(`expected uint32 constraint key, got ${key}`);
const n = Number(key);
if (!Number.isSafeInteger(n) || n < 0 || n > 0xffffffff)
throw new Error(`expected uint32 constraint key, got ${key}`);
return [n, value];
});
},
};
const Constraint = P.apply(P.array(P.U32LE, P.tuple([P.U32LE, fieldCoder])), constraintDict);
// A*B-C = 0
const Constraints = P.array(null, P.tuple([Constraint, Constraint, Constraint]));
const WireMap = P.array(null, P.U64LE);
// prefix() emits JS byte lengths, while Circom section headers serialize them as u64.
const sectionLen = P.apply(P.U64LE, P.coders.numberBigint);
const section = (inner) => P.prefix(sectionLen, inner);
const empty = P.bytes(null);
const R1CSSection = P.mappedTag(P.U32LE, {
header: [0x01, section(Header)],
constraint: [0x02, section(Constraints)],
wire2label: [0x03, section(WireMap)],
// not implemented: ultra-plonk
customGatesList: [0x04, section(empty)],
customGatesApplication: [0x05, section(empty)],
});
const R1CS = P.struct({
magic: P.magic(P.string(4), 'r1cs'),
version: P.U32LE,
sections: P.array(P.U32LE, R1CSSection),
});
const binWitness = P.array(null, fieldCoder);
const WTNSHeader = P.struct({
prime: P.prefix(P.U32LE, fieldCoder),
size: P.U32LE,
});
const WTNSSection = P.mappedTag(P.U32LE, {
header: [0x01, section(WTNSHeader)],
witness: [0x02, section(P.array(null, fieldCoder))],
});
const WTNS = P.struct({
magic: P.magic(P.string(4), 'wtns'),
version: P.U32LE,
sections: P.array(P.U32LE, WTNSSection),
});
const G1 = P.tuple([fieldCoder, fieldCoder]);
const G2 = P.tuple([fieldCoder, fieldCoder, fieldCoder, fieldCoder]);
const ZKeyHeader = P.map(P.U32LE, {
groth16: 1,
});
const ZKeyHeaderGroth = P.struct({
n8q: P.U32LE,
q: fieldCoder,
n8r: P.U32LE,
r: fieldCoder,
nVars: P.U32LE,
nPublic: P.U32LE,
domainSize: P.U32LE,
vk_alpha_1: G1,
vk_beta_1: G1,
vk_beta_2: G2,
vk_gamma_2: G2,
vk_delta_1: G1,
vk_delta_2: G2,
});
const ZKeyCoeff = P.struct({
matrix: P.U32LE,
constraint: P.U32LE,
signal: P.U32LE,
value: fieldCoder,
});
const ZKeySection = P.mappedTag(P.U32LE, {
header: [1, section(ZKeyHeader)],
headerGroth: [2, section(ZKeyHeaderGroth)],
IC: [3, section(P.array(null, G1))],
ccoefs: [4, section(P.array(P.U32LE, ZKeyCoeff))],
A: [5, section(P.array(null, G1))],
B1: [6, section(P.array(null, G1))],
B2: [7, section(P.array(null, G2))],
C: [8, section(P.array(null, G1))],
hExps: [9, section(P.array(null, G1))],
Contributions: [10, section(P.bytes(null))],
});
const ZKeyRaw = P.struct({
magic: P.magic(P.string(4), 'zkey'),
version: P.U32LE,
sections: P.array(P.U32LE, ZKeySection),
});
const getCircuitInfo = (bytes) => {
bytes = abytes(bytes, undefined, 'bytes');
const data = R1CS.decode(bytes);
const constraints = data.sections.find((i) => i.TAG === 'constraint');
if (!constraints)
throw new Error('R1CS: cannot find constraints');
const header = data.sections.find((i) => i.TAG === 'header');
if (!header)
throw new Error('R1CS: cannot find header');
if (header.data.prime !== field.ORDER)
throw new Error('R1CS: wrong field order');
return {
nVars: header.data.nWires,
nPubInputs: header.data.nPubIn,
nOutputs: header.data.nPubOut,
constraints: constraints.data,
};
};
function parseZKey(zkey) {
zkey = abytes(zkey, undefined, 'zkey');
const { Fr, Fp } = curve.fields;
// Montgomery encoding of field elements
const fieldFromMont = (f, is1) => {
const Rr = f.pow(BigInt(2), BigInt(f.BYTES * 8));
const RRi = f.inv(Rr);
const RRi2 = f.mul(RRi, RRi);
// G1/G2 coordinates carry one Montgomery factor; coefficient field elements need two.
return (x) => f.mul(x, is1 ? RRi : RRi2);
};
const is0 = (x) => x === BigInt(0);
const convFr2 = fieldFromMont(Fr, false);
const convFp = fieldFromMont(Fp, true);
const convG1 = ([x, y]) => is0(x) && is0(y) ? [BigInt(0), BigInt(1), BigInt(0)] : [convFp(x), convFp(y), BigInt(1)];
// [ [ 0n, 0n ], [ 0n, 0n ], [ 1n, 0n ] ], -> [ [ 0n, 0n ], [ 1n, 0n ], [ 0n, 0n ] ],
const convG2 = ([xc0, xc1, yc0, yc1]) => is0(xc0) && is0(xc1) && is0(yc0) && is0(yc1)
? [
[BigInt(0), BigInt(0)],
[BigInt(1), BigInt(0)],
[BigInt(0), BigInt(0)],
]
: [
[convFp(xc0), convFp(xc1)],
[convFp(yc0), convFp(yc1)],
[BigInt(1), BigInt(0)],
];
const data = ZKeyRaw.decode(zkey);
function getByTag(sections, tag) {
const v = sections.find((i) => i.TAG === tag);
if (!v)
throw new Error('ZKey: cannot find ' + String(tag));
return v.data;
}
function collect(sections, ks) {
const out = {};
for (const k of ks)
out[k] = getByTag(sections, k);
return out;
}
const res = collect(data.sections, [
'header',
'headerGroth',
'IC',
'ccoefs',
'A',
'B1',
'B2',
'C',
'hExps',
]);
// Same format as verification key
const json = {
protocol: res.header,
...res.headerGroth,
vk_alpha_1: convG1(res.headerGroth.vk_alpha_1),
vk_beta_1: convG1(res.headerGroth.vk_beta_1),
vk_delta_1: convG1(res.headerGroth.vk_delta_1),
vk_beta_2: convG2(res.headerGroth.vk_beta_2),
vk_delta_2: convG2(res.headerGroth.vk_delta_2),
vk_gamma_2: convG2(res.headerGroth.vk_gamma_2),
power: Math.log2(res.headerGroth.domainSize),
IC: res.IC.map(convG1),
ccoefs: res.ccoefs.map((i) => ({ ...i, value: convFr2(i.value) })),
A: res.A.map(convG1),
B1: res.B1.map(convG1),
B2: res.B2.map(convG2),
// snarkjs zkeys omit the leading zero C-query entries for public signals.
C: new Array(res.headerGroth.nPublic + 1).fill(null).concat(res.C.map(convG1)),
hExps: res.hExps.map(convG1),
};
// Our format (old snarkjs compat)
const pkey = {
protocol: 'groth',
nVars: json.nVars,
nPublic: json.nPublic,
domainSize: json.domainSize,
domainBits: json.power,
// Polynominals (instead polsA/polsB/polsC)
ccoefs: json.ccoefs, // changed
//
A: json.A,
B1: json.B1,
B2: json.B2,
C: json.C,
//
vk_alfa_1: json.vk_alpha_1,
vk_beta_1: json.vk_beta_1,
vk_delta_1: json.vk_delta_1,
vk_beta_2: json.vk_beta_2,
vk_delta_2: json.vk_delta_2,
//
hExps: json.hExps,
};
const vkey = {
protocol: 'groth',
nPublic: json.nPublic,
IC: json.IC,
vk_alfa_1: json.vk_alpha_1,
vk_beta_2: json.vk_beta_2,
vk_gamma_2: json.vk_gamma_2,
vk_delta_2: json.vk_delta_2,
};
return { json, pkey, vkey };
}
return { R1CS, binWitness, WTNS, getCircuitInfo, ZKeyRaw, parseZKey };
};
//# sourceMappingURL=witness.js.map