diff --git a/README.md b/README.md index 87353e1..996bdb0 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 ... diff --git a/examples/email_mask/src/main.nr b/examples/email_mask/src/main.nr index f02bbae..1f82190 100644 --- a/examples/email_mask/src/main.nr +++ b/examples/email_mask/src/main.nr @@ -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; @@ -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) } diff --git a/examples/extract_addresses/src/main.nr b/examples/extract_addresses/src/main.nr index e15ed3d..6de8825 100644 --- a/examples/extract_addresses/src/main.nr +++ b/examples/extract_addresses/src/main.nr @@ -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 @@ -30,8 +28,8 @@ fn main( from_header_sequence: Sequence, from_address_sequence: Sequence, to_header_sequence: Sequence, - to_address_sequence: Sequence -) -> pub ([Field; 2], BoundedVec, BoundedVec) { + to_address_sequence: Sequence, + ) -> pub ([Field; 2], BoundedVec, BoundedVec) { // check the body and header lengths are within bounds assert(header.len() <= MAX_EMAIL_HEADER_LENGTH); @@ -39,17 +37,14 @@ fn main( 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) } diff --git a/examples/partial_hash/src/main.nr b/examples/partial_hash/src/main.nr index aa4c3aa..28caf9c 100644 --- a/examples/partial_hash/src/main.nr +++ b/examples/partial_hash/src/main.nr @@ -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; @@ -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] } diff --git a/examples/remove_soft_line_breaks/src/main.nr b/examples/remove_soft_line_breaks/src/main.nr index db255c1..3262214 100644 --- a/examples/remove_soft_line_breaks/src/main.nr +++ b/examples/remove_soft_line_breaks/src/main.nr @@ -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; @@ -28,7 +28,7 @@ fn main( pubkey: RSAPubkey, 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); @@ -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] } diff --git a/examples/verify_email_1024_bit_dkim/src/main.nr b/examples/verify_email_1024_bit_dkim/src/main.nr index a759fab..d9534e3 100644 --- a/examples/verify_email_1024_bit_dkim/src/main.nr +++ b/examples/verify_email_1024_bit_dkim/src/main.nr @@ -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; @@ -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] } diff --git a/examples/verify_email_2048_bit_dkim/Prover.toml b/examples/verify_email_2048_bit_dkim/Prover.toml new file mode 100644 index 0000000..ff6cd82 --- /dev/null +++ b/examples/verify_email_2048_bit_dkim/Prover.toml @@ -0,0 +1,18 @@ +signature = ['0x5779c85587e51cb8de5c29d7fdfeb0', '0xcd7ea8b6119f76f117ecb5042f8fc0', '0xeb7ac32b81d5a87bc2046fa0004e27', '0x62708c43b0c07a8fe8bdc97c479138', '0xc1e90d184f22a80be4a484a6ebd462', '0x39f3ff00e47728aaf74802d2d1d07b', '0x0f39de2cf99bf20dab7b8ae9240acd', '0xf4875cb76ce2538f255d70476136d6', '0xde151a5005ca614d6af7dd01e2a083', '0x6fe12b286f3195cae005fd7d2a1766', '0xd6e43a3060eccc555f2ee1e2929932', '0x0d5fa7cc79c794ae80310b491a1b40', '0x9cff415204cbc05c772ede05903440', '0xe7190ccff38575ae70dd055cd892d2', '0xf34bb777c0c842b0e88738eafdf634', '0x21040437e1e945a201ff58e542be68', '0x12f254fa4a0fb776ffe8759eb9eefa', '0x12'] +body_hash_index = '363' + +[header] +storage = ['102', '114', '111', '109', '58', '114', '117', '110', '110', '105', '101', '114', '46', '108', '101', '97', '103', '117', '101', '115', '46', '48', '106', '64', '105', '99', '108', '111', '117', '100', '46', '99', '111', '109', '13', '10', '99', '111', '110', '116', '101', '110', '116', '45', '116', '121', '112', '101', '58', '116', '101', '120', '116', '47', '112', '108', '97', '105', '110', '59', '32', '99', '104', '97', '114', '115', '101', '116', '61', '117', '115', '45', '97', '115', '99', '105', '105', '13', '10', '109', '105', '109', '101', '45', '118', '101', '114', '115', '105', '111', '110', '58', '49', '46', '48', '32', '40', '77', '97', '99', '32', '79', '83', '32', '88', '32', '77', '97', '105', '108', '32', '49', '54', '46', '48', '32', '92', '40', '51', '55', '51', '49', '46', '53', '48', '48', '46', '50', '51', '49', '92', '41', '41', '13', '10', '115', '117', '98', '106', '101', '99', '116', '58', '72', '101', '108', '108', '111', '13', '10', '109', '101', '115', '115', '97', '103', '101', '45', '105', '100', '58', '60', '56', '70', '56', '49', '57', '68', '51', '50', '45', '66', '54', '65', '67', '45', '52', '56', '57', '68', '45', '57', '55', '55', '70', '45', '52', '51', '56', '66', '66', '67', '52', '67', '65', '66', '50', '55', '64', '109', '101', '46', '99', '111', '109', '62', '13', '10', '100', '97', '116', '101', '58', '83', '97', '116', '44', '32', '50', '54', '32', '65', '117', '103', '32', '50', '48', '50', '51', '32', '49', '50', '58', '50', '53', '58', '50', '50', '32', '43', '48', '52', '48', '48', '13', '10', '116', '111', '58', '122', '107', '101', '119', '116', '101', '115', '116', '64', '103', '109', '97', '105', '108', '46', '99', '111', '109', '13', '10', '100', '107', '105', '109', '45', '115', '105', '103', '110', '97', '116', '117', '114', '101', '58', '118', '61', '49', '59', '32', '97', '61', '114', '115', '97', '45', '115', '104', '97', '50', '53', '54', '59', '32', '99', '61', '114', '101', '108', '97', '120', '101', '100', '47', '114', '101', '108', '97', '120', '101', '100', '59', '32', '100', '61', '105', '99', '108', '111', '117', '100', '46', '99', '111', '109', '59', '32', '115', '61', '49', '97', '49', '104', '97', '105', '59', '32', '116', '61', '49', '54', '57', '51', '48', '51', '56', '51', '51', '55', '59', '32', '98', '104', '61', '55', '120', '81', '77', '68', '117', '111', '86', '86', '85', '52', '109', '48', '87', '48', '87', '82', '86', '83', '114', '86', '88', '77', '101', '71', '83', '73', '65', '83', '115', '110', '117', '99', '75', '57', '100', '74', '115', '114', '99', '43', '118', '85', '61', '59', '32', '104', '61', '102', '114', '111', '109', '58', '67', '111', '110', '116', '101', '110', '116', '45', '84', '121', '112', '101', '58', '77', '105', '109', '101', '45', '86', '101', '114', '115', '105', '111', '110', '58', '83', '117', '98', '106', '101', '99', '116', '58', '77', '101', '115', '115', '97', '103', '101', '45', '73', '100', '58', '68', '97', '116', '101', '58', '116', '111', '59', '32', '98', '61', '128', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '14', '192'] +len = '472' + +[pubkey] +modulus = ['0xe5cf995b5ef59ce9943d1f4209b6ab', '0xe0caf03235e91a2db27e9ed214bcc6', '0xafe1309f87414bd36ed296dacfade2', '0xbeff3f19046a43adce46c932514988', '0x324041af8736e87de4358860fff057', '0xadcc6669dfa346f322717851a8c22a', '0x8b2a193089e6bf951c553b5a6f71aa', '0x0a570fe582918c4f731a0002068df2', '0x39419a433d6bfdd1978356cbca4b60', '0x550d695a514d38b45c862320a00ea5', '0x1c56ac1dfbf1beea31e8a613c2a51f', '0x6a30c9f22d2e5cb6934263d0838809', '0x0a281f268a44b21a4f77a91a52f960', '0x5134dc3966c8e91402669a47cc8597', '0x71590781df114ec072e641cdc5d224', '0xa1bc0f0937489c806c1944fd029dc9', '0x911f6e47f84db3b64c3648ebb5a127', '0xd5'] +redc = ['0xa48a824e4ebc7e0f1059f3ecfa57c4', '0x05c1db23f3c7d47ad7e7d7cfda5189', '0x79bb6bbbd8facf011f022fa9051aec', '0x24faa4cef474bed639362ea71f7a21', '0x1503aa50b77e24b030841a7d061581', '0x5bbf4e62805e1860a904c0f66a5fad', '0x5cbd24b72442d2ce647dd7d0a44368', '0x074a8839a4460c169dce7138efdaef', '0x0f06e09e3191b995b08e5b45182f65', '0x51fad4a89f8369fe10e5d4b6e149a1', '0xdc778b15982d11ebf7fe23b4e15f10', '0xa09ff3a4567077510c474e4ac0a21a', '0xb37e69e5dbb77167b73065e4c5ad6a', '0xecf4774e22e7fe3a38642186f7ae74', '0x16e72b5eb4c813a3b37998083aab81', '0xa48e7050aa8abedce5a45c16985376', '0xdd3285e53b322b221f7bcf4f8f8ad8', '0x0132'] + +[dkim_header_sequence] +index = '269' +length = '203' + +[body] +storage = ['72', '101', '108', '108', '111', '44', '13', '10', '13', '10', '72', '111', '119', '32', '97', '114', '101', '32', '121', '111', '117', '63', '13', '10', '128', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '192', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'] +len = '24' diff --git a/examples/verify_email_2048_bit_dkim/src/main.nr b/examples/verify_email_2048_bit_dkim/src/main.nr index 76291c8..b498313 100644 --- a/examples/verify_email_2048_bit_dkim/src/main.nr +++ b/examples/verify_email_2048_bit_dkim/src/main.nr @@ -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; @@ -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] } diff --git a/js/package.json b/js/package.json index cce915c..4388454 100644 --- a/js/package.json +++ b/js/package.json @@ -13,8 +13,8 @@ "dependencies": { "@mach-34/noir-bignum-paramgen": "^1.1.0", "@noir-lang/backend_barretenberg": "=0.36.0", - "@noir-lang/noir_js": "=0.36.0", - "@noir-lang/noirc_abi": "^0.36.0", + "@noir-lang/noir_js": "=0.38.0", + "@noir-lang/noirc_abi": "=0.36.0", "@zk-email/helpers": "=6.1.5" }, "devDependencies": { diff --git a/js/yarn.lock b/js/yarn.lock index 397a102..09f17d9 100644 --- a/js/yarn.lock +++ b/js/yarn.lock @@ -1664,10 +1664,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@noir-lang/acvm_js@0.52.0": - version "0.52.0" - resolved "https://registry.yarnpkg.com/@noir-lang/acvm_js/-/acvm_js-0.52.0.tgz#7e028c1a1fdc01e37333e9e04bc2cf147db9448c" - integrity sha512-QEREOIbq+jK/Bqs9jk0+XAS/KYmQX7lBEUYhdFMxkNIQf3hDQ3hr9PcelOWQtoxiDn6IJ2sr7t7yeZFdRqCvhg== +"@noir-lang/acvm_js@0.54.0": + version "0.54.0" + resolved "https://registry.yarnpkg.com/@noir-lang/acvm_js/-/acvm_js-0.54.0.tgz#0d209e56ed062c92ba540118ef50d66e094acba1" + integrity sha512-yUMHXPQ2tlMGhFfAuvkt6HgQwUjlGrpvLMIp4gkwu0/0z1ueoKIOgEsqMcmbjYR+16iEYzv9RVKmNmye1yjr0g== "@noir-lang/backend_barretenberg@=0.36.0": version "0.36.0" @@ -1678,16 +1678,23 @@ "@noir-lang/types" "0.36.0" fflate "^0.8.0" -"@noir-lang/noir_js@=0.36.0": - version "0.36.0" - resolved "https://registry.yarnpkg.com/@noir-lang/noir_js/-/noir_js-0.36.0.tgz#63ef02cb8635a8edf6b32243aefcb7fdd4c3ce46" - integrity sha512-qMAfqkcWfERxS1IOeAjTssZGVolNLVIQhHI/Ers7iIx3tZYhejHRe30cMFdhgdJ9nfpqJea+TvlsKLsXbEBMfw== +"@noir-lang/noir_js@=0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@noir-lang/noir_js/-/noir_js-0.38.0.tgz#3940e34d2035e2369943caba272df878acf842af" + integrity sha512-9n1WeC9uAwCEkpSirwV+FtkZzCqO5vG8UFbkv1I1QValFWi+GlVKmlbEGTsCBA1HuJqPzOK0zdhagVi7K108vw== dependencies: - "@noir-lang/acvm_js" "0.52.0" - "@noir-lang/noirc_abi" "0.36.0" - "@noir-lang/types" "0.36.0" + "@noir-lang/acvm_js" "0.54.0" + "@noir-lang/noirc_abi" "0.38.0" + "@noir-lang/types" "0.38.0" -"@noir-lang/noirc_abi@0.36.0", "@noir-lang/noirc_abi@^0.36.0": +"@noir-lang/noirc_abi@0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@noir-lang/noirc_abi/-/noirc_abi-0.38.0.tgz#66ac2b8a34d829d85c087e2b0dd58142a037a41d" + integrity sha512-LIE9kha5lv+VW8lbivh2nKyUD2UX0hHPCxmvRS7L/bNfyw6fuIhCs/4/n05Qli+1Ab7srHB+TcG1QIqaC9XO8g== + dependencies: + "@noir-lang/types" "0.38.0" + +"@noir-lang/noirc_abi@=0.36.0": version "0.36.0" resolved "https://registry.yarnpkg.com/@noir-lang/noirc_abi/-/noirc_abi-0.36.0.tgz#b4bb93897f2ce90e951000139810dc726206e234" integrity sha512-xRs13RQArV+m4ehkWpbAB/67z7WBfB/EgFJTJtd2/QMdtJSDWJ+8zcf5oOjX+YpDt38c9qY9d/SRdIzws34m3w== @@ -1699,6 +1706,11 @@ resolved "https://registry.yarnpkg.com/@noir-lang/types/-/types-0.36.0.tgz#695f7ae552caf2d9033bdf6964d2d5ba6ac5db39" integrity sha512-3A/yJtnbTsn0o3T/D3YPs5b0vPOxYwos6Y5Ko6NhZsE6V31rYPv2a2NYmbfJRYuriwwzFbdE8+ui3UiV5BmvWw== +"@noir-lang/types@0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@noir-lang/types/-/types-0.38.0.tgz#3baec5955094c62b7fb1ba59e57843e396615abf" + integrity sha512-mP2oQQ7iQ8W8ned4kG+oYa0Vg/+arrPzD38Mc1WG1y1uHHb+pXsqUZAer9dqxlv2O3YPyWM+dUNg2zLiY6Wa6w== + "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" diff --git a/lib/src/dkim.nr b/lib/src/dkim.nr index 38071dc..e36fc4c 100644 --- a/lib/src/dkim.nr +++ b/lib/src/dkim.nr @@ -1,7 +1,7 @@ -use std::hash::{sha256_var, pedersen_hash}; +use crate::{KEY_LIMBS_1024, KEY_LIMBS_2048, RSA_EXPONENT}; use bignum::{params::BigNumParams, RuntimeBigNum}; use rsa::{rsa::verify_sha256_pkcs1v15, types::{RBN1024, RBN2048}}; -use crate::{KEY_LIMBS_1024, KEY_LIMBS_2048, RSA_EXPONENT}; +use std::hash::{pedersen_hash, sha256_var}; pub struct RSAPubkey { modulus: [Field; KEY_LIMBS], @@ -14,8 +14,8 @@ impl RSAPubkey { Self { modulus, redc } } - pub fn hash(self) -> Field { - pedersen_hash(self.modulus) + pub fn validate_range(self, signature: [Field; KEY_LIMBS]) { + for i in 0..KEY_LIMBS {} } } @@ -32,10 +32,27 @@ impl RSAPubkey { BigNumParams::new(false, self.modulus, self.redc); let signature: RBN1024 = RuntimeBigNum::from_array(params, signature); + signature.validate_in_range(); // 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 { @@ -51,8 +68,22 @@ impl RSAPubkey { BigNumParams::new(false, self.modulus, self.redc); let signature: RBN2048 = RuntimeBigNum::from_array(params, signature); + signature.validate_in_range(); // 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) + } } diff --git a/lib/src/headers/body_hash.nr b/lib/src/headers/body_hash.nr index 45efe7d..eb24fc8 100644 --- a/lib/src/headers/body_hash.nr +++ b/lib/src/headers/body_hash.nr @@ -1,8 +1,8 @@ -use base64::BASE64_DECODER; use crate::{ - Sequence, BODY_HASH_BASE64_LENGTH, MAX_DKIM_HEADER_FIELD_LENGTH, - headers::constrain_header_field, + BODY_HASH_BASE64_LENGTH, headers::constrain_header_field, MAX_DKIM_HEADER_FIELD_LENGTH, + Sequence, }; +use base64::BASE64_DECODER; /** * Constrained access to the body hash in the header @@ -27,19 +27,23 @@ pub fn get_body_hash( // constrain access to the body hash assert( body_hash_index > dkim_header_field_sequence.index - & body_hash_index < dkim_header_field_sequence.end_index(), - "Body hash index accessed outside of DKIM header field", - ); - let bh_prefix: [u8; 3] = comptime { "bh=".as_bytes() }; - for i in 0..3 { + & body_hash_index + BODY_HASH_BASE64_LENGTH < dkim_header_field_sequence.end_index() + 1 + , + "Body hash index accessed outside of DKIM header field", + ); + let bh_prefix: [u8; 5] = comptime { "; bh=".as_bytes() }; + for i in 0..5 { + let character = header.get_unchecked(body_hash_index - 5 + i); + assert(character == bh_prefix[i], "No 'bh=' prefix found at asserted bh index"); + } + let bh_suffix: u8 = comptime { ";".as_bytes()[0] }; assert( - header.get_unchecked(body_hash_index - 3 + i) == bh_prefix[i], - "No 'bh=' prefix found at asserted bh index", + header.get_unchecked(body_hash_index + BODY_HASH_BASE64_LENGTH) == bh_suffix, + "No ';' suffix found at asserted bh index", ); + // get the body hash + get_body_hash_unsafe(header, body_hash_index) } - // get the body hash - get_body_hash_unsafe(header, body_hash_index) -} /** * Get the body hash from the header without validating the access index diff --git a/lib/src/headers/email_address.nr b/lib/src/headers/email_address.nr index b835e14..6069eea 100644 --- a/lib/src/headers/email_address.nr +++ b/lib/src/headers/email_address.nr @@ -1,6 +1,6 @@ use crate::{ - Sequence, MAX_EMAIL_ADDRESS_LENGTH, EMAIL_ADDRESS_CHAR_TABLE, - headers::constrain_header_field_detect_last_angle_bracket, + EMAIL_ADDRESS_CHAR_TABLE, headers::constrain_header_field_detect_last_angle_bracket, + MAX_EMAIL_ADDRESS_LENGTH, Sequence, }; pub fn get_email_address( diff --git a/lib/src/headers/mod.nr b/lib/src/headers/mod.nr index 3016088..429c8b5 100644 --- a/lib/src/headers/mod.nr +++ b/lib/src/headers/mod.nr @@ -56,7 +56,7 @@ pub fn constrain_header_field( - 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 diff --git a/lib/src/macro.nr b/lib/src/macro.nr index 097880f..0b559e5 100644 --- a/lib/src/macro.nr +++ b/lib/src/macro.nr @@ -1,15 +1,15 @@ use dep::std::{ - meta::unquote, collections::umap::UHashMap, - hash::{BuildHasherDefault, poseidon2::Poseidon2Hasher}, option::Option, + collections::umap::UHashMap, hash::{BuildHasherDefault, poseidon2::Poseidon2Hasher}, + meta::unquote, option::Option, }; -use dep::std::{ - collections::bounded_vec::BoundedVec, hash::{sha256_var, pedersen_hash}, panic::panic, -}; +use crate::{KEY_BYTES_1024, KEY_LIMBS_1024, RSA_EXPONENT}; use dep::rsa::{ - bignum::{fields::{Params1024, Params2048}, runtime_bignum::BigNumInstance, BigNum}, types::RSA, + bignum::{BigNum, fields::{Params1024, Params2048}, runtime_bignum::BigNumInstance}, types::RSA, +}; +use dep::std::{ + collections::bounded_vec::BoundedVec, hash::{pedersen_hash, sha256_var}, panic::panic, }; -use crate::{KEY_LIMBS_1024, KEY_BYTES_1024, RSA_EXPONENT}; type BN1024 = BigNum; type RSA1024 = RSA, KEY_BYTES_1024>; diff --git a/lib/src/partial_hash.nr b/lib/src/partial_hash.nr index 3017a7d..c4784d1 100644 --- a/lib/src/partial_hash.nr +++ b/lib/src/partial_hash.nr @@ -1,5 +1,5 @@ -use std::runtime::is_unconstrained; use std::hash::sha256_compression; +use std::runtime::is_unconstrained; // https://github.com/noir-lang/noir/blob/76eec710ff73e5e45fdddcd41ae2cd74e879cfa5/noir_stdlib/src/hash/sha256.nr#L23 // Convert 64-byte array to array of 16 u32s @@ -72,10 +72,12 @@ global BLOCK_SIZE = 64; /** * Partially computes a SHA256 hash of a message but does not finalize * @notice can be used for post-partial hashing where client proves part of hash and relies on server to finish + * * - * @param N: the maximum length of the message to hash + * @param N: the maximum length of the message to hash. + * --- WARNING: N must be divisible by BLOCK_SIZE such that N % BLOCK_SIZE == 0 + * otherwise the remaining bytes will not be inputted when computing the initial hash * @param msg: the preimage to begin hashing - * @param message_size: the actual length of the preimage to hash * @return the intermediate hash state */ pub fn partial_sha256_var_start(msg: [u8; N]) -> [u32; 8] { diff --git a/lib/src/tests/mod.nr b/lib/src/tests/mod.nr index ffb34d5..d1b9b35 100644 --- a/lib/src/tests/mod.nr +++ b/lib/src/tests/mod.nr @@ -116,6 +116,16 @@ mod test_tampered_hash { "SHA256 hash should not match tampered body hash", ); } + + #[test(should_fail_with = "all to assert_max_bit_size")] + fn test_dkim_signature_unnormalized() { + let mut sig = EmailLarge::SIGNATURE; + let pubkey = EmailLarge::PUBKEY; + let delta = 1; + sig[0] += delta * 0x1000000000000000000000000000000; + sig[1] -= delta; + pubkey.verify_dkim_signature(EmailLarge::HEADER, sig); + } } mod header_field_access { @@ -167,6 +177,14 @@ mod header_field_access { let _ = get_body_hash(dkim_field, malicious_sequence, malicious_body_hash_index); } + #[test(should_fail_with = "No 'bh=' prefix found at asserted bh index")] + fn test_malicious_body_hash_index() { + // tests against "dkim-signature: v=1; a=rsa-sha256; d=example.com; s=selector; c=relaxed/relaxed; q=dns/txt; t=1683849600; x=1684454400; h=from:to:subject:date; z=From:bh=7xQMDuoVVU4m0W0WRVSrVXMeGSIASsnucK9dJsrc+vU=@domain.com|To:recipient@example.net|Subject:Hello|Date:Thu, 11 May 2023 15:00:00 -0700; bh=2jUSOH9NhtVGCaWpZT2ncBgaamXkef9OgICHkqfsmKY=; b=" + let (header, body_hash_index) = EmailLarge::tampered_dkim_field(); + let sequence: Sequence = Sequence { index: 0, length: header.len() }; + let _ = get_body_hash(header, sequence, body_hash_index); + } + #[test(should_fail_with = "Header field must end with CRLF")] fn test_header_field_sequence_overflow_end() { // make sequence extend beyond the end of the header field diff --git a/lib/src/tests/test_inputs.nr b/lib/src/tests/test_inputs.nr index 963c291..c989784 100644 --- a/lib/src/tests/test_inputs.nr +++ b/lib/src/tests/test_inputs.nr @@ -1,6 +1,6 @@ pub(crate) mod EmailLarge { - use crate::{Sequence, KEY_LIMBS_2048, dkim::RSAPubkey}; + use crate::{dkim::RSAPubkey, KEY_LIMBS_2048, Sequence}; // regular inputs pub(crate) global EMAIL_LARGE_MAX_HEADER_LENGTH: u32 = 512; @@ -178,11 +178,36 @@ pub(crate) mod EmailLarge { let dkim_sequence_start = DKIM_HEADER_SEQUENCE.index; dkim_sequence_start - 40 } + + pub unconstrained fn tampered_dkim_field() -> (BoundedVec, u32) { + let header: BoundedVec = BoundedVec::from_array([ + 100, 107, 105, 109, 45, 115, 105, 103, 110, 97, 116, 117, 114, 101, 58, 32, 118, 61, 49, + 59, 32, 97, 61, 114, 115, 97, 45, 115, 104, 97, 50, 53, 54, 59, 32, 100, 61, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 59, 32, 115, 61, 115, 101, 108, 101, 99, 116, + 111, 114, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, 108, 97, 120, + 101, 100, 59, 32, 113, 61, 100, 110, 115, 47, 116, 120, 116, 59, 32, 116, 61, 49, 54, + 56, 51, 56, 52, 57, 54, 48, 48, 59, 32, 120, 61, 49, 54, 56, 52, 52, 53, 52, 52, 48, 48, + 59, 32, 104, 61, 102, 114, 111, 109, 58, 116, 111, 58, 115, 117, 98, 106, 101, 99, 116, + 58, 100, 97, 116, 101, 59, 32, 122, 61, 70, 114, 111, 109, 58, 98, 104, 61, 55, 120, 81, + 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, + 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, + 64, 100, 111, 109, 97, 105, 110, 46, 99, 111, 109, 124, 84, 111, 58, 114, 101, 99, 105, + 112, 105, 101, 110, 116, 64, 101, 120, 97, 109, 112, 108, 101, 46, 110, 101, 116, 124, + 83, 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 124, 68, 97, 116, 101, 58, + 84, 104, 117, 44, 32, 49, 49, 32, 77, 97, 121, 32, 50, 48, 50, 51, 32, 49, 53, 58, 48, + 48, 58, 48, 48, 32, 45, 48, 55, 48, 48, 59, 32, 98, 104, 61, 50, 106, 85, 83, 79, 72, + 57, 78, 104, 116, 86, 71, 67, 97, 87, 112, 90, 84, 50, 110, 99, 66, 103, 97, 97, 109, + 88, 107, 101, 102, 57, 79, 103, 73, 67, 72, 107, 113, 102, 115, 109, 75, 89, 61, 59, 32, + 98, 61, + ]); + let body_hash_index: u32 = 151; + (header, body_hash_index) + } } pub(crate) mod EmailAddresses { - use crate::{Sequence, MAX_EMAIL_ADDRESS_LENGTH}; + use crate::{MAX_EMAIL_ADDRESS_LENGTH, Sequence}; pub(crate) global ADDRESS: BoundedVec = BoundedVec::from_array("runnier.leagues.0j@icloud.com".as_bytes()); pub(crate) global ADDRESS_ONLY: [u8; 38] =