Skip to content

Commit

Permalink
feat: add icp, prover and on chain stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
Bisht13 committed Oct 11, 2024
1 parent f08144a commit 036a96d
Show file tree
Hide file tree
Showing 19 changed files with 1,561 additions and 169 deletions.
627 changes: 580 additions & 47 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion packages/relayer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ anyhow = "1.0.89"
axum = "0.7.7"
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio", "migrate", "uuid", "time"] }
sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio", "migrate", "uuid", "time", "chrono"] }
tokio = { version = "1.40.0", features = ["full"] }
tower-http = { version = "0.6.1", features = ["cors"] }
# relayer-utils = { git = "https://github.com/zkemail/relayer-utils.git", branch = "main" }
Expand All @@ -23,3 +23,10 @@ ethers = "2.0.14"
reqwest = { version = "0.12.8", features = ["json"] }
handlebars = "6.1.0"
regex = "1.11.0"
ic-agent = { version = "0.37.1", features = ["pem", "reqwest"] }
ic-utils = "0.37.0"
candid = "0.10.10"
lazy_static = "1.5.0"

[build-dependencies]
ethers = "2.0.14"
23 changes: 23 additions & 0 deletions packages/relayer/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use ethers::contract::Abigen;

fn main() {
Abigen::new(
"EmailAuth",
"../contracts/artifacts/EmailAuth.sol/EmailAuth.json",
)
.unwrap()
.generate()
.unwrap()
.write_to_file("./src/abis/email_auth.rs")
.unwrap();

Abigen::new(
"ECDSAOwnedDKIMRegistry",
"../contracts/artifacts/ECDSAOwnedDKIMRegistry.sol/ECDSAOwnedDKIMRegistry.json",
)
.unwrap()
.generate()
.unwrap()
.write_to_file("./src/abis/ecdsa_owned_dkim_registry.rs")
.unwrap();
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
>
<tr>
<td style="padding: 0; font-size: 16px; color: #000">
Hi, <strong>{{userEmailAddr}}!</strong>
Hi,
</td>
</tr>
<tr>
Expand Down
2 changes: 1 addition & 1 deletion packages/relayer/email_templates/command_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
>
<tr>
<td style="padding: 0; font-size: 16px; color: #000">
Hi, <strong>{{userEmailAddr}}!</strong>
Hi,
</td>
</tr>
<tr>
Expand Down
6 changes: 2 additions & 4 deletions packages/relayer/email_templates/completion_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,12 @@
>
<tr>
<td style="padding: 0; font-size: 16px; color: #000">
Hi, {{userEmailAddr}}!
Hi,
</td>
</tr>
<tr>
<td style="padding-top: 15px; font-family: 'Fustat', sans-serif;">
{{body}}
<br /><br />
Your request ID is <strong>#{{requestId}}</strong> is now complete.
Your request ID is <strong>{{requestId}}</strong> is now complete.
</td>
</tr>
<tr>
Expand Down
10 changes: 8 additions & 2 deletions packages/relayer/migrations/20241008135456_init.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TYPE status_enum AS ENUM ('Request received', 'Email sent', 'Email response received', 'Proving', 'Performing on chain transaction', 'Finished');
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'status_enum') THEN
CREATE TYPE status_enum AS ENUM ('Request received', 'Processing', 'Completed', 'Failed');
END IF;
END $$;

CREATE TABLE IF NOT EXISTS requests (
id UUID PRIMARY KEY NOT NULL DEFAULT (uuid_generate_v4()),
status status_enum NOT NULL DEFAULT 'Request received',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
email_tx_auth JSONB NOT NULL
);

CREATE TABLE IF NOT EXISTS expected_replies (
Expand Down
207 changes: 207 additions & 0 deletions packages/relayer/src/chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use std::collections::HashMap;

use crate::*;
use abi::{Abi, Token};
use abis::{ECDSAOwnedDKIMRegistry, EmailAuth, EmailAuthMsg, EmailProof};
use anyhow::anyhow;
use config::ChainConfig;
use ethers::prelude::*;
use ethers::signers::Signer;
use ethers::utils::hex;
use model::RequestModel;
use statics::SHARED_MUTEX;

const CONFIRMATIONS: usize = 1;

type SignerM = SignerMiddleware<Provider<Http>, LocalWallet>;

/// Represents a client for interacting with the blockchain.
#[derive(Debug, Clone)]
pub struct ChainClient {
pub client: Arc<SignerM>,
}

impl ChainClient {
/// Sets up a new ChainClient.
///
/// # Returns
///
/// A `Result` containing the new `ChainClient` if successful, or an error if not.
pub async fn setup(chain: String, chains: HashMap<String, ChainConfig>) -> Result<Self> {
let chain_config = chains
.get(&chain)
.ok_or_else(|| anyhow!("Chain configuration not found"))?;
let wallet: LocalWallet = chain_config.private_key.parse()?;
let provider = Provider::<Http>::try_from(chain_config.rpc_url.clone())?;

// Create a new SignerMiddleware with the provider and wallet
let client = Arc::new(SignerMiddleware::new(
provider,
wallet.with_chain_id(chain_config.chain_id),
));

Ok(Self { client })
}

/// Sets the DKIM public key hash.
///
/// # Arguments
///
/// * `selector` - The selector string.
/// * `domain_name` - The domain name.
/// * `public_key_hash` - The public key hash as a 32-byte array.
/// * `signature` - The signature as Bytes.
/// * `dkim` - The ECDSA Owned DKIM Registry.
///
/// # Returns
///
/// A `Result` containing the transaction hash as a String if successful, or an error if not.
pub async fn set_dkim_public_key_hash(
&self,
selector: String,
domain_name: String,
public_key_hash: [u8; 32],
signature: Bytes,
dkim: ECDSAOwnedDKIMRegistry<SignerM>,
) -> Result<String> {
// Mutex is used to prevent nonce conflicts.
let mut mutex = SHARED_MUTEX
.lock()
.map_err(|e| anyhow::anyhow!("Mutex poisoned: {}", e))?;
*mutex += 1;

// Call the contract method
let call = dkim.set_dkim_public_key_hash(selector, domain_name, public_key_hash, signature);
let tx = call.send().await?;

// Wait for the transaction to be confirmed
let receipt = tx
.log()
.confirmations(CONFIRMATIONS)
.await?
.ok_or(anyhow!("No receipt"))?;

// Format the transaction hash
let tx_hash = receipt.transaction_hash;
let tx_hash = format!("0x{}", hex::encode(tx_hash.as_bytes()));
Ok(tx_hash)
}

/// Checks if a DKIM public key hash is valid.
///
/// # Arguments
///
/// * `domain_name` - The domain name.
/// * `public_key_hash` - The public key hash as a 32-byte array.
/// * `dkim` - The ECDSA Owned DKIM Registry.
///
/// # Returns
///
/// A `Result` containing a boolean indicating if the hash is valid.
pub async fn check_if_dkim_public_key_hash_valid(
&self,
domain_name: ::std::string::String,
public_key_hash: [u8; 32],
dkim: ECDSAOwnedDKIMRegistry<SignerM>,
) -> Result<bool> {
// Call the contract method to check if the hash is valid
let is_valid = dkim
.is_dkim_public_key_hash_valid(domain_name, public_key_hash)
.call()
.await?;
Ok(is_valid)
}

/// Gets the DKIM from an email auth address.
///
/// # Arguments
///
/// * `email_auth_addr` - The email auth address as a string.
///
/// # Returns
///
/// A `Result` containing the ECDSA Owned DKIM Registry if successful, or an error if not.
pub async fn get_dkim_from_email_auth(
&self,
email_auth_address: Address,
) -> Result<ECDSAOwnedDKIMRegistry<SignerM>, anyhow::Error> {
// Create a new EmailAuth contract instance
let contract = EmailAuth::new(email_auth_address, self.client.clone());

// Call the dkim_registry_addr method to get the DKIM registry address
let dkim = contract.dkim_registry_addr().call().await?;

// Create and return a new ECDSAOwnedDKIMRegistry instance
Ok(ECDSAOwnedDKIMRegistry::new(dkim, self.client.clone()))
}

pub async fn call(&self, request: RequestModel, email_auth_msg: EmailAuthMsg) -> Result<()> {
let abi = Abi {
functions: vec![request.email_tx_auth.function_abi.clone()]
.into_iter()
.map(|f| (f.name.clone(), vec![f]))
.collect(),
events: Default::default(),
constructor: None,
receive: false,
fallback: false,
errors: Default::default(),
};

let contract = Contract::new(
request.email_tx_auth.contract_address,
abi,
self.client.clone(),
);
let function = request.email_tx_auth.function_abi;
let remaining_args = request.email_tx_auth.remaining_args;

// Assuming remaining_args is a Vec of some type that can be converted to tokens
let mut tokens = email_auth_msg.to_tokens();

// Convert remaining_args to tokens and add them to the tokens vector
for arg in remaining_args {
// Convert each arg to a Token. This conversion depends on the type of arg.
// For example, if arg is a string, you might use Token::String(arg).
// Adjust the conversion based on the actual type of arg.
let token = Token::from(arg); // Replace with appropriate conversion
tokens.push(token);
}

// Now you can use the tokens vector to call the contract function
let call = contract.method::<_, ()>(&function.name, tokens)?;
let _result = call.send().await?;
Ok(())
}
}

impl EmailAuthMsg {
pub fn to_tokens(&self) -> Vec<Token> {
vec![
Token::Uint(self.template_id),
Token::Array(
self.command_params
.iter()
.map(|param| Token::Bytes(param.clone().to_vec()))
.collect(),
),
Token::Uint(self.skipped_command_prefix),
Token::Tuple(self.proof.to_tokens()),
]
}
}

impl EmailProof {
pub fn to_tokens(&self) -> Vec<Token> {
vec![
Token::String(self.domain_name.clone()),
Token::FixedBytes(self.public_key_hash.to_vec()),
Token::Uint(self.timestamp),
Token::String(self.masked_command.clone()),
Token::FixedBytes(self.email_nullifier.to_vec()),
Token::FixedBytes(self.account_salt.to_vec()),
Token::Bool(self.is_code_exist),
Token::Bytes(self.proof.clone().to_vec()),
]
}
}
80 changes: 80 additions & 0 deletions packages/relayer/src/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use anyhow::{anyhow, Result};
use ethers::types::{Bytes, U256};
use relayer_utils::{
command_templates, extract_template_vals_from_command, u256_to_bytes32_little,
};

use crate::{constants::COMMAND_FIELDS, model::RequestModel};

pub fn parse_command_template(template: &str, params: Vec<String>) -> String {
let mut parsed_string = template.to_string();
let mut param_iter = params.iter();

while let Some(value) = param_iter.next() {
if let Some(start) = parsed_string.find('{') {
if let Some(end) = parsed_string[start..].find('}') {
parsed_string.replace_range(start..start + end + 1, value);
}
}
}

parsed_string
}

/// Retrieves and encodes the command parameters for the email authentication request.
///
/// # Arguments
///
/// * `params` - The `EmailRequestContext` containing request details.
///
/// # Returns
///
/// A `Result` containing a vector of encoded command parameters or an `EmailError`.
pub async fn get_encoded_command_params(email: &str, request: RequestModel) -> Result<Vec<Bytes>> {
let command_template = request
.email_tx_auth
.command_template
.split_whitespace()
.map(String::from)
.collect();

let command_params = extract_template_vals_from_command(email, command_template)?;

let command_params_encoded = command_params
.iter()
.map(|param| {
param
.abi_encode(None)
.map_err(|e| anyhow::anyhow!(e.to_string()))
})
.collect::<Result<Vec<Bytes>>>()?;

Ok(command_params_encoded)
}

/// Extracts the masked command from public signals.
///
/// # Arguments
///
/// * `public_signals` - The vector of public signals.
/// * `start_idx` - The starting index for command extraction.
///
/// # Returns
///
/// A `Result` containing the masked command as a `String` or an error.
pub fn get_masked_command(public_signals: Vec<U256>, start_idx: usize) -> Result<String> {
// Gather signals from start_idx to start_idx + COMMAND_FIELDS
let command_bytes: Vec<u8> = public_signals
.iter()
.skip(start_idx)
.take(COMMAND_FIELDS)
.take_while(|&signal| *signal != U256::zero())
.flat_map(u256_to_bytes32_little)
.collect();

// Bytes to string, removing null bytes
let command = String::from_utf8(command_bytes.into_iter().filter(|&b| b != 0u8).collect())
.map_err(|e| anyhow!("Failed to convert bytes to string: {}", e))?;

Ok(command)
}
4 changes: 4 additions & 0 deletions packages/relayer/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub const SHA_PRECOMPUTE_SELECTOR: &str = r#"(<(=\r\n)?d(=\r\n)?i(=\r\n)?v(=\r\n)? (=\r\n)?i(=\r\n)?d(=\r\n)?=3D(=\r\n)?\"(=\r\n)?[^\"]*(=\r\n)?z(=\r\n)?k(=\r\n)?e(=\r\n)?m(=\r\n)?a(=\r\n)?i(=\r\n)?l(=\r\n)?[^\"]*(=\r\n)?\"(=\r\n)?[^>]*(=\r\n)?>(=\r\n)?)(=\r\n)?([^<>\/]+)(<(=\r\n)?\/(=\r\n)?d(=\r\n)?i(=\r\n)?v(=\r\n)?>(=\r\n)?)"#;

pub const DOMAIN_FIELDS: usize = 9;
pub const COMMAND_FIELDS: usize = 20;
Loading

0 comments on commit 036a96d

Please sign in to comment.