Skip to content

Commit

Permalink
Merge pull request #6 from zkemail/feat/comments
Browse files Browse the repository at this point in the history
Add AI generated comments
  • Loading branch information
Bisht13 authored Sep 19, 2024
2 parents 0c19631 + 4594736 commit 4f419a5
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 10 deletions.
66 changes: 63 additions & 3 deletions src/command_templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,33 @@ const INT_REGEX: &str = r"-?\d+";
const ETH_ADDR_REGEX: &str = r"0x[a-fA-F0-9]{40}";
const DECIMALS_REGEX: &str = r"\d+\.\d+";

/// Represents different types of template values.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemplateValue {
/// A string value.
String(String),
/// An unsigned integer value.
Uint(U256),
/// A signed integer value.
Int(I256),
/// A decimal value represented as a string.
Decimals(String),
/// An Ethereum address.
EthAddr(Address),
/// A fixed value represented as a string.
Fixed(String),
}

impl TemplateValue {
/// Encodes the template value into ABI format.
///
/// # Arguments
///
/// * `decimal_size` - An optional value specifying the number of decimal places for Decimals type.
///
/// # Returns
///
/// A `Result` containing the encoded bytes or an error.
pub fn abi_encode(&self, decimal_size: Option<u8>) -> Result<Bytes> {
match self {
Self::String(string) => Ok(Bytes::from(abi::encode(&[Token::String(string.clone())]))),
Expand All @@ -36,7 +52,17 @@ impl TemplateValue {
}
}

pub fn decimals_str_to_uint(str: &str, decimal_size: u8) -> U256 {
/// Converts a decimal string to a U256 integer.
///
/// # Arguments
///
/// * `str` - The decimal string to convert.
/// * `decimal_size` - The number of decimal places.
///
/// # Returns
///
/// A `U256` representing the decimal value.
fn decimals_str_to_uint(str: &str, decimal_size: u8) -> U256 {
let decimal_size = decimal_size as usize;
let dot = Regex::new("\\.").unwrap().find(str);
let (before_dot_str, mut after_dot_str) = match dot {
Expand All @@ -54,6 +80,16 @@ impl TemplateValue {
}
}

/// Extracts template values from a command input string.
///
/// # Arguments
///
/// * `input` - The input string to extract values from.
/// * `templates` - A vector of template strings.
///
/// # Returns
///
/// A `Result` containing a vector of `TemplateValue`s or an error.
pub fn extract_template_vals_from_command(
input: &str,
templates: Vec<String>,
Expand Down Expand Up @@ -88,13 +124,24 @@ pub fn extract_template_vals_from_command(
}
}

pub fn extract_template_vals(input: &str, templates: Vec<String>) -> Result<Vec<TemplateValue>> {
/// Extracts template values from an input string.
///
/// # Arguments
///
/// * `input` - The input string to extract values from.
/// * `templates` - A vector of template strings.
///
/// # Returns
///
/// A `Result` containing a vector of `TemplateValue`s or an error.
fn extract_template_vals(input: &str, templates: Vec<String>) -> Result<Vec<TemplateValue>> {
let input_decomposed: Vec<&str> = input.split_whitespace().collect();
let mut template_vals = Vec::new();

for (input_idx, template) in templates.iter().enumerate() {
match template.as_str() {
"{string}" => {
// Extract and validate string value
let string_match = Regex::new(STRING_REGEX)
.unwrap()
.find(input_decomposed[input_idx])
Expand All @@ -109,6 +156,7 @@ pub fn extract_template_vals(input: &str, templates: Vec<String>) -> Result<Vec<
template_vals.push(TemplateValue::String(string));
}
"{uint}" => {
// Extract and validate unsigned integer value
let uint_match = Regex::new(UINT_REGEX)
.unwrap()
.find(input_decomposed[input_idx])
Expand All @@ -125,6 +173,7 @@ pub fn extract_template_vals(input: &str, templates: Vec<String>) -> Result<Vec<
template_vals.push(TemplateValue::Uint(uint));
}
"{int}" => {
// Extract and validate signed integer value
let int_match = Regex::new(INT_REGEX)
.unwrap()
.find(input_decomposed[input_idx])
Expand All @@ -140,6 +189,7 @@ pub fn extract_template_vals(input: &str, templates: Vec<String>) -> Result<Vec<
template_vals.push(TemplateValue::Int(int));
}
"{decimals}" => {
// Extract and validate decimal value
let decimals_match = Regex::new(DECIMALS_REGEX)
.unwrap()
.find(input_decomposed[input_idx])
Expand All @@ -156,6 +206,7 @@ pub fn extract_template_vals(input: &str, templates: Vec<String>) -> Result<Vec<
template_vals.push(TemplateValue::Decimals(decimals));
}
"{ethAddr}" => {
// Extract and validate Ethereum address
let address_match = Regex::new(ETH_ADDR_REGEX)
.unwrap()
.find(input_decomposed[input_idx])
Expand All @@ -173,7 +224,16 @@ pub fn extract_template_vals(input: &str, templates: Vec<String>) -> Result<Vec<
Ok(template_vals)
}

// Generated by Github Copilot!
/// Converts an unsigned integer to a decimal string representation.
///
/// # Arguments
///
/// * `uint` - The unsigned integer to convert.
/// * `decimal` - The number of decimal places to use.
///
/// # Returns
///
/// A string representation of the decimal value.
pub fn uint_to_decimal_string(uint: u128, decimal: usize) -> String {
// Convert amount to string in wei format (no decimals)
let uint_str = uint.to_string();
Expand Down
43 changes: 36 additions & 7 deletions src/parse_email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ use zk_regex_apis::extract_substrs::{
extract_substr_idxes, extract_timestamp_idxes, extract_to_addr_idxes,
};

#[derive(Debug, Clone, Serialize, Deserialize)]
/// `ParsedEmail` holds the canonicalized parts of an email along with its signature and public key.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParsedEmail {
pub canonicalized_header: String, // The canonicalized email header.
pub canonicalized_body: String, // The canonicalized email body.
pub signature: Vec<u8>, // The email signature bytes.
pub public_key: Vec<u8>, // The public key bytes associated with the email.
pub cleaned_body: String, // The cleaned email body.
/// The canonicalized email header.
pub canonicalized_header: String,
/// The canonicalized email body.
pub canonicalized_body: String,
/// The email signature bytes.
pub signature: Vec<u8>,
/// The public key bytes associated with the email.
pub public_key: Vec<u8>,
/// The cleaned email body.
pub cleaned_body: String,
/// The email headers.
pub headers: EmailHeaders,
}

Expand Down Expand Up @@ -57,6 +63,7 @@ impl ParsedEmail {
// Extract all headers
let parsed_mail = parse_mail(raw_email.as_bytes())?;
let headers: EmailHeaders = EmailHeaders::new_from_mail(&parsed_mail);

// Construct the `ParsedEmail` instance.
let parsed_email = ParsedEmail {
canonicalized_header: String::from_utf8(canonicalized_header)?, // Convert bytes to string, may return an error if not valid UTF-8.
Expand Down Expand Up @@ -270,12 +277,14 @@ pub(crate) fn remove_quoted_printable_soft_breaks(body: Vec<u8>) -> Vec<u8> {

while let Some((i, &byte)) = iter.next() {
if byte == b'=' && body.get(i + 1..i + 3) == Some(&[b'\r', b'\n']) {
iter.nth(1); // Skip the next two bytes
// Skip the next two bytes (soft line break)
iter.nth(1);
} else {
result.push(byte);
}
}

// Resize the result to match the original body length
result.resize(body.len(), 0);
result
}
Expand All @@ -296,6 +305,7 @@ pub(crate) fn remove_quoted_printable_soft_breaks(body: Vec<u8>) -> Vec<u8> {
pub(crate) fn find_index_in_body(body: Option<&Vec<u8>>, pattern: &str) -> usize {
body.and_then(|body_bytes| {
if !pattern.is_empty() {
// Search for the pattern in the body
body_bytes
.windows(pattern.len())
.position(|w| w == pattern.as_bytes())
Expand All @@ -306,10 +316,20 @@ pub(crate) fn find_index_in_body(body: Option<&Vec<u8>>, pattern: &str) -> usize
.unwrap_or(0) // Default to 0 if not found or pattern is empty
}

/// Represents the email headers as a collection of key-value pairs.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailHeaders(HashMap<String, Vec<String>>);

impl EmailHeaders {
/// Creates a new `EmailHeaders` instance from a parsed email.
///
/// # Arguments
///
/// * `parsed_mail` - A reference to a `ParsedMail` instance.
///
/// # Returns
///
/// A new `EmailHeaders` instance containing the headers from the parsed email.
pub fn new_from_mail(parsed_mail: &ParsedMail) -> Self {
let mut headers = HashMap::new();
for header in &parsed_mail.headers {
Expand All @@ -320,6 +340,15 @@ impl EmailHeaders {
Self(headers)
}

/// Retrieves the value(s) of a specific header.
///
/// # Arguments
///
/// * `name` - The name of the header to retrieve.
///
/// # Returns
///
/// An `Option` containing a `Vec<String>` of header values if the header exists, or `None` if it doesn't.
pub fn get_header(&self, name: &str) -> Option<Vec<String>> {
self.0.get(name).cloned()
}
Expand Down
64 changes: 64 additions & 0 deletions src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,40 @@ use std::hash::{Hash, Hasher};

use crate::{field_to_hex, hex_to_field, AccountCode, AccountSalt, PaddedEmailAddr};

/// Represents the response from the prover.
#[derive(Debug, Clone, Deserialize)]
pub struct ProverRes {
/// The proof in JSON format.
proof: ProofJson,
/// The public signals associated with the proof.
pub_signals: Vec<String>,
}

/// Represents the proof in JSON format.
#[derive(Debug, Clone, Deserialize)]
pub struct ProofJson {
/// The pi_a component of the proof.
pi_a: Vec<String>,
/// The pi_b component of the proof.
pi_b: Vec<Vec<String>>,
/// The pi_c component of the proof.
pi_c: Vec<String>,
}

impl ProofJson {
/// Converts the proof to Ethereum-compatible bytes.
///
/// # Returns
///
/// A `Result` containing the Ethereum-compatible bytes or an error.
pub fn to_eth_bytes(&self) -> Result<Bytes> {
// Convert pi_a to Token::FixedArray
let pi_a = Token::FixedArray(vec![
Token::Uint(U256::from_dec_str(self.pi_a[0].as_str())?),
Token::Uint(U256::from_dec_str(self.pi_a[1].as_str())?),
]);

// Convert pi_b to nested Token::FixedArray
let pi_b = Token::FixedArray(vec![
Token::FixedArray(vec![
Token::Uint(U256::from_dec_str(self.pi_b[0][1].as_str())?),
Expand All @@ -41,36 +56,69 @@ impl ProofJson {
Token::Uint(U256::from_dec_str(self.pi_b[1][0].as_str())?),
]),
]);

// Convert pi_c to Token::FixedArray
let pi_c = Token::FixedArray(vec![
Token::Uint(U256::from_dec_str(self.pi_c[0].as_str())?),
Token::Uint(U256::from_dec_str(self.pi_c[1].as_str())?),
]);

// Encode the tokens and return as Bytes
Ok(Bytes::from(abi::encode(&[pi_a, pi_b, pi_c])))
}
}

/// Generates a proof for the given input.
///
/// # Arguments
///
/// * `input` - The input string for proof generation.
/// * `request` - The request string.
/// * `address` - The address string.
///
/// # Returns
///
/// A `Result` containing a tuple of `Bytes` (the proof) and `Vec<U256>` (public signals) or an error.
pub async fn generate_proof(
input: &str,
request: &str,
address: &str,
) -> Result<(Bytes, Vec<U256>)> {
let client = reqwest::Client::new();

// Send POST request to the prover
let res = client
.post(format!("{}/prove/{}", address, request))
.json(&serde_json::json!({ "input": input }))
.send()
.await?
.error_for_status()?;

// Parse the response JSON
let res_json = res.json::<ProverRes>().await?;

// Convert the proof to Ethereum-compatible bytes
let proof = res_json.proof.to_eth_bytes()?;

// Convert public signals to U256
let pub_signals = res_json
.pub_signals
.into_iter()
.map(|str| U256::from_dec_str(&str).expect("pub signal should be u256"))
.collect();

Ok((proof, pub_signals))
}

/// Calculates a default hash for the given input string.
///
/// # Arguments
///
/// * `input` - The input string to hash.
///
/// # Returns
///
/// A string representation of the calculated hash.
pub fn calculate_default_hash(input: &str) -> String {
let mut hasher = DefaultHasher::new();
input.hash(&mut hasher);
Expand All @@ -79,15 +127,31 @@ pub fn calculate_default_hash(input: &str) -> String {
hash_code.to_string()
}

/// Calculates the account salt based on the email address and account code.
///
/// # Arguments
///
/// * `email_addr` - The email address string.
/// * `account_code` - The account code string.
///
/// # Returns
///
/// A string representation of the calculated account salt.
pub fn calculate_account_salt(email_addr: &str, account_code: &str) -> String {
// Pad the email address
let padded_email_addr = PaddedEmailAddr::from_email_addr(email_addr);

// Convert account code to field element
let account_code = if account_code.starts_with("0x") {
hex_to_field(account_code).unwrap()
} else {
hex_to_field(&format!("0x{}", account_code)).unwrap()
};
let account_code = AccountCode::from(account_code);

// Generate account salt
let account_salt = AccountSalt::new(&padded_email_addr, account_code).unwrap();

// Convert account salt to hexadecimal representation
field_to_hex(&account_salt.0)
}

0 comments on commit 4f419a5

Please sign in to comment.