Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add namecheap prover #31

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions src/namecheap/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
MODAL_HOME_PATH=/root/
MODAL_ZK_P2P_CIRCOM_PATH=/root/zk-p2p/
MODAL_INCOMING_EML_PATH=/root/prover-api/received_eml/
SLACK_TOKEN=<YOUR_SLACK_TOKEN_HERE>
CHANNEL_ID=<YOUR_CHANNEL_ID_HERE>
106 changes: 106 additions & 0 deletions src/namecheap/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Todo: Add refresh logic.

# Folder structure
# /root/
# zk-p2p/
# circuits-circom/
# circuits/${payment_type}/build/
# namecheap_${email_type}/
# namecheap_${email_type}.zkey
# namecheap_${email_type}_js/
# generate_witness.js
# witness_calculator.js
# namecheap_${email_type}.wasm
# witness_${email_type}_${nonce}.wtns
# namecheap_${email_type}_cpp/
# namecheap_${email_type}
# prover-api/
# proofs/
# rapidsnark_proof_${email_type}_${nonce}.json
# rapidsnark_public_${email_type}_${nonce}.json
# received_eml/
# namecheap_${email_type}_${nonce}.eml
# inputs/
# input_namecheap_${email_type}_${nonce}.json
# circom_proofgen.sh
# rapidsnark/
# build/
# prover



# Use the official Rust image as the base image
FROM rust:latest
ARG ZKP2P_BRANCH_NAME=develop
ARG ZKP2P_VERSION=v2.5/v0.2.8
ARG PROVER_API_BRANCH_NAME=richard/namecheap
ARG PAYMENT_TYPE=namecheap
ARG PAYMENT_ROOT_DIR=/root/zk-p2p/circuits-circom/circuits/${PAYMENT_TYPE}
ARG BUILD_DIR=${PAYMENT_ROOT_DIR}/build

# Update the package list and install necessary dependencies
RUN apt-get update && \
apt install -y cmake build-essential pkg-config libssl-dev libgmp-dev libsodium-dev nasm

# Install Node.js 16.x and Yarn
ENV NODE_VERSION=16.19.0
RUN apt install -y curl
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
ENV NVM_DIR=/root/.nvm
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
RUN node --version
RUN npm --version
RUN npm install -g yarn
RUN npm install -g typescript

# Clone and build rapidsnark
RUN git clone https://github.com/Divide-By-0/rapidsnark /root/rapidsnark
WORKDIR /root/rapidsnark
RUN npm install
RUN git submodule init
RUN git submodule update
RUN npx task createFieldSources
RUN npx task buildPistache
RUN npx task buildProver
RUN chmod +x /root/rapidsnark/build/prover
WORKDIR /root/

# TODO: Instead we could just copy the build folder from the local machine
# COPY ./rapidsnark/build /rapidsnark/build

# Clone zk p2p repository at the latest commit and set it as the working directory
RUN git clone https://github.com/zkp2p/zk-p2p -b ${ZKP2P_BRANCH_NAME} /root/zk-p2p
WORKDIR ${PAYMENT_ROOT_DIR}
RUN yarn install
RUN yarn add tsx
WORKDIR /root/zk-p2p/circuits-circom/package
RUN yarn install

# Pull JS .wasm from S3 and place them in js folders
RUN wget -P ${BUILD_DIR}/${PAYMENT_TYPE}_push/${PAYMENT_TYPE}_push_js https://zk-p2p.s3.amazonaws.com/${ZKP2P_VERSION}/${PAYMENT_TYPE}_push/${PAYMENT_TYPE}_push.wasm --quiet

# Pull C .dat from S3 and place them in cpp folders
RUN wget -P ${BUILD_DIR}/${PAYMENT_TYPE}_push/${PAYMENT_TYPE}_push_cpp https://zk-p2p.s3.amazonaws.com/${ZKP2P_VERSION}/${PAYMENT_TYPE}_push/${PAYMENT_TYPE}_push.dat --quiet

# Pull C witness gen binary from S3 and place them in cpp folders
RUN wget -P ${BUILD_DIR}/${PAYMENT_TYPE}_push/${PAYMENT_TYPE}_push_cpp https://zk-p2p.s3.amazonaws.com/${ZKP2P_VERSION}/${PAYMENT_TYPE}_push/${PAYMENT_TYPE}_push --quiet

# Make cpp files executable
RUN chmod +x ${BUILD_DIR}/${PAYMENT_TYPE}_push/${PAYMENT_TYPE}_push_cpp/${PAYMENT_TYPE}_push

# Pull keys from S3
RUN wget -P ${BUILD_DIR}/${PAYMENT_TYPE}_push https://zk-p2p.s3.amazonaws.com/${ZKP2P_VERSION}/${PAYMENT_TYPE}_push/${PAYMENT_TYPE}_push.zkey --quiet

# Clone the prover-api repository at the latest commit and set it as the working directory
RUN git clone --branch ${PROVER_API_BRANCH_NAME} --single-branch https://github.com/zkp2p/prover-api /root/prover-api
WORKDIR /root/prover-api

# Install pytho, pip and requirements to run coordinator.py (Not required for modal)
# RUN apt-get install -y python3 python-is-python3 python3-pip python3-venv
# RUN python3 -m venv /venv
# # Activate the virtual environment and install requirements
# RUN /venv/bin/pip install --no-cache-dir -r /root/prover-api/requirements.txt
# RUN apt-get install vim -y
Empty file added src/namecheap/__init__.py
Empty file.
169 changes: 169 additions & 0 deletions src/namecheap/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import modal
import re
import os
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, status
from typing import Dict

from utils.helpers import fetch_domain_key, validate_dkim, sha256_hash
from utils.prove import run_prove_process
from utils.errors import Errors
from utils.env_utils import read_env_credentials
from utils.file_utils import write_file_to_local, read_proof_from_local
from utils.slack_utils import upload_file_to_slack

load_dotenv('./env') # Load environment variables from .env file

# --------- VALIDATE EMAIL ------------

DOMAIN = 'namecheap.com'
DOMAIN_KEYS = [
{
'selector': 's1',
'key': 'p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4EJ2WbK3G12fhP8hlHBTABlvdbKePJXwux+sjGXRnnoVdGAaw9q9D96qeW3uWqAbBSyPB06w4zTeK1qi7Ar+rBC91zKEiuoi6Rbd8xkDBG1Emo8RMhZjOHer5xl0TobynvYy6J4F/ge4OgA17nNDfc7n2Xg+OOKHVY4dVZfdgNR29eGraxD8X0E2pMBdNgtqKvt6S4irlnEuhvko+Ls3XqBicTnM30QO4ffyIJWlUqHEwVjBUHKXV+/sTif8UecWw2m9uLYlPbeNBAjMcRtmKYC+tKT39laA2mtPuQub9LHtgzkmAXqE9D7uvgc8gEoUgdvQyefKClRR/rKomB9CeQIDAQAB'
}
]
SELECTORS = [dk['selector'] for dk in DOMAIN_KEYS]
# NAME_PATTERN = r"^[A-Z][a-z'’-]+\s([A-Z][a-z'’-]+\s?)+$"
FROM_EMAIL_ADDRESS = 'From: "NameCheap.com Support" <[email protected]>'
EMAIL_SUBJECT = 'Subject: PUSH DOMAIN CONFIRMATION EMAIL - Namecheap.com'
DOCKER_IMAGE_NAME = '0xsachink/zkp2p:modal-namecheap-0.2.8-v1'
STUB_NAME = 'zkp2p-modal-namecheap-0.2.8'

SLACK_TOKEN = os.getenv('SLACK_TOKEN')
CHANNEL_ID = os.getenv('CHANNEL_ID')

Error = Errors()


def alert_on_slack(error_code, email_raw_content="", log_subject=False):

error_message = Error.get_error_message(error_code)
msg = f'Alert: {error_message}. Stub: {STUB_NAME}. Docker image: {DOCKER_IMAGE_NAME}'

response = upload_file_to_slack(
CHANNEL_ID,
SLACK_TOKEN,
msg,
{'file': email_raw_content}
)
return response.status_code

def validate_email(email_raw_content):

print(email_raw_content)

# Ensure the email is from the domain
if not re.search(fr'{FROM_EMAIL_ADDRESS}', email_raw_content):
error_code = Error.ErrorCodes.INVALID_FROM_ADDRESS
alert_on_slack(error_code, email_raw_content)
return False, error_code

# Ensure the email is a send email
if not re.search(fr'{EMAIL_SUBJECT}', email_raw_content):
error_code = Error.ErrorCodes.INVALID_EMAIL_SUBJECT
alert_on_slack(error_code, email_raw_content, log_subject=True)
return False, error_code

# Extract the selector from the email
# Selector is present in this form in the email 's=s1; bh='
match = re.search(r's=(.*?);[\s]*bh=', email_raw_content)

# Validate selector
if not match or match.group(1) not in SELECTORS:
error_code = Error.ErrorCodes.INVALID_SELECTOR
alert_on_slack(error_code, email_raw_content)
return False, error_code

# Validate domain key
# DNS query takes time and slows down the modal. Since this check is already done on the client, we can skip it.
# domain_key = fetch_domain_key(DOMAIN, selector)
# print("Fetched domain_key", domain_key)
# if domain_key == "" or domain_key is None or domain_key != DOMAIN_KEYS[SELECTORS.index(selector)]['key']:
# error_code = Error.ErrorCodes.INVALID_DOMAIN_KEY
# alert_on_slack(error_code, email_raw_content)
# return False, error_code

# Validate the DKIM signature
if not validate_dkim(email_raw_content):
error_code = Error.ErrorCodes.DKIM_VALIDATION_FAILED
alert_on_slack(error_code, email_raw_content)
return False, error_code

return True, ""

# ----------- ENV VARIABLES ------------ (Todo: Clean this)

env_credentials = read_env_credentials('./namecheap/.env.example', './namecheap/.env')
print("env crednetials", env_credentials)


# ----------------- MODAL -----------------

image = modal.Image.from_registry(
DOCKER_IMAGE_NAME,
add_python="3.11"
).pip_install_from_requirements("requirements.txt")
stub = modal.App(name=STUB_NAME, image=image)
credentials_secret = modal.Secret.from_dict(env_credentials)


# ----------------- API -----------------

@stub.function(cpu=48, memory=16000, secrets=[credentials_secret])
@modal.web_endpoint(method="POST")
def genproof_email(email_data: Dict):

email_raw_data = email_data["email"]
payment_type = email_data["payment_type"]
circuit_type = email_data["circuit_type"]
intent_hash = email_data["intent_hash"]

nonce = int(sha256_hash(email_raw_data), 16)

if payment_type == "namecheap":
pass
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=Error.get_error_response(Error.ErrorCodes.INVALID_PAYMENT_TYPE)
)

if circuit_type == "push":
pass
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=Error.get_error_response(Error.ErrorCodes.INVALID_CIRCUIT_TYPE)
)

# Validate email
valid_email, error_code = validate_email(email_raw_data)
if not valid_email:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=Error.get_error_response(error_code)
)

# Write file to local
write_file_to_local(email_raw_data, payment_type, circuit_type, str(nonce))

# Prove
run_prove_process(payment_type, circuit_type, str(nonce), intent_hash, "true")

# Read the proof from local
proof, public_values = read_proof_from_local(payment_type, circuit_type, str(nonce))

if proof == "" or public_values == "":
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=Error.get_error_response(Error.ErrorCodes.PROOF_GEN_FAILED)
)

# Construct a HTTP response
response = {
"proof": proof,
"public_values": public_values
}

return response
5 changes: 5 additions & 0 deletions test_zkemail_prover.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
TEST_EMAIL_PATH = os.getenv("TEST_EMAIL_PATH")
MODAL_ENDPOINT = os.getenv("MODAL_ENDPOINT")

TEST_PAYMENT_TYPE = "namecheap"
TEST_CIRCUIT_TYPE = "push"
TEST_EMAIL_PATH = "./received_eml/namecheap_email.eml"
MODAL_ENDPOINT = "https://zkp2p--zkp2p-modal-namecheap-0-2-8-genproof-email.modal.run"


if __name__ == "__main__":

Expand Down
Loading