Big thanks to Mach34 team for the initial version of zkemail in Noir.
A zkEmail account is a smart contract account that allows users to confirm transactions by replying to an email. How it works? Each email message is signed by the user's email provider (e.g., iCloud, Gmail or ProtonMail). User's smart contract account verifies the signature on-chain and executes the transaction if the signature is valid. In addition to the signature verification, it is also important to verify that
- email was sent from the user's email address,
- it was sent to the relayer email address and
- that the email contains the correct subject(which includes the calldata or tx description).
Alice sends the following email to a relayer:
from: alice@icloud.com
to: relayer@shieldswap.org
subject: Send 10 ETH to vitalik.eth
DKIM-Signature: /* signature by icloud.com */
/* body is not relevant */
Notice the DKIM-Signature
header? It is the signature created by iCloud's private key. The rest of the article will explain:
- What exactly is signed by an email provider
- How to get email provider's public key
- How to verify the signature in Noir
DKIM signature is created by an email provider that signs a subset of headers in a correct order and canonical format. Let's take a look at a DKIM-Signature header:
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==
It consists of key-value pairs separated by semicolons. The important parts are: a=
and b=
. Let's break it down:
a=
is the algorithm used to sign the message. In this case, it isrsa-sha256
.b=
is the signature itself in base64 encoding.
The email headers are sorted and canonicalized before signing. The order of the headers is specified by the h=
parameter of the DKIM-Signature
header. Then the headers are converted to lower case, extra whitespace is removed, and then joined by \r\n
characters. Lets take a look at the headers before canonicalization:
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: runnier.leagues.0j@icloud.com
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: Hello
Message-Id: <8F819D32-B6AC-489D-977F-438BBC4CAB27@me.com>
Date: Sat, 26 Aug 2023 12:25:22 +0400
to: zkewtest@gmail.com
And after canonicalization:
from:runnier.leagues.0j@icloud.com\r\ncontent-type:text/plain; charset=us-ascii\r\nmime-version:1.0 (Mac OS X Mail 16.0 \\(3731.500.231\\))\r\nsubject:Hello\r\nmessage-id:<8F819D32-B6AC-489D-977F-438BBC4CAB27@me.com>\r\ndate:Sat, 26 Aug 2023 12:25:22 +0400\r\nto:zkewtest@gmail.com\r\ndkim-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=
Note that the DKIM-Signature
header is also included in the canonicalized headers but it is missing the value of the b=
parameter. Only the key is included.
Upon sending an email, iCloud.com will sign the canonicalized headers with its private key. The signature is then included in the DKIM-Signature
header. Specifically in the b=
parameter.
2 questions:
- How to get email provider's public key?
- How to verify the signature in Noir?
If you look at the DKIM-Signature
header from above, you will notice 2 parameters: d=icloud.com
and s=1a1hai
. These are the d
omain and the s
elector of the email provider. We can get the public key by querying the specific DNS record: {s}._domainkey.{d}
. In this case, we have to query 1a1hai._domainkey.icloud.com
. Let's query it using dig
command:
dig +short TXT 1a1hai._domainkey.icloud.com
We get the following response:
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ZEfbkf4TbO2TDZI67WhJ6G8Dwk3SJyAbBlE/QKdyXFZB4HfEU7AcuZBzcXSJFE03DlmyOkUAmaaR8yFlwooHyaKRLIaT3epGlL5YGowyfItL" "ly2k0Jj0IOICRxWrB378b7qMeimE8KlH1UNaVpRTTi0XIYjIKAOpTlBmkM9a/3Rl4NWy8pLYApXD+WCkYxPcxoAAgaN8osqGTCJ5r+VHFU7Wm9xqq3MZmnfo" "0bzInF4UajCKjJAQa+HNuh95DWIYP/wV77/PxkEakOtzkbJMlFJiK/hMJ+HQUvTbtKW2s+t4uDK8DI16Rotsn6e0hS8xuXPmVte9ZzplD0fQgm2qwIDAQAB"
The interesting part is the p=
parameter. Please note that it is split into multiple quotes lines, so we have to concatenate them together. The result is:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ZEfbkf4TbO2TDZI67WhJ6G8Dwk3SJyAbBlE/QKdyXFZB4HfEU7AcuZBzcXSJFE03DlmyOkUAmaaR8yFlwooHyaKRLIaT3epGlL5YGowyfItLly2k0Jj0IOICRxWrB378b7qMeimE8KlH1UNaVpRTTi0XIYjIKAOpTlBmkM9a/3Rl4NWy8pLYApXD+WCkYxPcxoAAgaN8osqGTCJ5r+VHFU7Wm9xqq3MZmnfo0bzInF4UajCKjJAQa+HNuh95DWIYP/wV77/PxkEakOtzkbJMlFJiK/hMJ+HQUvTbtKW2s+t4uDK8DI16Rotsn6e0hS8xuXPmVte9ZzplD0fQgm2qwIDAQAB
This is the public key of the email provider(iCloud.com) encoded in base64. We will use it later to verify a signature in Noir.
Due to the limitations of ZK circuits, we have to use a specifically optimized implementation of RSA and big numbers provided by the Noir team: https://github.com/noir-lang/noir_rsa.
Install it in Nargo.toml:
[dependencies]
noir_rsa = { tag = "v0.2", git = "https://github.com/noir-lang/noir_rsa" }
Now, let's take a look at the Noir circuit for the verification:
use noir_rsa::bignum::BigNum;
use noir_rsa::bignum::runtime_bignum::BigNumInstance;
use noir_rsa::bignum::fields::Params2048;
use noir_rsa::RSA;
global PUBKEY_LIMBS_LEN: u32 = 18;
global SIGNATURE_LIMBS_LEN: u32 = 18;
type BN2048 = BigNum<18, Params2048>;
type RSA2048 = RSA<BN2048, BigNumInstance<18, Params2048>, 256>;
global rsa: RSA2048 = RSA {};
fn verify_email<let MAX_HEADERS_LEN: u32>(
headers: [u8; MAX_HEADERS_LEN],
headers_len: u32,
pubkey_limbs: [Field; PUBKEY_LIMBS_LEN],
pubkey_redc_limbs: [Field; PUBKEY_LIMBS_LEN],
signature: [Field; SIGNATURE_LIMBS_LEN]
) -> bool {
let signature: BN2048 = BigNum::from_array(signature);
let pubkey: BigNumInstance<18, Params2048> = BigNumInstance::new(pubkey_limbs, pubkey_redc_limbs);
let headers_hash = std::hash::sha256_var(headers, headers_len as u64);
rsa.verify_sha256_pkcs1v15(pubkey, headers_hash, signature)
}
I will skip the imports and type definitions for brevity(who cares anyway, just copy-paste it). Let's take a look at the verify_email
parameters:
headers
: the canonicalized headers as an array of bytes. It has a fixed length and has to be padded with 0 at the endheaders_len
: the actual length of the headerspubkey_limbs
&pubkey_redc_limbs
: the public key of the email provider specifically encoded for Noirsignature
: the signature of the email provider specifically encoded for Noir
In the function body, we first convert the signature and public key to big number instances. Then we hash the canonicalized headers with sha256 and verify the signature with the public key and the hash.
let signature: BN2048 = BigNum::from_array(signature);
let pubkey: BigNumInstance<18, Params2048> = BigNumInstance::new(pubkey_limbs, pubkey_redc_limbs);
let headers_hash = std::hash::sha256_var(headers, headers_len as u64);
rsa.verify_sha256_pkcs1v15(pubkey, headers_hash, signature)
If the verification is successful, the function returns true
, otherwise it returns false
.
Now, that we have the Noir circuit, how do we convert a raw email string to the structured inputs for the circuit?
Let's start with the headers. I will use mailparser
to parse email and dkim
to extract the canonicalized headers. Parse the email:
import DKIM from "dkim";
import { simpleParser } from "mailparser";
async function canonicalizeHeaders(emailStr: string) {
const email = await simpleParser(emailStr);
const dkimHeader = email.headers.get("dkim-signature").params;
const canonicalHeaders = DKIM.processHeader(
email.headerLines.map((x) => x.line),
dkimHeader.h.split(":").map((x) => x.trim()),
"relaxed",
);
const signatureBase64 = dkimHeader.b.replace(/\s/g, "");
return { canonicalHeaders, signatureBase64 };
}
Now, test it:
const { canonicalHeaders, signatureBase64 } = await canonicalizeHeaders(
String.raw`
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: runnier.leagues.0j@icloud.com
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: Hello
Message-Id: <8F819D32-B6AC-489D-977F-438BBC4CAB27@me.com>
Date: Sat, 26 Aug 2023 12:25:22 +0400
to: zkewtest@gmail.com
`.trim(),
);
console.log("signature:", signatureBase64);
console.log();
console.log("headers:", JSON.stringify(canonicalHeaders));
We get the following output:
signature: EhLyVPpKD7d2/+h1nrnu+iEEBDfh6UWiAf9Y5UK+aPNLt3fAyEKw6Ic46v32NOcZDM/zhXWucN0FXNiS0pz/QVIEy8Bcdy7eBZA0QA1fp8x5x5SugDELSRobQNbkOjBg7MxVXy7h4pKZMm/hKyhvMZXK4AX9fSoXZt4VGlAFymFNavfdAeKgg/SHXLds4lOPJV1wR2E21g853iz5m/INq3uK6SQKzTnz/wDkdyiq90gC0tHQe8HpDRhPIqgL5KSEpuvUYmJwjEOwwHqP6L3JfEeROOt6wyuB1ah7wgRvoABOJ81+qLYRn3bxF+y1BC+PwFd5yFWH5Ry43lwp1/3+sA==
headers: "from:runnier.leagues.0j@icloud.com\r\ncontent-type:text/plain; charset=us-ascii\r\nmime-version:1.0 (Mac OS X Mail 16.0 \\(3731.500.231\\))\r\nsubject:Hello\r\nmessage-id:<8F819D32-B6AC-489D-977F-438BBC4CAB27@me.com>\r\ndate:Sat, 26 Aug 2023 12:25:22 +0400\r\nto:zkewtest@gmail.com\r\ndkim-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="
And convert it to bytes:
const canonicalHeadersBytes = Array.from(
Buffer.from(canonicalHeaders, "utf-8"),
);
console.log(
"canonicalHeadersBytes:",
"[",
canonicalHeadersBytes.join(", "),
"]",
);
Output:
canonicalHeadersBytes: [ 102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 115, 45, 97, 115, 99, 105, 105, 13, 10, 109, 105, 109, 101, 45, 118, 101, 114, 115, 105, 111, 110, 58, 49, 46, 48, 32, 40, 77, 97, 99, 32, 79, 83, 32, 88, 32, 77, 97, 105, 108, 32, 49, 54, 46, 48, 32, 92, 40, 51, 55, 51, 49, 46, 53, 48, 48, 46, 50, 51, 49, 92, 41, 41, 13, 10, 115, 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 13, 10, 109, 101, 115, 115, 97, 103, 101, 45, 105, 100, 58, 60, 56, 70, 56, 49, 57, 68, 51, 50, 45, 66, 54, 65, 67, 45, 52, 56, 57, 68, 45, 57, 55, 55, 70, 45, 52, 51, 56, 66, 66, 67, 52, 67, 65, 66, 50, 55, 64, 109, 101, 46, 99, 111, 109, 62, 13, 10, 100, 97, 116, 101, 58, 83, 97, 116, 44, 32, 50, 54, 32, 65, 117, 103, 32, 50, 48, 50, 51, 32, 49, 50, 58, 50, 53, 58, 50, 50, 32, 43, 48, 52, 48, 48, 13, 10, 116, 111, 58, 122, 107, 101, 119, 116, 101, 115, 116, 64, 103, 109, 97, 105, 108, 46, 99, 111, 109, 13, 10, 100, 107, 105, 109, 45, 115, 105, 103, 110, 97, 116, 117, 114, 101, 58, 118, 61, 49, 59, 32, 97, 61, 114, 115, 97, 45, 115, 104, 97, 50, 53, 54, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, 108, 97, 120, 101, 100, 59, 32, 100, 61, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 59, 32, 115, 61, 49, 97, 49, 104, 97, 105, 59, 32, 116, 61, 49, 54, 57, 51, 48, 51, 56, 51, 51, 55, 59, 32, 98, 104, 61, 55, 120, 81, 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, 59, 32, 104, 61, 102, 114, 111, 109, 58, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 77, 105, 109, 101, 45, 86, 101, 114, 115, 105, 111, 110, 58, 83, 117, 98, 106, 101, 99, 116, 58, 77, 101, 115, 115, 97, 103, 101, 45, 73, 100, 58, 68, 97, 116, 101, 58, 116, 111, 59, 32, 98, 61 ]
Save these output for later.
Now, let's convert the email provider public key and signature to Noir big number limbs. There is only this official Rust library available. But you are lucky because I compiled it to wasm and published it to npm. Let's install it:
npm i @shieldswap/email_account_utils_rs
It accepts the public key and the signature in base64 and returns the public key limbs and signature limbs in JSON format:
import init, { get_limbs } from "@shieldswap/email_account_utils_rs";
await init(); // initialize the wasm module
// base64 encoded public key from email provider
const publicKey =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ZEfbkf4TbO2TDZI67WhJ6G8Dwk3SJyAbBlE/QKdyXFZB4HfEU7AcuZBzcXSJFE03DlmyOkUAmaaR8yFlwooHyaKRLIaT3epGlL5YGowyfItLly2k0Jj0IOICRxWrB378b7qMeimE8KlH1UNaVpRTTi0XIYjIKAOpTlBmkM9a/3Rl4NWy8pLYApXD+WCkYxPcxoAAgaN8osqGTCJ5r+VHFU7Wm9xqq3MZmnfo0bzInF4UajCKjJAQa+HNuh95DWIYP/wV77/PxkEakOtzkbJMlFJiK/hMJ+HQUvTbtKW2s+t4uDK8DI16Rotsn6e0hS8xuXPmVte9ZzplD0fQgm2qwIDAQAB";
const { public_key_limbs, public_key_redc_limbs, signature_limbs } = JSON.parse(
get_limbs(publicKey, signatureBase64),
);
console.log("public_key_limbs", "[" + public_key_limbs.join(",") + "]");
console.log(
"public_key_redc_limbs",
"[" + public_key_redc_limbs.join(",") + "]",
);
console.log("signature_limbs", "[" + signature_limbs.join(",") + "]");
Now, we have all of the required data to verify the signature, so let's do it. I will copy-paste the data generated by javascript into a Noir test for simplicity:
#[test]
fn test_email_signature() {
let headers = [
102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 115, 45, 97, 115, 99, 105, 105, 13, 10, 109, 105, 109, 101, 45, 118, 101, 114, 115, 105, 111, 110, 58, 49, 46, 48, 32, 40, 77, 97, 99, 32, 79, 83, 32, 88, 32, 77, 97, 105, 108, 32, 49, 54, 46, 48, 32, 92, 40, 51, 55, 51, 49, 46, 53, 48, 48, 46, 50, 51, 49, 92, 41, 41, 13, 10, 115, 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 13, 10, 109, 101, 115, 115, 97, 103, 101, 45, 105, 100, 58, 60, 56, 70, 56, 49, 57, 68, 51, 50, 45, 66, 54, 65, 67, 45, 52, 56, 57, 68, 45, 57, 55, 55, 70, 45, 52, 51, 56, 66, 66, 67, 52, 67, 65, 66, 50, 55, 64, 109, 101, 46, 99, 111, 109, 62, 13, 10, 100, 97, 116, 101, 58, 83, 97, 116, 44, 32, 50, 54, 32, 65, 117, 103, 32, 50, 48, 50, 51, 32, 49, 50, 58, 50, 53, 58, 50, 50, 32, 43, 48, 52, 48, 48, 13, 10, 116, 111, 58, 122, 107, 101, 119, 116, 101, 115, 116, 64, 103, 109, 97, 105, 108, 46, 99, 111, 109, 13, 10, 100, 107, 105, 109, 45, 115, 105, 103, 110, 97, 116, 117, 114, 101, 58, 118, 61, 49, 59, 32, 97, 61, 114, 115, 97, 45, 115, 104, 97, 50, 53, 54, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, 108, 97, 120, 101, 100, 59, 32, 100, 61, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 59, 32, 115, 61, 49, 97, 49, 104, 97, 105, 59, 32, 116, 61, 49, 54, 57, 51, 48, 51, 56, 51, 51, 55, 59, 32, 98, 104, 61, 55, 120, 81, 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, 59, 32, 104, 61, 102, 114, 111, 109, 58, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 77, 105, 109, 101, 45, 86, 101, 114, 115, 105, 111, 110, 58, 83, 117, 98, 106, 101, 99, 116, 58, 77, 101, 115, 115, 97, 103, 101, 45, 73, 100, 58, 68, 97, 116, 101, 58, 116, 111, 59, 32, 98, 61
];
let pubkey_limbs = [
0xe5cf995b5ef59ce9943d1f4209b6ab, 0xe0caf03235e91a2db27e9ed214bcc6, 0xafe1309f87414bd36ed296dacfade2, 0xbeff3f19046a43adce46c932514988, 0x324041af8736e87de4358860fff057, 0xadcc6669dfa346f322717851a8c22a, 0x8b2a193089e6bf951c553b5a6f71aa, 0x0a570fe582918c4f731a0002068df2, 0x39419a433d6bfdd1978356cbca4b60, 0x550d695a514d38b45c862320a00ea5, 0x1c56ac1dfbf1beea31e8a613c2a51f, 0x6a30c9f22d2e5cb6934263d0838809, 0x0a281f268a44b21a4f77a91a52f960, 0x5134dc3966c8e91402669a47cc8597, 0x71590781df114ec072e641cdc5d224, 0xa1bc0f0937489c806c1944fd029dc9, 0x911f6e47f84db3b64c3648ebb5a127, 0xd5
];
let pubkey_redc_limbs = [
0xa48a824e4ebc7e0f1059f3ecfa57c4, 0x05c1db23f3c7d47ad7e7d7cfda5189, 0x79bb6bbbd8facf011f022fa9051aec, 0x24faa4cef474bed639362ea71f7a21, 0x1503aa50b77e24b030841a7d061581, 0x5bbf4e62805e1860a904c0f66a5fad, 0x5cbd24b72442d2ce647dd7d0a44368, 0x074a8839a4460c169dce7138efdaef, 0x0f06e09e3191b995b08e5b45182f65, 0x51fad4a89f8369fe10e5d4b6e149a1, 0xdc778b15982d11ebf7fe23b4e15f10, 0xa09ff3a4567077510c474e4ac0a21a, 0xb37e69e5dbb77167b73065e4c5ad6a, 0xecf4774e22e7fe3a38642186f7ae74, 0x16e72b5eb4c813a3b37998083aab81, 0xa48e7050aa8abedce5a45c16985376, 0xdd3285e53b322b221f7bcf4f8f8ad8, 0x0132
];
let signature_limbs = [
0x5779c85587e51cb8de5c29d7fdfeb0, 0xcd7ea8b6119f76f117ecb5042f8fc0, 0xeb7ac32b81d5a87bc2046fa0004e27, 0x62708c43b0c07a8fe8bdc97c479138, 0xc1e90d184f22a80be4a484a6ebd462, 0x39f3ff00e47728aaf74802d2d1d07b, 0x0f39de2cf99bf20dab7b8ae9240acd, 0xf4875cb76ce2538f255d70476136d6, 0xde151a5005ca614d6af7dd01e2a083, 0x6fe12b286f3195cae005fd7d2a1766, 0xd6e43a3060eccc555f2ee1e2929932, 0x0d5fa7cc79c794ae80310b491a1b40, 0x9cff415204cbc05c772ede05903440, 0xe7190ccff38575ae70dd055cd892d2, 0xf34bb777c0c842b0e88738eafdf634, 0x21040437e1e945a201ff58e542be68, 0x12f254fa4a0fb776ffe8759eb9eefa, 0x12
];
let result = verify_email(
headers,
headers.len(),
pubkey_limbs,
pubkey_redc_limbs,
signature_limbs
);
assert(result, "email verification failed");
}
And run the test:
nargo test
[zkemail] Running 1 test function
[zkemail] Testing test_email_signature... ok
[zkemail] 1 test passed
This is how to verify a DKIM signature in Noir. But there is more to a zkEmail account than just verifying DKIM signatures. In the end of the day, anybody can send an email and get a valid DKIM signature.
We also need to verify that the email was sent from the right address and that the email was sent to the right address. As well as that the email subject contains the correct calldata. Stay tuned, coming soon in Part 2!
Meanwhile, checkout:
- an email account on ShieldWallet: https://wallet.shieldswap.org/login/email
- prove that you received an email from someone using Noir: https://zkemail.shieldswap.org
- and swap some tokens on ShieldSwap (testnet, tokens are worthless): https://app.shieldswap.org