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