Skip to content

Commit

Permalink
Merge pull request #15 from zkemail/feat/expose_more_wasm
Browse files Browse the repository at this point in the history
Feat/expose more wasm
  • Loading branch information
SoraSuegami authored Dec 13, 2024
2 parents cda92bf + f485ae2 commit 250c2aa
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 11 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
"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"
},
"peerDependencies": {
"typescript": "^5.0.0"
}
}
}
15 changes: 10 additions & 5 deletions src/circuit.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -36,6 +37,7 @@ struct EmailCircuitInput {
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailCircuitParams {
pub ignore_body_hash_check: Option<bool>, // Flag to ignore the body hash check
pub max_header_length: Option<usize>, // The maximum length of the email header
Expand Down Expand Up @@ -176,7 +178,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];
Expand Down Expand Up @@ -574,14 +578,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())
Expand All @@ -590,7 +594,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();
Expand Down Expand Up @@ -652,6 +656,7 @@ pub fn compute_signal_length(max_length: usize) -> usize {

#[cfg(test)]
mod tests {

use super::*;
use std::path::PathBuf;

Expand Down
225 changes: 221 additions & 4 deletions src/wasm.rs
Original file line number Diff line number Diff line change
@@ -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")]
Expand All @@ -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")]
Expand Down Expand Up @@ -311,3 +316,215 @@ 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: {}", e))?,
);
// Deserialize params from JsValue
let params: Option<EmailCircuitParams> = 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::<String>() {
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<u8>) -> 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<u8>) -> 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<u8> = 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(mut signautre: Vec<u8>) -> 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")),
}
}

#[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<Array, JsValue> {
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<Array, JsValue> {
let regex_config = include_str!("../regexes/invitation_code_with_prefix.json");
extractSubstrIdxes(inputStr, JsValue::from_str(regex_config), false)
}

0 comments on commit 250c2aa

Please sign in to comment.