chore: turnOnBodyMasking -> enableBodyMasking name change

This commit is contained in:
shreyas-londhe
2024-08-02 09:41:29 +05:30
parent ca5714f745
commit f6bdb5d9cc
3 changed files with 200 additions and 166 deletions

View File

@@ -23,7 +23,7 @@ include "./helpers/body-masker.circom";
/// @param k Number of chunks the RSA key is split into. Recommended to be 17.
/// @param ignoreBodyHashCheck Set 1 to skip body hash check in case data to prove/extract is only in the headers.
/// @param removeSoftLineBreaks Set 1 to remove soft line breaks from the email body.
/// @param turnOnBodyMasking Set 1 to turn on body masking.
/// @param enableBodyMasking Set 1 to turn on body masking.
/// @input emailHeader[maxHeadersLength] Email headers that are signed (ones in `DKIM-Signature` header) as ASCII int[], padded as per SHA-256 block size.
/// @input emailHeaderLength Length of the email header including the SHA-256 padding.
/// @input pubkey[k] RSA public key split into k chunks of n bits each.
@@ -37,7 +37,7 @@ include "./helpers/body-masker.circom";
/// @output pubkeyHash Poseidon hash of the pubkey - Poseidon(n/2)(n/2 chunks of pubkey with k*2 bits per chunk).
/// @output decodedEmailBodyOut[maxBodyLength] Decoded email body with soft line breaks removed.
/// @output maskedBody[maxBodyLength] Masked email body.
template EmailVerifier(maxHeadersLength, maxBodyLength, n, k, ignoreBodyHashCheck, removeSoftLineBreaks, turnOnBodyMasking) {
template EmailVerifier(maxHeadersLength, maxBodyLength, n, k, ignoreBodyHashCheck, removeSoftLineBreaks, enableBodyMasking) {
assert(maxHeadersLength % 64 == 0);
assert(maxBodyLength % 64 == 0);
assert(n * k > 2048); // to support 2048 bit RSA
@@ -144,7 +144,7 @@ template EmailVerifier(maxHeadersLength, maxBodyLength, n, k, ignoreBodyHashChec
decodedEmailBodyOut <== qpEncodingChecker.decoded;
}
if (turnOnBodyMasking == 1) {
if (enableBodyMasking == 1) {
signal input mask[maxBodyLength];
signal output maskedBody[maxBodyLength];
component bodyMasker = BodyMasker(maxBodyLength);

View File

@@ -7,7 +7,6 @@ import { generateEmailVerifierInputsFromDKIMResult } from "@zk-email/helpers/src
import { verifyDKIMSignature } from "@zk-email/helpers/src/dkim";
import { poseidonLarge } from "@zk-email/helpers/src/hash";
describe("EmailVerifier", () => {
jest.setTimeout(10 * 60 * 1000); // 10 minutes
@@ -15,7 +14,9 @@ describe("EmailVerifier", () => {
let circuit: any;
beforeAll(async () => {
const rawEmail = fs.readFileSync(path.join(__dirname, "./test-emails/test.eml"));
const rawEmail = fs.readFileSync(
path.join(__dirname, "./test-emails/test.eml")
);
dkimResult = await verifyDKIMSignature(rawEmail);
circuit = await wasm_tester(
@@ -32,21 +33,27 @@ describe("EmailVerifier", () => {
});
it("should verify email without any SHA precompute selector", async function () {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkimResult,
{
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
const witness = await circuit.calculateWitness(emailVerifierInputs);
await circuit.checkConstraints(witness);
});
it("should verify email with a SHA precompute selector", async function () {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkimResult,
{
shaPrecomputeSelector: "How are",
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
const witness = await circuit.calculateWitness(emailVerifierInputs);
await circuit.checkConstraints(witness);
@@ -54,12 +61,15 @@ describe("EmailVerifier", () => {
it("should fail if the rsa signature is wrong", async function () {
const invalidRSASignature = dkimResult.signature + 1n;
const dkim = { ...dkimResult, signature: invalidRSASignature }
const dkim = { ...dkimResult, signature: invalidRSASignature };
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkim, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkim,
{
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
expect.assertions(1);
try {
@@ -74,12 +84,15 @@ describe("EmailVerifier", () => {
const invalidHeader = Buffer.from(dkimResult.headers);
invalidHeader[0] = 1;
const dkim = { ...dkimResult, headers: invalidHeader }
const dkim = { ...dkimResult, headers: invalidHeader };
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkim, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkim,
{
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
expect.assertions(1);
try {
@@ -91,10 +104,13 @@ describe("EmailVerifier", () => {
});
it("should fail if message padding is tampered", async function () {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkimResult,
{
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
emailVerifierInputs.emailHeader[640 - 1] = "1";
expect.assertions(1);
@@ -110,12 +126,15 @@ describe("EmailVerifier", () => {
const invalidBody = Buffer.from(dkimResult.body);
invalidBody[invalidBody.length - 1] = 1;
const dkim = { ...dkimResult, body: invalidBody }
const dkim = { ...dkimResult, body: invalidBody };
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkim, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkim,
{
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
expect.assertions(1);
try {
@@ -127,10 +146,13 @@ describe("EmailVerifier", () => {
});
it("should fail if body padding is tampered", async function () {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkimResult,
{
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
emailVerifierInputs.emailBody![768 - 1] = "1";
expect.assertions(1);
@@ -145,12 +167,15 @@ describe("EmailVerifier", () => {
it("should fail if body hash is tampered", async function () {
const invalidBodyHash = dkimResult.bodyHash + "a";
const dkim = { ...dkimResult, bodyHash: invalidBodyHash }
const dkim = { ...dkimResult, bodyHash: invalidBodyHash };
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkim, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkim,
{
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
expect.assertions(1);
try {
@@ -162,11 +187,14 @@ describe("EmailVerifier", () => {
});
it("should produce dkim pubkey hash correctly", async function () {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkimResult,
{
shaPrecomputeSelector: "How are",
maxHeadersLength: 640,
maxBodyLength: 768,
});
}
);
// Calculate the Poseidon hash with pubkey chunked to 9*242 like in circuit
const poseidonHash = await poseidonLarge(dkimResult.publicKey, 9, 242);
@@ -180,7 +208,6 @@ describe("EmailVerifier", () => {
});
});
describe("EmailVerifier : Without body check", () => {
jest.setTimeout(10 * 60 * 1000); // 10 minutes
@@ -195,7 +222,10 @@ describe("EmailVerifier : Without body check", () => {
dkimResult = await verifyDKIMSignature(rawEmail);
circuit = await wasm_tester(
path.join(__dirname, "./test-circuits/email-verifier-no-body-test.circom"),
path.join(
__dirname,
"./test-circuits/email-verifier-no-body-test.circom"
),
{
recompile: true,
include: path.join(__dirname, "../../../node_modules"),
@@ -206,11 +236,14 @@ describe("EmailVerifier : Without body check", () => {
it("should verify email when ignore_body_hash_check is true", async function () {
// The result wont have shaPrecomputeSelector, maxHeadersLength, maxBodyLength, ignoreBodyHashCheck
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkimResult,
{
maxHeadersLength: 640,
maxBodyLength: 768,
ignoreBodyHashCheck: true,
});
}
);
const witness = await circuit.calculateWitness(emailVerifierInputs);
await circuit.checkConstraints(witness);
@@ -253,8 +286,8 @@ describe("EmailVerifier : With body masking", () => {
maxHeadersLength: 640,
maxBodyLength: 768,
ignoreBodyHashCheck: false,
turnOnBodyMasking: true,
mask: mask.map((value) => value ? 1 : 0),
enableBodyMasking: true,
mask: mask.map((value) => (value ? 1 : 0)),
}
);
@@ -270,44 +303,44 @@ describe("EmailVerifier : With body masking", () => {
});
});
describe('EmailVerifier : With soft line breaks', () => {
jest.setTimeout(10 * 60 * 1000); // 10 minutes
describe("EmailVerifier : With soft line breaks", () => {
jest.setTimeout(10 * 60 * 1000); // 10 minutes
let dkimResult: DKIMVerificationResult;
let circuit: any;
let dkimResult: DKIMVerificationResult;
let circuit: any;
beforeAll(async () => {
const rawEmail = fs.readFileSync(
path.join(__dirname, './test-emails/lorem_ipsum.eml'),
'utf8'
);
dkimResult = await verifyDKIMSignature(rawEmail);
beforeAll(async () => {
const rawEmail = fs.readFileSync(
path.join(__dirname, "./test-emails/lorem_ipsum.eml"),
"utf8"
);
dkimResult = await verifyDKIMSignature(rawEmail);
circuit = await wasm_tester(
path.join(
__dirname,
'./test-circuits/email-verifier-with-soft-line-breaks-test.circom'
),
{
recompile: true,
include: path.join(__dirname, '../../../node_modules'),
output: path.join(__dirname, "./compiled-test-circuits"),
}
);
});
circuit = await wasm_tester(
path.join(
__dirname,
"./test-circuits/email-verifier-with-soft-line-breaks-test.circom"
),
{
recompile: true,
include: path.join(__dirname, "../../../node_modules"),
output: path.join(__dirname, "./compiled-test-circuits"),
}
);
});
it('should verify email when removeSoftLineBreaks is true', async function () {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkimResult,
{
maxHeadersLength: 640,
maxBodyLength: 1408,
ignoreBodyHashCheck: false,
removeSoftLineBreaks: true,
}
);
it("should verify email when removeSoftLineBreaks is true", async function () {
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(
dkimResult,
{
maxHeadersLength: 640,
maxBodyLength: 1408,
ignoreBodyHashCheck: false,
removeSoftLineBreaks: true,
}
);
const witness = await circuit.calculateWitness(emailVerifierInputs);
await circuit.checkConstraints(witness);
});
const witness = await circuit.calculateWitness(emailVerifierInputs);
await circuit.checkConstraints(witness);
});
});

View File

@@ -1,51 +1,54 @@
import { Uint8ArrayToCharArray, toCircomBigIntBytes } from './binary-format';
import { MAX_BODY_PADDED_BYTES, MAX_HEADER_PADDED_BYTES } from './constants';
import { DKIMVerificationResult, verifyDKIMSignature } from './dkim';
import { generatePartialSHA, sha256Pad } from './sha-utils';
import { Uint8ArrayToCharArray, toCircomBigIntBytes } from "./binary-format";
import { MAX_BODY_PADDED_BYTES, MAX_HEADER_PADDED_BYTES } from "./constants";
import { DKIMVerificationResult, verifyDKIMSignature } from "./dkim";
import { generatePartialSHA, sha256Pad } from "./sha-utils";
type CircuitInput = {
emailHeader: string[];
emailHeaderLength: string;
pubkey: string[];
signature: string[];
emailBody?: string[];
emailBodyLength?: string;
precomputedSHA?: string[];
bodyHashIndex?: string;
decodedEmailBodyIn?: string[];
mask?: number[];
emailHeader: string[];
emailHeaderLength: string;
pubkey: string[];
signature: string[];
emailBody?: string[];
emailBodyLength?: string;
precomputedSHA?: string[];
bodyHashIndex?: string;
decodedEmailBodyIn?: string[];
mask?: number[];
};
type InputGenerationArgs = {
ignoreBodyHashCheck?: boolean;
turnOnBodyMasking?: boolean;
shaPrecomputeSelector?: string;
maxHeadersLength?: number; // Max length of the email header including padding
maxBodyLength?: number; // Max length of the email body after shaPrecomputeSelector including padding
removeSoftLineBreaks?: boolean;
mask?: number[];
ignoreBodyHashCheck?: boolean;
enableBodyMasking?: boolean;
shaPrecomputeSelector?: string;
maxHeadersLength?: number; // Max length of the email header including padding
maxBodyLength?: number; // Max length of the email body after shaPrecomputeSelector including padding
removeSoftLineBreaks?: boolean;
mask?: number[];
};
function removeSoftLineBreaks(body: string[]): string[] {
const result = [];
let i = 0;
while (i < body.length) {
if (i + 2 < body.length &&
body[i] === '61' && // '=' character
body[i + 1] === '13' && // '\r' character
body[i + 2] === '10') { // '\n' character
// Skip the soft line break sequence
i += 3; // Move past the soft line break
} else {
result.push(body[i]);
i++;
const result = [];
let i = 0;
while (i < body.length) {
if (
i + 2 < body.length &&
body[i] === "61" && // '=' character
body[i + 1] === "13" && // '\r' character
body[i + 2] === "10"
) {
// '\n' character
// Skip the soft line break sequence
i += 3; // Move past the soft line break
} else {
result.push(body[i]);
i++;
}
}
}
// Pad the result with zeros to make it the same length as the body
while (result.length < body.length) {
result.push('0');
}
return result;
// Pad the result with zeros to make it the same length as the body
while (result.length < body.length) {
result.push("0");
}
return result;
}
/**
@@ -56,15 +59,12 @@ function removeSoftLineBreaks(body: string[]): string[] {
* @returns Circuit inputs for the EmailVerifier circuit
*/
export async function generateEmailVerifierInputs(
rawEmail: Buffer | string,
params: InputGenerationArgs = {},
rawEmail: Buffer | string,
params: InputGenerationArgs = {}
) {
const dkimResult = await verifyDKIMSignature(rawEmail);
const dkimResult = await verifyDKIMSignature(rawEmail);
return generateEmailVerifierInputsFromDKIMResult(
dkimResult,
params,
);
return generateEmailVerifierInputsFromDKIMResult(dkimResult, params);
}
/**
@@ -75,64 +75,65 @@ export async function generateEmailVerifierInputs(
* @returns Circuit inputs for the EmailVerifier circuit
*/
export function generateEmailVerifierInputsFromDKIMResult(
dkimResult: DKIMVerificationResult,
params: InputGenerationArgs = {},
dkimResult: DKIMVerificationResult,
params: InputGenerationArgs = {}
): CircuitInput {
const {
headers, body, bodyHash, publicKey, signature,
} = dkimResult;
const { headers, body, bodyHash, publicKey, signature } = dkimResult;
// SHA add padding
const [messagePadded, messagePaddedLen] = sha256Pad(
headers,
params.maxHeadersLength || MAX_HEADER_PADDED_BYTES,
);
const circuitInputs: CircuitInput = {
emailHeader: Uint8ArrayToCharArray(messagePadded), // Packed into 1 byte signals
emailHeaderLength: messagePaddedLen.toString(),
pubkey: toCircomBigIntBytes(publicKey),
signature: toCircomBigIntBytes(signature),
};
if (!params.ignoreBodyHashCheck) {
if (!body || !bodyHash) {
throw new Error(
'body and bodyHash are required when ignoreBodyHashCheck is false',
);
}
const bodyHashIndex = headers.toString().indexOf(bodyHash);
const maxBodyLength = params.maxBodyLength || MAX_BODY_PADDED_BYTES;
// 65 comes from the 64 at the end and the 1 bit in the start, then 63 comes from the formula to round it up to the nearest 64.
// see sha256algorithm.com for a more full explanation of padding length
const bodySHALength = Math.floor((body.length + 63 + 65) / 64) * 64;
const [bodyPadded, bodyPaddedLen] = sha256Pad(
body,
Math.max(maxBodyLength, bodySHALength),
// SHA add padding
const [messagePadded, messagePaddedLen] = sha256Pad(
headers,
params.maxHeadersLength || MAX_HEADER_PADDED_BYTES
);
const { precomputedSha, bodyRemaining, bodyRemainingLength } = generatePartialSHA({
body: bodyPadded,
bodyLength: bodyPaddedLen,
selectorString: params.shaPrecomputeSelector,
maxRemainingBodyLength: maxBodyLength,
});
const circuitInputs: CircuitInput = {
emailHeader: Uint8ArrayToCharArray(messagePadded), // Packed into 1 byte signals
emailHeaderLength: messagePaddedLen.toString(),
pubkey: toCircomBigIntBytes(publicKey),
signature: toCircomBigIntBytes(signature),
};
circuitInputs.emailBodyLength = bodyRemainingLength.toString();
circuitInputs.precomputedSHA = Uint8ArrayToCharArray(precomputedSha);
circuitInputs.bodyHashIndex = bodyHashIndex.toString();
circuitInputs.emailBody = Uint8ArrayToCharArray(bodyRemaining);
if (!params.ignoreBodyHashCheck) {
if (!body || !bodyHash) {
throw new Error(
"body and bodyHash are required when ignoreBodyHashCheck is false"
);
}
if (params.removeSoftLineBreaks) {
circuitInputs.decodedEmailBodyIn = removeSoftLineBreaks(circuitInputs.emailBody);
const bodyHashIndex = headers.toString().indexOf(bodyHash);
const maxBodyLength = params.maxBodyLength || MAX_BODY_PADDED_BYTES;
// 65 comes from the 64 at the end and the 1 bit in the start, then 63 comes from the formula to round it up to the nearest 64.
// see sha256algorithm.com for a more full explanation of padding length
const bodySHALength = Math.floor((body.length + 63 + 65) / 64) * 64;
const [bodyPadded, bodyPaddedLen] = sha256Pad(
body,
Math.max(maxBodyLength, bodySHALength)
);
const { precomputedSha, bodyRemaining, bodyRemainingLength } =
generatePartialSHA({
body: bodyPadded,
bodyLength: bodyPaddedLen,
selectorString: params.shaPrecomputeSelector,
maxRemainingBodyLength: maxBodyLength,
});
circuitInputs.emailBodyLength = bodyRemainingLength.toString();
circuitInputs.precomputedSHA = Uint8ArrayToCharArray(precomputedSha);
circuitInputs.bodyHashIndex = bodyHashIndex.toString();
circuitInputs.emailBody = Uint8ArrayToCharArray(bodyRemaining);
if (params.removeSoftLineBreaks) {
circuitInputs.decodedEmailBodyIn = removeSoftLineBreaks(
circuitInputs.emailBody
);
}
if (params.enableBodyMasking) {
circuitInputs.mask = params.mask;
}
}
if (params.turnOnBodyMasking) {
circuitInputs.mask = params.mask;
}
}
return circuitInputs;
return circuitInputs;
}