Skip to content

Commit

Permalink
add js interface and compressed r computation
Browse files Browse the repository at this point in the history
  • Loading branch information
jp4g committed Nov 3, 2024
1 parent afdf133 commit 9474eae
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 43 deletions.
8 changes: 8 additions & 0 deletions examples/remove_soft_line_breaks/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "remove_soft_line_breaks"
type = "bin"
authors = ["Mach 34"]
compiler_version = ">=0.35.0"

[dependencies]
zkemail = { path = "../../lib"}
64 changes: 64 additions & 0 deletions examples/remove_soft_line_breaks/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use zkemail::{
KEY_LIMBS_2048, dkim::RSAPubkey, headers::body_hash::get_body_hash,
standard_outputs, Sequence, remove_soft_line_breaks::remove_soft_line_breaks
};
use std::hash::sha256_var;

global MAX_EMAIL_HEADER_LENGTH: u32 = 512;
global MAX_EMAIL_BODY_LENGTH: u32 = 1024;

/**
* Verify an arbitrary email signed by a 2048-bit RSA DKIM signature
* @dev TOTAL CONSTRAINTS: ~222,783
*
* @param header - The email header, 0-padded at end to the MAX_EMAIL_HEADER_LENGTH
* @param body - The email body, 0-padded at end to the MAX_EMAIL_BODY_LENGTH
* @param pubkey - The DKIM RSA Public Key modulus and reduction parameter
* @param signature - The DKIM RSA Signature
* @param body_hash_index - The index of the body hash in the partial hash array
* @param dkim_header_sequence - The index and length of the DKIM header field
* @return -
* 0: Pedersen hash of DKIM public key (root of trust)
* 1: Pedersen hash of DKIM signature (email nullifier)
*/
fn main(
header: BoundedVec<u8, MAX_EMAIL_HEADER_LENGTH>,
body: BoundedVec<u8, MAX_EMAIL_BODY_LENGTH>,
decoded_body: BoundedVec<u8, MAX_EMAIL_BODY_LENGTH>,
pubkey: RSAPubkey<KEY_LIMBS_2048>,
signature: [Field; KEY_LIMBS_2048],
body_hash_index: u32,
dkim_header_sequence: Sequence
) -> pub [Field; 2] {
// check the body and header lengths are within bounds
assert(header.len() <= MAX_EMAIL_HEADER_LENGTH);
assert(body.len() <= MAX_EMAIL_BODY_LENGTH);

// // ~ 86,553 constraints
// // verify the dkim signature over the header
// pubkey.verify_dkim_signature(header, signature);

// // ~ 6,289 constraints
// // extract the body hash from the header
// let signed_body_hash = get_body_hash(header, dkim_header_sequence, body_hash_index);

// // ~ 113,962 constraints
// // hash the asserted body
// let computed_body_hash: [u8; 32] = sha256_var(body.storage(), body.len() as u64);

// // compare the body hashes
// assert(
// signed_body_hash == computed_body_hash, "SHA256 hash computed over body does not match body hash found in DKIM-signed header"
// );

// ensure the decoded body is the same as the original body
assert(
remove_soft_line_breaks(body.storage(), decoded_body.storage()),
"Decoded body does not properly remove soft line breaks"
);

// // ~ 10,255 constraints
// // hash the pubkey and signature for the standard outputs
// standard_outputs(pubkey.modulus, signature)
[0, 0]
}
2 changes: 1 addition & 1 deletion js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zk-email/zkemail-nr",
"version": "1.2.1",
"version": "1.2.2",
"main": "dist",
"types": "dist",
"license": "MIT",
Expand Down
65 changes: 38 additions & 27 deletions js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export type CircuitInput = {
// inputs used for only masking
header_mask?: string[];
body_mask?: string[];
// input for decoded body
decoded_body?: BoundedVec;
// inputs used for address extraction
from_header_sequence?: Sequence;
from_address_sequence?: Sequence;
Expand All @@ -62,33 +64,37 @@ export type InputGenerationArgs = {
extractTo?: boolean;
};

// copied without modification, but not publicly exported in original
// function removeSoftLineBreaks(body: string[]): string[] {
// const result = [];
// let i = 0;
// while (i < body.length) {
// if (
// i + 2 < body.length &&
// body[i] === "61" && // '=' character
// body[i + 1] === "13" && // '\r' character
// body[i + 2] === "10"
// ) {
// // '\n' character
// // Skip the soft line break sequence
// i += 3; // Move past the soft line break
// } else {
// result.push(body[i]);
// i++;
// }
// }
// // Pad the result with zeros to make it the same length as the body
// while (result.length < body.length) {
// result.push("0");
// }
// return result;
// }

// copied without modification, needed for different generateEmailVerifierInnputsFromDKIMResult
/** Formatted for BoundedVec in case used in other places */
function removeSoftLineBreaks(body: BoundedVec): BoundedVec {
const result = [];
let i = 0;
let count = 0;
while (i < body.storage.length) {
if (
i + 2 < body.storage.length &&
body.storage[i] === "61" && // '=' character
body.storage[i + 1] === "13" && // '\r' character
body.storage[i + 2] === "10"
) {
// '\n' character
// Skip the soft line break sequence
i += 3; // Move past the soft line break
} else {
result.push(body.storage[i]);
i++;
count++;
}
}
// Pad the result with zeros to make it the same length as the body
while (result.length < body.storage.length) {
result.push("0");
}
return {
storage: result,
len: count.toString()
};
}

/**
* @description Generate circuit inputs for the EmailVerifier circuit from raw email content
* @param rawEmail Full email content as a buffer or string
Expand Down Expand Up @@ -201,6 +207,11 @@ export function generateEmailVerifierInputsFromDKIMResult(
if (params.headerMask) circuitInputs.header_mask = params.headerMask.map((x) => x.toString());
if (params.bodyMask) circuitInputs.body_mask = params.bodyMask.map((x) => x.toString());

// remove soft line breaks
if (params.removeSoftLineBreaks) {
circuitInputs.decoded_body = removeSoftLineBreaks(circuitInputs.body);
}

// address extraction
if (params.extractFrom) {
const fromSequences = getAddressHeaderSequence(headers, "from");
Expand Down
6 changes: 4 additions & 2 deletions js/tests/circuits.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import fs from "fs";
import path from "path";
import { describe, beforeAll, } from "jest";
import { ZKEmailProver } from "../src/prover";
import { generateEmailVerifierInputs } from "../src/index";
import { makeEmailAddressCharTable, toProverToml } from "../src/utils";
import { generateEmailVerifierInputs } from "../src/index";
import { toProverToml } from "../src/utils";
// import circuit1024 from "../../examples/verify_email_1024_bit_dkim/target/verify_email_1024_bit_dkim.json";
import circuit2048 from "../../examples/verify_email_2048_bit_dkim/target/verify_email_2048_bit_dkim.json";
import circuitPartialHash from "../../examples/partial_hash/target/partial_hash.json";
import circuitEmailMask from "../../examples/email_mask/target/email_mask.json";
import circuitExtractAddresses from "../../examples/extract_addresses/target/extract_addresses.json";
import circuitRemoveSoftLineBreak from "../examples/remove_soft_line_breaks/target/remove_soft_line_breaks.json";

const emails = {
small: fs.readFileSync(path.join(__dirname, "./test-data/email-good.eml")),
Expand Down
1 change: 1 addition & 0 deletions lib/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ compiler_version = ">=0.35.0"
[dependencies]
rsa = { tag = "v0.3.1", git = "https://github.com/noir-lang/noir_rsa", directory = "lib" }
base64 = { tag = "v0.2.2", git = "https://github.com/noir-lang/noir_base64" }
nodash = { tag = "v0.36.0", git = "https://github.com/olehmisar/nodash" }
# string_search = { tag = "v0.1", git = "https://github.com/noir-lang/noir_string_search" }
35 changes: 29 additions & 6 deletions lib/src/remove_soft_line_breaks.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
use std::hash::poseidon2::Poseidon2;
use nodash::array::pack_bytes;

/**
* Computes R by packing bytes into fields before hashing to reduce the work
*
* @param encoded The encoded input array
* @param decoded The decoded input array
* @returns the poseidon hash of the bytes packed into field elements
*/
fn compressed_r<let N: u32>(
encoded: [u8; N],
decoded: [u8; N]
) -> Field {
let encoded_packed = pack_bytes(encoded);
let decoded_packed = pack_bytes(decoded);
let mut input: [Field; 2 * (N / 31 + 1)] = [0; 2 * (N / 31 + 1)];
for i in 0..encoded_packed.len() {
input[i] = encoded_packed[i];
input[i + N / 31 + 1] = decoded_packed[i];
}
Poseidon2::hash(input, input.len())
}

/**
* Remove soft line breaks from the given text
Expand All @@ -10,12 +32,13 @@ pub fn remove_soft_line_breaks<let N: u32>(
decoded: [u8; N]
) -> bool {
// derive r from poseidon hash
let mut r_input: [Field; 2*N] = [0; 2*N];
for i in 0..encoded.len() {
r_input[i] = encoded[i] as Field;
r_input[i + N] = encoded[i] as Field;
}
let r = Poseidon2::hash(r_input, 2*N);
// let mut r_input: [Field; 2*N] = [0; 2*N];
// for i in 0..encoded.len() {
// r_input[i] = encoded[i] as Field;
// r_input[i + N] = encoded[i] as Field;
// }
// let r = Poseidon2::hash(r_input, 2*N);
let r = compressed_r(encoded, decoded);

// check for "=" (0x3D), "\r" (0x0D), and "\n" (0x0A)
let mut is_equals: [bool; N] = [false; N];
Expand Down
7 changes: 0 additions & 7 deletions scripts/setup_helpers.sh

This file was deleted.

0 comments on commit 9474eae

Please sign in to comment.