UNPKG

@zkp2p/reclaim-witness-sdk

Version:

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

200 lines 17.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertValidClaimRequest = assertValidClaimRequest; exports.assertValidProviderTranscript = assertValidProviderTranscript; exports.assertTranscriptsMatch = assertTranscriptsMatch; exports.decryptTranscript = decryptTranscript; exports.getWithoutHeader = getWithoutHeader; const tls_1 = require("@reclaimprotocol/tls"); const api_1 = require("../../proto/api"); const providers_1 = require("../../providers"); const generics_1 = require("../../server/utils/generics"); const process_handshake_1 = require("../../server/utils/process-handshake"); const utils_1 = require("../../utils"); const signatures_1 = require("../../utils/signatures"); /** * Asserts that the claim request is valid. * * 1. We begin by verifying the signature of the claim request. * 2. Next, we produce the transcript of the TLS exchange * from the proofs provided by the client. * 3. We then pull the provider the client is trying to claim * from * 4. We then use the provider's verification function to verify * whether the claim is valid. * * If any of these steps fail, we throw an error. */ async function assertValidClaimRequest(request, metadata, logger) { var _a; const { data, signatures: { requestSignature } = {}, zkEngine, fixedServerIV, fixedClientIV } = request; if (!data) { throw new utils_1.AttestorError('ERROR_INVALID_CLAIM', 'No info provided on claim request'); } if (!(requestSignature === null || requestSignature === void 0 ? void 0 : requestSignature.length)) { throw new utils_1.AttestorError('ERROR_INVALID_CLAIM', 'No signature provided on claim request'); } // verify request signature const serialisedReq = api_1.ClaimTunnelRequest .encode({ ...request, signatures: undefined }) .finish(); const { verify: verifySig } = signatures_1.SIGNATURES[metadata.signatureType]; const verified = await verifySig(serialisedReq, requestSignature, data.owner); if (!verified) { throw new utils_1.AttestorError('ERROR_INVALID_CLAIM', 'Invalid signature on claim request'); } const receipt = await decryptTranscript(request.transcript, logger, zkEngine === api_1.ZKProofEngine.ZK_ENGINE_GNARK ? 'gnark' : 'snarkjs', fixedServerIV, fixedClientIV); const reqHost = (_a = request.request) === null || _a === void 0 ? void 0 : _a.host; if (receipt.hostname !== reqHost) { throw new Error(`Expected server name ${reqHost}, got ${receipt.hostname}`); } // get all application data messages const applData = (0, utils_1.extractApplicationDataFromTranscript)(receipt); const newData = await assertValidProviderTranscript(applData, data, logger, { version: metadata.clientVersion }); if (newData !== data) { logger.info({ newData }, 'updated claim info'); } return newData; } /** * Verify that the transcript contains a valid claim * for the provider. */ async function assertValidProviderTranscript(applData, info, logger, providerCtx) { var _a; const providerName = info.provider; const provider = providers_1.providers[providerName]; if (!provider) { throw new utils_1.AttestorError('ERROR_INVALID_CLAIM', `Unsupported provider: ${providerName}`); } const params = (0, generics_1.niceParseJsonObject)(info.parameters, 'params'); const ctx = (0, generics_1.niceParseJsonObject)(info.context, 'context'); (0, utils_1.assertValidateProviderParams)(providerName, params); const rslt = await provider.assertValidProviderReceipt({ receipt: applData, params, logger, ctx: providerCtx }); ctx.providerHash = (0, utils_1.hashProviderParams)(params); const extractedParameters = (rslt === null || rslt === void 0 ? void 0 : rslt.extractedParameters) || {}; if (Object.keys(extractedParameters).length) { ctx.extractedParameters = extractedParameters; } info.context = (_a = (0, utils_1.canonicalStringify)(ctx)) !== null && _a !== void 0 ? _a : ''; return info; } /** * Verify that the transcript provided by the client * matches the transcript of the tunnel, the server * has created. */ function assertTranscriptsMatch(clientTranscript, tunnelTranscript) { const clientSends = (0, tls_1.concatenateUint8Arrays)(clientTranscript .filter(m => m.sender === api_1.TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT) .map(m => m.message)); const tunnelSends = (0, tls_1.concatenateUint8Arrays)(tunnelTranscript .filter(m => m.sender === 'client') .map(m => m.message)); if (!(0, tls_1.areUint8ArraysEqual)(clientSends, tunnelSends)) { throw utils_1.AttestorError.badRequest('Outgoing messages from client do not match the tunnel transcript'); } const clientRecvs = (0, tls_1.concatenateUint8Arrays)(clientTranscript .filter(m => m.sender === api_1.TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER) .map(m => m.message)); const tunnelRecvs = (0, tls_1.concatenateUint8Arrays)(tunnelTranscript .filter(m => m.sender === 'server') .map(m => m.message)) // We only need to compare the first N messages // that the client claims to have received // the rest are not relevant -- so even if they're // not present in the tunnel transcript, it's fine .slice(0, clientRecvs.length); if (!(0, tls_1.areUint8ArraysEqual)(clientRecvs, tunnelRecvs)) { throw utils_1.AttestorError.badRequest('Incoming messages from server do not match the tunnel transcript'); } } async function decryptTranscript(transcript, logger, zkEngine, serverIV, clientIV) { const { tlsVersion, cipherSuite, hostname, nextMsgIndex } = await (0, process_handshake_1.processHandshake)(transcript, logger); let clientRecordNumber = tlsVersion === 'TLS1_3' ? -1 : 0; // TLS 1.3 has already one record encrypted at this point let serverRecordNumber = clientRecordNumber; transcript = transcript.slice(nextMsgIndex); const decryptedTranscript = []; for (const [i, { sender, message, reveal: { zkReveal, directReveal } = {} }] of transcript.entries()) { //start with first message after last handshake message await getDecryptedMessage(sender, message, directReveal, zkReveal, i); } return { transcript: decryptedTranscript, hostname: hostname, tlsVersion: tlsVersion, }; async function getDecryptedMessage(sender, message, directReveal, zkReveal, i) { var _a, _b; try { const isServer = sender === api_1.TranscriptMessageSenderType .TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER; const recordHeader = message.slice(0, 5); const content = getWithoutHeader(message); if (isServer) { serverRecordNumber++; } else { clientRecordNumber++; } let redacted = true; let plaintext = undefined; let plaintextLength; if ((_a = directReveal === null || directReveal === void 0 ? void 0 : directReveal.key) === null || _a === void 0 ? void 0 : _a.length) { const result = await (0, utils_1.decryptDirect)(directReveal, cipherSuite, recordHeader, tlsVersion, content); plaintext = result.plaintext; redacted = false; plaintextLength = plaintext.length; } else if ((_b = zkReveal === null || zkReveal === void 0 ? void 0 : zkReveal.proofs) === null || _b === void 0 ? void 0 : _b.length) { const result = await (0, utils_1.verifyZkPacket)({ ciphertext: content, zkReveal, logger, cipherSuite, zkEngine: zkEngine, iv: sender === api_1.TranscriptMessageSenderType .TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER ? serverIV : clientIV, recordNumber: isServer ? serverRecordNumber : clientRecordNumber }); plaintext = result.redactedPlaintext; redacted = false; plaintextLength = plaintext.length; } else { plaintext = content; plaintextLength = plaintext.length; } decryptedTranscript.push({ sender: sender === api_1.TranscriptMessageSenderType .TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT ? 'client' : 'server', redacted, message: plaintext, recordHeader, plaintextLength, }); } catch (error) { throw new utils_1.AttestorError('ERROR_INVALID_CLAIM', `error in handling packet at idx ${i}: ${error}`, { packetIdx: i, error: error, }); } } } function getWithoutHeader(message) { // strip the record header (xx 03 03 xx xx) return message.slice(5); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXNzZXJ0LXZhbGlkLWNsYWltLXJlcXVlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VydmVyL3V0aWxzL2Fzc2VydC12YWxpZC1jbGFpbS1yZXF1ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBK0NBLDBEQW9FQztBQU1ELHNFQXFDQztBQU9ELHdEQTJDQztBQUVELDhDQTZHQztBQUVELDRDQUdDO0FBcFVELDhDQUc2QjtBQUU3Qix1Q0FRc0I7QUFDdEIsNkNBQXlDO0FBQ3pDLHdEQUErRDtBQUMvRCwwRUFBcUU7QUFTckUscUNBTWtCO0FBQ2xCLHFEQUFpRDtBQUVqRDs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSSxLQUFLLFVBQVUsdUJBQXVCLENBQzVDLE9BQTJCLEVBQzNCLFFBQXFCLEVBQ3JCLE1BQWM7O0lBRWQsTUFBTSxFQUNMLElBQUksRUFDSixVQUFVLEVBQUUsRUFBRSxnQkFBZ0IsRUFBRSxHQUFHLEVBQUUsRUFDckMsUUFBUSxFQUNSLGFBQWEsRUFDYixhQUFhLEVBQ2IsR0FBRyxPQUFPLENBQUE7SUFDWCxJQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDVixNQUFNLElBQUkscUJBQWEsQ0FDdEIscUJBQXFCLEVBQ3JCLG1DQUFtQyxDQUNuQyxDQUFBO0lBQ0YsQ0FBQztJQUVELElBQUcsQ0FBQyxDQUFBLGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLE1BQU0sQ0FBQSxFQUFFLENBQUM7UUFDOUIsTUFBTSxJQUFJLHFCQUFhLENBQ3RCLHFCQUFxQixFQUNyQix3Q0FBd0MsQ0FDeEMsQ0FBQTtJQUNGLENBQUM7SUFFRCwyQkFBMkI7SUFDM0IsTUFBTSxhQUFhLEdBQUcsd0JBQWtCO1NBQ3RDLE1BQU0sQ0FBQyxFQUFFLEdBQUcsT0FBTyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsQ0FBQztTQUM3QyxNQUFNLEVBQUUsQ0FBQTtJQUNWLE1BQU0sRUFBRSxNQUFNLEVBQUUs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