Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/improvements in helpers #189

Merged
merged 19 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/circuits/.npmignore

This file was deleted.

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
24 changes: 23 additions & 1 deletion packages/circuits/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zk-email/circuits",
"version": "3.2.4",
"version": "4.0.0",
"scripts": {
"publish": "yarn npm publish --access=public",
"test": "NODE_OPTIONS=--max_old_space_size=8192 jest tests"
Expand All @@ -19,5 +19,27 @@
"circomlibjs": "^0.1.7",
"jest": "^29.5.0",
"typescript": "^5.2.2"
},
"files": [
"/helpers",
"/lib",
"/utils",
"./email-verifier.circom"
],
"babel": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
],
"@babel/preset-typescript",
[
"jest"
]
]
}
}
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
7 changes: 0 additions & 7 deletions packages/helpers/babel.config.js

This file was deleted.

Loading
Loading