Skip to content

Commit

Permalink
Merge pull request #14 from zkemail/chore/update-readme-0.3.2
Browse files Browse the repository at this point in the history
Chore/update readme 0.3.2
  • Loading branch information
jp4g authored Oct 21, 2024
2 parents e375593 + a2f0fa2 commit 14f3faf
Show file tree
Hide file tree
Showing 9 changed files with 695 additions and 125 deletions.
123 changes: 75 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,119 @@ ZKEmail written in [NoirLang](https://noir-lang.org/)

In your Nargo.toml file, add the version of this library you would like to install under dependency:

```
```toml
[dependencies]
zkemail = { tag = "v0.3.1", git = "https://github.com/zkemail/zkemail.nr", directory = "lib" }
zkemail = { tag = "v0.3.2", git = "https://github.com/zkemail/zkemail.nr", directory = "lib" }
```

The library exports the following functions:
- `verify_dkim_1024` and `verify_dkim_2048` - for verifying DKIM signatures over an email header. This is needed for all email verifications.
- `get_body_hash_by_index` - to get the body hash from the header.
- `body_hash_base64_decode` - to decode the body hash from the header.
- Above two methods are needed to verify the body hash of an email. This is only needed if you want to contrain something over the email body.
- `dkim::RSAPubkey::verify_dkim_signature` - for verifying DKIM signatures over an email header. This is needed for all email verifications.
- `headers::body_hash::get_body_hash` - constrained access and decoding of the body hash from the header
- `headers::email_address::get_email_address` - constrained extraction of to or from email addresses
- `headers::constrain_header_field` - constrain an index/ length in the header to be the correct name, full, and uninterrupted
- `partial_hash::partial_sha256_var_end` - finish a precomputed sha256 hash over the body
- `masking::mask_text` - apply a byte mask to the header or body to selectively reveal parts of the entire email
- `standard_outputs` - returns the hash of the DKIM pubkey and a nullifier for the email (`hash(signature)`)

Additionally, the `@zk-email/zkemail-nr` JS library exports an ergonomic API for easily deriving circuit inputs needed to utilize the Noir library.

```
For demonstrations of all functionality, see the [examples](./examples).

### Basic Email Verification
A basic email verifier will often look like this:
```rust
use dep::zkemail::{
KEY_LIMBS_1024, dkim::verify_dkim_1024, get_body_hash_by_index,
KEY_LIMBS_1024, dkim::RSAPubkey, get_body_hash_by_index,
base64::body_hash_base64_decode, standard_outputs
};
use dep::std::hash::sha256_var;

// Somewhere in your function
...
verify_dkim_1024(header, header_length, pubkey, pubkey_redc, signature);
let body_hash_encoded = get_body_hash_by_index(header, body_hash_index);
let signed_body_hash: [u8; 32] = body_hash_base64_decode(body_hash_encoded);
let computed_body_hash: [u8; 32] = sha256_var(body, body_length as u64);
// verify the dkim signature over the asserted header
pubkey.verify_dkim_signature(header, signature);
// extract the body hash from the header
let signed_body_hash = get_body_hash(header, dkim_header_sequence, body_hash_index);
// compute the sha256 hash of the asserted body
let computed_body_hash: [u8; 32] = sha256_var(body.storage, body.len() as u64);
// constain the computed body hash to match the one found in the header
assert(
signed_body_hash == computed_body_hash, "SHA256 hash computed over body does not match body hash found in DKIM-signed header"
signed_body_hash == computed_body_hash,
"SHA256 hash computed over body does not match body hash found in DKIM-signed header"
);
...
```
From here, you can operate on the header or body with guarantees that the accessed text was signed by the DKIM key.

You may also have an email where you need access to the header, but not the body. You can simply omit everything after `verify_dkim_signature` and proceed!

### Usage with partial SHA

You can use partial hashing technique for email with large body when the part you want to contrain in the body is towards the end.
You can use partial hashing technique for email with large body when the part you want to constrain in the body is towards the end.

Since SHA works in chunks of 64 bytes, we can hash the body up to the chunk from where we want to extract outside of the circuit and do the remaining hash in the circuit. This will save a lot of constraints as SHA is very expensive in circuit.
Since SHA works in chunks of 64 bytes, we can hash the body up to the chunk from where we want to extract outside of the circuit and do the remaining hash in the circuit. This will save a lot of constraints as SHA is very expensive in circuit (~100 constraints/ byte).

```
```rust
use dep::zkemail::{
KEY_LIMBS_2048, dkim::verify_dkim_2048, get_body_hash_by_index,
partial_hash::partial_sha256_var_end, base64::body_hash_base64_decode,
KEY_LIMBS_2048, dkim::RSAPubkey, headers::body_hash::get_body_hash,
partial_hash::partial_sha256_var_end
};

...
// verify the dkim signature over the header
verify_dkim_2048(header, header_length, pubkey, pubkey_redc, signature);
// manually extract the body hash from the header
let body_hash_encoded = get_body_hash_by_index(header, body_hash_index);
let signed_body_hash: [u8; 32] = body_hash_base64_decode(body_hash_encoded);
// verify the dkim signature over the asserted header
pubkey.verify_dkim_signature(header, signature);
// extract the body hash from the header
let signed_body_hash = get_body_hash(header, dkim_header_sequence, body_hash_index);
// finish the partial hash
let computed_body_hash = partial_sha256_var_end(partial_body_hash, body, partial_body_length as u64, body_length as u64);
// check the body hashes match
let computed_body_hash = partial_sha256_var_end(partial_body_hash, body.storage(), body.len() as u64, partial_body_real_length);
// constain the computed body hash to match the one found in the header
assert(
signed_body_hash == computed_body_hash, "Sha256 hash computed over body does not match DKIM-signed header"
signed_body_hash == computed_body_hash,
"SHA256 hash computed over body does not match body hash found in DKIM-signed header"
);
...
```

Find more examples in the [examples](./examples) folder.
### Extracting Email Addresses

To and from email addresses can be extracted from the header with `get_email_address`
```rust
use dep::zkemail::get_email_address;
...
// define the header field to access (set "to" or "from")
let to = comptime { "to".as_bytes() };
// constrained retrieval of the email header
let to_address = get_email_address(header, to_header_sequence, to_address_sequence, to);
...
```
`to_address` is a "BoundedVec", meaning the output of a parsed email address "[email protected]" would export
```json
{
"storage": [122, 107, 101, 109, 97, 105, 108, 64, 112, 114, 111, 118, 101, 46, 101, 109, 97, 105, 108, 0, ..., 0],
"len": 19
}
```
which is easily interpreted with `Buffer.from(output.storage.slice(0, output.storage.len)).toString()`. You can additionally perform your own transformations or commitments in-circuit.


## Using the Input Generation JS Library

Install the library:
```
yarn add @mach-34/zkemail-nr
```console
yarn add @zk-email/zkemail-nr
```

### Usage
See the [witness simulation](./js/tests/circuits.test.ts) and [proving](./js/tests/proving.test.ts) tests for an in-depth demonstration of each use case.

```
import { generateEmailVerifierInputs } from "@mach-34/zkemail-nr";
```js
// example of generating inputs for a partial hash
import { generateEmailVerifierInputs } from "@zk-email/zkemail-nr";

const zkEmailInputs = await generateEmailVerifierInputs(emailContent, {
maxBodyLength: 1280, // Same as MAX_PARTIAL_EMAIL_BODY_LENGTH in circuit
maxHeadersLength: 1408, // Same as MAX_EMAIL_HEADER_LENGTH in circuit
shaPrecomputeSelector: "some string in body up to which you want to hash outside circuit", // if you want to use partial hashing
maxBodyLength: 1280,
maxHeadersLength: 1408,
shaPrecomputeSelector: "some string in body up to which you want to hash outside circuit",
});

```
Expand All @@ -100,15 +129,13 @@ TODO
TODO

## Todo
- Negative Unit Testing
- Expected InputGen testing
- Robust from/ to string search implementation
- Contract/ testing for UltraPlonk reintegrated
- EVM Contract tests for email integration
- Aztec Contract tests for email integration
- 1024-bit key demo eml (current one is sensitive and cannot be provided in public repo)
- DKIM Key pedersen hash function
- Handle optional inputs (partial hashing, no body check, etc) gracefully again
- Partial SHA256 hashing
- Implementation with Regex
- test does not exit on completion?
- Implementation with Regex
- Add constraint estimations and benchmarking
- Add native proving scripts
- Macro Impl

By [Mach-34](https://mach34.space)
4 changes: 2 additions & 2 deletions js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mach-34/zkemail-nr",
"version": "1.2.0",
"name": "@zk-email/zkemail-nr",
"version": "1.2.1",
"main": "dist",
"types": "dist",
"license": "MIT",
Expand Down
6 changes: 5 additions & 1 deletion js/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ export function toProverToml(inputs: any): string {
} else {
let values = "";
for (const [k, v] of Object.entries(value!)) {
values = values.concat(`${k} = '${v}'\n`);
if (Array.isArray(v)) {
values = values.concat(`${k} = [${v.map((val) => `'${val}'`).join(", ")}]\n`);
} else {
values = values.concat(`${k} = '${v}'\n`);
}
}
structs.push(`[${key}]\n${values}`);
}
Expand Down
3 changes: 2 additions & 1 deletion js/tests/circuits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from "fs";
import path from "path";
import { ZKEmailProver } from "../src/prover";
import { generateEmailVerifierInputs } from "../src/index";
import { makeEmailAddressCharTable } from "../src/utils";
import { makeEmailAddressCharTable, 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";
Expand Down Expand Up @@ -59,6 +59,7 @@ describe("ZKEmail.nr Circuit Unit Tests", () => {
inputParams
);
await prover2048.simulateWitness(inputs);
console.log(toProverToml(inputs));
});
it("Partial Hash", async () => {
const inputs = await generateEmailVerifierInputs(emails.large, {
Expand Down
18 changes: 18 additions & 0 deletions js/tests/test-data/email-header-tampered.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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: Hello
Message-Id: <[email protected]>
Date: Sat, 26 Aug 2023 12:25:22 +0400
to: [email protected]

Hello,

How are you?
9 changes: 5 additions & 4 deletions lib/src/lib.nr
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use dep::std::{hash::pedersen_hash, collections::bounded_vec::BoundedVec};
use crate::dkim::RSAPubkey;

mod dkim;
mod headers;
mod partial_hash;
mod masking;
pub mod dkim;
pub mod headers;
pub mod partial_hash;
pub mod masking;
// mod macro;
mod tests;

global RSA_EXPONENT: u32 = 65537;
global KEY_BYTES_1024: u32 = 128;
Expand Down
Loading

0 comments on commit 14f3faf

Please sign in to comment.