Skip to content

Commit

Permalink
circuit: fix tests to match new helper methods
Browse files Browse the repository at this point in the history
  • Loading branch information
saleel committed Apr 3, 2024
1 parent 57257ec commit 2001fa6
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 135 deletions.
16 changes: 8 additions & 8 deletions packages/circuits/email-verifier.circom
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ include "./utils/hash.circom";
/// @notice Circuit to verify email signature as per DKIM standard.
/// @notice Verifies the signature is valid for the given header and pubkey, and the hash of the body matches the hash in the header.
/// @notice This cicuit only verifies signature as per `rsa-sha256` algorithm.
/// @param maxHeaderLength Maximum length for the email header.
/// @param maxHeadersLength Maximum length for the email header.
/// @param maxBodyLength Maximum length for the email body.
/// @param n Number of bits per chunk the RSA key is split into. Recommended to be 121.
/// @param k Number of chunks the RSA key is split into. Recommended to be 17.
Expand All @@ -29,14 +29,14 @@ include "./utils/hash.circom";
/// @input bodyHashIndex Index of the body hash `bh` in the emailHeader.
/// @input precomputedSHA Precomputed SHA-256 hash of the email body till the bodyHashIndex.
/// @output pubkeyHash Poseidon hash of the pubkey - Poseidon(n/2)(n/2 chunks of pubkey with k*2 bits per chunk).
template EmailVerifier(maxHeaderLength, maxBodyLength, n, k, ignoreBodyHashCheck) {
assert(maxHeaderLength % 64 == 0);
template EmailVerifier(maxHeadersLength, maxBodyLength, n, k, ignoreBodyHashCheck) {
assert(maxHeadersLength % 64 == 0);
assert(maxBodyLength % 64 == 0);
assert(n * k > 2048); // to support 2048 bit RSA
assert(n < (255 \ 2)); // for multiplication to fit in the field (255 bits)


signal input emailHeader[maxHeaderLength];
signal input emailHeader[maxHeadersLength];
signal input emailHeaderLength;
signal input pubkey[k];
signal input signature[k];
Expand All @@ -45,11 +45,11 @@ template EmailVerifier(maxHeaderLength, maxBodyLength, n, k, ignoreBodyHashCheck


// Assert emailHeader data after emailHeaderLength are zeros
AssertZeroPadding(maxHeaderLength)(emailHeader, emailHeaderLength + 1);
AssertZeroPadding(maxHeadersLength)(emailHeader, emailHeaderLength + 1);


// Calculate SHA256 hash of the `emailHeader` - 506,670 constraints
signal output sha[256] <== Sha256Bytes(maxHeaderLength)(emailHeader, emailHeaderLength);
signal output sha[256] <== Sha256Bytes(maxHeadersLength)(emailHeader, emailHeaderLength);


// Pack SHA output bytes to int[] for RSA input message
Expand Down Expand Up @@ -89,11 +89,11 @@ template EmailVerifier(maxHeaderLength, maxBodyLength, n, k, ignoreBodyHashCheck

// Body hash regex - 617,597 constraints
// Extract the body hash from the header (i.e. the part after bh= within the DKIM-signature section)
signal (bhRegexMatch, bhReveal[maxHeaderLength]) <== BodyHashRegex(maxHeaderLength)(emailHeader);
signal (bhRegexMatch, bhReveal[maxHeadersLength]) <== BodyHashRegex(maxHeadersLength)(emailHeader);
bhRegexMatch === 1;

var shaB64Length = 44; // Length of SHA-256 hash when base64 encoded - ceil(32 / 3) * 4
signal bhBase64[shaB64Length] <== SelectRegexReveal(maxHeaderLength, shaB64Length)(bhReveal, bodyHashIndex);
signal bhBase64[shaB64Length] <== SelectRegexReveal(maxHeadersLength, shaB64Length)(bhReveal, bodyHashIndex);
signal headerBodyHash[32] <== Base64Decode(32)(bhBase64);


Expand Down
134 changes: 32 additions & 102 deletions packages/circuits/tests/email-verifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { buildPoseidon } from "circomlibjs";
import { wasm as wasm_tester } from "circom_tester";
import path from "path";
import { DKIMVerificationResult } from "@zk-email/helpers/src/dkim";
import { generateCircuitInputs } from "@zk-email/helpers/src/input-helpers";
import { generateEmailVerifierInputsFromDKIMResult } from "@zk-email/helpers/src/input-generators";
import { verifyDKIMSignature } from "@zk-email/helpers/src/dkim";
import { bigIntToChunkedBytes } from "@zk-email/helpers/src/binaryFormat";
import { bigIntToChunkedBytes } from "@zk-email/helpers/src/binary-format";


describe("EmailVerifier", () => {
Expand All @@ -32,13 +32,8 @@ describe("EmailVerifier", () => {
});

it("should verify email without any SHA precompute selector", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
maxMessageLength: 640,
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand All @@ -47,14 +42,9 @@ describe("EmailVerifier", () => {
});

it("should verify email with a SHA precompute selector", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand All @@ -64,15 +54,10 @@ describe("EmailVerifier", () => {

it("should fail if the rsa signature is wrong", async function () {
const invalidRSASignature = dkimResult.signature + 1n;
const dkim = { ...dkimResult, signature: invalidRSASignature }

const emailVerifierInputs = generateCircuitInputs({
rsaSignature: invalidRSASignature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkim, {
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand All @@ -85,39 +70,14 @@ describe("EmailVerifier", () => {
}
});

it("should fail if precompute string is not found in body", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
shaPrecomputeSelector: "invalid",
maxMessageLength: 640,
maxBodyLength: 768,
});
it("should fail if message is tampered", async function () {
const invalidHeader = Buffer.from(dkimResult.headers);
invalidHeader[0] = 1;

expect.assertions(1);
try {
const witness = await circuit.calculateWitness(emailVerifierInputs);
await circuit.checkConstraints(witness);
} catch (error) {
expect((error as Error).message).toMatch("Assert Failed");
}
});
const dkim = { ...dkimResult, headers: invalidHeader }

it("should fail if message is tampered", async function () {
const invalidMessage = Buffer.from(dkimResult.message);
invalidMessage[0] = 1;

const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: invalidMessage,
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkim, {
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand All @@ -131,14 +91,8 @@ describe("EmailVerifier", () => {
});

it("should fail if message padding is tampered", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
maxHeadersLength: 640,
maxBodyLength: 768,
});
emailVerifierInputs.emailHeader[640 - 1] = "1";
Expand All @@ -156,14 +110,10 @@ describe("EmailVerifier", () => {
const invalidBody = Buffer.from(dkimResult.body);
invalidBody[invalidBody.length - 1] = 1;

const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: invalidBody,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
const dkim = { ...dkimResult, body: invalidBody }

const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkim, {
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand All @@ -177,14 +127,8 @@ describe("EmailVerifier", () => {
});

it("should fail if body padding is tampered", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
maxHeadersLength: 640,
maxBodyLength: 768,
});
emailVerifierInputs.emailBody![768 - 1] = "1";
Expand All @@ -201,14 +145,10 @@ describe("EmailVerifier", () => {
it("should fail if body hash is tampered", async function () {
const invalidBodyHash = dkimResult.bodyHash + "a";

const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: invalidBodyHash,
message: dkimResult.message,
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
const dkim = { ...dkimResult, bodyHash: invalidBodyHash }

const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkim, {
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand All @@ -222,14 +162,9 @@ describe("EmailVerifier", () => {
});

it("should produce dkim pubkey hash correctly", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand Down Expand Up @@ -272,14 +207,9 @@ describe("EmailVerifier : Without body check", () => {
});

it("should verify email when ignore_body_hash_check is true", async function () {
// The result wont have shaPrecomputeSelector, maxMessageLength, maxBodyLength, ignoreBodyHashCheck
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
maxMessageLength: 640,
// The result wont have shaPrecomputeSelector, maxHeadersLength, maxBodyLength, ignoreBodyHashCheck
const emailVerifierInputs = generateEmailVerifierInputsFromDKIMResult(dkimResult, {
maxHeadersLength: 640,
maxBodyLength: 768,
ignoreBodyHashCheck: true,
});
Expand Down
29 changes: 8 additions & 21 deletions packages/circuits/tests/rsa.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import fs from "fs";
import path from "path";
import { wasm as wasm_tester } from "circom_tester";
import { DKIMVerificationResult } from "@zk-email/helpers/src/dkim";
import { generateCircuitInputs } from "@zk-email/helpers/src/input-helpers";
import { verifyDKIMSignature } from "@zk-email/helpers/src/dkim";
import { toCircomBigIntBytes } from "@zk-email/helpers/src/binaryFormat";
import { generateEmailVerifierInputs } from "@zk-email/helpers/src/input-generators";
import { toCircomBigIntBytes } from "@zk-email/helpers/src/binary-format";


describe("RSA", () => {
jest.setTimeout(10 * 60 * 1000); // 10 minutes

let circuit: any;
let dkimResult: DKIMVerificationResult;
let rawEmail: Buffer;

beforeAll(async () => {
circuit = await wasm_tester(
Expand All @@ -22,18 +20,12 @@ describe("RSA", () => {
// output: path.join(__dirname, "./compiled-test-circuits"),
}
);
const rawEmail = fs.readFileSync(path.join(__dirname, "./test-emails/test.eml"));
dkimResult = await verifyDKIMSignature(rawEmail);
rawEmail = fs.readFileSync(path.join(__dirname, "./test-emails/test.eml"));
});

it("should verify 2048 bit rsa signature correctly", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
maxMessageLength: 640,
const emailVerifierInputs = await generateEmailVerifierInputs(rawEmail, {
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand Down Expand Up @@ -72,13 +64,8 @@ describe("RSA", () => {
});

it("should fail when verifying with an incorrect signature", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
maxMessageLength: 640,
const emailVerifierInputs = await generateEmailVerifierInputs(rawEmail, {
maxHeadersLength: 640,
maxBodyLength: 768,
});

Expand Down
4 changes: 2 additions & 2 deletions packages/circuits/tests/sha.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { wasm as wasm_tester } from "circom_tester";
import path from "path";
import { sha256Pad, shaHash } from "@zk-email/helpers/src/shaHash";
import { Uint8ArrayToCharArray, uint8ToBits } from "@zk-email/helpers/src/binaryFormat";
import { sha256Pad, shaHash } from "@zk-email/helpers/src/sha-utils";
import { Uint8ArrayToCharArray, uint8ToBits } from "@zk-email/helpers/src/binary-format";


describe("SHA256 for email header", () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/helpers/src/input-generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type CircuitInput = {
type InputGenerationArgs = {
ignoreBodyHashCheck?: boolean;
shaPrecomputeSelector?: string;
maxHeaderLength?: number; // Max length of the email header including padding
maxHeadersLength?: number; // Max length of the email header including padding
maxBodyLength?: number; // Max length of the email body after shaPrecomputeSelector including padding
};

Expand Down Expand Up @@ -58,7 +58,7 @@ export function generateEmailVerifierInputsFromDKIMResult(
// SHA add padding
const [messagePadded, messagePaddedLen] = sha256Pad(
headers,
params.maxHeaderLength || MAX_HEADER_PADDED_BYTES
params.maxHeadersLength || MAX_HEADER_PADDED_BYTES
);

const circuitInputs: CircuitInput = {
Expand Down
Loading

0 comments on commit 2001fa6

Please sign in to comment.