Skip to content

Commit

Permalink
Merge pull request #24 from zkemail/feat/redc-manipulation
Browse files Browse the repository at this point in the history
veridise audit fix: redc field is manipulable
  • Loading branch information
jp4g authored Jan 13, 2025
2 parents 32e9559 + edb81e6 commit 7a3a841
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 61 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ The library exports the following functions:
- `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.

Expand All @@ -28,9 +27,9 @@ A basic email verifier will often look like this:
```rust
use dep::zkemail::{
KEY_LIMBS_1024, dkim::RSAPubkey, get_body_hash_by_index,
base64::body_hash_base64_decode, standard_outputs
base64::body_hash_base64_decode
};
use dep::std::hash::sha256_var;
use dep::std::hash::{sha256_var, pedersen_hash};

// Somewhere in your function
...
Expand Down
7 changes: 4 additions & 3 deletions examples/email_mask/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use dep::zkemail::{
KEY_LIMBS_2048, dkim::RSAPubkey, headers::body_hash::get_body_hash,
standard_outputs, Sequence, masking::mask_text
Sequence, masking::mask_text
};
use dep::std::{collections::bounded_vec::BoundedVec, hash::sha256_var};
use dep::std::{collections::bounded_vec::BoundedVec, hash::{pedersen_hash, sha256_var}};

global MAX_EMAIL_HEADER_LENGTH: u32 = 512;
global MAX_EMAIL_BODY_LENGTH: u32 = 1024;
Expand Down Expand Up @@ -55,6 +55,7 @@ fn main(
let masked_body = mask_text(body, body_mask);

// hash the pubkey and signature for the standard outputs
let standard_out = standard_outputs(pubkey.modulus, signature);
let email_nullifier = pedersen_hash(signature);
let standard_out = [pubkey.hash(), email_nullifier];
(standard_out, masked_header, masked_body)
}
23 changes: 9 additions & 14 deletions examples/extract_addresses/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use dep::zkemail::{
KEY_LIMBS_2048, dkim::RSAPubkey,
headers::{body_hash::get_body_hash, email_address::get_email_address}, standard_outputs, Sequence,
MAX_EMAIL_ADDRESS_LENGTH
KEY_LIMBS_2048, dkim::RSAPubkey, headers::email_address::get_email_address, Sequence,
MAX_EMAIL_ADDRESS_LENGTH,
};
use dep::std::{collections::bounded_vec::BoundedVec, hash::sha256_var};
use dep::std::{collections::bounded_vec::BoundedVec, hash::pedersen_hash};

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 and extract sender and recipient addresses
Expand All @@ -30,26 +28,23 @@ fn main(
from_header_sequence: Sequence,
from_address_sequence: Sequence,
to_header_sequence: Sequence,
to_address_sequence: Sequence
) -> pub ([Field; 2], BoundedVec<u8, MAX_EMAIL_ADDRESS_LENGTH>, BoundedVec<u8, MAX_EMAIL_ADDRESS_LENGTH>) {
to_address_sequence: Sequence,
) -> pub ([Field; 2], BoundedVec<u8, MAX_EMAIL_ADDRESS_LENGTH>, BoundedVec<u8, MAX_EMAIL_ADDRESS_LENGTH>) {
// check the body and header lengths are within bounds
assert(header.len() <= MAX_EMAIL_HEADER_LENGTH);

// verify the dkim signature over the header
pubkey.verify_dkim_signature(header, signature);

// extract to and from email addresses
let from = comptime {
"from".as_bytes()
};
let to = comptime {
"to".as_bytes()
};
let from = comptime { "from".as_bytes() };
let to = comptime { "to".as_bytes() };
// 16k gate cost? has to be able to be brought down
let from_address = get_email_address(header, from_header_sequence, from_address_sequence, from);
let to_address = get_email_address(header, to_header_sequence, to_address_sequence, to);

// hash the pubkey and signature for the standard outputs
let standard_out = standard_outputs(pubkey.modulus, signature);
let email_nullifier = pedersen_hash(signature);
let standard_out = [pubkey.hash(), email_nullifier];
(standard_out, from_address, to_address)
}
6 changes: 4 additions & 2 deletions examples/partial_hash/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use dep::zkemail::{
KEY_LIMBS_2048, dkim::RSAPubkey, headers::body_hash::get_body_hash,
partial_hash::partial_sha256_var_end, standard_outputs, Sequence
partial_hash::partial_sha256_var_end, Sequence
};
use std::hash::pedersen_hash;

global MAX_EMAIL_HEADER_LENGTH: u32 = 512;
global MAX_PARTIAL_EMAIL_BODY_LENGTH: u32 = 192;
Expand Down Expand Up @@ -52,5 +53,6 @@ fn main(
);

// hash the pubkey and signature for the standard outputs
standard_outputs(pubkey.modulus, signature)
let email_nullifier = pedersen_hash(signature);
[pubkey.hash(), email_nullifier]
}
16 changes: 9 additions & 7 deletions examples/remove_soft_line_breaks/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use zkemail::{
KEY_LIMBS_2048, dkim::RSAPubkey, headers::body_hash::get_body_hash,
standard_outputs, Sequence, remove_soft_line_breaks::remove_soft_line_breaks
KEY_LIMBS_2048, dkim::RSAPubkey, headers::body_hash::get_body_hash, Sequence,
remove_soft_line_breaks::remove_soft_line_breaks,
};
use std::hash::sha256_var;
use std::hash::{pedersen_hash, sha256_var};

global MAX_EMAIL_HEADER_LENGTH: u32 = 512;
global MAX_EMAIL_BODY_LENGTH: u32 = 1024;
Expand All @@ -28,7 +28,7 @@ fn main(
pubkey: RSAPubkey<KEY_LIMBS_2048>,
signature: [Field; KEY_LIMBS_2048],
body_hash_index: u32,
dkim_header_sequence: Sequence
dkim_header_sequence: Sequence,
) -> pub [Field; 2] {
// check the body and header lengths are within bounds
assert(header.len() <= MAX_EMAIL_HEADER_LENGTH);
Expand All @@ -48,17 +48,19 @@ fn main(

// 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"
signed_body_hash == computed_body_hash,
"SHA256 hash computed over body does not match body hash found in DKIM-signed header",
);

// ~ 37,982 constraints
// 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"
"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)
let email_nullifier = pedersen_hash(signature);
[pubkey.hash(), email_nullifier]
}
7 changes: 4 additions & 3 deletions examples/verify_email_1024_bit_dkim/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use dep::zkemail::{
KEY_LIMBS_1024, dkim::RSAPubkey, headers::body_hash::get_body_hash,
standard_outputs, Sequence
Sequence
};
use dep::std::{collections::bounded_vec::BoundedVec, hash::sha256_var};
use dep::std::{collections::bounded_vec::BoundedVec, hash::{sha256_var, pedersen_hash}};

global MAX_EMAIL_HEADER_LENGTH: u32 = 512;
global MAX_EMAIL_BODY_LENGTH: u32 = 1024;
Expand Down Expand Up @@ -48,5 +48,6 @@ fn main(
);

// hash the pubkey and signature for the standard outputs
standard_outputs(pubkey.modulus, signature)
let email_nullifier = pedersen_hash(signature);
[pubkey.hash(), email_nullifier]
}
7 changes: 4 additions & 3 deletions examples/verify_email_2048_bit_dkim/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use dep::zkemail::{
KEY_LIMBS_2048, dkim::RSAPubkey, headers::body_hash::get_body_hash,
standard_outputs, Sequence
Sequence
};
use dep::std::{collections::bounded_vec::BoundedVec, hash::sha256_var};
use dep::std::{collections::bounded_vec::BoundedVec, hash::{sha256_var, pedersen_hash}};

global MAX_EMAIL_HEADER_LENGTH: u32 = 512;
global MAX_EMAIL_BODY_LENGTH: u32 = 1024;
Expand Down Expand Up @@ -52,5 +52,6 @@ fn main(

// ~ 10,255 constraints
// hash the pubkey and signature for the standard outputs
standard_outputs(pubkey.modulus, signature)
let email_nullifier = pedersen_hash(signature);
[pubkey.hash(), email_nullifier]
}
33 changes: 29 additions & 4 deletions lib/src/dkim.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ impl<let KEY_LIMBS: u32> RSAPubkey<KEY_LIMBS> {
pub fn new(modulus: [Field; KEY_LIMBS], redc: [Field; KEY_LIMBS]) -> Self {
Self { modulus, redc }
}

pub fn hash(self) -> Field {
pedersen_hash(self.modulus)
}
}

impl RSAPubkey<KEY_LIMBS_1024> {
Expand All @@ -36,6 +32,22 @@ impl RSAPubkey<KEY_LIMBS_1024> {
// verify the DKIM signature over the header
assert(verify_sha256_pkcs1v15(header_hash, signature, RSA_EXPONENT));
}

pub fn hash(self) -> Field {
let mut dkim_preimage = [0; 9];
// compose first 4 limbs of modulus and redc
for i in 0..4 {
let modulus_hi = self.modulus[i * 2] * 2.pow_32(120);
let redc_hi = self.redc[i * 2] * 2.pow_32(120);
dkim_preimage[i] = modulus_hi + self.modulus[i * 2 + 1];
dkim_preimage[i + 4] = redc_hi + self.redc[i * 2 + 1];
}
// compose last two elements of redc and modulus together
let modulus_hi = self.modulus[8] * 2.pow_32(120);
dkim_preimage[8] = modulus_hi + self.redc[8];
// hash the pubkey
pedersen_hash(dkim_preimage)
}
}

impl RSAPubkey<KEY_LIMBS_2048> {
Expand All @@ -55,4 +67,17 @@ impl RSAPubkey<KEY_LIMBS_2048> {
// verify the DKIM signature over the header
assert(verify_sha256_pkcs1v15(header_hash, signature, RSA_EXPONENT));
}

pub fn hash(self) -> Field {
let mut dkim_preimage = [0; 18];
// compose limbs
for i in 0..9 {
let modulus_hi = self.modulus[i * 2] * 2.pow_32(120);
let redc_hi = self.redc[i * 2] * 2.pow_32(120);
dkim_preimage[i] = modulus_hi + self.modulus[i * 2 + 1];
dkim_preimage[i + 9] = redc_hi + self.redc[i * 2 + 1];
}
// hash the pubkey
pedersen_hash(dkim_preimage)
}
}
22 changes: 0 additions & 22 deletions lib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,6 @@ global EMAIL_ADDRESS_CHAR_TABLE: [u8; 123] = [
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
];

/**
* Standard outputs that essentially every email circuit will need to export (alongside app-specific outputs)
* @notice if you only need the pubkey hash just import pedersen and hash away
*
* @param pubkey - the BN limbs of the DKIM RSA pubkey
* @param signature - the BN limbs of the DKIM RSA signature
* @returns
* 0: Pedersen hash of DKIM public key (root of trust)
* 1: Pedersen hash of DKIM signature (email nullifier)
*/
pub fn standard_outputs<let KEY_BYTE_LENGTH: u32>(
pubkey: [Field; KEY_BYTE_LENGTH],
signature: [Field; KEY_BYTE_LENGTH],
) -> [Field; 2] {
// create pedersen hash of DKIM signing key to minimize public outputs
let pubkey_hash = pedersen_hash(pubkey);
// create email nullifier for email
let email_nullifier = pedersen_hash(signature);
// output the root of trust and email nullifier
[pubkey_hash, email_nullifier]
}

/**
* Default email verification function
* @dev use #[zkemail] attribute macro to apply other functionality
Expand Down

0 comments on commit 7a3a841

Please sign in to comment.