@zkp2p/reclaim-witness-sdk
Version:
<div> <div> <img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/Attestor-Core.png" /> </div> </div>
337 lines • 26.8 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const tls_1 = require("@reclaimprotocol/tls");
const assert_1 = __importDefault(require("assert"));
const config_1 = require("../config");
const api_1 = require("../proto/api");
const toprf_1 = require("../server/handlers/toprf");
const utils_1 = require("../utils");
require("../server/utils/config-env");
const ZK_CIPHER_SUITES = [
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
];
const ZK_ENGINES = [
'gnark',
'snarkjs'
];
jest.setTimeout(90000); // 90s
describe('Redaction Tests', () => {
it('should correctly redact blocks', async () => {
const vectors = [
{
input: [
'hell',
'o world'
],
output: [
'h***',
'* world'
],
redactions: [
{ fromIndex: 1, toIndex: 5 }
]
},
{
input: [
'hell',
'o world'
],
output: [
// first block is completely
// redacted, so it won't be included
'* world'
],
redactions: [
{ fromIndex: 0, toIndex: 5 }
]
},
{
input: [
'hello',
'how',
'do',
'you',
'do'
],
output: [
'he**o',
'd*',
'y*u',
'do'
],
redactions: [
{ fromIndex: 2, toIndex: 4 },
{ fromIndex: 5, toIndex: 8 },
{ fromIndex: 9, toIndex: 10 },
{ fromIndex: 11, toIndex: 12 }
]
}
];
for (const { input, output, redactions } of vectors) {
const realOutput = await (0, utils_1.getBlocksToReveal)(input.map(i => ({ plaintext: Buffer.from(i) })), () => redactions, () => {
throw new Error('should not call this');
});
if (realOutput === 'all') {
fail('should not return "all"');
continue;
}
expect(realOutput).toHaveLength(output.length);
for (const [i, element] of output.entries()) {
expect((0, utils_1.uint8ArrayToStr)(realOutput[i].redactedPlaintext)).toEqual(element);
}
}
});
it('should correctly hash blocks', async () => {
const nullifer = (0, tls_1.strToUint8Array)('abcdefg');
const base64Nullifier = Buffer.from(nullifer).toString('base64');
const vectors = [
{
input: [
'hell',
'o world'
],
output: [
'h' + base64Nullifier.slice(0, 3),
base64Nullifier.slice(3, 4) + ' world'
],
redactions: [
{ fromIndex: 1, toIndex: 5, hash: 'oprf' }
]
},
{
input: [
'hell',
'o world'
],
output: [
base64Nullifier.slice(0, 4),
base64Nullifier.slice(4, 5) + ' world'
],
redactions: [
{ fromIndex: 0, toIndex: 5, hash: 'oprf' }
]
},
];
for (const { input, output, redactions } of vectors) {
const realOutput = await (0, utils_1.getBlocksToReveal)(input.map(i => ({ plaintext: Buffer.from(i) })), () => redactions, async () => ({
dataLocation: undefined,
nullifier: nullifer,
responses: [],
mask: (0, tls_1.strToUint8Array)('mask'),
plaintext: (0, tls_1.strToUint8Array)('abcdefg')
}));
if (realOutput === 'all') {
fail('should not return "all"');
}
expect(realOutput).toHaveLength(output.length);
for (const [i, element] of output.entries()) {
expect((0, utils_1.uint8ArrayToStr)(realOutput[i].redactedPlaintext)).toEqual(element);
}
}
});
});
describe('OPRF Slicing Tests', () => {
const cipherSuite = 'TLS_CHACHA20_POLY1305_SHA256';
const alg = 'CHACHA20-POLY1305';
const zkEngine = 'gnark';
const keylength = 32;
it('should correctly demarcate blocks for OPRF', async () => {
var _a, _b;
const plaintext = `lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
id est laborum`;
const vectors = [
{
plaintext: plaintext,
redactions: [
{ fromIndex: 0, toIndex: 35, hash: 'oprf' },
]
},
{
plaintext: plaintext,
redactions: [
{ fromIndex: 128, toIndex: 138, hash: 'oprf' },
]
},
{
plaintext: plaintext,
redactions: [
{ fromIndex: 125, toIndex: 135, hash: 'oprf' },
]
}
];
const key = Buffer.alloc(keylength, 0);
key[0] = 1;
key[3] = 4;
const { ivLength: fixedIvLength, } = tls_1.SUPPORTED_CIPHER_SUITE_MAP[cipherSuite];
const fixedIv = Buffer.alloc(fixedIvLength, 0);
fixedIv[0] = 1;
fixedIv[3] = 4;
const encKey = await tls_1.crypto.importKey(alg, key);
for (const [i, { plaintext, redactions }] of vectors.entries()) {
const plaintextArr = Buffer.from(plaintext);
const { ciphertext, iv } = await (0, tls_1.encryptWrappedRecord)(plaintextArr, {
key: encKey,
iv: fixedIv,
recordNumber: 1234,
recordHeaderOpts: { type: 'WRAPPED_RECORD' },
cipherSuite,
version: cipherSuite.includes('ECDHE_')
? 'TLS1_2'
: 'TLS1_3',
});
const packet = {
type: 'ciphertext',
encKey,
iv,
recordNumber: 1234,
plaintext: plaintextArr,
ciphertext,
fixedIv: fixedIv,
data: ciphertext
};
const blocksToReveal = await (0, utils_1.getBlocksToReveal)([packet], () => redactions, performOprf);
(0, assert_1.default)(blocksToReveal !== 'all');
expect(blocksToReveal).toHaveLength(1);
expect(blocksToReveal[0].toprfs).toBeTruthy();
const revealsMap = new Map();
revealsMap.set(packet, {
type: 'zk',
redactedPlaintext: blocksToReveal[0].redactedPlaintext,
toprfs: blocksToReveal[0].toprfs
});
const revealedMessages = await (0, utils_1.preparePacketsForReveal)([{ sender: 'server', message: packet }], revealsMap, {
logger: utils_1.logger,
cipherSuite: cipherSuite,
zkEngine: zkEngine,
});
const proofs = (_b = (_a = revealedMessages[0].reveal) === null || _a === void 0 ? void 0 : _a.zkReveal) === null || _b === void 0 ? void 0 : _b.proofs;
expect(proofs === null || proofs === void 0 ? void 0 : proofs.length).toBeTruthy();
const x = await (0, utils_1.verifyZkPacket)({
ciphertext,
zkReveal: { proofs: proofs },
logger: utils_1.logger,
cipherSuite,
zkEngine: zkEngine,
recordNumber: 1234,
iv: fixedIv
});
expect(x.redactedPlaintext).toEqual(blocksToReveal[0].redactedPlaintext);
console.log(`done: ${i + 1}/${vectors.length}`);
}
});
async function performOprf(plaintext) {
utils_1.logger.info({ length: plaintext.length }, 'generating OPRF...');
const oprfOperator = (0, utils_1.makeDefaultOPRFOperator)('chacha20', zkEngine, utils_1.logger);
const reqData = await oprfOperator.generateOPRFRequestData(plaintext, config_1.TOPRF_DOMAIN_SEPARATOR, utils_1.logger);
const res = await (0, toprf_1.toprf)({
maskedData: reqData.maskedData,
engine: api_1.ZKProofEngine.ZK_ENGINE_GNARK
}, { logger: utils_1.logger });
const nullifier = await oprfOperator.finaliseOPRF(res.publicKeyShare, reqData, [res]);
const data = {
nullifier,
responses: [res],
mask: reqData.mask,
dataLocation: undefined,
plaintext
};
return data;
}
});
describe.each(ZK_CIPHER_SUITES)('[%s] should generate ZK proof for some ciphertext', (cipherSuite) => {
describe.each(ZK_ENGINES)('[%s]', (zkEngine) => {
const zkProofConcurrency = zkEngine === 'snarkjs' ? 1 : undefined;
it(zkEngine + '-' + cipherSuite, async () => {
const alg = cipherSuite.includes('CHACHA20')
? 'CHACHA20-POLY1305'
: (cipherSuite.includes('AES_256_GCM')
? 'AES-256-GCM'
: 'AES-128-GCM');
const keylength = alg === 'AES-128-GCM' ? 16 : 32;
const key = Buffer.alloc(keylength, 0);
key[0] = 1;
key[3] = 4;
const { ivLength: fixedIvLength, } = tls_1.SUPPORTED_CIPHER_SUITE_MAP[cipherSuite];
const fixedIv = Buffer.alloc(fixedIvLength, 0);
fixedIv[0] = 1;
fixedIv[3] = 4;
const encKey = await tls_1.crypto.importKey(alg, key);
const vectors = [
{
plaintext: 'My cool API secret is "my name jeff". Please don\'t reveal it',
redactions: [
{ fromIndex: 23, toIndex: 35 }
]
},
{
plaintext: `lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
id est laborum`,
redactions: [
{ fromIndex: 5, toIndex: 15 },
]
}
];
const proofGenerator = await (0, utils_1.makeZkProofGenerator)({
logger: utils_1.logger,
cipherSuite,
zkEngine,
zkProofConcurrency,
});
for (const { plaintext, redactions } of vectors) {
const plaintextArr = Buffer.from(plaintext);
const redactedPlaintext = (0, utils_1.redactSlices)(plaintextArr, redactions);
// ensure redaction fn kinda works at least
expect(redactedPlaintext).not.toEqual(plaintextArr);
const { ciphertext, iv } = await (0, tls_1.encryptWrappedRecord)(plaintextArr, {
key: encKey,
iv: fixedIv,
recordNumber: 1234,
recordHeaderOpts: { type: 'WRAPPED_RECORD' },
cipherSuite,
version: cipherSuite.includes('ECDHE_')
? 'TLS1_2'
: 'TLS1_3',
});
const packet = {
type: 'ciphertext',
encKey,
iv,
recordNumber: 1234,
plaintext: plaintextArr,
ciphertext,
fixedIv: fixedIv,
data: ciphertext
};
let proofs;
await proofGenerator.addPacketToProve(packet, { type: 'zk', redactedPlaintext }, p => proofs = p);
await proofGenerator.generateProofs();
const x = await (0, utils_1.verifyZkPacket)({
ciphertext,
zkReveal: { proofs: proofs },
logger: utils_1.logger,
cipherSuite,
zkEngine: zkEngine,
recordNumber: 1234,
iv: fixedIv
});
expect(redactedPlaintext).toEqual(x.redactedPlaintext);
}
});
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC56ay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0cy90ZXN0LnprLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUEsOENBQTZIO0FBRTdILG9EQUEyQjtBQUMzQix1Q0FBbUQ7QUFDbkQsdUNBQStFO0FBQy9FLHFEQUFpRDtBQUVqRCxxQ0FTa0I7QUFDbEIsdUNBQW9DO0FBRXBDLE1BQU0sZ0JBQWdCLEdBQWtCO0lBQ3ZDLDhCQUE4QjtJQUM5Qix3QkFBd0I7SUFDeEIseUNBQXlDO0NBQ3pDLENBQUE7QUFFRCxNQUFNLFVBQVUsR0FBZTtJQUM5QixPQUFPO0lBQ1AsU0FBUztDQUNULENBQUE7QUFRRCxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQU0sQ0FBQyxDQUFBLENBQUMsTUFBTTtBQUU5QixRQUFRLENBQUMsaUJBQWlCLEVBQUUsR0FBRyxFQUFFO0lBRWhDLEVBQUUsQ0FBQyxnQ0FBZ0MsRUFBRSxLQUFLLElBQUcsRUFBRTtRQUM5QyxNQUFNLE9BQU8sR0FBMEI7WUFDdEM7Z0JBQ0MsS0FBSyxFQUFFO29CQUNOLE1BQU07b0JBQ04sU0FBUztpQkFDVDtnQkFDRCxNQUFNLEVBQUU7b0JBQ1AsTUFBTTtvQkFDTixTQUFTO2lCQUNUO2dCQUNELFVBQVUsRUFBRTtvQkFDWCxFQUFFLFNBQVMsRUFBRSxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRTtpQkFDNUI7YUFDRDtZQUNEO2dCQUNDLEtBQUssRUFBRTtvQkFDTixNQUFNO29CQUNOLFNBQVM7aUJBQ1Q7Z0JBQ0QsTUFBTSxFQUFFO29CQUNQLDRCQUE0QjtvQkFDNUIsb0NBQW9DO29CQUNwQyxTQUFTO2lCQUNUO2dCQUNELFVBQVUsRUFBRTtvQkFDWCxFQUFFLFNBQVMsRUFBRSxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRTtpQkFDNUI7YUFDRDtZQUNEO2dCQUNDLEtBQUssRUFBRTtvQkFDTixPQUFPO29CQUNQLEtBQUs7b0JBQ0wsSUFB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
;