Skip to content

Commit

Permalink
Merge pull request #100 from zkemail/rsa-pk-hash
Browse files Browse the repository at this point in the history
Hash RSA public key in circuit
  • Loading branch information
Divide-By-0 authored Sep 1, 2023
2 parents 4f3054a + 09bb172 commit 9f68bc8
Show file tree
Hide file tree
Showing 53 changed files with 2,728 additions and 2,128 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
"typescript": "^5.2.2"
}
}
14 changes: 12 additions & 2 deletions packages/circuits/email-verifier.circom
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma circom 2.1.5;

include "circomlib/circuits/bitify.circom";
include "circomlib/circuits/mimcsponge.circom";
include "./helpers/sha.circom";
include "./helpers/rsa.circom";
include "./helpers/base64.circom";
Expand All @@ -20,7 +21,7 @@ template EmailVerifier(max_header_bytes, max_body_bytes, n, k, ignore_body_hash_
assert(n < (255 \ 2)); // we want a multiplication to fit into a circom signal

signal input in_padded[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length
signal input modulus[k]; // rsa pubkey, verified with smart contract + DNSSEC proof. split up into k parts of n bits each.
signal input pubkey[k]; // rsa pubkey, verified with smart contract + DNSSEC proof. split up into k parts of n bits each.
signal input signature[k]; // rsa signature. split up into k parts of n bits each.
signal input in_len_padded_bytes; // length of in email data including the padding, which will inform the sha256 block length

Expand All @@ -34,6 +35,8 @@ template EmailVerifier(max_header_bytes, max_body_bytes, n, k, ignore_body_hash_
// section of the "DKIM-Signature:"" line, along with the body hash.
// Note that nothing above the "DKIM-Signature:" line is signed.
signal output sha[256] <== Sha256Bytes(max_header_bytes)(in_padded, in_len_padded_bytes);
signal output pubkey_hash;

var msg_len = (256 + n) \ n;

component base_msg[msg_len];
Expand All @@ -56,7 +59,7 @@ template EmailVerifier(max_header_bytes, max_body_bytes, n, k, ignore_body_hash_
for (var i = msg_len; i < k; i++) {
rsa.base_message[i] <== 0;
}
rsa.modulus <== modulus;
rsa.modulus <== pubkey;
rsa.signature <== signature;


Expand Down Expand Up @@ -101,4 +104,11 @@ template EmailVerifier(max_header_bytes, max_body_bytes, n, k, ignore_body_hash_
sha_body_bytes[i].out === sha_b64_out[i];
}
}

// Calculate the hash (MIMC) of public key and produce as an output
// This can be used to verify the public key is correct in contract without requiring the actual key
component hasher = MiMCSponge(k, 220, 1);
hasher.ins <== pubkey;
hasher.k <== 123;
pubkey_hash <== hasher.outs[0];
}
4 changes: 2 additions & 2 deletions packages/circuits/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@zk-email/circuits",
"version": "1.1.2",
"version": "2.0.0",
"scripts": {
"publish": "yarn npm publish --access=public",
"test": "NODE_OPTIONS=--max_old_space_size=4096 jest tests/*.ts"
"test": "NODE_OPTIONS=--max_old_space_size=8192 jest tests/*.ts"
},
"devDependencies": {
"circom_tester": "^0.0.19",
Expand Down
2 changes: 1 addition & 1 deletion packages/circuits/tests/email-verifier-test.circom
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ pragma circom 2.1.5;

include "../email-verifier.circom";

component main { public [ modulus ] } = EmailVerifier(640, 768, 121, 17, 0);
component main { public [ pubkey ] } = EmailVerifier(640, 768, 121, 17, 0);
64 changes: 42 additions & 22 deletions packages/circuits/tests/email-verifier.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import fs from "fs";
import { buildMimcSponge } from "circomlibjs";
import { wasm as wasm_tester } from "circom_tester";
import { Scalar } from "ffjavascript";
import path from "path";
import { DKIMVerificationResult } from "@zk-email/helpers/src/dkim";
import { generateCircuitInputs } from "@zk-email/helpers/src/input-helpers";

const { verifyDKIMSignature } = require("@zk-email/helpers/src/dkim");
const fs = require("fs");
const path = require("path");
const wasm_tester = require("circom_tester").wasm;
const F1Field = require("ffjavascript").F1Field;
const Scalar = require("ffjavascript").Scalar;
import { verifyDKIMSignature } from "@zk-email/helpers/src/dkim";

exports.p = Scalar.fromString(
"21888242871839275222246405745257275088548364400416034343698204186575808495617"
Expand All @@ -19,18 +18,15 @@ describe("EmailVerifier", () => {
let circuit: any;

beforeAll(async () => {
const rawEmail = fs.readFileSync(
path.join(__dirname, "./test.eml"),
"utf8"
);
const rawEmail = fs.readFileSync(path.join(__dirname, "./test.eml"));
dkimResult = await verifyDKIMSignature(rawEmail);

circuit = await wasm_tester(
path.join(__dirname, "./email-verifier-test.circom"),
{
// NOTE: We are running tests against pre-compiled circuit in the below path
// You need to manually compile when changes are made to circuit if `recompile` is set to `false`.
// circom "./tests/email-verifier-test.circom" --r1cs --wasm --sym --c --wat --output "./tests/compiled-test-circuit"
// @dev During development recompile can be set to false if you are only making changes in the tests.
// This will save time by not recompiling the circuit every time.
// Compile: circom "./tests/email-verifier-test.circom" --r1cs --wasm --sym --c --wat --output "./tests/compiled-test-circuit"
recompile: true,
output: path.join(__dirname, "./compiled-test-circuit"),
include: path.join(__dirname, "../../../node_modules"),
Expand All @@ -41,7 +37,7 @@ describe("EmailVerifier", () => {
it("should verify email without any SHA precompute selector", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaModulus: dkimResult.modulus,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
Expand All @@ -56,7 +52,7 @@ describe("EmailVerifier", () => {
it("should verify email with a SHA precompute selector", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaModulus: dkimResult.modulus,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
Expand All @@ -74,7 +70,7 @@ describe("EmailVerifier", () => {

const emailVerifierInputs = generateCircuitInputs({
rsaSignature: invalidRSASignature,
rsaModulus: dkimResult.modulus,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
Expand All @@ -95,7 +91,7 @@ describe("EmailVerifier", () => {
it("should fail if precompute string is not found in body", async function () {
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaModulus: dkimResult.modulus,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
Expand All @@ -119,7 +115,7 @@ describe("EmailVerifier", () => {

const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaModulus: dkimResult.modulus,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: invalidMessage,
Expand All @@ -143,7 +139,7 @@ describe("EmailVerifier", () => {

const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaModulus: dkimResult.modulus,
rsaPublicKey: dkimResult.publicKey,
body: invalidBody,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
Expand All @@ -162,11 +158,11 @@ describe("EmailVerifier", () => {
});

it("should fail if body hash is tampered", async function () {
const invalidBodyHash = dkimResult.bodyHash + 'a';
const invalidBodyHash = dkimResult.bodyHash + "a";

const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaModulus: dkimResult.modulus,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: invalidBodyHash,
message: dkimResult.message,
Expand All @@ -183,4 +179,28 @@ describe("EmailVerifier", () => {
expect((error as Error).message).toMatch("Assert Failed");
}
});

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,
shaPrecomputeSelector: "How are",
maxMessageLength: 640,
maxBodyLength: 768,
});

// Calculate the MIMC hash
const mimc = await buildMimcSponge();
const hash = mimc.multiHash(emailVerifierInputs.pubkey, 123, 1);

// Calculate the hash using the circuit
const witness = await circuit.calculateWitness(emailVerifierInputs);

await circuit.assertOut(witness, {
pubkey_hash: mimc.F.toObject(hash),
});
});
});
2 changes: 1 addition & 1 deletion packages/circuits/tests/no-body-hash.test.circom
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ pragma circom 2.1.5;

include "../email-verifier.circom";

component main { public [ modulus ] } = EmailVerifier(640, 768, 121, 17, 1);
component main { public [ pubkey ] } = EmailVerifier(640, 768, 121, 17, 1);
5 changes: 1 addition & 4 deletions packages/circuits/tests/no-body-hash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ describe("EmailVerifier : Without body check", () => {
circuit = await wasm_tester(
path.join(__dirname, "./no-body-hash.test.circom"),
{
// NOTE: We are running tests against pre-compiled circuit in the below path
// You need to manually compile when changes are made to circuit if `recompile` is set to `false`.
// circom "./tests/email-verifier-test.circom" --r1cs --wasm --sym --c --wat --output "./tests/compiled-test-circuit"
recompile: true,
output: path.join(__dirname, "./compiled-test-circuit"),
include: path.join(__dirname, "../../../node_modules"),
Expand All @@ -42,7 +39,7 @@ describe("EmailVerifier : Without body check", () => {
// The result wont have shaPrecomputeSelector, maxMessageLength, maxBodyLength, ignoreBodyHashCheck
const emailVerifierInputs = generateCircuitInputs({
rsaSignature: dkimResult.signature,
rsaModulus: dkimResult.modulus,
rsaPublicKey: dkimResult.publicKey,
body: dkimResult.body,
bodyHash: dkimResult.bodyHash,
message: dkimResult.message,
Expand Down
80 changes: 14 additions & 66 deletions packages/circuits/tests/test.eml
Original file line number Diff line number Diff line change
@@ -1,70 +1,18 @@
Return-path: <[email protected]>
Original-recipient: rfc822;[email protected]
Received: from ci74p00im-qukt09071102.me.com by p59-mailgateway-smtp-6776dc6585-266mm (mailgateway 2318B155)
with SMTP id 4bbe59e4-9423-43ff-9c6e-d47053303967
for <[email protected]>; Thu, 22 Jun 2023 20:02:52 GMT
X-Apple-MoveToFolder: INBOX
X-Apple-Action: MOVE_TO_FOLDER/INBOX
X-Apple-UUID: 4bbe59e4-9423-43ff-9c6e-d47053303967
Received: from pv50p00im-ztdg10012001.me.com (pv50p00im-ztdg10012001.me.com [17.58.6.51])
by ci74p00im-qukt09071102.me.com (Postfix) with ESMTPS id 491294B00172
for <[email protected]>; Thu, 22 Jun 2023 20:02:47 +0000 (UTC)
X-ICL-SCORE: 3.233003230041
X-ICL-INFO: GAtbVUseBFFGSVZESgMGUkFIRFcUWUIPAApbVRYSFhEAREQZF15TQFUcAkpaQ1cOEBwKNxVVGAEa
FERXHlQLQBgcSBQXXRRCBhAWSloBAUxAQUhBVgUHQFURAxsXDRQSA0xWB0gAXw9YAxITHwEGUkRL
VkdJHlsHWxoJGloQRhYHREQHDgUGEkVJDxpVSkIGEkhWR0kCBlJEVwsSVlNZD1dZAhNFElsHWxoJ
GloQWwsRRERLPnEOUENMVUBVAAYgM1RSQ0x1GyBNSyJaTQZzIEBPIEE+AwFTNRQDWRtfW1xXWRQU
RRJFAxkcAxs4Q1cOEBwKWQBJTEA=
Authentication-Results: bimi.icloud.com; bimi=declined
X-ARC-Info: policy=fail; arc=none
Authentication-Results: arc.icloud.com; arc=none
Authentication-Results: dmarc.icloud.com; dmarc=pass header.from=me.com
X-DMARC-Info: pass=pass; dmarc-policy=quarantine; s=r0; d=r1; pdomain=me.com
X-DMARC-Policy: v=DMARC1; p=quarantine; rua=mailto:[email protected]; ruf=mailto:[email protected];
Authentication-Results: dkim-verifier.icloud.com;
dkim=pass (2048-bit key) header.d=me.com [email protected] header.b=FpmCwgC9
Authentication-Results: spf.icloud.com; spf=none (spf.icloud.com: [email protected] does not designate permitted sender hosts) smtp.mailfrom=[email protected]
Received-SPF: none (spf.icloud.com: [email protected] does not designate permitted sender hosts) receiver=spf.icloud.com; client-ip=17.58.6.51; helo=pv50p00im-ztdg10012001.me.com; envelope-from=[email protected]
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=me.com; s=1a1hai;
t=1687464166; bh=FxfpVjf51msd35Z/BVrd7Uv2GcGRjxaW9dGr90Y2M88=;
h=From:Content-Type:Mime-Version:Subject:Message-Id:Date:To;
b=FpmCwgC9dKuHA2BXHuO6Ujfls+b5psK4yurk9TkR9Q2rpH8ahp6XDuDRQW/8mucud
kIoudejHdNfXqUpWzmtcVCff+cWyzBJarYt8YKgPKQZ3f+UZqXMPJ7t1sZbIJkSU7n
zTiXh+F7KX/kIsJ1vUHnf40EsULPMU2CUSdBxvHpH4C2+MiVcx/mazLdh9BlpKavwY
QMl3uEDcH/blpvK7YRFn3eYpFL8TkS819/aBUcQg6aGZMJrGsr+cQFCsPmXTPHbxBD
LUnk7BUdzdvIwPDnOOrFJIf6JCbJd1rWIiGy3ZOP67+h79eHkQPcZxl/fq0BFQypPq
TOvKhhMsQAmHw==
Received: from smtpclient.apple (pv50p00im-dlb-asmtp-mailmevip.me.com [17.56.9.10])
by pv50p00im-ztdg10012001.me.com (Postfix) with ESMTPSA id A9258A01A4
for <[email protected]>; Thu, 22 Jun 2023 20:02:44 +0000 (UTC)
From: Saleel <[email protected]>
Content-Type: text/plain;
charset=us-ascii
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=icloud.com; s=1a1hai; t=1693038337; bh=7xQMDuoVVU4m0W0WRVSrVXMeGSIASsnucK9dJsrc+vU=; h=from:Content-Type:Mime-Version:Subject:Message-Id:Date:to; b=EhLyVPpKD7d2/+h1nrnu+iEEBDfh6UWiAf9Y5UK+aPNLt3fAyEKw6Ic46v32NOcZD
M/zhXWucN0FXNiS0pz/QVIEy8Bcdy7eBZA0QA1fp8x5x5SugDELSRobQNbkOjBg7Mx
VXy7h4pKZMm/hKyhvMZXK4AX9fSoXZt4VGlAFymFNavfdAeKgg/SHXLds4lOPJV1wR
2E21g853iz5m/INq3uK6SQKzTnz/wDkdyiq90gC0tHQe8HpDRhPIqgL5KSEpuvUYmJ
wjEOwwHqP6L3JfEeROOt6wyuB1ah7wgRvoABOJ81+qLYRn3bxF+y1BC+PwFd5yFWH5
Ry43lwp1/3+sA==
from: [email protected]
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3731.500.231\))
Subject: Test email subject
Message-Id: <[email protected]>
Date: Fri, 23 Jun 2023 01:32:20 +0530
To: Saleel <[email protected]>
X-Mailer: Apple Mail (2.3731.500.231)
X-Proofpoint-GUID: MU47Y4dCOAohatJZW3Su6CexM6uUt94W
X-Proofpoint-ORIG-GUID: MU47Y4dCOAohatJZW3Su6CexM6uUt94W
X-MANTSH: 1TEIXSUMdHVoaGkNHB1tfQV4aEhoTGxsaGBEKTEMXGxoEGxwSBBscGgQfGhAbHho
fGhEKTFkXGx8ZEQpZRBdsUm5CHktdTXIFbxEKWU0XZEVETxEKWUkXGRxxGwYdG3cGEh0GGgYaB
hoGGRpxGxAadwYaBhoGGgYaBhoGGnEaEBp3BhoRClleF2xseREKQ04XZ38eHXMeTmlla0VCS15
gcH0ZeV8caU9SZxxff14THn0RClhcFxkEGgQfGgUbGhoEEhgEHhgEGBIQGx4aHxoRCl5ZF0hTQ
RhsEQpNXBcaEQpMWhdoaU1raxEKTEYXTWsRCkNaFxsdBB8SBBwEHxsRCkJeFxsRCkJcFxsRCl5
OFxsRCkJLF2xwYHlAHWJSaRpiEQpCSRdscGB5QB1iUmkaYhEKQkUXbllZcnNAGl1aGF4RCkJOF
2xwYHlAHWJSaRpiEQpCTBdgTVt4G1BffxhwZxEKQm4XbWVwG0JrYh5eZlwRCkJsF2h7YBNtbBh
JfU4dEQpCQBdufWFrHURjemEFHxEKQlgXemEZYklYHBwYWk8RCk1eFxsRClpYFxgRCnBoF2RMR
20YaAEeBXJpEBkaEQpwbBdpRhJHa2Bzb1NHZhAZGhEKbX4XGxEKWE0XSxE=
X-CLX-Shades: None
X-Proofpoint-Virus-Version: =?UTF-8?Q?vendor=3Dfsecure_engine=3D1.1.170-22c6f66c430a71ce266a39bfe25bc?=
=?UTF-8?Q?2903e8d5c8f:6.0.425,18.0.790,17.0.605.474.0000000_definitions?=
=?UTF-8?Q?=3D2022-01-13=5F02:2022-01-11=5F01,2022-01-13=5F02,2020-01-23?=
=?UTF-8?Q?=5F02_signatures=3D0?=
X-Proofpoint-Spam-Reason: safe
Subject: Hello
Message-Id: <[email protected]>
Date: Sat, 26 Aug 2023 12:25:22 +0400
to: [email protected]

Hello
Hello,

How are you doing?
How are you?
39 changes: 39 additions & 0 deletions packages/contracts/DKIMRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/access/Ownable.sol";

/**
A Registry that store the hash(dkim_public_key) for each domain
The hash is calculated by taking Poseidon of DKIM key split into 9 chunks of 242 bits each
*/
contract DKIMRegistry is Ownable {
// Mapping from domain name to DKIM public key hash
mapping(string => uint256) public dkimPublicKeyHashes;

constructor() {
// Set values for popular domains
dkimPublicKeyHashes["gmail.com"] = uint256(20579775636546222313859320423592165398188168817714003219389601176739340973605);
dkimPublicKeyHashes["hotmail.com"] = uint256(2750248559912404074361997670683337416910370052869160728223409986079552486582);
dkimPublicKeyHashes["twitter.com"] = uint256(12431732230788297063498039481224031586256793440953465069048041914965586355958);
dkimPublicKeyHashes["ethereum.org"] = uint256(13749471426528386843484698195116860745506750565298853141220185289842769029726);
dkimPublicKeyHashes["skiff.com"] = uint256(11874169184886542147081299005924838984240934585001783050565158265014763417816);
}

function _stringEq(string memory a, string memory b) internal pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}

function getDKIMPublicKeyHash(
string memory domainName
) public view returns (uint256) {
return dkimPublicKeyHashes[domainName];
}

function setDKIMPublicKeyHash(
string memory domainName,
uint256 publicKeyHash
) public onlyOwner {
dkimPublicKeyHashes[domainName] = publicKeyHash;
}
}
Loading

0 comments on commit 9f68bc8

Please sign in to comment.