-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
52 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,89 +14,105 @@ The library exports the following functions: | |
- `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 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" | ||
); | ||
... | ||
``` | ||
|
||
### Extracting Email Addresses | ||
|
||
In the header, email addresses can appear in a variety of formats: | ||
* `"name" <[email protected]>` | ||
* `name <[email protected]>` | ||
* `name [email protected]` | ||
* `[email protected]` | ||
Without regex, we take a slightly different approach. | ||
|
||
|
||
Find examples of each implementation in the [examples](./examples) folder. | ||
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: | ||
```console | ||
yarn add @mach-34/zkemail-nr | ||
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. | ||
|
||
```js | ||
// example of generating inputs for a partial hash | ||
import { generateEmailVerifierInputs } from "@mach-34/zkemail-nr"; | ||
import { generateEmailVerifierInputs } from "@zk-email/zkemail-nr"; | ||
|
||
const zkEmailInputs = await generateEmailVerifierInputs(emailContent, { | ||
maxBodyLength: 1280, | ||
|
@@ -113,7 +129,6 @@ TODO | |
TODO | ||
|
||
## Todo | ||
- Negative Unit Testing | ||
- Expected InputGen testing | ||
- EVM Contract tests for email integration | ||
- Aztec Contract tests for email integration | ||
|
@@ -123,19 +138,4 @@ TODO | |
- Add native proving scripts | ||
- Macro Impl | ||
|
||
### Proposed Macro Impl | ||
```rust | ||
// zkemail attribute would automatically inject fields | ||
// header: BoundedVec<u8, MAX_HEADER_LEN>, | ||
// pubkey: RSAPubkey<KEY_LIMBS>, | ||
// signature: [Field; KEY_LIMBS] | ||
// and add range check header.len() + pubkey::verify_rsa_signature | ||
// it then would add additional necessary fields for each trait, add the | ||
#[zkemail(PartialHash, From, DKIMHash, Nullifier)] | ||
let Input<let MAX_HEADER_LENGTH: u32, let MAX_BODY_LEN: u32, let KEY_LIMBS: u32> {}; | ||
//fn main(input: Input) { | ||
// input.verify() | ||
//} | ||
``` | ||
|
||
By [Mach-34](https://mach34.space) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters