UNPKG

@reclaimprotocol/zk-symmetric-crypto

Version:

JS Wrappers for Various ZK Snark Circuits

122 lines (121 loc) 4.41 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateProof = generateProof; exports.verifyProof = verifyProof; exports.generateZkWitness = generateZkWitness; exports.getPublicSignals = getPublicSignals; const config_1 = require("./config"); const utils_1 = require("./utils"); /** * Generate ZK proof for CHACHA20-CTR encryption. * Circuit proves that the ciphertext is a * valid encryption of the given plaintext. * The plaintext can be partially redacted. */ async function generateProof(opts) { const { algorithm, operator, logger } = opts; const { witness, plaintextArray } = await generateZkWitness(opts); let wtnsSerialised; if ('mask' in opts) { wtnsSerialised = await operator.generateWitness({ ...witness, toprf: opts.toprf, mask: opts.mask, }); } else { // @ts-expect-error wtnsSerialised = await operator.generateWitness(witness); } const { proof } = await operator.groth16Prove(wtnsSerialised, logger); return { algorithm, proofData: proof, plaintext: plaintextArray }; } /** * Verify a ZK proof for CHACHA20-CTR encryption. * * @param proofs JSON proof generated by "generateProof" * @param publicInput * @param zkey */ async function verifyProof(opts) { const publicSignals = getPublicSignals(opts); const { proof: { proofData }, operator, logger } = opts; let verified; if ('toprf' in opts) { verified = await operator.groth16Verify({ ...publicSignals, toprf: opts.toprf }, proofData, logger); } else { // serialise to array of numbers for the ZK circuit verified = await operator.groth16Verify( // @ts-expect-error publicSignals, proofData, logger); } if (!verified) { throw new Error('invalid proof'); } } /** * Generate a ZK witness for the symmetric encryption circuit. * This witness can then be used to generate a ZK proof, * using the operator's groth16Prove function. */ async function generateZkWitness({ algorithm, privateInput: { key }, publicInput: { ciphertext, iv, offsetBytes = 0 }, }) { const { keySizeBytes, ivSizeBytes, } = config_1.CONFIG[algorithm]; if (key.length !== keySizeBytes) { throw new Error(`key must be ${keySizeBytes} bytes`); } if (iv.length !== ivSizeBytes) { throw new Error(`iv must be ${ivSizeBytes} bytes`); } const startCounter = (0, utils_1.getCounterForByteOffset)(algorithm, offsetBytes); const ciphertextArray = padCiphertextToChunkSize(algorithm, ciphertext); const plaintextArray = await decryptCiphertext({ algorithm, key, iv, startOffset: offsetBytes, ciphertext: ciphertextArray, }); const witness = { key, nonce: iv, counter: startCounter, in: ciphertextArray, out: plaintextArray, }; return { witness, plaintextArray }; } function getPublicSignals({ proof: { algorithm, plaintext }, publicInput: { ciphertext, iv, offsetBytes = 0 }, }) { const startCounter = (0, utils_1.getCounterForByteOffset)(algorithm, offsetBytes); const ciphertextArray = padCiphertextToChunkSize(algorithm, ciphertext); if (ciphertextArray.length !== plaintext.length) { throw new Error('ciphertext and plaintext must be the same length'); } return { nonce: iv, counter: startCounter, in: ciphertextArray, out: plaintext, }; } function padCiphertextToChunkSize(alg, ciphertext) { const { chunkSize, bitsPerWord } = config_1.CONFIG[alg]; const expectedSizeBytes = (chunkSize * bitsPerWord) / 8; if (ciphertext.length > expectedSizeBytes) { throw new Error(`ciphertext must be <= ${expectedSizeBytes}b`); } if (ciphertext.length < expectedSizeBytes) { const arr = new Uint8Array(expectedSizeBytes).fill(0); arr.set(ciphertext); ciphertext = arr; } return ciphertext; } async function decryptCiphertext({ algorithm, key, iv, startOffset, ciphertext, }) { const { encrypt } = config_1.CONFIG[algorithm]; // fake the start of the ciphertext (it's irrelevant) const inp = new Uint8Array(startOffset + ciphertext.length); inp.set(ciphertext, startOffset); const out = await encrypt({ key, iv, in: inp }); return out.slice(startOffset); }