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

Feat: Body Parsing #53

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
2ec4174
feat: email_auth template with body parsing
shreyas-londhe Aug 28, 2024
a9eb5b9
chore: bump zkemail-circuits to 6.1.5
shreyas-londhe Aug 29, 2024
253a02c
feat: extracting command from body in body-parsing
shreyas-londhe Aug 30, 2024
89f39f7
feat: email_auth body parsing circuit working with tests
shreyas-londhe Sep 1, 2024
6f6a726
fix: minor test changes
shreyas-londhe Sep 1, 2024
4de8d4d
chore: update dependencies
shreyas-londhe Sep 1, 2024
8685d3a
Merge branch 'feat/body-parsing-circuit' of https://github.com/zkemai…
SoraSuegami Sep 2, 2024
885b7a5
(wip) chore: pass github workflow
Bisht13 Sep 2, 2024
c88c574
Add a verifier contract
SoraSuegami Sep 2, 2024
48605a6
Merge branch 'feat/body-parsing-circuit' of https://github.com/zkemai…
SoraSuegami Sep 2, 2024
8a69a61
Update proving key url
SoraSuegami Sep 2, 2024
f5164a5
feat: add body parsing
Bisht13 Aug 21, 2024
cfa80b0
chore: update
Bisht13 Aug 21, 2024
f4dbfce
chore: separated body parsing tests
shreyas-londhe Sep 2, 2024
7214d3b
fix: refactring in recipent_enabled test
shreyas-londhe Sep 2, 2024
eedd202
fix: minor
shreyas-londhe Sep 2, 2024
e6caddf
chore: update relayer
Bisht13 Sep 2, 2024
9ccd3b9
fix: updated command regex
shreyas-londhe Sep 3, 2024
e2ed105
chore: changed max_command_bytes to 605
shreyas-londhe Sep 3, 2024
a8f2ef0
Update verifier
SoraSuegami Sep 3, 2024
05eaba9
Merge branch 'main' of https://github.com/zkemail/ether-email-auth in…
SoraSuegami Sep 3, 2024
80bdfc7
Merge branch 'main' of https://github.com/zkemail/ether-email-auth in…
SoraSuegami Sep 3, 2024
6b84605
feat: sha precompute test
shreyas-londhe Sep 3, 2024
2587fc8
chore: update relayer utils version
Bisht13 Sep 3, 2024
54d1526
feat: command update + relayer changes
Bisht13 Sep 4, 2024
19b46df
Fix circuit name in core.py
SoraSuegami Sep 4, 2024
05225ed
Merge branch 'feat/body-parsing-circuit' of https://github.com/zkemai…
SoraSuegami Sep 4, 2024
d192781
chore: update version
Bisht13 Sep 4, 2024
4fa904d
chore: update circuit test
Bisht13 Sep 4, 2024
3f62a9b
chore: update relayer-uitls dep
shreyas-londhe Sep 4, 2024
dca6793
Update verifier
SoraSuegami Sep 4, 2024
d8eb920
Change the max header/body sizes in the body-parsing circuits.
SoraSuegami Sep 4, 2024
97094f9
Add test circuits for body parsing
SoraSuegami Sep 4, 2024
c5f4bb5
Update verifier
SoraSuegami Sep 5, 2024
648720a
Fixing integration test
SoraSuegami Sep 6, 2024
703ce4e
Update emails for integration tests
SoraSuegami Sep 6, 2024
6edbe7a
Update yarn.lock
SoraSuegami Sep 6, 2024
b51d635
Fix scripts and email for integration test
SoraSuegami Sep 6, 2024
0566f14
chore: update circuit test
Bisht13 Sep 6, 2024
fbd3844
Integration test worked
SoraSuegami Sep 6, 2024
69963ef
Merge branch 'feat/body-parsing-circuit' of https://github.com/zkemai…
SoraSuegami Sep 6, 2024
d2f59da
fix: body parsing test
Bisht13 Sep 6, 2024
74c4557
feat: complete flow
Bisht13 Sep 6, 2024
cdf17ec
Replace subject with command.
SoraSuegami Sep 7, 2024
802e284
Merge branch 'feat/body-parsing-circuit' of https://github.com/zkemai…
SoraSuegami Sep 7, 2024
776fa59
Fix compile errors in relayer
SoraSuegami Sep 7, 2024
16eaa85
Remove skipped_command_bytes
SoraSuegami Sep 7, 2024
9885c6e
chore: refactor
Bisht13 Sep 8, 2024
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
1,165 changes: 342 additions & 823 deletions Cargo.lock

Large diffs are not rendered by default.

48 changes: 32 additions & 16 deletions packages/circuits/helpers/email_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,38 @@ import fs from "fs";
import { promisify } from "util";
const relayerUtils = require("@zk-email/relayer-utils");

export async function genEmailAuthInput(
emailFilePath: string,
accountCode: string
export async function genEmailCircuitInput(
emailFilePath: string,
accountCode: string,
options?: {
shaPrecomputeSelector?: string;
maxHeaderLength?: number;
maxBodyLength?: number;
ignoreBodyHashCheck?: boolean;
}
): Promise<{
padded_header: string[];
public_key: string[];
signature: string[];
padded_header_len: string;
account_code: string;
from_addr_idx: number;
subject_idx: number;
domain_idx: number;
timestamp_idx: number;
code_idx: number;
padded_header: string[];
public_key: string[];
signature: string[];
padded_header_len: string;
account_code: string;
from_addr_idx: number;
subject_idx: number;
domain_idx: number;
timestamp_idx: number;
code_idx: number;
body_hash_idx: number;
precomputed_sha: string[];
padded_body: string[];
padded_body_len: string;
command_idx: number;
padded_cleaned_body: string[];
}> {
const emailRaw = await promisify(fs.readFile)(emailFilePath, "utf8");
const jsonStr = await relayerUtils.genEmailAuthInput(emailRaw, accountCode);
return JSON.parse(jsonStr);
const emailRaw = await promisify(fs.readFile)(emailFilePath, "utf8");
const jsonStr = await relayerUtils.genEmailCircuitInput(
emailRaw,
accountCode,
options
);
return JSON.parse(jsonStr);
}
4 changes: 2 additions & 2 deletions packages/circuits/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"test": "NODE_OPTIONS=--max_old_space_size=8192 jest"
},
"dependencies": {
"@zk-email/circuits": "^6.1.1",
"@zk-email/circuits": "^6.1.5",
"@zk-email/zk-regex-circom": "^2.1.0",
"@zk-email/relayer-utils": "^0.2.4",
"@zk-email/relayer-utils": "^0.3.0",
"commander": "^11.0.0",
"snarkjs": "^0.7.0"
},
Expand Down
197 changes: 191 additions & 6 deletions packages/circuits/src/email_auth_template.circom
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ include "circomlib/circuits/comparators.circom";
include "circomlib/circuits/poseidon.circom";
include "@zk-email/circuits/email-verifier.circom";
include "@zk-email/circuits/utils/regex.circom";
include "@zk-email/circuits/utils/array.circom";
include "./utils/constants.circom";
include "./utils/account_salt.circom";
include "./utils/hash_sign.circom";
Expand All @@ -15,6 +16,7 @@ include "./utils/hex2int.circom";
include "./utils/email_addr_commit.circom";
include "./regexes/invitation_code_with_prefix_regex.circom";
include "./regexes/invitation_code_regex.circom";
include "./regexes/command_regex.circom";
include "@zk-email/zk-regex-circom/circuits/common/from_addr_regex.circom";
include "@zk-email/zk-regex-circom/circuits/common/email_addr_regex.circom";
include "@zk-email/zk-regex-circom/circuits/common/email_domain_regex.circom";
Expand All @@ -33,7 +35,6 @@ template EmailAuth(n, k, max_header_bytes, max_subject_bytes, recipient_enabled)
signal input public_key[k]; // RSA public key (modulus), k parts of n bits each.
signal input signature[k]; // RSA signature, k parts of n bits each.
signal input padded_header_len; // length of in email data including the padding
// signal input sender_relayer_rand; // Private randomness of the relayer
signal input account_code;
signal input from_addr_idx; // Index of the from email address (= sender email address) in the email header
signal input subject_idx; // Index of the subject in the header
Expand All @@ -45,7 +46,7 @@ template EmailAuth(n, k, max_header_bytes, max_subject_bytes, recipient_enabled)
var email_max_bytes = email_max_bytes_const();
var subject_field_len = compute_ints_size(max_subject_bytes);
var domain_len = domain_len_const();
var domain_filed_len = compute_ints_size(domain_len);
var domain_field_len = compute_ints_size(domain_len);
var k2_chunked_size = k >> 1;
if(k % 2 == 1) {
k2_chunked_size += 1;
Expand All @@ -54,7 +55,7 @@ template EmailAuth(n, k, max_header_bytes, max_subject_bytes, recipient_enabled)
var code_len = invitation_code_len_const();


signal output domain_name[domain_filed_len];
signal output domain_name[domain_field_len];
signal output public_key_hash;
signal output email_nullifier;
signal output timestamp;
Expand All @@ -63,12 +64,11 @@ template EmailAuth(n, k, max_header_bytes, max_subject_bytes, recipient_enabled)
signal output is_code_exist;

// Verify Email Signature
component email_verifier = EmailVerifier(max_header_bytes, 0, n, k, 1);
component email_verifier = EmailVerifier(max_header_bytes, 0, n, k, 1, 0, 0);
email_verifier.emailHeader <== padded_header;
email_verifier.pubkey <== public_key;
email_verifier.signature <== signature;
email_verifier.emailHeaderLength <== padded_header_len;
signal header_hash[256] <== email_verifier.sha;
public_key_hash <== email_verifier.pubkeyHash;

// FROM HEADER REGEX
Expand Down Expand Up @@ -168,7 +168,7 @@ template EmailAuth(n, k, max_header_bytes, max_subject_bytes, recipient_enabled)
cm_rand_input[k2_chunked_size] <== 1;
signal cm_rand <== Poseidon(k2_chunked_size+1)(cm_rand_input);
signal replaced_email_addr_regex_reveal[max_subject_bytes];
for(var i=0; i<max_subject_bytes; i++) {
for(var i=0; i < max_subject_bytes; i++) {
if(i==0) {
replaced_email_addr_regex_reveal[i] <== (subject_email_addr_regex_reveal[i] - 1) * has_email_recipient + 1;
} else {
Expand All @@ -182,6 +182,191 @@ template EmailAuth(n, k, max_header_bytes, max_subject_bytes, recipient_enabled)
recipient_email_addr[i] <== shifted_email_addr[i] * has_email_recipient;
}

signal recipient_email_addr_ints[num_email_addr_ints] <== Bytes2Ints(email_max_bytes)(recipient_email_addr);
signal recipient_email_addr_commit_raw;
recipient_email_addr_commit_raw <== EmailAddrCommit(num_email_addr_ints)(cm_rand, recipient_email_addr_ints);
recipient_email_addr_commit <== has_email_recipient * recipient_email_addr_commit_raw;
}
}

// Verify email from user (sender) and extract subject, timestmap, recipient email (commitment), etc.
// * n - the number of bits in each chunk of the RSA public key (modulust)
// * k - the number of chunks in the RSA public key (n * k > 2048)
// * max_header_bytes - max number of bytes in the email header
// * max_body_bytes - max number of bytes in the email body
// * max_command_bytes - max number of bytes in the command
// * recipient_enabled - whether the email address commitment of the recipient = email address in the subject is exposed
// * is_qp_encoded - whether the email body is qp encoded
template EmailAuthWithBodyParsing(n, k, max_header_bytes, max_body_bytes, max_command_bytes, recipient_enabled, is_qp_encoded) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_qp_encoded

Can we fix the value of is_qp_encoded when operating the service?
Or do we need to deploy two circuits and choose which circuit is used for each email?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second option, because the flag will be turned on for email with big commands (which have soft-line breaks) and should be turned off for small ones as the proving time improves.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think a malicious relayer can choose an inappropriate circuit, which can be an attack vector?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the reason to turn off the flag is only for efficiency, I think we can always use a circuit with soft-line breaks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any attack surface here as the regex just won't pass if you send a body with QP encoding but don't remove those in the circuit. We can do the swapping thing imo

signal input padded_header[max_header_bytes]; // email data (only header part)
signal input padded_header_len; // length of in email data including the padding
signal input public_key[k]; // RSA public key (modulus), k parts of n bits each.
signal input signature[k]; // RSA signature, k parts of n bits each.
signal input body_hash_idx; // index of the bodyhash in the header
signal input precomputed_sha[32]; // precomputed sha256 of the email body
signal input padded_body[max_body_bytes]; // email data (only body part)
signal input padded_body_len; // length of in email data including the padding
signal input account_code;
signal input from_addr_idx; // Index of the from email address (= sender email address) in the email header
signal input domain_idx; // Index of the domain name in the from email address
signal input timestamp_idx; // Index of the timestamp in the header
signal input code_idx; // index of the invitation code in the header
signal input command_idx; // index of the command in the body
/// Note: padded_cleaned_body is only used for qp encoded email body,
/// for non-qp encoded email body, it should be equal to padded_body
signal input padded_cleaned_body[max_body_bytes]; // cleaned email body

var email_max_bytes = email_max_bytes_const();
var command_field_len = compute_ints_size(max_command_bytes);
var domain_len = domain_len_const();
var domain_field_len = compute_ints_size(domain_len);
var k2_chunked_size = k >> 1;
if(k % 2 == 1) {
k2_chunked_size += 1;
}
var timestamp_len = timestamp_len_const();
var code_len = invitation_code_len_const();

signal output domain_name[domain_field_len];
signal output public_key_hash;
signal output email_nullifier;
signal output timestamp;
signal output masked_command[command_field_len];
signal output account_salt;
signal output is_code_exist;

// Verify Email Signature
component email_verifier = EmailVerifier(max_header_bytes, max_body_bytes, n, k, 0, is_qp_encoded, 0);
email_verifier.emailHeader <== padded_header;
email_verifier.emailHeaderLength <== padded_header_len;
email_verifier.pubkey <== public_key;
email_verifier.signature <== signature;
email_verifier.bodyHashIndex <== body_hash_idx;
email_verifier.precomputedSHA <== precomputed_sha;
email_verifier.emailBody <== padded_body;
email_verifier.emailBodyLength <== padded_body_len;
if (is_qp_encoded == 1) {
email_verifier.decodedEmailBodyIn <== padded_cleaned_body;
}
public_key_hash <== email_verifier.pubkeyHash;

// FROM HEADER REGEX
signal from_regex_out, from_regex_reveal[max_header_bytes];
(from_regex_out, from_regex_reveal) <== FromAddrRegex(max_header_bytes)(padded_header);
from_regex_out === 1;
signal from_email_addr[email_max_bytes];
from_email_addr <== SelectRegexReveal(max_header_bytes, email_max_bytes)(from_regex_reveal, from_addr_idx);

// DOMAIN NAME HEADER REGEX
signal domain_regex_out, domain_regex_reveal[email_max_bytes];
(domain_regex_out, domain_regex_reveal) <== EmailDomainRegex(email_max_bytes)(from_email_addr);
domain_regex_out === 1;
signal domain_name_bytes[domain_len];
domain_name_bytes <== SelectRegexReveal(email_max_bytes, domain_len)(domain_regex_reveal, domain_idx);
domain_name <== Bytes2Ints(domain_len)(domain_name_bytes);

signal sign_hash;
signal sign_ints[k2_chunked_size];
(sign_hash, sign_ints) <== HashSign(n,k)(signature);
email_nullifier <== EmailNullifier()(sign_hash);

// Timestamp regex + convert to decimal format
signal timestamp_regex_out, timestamp_regex_reveal[max_header_bytes];
(timestamp_regex_out, timestamp_regex_reveal) <== TimestampRegex(max_header_bytes)(padded_header);
signal timestamp_str[timestamp_len];
timestamp_str <== SelectRegexReveal(max_header_bytes, timestamp_len)(timestamp_regex_reveal, timestamp_idx);
signal raw_timestamp <== Digit2Int(timestamp_len)(timestamp_str);
timestamp <== timestamp_regex_out * raw_timestamp;

// Extract the command from the body
signal command_regex_out, command_regex_reveal[max_body_bytes];
if (is_qp_encoded != 1) {
(command_regex_out, command_regex_reveal) <== CommandRegex(max_body_bytes)(padded_body);
} else {
(command_regex_out, command_regex_reveal) <== CommandRegex(max_body_bytes)(padded_cleaned_body);
}
command_regex_out === 1;
signal command_all[max_command_bytes];
command_all <== SelectRegexReveal(max_body_bytes, max_command_bytes)(command_regex_reveal, command_idx);

signal prefixed_code_regex_out, prefixed_code_regex_reveal[max_command_bytes];
(prefixed_code_regex_out, prefixed_code_regex_reveal) <== InvitationCodeWithPrefixRegex(max_command_bytes)(command_all);
is_code_exist <== IsZero()(prefixed_code_regex_out-1);
signal removed_code[max_command_bytes];
for(var i = 0; i < max_command_bytes; i++) {
removed_code[i] <== is_code_exist * prefixed_code_regex_reveal[i];
}
signal command_email_addr_regex_out, command_email_addr_regex_reveal[max_command_bytes];
(command_email_addr_regex_out, command_email_addr_regex_reveal) <== EmailAddrRegex(max_command_bytes)(command_all);
signal is_command_email_addr_exist <== IsZero()(command_email_addr_regex_out-1);
signal removed_command_email_addr[max_command_bytes];
for(var i = 0; i < max_command_bytes; i++) {
removed_command_email_addr[i] <== is_command_email_addr_exist * command_email_addr_regex_reveal[i];
}
signal masked_command_bytes[max_command_bytes];
for(var i = 0; i < max_command_bytes; i++) {
masked_command_bytes[i] <== command_all[i] - removed_code[i] - removed_command_email_addr[i];
}
masked_command <== Bytes2Ints(max_command_bytes)(masked_command_bytes);

// INVITATION CODE REGEX
signal code_regex_out, code_regex_reveal[max_body_bytes];
if (is_qp_encoded != 1) {
(code_regex_out, code_regex_reveal) <== InvitationCodeRegex(max_body_bytes)(padded_body);
} else {
(code_regex_out, code_regex_reveal) <== InvitationCodeRegex(max_body_bytes)(padded_cleaned_body);
}
signal code_consistency <== IsZero()(is_code_exist * (1 - code_regex_out));
code_consistency === 1;
signal replaced_code_regex_reveal[max_body_bytes];
for(var i=0; i<max_body_bytes; i++) {
if(i==0) {
replaced_code_regex_reveal[i] <== (code_regex_reveal[i] - 1) * is_code_exist + 1;
} else {
replaced_code_regex_reveal[i] <== code_regex_reveal[i] * is_code_exist;
}
}
signal shifted_code_hex[code_len] <== SelectRegexReveal(max_body_bytes, code_len)(replaced_code_regex_reveal, code_idx);
signal invitation_code_hex[code_len];
for(var i=0; i<code_len; i++) {
invitation_code_hex[i] <== is_code_exist * (shifted_code_hex[i] - 48) + 48;
}
signal embedded_account_code <== Hex2Field()(invitation_code_hex);
is_code_exist * (embedded_account_code - account_code) === 0;

// Account salt
var num_email_addr_ints = compute_ints_size(email_max_bytes);
signal from_addr_ints[num_email_addr_ints] <== Bytes2Ints(email_max_bytes)(from_email_addr);
account_salt <== AccountSalt(num_email_addr_ints)(from_addr_ints, account_code);

if(recipient_enabled==1) {
signal input command_email_addr_idx;
signal output has_email_recipient;
signal output recipient_email_addr_commit;
has_email_recipient <== is_command_email_addr_exist;

// Email address commitment
signal cm_rand_input[k2_chunked_size+1];
for(var i=0; i<k2_chunked_size;i++){
cm_rand_input[i] <== sign_ints[i];
}
cm_rand_input[k2_chunked_size] <== 1;
signal cm_rand <== Poseidon(k2_chunked_size+1)(cm_rand_input);
signal replaced_email_addr_regex_reveal[max_command_bytes];
for(var i=0; i < max_command_bytes; i++) {
if(i==0) {
replaced_email_addr_regex_reveal[i] <== (command_email_addr_regex_reveal[i] - 1) * has_email_recipient + 1;
} else {
replaced_email_addr_regex_reveal[i] <== command_email_addr_regex_reveal[i] * has_email_recipient;
}
}
signal shifted_email_addr[email_max_bytes];
shifted_email_addr <== SelectRegexReveal(max_command_bytes, email_max_bytes)(replaced_email_addr_regex_reveal, command_email_addr_idx);
signal recipient_email_addr[email_max_bytes];
for(var i=0; i < email_max_bytes; i++) {
recipient_email_addr[i] <== shifted_email_addr[i] * has_email_recipient;
}

signal recipient_email_addr_ints[num_email_addr_ints] <== Bytes2Ints(email_max_bytes)(recipient_email_addr);
signal recipient_email_addr_commit_raw;
recipient_email_addr_commit_raw <== EmailAddrCommit(num_email_addr_ints)(cm_rand, recipient_email_addr_ints);
Expand Down
5 changes: 5 additions & 0 deletions packages/circuits/src/email_auth_with_body_parsing.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.6;

include "./email_auth_template.circom";

component main = EmailAuthWithBodyParsing(121, 17, 640, 768, 256, 0, 0);
shreyas-londhe marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.6;

include "./email_auth_template.circom";

component main = EmailAuthWithBodyParsing(121, 17, 640, 768, 256, 0, 1);
shreyas-londhe marked this conversation as resolved.
Show resolved Hide resolved
16 changes: 16 additions & 0 deletions packages/circuits/src/regexes/command.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"parts": [
{
"is_public": false,
"regex_def": "<div dir=3D\"zkemail\">"
},
{
"is_public": true,
"regex_def": "[^<>/]+"
},
{
"is_public": false,
"regex_def": "</div>"
}
]
}
Loading
Loading