UNPKG

@reclaimprotocol/attestor-core

Version:

<div> <div> <img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/Attestor-Core.png" /> </div> </div>

444 lines 38.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeZkProofGenerator = makeZkProofGenerator; exports.verifyZkPacket = verifyZkPacket; exports.makeDefaultZkOperator = makeDefaultZkOperator; exports.makeDefaultOPRFOperator = makeDefaultOPRFOperator; exports.getEngineString = getEngineString; exports.getEngineProto = getEngineProto; const tls_1 = require("@reclaimprotocol/tls"); const zk_symmetric_crypto_1 = require("@reclaimprotocol/zk-symmetric-crypto"); const config_1 = require("../config"); const api_1 = require("../proto/api"); const env_1 = require("../utils/env"); const error_1 = require("../utils/error"); const generics_1 = require("../utils/generics"); const logger_1 = require("../utils/logger"); const redactions_1 = require("../utils/redactions"); const ZK_CONCURRENCY = +((0, env_1.getEnvVariable)('ZK_CONCURRENCY') || config_1.DEFAULT_ZK_CONCURRENCY); async function makeZkProofGenerator({ zkOperators, oprfOperators, logger = logger_1.logger, zkProofConcurrency = ZK_CONCURRENCY, cipherSuite, zkEngine = 'snarkjs' }) { const { default: PQueue } = await import('p-queue'); const zkQueue = new PQueue({ concurrency: zkProofConcurrency, autoStart: true, }); const packetsToProve = []; logger = (logger || logger_1.logger).child({ module: 'zk', zkEngine: zkEngine }); let zkProofsToGen = 0; return { /** * Adds the given packet to the list of packets to * generate ZK proofs for. * * Call `generateProofs()` to finally generate the proofs */ async addPacketToProve(packet, { redactedPlaintext, toprfs }, onGeneratedProofs) { if (packet.type === 'plaintext') { throw new Error('Cannot generate proof for plaintext'); } const alg = (0, generics_1.getZkAlgorithmForCipherSuite)(cipherSuite); const chunkSizeBytes = getChunkSizeBytes(alg); const key = await tls_1.crypto.exportKey(packet.encKey); const iv = packet.iv; const ciphertext = (0, generics_1.getPureCiphertext)(packet.ciphertext, cipherSuite); const packetToProve = { onGeneratedProofs, algorithm: alg, proofsToGenerate: [], iv: packet.fixedIv, }; const slicesDone = []; // first we'll handle all TOPRF blocks // we do these first, because they can span multiple chunks // & we need to be able to span the right chunks for (const toprf of toprfs || []) { const fromIndex = getIdealOffsetForToprfBlock(alg, toprf); const toIndex = Math.min(fromIndex + chunkSizeBytes, ciphertext.length); // ensure this OPRF block doesn't overlap with any other OPRF block const slice = { fromIndex, toIndex }; assertNoOverlapOprf(slice); addProofsToGenerate(slice, { ...toprf, dataLocation: { ...toprf.dataLocation, fromIndex: toprf.dataLocation.fromIndex - fromIndex } }); } // now we'll go through the rest of the ciphertext, and add proofs // for the sections that haven't been covered by the TOPRF blocks const slicesCp = sortSlices(slicesDone.slice()); let fromIndex = 0; for (const done of slicesCp) { if (done.fromIndex > fromIndex) { addProofsToGenerate({ fromIndex, toIndex: done.fromIndex }); } fromIndex = done.toIndex; } if (fromIndex < ciphertext.length) { addProofsToGenerate({ fromIndex, toIndex: ciphertext.length }); } // generate proofs in order of start index packetToProve.proofsToGenerate .sort((a, b) => a.startIdx - b.startIdx); packetsToProve.push(packetToProve); function assertNoOverlapOprf(slice) { for (const done of slicesDone) { if ( // 1d box overlap slice.fromIndex < done.toIndex && slice.toIndex > done.fromIndex) { throw new error_1.AttestorError('ERROR_BAD_REQUEST', 'Single chunk has multiple OPRFs'); } } } function addProofsToGenerate({ fromIndex, toIndex }, toprf) { for (let i = fromIndex; i < toIndex; i += chunkSizeBytes) { const slice = { fromIndex: i, toIndex: Math.min(i + chunkSizeBytes, toIndex) }; slicesDone.push(slice); const proofParams = getProofGenerationParamsForSlice({ key, iv, ciphertext, redactedPlaintext, slice, toprf, }); if (!proofParams) { continue; } packetToProve.proofsToGenerate.push(proofParams); zkProofsToGen += 1; } } }, getTotalChunksToProve() { return zkProofsToGen; }, async generateProofs(onChunkDone) { var _a; if (!packetsToProve.length) { return; } const start = Date.now(); const tasks = []; for (const { onGeneratedProofs, algorithm, proofsToGenerate } of packetsToProve) { const proofs = []; let proofsLeft = proofsToGenerate.length; for (const proofToGen of proofsToGenerate) { tasks.push(zkQueue.add(async () => { const proof = await generateProofForChunk(algorithm, proofToGen); onChunkDone === null || onChunkDone === void 0 ? void 0 : onChunkDone(); proofs.push(proof); proofsLeft -= 1; if (proofsLeft === 0) { onGeneratedProofs(proofs); } }, { throwOnTimeout: true })); } } await Promise.all(tasks); logger === null || logger === void 0 ? void 0 : logger.info({ durationMs: Date.now() - start, zkProofsToGen }, 'generated ZK proofs'); // reset the packets to prove packetsToProve.splice(0, packetsToProve.length); zkProofsToGen = 0; // release ZK resources to free up memory const alg = (0, generics_1.getZkAlgorithmForCipherSuite)(cipherSuite); const zkOperator = await getZkOperatorForAlgorithm(alg); (_a = zkOperator.release) === null || _a === void 0 ? void 0 : _a.call(zkOperator); }, }; async function generateProofForChunk(algorithm, { startIdx, redactedPlaintext, privateInput, publicInput, toprf, }) { const operator = toprf ? getOprfOperatorForAlgorithm(algorithm) : getZkOperatorForAlgorithm(algorithm); const proof = await (0, zk_symmetric_crypto_1.generateProof)({ algorithm, privateInput, publicInput, operator, logger, ...(toprf ? { toprf: { pos: toprf.dataLocation.fromIndex, len: toprf.dataLocation.length, output: toprf.nullifier, responses: toprf.responses, domainSeparator: config_1.TOPRF_DOMAIN_SEPARATOR }, mask: toprf.mask, } : {}) }); logger === null || logger === void 0 ? void 0 : logger.debug({ startIdx }, 'generated proof for chunk'); return { // backwards compatibility proofJson: '', proofData: typeof proof.proofData === 'string' ? (0, tls_1.strToUint8Array)(proof.proofData) : proof.proofData, toprf, decryptedRedactedCiphertext: proof.plaintext, redactedPlaintext, startIdx }; } function getZkOperatorForAlgorithm(algorithm) { return (zkOperators === null || zkOperators === void 0 ? void 0 : zkOperators[algorithm]) || makeDefaultZkOperator(algorithm, zkEngine, logger); } function getOprfOperatorForAlgorithm(algorithm) { return (oprfOperators === null || oprfOperators === void 0 ? void 0 : oprfOperators[algorithm]) || makeDefaultOPRFOperator(algorithm, zkEngine, logger); } } /** * Verify the given ZK proof */ async function verifyZkPacket({ cipherSuite, ciphertext, zkReveal, zkOperators, oprfOperators, logger = logger_1.logger, zkEngine = 'snarkjs', iv, recordNumber }) { if (!zkReveal) { throw new Error('No ZK reveal'); } const { proofs } = zkReveal; const algorithm = (0, generics_1.getZkAlgorithmForCipherSuite)(cipherSuite); const recordIV = (0, generics_1.getRecordIV)(ciphertext, cipherSuite); ciphertext = (0, generics_1.getPureCiphertext)(ciphertext, cipherSuite); /** * to verify if the user has given us the correct redacted plaintext, * and isn't providing plaintext that they haven't proven they have * we start with a fully redacted plaintext, and then replace the * redacted parts with the plaintext that the user has provided * in the proofs */ const realRedactedPlaintext = new Uint8Array(ciphertext.length).fill(redactions_1.REDACTION_CHAR_CODE); await Promise.all(proofs.map(async (proof, i) => { try { await verifyProofPacket(proof); } catch (e) { e.message += ` (chunk ${i}, startIdx ${proof.startIdx})`; throw e; } })); return { redactedPlaintext: realRedactedPlaintext }; async function verifyProofPacket({ proofData, proofJson, decryptedRedactedCiphertext, redactedPlaintext, startIdx, toprf, }) { var _a, _b, _c; // get the ciphertext chunk we received from the server // the ZK library, will verify that the decrypted redacted // ciphertext matches the ciphertext received from the server const ciphertextChunk = ciphertext.slice(startIdx, startIdx + redactedPlaintext.length); // redact ciphertext if plaintext is redacted // to prepare for decryption in ZK circuit // the ZK circuit will take in the redacted ciphertext, // which shall produce the redacted plaintext for (let i = 0; i < ciphertextChunk.length; i++) { if (redactedPlaintext[i] === redactions_1.REDACTION_CHAR_CODE) { ciphertextChunk[i] = redactions_1.REDACTION_CHAR_CODE; } } // redact OPRF indices -- because they'll incorrectly // be marked as incongruent let comparePlaintext = redactedPlaintext; if (toprf) { comparePlaintext = new Uint8Array(redactedPlaintext); for (let i = 0; i < toprf.dataLocation.length; i++) { comparePlaintext[i + toprf.dataLocation.fromIndex] = redactions_1.REDACTION_CHAR_CODE; } // the transcript will contain only the stringified // nullifier. So here, we'll compare the provable // binary nullifier with the stringified nullifier // that the user has provided const nulliferStr = (0, redactions_1.binaryHashToStr)(toprf.nullifier, toprf.dataLocation.length); const txtHash = redactedPlaintext.slice((_a = toprf.dataLocation) === null || _a === void 0 ? void 0 : _a.fromIndex, ((_b = toprf.dataLocation) === null || _b === void 0 ? void 0 : _b.fromIndex) + ((_c = toprf.dataLocation) === null || _c === void 0 ? void 0 : _c.length)); if ((0, generics_1.uint8ArrayToStr)(txtHash) !== nulliferStr .slice(0, txtHash.length)) { throw new Error('OPRF nullifier not congruent'); } } if (!(0, redactions_1.isRedactionCongruent)(comparePlaintext, decryptedRedactedCiphertext)) { throw new Error('redacted ciphertext not congruent'); } let nonce = (0, tls_1.concatenateUint8Arrays)([iv, recordIV]); if (!recordIV.length) { nonce = (0, tls_1.generateIV)(nonce, recordNumber); } await (0, zk_symmetric_crypto_1.verifyProof)({ proof: { algorithm, proofData: proofData.length ? proofData : (0, tls_1.strToUint8Array)(proofJson), plaintext: decryptedRedactedCiphertext, }, publicInput: { ciphertext: ciphertextChunk, iv: nonce, offsetBytes: startIdx }, logger, ...(toprf ? { operator: getOprfOperator(), toprf: { pos: toprf.dataLocation.fromIndex, len: toprf.dataLocation.length, domainSeparator: config_1.TOPRF_DOMAIN_SEPARATOR, output: toprf.nullifier, responses: toprf.responses, } } : { operator: getZkOperator() }) }); logger === null || logger === void 0 ? void 0 : logger.debug({ startIdx, endIdx: startIdx + redactedPlaintext.length }, 'verified proof'); realRedactedPlaintext.set(redactedPlaintext, startIdx); } function getZkOperator() { return (zkOperators === null || zkOperators === void 0 ? void 0 : zkOperators[algorithm]) || makeDefaultZkOperator(algorithm, zkEngine, logger); } function getOprfOperator() { return (oprfOperators === null || oprfOperators === void 0 ? void 0 : oprfOperators[algorithm]) || makeDefaultOPRFOperator(algorithm, zkEngine, logger); } } // the chunk size of the ZK circuit in bytes // this will be >= the block size function getChunkSizeBytes(alg) { const { chunkSize, bitsPerWord } = zk_symmetric_crypto_1.CONFIG[alg]; return chunkSize * bitsPerWord / 8; } const zkEngines = {}; const oprfEngines = {}; const operatorMakers = { 'snarkjs': zk_symmetric_crypto_1.makeSnarkJsZKOperator, 'gnark': zk_symmetric_crypto_1.makeGnarkZkOperator, }; const OPRF_OPERATOR_MAKERS = { 'gnark': zk_symmetric_crypto_1.makeGnarkOPRFOperator }; function makeDefaultZkOperator(algorithm, zkEngine, logger) { let zkOperators = zkEngines[zkEngine]; if (!zkOperators) { zkEngines[zkEngine] = {}; zkOperators = zkEngines[zkEngine]; } if (!zkOperators[algorithm]) { const isNode = (0, env_1.detectEnvironment)() === 'node'; const opType = isNode ? 'local' : 'remote'; logger === null || logger === void 0 ? void 0 : logger.info({ type: opType, algorithm }, 'fetching zk operator'); const fetcher = opType === 'local' ? (0, zk_symmetric_crypto_1.makeLocalFileFetch)() : (0, zk_symmetric_crypto_1.makeRemoteFileFetch)({ baseUrl: config_1.DEFAULT_REMOTE_FILE_FETCH_BASE_URL, }); const maker = operatorMakers[zkEngine]; if (!maker) { throw new Error(`No ZK operator maker for ${zkEngine}`); } zkOperators[algorithm] = maker({ algorithm, fetcher }); } return zkOperators[algorithm]; } function makeDefaultOPRFOperator(algorithm, zkEngine, logger) { let operators = oprfEngines[zkEngine]; if (!operators) { oprfEngines[zkEngine] = {}; operators = oprfEngines[zkEngine]; } if (!operators[algorithm]) { const isNode = (0, env_1.detectEnvironment)() === 'node'; const type = isNode ? 'local' : 'remote'; logger === null || logger === void 0 ? void 0 : logger.info({ type, algorithm }, 'fetching oprf operator'); const fetcher = type === 'local' ? (0, zk_symmetric_crypto_1.makeLocalFileFetch)() : (0, zk_symmetric_crypto_1.makeRemoteFileFetch)({ baseUrl: config_1.DEFAULT_REMOTE_FILE_FETCH_BASE_URL, }); const maker = OPRF_OPERATOR_MAKERS[zkEngine]; if (!maker) { throw new Error(`No OPRF operator maker for ${zkEngine}`); } operators[algorithm] = maker({ algorithm, fetcher }); } return operators[algorithm]; } function getEngineString(engine) { if (engine === api_1.ZKProofEngine.ZK_ENGINE_GNARK) { return 'gnark'; } if (engine === api_1.ZKProofEngine.ZK_ENGINE_SNARKJS) { return 'snarkjs'; } throw new Error(`Unknown ZK engine: ${engine}`); } function getEngineProto(engine) { if (engine === 'gnark') { return api_1.ZKProofEngine.ZK_ENGINE_GNARK; } if (engine === 'snarkjs') { return api_1.ZKProofEngine.ZK_ENGINE_SNARKJS; } throw new Error(`Unknown ZK engine: ${engine}`); } function getProofGenerationParamsForSlice({ key, iv, ciphertext, redactedPlaintext, slice: { fromIndex, toIndex }, toprf, }) { const ciphertextChunk = ciphertext.slice(fromIndex, toIndex); const plaintextChunk = redactedPlaintext.slice(fromIndex, toIndex); if ((0, redactions_1.isFullyRedacted)(plaintextChunk)) { return; } // redact ciphertext if plaintext is redacted // to prepare for decryption in ZK circuit // the ZK circuit will take in the redacted ciphertext, // which shall produce the redacted plaintext for (let i = 0; i < ciphertextChunk.length; i++) { if (plaintextChunk[i] === redactions_1.REDACTION_CHAR_CODE) { ciphertextChunk[i] = redactions_1.REDACTION_CHAR_CODE; } } return { startIdx: fromIndex, redactedPlaintext: plaintextChunk, privateInput: { key }, publicInput: { ciphertext: ciphertextChunk, iv, offsetBytes: fromIndex }, toprf }; } /** * Get the ideal location to generate a ZK proof for a TOPRF block. * Ideally it should be put into a slice that's a divisor of the chunk size, * as that'll minimize the number of proofs that need to be generated. * @returns the offset in bytes */ function getIdealOffsetForToprfBlock(alg, { dataLocation }) { const chunkSizeBytes = getChunkSizeBytes(alg); const offsetChunks = Math.floor(dataLocation.fromIndex / chunkSizeBytes) * chunkSizeBytes; const endOffsetChunks = Math.floor((dataLocation.fromIndex + dataLocation.length) / chunkSizeBytes); // happy case -- the OPRF block fits into a single chunk, that's a // divisor of the chunk size if (endOffsetChunks === offsetChunks) { return offsetChunks * chunkSizeBytes; } const blockSizeBytes = (0, zk_symmetric_crypto_1.getBlockSizeBytes)(alg); const offsetBytes = Math.floor(dataLocation.fromIndex / blockSizeBytes) * blockSizeBytes; if ((dataLocation.fromIndex + dataLocation.length) - offsetBytes > chunkSizeBytes) { throw new error_1.AttestorError('ERROR_BAD_REQUEST', 'OPRF data cannot fit into a single chunk'); } return offsetBytes; } function sortSlices(slices) { return slices.sort((a, b) => a.fromIndex - b.fromIndex); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiemsuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvemsudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUErRUEsb0RBZ1FDO0FBS0Qsd0NBc0tDO0FBMEJELHNEQThCQztBQUVELDBEQThCQztBQUVELDBDQVVDO0FBR0Qsd0NBVUM7QUEzbUJELDhDQUErRztBQUMvRyw4RUFleUQ7QUFDekQsdUNBQStHO0FBQy9HLHVDQUEwSDtBQUUxSCx1Q0FBaUU7QUFDakUsMkNBQStDO0FBQy9DLGlEQUFrSDtBQUNsSCw2Q0FBbUQ7QUFDbkQscURBQWtIO0FBa0RsSCxNQUFNLGNBQWMsR0FBRyxDQUFDLENBQ3ZCLElBQUEsb0JBQWMsRUFBQyxnQkFBZ0IsQ0FBQztPQUM3QiwrQkFBc0IsQ0FDekIsQ0FBQTtBQUVNLEtBQUssVUFBVSxvQkFBb0IsQ0FDekMsRUFDQyxXQUFXLEVBQ1gsYUFBYSxFQUNiLE1BQU0sR0FBRyxlQUFNLEVBQ2Ysa0JBQWtCLEdBQUcsY0FBYyxFQUNuQyxXQUFXLEVBQ1gsUUFBUSxHQUFHLFNBQVMsRUFDQztJQUd0QixNQUFNLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxHQUFHLE1BQU0sTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFBO0lBQ25ELE1BQU0sT0FBTyxHQUFHLElBQUksTUFBTSxDQUFDO1FBQzFCLFdBQVcsRUFBRSxrQkFBa0I7UUFDL0IsU0FBUyxFQUFFLElBQUk7S0FDZixDQUFDLENBQUE7SUFFRixNQUFNLGNBQWMsR0FBc0IsRUFBRSxDQUFBO0lBRTVDLE1BQU0sR0FBRyxDQUFDLE1BQU0sSUFBSSxlQUFNLENBQUMsQ0FBQyxLQUFLLENBQUMsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFBO0lBQ3ZFLElBQUksYUFBYSxHQUFHLENBQUMsQ0FBQTtJQUVyQixPQUFPO1FBQ047Ozs7O1dBS0c7UUFDSCxLQUFLLENBQUMsZ0JBQWdCLENBQ3JCLE1B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