From 50aa5dbc41aa8130d3f73436534dfede315713ba Mon Sep 17 00:00:00 2001 From: Jack Gilcrest Date: Sat, 2 Nov 2024 23:09:58 -0600 Subject: [PATCH] circuit simulation in js working + refactor some --- examples/remove_soft_line_breaks/src/main.nr | 34 ++-- js/package.json | 2 +- js/tests/circuits.test.ts | 31 +-- js/yarn.lock | 8 +- lib/src/remove_soft_line_breaks.nr | 192 ++++++++----------- 5 files changed, 121 insertions(+), 146 deletions(-) diff --git a/examples/remove_soft_line_breaks/src/main.nr b/examples/remove_soft_line_breaks/src/main.nr index d9547e7..db255c1 100644 --- a/examples/remove_soft_line_breaks/src/main.nr +++ b/examples/remove_soft_line_breaks/src/main.nr @@ -34,31 +34,31 @@ fn main( 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); + // ~ 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); + // ~ 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); + // ~ 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" - // ); + // 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" + ); + // ~ 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" ); - // // ~ 10,255 constraints - // // hash the pubkey and signature for the standard outputs - // standard_outputs(pubkey.modulus, signature) - [0, 0] + // ~ 10,255 constraints + // hash the pubkey and signature for the standard outputs + standard_outputs(pubkey.modulus, signature) } diff --git a/js/package.json b/js/package.json index df87a5b..4352eb4 100644 --- a/js/package.json +++ b/js/package.json @@ -21,7 +21,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.4", "@babel/preset-typescript": "^7.24.7", - "@types/jest": "^29.5.13", + "@types/jest": "^29.5.14", "@types/mocha": "^10.0.8", "@types/node": "^22.5.5", "@typescript-eslint/eslint-plugin": "7", diff --git a/js/tests/circuits.test.ts b/js/tests/circuits.test.ts index 9bfbda6..f13364e 100644 --- a/js/tests/circuits.test.ts +++ b/js/tests/circuits.test.ts @@ -1,6 +1,5 @@ import fs from "fs"; import path from "path"; -import { describe, beforeAll, } from "jest"; import { ZKEmailProver } from "../src/prover"; import { generateEmailVerifierInputs } from "../src/index"; import { toProverToml } from "../src/utils"; @@ -9,7 +8,7 @@ import circuit2048 from "../../examples/verify_email_2048_bit_dkim/target/verify 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"; +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")), @@ -25,25 +24,25 @@ const inputParams = { }; describe("ZKEmail.nr Circuit Unit Tests", () => { - // todo: get a github email from a throwaway account to verify - // let prover1024: ZKEmailProver; - const selectorText = "All nodes in the Bitcoin network can consult it"; let prover2048: ZKEmailProver; let proverPartialHash: ZKEmailProver; let proverMasked: ZKEmailProver; let proverExtractAddresses: ZKEmailProver; + let proverRemoveSoftLineBreak: ZKEmailProver; beforeAll(() => { //@ts-ignore // prover1024 = new ZKEmailProver(circuit1024, "all"); //@ts-ignore - prover2048 = new ZKEmailProver(circuit2048, "all"); + prover2048 = new ZKEmailProver(circuit2048); //@ts-ignore - proverPartialHash = new ZKEmailProver(circuitPartialHash, "all"); + proverPartialHash = new ZKEmailProver(circuitPartialHash); //@ts-ignore - proverMasked = new ZKEmailProver(circuitEmailMask, "all"); + proverMasked = new ZKEmailProver(circuitEmailMask); //@ts-ignore - proverExtractAddresses = new ZKEmailProver(circuitExtractAddresses, "all"); + proverExtractAddresses = new ZKEmailProver(circuitExtractAddresses); + //@ts-ignore + proverRemoveSoftLineBreak = new ZKEmailProver(circuitRemoveSoftLineBreak); }); afterAll(async () => { @@ -52,20 +51,21 @@ describe("ZKEmail.nr Circuit Unit Tests", () => { await proverPartialHash.destroy(); await proverMasked.destroy(); await proverExtractAddresses.destroy(); + await proverRemoveSoftLineBreak.destroy(); }); - describe("Successful Cases", () => { + describe("Simulate Witnesses", () => { it("2048-bit DKIM", async () => { const inputs = await generateEmailVerifierInputs( emails.small, inputParams ); await prover2048.simulateWitness(inputs); - console.log(toProverToml(inputs)); + // console.log(toProverToml(inputs)); }); it("Partial Hash", async () => { const inputs = await generateEmailVerifierInputs(emails.large, { - shaPrecomputeSelector: selectorText, + shaPrecomputeSelector: "All nodes in the Bitcoin network can consult it", maxHeadersLength: 512, maxBodyLength: 192, }); @@ -141,5 +141,12 @@ describe("ZKEmail.nr Circuit Unit Tests", () => { expect(expectedFrom).toEqual(actualFrom); expect(expectedTo).toEqual(actualTo); }); + it("Remove Soft Line Breaks", async () => { + const inputs = await generateEmailVerifierInputs(emails.large, { + removeSoftLineBreaks: true, + ...inputParams, + }); + await proverRemoveSoftLineBreak.simulateWitness(inputs); + }) }); }); diff --git a/js/yarn.lock b/js/yarn.lock index 2d0a455..ef7009e 100644 --- a/js/yarn.lock +++ b/js/yarn.lock @@ -1782,10 +1782,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.13": - version "29.5.13" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc" - integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg== +"@types/jest@^29.5.14": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" diff --git a/lib/src/remove_soft_line_breaks.nr b/lib/src/remove_soft_line_breaks.nr index 5f4ee62..7cb3d37 100644 --- a/lib/src/remove_soft_line_breaks.nr +++ b/lib/src/remove_soft_line_breaks.nr @@ -1,5 +1,5 @@ -use std::hash::poseidon2::Poseidon2; use nodash::array::pack_bytes; +use std::hash::poseidon2::Poseidon2; /** * Computes R by packing bytes into fields before hashing to reduce the work @@ -7,11 +7,8 @@ use nodash::array::pack_bytes; * @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( - encoded: [u8; N], - decoded: [u8; N] -) -> Field { + */ +fn compressed_r(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)]; @@ -23,56 +20,39 @@ fn compressed_r( } /** - * Remove soft line breaks from the given text + * Find the indexes where the RLC computation should zero out * - * @param text The text to remove soft line breaks from + * @param encoded The encoded input array + * @returns indexes where the RLC computation should zero out */ -pub fn remove_soft_line_breaks( - encoded: [u8; N], - 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 r = compressed_r(encoded, decoded); - - // check for "=" (0x3D), "\r" (0x0D), and "\n" (0x0A) - let mut is_equals: [bool; N] = [false; N]; - let mut is_cr: [bool; N] = [false; N]; - let mut is_lf: [bool; N] = [false; N]; - for i in 0..N - 2 { - is_equals[i] = encoded[i] == 0x3D; - is_cr[i] = encoded[i + 1] == 0x0D; - is_lf[i] = encoded[i + 2] == 0x0A; - } - is_equals[N - 2] = encoded[N - 2] == 0x3D; - is_equals[N - 1] = encoded[N - 1] == 0x3D; - is_cr[N - 1] = encoded[N - 1] == 0x0D; - +pub fn find_zeroes(encoded: [u8; N]) -> [bool; N] { // identify soft line breaks - let mut is_soft_break: [bool; N] = [false; N]; - for i in 0..N-2 { - is_soft_break[i] = is_equals[i] & is_cr[i] & is_lf[i]; + let mut is_break: [bool; N] = [false; N]; + for i in 0..N - 2 { + is_break[i] = (encoded[i] == 0x3D) + & (encoded[i + 1] == 0x0D) + & (encoded[i + 2] == 0x0A); } - // determine chars to zero + // find indexes of chars to zero let mut should_zero: [bool; N] = [false; N]; - should_zero[0] = is_soft_break[0]; - should_zero[1] = is_soft_break[1] + is_soft_break[0]; - should_zero[N - 1] = is_soft_break[N - 2] + is_soft_break[N - 3]; + should_zero[0] = is_break[0]; + should_zero[1] = is_break[1] + is_break[0]; + should_zero[N - 1] = is_break[N - 2] + is_break[N - 3]; for i in 2..N - 1 { - should_zero[i] = is_soft_break[i] + is_soft_break[i - 1] + is_soft_break[i - 2]; + should_zero[i] = is_break[i] + is_break[i - 1] + is_break[i - 2]; } - // process encoded input - let mut processed: [u8; N] = [0; N]; - for i in 0..N { - processed[i] = (1 - should_zero[i] as u8) * encoded[i]; - } + should_zero +} + +pub fn powers_of_r( + encoded: [u8; N], + decoded: [u8; N], + should_zero: [bool; N] +) -> ([Field; N], [Field; N]) { + // compute r + let r = compressed_r(encoded, decoded); // calculate powers of r for encoded let mut r_encoded: [Field; N] = [0; N]; @@ -97,35 +77,47 @@ pub fn remove_soft_line_breaks( r_decoded[i] = r_decoded[i - 1] * r; } + (r_encoded, r_decoded) +} + +/** + * Remove soft line breaks from the given text + * + * @param text The text to remove soft line breaks from + */ +pub fn remove_soft_line_breaks(encoded: [u8; N], decoded: [u8; N]) -> bool { + // find indexes to zero out + let should_zero = find_zeroes(encoded); + + // process encoded input + let mut processed: [u8; N] = [0; N]; + for i in 0..N { + processed[i] = (1 - should_zero[i] as u8) * encoded[i]; + } + + // calculate powers of r for encoded + let (r_encoded, r_decoded) = powers_of_r(encoded, decoded, should_zero); + // calculate rlc for processed - let mut sum_enc: [Field; N] = [0; N]; - let mut sum_dec: [Field; N] = [0; N]; - sum_enc[0] = r_encoded[0] * processed[0] as Field; - sum_dec[0] = r_decoded[0] * decoded[0] as Field; + let mut sum_enc: Field = r_encoded[0] * processed[0] as Field; + let mut sum_dec: Field = r_decoded[0] * decoded[0] as Field; for i in 1..N { - sum_enc[i] = sum_enc[i - 1] + r_encoded[i] * processed[i] as Field; - sum_dec[i] = sum_dec[i - 1] + r_decoded[i] * decoded[i] as Field; + sum_enc = sum_enc + r_encoded[i] * processed[i] as Field; + sum_dec = sum_dec + r_decoded[i] * decoded[i] as Field; } - - // determine if rlc for decoded and encoded match - sum_enc[N - 1] == sum_dec[N - 1] + sum_enc == sum_dec } // test vectors copied from https://github.com/zkemail/zk-email-verify/blob/main/packages/circuits/tests/remove-soft-line-breaks.test.ts - #[test] pub fn test_remove_soft_line_breaks() { let encoded = [ - 115, 101, 115, 58, 61, 13, 10, 45, 32, - 83, 114, 101, 97, 107, 61, 13, 10, 0, + 115, 101, 115, 58, 61, 13, 10, 45, 32, 83, 114, 101, 97, 107, 61, 13, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 ]; let decoded = [ - 115, 101, 115, 58, 45, 32, 83, 114, 101, - 97, 107, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 115, 101, 115, 58, 45, 32, 83, 114, 101, 97, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, ]; let res = remove_soft_line_breaks(encoded, decoded); assert(res, "Expected to remove soft line breaks"); @@ -134,16 +126,12 @@ pub fn test_remove_soft_line_breaks() { #[test] pub fn test_return_false_incorrect_decoded_input() { let encoded = [ - 115, 101, 115, 58, 61, 13, 10, 45, 32, - 83, 114, 101, 97, 107, 61, 13, 10, 0, + 115, 101, 115, 58, 61, 13, 10, 45, 32, 83, 114, 101, 97, 107, 61, 13, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 ]; let decoded = [ - 115, 101, 115, 58, 45, 32, 83, 114, 101, - 97, 108, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 115, 101, 115, 58, 45, 32, 83, 114, 101, 97, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, ]; let res = remove_soft_line_breaks(encoded, decoded); assert(!res, "Expected to return false for incorrect decoded input"); @@ -152,16 +140,12 @@ pub fn test_return_false_incorrect_decoded_input() { #[test] pub fn test_handle_no_soft_line_breaks() { let encoded = [ - 104, 101, 108, 108, 111, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, ]; let decoded = [ - 104, 101, 108, 108, 111, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, ]; let res = remove_soft_line_breaks(encoded, decoded); assert(res, "Expected to handle no soft line breaks"); @@ -170,16 +154,12 @@ pub fn test_handle_no_soft_line_breaks() { #[test] pub fn test_handle_consecutive_soft_line_breaks() { let encoded = [ - 104, 101, 108, 108, 111, 61, 13, 10, 61, - 13, 10, 119, 111, 114, 108, 100, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 61, 13, 10, 61, 13, 10, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; let decoded = [ - 104, 101, 108, 108, 111, 119, 111, 114, 108, - 100, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, ]; let res = remove_soft_line_breaks(encoded, decoded); assert(res, "Expected to handle consecutive soft line breaks"); @@ -188,16 +168,12 @@ pub fn test_handle_consecutive_soft_line_breaks() { #[test] pub fn test_handle_soft_line_break_beginning() { let encoded = [ - 61, 13, 10, 104, 101, 108, 108, 111, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 61, 13, 10, 104, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, ]; let decoded = [ - 104, 101, 108, 108, 111, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, ]; let res = remove_soft_line_breaks(encoded, decoded); assert(res, "Expected to handle soft line break at beginning"); @@ -206,16 +182,12 @@ pub fn test_handle_soft_line_break_beginning() { #[test] pub fn test_handle_soft_line_break_end() { let encoded = [ - 104, 101, 108, 108, 111, 61, 13, 10, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 61, 13, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, ]; let decoded = [ - 104, 101, 108, 108, 111, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, ]; let res = remove_soft_line_breaks(encoded, decoded); assert(res, "Expected to handle soft line break at end"); @@ -224,17 +196,13 @@ pub fn test_handle_soft_line_break_end() { #[test] pub fn test_handle_incomplete_line_break() { let encoded = [ - 104, 101, 108, 108, 111, 61, 13, 11, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 61, 13, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, ]; let decoded = [ - 104, 101, 108, 108, 111, 61, 13, 11, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 + 104, 101, 108, 108, 111, 61, 13, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, ]; let res = remove_soft_line_breaks(encoded, decoded); assert(res, "Expected to handle incomplete line break"); -} \ No newline at end of file +}