@reclaimprotocol/zk-symmetric-crypto
Version:
JS Wrappers for Various ZK Snark Circuits
118 lines (117 loc) • 5.39 kB
JavaScript
import { Base64, fromUint8Array, toUint8Array } from 'js-base64';
import koffi from 'koffi';
import { executeGnarkFn, executeGnarkFnAndGetJson, generateGnarkWitness, initGnarkAlgorithm, serialiseGnarkWitness } from "./utils.js";
const ALGS_MAP = {
'chacha20': { ext: 'chacha20_oprf', id: 3 },
'aes-128-ctr': { ext: 'aes128_oprf', id: 4 },
'aes-256-ctr': { ext: 'aes256_oprf', id: 5 },
};
export function makeGnarkOPRFOperator({ fetcher, algorithm }) {
return {
async generateWitness(input) {
return serialiseGnarkWitness(algorithm, input);
},
async groth16Prove(witness, logger) {
const lib = await initGnark(logger);
const rslt = await executeGnarkFnAndGetJson(lib.prove, witness);
if (typeof rslt !== 'object' || !('proof' in rslt) || !rslt.proof) {
throw new Error(`Failed to create gnark TOPRF proof: ${JSON.stringify(rslt)}`);
}
return { proof: Base64.toUint8Array(rslt.proof) };
},
async groth16Verify(publicSignals, proof, logger) {
const lib = await initGnark(logger);
const pubSignals = generateGnarkWitness(algorithm, publicSignals);
const verifyParams = JSON.stringify({
cipher: `${algorithm}-toprf`,
proof: typeof proof === 'string'
? proof
: Base64.fromUint8Array(proof),
publicSignals: pubSignals
});
return executeGnarkFn(lib.verify, verifyParams) === 1;
},
async generateThresholdKeys(total, threshold, logger) {
const lib = await initGnark(logger);
const { generateThresholdKeys, vfree } = lib;
const params = { total: total, threshold: threshold };
const res = executeGnarkFn(generateThresholdKeys, JSON.stringify(params));
const resJson = Buffer.from(koffi.decode(res.r0, 'unsigned char', res.r1)).toString();
vfree(res.r0); // Avoid memory leak!
const parsed = JSON.parse(resJson);
const shares = [];
for (let i = 0; i < parsed.shares.length; i++) {
const share = parsed.shares[i];
shares.push({
index: share.index,
publicKey: toUint8Array(share.publicKey),
privateKey: toUint8Array(share.privateKey),
});
}
return {
publicKey: toUint8Array(parsed.publicKey),
privateKey: toUint8Array(parsed.privateKey),
shares: shares,
};
},
async generateOPRFRequestData(data, domainSeparator, logger) {
const lib = await initGnark(logger);
const params = {
data: Base64.fromUint8Array(data),
domainSeparator: domainSeparator,
};
const parsed = await executeGnarkFnAndGetJson(lib.generateOPRFRequest, JSON.stringify(params));
return {
mask: toUint8Array(parsed.mask),
maskedData: toUint8Array(parsed.maskedData),
secretElements: [
toUint8Array(parsed.secretElements[0]),
toUint8Array(parsed.secretElements[1])
]
};
},
async finaliseOPRF(serverPublicKey, request, responses, logger) {
const lib = await initGnark(logger);
const params = {
serverPublicKey: fromUint8Array(serverPublicKey),
request: {
mask: fromUint8Array(request.mask),
maskedData: fromUint8Array(request.maskedData),
secretElements: [
fromUint8Array(request.secretElements[0]),
fromUint8Array(request.secretElements[1])
]
},
responses: responses.map(({ publicKeyShare, evaluated, c, r }) => ({
publicKeyShare: fromUint8Array(publicKeyShare),
evaluated: fromUint8Array(evaluated),
c: fromUint8Array(c),
r: fromUint8Array(r),
}))
};
const parsed = await executeGnarkFnAndGetJson(lib.toprfFinalize, JSON.stringify(params));
return toUint8Array(parsed.output);
},
async evaluateOPRF(serverPrivate, maskedData, logger) {
const lib = await initGnark(logger);
const { oprfEvaluate, vfree } = lib;
const params = {
serverPrivate: fromUint8Array(serverPrivate),
maskedData: fromUint8Array(maskedData),
};
const res = executeGnarkFn(oprfEvaluate, JSON.stringify(params));
const resJson = Buffer.from(koffi.decode(res.r0, 'unsigned char', res.r1)).toString();
vfree(res.r0); // Avoid memory leak!
const parsed = JSON.parse(resJson);
return {
evaluated: toUint8Array(parsed.evaluated),
c: toUint8Array(parsed.c),
r: toUint8Array(parsed.r),
};
},
};
async function initGnark(logger) {
const { ext, id } = ALGS_MAP[algorithm];
return initGnarkAlgorithm(id, ext, fetcher, logger);
}
}