@reclaimprotocol/attestor-core
Version:
<div> <div> <img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/Attestor-Core.png" /> </div> </div>
348 lines • 22.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.unixTimestampSeconds = void 0;
exports.uint8ArrayToStr = uint8ArrayToStr;
exports.getTranscriptString = getTranscriptString;
exports.findIndexInUint8Array = findIndexInUint8Array;
exports.uint8ArrayToBinaryStr = uint8ArrayToBinaryStr;
exports.gunzipSync = gunzipSync;
exports.getZkAlgorithmForCipherSuite = getZkAlgorithmForCipherSuite;
exports.getPureCiphertext = getPureCiphertext;
exports.getRecordIV = getRecordIV;
exports.getProviderValue = getProviderValue;
exports.generateRpcMessageId = generateRpcMessageId;
exports.generateSessionId = generateSessionId;
exports.generateTunnelId = generateTunnelId;
exports.makeRpcEvent = makeRpcEvent;
exports.getRpcTypeFromKey = getRpcTypeFromKey;
exports.getRpcResponseType = getRpcResponseType;
exports.getRpcRequestType = getRpcRequestType;
exports.isApplicationData = isApplicationData;
exports.extractArrayBufferFromWsData = extractArrayBufferFromWsData;
exports.getRpcRequest = getRpcRequest;
exports.extractApplicationDataFromTranscript = extractApplicationDataFromTranscript;
exports.extractHandshakeFromTranscript = extractHandshakeFromTranscript;
exports.decryptDirect = decryptDirect;
exports.packRpcMessages = packRpcMessages;
exports.ethersStructToPlainObject = ethersStructToPlainObject;
const tls_1 = require("@reclaimprotocol/tls");
const zk_symmetric_crypto_1 = require("@reclaimprotocol/zk-symmetric-crypto");
const api_1 = require("../proto/api");
const DEFAULT_REDACTION_DATA = new Uint8Array(4)
.fill(zk_symmetric_crypto_1.REDACTION_CHAR_CODE);
function uint8ArrayToStr(arr) {
return new TextDecoder().decode(arr);
}
function getTranscriptString(receipt) {
var _a;
const applMsgs = extractApplicationDataFromTranscript(receipt);
const strList = [];
for (const { message, sender } of applMsgs) {
const content = uint8ArrayToStr(message);
if ((_a = strList[strList.length - 1]) === null || _a === void 0 ? void 0 : _a.startsWith(sender)) {
strList[strList.length - 1] += content;
}
else {
strList.push(`${sender}: ${content}`);
}
}
return strList.join('\n');
}
const unixTimestampSeconds = () => Math.floor(Date.now() / 1000);
exports.unixTimestampSeconds = unixTimestampSeconds;
/**
* Find index of needle in haystack
*/
function findIndexInUint8Array(haystack, needle) {
for (let i = 0; i < haystack.length; i++) {
if ((0, tls_1.areUint8ArraysEqual)(haystack.slice(i, i + needle.length), needle)) {
return i;
}
}
return -1;
}
/**
* convert a Uint8Array to a binary encoded str
* from: https://github.com/feross/buffer/blob/795bbb5bda1b39f1370ebd784bea6107b087e3a7/index.js#L1063
* @param buf
* @returns
*/
function uint8ArrayToBinaryStr(buf) {
let ret = '';
for (const v of buf) {
(ret += String.fromCharCode(v));
}
return ret;
}
function gunzipSync(buf) {
const { gunzipSync } = require('zlib');
return gunzipSync(buf);
}
/**
* Fetch the ZK algorithm for the specified cipher suite
*/
function getZkAlgorithmForCipherSuite(cipherSuite) {
if (cipherSuite.includes('CHACHA20')) {
return 'chacha20';
}
if (cipherSuite.includes('AES_256_GCM')) {
return 'aes-256-ctr';
}
if (cipherSuite.includes('AES_128_GCM')) {
return 'aes-128-ctr';
}
throw new Error(`${cipherSuite} not supported for ZK ops`);
}
/**
* Get the pure ciphertext without any MAC,
* or authentication tag,
* @param content content w/o header
* @param cipherSuite
*/
function getPureCiphertext(content, cipherSuite) {
// assert that the cipher suite is supported
getZkAlgorithmForCipherSuite(cipherSuite);
// 16 => auth tag length
content = content.slice(0, -16);
const { ivLength: fixedIvLength, } = tls_1.SUPPORTED_CIPHER_SUITE_MAP[cipherSuite];
// 12 => total IV length
const recordIvLength = 12 - fixedIvLength;
// record IV is prefixed to the ciphertext
content = content.slice(recordIvLength);
return content;
}
/**
* Get the 8 byte IV part that's stored in the record for some cipher suites
* @param content content w/o header
* @param cipherSuite
*/
function getRecordIV(content, cipherSuite) {
// assert that the cipher suite is supported
getZkAlgorithmForCipherSuite(cipherSuite);
const { ivLength: fixedIvLength, } = tls_1.SUPPORTED_CIPHER_SUITE_MAP[cipherSuite];
// 12 => total IV length
const recordIvLength = 12 - fixedIvLength;
return content.slice(0, recordIvLength);
}
function getProviderValue(params, fn, secretParams) {
return typeof fn === 'function'
// @ts-ignore
? fn(params, secretParams)
: fn;
}
function generateRpcMessageId() {
return (0, tls_1.uint8ArrayToDataView)(tls_1.crypto.randomBytes(8)).getUint32(0);
}
/**
* Random session ID for a WebSocket client.
*/
function generateSessionId() {
return generateRpcMessageId();
}
/**
* Random ID for a tunnel.
*/
function generateTunnelId() {
return generateRpcMessageId();
}
function makeRpcEvent(type, data) {
const ev = new Event(type);
ev.data = data;
return ev;
}
/**
* Get the RPC type from the key.
* For eg. "claimTunnelRequest" ->
* { type: 'claimTunnel', direction: 'request' }
*/
function getRpcTypeFromKey(key) {
if (key.endsWith('Request')) {
return {
type: key.slice(0, -7),
direction: 'request'
};
}
if (key.endsWith('Response')) {
return {
type: key.slice(0, -8),
direction: 'response'
};
}
}
/**
* Get the RPC response type from the RPC type.
* For eg. "claimTunnel" -> "claimTunnelResponse"
*/
function getRpcResponseType(type) {
return `${type}Response`;
}
/**
* Get the RPC request type from the RPC type.
* For eg. "claimTunnel" -> "claimTunnelRequest"
*/
function getRpcRequestType(type) {
return `${type}Request`;
}
function isApplicationData(packet, tlsVersion) {
return packet.type === 'ciphertext'
&& (packet.contentType === 'APPLICATION_DATA'
|| (packet.data[0] === tls_1.PACKET_TYPE.WRAPPED_RECORD
&& tlsVersion === 'TLS1_2'));
}
/**
* Convert the received data from a WS to a Uint8Array
*/
async function extractArrayBufferFromWsData(data) {
if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
}
// uint8array/Buffer
if (typeof data === 'object' && data && 'buffer' in data) {
return data;
}
if (typeof data === 'string') {
return (0, tls_1.strToUint8Array)(data);
}
if (data instanceof Blob) {
return new Uint8Array(await data.arrayBuffer());
}
throw new Error('unsupported data: ' + String(data));
}
/**
* Check if the RPC message is a request or a response.
*/
function getRpcRequest(msg) {
if (msg.requestError) {
return {
direction: 'response',
type: 'error'
};
}
for (const key in msg) {
if (!msg[key]) {
continue;
}
const rpcType = getRpcTypeFromKey(key);
if (!rpcType) {
continue;
}
return rpcType;
}
}
/**
* Finds all application data messages in a transcript
* and returns them. Removes the "contentType" suffix from the message.
* in TLS 1.3
*/
function extractApplicationDataFromTranscript({ transcript, tlsVersion }) {
const msgs = [];
for (const m of transcript) {
let message;
// redacted msgs but with a valid packet header
// can be considered application data messages
if (m.redacted) {
if (!m.plaintextLength) {
message = DEFAULT_REDACTION_DATA;
}
else {
const len = tlsVersion === 'TLS1_3'
// remove content type suffix
? m.plaintextLength - 1
: m.plaintextLength;
message = new Uint8Array(len)
.fill(zk_symmetric_crypto_1.REDACTION_CHAR_CODE);
}
// otherwise, we need to check the content type
}
else if (tlsVersion === 'TLS1_3') {
const contentType = m.message[m.message.length - 1];
if (contentType !== tls_1.CONTENT_TYPE_MAP['APPLICATION_DATA']) {
continue;
}
message = m.message.slice(0, -1);
}
else if (m.recordHeader[0] === tls_1.PACKET_TYPE.WRAPPED_RECORD) {
message = m.message;
}
else {
continue;
}
msgs.push({ message, sender: m.sender });
}
return msgs;
}
function extractHandshakeFromTranscript({ transcript, tlsVersion }) {
const msgs = [];
for (const [i, m] of transcript.entries()) {
if (m.redacted) {
break; // stop at first encrypted message
}
let message;
if (m.recordHeader[0] === tls_1.PACKET_TYPE.HELLO) {
message = m.message;
}
else if (m.recordHeader[0] === tls_1.PACKET_TYPE.WRAPPED_RECORD) {
if (tlsVersion === 'TLS1_3') {
const contentType = m.message[m.message.length - 1];
if (contentType !== tls_1.CONTENT_TYPE_MAP['HANDSHAKE']) {
break;
}
message = m.message.slice(0, -1);
}
else {
break;
}
}
else {
continue;
}
if (!message.length) {
throw new Error('unsupported handshake message');
}
msgs.push({ message, sender: m.sender, index: i });
}
return msgs;
}
async function decryptDirect(directReveal, cipherSuite, recordHeader, serverTlsVersion, content) {
const { key, iv, recordNumber } = directReveal;
const { cipher } = tls_1.SUPPORTED_CIPHER_SUITE_MAP[cipherSuite];
const importedKey = await tls_1.crypto.importKey(cipher, key);
return await (0, tls_1.decryptWrappedRecord)(content, {
iv,
key: importedKey,
recordHeader,
recordNumber,
version: serverTlsVersion,
cipherSuite,
});
}
function packRpcMessages(...msgs) {
return api_1.RPCMessages.create({
messages: msgs.map(msg => (api_1.RPCMessage.create({
...msg,
id: msg.id || generateRpcMessageId()
})))
});
}
/**
* Converts an Ethers struct (an array w named keys) to
* a plain object. Recursively converts all structs inside.
* Required to correctly JSON.stringify the struct.
*/
function ethersStructToPlainObject(struct) {
if (!Array.isArray(struct)) {
return struct;
}
const namedKeys = Object.keys(struct)
.filter(key => isNaN(Number(key)));
// seems to be an actual array
if (!namedKeys.length) {
return struct.map(ethersStructToPlainObject);
}
const obj = {};
for (const key of namedKeys) {
obj[key] = ethersStructToPlainObject(struct[key]);
}
return obj;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJpY3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvZ2VuZXJpY3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBMEJBLDBDQUVDO0FBRUQsa0RBYUM7QUFPRCxzREFXQztBQVFELHNEQVNDO0FBRUQsZ0NBR0M7QUFLRCxvRUFjQztBQVFELDhDQW1CQztBQVFELGtDQWFDO0FBRUQsNENBS0M7QUFFRCxvREFJQztBQUtELDhDQUVDO0FBS0QsNENBRUM7QUFFRCxvQ0FPQztBQU9ELDhDQWNDO0FBTUQsZ0RBRUM7QUFNRCw4Q0FFQztBQUVELDhDQVlDO0FBS0Qsb0VBcUJDO0FBS0Qsc0NBb0JDO0FBT0Qsb0ZBcUNDO0FBUUQsd0VBb0NDO0FBRUQsc0NBZUM7QUFFRCwwQ0FTQztBQU9ELDhEQWtCQztBQTdhRCw4Q0FTNkI7QUFDN0IsOEVBQTBFO0FBQzFFLHVDQUF1RDtBQVl2RCxNQUFNLHNCQUFzQixHQUFHLElBQUksVUFBVSxDQUFDLENBQUMsQ0FBQztLQUM5QyxJQUFJLENBQUMseUNBQW1CLENBQUMsQ0FBQTtBQUUzQixTQUFnQixlQUFlLENBQUMsR0FBZTtJQUM5QyxPQUFPLElBQUksV0FBVyxFQUFFLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFBO0FBQ3JDLENBQUM7QUFFRCxTQUFnQixtQkFBbUIsQ0FBQyxPQUE2Qjs7SUFDaEUsTUFBTSxRQUFRLEdBQUcsb0NBQW9DLENBQUMsT0FBTyxDQUFDLENBQUE7SUFDOUQsTUFBTSxPQUFPLEdBQWEsRUFBRSxDQUFBO0lBQzVCLEtBQUksTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxRQUFRLEVBQUUsQ0FBQztRQUMzQyxNQUFNLE9BQU8sR0FBRyxlQUFlLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDeEMsSUFBRyxNQUFBLE9BQU8sQ0FBQyxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQywwQ0FBRSxVQUFVLENBQUMsTUFBTSxDQUFD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
;