@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
JavaScript
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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
;