From af4764e29008d622a68b2a78fa7f37de16d250b1 Mon Sep 17 00:00:00 2001 From: SoraSuegami Date: Thu, 12 Dec 2024 15:46:09 +0900 Subject: [PATCH 1/5] Expose more wasm functions --- src/circuit.rs | 8 +- src/wasm.rs | 222 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 222 insertions(+), 8 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index d84e586..68d5d05 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -484,14 +484,14 @@ pub async fn generate_circuit_inputs_with_decomposed_regexes_and_external_inputs // Determine the input string based on the regex location let input = if decomposed_regex.location == "header" { - &String::from_utf8_lossy(&email_circuit_inputs.header_padded.clone()).into_owned() + String::from_utf8_lossy(&email_circuit_inputs.header_padded.clone()).into_owned() } else if decomposed_regex.location == "body" && params.remove_soft_lines_breaks { - &cleaned_body + cleaned_body .as_ref() .map(|(v, _)| String::from_utf8_lossy(v).into_owned()) .unwrap_or_else(|| String::new()) } else { - &email_circuit_inputs + email_circuit_inputs .body_padded .as_ref() .map(|v| String::from_utf8_lossy(v).into_owned()) @@ -500,7 +500,7 @@ pub async fn generate_circuit_inputs_with_decomposed_regexes_and_external_inputs // Extract substring indices using the decomposed regex configuration let idxes: Vec<(usize, usize)> = - extract_substr_idxes(input, &decomposed_regex_config, false)?; + extract_substr_idxes(&input, &decomposed_regex_config, false)?; // Add the first index to the circuit inputs circuit_inputs[format!("{}RegexIdx", decomposed_regex.name)] = idxes[0].0.into(); diff --git a/src/wasm.rs b/src/wasm.rs index 9612212..912c3c5 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -1,5 +1,5 @@ #[cfg(target_arch = "wasm32")] -use js_sys::Promise; +use js_sys::{Array, Promise}; #[cfg(target_arch = "wasm32")] use rand::rngs::OsRng; #[cfg(target_arch = "wasm32")] @@ -9,13 +9,18 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use crate::{ - generate_circuit_inputs_with_decomposed_regexes_and_external_inputs, hex_to_field, AccountCode, - AccountSalt, CircuitInputWithDecomposedRegexesAndExternalInputsParams, DecomposedRegex, + bytes_to_fields, email_nullifier, extract_rand_from_signature, field_to_hex, + generate_circuit_inputs_with_decomposed_regexes_and_external_inputs, + generate_email_circuit_input, hex_to_field, AccountCode, AccountSalt, + CircuitInputWithDecomposedRegexesAndExternalInputsParams, DecomposedRegex, EmailCircuitParams, ExternalInput, PaddedEmailAddr, ParsedEmail, }; #[cfg(target_arch = "wasm32")] +use itertools::Itertools; +#[cfg(target_arch = "wasm32")] use wasm_bindgen_futures::future_to_promise; - +#[cfg(target_arch = "wasm32")] +use zk_regex_apis::extractSubstrIdxes; #[wasm_bindgen] #[allow(non_snake_case)] #[cfg(target_arch = "wasm32")] @@ -311,3 +316,212 @@ pub async fn publicKeyHash(public_key_n: JsValue) -> Promise { } }) } + +#[wasm_bindgen] +#[allow(non_snake_case)] +#[cfg(target_arch = "wasm32")] +/// Generates the circuit inputs for email verification circuits using the given email data, account code, and optional parameters. +/// +/// # Arguments +/// +/// * `email` - A `String` representing the raw email data to be verified. +/// * `account_code` - A `String` representing the account code in hexadecimal format. +/// * `params` - An object representing the optional parameters for the circuit. +/// +/// # Returns +/// +/// A `Promise` that resolves with the serialized `CircuitInputs` or rejects with an error message. +pub async fn generateEmailCircuitInput( + email: String, + account_code: String, + params: JsValue, +) -> Promise { + console_error_panic_hook::set_once(); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| async move { + // Parse account_code + let account_code = AccountCode::from( + hex_to_field(&account_code).map_err(|e| format!("Failed to parse AccountCode"))?, + ); + // Deserialize params from JsValue + let params: Option = if params.is_null() { + None + } else { + let params = from_value(params).map_err(|e| format!("Invalid params: {}", e))?; + Some(params) + }; + + // Call the core function + let circuit_inputs = generate_email_circuit_input(&email, &account_code, params) + .await + .map_err(|e| format!("Error generating circuit inputs: {}", e))?; + + // Serialize the output to JsValue + to_value(&circuit_inputs).map_err(|e| format!("Failed to serialize CircuitInputs: {}", e)) + })); + + match result { + Ok(future) => match future.await { + Ok(serialized_inputs) => Promise::resolve(&serialized_inputs), + Err(err_msg) => Promise::reject(&JsValue::from_str(&err_msg)), + }, + Err(panic) => { + let panic_msg = match panic.downcast::() { + Ok(msg) => *msg, + Err(panic) => match panic.downcast::<&str>() { + Ok(msg) => msg.to_string(), + Err(_) => "Unknown panic occurred".to_string(), + }, + }; + Promise::reject(&JsValue::from_str(&format!( + "Panic occurred: {}", + panic_msg + ))) + } + } +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +#[cfg(target_arch = "wasm32")] +/// Extracts the randomness from a given signature in the same manner as circuits. +/// +/// # Arguments +/// +/// * `signature` - A `Uint8Array` containing the signature data. +/// +/// # Returns +/// +/// A `Promise` that resolves with the extracted randomness as a hexadecimal string, or rejects with an error message. +pub async fn extractRandFromSignature(signautre: Vec) -> Promise { + console_error_panic_hook::set_once(); + + let cm_rand = match extract_rand_from_signature(&signautre) { + Ok(field) => field, + Err(_) => return Promise::reject(&JsValue::from_str("Failed to extract randomness")), + }; + match to_value(&field_to_hex(&cm_rand)) { + Ok(serialized_rand) => Promise::resolve(&serialized_rand), + Err(_) => Promise::reject(&JsValue::from_str("Failed to serialize randomness")), + } +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +#[cfg(target_arch = "wasm32")] +/// Commits an email address using a given signature as the randomness. +/// +/// # Arguments +/// +/// * `email_addr` - A `String` representing the email address to be committed. +/// * `signature` - A `Uint8Array` containing the signature data to be used as randomness. +/// +/// # Returns +/// +/// A `Promise` that resolves with the commitment as a hexadecimal string, or rejects with an error message. +pub async fn emailAddrCommitWithSignature(email_addr: String, signautre: Vec) -> Promise { + use crate::PaddedEmailAddr; + + console_error_panic_hook::set_once(); + + let padded_email_addr = PaddedEmailAddr::from_email_addr(&email_addr); + let cm = match padded_email_addr.to_commitment_with_signature(&signautre) { + Ok(cm) => cm, + Err(_) => return Promise::reject(&JsValue::from_str("Failed to commit email address")), + }; + + match to_value(&field_to_hex(&cm)) { + Ok(cm) => Promise::resolve(&cm), + Err(_) => Promise::reject(&JsValue::from_str("Failed to serialize randomness")), + } +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +#[cfg(target_arch = "wasm32")] +/// Converts a byte array to a list of field elements. +/// +/// # Arguments +/// +/// * `bytes` - A `Uint8Array` containing the byte array to convert. +/// +/// # Returns +/// +/// A `Promise` that resolves with a list of field elements as hexadecimal strings, or rejects with an error message. +pub async fn bytesToFields(bytes: JsValue) -> Promise { + use wasm_bindgen::JsValue; + + console_error_panic_hook::set_once(); + + let bytes: Vec = match from_value(bytes) { + Ok(bytes) => bytes, + Err(_) => return Promise::reject(&JsValue::from_str("Failed to convert input to bytes")), + }; + let fields = bytes_to_fields(&bytes) + .into_iter() + .map(|field| field_to_hex(&field)) + .collect_vec(); + match to_value(&fields) { + Ok(serialized_fields) => Promise::resolve(&serialized_fields), + Err(_) => Promise::reject(&JsValue::from_str("Failed to serialize fields")), + } +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +#[cfg(target_arch = "wasm32")] +/// Computes the nullifier for an email address using a given signature. +/// +/// # Arguments +/// +/// * `signature` - A `Uint8Array` containing the signature data to be used for the nullifier. +/// +/// # Returns +/// +/// A `Promise` that resolves with the email nullifier as a hexadecimal string, or rejects with an error message. +pub async fn emailNullifier(signautre: Vec) -> Promise { + use js_sys::Promise; + + use crate::field_to_hex; + + console_error_panic_hook::set_once(); + + match email_nullifier(&signautre) { + Ok(field) => Promise::resolve(&JsValue::from_str(&field_to_hex(&field))), + Err(_) => Promise::reject(&JsValue::from_str("Failed to compute email nullifier")), + } +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +#[cfg(target_arch = "wasm32")] +/// Extracts the indices of the invitation code in the given input string. +/// +/// # Arguments +/// +/// * `inputStr` - A `String` representing the input string to extract the invitation code indices from. +/// +/// # Returns +/// +/// A `Promise` that resolves with an array of arrays containing the start and end indices of the invitation code substrings, +pub fn extractInvitationCodeIdxes(inputStr: &str) -> Result { + let regex_config = include_str!("../regexes/invitation_code.json"); + extractSubstrIdxes(inputStr, JsValue::from_str(regex_config), false) +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +#[cfg(target_arch = "wasm32")] +/// Extracts the indices of the invitation code with prefix in the given input string. +/// +/// # Arguments +/// +/// * `inputStr` - A `String` representing the input string to extract the invitation code indices from. +/// +/// # Returns +/// +/// A `Promise` that resolves with an array of arrays containing the start and end indices of the invitation code substrings, +pub fn extractInvitationCodeWithPrefixIdxes(inputStr: &str) -> Result { + let regex_config = include_str!("../regexes/invitation_code_with_prefix.json"); + extractSubstrIdxes(inputStr, JsValue::from_str(regex_config), false) +} From 2426492e7bba5a42cf1f0b6bbf178fa3cd475778 Mon Sep 17 00:00:00 2001 From: SoraSuegami Date: Thu, 12 Dec 2024 15:49:17 +0900 Subject: [PATCH 2/5] Remove warning in wasm.rs --- src/wasm.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wasm.rs b/src/wasm.rs index 912c3c5..4ee7f9d 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -341,7 +341,8 @@ pub async fn generateEmailCircuitInput( let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| async move { // Parse account_code let account_code = AccountCode::from( - hex_to_field(&account_code).map_err(|e| format!("Failed to parse AccountCode"))?, + hex_to_field(&account_code) + .map_err(|e| format!("Failed to parse AccountCode: {}", e))?, ); // Deserialize params from JsValue let params: Option = if params.is_null() { From 542c27931ace62fd1320b8ce30dd0c36d639eff1 Mon Sep 17 00:00:00 2001 From: SoraSuegami Date: Fri, 13 Dec 2024 00:41:41 +0900 Subject: [PATCH 3/5] Fix some bugs --- src/circuit.rs | 6 +++++- src/wasm.rs | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 6b4622b..afbbf08 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, Result}; use num_bigint::BigInt; +use regex::Regex; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::{cmp, collections::VecDeque}; @@ -176,7 +177,9 @@ fn find_selector_in_clean_content( position_map: &[usize], ) -> Result<(String, usize)> { let clean_string = String::from_utf8_lossy(clean_content); - if let Some(selector_index) = clean_string.find(selector) { + let re = Regex::new(selector).unwrap(); + if let Some(m) = re.find(&clean_string) { + let selector_index = m.start(); // Map this cleaned index back to original if selector_index < position_map.len() { let original_index = position_map[selector_index]; @@ -647,6 +650,7 @@ pub fn compute_signal_length(max_length: usize) -> usize { #[cfg(test)] mod tests { + use super::*; use std::path::PathBuf; diff --git a/src/wasm.rs b/src/wasm.rs index 4ee7f9d..64dee1e 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -480,13 +480,15 @@ pub async fn bytesToFields(bytes: JsValue) -> Promise { /// # Returns /// /// A `Promise` that resolves with the email nullifier as a hexadecimal string, or rejects with an error message. -pub async fn emailNullifier(signautre: Vec) -> Promise { +pub async fn emailNullifier(mut signautre: Vec) -> Promise { use js_sys::Promise; use crate::field_to_hex; console_error_panic_hook::set_once(); + // Reverse the bytes for little-endian format + signautre.reverse(); match email_nullifier(&signautre) { Ok(field) => Promise::resolve(&JsValue::from_str(&field_to_hex(&field))), Err(_) => Promise::reject(&JsValue::from_str("Failed to compute email nullifier")), From e660944edb6b7fcac9afd3f188c11549264f4f2b Mon Sep 17 00:00:00 2001 From: Dimitri Date: Fri, 13 Dec 2024 11:31:34 +0700 Subject: [PATCH 4/5] chore: camelCase serde rename for wasm bindings --- src/circuit.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/circuit.rs b/src/circuit.rs index afbbf08..6e5f0cd 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -37,6 +37,7 @@ struct EmailCircuitInput { } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EmailCircuitParams { pub ignore_body_hash_check: Option, // Flag to ignore the body hash check pub max_header_length: Option, // The maximum length of the email header From f485ae28fc5059d57c6636458769436199e5ce03 Mon Sep 17 00:00:00 2001 From: SoraSuegami Date: Sat, 14 Dec 2024 07:12:31 +0900 Subject: [PATCH 5/5] Update version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 163d1e9..fb4cded 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "wasm:postbuild": "node build.js", "build": "npm run wasm:build && npm run wasm:postbuild" }, - "version": "0.4.58", + "version": "0.4.59", "devDependencies": { "@types/bun": "latest", "prettier": "^3.3.3" @@ -16,4 +16,4 @@ "peerDependencies": { "typescript": "^5.0.0" } -} +} \ No newline at end of file