├── .dockerignore ├── .env.example ├── .gitbook.yaml ├── .github ├── circuits-runner │ ├── Dockerfile │ └── entrypoint.sh ├── pull_request_template.md └── workflows │ ├── build-fmt.yml │ ├── build-img.yml │ └── unit-tests.yml ├── .gitignore ├── .gitmodules ├── Base.Dockerfile ├── Cargo.lock ├── Cargo.toml ├── Full.Dockerfile ├── IMAP.Dockerfile ├── LICENSE ├── README.md ├── Relayer.Dockerfile ├── SMTP.Dockerfile ├── docker-compose.yaml ├── docs ├── book.toml ├── deployed-contracts.md ├── getting-started.md ├── images │ ├── RelayerInfra.png │ ├── account-recovery-flow.png │ └── architecture-flow.png ├── src │ ├── SUMMARY.md │ ├── circuits.md │ ├── overview.md │ ├── smart_contract.md │ ├── wallet.md │ ├── zk_email.md │ ├── zk_email_wallet.md │ └── zk_regex.md └── upgrade.md ├── kubernetes ├── circuit-test-job.yml ├── cloudbuild-base.yml ├── cloudbuild-relayer.yml ├── cronjob.yml ├── ingress.yml ├── managed-cert.yml ├── relayer.staging.yml └── relayer.yml ├── package.json ├── packages ├── circuits │ ├── README.md │ ├── helpers │ │ ├── email_auth.ts │ │ └── recipient.ts │ ├── package.json │ ├── scripts │ │ ├── dev-setup.ts │ │ └── gen_input.ts │ ├── src │ │ ├── email_auth.circom │ │ ├── email_auth_legacy.circom │ │ ├── email_auth_legacy_template.circom │ │ ├── email_auth_template.circom │ │ ├── email_auth_with_recipient.circom │ │ ├── regexes │ │ │ ├── command.json │ │ │ ├── command_regex.circom │ │ │ ├── forced_subject.json │ │ │ ├── forced_subject_regex.circom │ │ │ ├── invitation_code.json │ │ │ ├── invitation_code_regex.circom │ │ │ ├── invitation_code_with_prefix.json │ │ │ └── invitation_code_with_prefix_regex.circom │ │ └── utils │ │ │ ├── account_salt.circom │ │ │ ├── bytes2ints.circom │ │ │ ├── constants.circom │ │ │ ├── digit2int.circom │ │ │ ├── email_addr_commit.circom │ │ │ ├── email_nullifier.circom │ │ │ ├── hash_sign.circom │ │ │ └── hex2int.circom │ └── tests │ │ ├── circuits │ │ ├── email_auth_with_recipient.circom │ │ ├── test_email_auth.circom │ │ ├── test_email_auth_with_recipient.circom │ │ ├── test_forced_subject_regex.circom │ │ ├── test_invitation_code_regex.circom │ │ └── test_invitation_code_with_prefix_regex.circom │ │ ├── emai_auth_legacy_recipient.test.ts │ │ ├── emai_auth_recipient.test.ts │ │ ├── email_auth.test.ts │ │ ├── email_auth_legacy.test.ts │ │ ├── emails │ │ ├── email_auth_invalid_test1.eml │ │ ├── email_auth_invalid_test2.eml │ │ ├── email_auth_legacy_invalid_test1.eml │ │ ├── email_auth_legacy_invalid_test2.eml │ │ ├── email_auth_legacy_test1.eml │ │ ├── email_auth_legacy_test2.eml │ │ ├── email_auth_legacy_test3.eml │ │ ├── email_auth_legacy_test4.eml │ │ ├── email_auth_legacy_test5.eml │ │ ├── email_auth_legacy_test6.eml │ │ ├── email_auth_test1.eml │ │ ├── email_auth_test2.eml │ │ ├── email_auth_test3.eml │ │ ├── email_auth_test4.eml │ │ ├── email_auth_test5.eml │ │ ├── email_auth_test6.eml │ │ └── email_auth_test7.eml │ │ ├── forced_subject_regex.test.ts │ │ ├── invitation_code_regex.test.ts │ │ └── script │ │ └── send_email.py ├── contracts │ ├── .env.example │ ├── .zksolc-libraries-cache │ │ └── missing_library_dependencies.json │ ├── README.md │ ├── foundry.toml │ ├── package.json │ ├── remappings.txt │ ├── script │ │ ├── BaseDeployScript.sol │ │ ├── ChangeOwners.s.sol │ │ ├── ChangeSignerInECDSAOwnedDKIMRegistry.s.sol │ │ ├── DeployEmailAuthWithCreate2ZKSync.s.sol │ │ ├── DeployEmailSignerFactory.s.sol │ │ ├── RenounceOwners.s.sol │ │ └── Upgrades.s.sol │ ├── src │ │ ├── EmailAuth.sol │ │ ├── EmailSigner.sol │ │ ├── EmailSignerFactory.sol │ │ ├── interfaces │ │ │ ├── IERC1271.sol │ │ │ ├── IEmailTypes.sol │ │ │ ├── IGroth16Verifier.sol │ │ │ └── IVerifier.sol │ │ ├── libraries │ │ │ ├── CommandUtils.sol │ │ │ ├── DecimalUtils.sol │ │ │ └── StringUtils.sol │ │ └── utils │ │ │ ├── ECDSAOwnedDKIMRegistry.sol │ │ │ ├── Groth16Verifier.sol │ │ │ ├── Verifier.sol │ │ │ ├── ZKSyncCreate2Factory.sol │ │ │ └── ZKSyncCreate2FactoryBase.sol │ └── test │ │ ├── ComputeCreate2AddressZKSync.t.sol │ │ ├── DKIMRegistryUpgrade.t.sol │ │ ├── EmailAuth.t.sol │ │ ├── EmailAuthWithUserOverrideableDkim.t.sol │ │ ├── EmailSigner.t.sol │ │ ├── EmailSignerFactory.t.sol │ │ ├── fixtures │ │ ├── EmailAuthMsgFixtures.sol │ │ ├── Fixtures.t.sol │ │ ├── Groth16Verifier.sol │ │ ├── README.md │ │ ├── case1_signHash │ │ │ ├── EmailAuthMsg.json │ │ │ └── raw.eml │ │ ├── case2_signHash │ │ │ ├── EmailAuthMsg.json │ │ │ └── raw.eml │ │ ├── case3_sendEth │ │ │ ├── EmailAuthMsg.json │ │ │ └── raw.eml │ │ └── case4_acceptGuardian │ │ │ ├── EmailAuthMsg.json │ │ │ └── raw.eml │ │ ├── helpers │ │ ├── DeploymentHelper.sol │ │ ├── SignerDeploymentHelper.sol │ │ ├── SignerStructHelper.sol │ │ └── StructHelper.sol │ │ ├── libraries │ │ └── StringUtils │ │ │ ├── StringUtils_hexToBytes.t.sol │ │ │ ├── StringUtils_hexToBytes32.t.sol │ │ │ └── fuzz │ │ │ ├── StringUtils_fuzz_hexToBytes.t.sol │ │ │ └── StringUtils_fuzz_hexToBytes32.t.sol │ │ └── utils │ │ ├── ECDSAOwnedDKIMRegistry │ │ ├── changeSigner.t.sol │ │ ├── computeSignedMsg.t.sol │ │ ├── isDKIMPublicKeyHashValid.t.sol │ │ ├── revokeDKIMPublicKeyHash.t.sol │ │ └── setDKIMPublicKeyHash.t.sol │ │ └── Verifier.t.sol ├── prover │ ├── Dockerfile │ ├── circom_proofgen.sh │ ├── core.py │ ├── local.py │ ├── local_setup.sh │ ├── modal_server.py │ └── requirements.txt └── relayer │ ├── .sqlx │ ├── query-4097c486517af7c203aedf19c79a27c94af3e79af391351d7f54832536b656c3.json │ ├── query-51babdcefaa164b8d2498552e2924c83c6394dc0c6ae546f72a5cb08c2fa5485.json │ ├── query-7aefad132b94f2d7cc3baf2e9cf9ee83b772ea1e914666757da17235fc27fa4c.json │ ├── query-87f63ec8051ab2fcd3fc963b1f3e0ec9d94a412ad0afd53e96c2c42d5c52ef36.json │ ├── query-a8157c6e1edb1f3467740df878063d4727b69fac81fbe51b28c1fce828de51ea.json │ └── query-e884c0a99d266e857015603cdd7f29bfd5adf815dfcc1e2159c61276f262831e.json │ ├── CODING_GUIDELINES.md │ ├── CONTRIBUTING.md │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── config.example.json │ ├── email_templates │ ├── acknowledgement_template.html │ ├── command_template.html │ ├── completion_template.html │ ├── error_for_admin.html │ └── error_template.html │ ├── migrations │ ├── 20241008135456_init.down.sql │ ├── 20241008135456_init.up.sql │ ├── 20241128143748_create_email_auth_messages.down.sql │ └── 20241128143748_create_email_auth_messages.up.sql │ └── src │ ├── abis │ └── mod.rs │ ├── chain.rs │ ├── command.rs │ ├── config.rs │ ├── constants.rs │ ├── dkim.rs │ ├── handler.rs │ ├── mail.rs │ ├── main.rs │ ├── model.rs │ ├── prove.rs │ ├── route.rs │ ├── schema.rs │ └── statics.rs ├── rust-toolchain ├── rustfmt.toml └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | *.log 9 | 10 | # Circuits 11 | packages/circuits/build 12 | packages/circuits/inputs 13 | packages/circuits/*.ptau 14 | *.r1cs 15 | *.sym 16 | *.wasm 17 | *.c 18 | 19 | # Contracts 20 | packages/contracts/broadcast 21 | packages/contracts/cache 22 | packages/contracts/out 23 | packages/contracts/cache 24 | packages/contracts/artifacts 25 | packages/contracts/test/build_integration/*.json 26 | packages/contracts/test/build_integration/*.zkey 27 | packages/contracts/test/build_integration/*.wasm 28 | packages/contracts/test/build_integration/*.txt 29 | packages/contracts/test/EmailAccountRecoveryZkSync 30 | 31 | # NFT Relayer 32 | packages/nft_relayer/sendgrid.env 33 | target 34 | packages/nft_relayer/db/* 35 | packages/nft_relayer/*.db 36 | packages/nft_relayer/received_eml/*.eml 37 | packages/nft_relayer/received_eml/*.json 38 | packages/nft_relayer/proofs 39 | packages/nft_relayer/logs 40 | sql_database.db 41 | .sqlx 42 | .ic.pem 43 | 44 | # Relayer 45 | packages/relayer/sendgrid.env 46 | target 47 | packages/relayer/db/* 48 | packages/relayer/*.db 49 | packages/relayer/received_eml/*.eml 50 | packages/relayer/received_eml/*.json 51 | packages/relayer/proofs 52 | packages/relayer/logs 53 | sql_database.db 54 | .sqlx 55 | .ic.pem 56 | 57 | # Prover 58 | packages/prover/build/* 59 | packages/prover/params/*.zkey 60 | packages/prover/__pycache__/* 61 | 62 | # Subgraphs 63 | packages/subgraph/build 64 | 65 | # Subgraphs 66 | packages/subgraph/build 67 | 68 | # Subgraphs 69 | packages/subgraph/build 70 | 71 | # Mac 72 | .DS_Store 73 | 74 | # mdbook 75 | book 76 | 77 | # Vs code settings 78 | .vscode 79 | 80 | Relayer.Dockerfile -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # SMTP service - for sending emails to users 2 | SMTP_PORT=3000 3 | SMTP_INTERNAL_SERVER_HOST=0.0.0.0 4 | SMTP_INTERNAL_SERVER_PORT=3000 5 | SMTP_DOMAIN_NAME=smtp.gmail.com 6 | SMTP_LOGIN_ID=thezdev1@gmail.com # IMAP login id - usually your email address. 7 | SMTP_LOGIN_PASSWORD="" # IMAP password - usually your email password. 8 | SMTP_MESSAGE_ID_DOMAIN=mail.gmail.com 9 | SMPT_JSON_LOGGER=true 10 | SMTP_EMAIL_SENDER_NAME=Email Wallet 11 | 12 | # Imap service - for receiving emails from users 13 | IMAP_LOGIN_ID=thezdev1@gmail.com 14 | IMAP_LOGIN_PASSWORD="" # IMAP password - usually your email password. 15 | IMAP_DOMAIN_NAME=imap.gmail.com 16 | IMAP_PORT=993 17 | IMAP_AUTH_TYPE=password 18 | # OR 19 | IMAP_CLIENT_ID= # IMAP client id 20 | IMAP_CLIENT_SECRET= # IMAP client secret 21 | IMAP_AUTH_URL= # IMAP auth url 22 | IMAP_TOKEN_URL= # IMAP token url 23 | IMAP_REDIRECT_URL= # IMAP redirect url 24 | IMAP_JSON_LOGGER=true 25 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./ 2 | structure: 3 | readme: README.md 4 | ignore: 5 | - docs/ 6 | - packages/ 7 | 8 | -------------------------------------------------------------------------------- /.github/circuits-runner/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image (Ubuntu 22.04) 2 | FROM ubuntu:22.04 3 | 4 | # Install required packages 5 | RUN apt-get update && apt-get install -y \ 6 | unzip wget curl sudo git build-essential \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | # Install Node.js 20 10 | RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ 11 | apt-get install -y nodejs 12 | 13 | # Install Bun 14 | RUN curl -fsSL https://bun.sh/install | bash && \ 15 | echo 'export PATH="$HOME/.bun/bin:$PATH"' >> ~/.bashrc 16 | 17 | # Install Rust 18 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y 19 | ENV PATH="/root/.cargo/bin:${PATH}" 20 | 21 | # Install Circom 22 | RUN mkdir -p /root/bin && \ 23 | wget https://github.com/iden3/circom/releases/download/v2.1.9/circom-linux-amd64 -O /root/bin/circom && \ 24 | chmod +x /root/bin/circom 25 | 26 | # Set PATH 27 | ENV PATH="/root/bin:${PATH}" 28 | 29 | # Install Yarn 30 | RUN npm install -g yarn 31 | 32 | # Install Git 33 | RUN apt-get update && apt-get install -y git 34 | 35 | # Add entrypoint script 36 | COPY entrypoint.sh /entrypoint.sh 37 | RUN chmod +x /entrypoint.sh 38 | 39 | # Entrypoint 40 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /.github/circuits-runner/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REPO_URL=${REPO_URL:-"https://github.com/your/repo.git"} 4 | COMMIT_HASH=${COMMIT_HASH:-"your_commit_hash"} 5 | 6 | echo "Cloning repository: $REPO_URL" 7 | git clone $REPO_URL repo 8 | cd repo 9 | 10 | echo "Checking out commit: $COMMIT_HASH" 11 | git checkout $COMMIT_HASH 12 | 13 | echo "Installing dependencies" 14 | yarn install --frozen-lockfile 15 | 16 | echo "Running tests" 17 | cd packages/circuits 18 | yarn test-large --no-cache -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | 6 | ## Type of change 7 | 8 | - [ ] Bug fix (non-breaking change which fixes an issue) 9 | - [ ] New feature (non-breaking change which adds functionality) 10 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 11 | - [ ] This change requires a documentation update 12 | 13 | ## How Has This Been Tested? 14 | 15 | 16 | - [ ] Test A 17 | - [ ] Test B 18 | 19 | ## Checklist: 20 | 21 | - [ ] My code follows the style guidelines of this project 22 | - [ ] I have performed a self-review of my own code 23 | - [ ] I have commented my code, particularly in hard-to-understand areas 24 | - [ ] I have made corresponding changes to the documentation 25 | - [ ] My changes generate no new warnings 26 | - [ ] I have added tests that prove my fix is effective or that my feature works 27 | - [ ] New and existing unit tests pass locally with my changes 28 | - [ ] Any dependent changes have been merged and published in downstream modules -------------------------------------------------------------------------------- /.github/workflows/build-fmt.yml: -------------------------------------------------------------------------------- 1 | name: Build and Format 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-and-format: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - run: rustup show 13 | 14 | - name: Install rustfmt and clippy 15 | run: | 16 | rustup component add rustfmt 17 | rustup component add clippy 18 | 19 | - uses: Swatinem/rust-cache@v2 20 | 21 | - name: Install Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 18 25 | 26 | - name: Install yarn 27 | run: npm install -g yarn 28 | 29 | - name: Install dependencies 30 | run: yarn install --frozen-lockfile 31 | 32 | - name: Install Foundry 33 | uses: foundry-rs/foundry-toolchain@v1.2.0 34 | with: 35 | version: nightly-0079a1146b79a4aeda58b0258215bedb1f92700b 36 | 37 | - name: Build contracts 38 | working-directory: packages/contracts 39 | run: yarn build 40 | 41 | - name: Build and check for warnings 42 | env: 43 | RUSTFLAGS: "-D warnings" 44 | run: cargo build --release 45 | 46 | - name: Check formatting 47 | run: cargo fmt -- --check 48 | 49 | - name: Run clippy 50 | run: cargo clippy -- -D warnings 51 | -------------------------------------------------------------------------------- /.github/workflows/build-img.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - refactor 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | 12 | jobs: 13 | build-and-push: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | packages: write 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | 23 | - name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@v2 25 | 26 | - name: Log in to the Container registry 27 | uses: docker/login-action@v2 28 | with: 29 | registry: ${{ env.REGISTRY }} 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Extract metadata (tags, labels) for Docker 34 | id: meta 35 | uses: docker/metadata-action@v4 36 | with: 37 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 38 | tags: | 39 | type=sha,prefix= 40 | type=raw,value=latest 41 | 42 | - name: Build and push Docker image 43 | uses: docker/build-push-action@v4 44 | with: 45 | context: . 46 | file: ./Full.Dockerfile 47 | push: true 48 | tags: ${{ steps.meta.outputs.tags }} 49 | labels: ${{ steps.meta.outputs.labels }} 50 | cache-from: type=gha 51 | cache-to: type=gha,mode=max 52 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | circuits: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | 11 | - name: Authenticate with GCP 12 | uses: google-github-actions/auth@v2 13 | with: 14 | credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} 15 | 16 | - name: 'Set up Cloud SDK' 17 | uses: 'google-github-actions/setup-gcloud@v2' 18 | with: 19 | version: '>= 363.0.0' 20 | 21 | - name: Install gke-gcloud-auth-plugin 22 | run: | 23 | gcloud components install gke-gcloud-auth-plugin -q 24 | 25 | - name: Configure kubectl 26 | run: | 27 | gcloud container clusters get-credentials ${{ secrets.YOUR_CLUSTER_NAME }} --region ${{ secrets.YOUR_REGION }} 28 | 29 | - name: Delete existing job if it exists 30 | run: | 31 | kubectl delete job circuits-job --ignore-not-found=true 32 | 33 | - name: Prepare and Deploy Circuits Job to GKE 34 | env: 35 | REPO_URL: "https://github.com/${{ github.repository }}" 36 | COMMIT_HASH: "${{ github.sha }}" 37 | run: | 38 | # Prepare the job file with substituted environment variables 39 | envsubst < kubernetes/circuit-test-job.yml > kubernetes/circuit-test-job-processed.yml 40 | 41 | # Output the processed job file for verification 42 | cat kubernetes/circuit-test-job-processed.yml 43 | 44 | # Apply the job to GKE 45 | kubectl apply -f kubernetes/circuit-test-job-processed.yml 46 | 47 | - name: Wait for Job to Complete 48 | run: | 49 | kubectl wait --for=condition=complete --timeout=3600s job/circuits-job 50 | 51 | - name: Stream Logs from GKE 52 | run: | 53 | kubectl logs job/circuits-job 54 | 55 | contracts: 56 | name: contracts 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v3 60 | 61 | - name: Install Node.js 62 | uses: actions/setup-node@v3 63 | with: 64 | node-version: 18 65 | 66 | - name: Install yarn 67 | run: npm install -g yarn 68 | 69 | - name: Install dependencies 70 | run: yarn install --frozen-lockfile 71 | 72 | - name: Install Foundry 73 | uses: foundry-rs/foundry-toolchain@v1.2.0 74 | with: 75 | version: nightly-0079a1146b79a4aeda58b0258215bedb1f92700b 76 | 77 | - name: Run tests 78 | working-directory: packages/contracts 79 | run: yarn test 80 | 81 | relayer: 82 | name: relayer 83 | runs-on: ubuntu-latest 84 | steps: 85 | - uses: actions/checkout@v3 86 | 87 | - name: Install Node.js 88 | uses: actions/setup-node@v3 89 | with: 90 | node-version: 18 91 | 92 | - name: Install yarn 93 | run: npm install -g yarn 94 | 95 | - name: Install dependencies 96 | run: yarn install --frozen-lockfile 97 | 98 | - name: Install Foundry 99 | uses: foundry-rs/foundry-toolchain@v1.2.0 100 | with: 101 | version: nightly-0079a1146b79a4aeda58b0258215bedb1f92700b 102 | 103 | - name: Build contracts 104 | working-directory: packages/contracts 105 | run: yarn build 106 | 107 | - name: Run tests 108 | working-directory: packages/relayer 109 | run: cargo test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | *.log 9 | 10 | # Circuits 11 | packages/circuits/build 12 | packages/circuits/inputs 13 | packages/circuits/*.ptau 14 | *.r1cs 15 | *.sym 16 | *.wasm 17 | *.c 18 | *.zkey 19 | 20 | # Contracts 21 | packages/contracts/broadcast 22 | packages/contracts/cache 23 | packages/contracts/out 24 | packages/contracts/cache 25 | packages/contracts/artifacts 26 | packages/contracts/artifacts-zk 27 | packages/contracts/test/build_integration/*.json 28 | packages/contracts/test/build_integration/*.zkey 29 | packages/contracts/test/build_integration/*.wasm 30 | packages/contracts/test/build_integration/*.txt 31 | packages/contracts/test/test-proofs 32 | packages/contracts/deployments 33 | 34 | # Relayer 35 | target 36 | packages/relayer/logs 37 | packages/relayer/config.json 38 | .ic.pem 39 | 40 | # ABIs 41 | packages/relayer/src/abis/* 42 | !packages/relayer/src/abis/mod.rs 43 | 44 | # Prover 45 | packages/prover/build/* 46 | packages/prover/params/*.zkey 47 | packages/prover/__pycache__/* 48 | 49 | # Subgraphs 50 | packages/subgraph/build 51 | 52 | # Subgraphs 53 | packages/subgraph/build 54 | 55 | # Subgraphs 56 | packages/subgraph/build 57 | 58 | # Kubernetes 59 | secrets.yml 60 | 61 | # Mac 62 | .DS_Store 63 | 64 | # mdbook 65 | book 66 | 67 | # Vs code settings 68 | .vscode 69 | 70 | # Editor settings 71 | .idea 72 | 73 | # Kubernetes 74 | secrets.yml 75 | 76 | # For zksync 77 | zkout 78 | .cache 79 | 80 | # Example 81 | example/contracts/artifacts 82 | example/contracts/cache 83 | example/contracts/node_modules 84 | example/scripts/dist 85 | example/contracts/broadcast 86 | 87 | # lock files 88 | bun.lock -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/contracts/lib/forge-std"] 2 | path = packages/contracts/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /Base.Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the latest official Rust image as the base 2 | FROM rust:latest 3 | 4 | # Use bash as the shell 5 | SHELL ["/bin/bash", "-c"] 6 | 7 | # Install NVM, Node.js, and Yarn 8 | RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash \ 9 | && . $HOME/.nvm/nvm.sh \ 10 | && nvm install 18 \ 11 | && nvm alias default 18 \ 12 | && nvm use default \ 13 | && npm install -g yarn 14 | 15 | # Set the working directory 16 | WORKDIR /relayer 17 | 18 | # Pre-configure Git to avoid common issues and increase clone verbosity 19 | RUN git config --global advice.detachedHead false \ 20 | && git config --global core.compression 0 \ 21 | && git config --global protocol.version 2 \ 22 | && git config --global http.postBuffer 1048576000 \ 23 | && git config --global fetch.verbose true 24 | 25 | # Copy project files 26 | COPY . . 27 | 28 | # Remove the packages/relayer directory 29 | RUN rm -rf packages/relayer 30 | 31 | # Install Yarn dependencies with retry mechanism 32 | RUN . $HOME/.nvm/nvm.sh && nvm use default && yarn || \ 33 | (sleep 5 && yarn) || \ 34 | (sleep 10 && yarn) 35 | 36 | # Install Foundry 37 | RUN curl -L https://foundry.paradigm.xyz | bash \ 38 | && source $HOME/.bashrc \ 39 | && foundryup 40 | 41 | # Verify Foundry installation 42 | RUN source $HOME/.bashrc && forge --version 43 | 44 | # Set the working directory for contracts 45 | WORKDIR /relayer/packages/contracts 46 | 47 | # Install Yarn dependencies for contracts 48 | RUN source $HOME/.nvm/nvm.sh && nvm use default && yarn 49 | 50 | # Build the contracts using Foundry 51 | RUN source $HOME/.bashrc && forge build -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["packages/relayer"] 3 | exclude = ["node_modules/*", "packages/relayer/src/abis"] 4 | resolver = "2" 5 | -------------------------------------------------------------------------------- /Full.Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the latest official Rust image as the base 2 | FROM rust:latest 3 | 4 | # Use bash as the shell 5 | SHELL ["/bin/bash", "-c"] 6 | 7 | # Install NVM, Node.js, and Yarn 8 | RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash \ 9 | && . $HOME/.nvm/nvm.sh \ 10 | && nvm install 18 \ 11 | && nvm alias default 18 \ 12 | && nvm use default \ 13 | && npm install -g yarn 14 | 15 | # Set the working directory 16 | WORKDIR /relayer 17 | 18 | # Pre-configure Git to avoid common issues and increase clone verbosity 19 | RUN git config --global advice.detachedHead false \ 20 | && git config --global core.compression 0 \ 21 | && git config --global protocol.version 2 \ 22 | && git config --global http.postBuffer 1048576000 \ 23 | && git config --global fetch.verbose true 24 | 25 | # Copy project files 26 | COPY . . 27 | 28 | # Remove the packages/relayer directory 29 | RUN rm -rf packages/relayer 30 | 31 | # Install Yarn dependencies with retry mechanism 32 | RUN . $HOME/.nvm/nvm.sh && nvm use default && yarn || \ 33 | (sleep 5 && yarn) || \ 34 | (sleep 10 && yarn) 35 | 36 | # Install Foundry 37 | RUN curl -L https://foundry.paradigm.xyz | bash \ 38 | && source $HOME/.bashrc \ 39 | && foundryup 40 | 41 | # Verify Foundry installation 42 | RUN source $HOME/.bashrc && forge --version 43 | 44 | # Set the working directory for contracts 45 | WORKDIR /relayer/packages/contracts 46 | 47 | # Install Yarn dependencies for contracts 48 | RUN source $HOME/.nvm/nvm.sh && nvm use default && yarn 49 | 50 | # Build the contracts using Foundry 51 | RUN source $HOME/.bashrc && forge build 52 | 53 | # Copy the project files 54 | COPY packages/relayer /relayer/packages/relayer 55 | 56 | # Set the working directory for the Rust project 57 | WORKDIR /relayer/packages/relayer 58 | 59 | # Build the Rust project with caching 60 | RUN cargo build 61 | 62 | # Expose port 63 | EXPOSE 4500 64 | 65 | # Set the default command 66 | CMD ["cargo", "run"] 67 | -------------------------------------------------------------------------------- /IMAP.Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Rust image as a base image 2 | FROM rust:latest 3 | 4 | # Set the working directory inside the container 5 | WORKDIR /app 6 | 7 | # Clone the GitHub repository 8 | RUN git clone https://github.com/zkemail/relayer-imap.git 9 | 10 | # Change to the directory of the cloned repository 11 | WORKDIR /app/relayer-imap 12 | 13 | # Build the Rust package 14 | RUN cargo build 15 | 16 | # Specify the command to run when the container starts 17 | CMD ["cargo", "run", "--bin", "relayer-imap"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 zk-email-wallet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Relayer.Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the base image 2 | FROM bisht13/relayer-base 3 | 4 | # Copy the project files 5 | COPY packages/relayer /relayer/packages/relayer 6 | 7 | # Set the working directory for the Rust project 8 | WORKDIR /relayer/packages/relayer 9 | 10 | # Build the Rust project with caching 11 | RUN cargo build 12 | 13 | # Expose port 14 | EXPOSE 4500 15 | 16 | # Set the default command 17 | CMD ["cargo", "run"] 18 | -------------------------------------------------------------------------------- /SMTP.Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Rust image as a base image 2 | FROM rust:latest 3 | 4 | # Set the working directory inside the container 5 | WORKDIR /app 6 | 7 | # Clone the GitHub repository 8 | RUN git clone https://github.com/zkfriendly/relayer-smtp.git 9 | 10 | # Change to the directory of the cloned repository 11 | WORKDIR /app/relayer-smtp 12 | 13 | # Build the Rust package 14 | RUN cargo build 15 | 16 | # Expose port 17 | EXPOSE 3000 18 | 19 | # Specify the command to run when the container starts 20 | CMD ["cargo", "run", "--bin", "relayer-smtp"] -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Docker Compose configuration for local development environment 2 | # This setup provides a playground environment for developing and testing the relayer 3 | # It includes: 4 | # - A PostgreSQL database for storing relayer data 5 | # - The relayer service built from local source code 6 | version: '3.8' 7 | 8 | services: 9 | db: 10 | image: postgres:15 11 | environment: 12 | POSTGRES_USER: relayer 13 | POSTGRES_PASSWORD: relayer_password 14 | POSTGRES_DB: relayer 15 | ports: 16 | - "5432:5432" 17 | volumes: 18 | - postgres_data:/var/lib/postgresql/data 19 | healthcheck: 20 | test: [ "CMD-SHELL", "pg_isready -U relayer" ] 21 | interval: 5s 22 | timeout: 5s 23 | retries: 5 24 | 25 | smtp: 26 | build: 27 | context: . 28 | dockerfile: SMTP.Dockerfile 29 | environment: 30 | - SERVER_HOST=${SMTP_INTERNAL_SERVER_HOST} 31 | - SERVER_PORT=${SMTP_INTERNAL_SERVER_PORT} 32 | - SMTP_DOMAIN_NAME=${SMTP_DOMAIN_NAME} 33 | - SMTP_LOGIN_ID=${SMTP_LOGIN_ID} 34 | - SMTP_LOGIN_PASSWORD=${SMTP_LOGIN_PASSWORD} 35 | - MESSAGE_ID_DOMAIN=${SMTP_MESSAGE_ID_DOMAIN} 36 | - JSON_LOGGER=${SMPT_JSON_LOGGER} 37 | ports: 38 | - "${SMTP_PORT}:${SMTP_INTERNAL_SERVER_PORT}" 39 | healthcheck: 40 | test: [ "CMD", "curl", "-f", "http://localhost:${SMTP_INTERNAL_SERVER_PORT}/api/ping" ] 41 | interval: 1m30s 42 | timeout: 30s 43 | retries: 5 44 | start_period: 30s 45 | 46 | imap: 47 | build: 48 | context: . 49 | dockerfile: IMAP.Dockerfile 50 | environment: 51 | - RELAYER_ENDPOINT=http://host.docker.internal:8000/api/receiveEmail 52 | - IMAP_LOGIN_ID=${IMAP_LOGIN_ID} 53 | - IMAP_LOGIN_PASSWORD=${IMAP_LOGIN_PASSWORD} 54 | - IMAP_DOMAIN_NAME=${IMAP_DOMAIN_NAME} 55 | - IMAP_PORT=${IMAP_PORT} 56 | - AUTH_TYPE=${IMAP_AUTH_TYPE} 57 | - JSON_LOGGER=${IMAP_JSON_LOGGER} 58 | extra_hosts: 59 | - "host.docker.internal:host-gateway" 60 | 61 | volumes: 62 | postgres_data: 63 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Magamedrasul Ibragimov, Sora Suegami, Aayush Gupta, ZK Email Team"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "ZK-Email Wallet" 7 | -------------------------------------------------------------------------------- /docs/deployed-contracts.md: -------------------------------------------------------------------------------- 1 | # Deployed contracts 2 | 3 | | Contract name | Base Sepolia | 4 | |---------|---------| 5 | | ECDSAOwnedDKIMRegistry | 0xb124ED0a80761D3Fa623DC86bAe911a2209C7Cd1 | 6 | | Verifier | 0x72b5EeFf0D56a3EA6436002b14E3289dc96baa22 | 7 | | EmailAuth | 0x9e5a44e7aac3E4db95D182343Ab00983aCAE213f | 8 | | RecoveryController | 0xeE968B19F18e645A0c090571bB7D9b414C215492 | 9 | | SimpleWallet impl | 0x5078b06C7D998ca6e8c7F6c3b43Ca6d07060C8e8 | -------------------------------------------------------------------------------- /docs/images/RelayerInfra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkemail/email-tx-builder/895e1fe943e967b0faab6a476f3b82b37d14300d/docs/images/RelayerInfra.png -------------------------------------------------------------------------------- /docs/images/account-recovery-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkemail/email-tx-builder/895e1fe943e967b0faab6a476f3b82b37d14300d/docs/images/account-recovery-flow.png -------------------------------------------------------------------------------- /docs/images/architecture-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkemail/email-tx-builder/895e1fe943e967b0faab6a476f3b82b37d14300d/docs/images/architecture-flow.png -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [ZK-Email Wallet](./zk_email_wallet.md) 4 | - [Overview](./overview.md) 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/src/circuits.md: -------------------------------------------------------------------------------- 1 | # Circuits 2 | -------------------------------------------------------------------------------- /docs/src/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The ZK-Email Wallet is a non-custodial cryptocurrency wallet managed via email. 4 | 5 | The core component of this wallet is a [smart-contract](https://github.com/zkemail/zk-email-wallet/tree/feat/v1/packages/contracts). The V1 contract supports: 6 | * Transfers of ETH 7 | * Transfers of ERC20 tokens 8 | * Extensions for using external contracts, such as Uniswap, and other functionalities 9 | 10 | --- 11 | 12 | ## ZK-Email 13 | 14 | The wallet is managed by users through email, leveraging **zk-email** or **proof of email** technology for trustless operations. 15 | 16 | ZK-Email is a mechanism that validates the existence of an email message and verifies statements about any data contained within it. This technology can be used to build a trustless bridge from Web2 to Web3, without the need for third-party intermediaries like oracles such as Chainlink. In this context, email serves as a source of oracle data. 17 | 18 | ### Core Technical Components 19 | 20 | The core components of ZK-Email include: 21 | * [ZK-RSA](https://mirror.xyz/privacy-scaling-explorations.eth/mmkG4uB2PR_peGucULAa7zHag-jz1Y5biZH8W6K2LYM) - RSA signature verification in Zero-Knowledge (ZK) 22 | * [ZK-Regex](https://katat.me/blog/ZK+Regex) - Regex pattern matching in ZK 23 | 24 | Most modern mail servers sign email messages with a [DKIM signature](https://www.emailonacid.com/blog/article/email-deliverability/what_is_dkim_everything_you_need_to_know_about_digital_signatures/) (essentially RSA) to mitigate various attack vectors. By verifying the DKIM signature of an email, we can confirm that it was processed by a specific mail server. This alone makes it possible to build a Web2 <-> Web3 bridge. However, in most cases, we need to conceal certain information contained in the message. Removing this information from the message would invalidate the signature, so we use ZK proving systems to check the signature in ZK and keep all data private, except for what needs to be public. This approach enhances privacy and significantly reduces the size of the calldata. 25 | 26 | One challenge is that email messages often don't adhere to a specific format. To effectively retrieve necessary information, we use ZK-Regex. 27 | 28 | For more information on ZK-Email, refer to [this blog post](https://blog.aayushg.com/posts/zkemail/) by Aayush. 29 | 30 | ZK-Email can be used to build various applications, including: 31 | * [Proof of Twitter](https://zkemail.xyz/) - Proving ownership of a Twitter account/username on-chain 32 | * [ZK-P2P](https://github.com/zkp2p/zk-p2p-v1) - A trustless bridge from fiat to crypto 33 | 34 | The ZK-Email Wallet is one such application built using ZK-Email. 35 | -------------------------------------------------------------------------------- /docs/src/smart_contract.md: -------------------------------------------------------------------------------- 1 | # Smart-contract 2 | -------------------------------------------------------------------------------- /docs/src/wallet.md: -------------------------------------------------------------------------------- 1 | # Wallet 2 | -------------------------------------------------------------------------------- /docs/src/zk_email.md: -------------------------------------------------------------------------------- 1 | # ZK-Email 2 | -------------------------------------------------------------------------------- /docs/src/zk_regex.md: -------------------------------------------------------------------------------- 1 | # ZK-Regex 2 | -------------------------------------------------------------------------------- /docs/upgrade.md: -------------------------------------------------------------------------------- 1 | # Upgrading Contracts 2 | 3 | Sometimes, we need to fix problems in our smart contracts. We can do this by upgrading contracts that use ERC1967Proxy. Here's how to do it: 4 | 5 | ## Upgrading the Verifier 6 | 7 | 1. Change the code in this file: 8 | `packages/contracts/src/utils/Verifier.sol` 9 | 10 | 2. Add this to your `.env` file: 11 | ``` 12 | VERIFIER={DEPLOYED_VERIFIER_PROXY_ADDRESS} 13 | ``` 14 | 15 | 3. Run this command: 16 | ``` 17 | source .env 18 | forge script script/Upgrades.s.sol:Upgrades --rpc-url $RPC_URL --chain-id $CHAIN_ID --etherscan-api-key $ETHERSCAN_API_KEY --broadcast --verify -vvvv 19 | ``` 20 | 21 | ## Upgrading Other Contracts 22 | 23 | You can also upgrade ECDSAOwnedDKIMRegistry and UserOverrideableDKIMRegistry: 24 | 25 | ### For ECDSAOwnedDKIMRegistry: 26 | 1. Change the code in: 27 | `packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol` 28 | 29 | 2. Add to `.env`: 30 | ``` 31 | ECDSA_DKIM={DEPLOYED_ECDSA_DKIM_PROXY_ADDRESS} 32 | ``` 33 | 34 | ### For UserOverrideableDKIMRegistry: 35 | 1. Change the code in: 36 | `@zk-email/contracts/UserOverrideableDKIMRegistry.sol` 37 | 38 | 2. Add to `.env`: 39 | ``` 40 | DKIM={DEPLOYED_USEROVERRIDEABLE_DKIM_PROXY_ADDRESS} 41 | ``` 42 | 43 | Remember to be careful when changing these contracts. Always test your changes before using them. 44 | -------------------------------------------------------------------------------- /kubernetes/circuit-test-job.yml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: circuits-job 5 | spec: 6 | ttlSecondsAfterFinished: 60 # Delete automatically 1 minute after completion 7 | template: 8 | spec: 9 | nodeSelector: 10 | cloud.google.com/machine-family: c3 11 | containers: 12 | - name: circuits-runner 13 | image: wshino/circuits-runner:test5 14 | imagePullPolicy: Always # Always pull the latest image 15 | env: 16 | - name: REPO_URL 17 | value: "${REPO_URL}" 18 | - name: COMMIT_HASH 19 | value: "${COMMIT_HASH}" 20 | resources: 21 | requests: 22 | cpu: "16" 23 | memory: "64Gi" 24 | limits: 25 | cpu: "16" 26 | memory: "64Gi" 27 | restartPolicy: Never -------------------------------------------------------------------------------- /kubernetes/cloudbuild-base.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | # Build the base container image 3 | - name: 'gcr.io/cloud-builders/docker' 4 | args: 5 | [ 6 | 'build', 7 | '-t', 8 | 'us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer-base:v1', 9 | '-f', 10 | 'Base.Dockerfile', 11 | '.', 12 | ] 13 | # Push the base container image to Artifact Registry 14 | - name: 'gcr.io/cloud-builders/docker' 15 | args: 16 | [ 17 | 'push', 18 | 'us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer-base:v1', 19 | ] 20 | -------------------------------------------------------------------------------- /kubernetes/cloudbuild-relayer.yml: -------------------------------------------------------------------------------- 1 | options: 2 | machineType: 'N1_HIGHCPU_32' 3 | steps: 4 | # Build the base container image 5 | - name: 'gcr.io/cloud-builders/docker' 6 | args: 7 | [ 8 | 'build', 9 | '-t', 10 | 'us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer:v2', 11 | '-f', 12 | 'Relayer.Dockerfile', 13 | '.', 14 | ] 15 | # Push the base container image to Artifact Registry 16 | - name: 'gcr.io/cloud-builders/docker' 17 | args: 18 | [ 19 | 'push', 20 | 'us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer:v2', 21 | ] 22 | -------------------------------------------------------------------------------- /kubernetes/cronjob.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: cronjob-service-account 5 | namespace: email-tx-auth 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: Role 9 | metadata: 10 | namespace: email-tx-auth 11 | name: deployment-restart-role 12 | rules: 13 | - apiGroups: ["apps", "extensions"] 14 | resources: ["deployments"] 15 | verbs: ["get", "list", "watch", "update", "patch"] 16 | --- 17 | apiVersion: rbac.authorization.k8s.io/v1 18 | kind: RoleBinding 19 | metadata: 20 | name: deployment-restart-rolebinding 21 | namespace: email-tx-auth 22 | subjects: 23 | - kind: ServiceAccount 24 | name: cronjob-service-account 25 | namespace: email-tx-auth 26 | roleRef: 27 | kind: Role 28 | name: deployment-restart-role 29 | apiGroup: rbac.authorization.k8s.io 30 | --- 31 | apiVersion: batch/v1 32 | kind: CronJob 33 | metadata: 34 | name: restart-deployment 35 | namespace: email-tx-auth 36 | spec: 37 | schedule: "0 * * * *" 38 | jobTemplate: 39 | spec: 40 | template: 41 | spec: 42 | serviceAccountName: cronjob-service-account 43 | containers: 44 | - name: kubectl 45 | image: bitnami/kubectl:latest 46 | command: 47 | - /bin/sh 48 | - -c 49 | - | 50 | kubectl rollout restart deployment relayer-imap --namespace email-tx-auth 51 | restartPolicy: OnFailure 52 | -------------------------------------------------------------------------------- /kubernetes/ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: managed-cert-ingress-email-auth 5 | namespace: email-tx-auth 6 | annotations: 7 | kubernetes.io/ingress.global-static-ip-name: generic-relayer 8 | networking.gke.io/managed-certificates: managed-cert-email-tx-auth 9 | ingressClassName: "gce" 10 | spec: 11 | rules: 12 | - host: relayer.zk.email 13 | http: 14 | paths: 15 | - pathType: Prefix 16 | path: / 17 | backend: 18 | service: 19 | name: relayer-svc-email-tx-auth 20 | port: 21 | number: 443 22 | -------------------------------------------------------------------------------- /kubernetes/managed-cert.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.gke.io/v1 2 | kind: ManagedCertificate 3 | metadata: 4 | name: managed-cert-email-tx-auth 5 | namespace: email-tx-auth 6 | spec: 7 | domains: 8 | - relayer.zk.email 9 | -------------------------------------------------------------------------------- /kubernetes/relayer.staging.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: relayer-config-email-auth 5 | namespace: ar-base-sepolia-staging 6 | labels: 7 | app: relayer 8 | data: 9 | EMAIL_ACCOUNT_RECOVERY_VERSION_ID: "" 10 | CHAIN_RPC_PROVIDER: "" 11 | CHAIN_RPC_EXPLORER: "" 12 | CHAIN_ID: "" 13 | WEB_SERVER_ADDRESS: "" 14 | REGEX_JSON_DIR_PATH: "" 15 | EMAIL_TEMPLATES_PATH: "" 16 | CANISTER_ID: "" 17 | IC_REPLICA_URL: "" 18 | JSON_LOGGER: "" 19 | PEM_PATH: "" 20 | SMTP_SERVER: "" 21 | 22 | --- 23 | apiVersion: v1 24 | kind: Secret 25 | metadata: 26 | name: relayer-secret-email-auth 27 | namespace: ar-base-sepolia-staging 28 | labels: 29 | app: relayer 30 | type: Opaque 31 | data: 32 | PRIVATE_KEY: 33 | DATABASE_URL: 34 | PROVER_ADDRESS: 35 | ICPEM: 36 | ERROR_EMAIL_ADDR: 37 | --- 38 | apiVersion: v1 39 | kind: Secret 40 | metadata: 41 | name: relayer-smtp-secret 42 | namespace: ar-base-sepolia-staging 43 | labels: 44 | app: relayer 45 | type: Opaque 46 | data: 47 | SMTP_LOGIN_ID: 48 | SMTP_LOGIN_PASSWORD: 49 | SMTP_DOMAIN_NAME: 50 | SERVER_HOST: 51 | SERVER_PORT: 52 | JSON_LOGGER: 53 | 54 | --- 55 | apiVersion: v1 56 | kind: Secret 57 | metadata: 58 | name: relayer-imap-secret 59 | namespace: ar-base-sepolia-staging 60 | labels: 61 | app: relayer 62 | type: Opaque 63 | data: 64 | RELAYER_ENDPOINT: 65 | IMAP_LOGIN_ID: 66 | IMAP_LOGIN_PASSWORD: 67 | IMAP_PORT: 68 | IMAP_DOMAIN_NAME: 69 | SERVER_HOST: 70 | AUTH_TYPE: 71 | JSON_LOGGER: 72 | 73 | --- 74 | apiVersion: apps/v1 75 | kind: Deployment 76 | metadata: 77 | name: relayer-email-auth 78 | namespace: ar-base-sepolia-staging 79 | labels: 80 | app: relayer 81 | spec: 82 | selector: 83 | matchLabels: 84 | app: relayer 85 | template: 86 | metadata: 87 | labels: 88 | app: relayer 89 | spec: 90 | containers: 91 | - name: relayer-container 92 | image: us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer:v2 93 | ports: 94 | - containerPort: 4500 95 | envFrom: 96 | - configMapRef: 97 | name: relayer-config-email-auth 98 | - secretRef: 99 | name: relayer-secret-email-auth 100 | livenessProbe: 101 | httpGet: 102 | path: /api/echo 103 | port: 4500 104 | initialDelaySeconds: 60 105 | periodSeconds: 30 106 | readinessProbe: 107 | httpGet: 108 | path: /api/echo 109 | port: 4500 110 | initialDelaySeconds: 60 111 | periodSeconds: 30 112 | volumeMounts: 113 | - name: pem-volume 114 | mountPath: "/relayer/packages/relayer/.ic.pem" 115 | subPath: ".ic.pem" 116 | - name: smtp-container 117 | image: bisht13/relayer-smtp-new:latest 118 | ports: 119 | - containerPort: 8080 120 | envFrom: 121 | - secretRef: 122 | name: relayer-smtp-secret 123 | - name: imap-container 124 | image: bisht13/relayer-imap-new:latest 125 | envFrom: 126 | - secretRef: 127 | name: relayer-imap-secret 128 | volumes: 129 | - name: pem-volume 130 | secret: 131 | secretName: relayer-secret-email-auth 132 | items: 133 | - key: ICPEM 134 | path: ".ic.pem" 135 | --- 136 | apiVersion: v1 137 | kind: Service 138 | metadata: 139 | name: relayer-svc-email-auth 140 | namespace: ar-base-sepolia-staging 141 | spec: 142 | selector: 143 | app: relayer 144 | ports: 145 | - protocol: TCP 146 | port: 443 147 | targetPort: 4500 148 | type: ClusterIP 149 | 150 | --- 151 | apiVersion: v1 152 | kind: Service 153 | metadata: 154 | name: relayer-smtp-svc 155 | namespace: ar-base-sepolia-staging 156 | spec: 157 | selector: 158 | app: relayer 159 | ports: 160 | - protocol: TCP 161 | port: 443 162 | targetPort: 8080 163 | type: ClusterIP 164 | -------------------------------------------------------------------------------- /kubernetes/relayer.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: relayer-email-tx-auth 5 | namespace: email-tx-auth 6 | labels: 7 | app: relayer 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: relayer 13 | template: 14 | metadata: 15 | labels: 16 | app: relayer 17 | spec: 18 | containers: 19 | - name: relayer-container 20 | image: bisht13/generic-relayer-7:latest 21 | ports: 22 | - containerPort: 4500 23 | resources: 24 | requests: 25 | cpu: 2 26 | memory: 4Gi 27 | limits: 28 | cpu: 4 29 | memory: 8Gi 30 | livenessProbe: 31 | httpGet: 32 | path: /api/healthz 33 | port: 4500 34 | initialDelaySeconds: 60 35 | periodSeconds: 30 36 | readinessProbe: 37 | httpGet: 38 | path: /api/healthz 39 | port: 4500 40 | initialDelaySeconds: 60 41 | periodSeconds: 30 42 | volumeMounts: 43 | - name: pem-volume 44 | mountPath: "/relayer/packages/relayer/.ic.pem" 45 | subPath: ".ic.pem" 46 | - name: secret-volume 47 | mountPath: "/relayer/packages/relayer/config.json" 48 | subPath: "config.json" 49 | volumes: 50 | - name: secret-volume 51 | secret: 52 | secretName: relayer-secret-email-tx-auth 53 | items: 54 | - key: config.json 55 | path: config.json 56 | - name: pem-volume 57 | secret: 58 | secretName: relayer-secret-email-tx-auth 59 | items: 60 | - key: ICPEM 61 | path: ".ic.pem" 62 | 63 | --- 64 | apiVersion: apps/v1 65 | kind: Deployment 66 | metadata: 67 | name: relayer-smtp 68 | namespace: email-tx-auth 69 | labels: 70 | app: relayer-smtp 71 | spec: 72 | replicas: 1 # Fixed replica count 73 | selector: 74 | matchLabels: 75 | app: relayer-smtp 76 | template: 77 | metadata: 78 | labels: 79 | app: relayer-smtp 80 | spec: 81 | containers: 82 | - name: smtp-container 83 | image: bisht13/relayer-smtp:latest 84 | ports: 85 | - containerPort: 8080 86 | envFrom: 87 | - secretRef: 88 | name: relayer-smtp-secret 89 | 90 | --- 91 | apiVersion: apps/v1 92 | kind: Deployment 93 | metadata: 94 | name: relayer-imap 95 | namespace: email-tx-auth 96 | labels: 97 | app: relayer-imap 98 | spec: 99 | replicas: 1 # Fixed replica count 100 | selector: 101 | matchLabels: 102 | app: relayer-imap 103 | template: 104 | metadata: 105 | labels: 106 | app: relayer-imap 107 | spec: 108 | containers: 109 | - name: imap-container 110 | image: bisht13/relayer-imap-new:latest 111 | envFrom: 112 | - secretRef: 113 | name: relayer-imap-secret 114 | 115 | --- 116 | apiVersion: v1 117 | kind: Service 118 | metadata: 119 | name: relayer-svc-email-tx-auth 120 | namespace: email-tx-auth 121 | spec: 122 | selector: 123 | app: relayer 124 | ports: 125 | - protocol: TCP 126 | port: 443 127 | targetPort: 4500 128 | type: ClusterIP 129 | 130 | --- 131 | apiVersion: v1 132 | kind: Service 133 | metadata: 134 | name: relayer-smtp-svc 135 | namespace: email-tx-auth 136 | spec: 137 | selector: 138 | app: relayer-smtp 139 | ports: 140 | - protocol: TCP 141 | port: 443 142 | targetPort: 8080 143 | type: ClusterIP 144 | 145 | --- 146 | apiVersion: autoscaling/v2 147 | kind: HorizontalPodAutoscaler 148 | metadata: 149 | name: relayer-email-tx-auth-hpa 150 | namespace: email-tx-auth 151 | spec: 152 | scaleTargetRef: 153 | apiVersion: apps/v1 154 | kind: Deployment 155 | name: relayer-email-tx-auth 156 | minReplicas: 1 157 | maxReplicas: 10 158 | metrics: 159 | - type: Resource 160 | resource: 161 | name: cpu 162 | target: 163 | type: Utilization 164 | averageUtilization: 50 165 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@zk-email/email-tx-builder", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Smart contracts to auth messages via emails", 6 | "engines": { 7 | "node": ">=18" 8 | }, 9 | "scripts": { 10 | "test": "yarn workspaces -pt run test", 11 | "dev-setup": "yarn workspace @email-wallet/circom dev-setup", 12 | "prettier": "prettier --write \"packages/contracts/**/*.{js,jsx,ts,tsx,sol}\"" 13 | }, 14 | "workspaces": [ 15 | "packages/*" 16 | ], 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@babel/core": "^7.22.5", 20 | "@babel/plugin-transform-modules-commonjs": "^7.22.15", 21 | "@babel/preset-env": "^7.22.2", 22 | "@babel/preset-react": "^7.22.0", 23 | "@babel/preset-typescript": "^7.21.5", 24 | "@types/jest": "^29.5.4", 25 | "babel-jest": "^29.5.0", 26 | "babel-preset-jest": "^29.5.0", 27 | "jest": "^29.5.0", 28 | "prettier": "^3.0.0", 29 | "prettier-plugin-solidity": "^1.1.3", 30 | "ts-jest": "^29.1.1", 31 | "typescript": "^4.5.4" 32 | }, 33 | "babel": { 34 | "presets": [ 35 | [ 36 | "@babel/preset-env" 37 | ], 38 | "@babel/preset-typescript", 39 | [ 40 | "jest" 41 | ] 42 | ] 43 | }, 44 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 45 | } 46 | -------------------------------------------------------------------------------- /packages/circuits/helpers/email_auth.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { promisify } from "util"; 3 | const relayerUtils = require("@zk-email/relayer-utils"); 4 | 5 | export async function genEmailCircuitInput( 6 | emailFilePath: string, 7 | accountCode: string, 8 | options?: { 9 | shaPrecomputeSelector?: string; 10 | maxHeaderLength?: number; 11 | maxBodyLength?: number; 12 | ignoreBodyHashCheck?: boolean; 13 | } 14 | ): Promise<{ 15 | padded_header: string[]; 16 | public_key: string[]; 17 | signature: string[]; 18 | padded_header_len: string; 19 | account_code: string; 20 | from_addr_idx: number; 21 | subject_idx: number; 22 | domain_idx: number; 23 | timestamp_idx: number; 24 | code_idx: number; 25 | body_hash_idx: number; 26 | precomputed_sha: string[]; 27 | padded_body: string[]; 28 | padded_body_len: string; 29 | command_idx: number; 30 | padded_cleaned_body: string[]; 31 | }> { 32 | const emailRaw = await promisify(fs.readFile)(emailFilePath, "utf8"); 33 | const jsonStr = await relayerUtils.genEmailCircuitInput( 34 | emailRaw, 35 | accountCode, 36 | options 37 | ); 38 | return JSON.parse(jsonStr); 39 | } 40 | -------------------------------------------------------------------------------- /packages/circuits/helpers/recipient.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { promisify } from "util"; 3 | import path from "path"; 4 | const relayerUtils = require("@zk-email/relayer-utils"); 5 | 6 | export async function genRecipientInputLegacy(emailFilePath: string): Promise<{ 7 | subject_email_addr_idx: number; 8 | rand: string; 9 | }> { 10 | const emailRaw = await promisify(fs.readFile)(emailFilePath, "utf8"); 11 | const parsedEmail = await relayerUtils.parseEmail(emailRaw); 12 | const subjectEmailIdxes = relayerUtils.extractSubjectAllIdxes( 13 | parsedEmail.canonicalizedHeader 14 | )[0]; 15 | const subject = parsedEmail.canonicalizedHeader.slice( 16 | subjectEmailIdxes[0], 17 | subjectEmailIdxes[1] 18 | ); 19 | let subjectEmailAddrIdx = 0; 20 | try { 21 | subjectEmailAddrIdx = relayerUtils.extractEmailAddrIdxes(subject)[0][0]; 22 | } catch (e) { 23 | console.log("No email address in subject"); 24 | subjectEmailAddrIdx = 0; 25 | } 26 | const rand = relayerUtils.extractRandFromSignature(parsedEmail.signature); 27 | return { 28 | subject_email_addr_idx: subjectEmailAddrIdx, 29 | rand: rand, 30 | }; 31 | } 32 | 33 | export async function genRecipientInput(command: string, signature: string): Promise<{ 34 | command_email_addr_idx: number; 35 | rand: string; 36 | }> { 37 | let commandEmailAddrIdx = 0; 38 | try { 39 | commandEmailAddrIdx = relayerUtils.extractEmailAddrIdxes(command)[0][0]; 40 | } catch (e) { 41 | console.log("No email address in command"); 42 | } 43 | const rand = relayerUtils.extractRandFromSignature(signature); 44 | return { 45 | command_email_addr_idx: commandEmailAddrIdx, 46 | rand: rand, 47 | }; 48 | } -------------------------------------------------------------------------------- /packages/circuits/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@zk-email/email-tx-builder-circom", 3 | "license": "MIT", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "mkdir -p build && circom src/email_auth.circom --r1cs --wasm --sym --c -l ../../node_modules -o ./build", 7 | "build-legacy": "mkdir -p build && circom src/email_auth_legacy.circom --r1cs --wasm --sym --c -l ../../node_modules -o ./build", 8 | "build-recipient": "mkdir -p build && circom src/email_auth_with_recipient.circom --r1cs --wasm --sym --c -l ../../node_modules -o ./build", 9 | "dev-setup": "NODE_OPTIONS=--max_old_space_size=16384 npx ts-node scripts/dev-setup.ts --output ./build", 10 | "gen-input": "NODE_OPTIONS=--max_old_space_size=8192 npx ts-node scripts/gen_input.ts", 11 | "test": "NODE_OPTIONS=--max_old_space_size=8192 jest", 12 | "test-large": "NODE_OPTIONS=--max_old_space_size=32768 jest" 13 | }, 14 | "dependencies": { 15 | "@zk-email/circuits": "=6.3.2", 16 | "@zk-email/relayer-utils": "=0.3.7", 17 | "@zk-email/zk-regex-circom": "=2.3.1", 18 | "commander": "^12.1.0", 19 | "snarkjs": "=0.7.5" 20 | }, 21 | "devDependencies": { 22 | "@babel/preset-env": "^7.22.20", 23 | "@babel/preset-typescript": "^7.23.0", 24 | "@types/jest": "^29.5.4", 25 | "chai": "^4.3.7", 26 | "circom_tester": "^0.0.20", 27 | "circomlib": "^2.0.5", 28 | "circomlibjs": "^0.1.2", 29 | "ffjavascript": "^0.3.1", 30 | "jest": "^29.5.0", 31 | "mocha": "^10.2.0", 32 | "ts-jest": "^29.1.1", 33 | "typescript": "^4.8.3" 34 | }, 35 | "babel": { 36 | "presets": [ 37 | [ 38 | "@babel/preset-env" 39 | ], 40 | "@babel/preset-typescript", 41 | [ 42 | "jest" 43 | ] 44 | ] 45 | }, 46 | "files": [ 47 | "/src", 48 | "/helpers", 49 | "/scripts", 50 | "package.json", 51 | "README.md" 52 | ] 53 | } -------------------------------------------------------------------------------- /packages/circuits/scripts/gen_input.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This script is for generating input for the main circuit. 4 | * 5 | */ 6 | 7 | 8 | import { program } from "commander"; 9 | import fs from "fs"; 10 | import { promisify } from "util"; 11 | import { genEmailCircuitInput } from "../helpers/email_auth"; 12 | import path from "path"; 13 | const snarkjs = require("snarkjs"); 14 | 15 | program 16 | .requiredOption( 17 | "--email-file ", 18 | "Path to an email file" 19 | ) 20 | .requiredOption( 21 | "--account-code ", 22 | "The account code for the sender's email address" 23 | ) 24 | .requiredOption( 25 | "--input-file ", 26 | "Path of a json file to write the generated input" 27 | ) 28 | .option("--silent", "No console logs") 29 | .option("--legacy", "Use a legacy circuit") 30 | .option("--prove", "Also generate proof"); 31 | 32 | program.parse(); 33 | const args = program.opts(); 34 | 35 | function log(...message: any) { 36 | if (!args.silent) { 37 | console.log(...message); 38 | } 39 | } 40 | 41 | async function generate() { 42 | if (!args.inputFile.endsWith(".json")) { 43 | throw new Error("--input file path arg must end with .json"); 44 | } 45 | 46 | if (args.legacy) { 47 | log("Generating Inputs for:", args); 48 | 49 | const circuitInputs = await genEmailCircuitInput(args.emailFile, args.accountCode, { 50 | maxHeaderLength: 1024, 51 | ignoreBodyHashCheck: true 52 | }); 53 | log("\n\nGenerated Inputs:", circuitInputs, "\n\n"); 54 | 55 | await promisify(fs.writeFile)(args.inputFile, JSON.stringify(circuitInputs, null, 2)); 56 | 57 | log("Inputs written to", args.inputFile); 58 | 59 | if (args.prove) { 60 | const dir = path.dirname(args.inputFile); 61 | const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, path.join(dir, "email_auth_legacy.wasm"), path.join(dir, "email_auth_legacy.zkey"), console); 62 | await promisify(fs.writeFile)(path.join(dir, "email_auth_legacy_proof.json"), JSON.stringify(proof, null, 2)); 63 | await promisify(fs.writeFile)(path.join(dir, "email_auth_legacy_public.json"), JSON.stringify(publicSignals, null, 2)); 64 | log("✓ Proof for email auth legacy circuit generated"); 65 | } 66 | } else { 67 | log("Generating Inputs for:", args); 68 | 69 | const { subject_idx, ...circuitInputs } = await genEmailCircuitInput(args.emailFile, args.accountCode, { 70 | maxHeaderLength: 1024, 71 | maxBodyLength: 1024, 72 | ignoreBodyHashCheck: false, 73 | shaPrecomputeSelector: '(
]*>)' 74 | }); 75 | console.log(circuitInputs.padded_body.length); 76 | log("\n\nGenerated Inputs:", circuitInputs, "\n\n"); 77 | 78 | await promisify(fs.writeFile)(args.inputFile, JSON.stringify(circuitInputs, null, 2)); 79 | 80 | log("Inputs written to", args.inputFile); 81 | 82 | if (args.prove) { 83 | const dir = path.dirname(args.inputFile); 84 | const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, path.join(dir, "email_auth.wasm"), path.join(dir, "email_auth.zkey"), console); 85 | await promisify(fs.writeFile)(path.join(dir, "email_auth_proof.json"), JSON.stringify(proof, null, 2)); 86 | await promisify(fs.writeFile)(path.join(dir, "email_auth_public.json"), JSON.stringify(publicSignals, null, 2)); 87 | log("✓ Proof for email auth circuit generated"); 88 | } 89 | } 90 | process.exit(0); 91 | } 92 | 93 | generate().catch((err) => { 94 | console.error("Error generating inputs", err); 95 | process.exit(1); 96 | }); 97 | -------------------------------------------------------------------------------- /packages/circuits/src/email_auth.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.6; 2 | 3 | include "./email_auth_template.circom"; 4 | 5 | component main = EmailAuth(121, 17, 1024, 1024, 605, 0, 1); -------------------------------------------------------------------------------- /packages/circuits/src/email_auth_legacy.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.6; 2 | 3 | include "./email_auth_legacy_template.circom"; 4 | 5 | component main = EmailAuthLegacy(121, 17, 1024, 605, 0); -------------------------------------------------------------------------------- /packages/circuits/src/email_auth_with_recipient.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.6; 2 | 3 | include "./email_auth_template.circom"; 4 | 5 | component main = EmailAuth(121, 17, 1024, 1024, 605, 1, 1); -------------------------------------------------------------------------------- /packages/circuits/src/regexes/command.json: -------------------------------------------------------------------------------- 1 | { 2 | "parts": [ 3 | { 4 | "is_public": false, 5 | "regex_def": "(
]*>)" 6 | }, 7 | { 8 | "is_public": true, 9 | "regex_def": "[^<>/]+" 10 | }, 11 | { 12 | "is_public": false, 13 | "regex_def": "
" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /packages/circuits/src/regexes/forced_subject.json: -------------------------------------------------------------------------------- 1 | { 2 | "parts": [ 3 | { 4 | "is_public": false, 5 | "regex_def": "(\r\n|^)subject:((re: )|(RE: )|(Re: )|(fwd: )|(FWD: )|(Fwd: ))*\\[Reply Needed\\][^\r\n]*\r\n" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /packages/circuits/src/regexes/invitation_code.json: -------------------------------------------------------------------------------- 1 | { 2 | "parts": [ 3 | { 4 | "is_public": false, 5 | "regex_def": "( )(c|C)ode( )?" 6 | }, 7 | { 8 | "is_public": true, 9 | "regex_def": "(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)+" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /packages/circuits/src/regexes/invitation_code_with_prefix.json: -------------------------------------------------------------------------------- 1 | { 2 | "parts": [ 3 | { 4 | "is_public": true, 5 | "regex_def": "( )(c|C)ode( )?(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)+" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /packages/circuits/src/utils/account_salt.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.6; 2 | 3 | include "circomlib/circuits/poseidon.circom"; 4 | 5 | template AccountSalt(num_ints) { 6 | signal input email_addr_ints[num_ints]; 7 | signal input account_code; 8 | 9 | signal output salt; 10 | 11 | component poseidon = Poseidon(2+num_ints); 12 | for(var i=0; i> 1; 11 | if(k % 2 == 1) { 12 | k2_chunked_size += 1; 13 | } 14 | signal output sign_ints[k2_chunked_size]; 15 | 16 | for(var i = 0; i < k2_chunked_size; i++) { 17 | if(i==k2_chunked_size-1 && k % 2 == 1) { 18 | sign_ints[i] <== signature[2*i]; 19 | } else { 20 | sign_ints[i] <== signature[2*i] + (1< 29 | Received: from mail-sor-f41.google.com (mail-sor-f41.google.com. [209.85.220.41]) 30 | by mx.google.com with SMTPS id m12-20020a0dca0c000000b0058460bc906bsor2973516ywd.11.2023.09.17.15.30.12 31 | for 32 | (Google Transport Security); 33 | Sun, 17 Sep 2023 15:30:13 -0700 (PDT) 34 | Received-SPF: pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) client-ip=209.85.220.41; 35 | Authentication-Results: mx.google.com; 36 | dkim=pass header.i=@gmail.com header.s=20230601 header.b=RHBgQbCx; 37 | spf=pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=suegamisora@gmail.com; 38 | dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com 39 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 40 | d=gmail.com; s=20230601; t=1694989812; x=1695594612; dara=google.com; 41 | h=to:subject:message-id:date:from:mime-version:from:to:cc:subject 42 | :date:message-id:reply-to; 43 | bh=BWETwQ9JDReS4GyR2v2TTR8Bpzj9ayumsWQJ3q7vehs=; 44 | b=RHBgQbCxhob0RHu/S1J4YdDG2aP1fRDJR6tQrN0wyfVHesARR9NhQIJ/lyUehRTLu/ 45 | wfbHY5RGwSv/87X2iX/p94rGXKJ7R9QFWuuHi0owVNLw2cDNL7vNpT71IgujrHGBTQlB 46 | gvGFd+Gp7/X7jY5DBte4MCi+syCGrf/v+qFsTQ/XUEKpsUu7beMEjTz879yZtdr7LVAE 47 | 9bcsAe18sfX2li2ubRqdV/EPg6sWXIo19jtRTEZ2q1B1i6jxWmaUB+/Th+F8VUXHcJ2a 48 | GluecEG4pSAeP/a64bbpkmOxdMx4bb5A4jyWr1q+TyeqfHXxOkHf8c7+gMHhzGp/tJiu 49 | d12g== 50 | X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 51 | d=1e100.net; s=20230601; t=1694989812; x=1695594612; 52 | h=to:subject:message-id:date:from:mime-version:x-gm-message-state 53 | :from:to:cc:subject:date:message-id:reply-to; 54 | bh=BWETwQ9JDReS4GyR2v2TTR8Bpzj9ayumsWQJ3q7vehs=; 55 | b=AFkTp60CFyHReF9haHoUVGsSO0+uQZWNhP/SdjYaR1hKqd7FDcv9QMWyo7Ruo2q668 56 | AtM04vsAxL3KPbCh8l7ghxonJlfpIdwnJFFwiIkAaymLN2nfa/iA3yTqLPsZc21+DKWI 57 | 4L161IUCshKj2K2/ErZr/cslf7g9X4vA5z6vaQhUJgB1JL4dlx5eWwgldtAaL/5jSQAg 58 | gThwEVUU6QqljCd1eVCyG5CcQGfJc6BuHNr3beq+4WMlvll97o5n3cpa+QFJJejXGoRn 59 | n0QiaK8Xki3ajWSdhu+K2y+Y4Cd9KBp4utQhZ4Ed74b0o4JMmdU+UOqcxvQs3mRrqGTn 60 | YsQA== 61 | X-Gm-Message-State: AOJu0Yy/ikSKXhxWeOjidkPu0ZD7ioRrSKLdlg8Ngfo45pZ1AlVfsXzW 62 | oHfddvOgIXyqeA8uYaGcdfJtfrWelHBChkfXWe8TvYbSGTp2mw== 63 | X-Google-Smtp-Source: AGHT+IFwSk4ZOp7bjwU0GeSyGMnjOk59tjDeY5XcQ79bFaW3jx2uDJC6n1WvtgsSMCTZrtUKAJbs8eUMJCXS0G8mfPQ= 64 | X-Received: by 2002:a81:9108:0:b0:583:f78c:994e with SMTP id 65 | i8-20020a819108000000b00583f78c994emr7327928ywg.42.1694989812400; Sun, 17 Sep 66 | 2023 15:30:12 -0700 (PDT) 67 | MIME-Version: 1.0 68 | From: Sora Suegami 69 | Date: Mon, 18 Sep 2023 07:29:59 +0900 70 | Message-ID: 71 | Subject: Send 0.1 ETH to alice@gmail.com 72 | To: emailwallet.relayer@gmail.com 73 | Content-Type: multipart/alternative; boundary="000000000000aa87aa060595912c" 74 | 75 | --000000000000aa87aa060595912c 76 | Content-Type: text/plain; charset="UTF-8" 77 | 78 | 79 | 80 | --000000000000aa87aa060595912c 81 | Content-Type: text/html; charset="UTF-8" 82 | 83 |

84 | 85 | --000000000000aa87aa060595912c-- 86 | -------------------------------------------------------------------------------- /packages/circuits/tests/emails/email_auth_legacy_test2.eml: -------------------------------------------------------------------------------- 1 | Delivered-To: emailwallet.relayer@gmail.com 2 | Received: by 2002:a05:7108:2c4:b0:342:22f4:d6db with SMTP id i4csp2966gds; 3 | Tue, 10 Oct 2023 11:58:16 -0700 (PDT) 4 | X-Received: by 2002:a81:4a55:0:b0:599:8bd:5bdf with SMTP id x82-20020a814a55000000b0059908bd5bdfmr21750490ywa.50.1696964296192; 5 | Tue, 10 Oct 2023 11:58:16 -0700 (PDT) 6 | ARC-Seal: i=1; a=rsa-sha256; t=1696964296; cv=none; 7 | d=google.com; s=arc-20160816; 8 | b=a9xg8fw3SXXh572oqDh838o0dBzsmtImJhloG43idR4VIr3bD4bEg6yHiVQz/gCVHB 9 | JWSqBwHpS0ejzoa4Z27guBfS062lETo+O297nNn/KXBFJ+E0RMj65rvQTgmLI703M7sp 10 | uImKsxcVLIdfjERz8GBr0eRrg65WX1dvg39SzDcCtDYaHrV0yrs65B7H9DDdo8IknkF+ 11 | S5j0tBvM15ZHa9U+tn/0pUT5MwAv3DREZP+dyKfZxh0/9z2jycOOi5NSyUXRbTtXppdj 12 | PuaycQ+TMdAmOTdIMiLog1W8QOzrzv1uanDloMFu4ykOZ8q8MdDd1kk1fIDp4YbF4+KE 13 | NwVQ== 14 | ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; 15 | h=to:subject:message-id:date:from:mime-version:dkim-signature; 16 | bh=2mqY3PZCoz8InXGWf2u3R74gNz152fRD9sTUPAxlKYk=; 17 | fh=AKWw92sdXoMEDdHXKLL06vnizTpObKPGCfYXnoQDKO8=; 18 | b=wUxZO1jAnI6WgyweT+69bF7FzkKLyGZRvzQCNOaxKUnS+DQzLe51E19G5qYQNemciH 19 | f5yU0dUPFZa3gJuf/CmImd6VlEojBNPfk0bY1VpCH61a8OfuXi8XbMeF82GoMHi2Id70 20 | QR4QcUsUUbBV2R3Prw3Jy1tI9F0KWG5iyztI6DvWJX546cMvgYZn2l9ah+mlsreB8gaL 21 | kbhCQ3/vL8K2CDT0Cw3d8+dMzLxMgwpuNE1cF5Wff6vsrgbuYgsyB9qQEF5wRVgEQVxK 22 | 0ewpCgRzR01nW6/no/aDZ3BhVd9UW5C9CX7+y8+RZ3vRkWwowaH5+juqESG/A9U/HP2p 23 | hb6Q== 24 | ARC-Authentication-Results: i=1; mx.google.com; 25 | dkim=pass header.i=@gmail.com header.s=20230601 header.b=ZtY1tM0G; 26 | spf=pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=suegamisora@gmail.com; 27 | dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com 28 | Return-Path: 29 | Received: from mail-sor-f41.google.com (mail-sor-f41.google.com. [209.85.220.41]) 30 | by mx.google.com with SMTPS id r194-20020a0de8cb000000b005704eb70d02sor6290629ywe.11.2023.10.10.11.58.16 31 | for 32 | (Google Transport Security); 33 | Tue, 10 Oct 2023 11:58:16 -0700 (PDT) 34 | Received-SPF: pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) client-ip=209.85.220.41; 35 | Authentication-Results: mx.google.com; 36 | dkim=pass header.i=@gmail.com header.s=20230601 header.b=ZtY1tM0G; 37 | spf=pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=suegamisora@gmail.com; 38 | dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com 39 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 40 | d=gmail.com; s=20230601; t=1696964295; x=1697569095; dara=google.com; 41 | h=to:subject:message-id:date:from:mime-version:from:to:cc:subject 42 | :date:message-id:reply-to; 43 | bh=2mqY3PZCoz8InXGWf2u3R74gNz152fRD9sTUPAxlKYk=; 44 | b=ZtY1tM0Gaga0hoo7frGS439zDz3JKhYiDIag5WFs4cE0JBIQxOP5CPOpMhIP7mkkh8 45 | BT3+uoAIYAj6ewIFGtoE4iW+tEo8m+uusFn0+u+BiMUxrifgSUpm6L+7jmlRDGTELbiI 46 | x0JrI+dqFN4qeDYbVrFpko2bh4EdBDFBZjHW/KM8dFR+FDyVcNWeNqdeD/0ht13SwULG 47 | RsUboFNx3zVy4aCt0oi8kIc1c9XAYcM/lvM/pXkJVE4hPwErAwk5CZy25Uu6NN95eum4 48 | aaOI8h5MJ2aDR9a1jipuDcNJM/pLU0oyIPU9juP6otGCMotHqzJbJ0VQZm1pZAg++0Wy 49 | X3cQ== 50 | X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 51 | d=1e100.net; s=20230601; t=1696964295; x=1697569095; 52 | h=to:subject:message-id:date:from:mime-version:x-gm-message-state 53 | :from:to:cc:subject:date:message-id:reply-to; 54 | bh=2mqY3PZCoz8InXGWf2u3R74gNz152fRD9sTUPAxlKYk=; 55 | b=A9W23+Q7/4uDNevuWqpa7eRZlalXwzFjF2AZmRoa7XdFmJAN8puVKgJJgFAN9GxFG0 56 | TkGYTP5ZjSnAIkrjnnR1SpFgXnO02t6H+mJha8TK6d/QwSx3MO//ACD9H0Sy+fnygLly 57 | +79YG4X8M51xVC7rzf3M8mfubRAMCIgx3H6bdv50KTugO0K7kAwaVfH7IelJziR6kDAf 58 | /L43Mu2QQHNJSqE3uT5LJ5P8tVBP8U28adAPvVMB2UqNjAml8Jx8K/lIVL9yNS1HodjR 59 | xx4CBDybd4ojU5Rsmyak489TFOq+7ibJRRqQoNDfZeulVjHnZC8nsJTJaMA1leEdBy8b 60 | 7ReA== 61 | X-Gm-Message-State: AOJu0Yx6Y8WGZ6/NuwJ93pEaku/8m84Fmu2At47SDfTmL91QgE7EwYp6 62 | PBXEK923tWejtcRNtvppo7NGaLlg/rRoIvkx0yMqdMC2 63 | X-Google-Smtp-Source: AGHT+IFTvu5B4yrT+n5M7sJsvjMOv8jW49fPuUZ9TW24SK9KQjLHUbZivpC/9kYnwwKhGAIlbfFezDxoOdgtxiVT4Sg= 64 | X-Received: by 2002:a81:ac42:0:b0:5a7:acae:3bb0 with SMTP id 65 | z2-20020a81ac42000000b005a7acae3bb0mr4278669ywj.5.1696964295610; Tue, 10 Oct 66 | 2023 11:58:15 -0700 (PDT) 67 | MIME-Version: 1.0 68 | From: Sora Suegami 69 | Date: Wed, 11 Oct 2023 03:58:04 +0900 70 | Message-ID: 71 | Subject: Swap 1 ETH to DAI 72 | To: emailwallet.relayer@gmail.com 73 | Content-Type: multipart/alternative; boundary="0000000000000958390607614a21" 74 | 75 | --0000000000000958390607614a21 76 | Content-Type: text/plain; charset="UTF-8" 77 | 78 | 79 | 80 | --0000000000000958390607614a21 81 | Content-Type: text/html; charset="UTF-8" 82 | 83 |

84 | 85 | --0000000000000958390607614a21-- 86 | -------------------------------------------------------------------------------- /packages/circuits/tests/emails/email_auth_legacy_test3.eml: -------------------------------------------------------------------------------- 1 | Delivered-To: emailwallet.relayer@gmail.com 2 | Received: by 2002:a05:7108:2c4:b0:342:22f4:d6db with SMTP id i4csp17038gds; 3 | Tue, 10 Oct 2023 12:25:33 -0700 (PDT) 4 | X-Received: by 2002:a81:47c4:0:b0:5a0:ae01:803c with SMTP id u187-20020a8147c4000000b005a0ae01803cmr19454050ywa.38.1696965932748; 5 | Tue, 10 Oct 2023 12:25:32 -0700 (PDT) 6 | ARC-Seal: i=1; a=rsa-sha256; t=1696965932; cv=none; 7 | d=google.com; s=arc-20160816; 8 | b=Fsg3KEAwUjggHJzCpAIJuNz8Eq2GPO+sBNTbWQHDKlXgaLZxBWSohcJ7LrXfhUmuDA 9 | Ji+99B0FbLIrjSPTojnSSVFahIf6015uZw7MB88+PhDeWPDHp1RZOAhwurrVDwijUigo 10 | 07Tbc8Q5oFoMKst5d15ZMZQt/HcTunflxv/DdhmSKm1loIISuUc6VesLfqPe9MTaMztP 11 | biSHonNs9AsiAi52llttRYAspoRP0rH/PLZ9KXZ0qqmTleAS1hUOwjbxkFYOtxYGyJpG 12 | p1ouNBXpbzxWPivcoegCABNTCUDYYBZ0saR+MgACkDaULIXt2ett1jzPLsAdZvFJiSKF 13 | Id+w== 14 | ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; 15 | h=to:subject:message-id:date:from:mime-version:dkim-signature; 16 | bh=cyDN+YYekjfuyO0NSLmF5i598CHm65FOUxCJHB0q+Sw=; 17 | fh=AKWw92sdXoMEDdHXKLL06vnizTpObKPGCfYXnoQDKO8=; 18 | b=o5ksV0Fs6jZsEMgi0Z1SgiYU+tbWG3l2lw3KHVFUMWIToNFHYFmVQkr6rCUki1g2K0 19 | Bm8TubRMIuUkuyqMB6dWN5Acm/0lVhF0PMsZP8YqY3iNJWWdCWefl4dMFjkeijP4ki1j 20 | BKzBYKr3UQCVeuVlFvaPeI/BpcD+QeCjqu80CGl3JFKyFUeS9Dou1lyLXm7Va20W+1+Y 21 | ze8h3fv0h5DlTXA2oZVvsraXekIWKrxa+h45SYw/NSZ7bIwIM1W7B6hJ2IFDG25C65LX 22 | ExpUkmBPfmps93jH5fWi+YxwRcx+dpCzn1MAodo0Ruyxui0KTYdCMvHBJVe1VUjIejzh 23 | +cWQ== 24 | ARC-Authentication-Results: i=1; mx.google.com; 25 | dkim=pass header.i=@gmail.com header.s=20230601 header.b=cZhRvd7Z; 26 | spf=pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=suegamisora@gmail.com; 27 | dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com 28 | Return-Path: 29 | Received: from mail-sor-f41.google.com (mail-sor-f41.google.com. [209.85.220.41]) 30 | by mx.google.com with SMTPS id a20-20020a0dd814000000b005a220219985sor6072943ywe.16.2023.10.10.12.25.32 31 | for 32 | (Google Transport Security); 33 | Tue, 10 Oct 2023 12:25:32 -0700 (PDT) 34 | Received-SPF: pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) client-ip=209.85.220.41; 35 | Authentication-Results: mx.google.com; 36 | dkim=pass header.i=@gmail.com header.s=20230601 header.b=cZhRvd7Z; 37 | spf=pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=suegamisora@gmail.com; 38 | dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com 39 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 40 | d=gmail.com; s=20230601; t=1696965932; x=1697570732; dara=google.com; 41 | h=to:subject:message-id:date:from:mime-version:from:to:cc:subject 42 | :date:message-id:reply-to; 43 | bh=cyDN+YYekjfuyO0NSLmF5i598CHm65FOUxCJHB0q+Sw=; 44 | b=cZhRvd7Z9zTNAscMPIquUHj6CG/yLgKsvbGL2ikAMHAlGeJbhR4o0u2n4Rr9WN6h8O 45 | G7SJcvUD6enlg/a9/1AU0y73bjVPjMjY2nDGk+8ltokhBfYSeZcVsKA7iMuK+tBoORth 46 | ZFtTEdVlO7Ekly95ara84epofiYCCGTdvED/RshhzZqjhrp1Mbw/wFtHUAujXVkOmvt3 47 | MB0knG+mamYAjLqH6Bc/g50gbMMsk1yF5VUYWnbhCK6DUGOeNyZmeYYRLm2yFlSAbu8h 48 | PJPz2QbNY+D5CrxWdetq1JizYnJYOOSiRV7nflunEMji9cLW5I6lUqIlKxhQkdWqd+6b 49 | 3tTg== 50 | X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 51 | d=1e100.net; s=20230601; t=1696965932; x=1697570732; 52 | h=to:subject:message-id:date:from:mime-version:x-gm-message-state 53 | :from:to:cc:subject:date:message-id:reply-to; 54 | bh=cyDN+YYekjfuyO0NSLmF5i598CHm65FOUxCJHB0q+Sw=; 55 | b=qeLyin8vj0JfK6n5sLb1/otbg/3P9Hf2kwVd8fnyEa/gnFl5db2e/2XvyhGjP2zMpU 56 | sGjPCkXk7wCQozVEO35tBYsOzgah9uT480TUwVtTyuwW0xq4/4ZS1YizXlnRdmBR8SN8 57 | Ihjdyf5qhwKGv26xmUgGF7MqDbbfqFdwlPPRdYsPie+MWHdNMqu4dp1JW8Xtg3mpjcdN 58 | 3usOCapPcAJCJG89wyaQWBaKbG9PuGCwULXw/BNMd6vYxQklrVOtNL5m9zEQG6DjBZeO 59 | kD6lTBrYK7e6xMgTuqsF7vYIgAe51t1ijmy3r1hb6x7GPx+XId+PrVL1pMmWTFF4EcbW 60 | W21w== 61 | X-Gm-Message-State: AOJu0Yw2zJT1rbv9BoSJAwB4wOrj56soVWWji9646BXOk/k2D2gF+jCM 62 | CRR/OyY9S2uGYRb5k9ED1f+XrRPwUp1R0gSBu2pZL/jP 63 | X-Google-Smtp-Source: AGHT+IGSbOedb5aIuVhx7TayE6QXtIMR+yVf+UDmgKCrssN+p7v5iVhQJ21PrC58e5NK2EQvGB52q4bEypFxNZe+MG0= 64 | X-Received: by 2002:a0d:de85:0:b0:59b:c0a8:2882 with SMTP id 65 | h127-20020a0dde85000000b0059bc0a82882mr19390753ywe.46.1696965932010; Tue, 10 66 | Oct 2023 12:25:32 -0700 (PDT) 67 | MIME-Version: 1.0 68 | From: "dummy@gmail.com" 69 | Date: Wed, 11 Oct 2023 04:25:21 +0900 70 | Message-ID: 71 | Subject: Send 1 ETH to bob@example.com 72 | To: emailwallet.relayer@gmail.com 73 | Content-Type: multipart/alternative; boundary="00000000000092d27c060761abc7" 74 | 75 | --00000000000092d27c060761abc7 76 | Content-Type: text/plain; charset="UTF-8" 77 | 78 | 79 | 80 | --00000000000092d27c060761abc7 81 | Content-Type: text/html; charset="UTF-8" 82 | 83 |

84 | 85 | --00000000000092d27c060761abc7-- 86 | -------------------------------------------------------------------------------- /packages/circuits/tests/emails/email_auth_legacy_test4.eml: -------------------------------------------------------------------------------- 1 | Delivered-To: emailwallet.relayer@gmail.com 2 | Received: by 2002:a05:7108:2c4:b0:342:22f4:d6db with SMTP id i4csp25580gds; 3 | Tue, 10 Oct 2023 12:43:48 -0700 (PDT) 4 | X-Received: by 2002:a81:574f:0:b0:5a7:ab51:af5c with SMTP id l76-20020a81574f000000b005a7ab51af5cmr4673683ywb.13.1696967028753; 5 | Tue, 10 Oct 2023 12:43:48 -0700 (PDT) 6 | ARC-Seal: i=1; a=rsa-sha256; t=1696967028; cv=none; 7 | d=google.com; s=arc-20160816; 8 | b=r2caNqB+Tqis40axvQ+9WATqElkSRMoLieRYBHHeoHlF3b5cTf4S5iBEzrihGSExv3 9 | hTEtPaFCXEplKGQn8+XVgqjAct61mkAYzHxflnaC1pgkd1CYbkt4zyNJEIW7qb+AIMSL 10 | 1DdnLEuU9sb2oZNfbHyXMQaqXe6sEW6sZQDp6BZJHV0YsOAFhlRiacomh65Oq/DfY1P2 11 | Dvy9wahx+Qki20kIAfzR/TKuAx5z9IUwaOshdQuWH8i6HFjC8MRO0RwGVUb5ZMkRZFu7 12 | c4Sx/r9SfTiI4zsos0PvnaOvdNBXg2SPDMNYp0c/sFbngRVHuNK5WPE86v3vHnMi8Gmy 13 | pmkQ== 14 | ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; 15 | h=to:subject:message-id:date:from:mime-version:dkim-signature; 16 | bh=dH2d6q5VcS+7rfJUaWYK0fSFe6OjBSMp9T98v8goAXc=; 17 | fh=AKWw92sdXoMEDdHXKLL06vnizTpObKPGCfYXnoQDKO8=; 18 | b=C3ka6pwtIk5/XPtFgf/FbwOaJx7hdOxBOXAmkHKs/h6WtxIHmo65elkM6NsLK0fPKL 19 | lzfD9jwUmfFTKto1p2dbsqQ3RSlNHsgvAQq60jv7NiwbSFRF8tV8V49waE5rwLGj+egO 20 | CoYGPGYEwxtXsDC4jUFMevFH7xEArXp1Lb+pRTkuejQwGghyXQr0Quo6Dgv8ztrjE6+e 21 | URqviuUsFNCFqHnPAgeyAQ+yrllq/as6SPZSky6UG42xkzktlobE3Yea08pLY0OL8UHK 22 | 1WHVJbT5DFhIyZ+gCtkMQ3weEXl5rL1IWuuv1W1GeXZFv7rwzvPOE1OzyK33ZL4eWSXF 23 | 2Clw== 24 | ARC-Authentication-Results: i=1; mx.google.com; 25 | dkim=pass header.i=@gmail.com header.s=20230601 header.b="GO/VW+4p"; 26 | spf=pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=suegamisora@gmail.com; 27 | dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com 28 | Return-Path: 29 | Received: from mail-sor-f41.google.com (mail-sor-f41.google.com. [209.85.220.41]) 30 | by mx.google.com with SMTPS id i200-20020a8191d1000000b00586a708c858sor6327710ywg.12.2023.10.10.12.43.48 31 | for 32 | (Google Transport Security); 33 | Tue, 10 Oct 2023 12:43:48 -0700 (PDT) 34 | Received-SPF: pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) client-ip=209.85.220.41; 35 | Authentication-Results: mx.google.com; 36 | dkim=pass header.i=@gmail.com header.s=20230601 header.b="GO/VW+4p"; 37 | spf=pass (google.com: domain of suegamisora@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=suegamisora@gmail.com; 38 | dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com 39 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 40 | d=gmail.com; s=20230601; t=1696967028; x=1697571828; dara=google.com; 41 | h=to:subject:message-id:date:from:mime-version:from:to:cc:subject 42 | :date:message-id:reply-to; 43 | bh=dH2d6q5VcS+7rfJUaWYK0fSFe6OjBSMp9T98v8goAXc=; 44 | b=GO/VW+4pWlcPLz1OwBRtCcm/kaXWkMLFDByoke7dUKuKtScGtcpOfEOlc7LS26pRSn 45 | G0KnZ6BH5SNcrx2uFXM/hPZi3OHdTBwGJ9Z3T5wmr94CY3+9JrrgjbXKv+3wCO9Xeqe2 46 | U7BE8fm2PeDR1oyxv1mufUqM7MDv/EZ6ZJXNw1w47SDu0jEtBfe44W/tsQGxgCziMzSQ 47 | 2ziwXcCDoenkL0Wxq72g1Cab4o3gbBU19VCEtBWvkONhSTVN9lQuyIajNAmsD68OJwEe 48 | TL9TWOzDeP5v37VQNVIQLN6fIUx8tjx2iH4+DMJPvRMzVZ+0MbuvcYLr8915xDjz7jjO 49 | DK2A== 50 | X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 51 | d=1e100.net; s=20230601; t=1696967028; x=1697571828; 52 | h=to:subject:message-id:date:from:mime-version:x-gm-message-state 53 | :from:to:cc:subject:date:message-id:reply-to; 54 | bh=dH2d6q5VcS+7rfJUaWYK0fSFe6OjBSMp9T98v8goAXc=; 55 | b=kLGz1m3GUfobNymbK3/DSTvJXAoppFTs4fRqRZd77TK9CkxPQe025/3PQAVhU2YW58 56 | r0Jk6MAhFcN/n15MPXdjVBT6eqC3sCmYhBR9n6RmX7vp2pEius50Lty64Q/eUj2cFH+t 57 | CUNs6ctISm65/agHRNvt6no8XgzFr3Ma4KgfMhhzxcIK2bopoW90LfUmyKZFXSlaBVbm 58 | KVQXbAC0NN/2DtYUykJTQJ2Ze0Cu9P1TxcyZZHpaSed7gz71fO4u/W8CXpslwOJfSfXl 59 | VyiCQ1v8exrM+Q8WEmiGdqLTqZfTK6VcG2w7kmDG+ZhbXA2IRDsUw/j0h868878UaFNO 60 | UPuQ== 61 | X-Gm-Message-State: AOJu0YxbWTRDE1w3F70lfb2Bb828Alj1VEuoPi9QDhiWl/m63ILSpyXh 62 | BV+ZKjoFknfARLzERgxd6gE8y/UYCBk+pUGDeB5KmATJ 63 | X-Google-Smtp-Source: AGHT+IEeri9J0dpOppDay4cMM7UxoTbvoPvlULY8a5bikCTsqVXU2KmrxMjfyr+vW9S33WH/2w7ouMrZKF3lk6KAyCQ= 64 | X-Received: by 2002:a81:ac42:0:b0:5a7:acae:3bb0 with SMTP id 65 | z2-20020a81ac42000000b005a7acae3bb0mr4382907ywj.5.1696967028246; Tue, 10 Oct 66 | 2023 12:43:48 -0700 (PDT) 67 | MIME-Version: 1.0 68 | From: =?UTF-8?B?5pyr56We5aWP5a6Z?= 69 | Date: Wed, 11 Oct 2023 04:43:37 +0900 70 | Message-ID: 71 | Subject: Send 1 ETH to bob@example.com 72 | To: emailwallet.relayer@gmail.com 73 | Content-Type: multipart/alternative; boundary="000000000000ea0b6c060761ec19" 74 | 75 | --000000000000ea0b6c060761ec19 76 | Content-Type: text/plain; charset="UTF-8" 77 | 78 | 79 | 80 | --000000000000ea0b6c060761ec19 81 | Content-Type: text/html; charset="UTF-8" 82 | 83 |

84 | 85 | --000000000000ea0b6c060761ec19-- 86 | -------------------------------------------------------------------------------- /packages/circuits/tests/script/send_email.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | from email.mime.multipart import MIMEMultipart 3 | from email.mime.text import MIMEText 4 | 5 | 6 | def send_html_email( 7 | sender_email, sender_password, recipient_email, subject, html_content 8 | ): 9 | # Create a multipart message 10 | msg = MIMEMultipart("alternative") 11 | msg["From"] = sender_email 12 | msg["To"] = recipient_email 13 | msg["Subject"] = subject 14 | 15 | # Attach the HTML body to the email 16 | msg.attach(MIMEText(html_content, "html")) 17 | 18 | try: 19 | # Connect to the Gmail SMTP server 20 | server = smtplib.SMTP("smtp.gmail.com", 587) 21 | server.starttls() # Upgrade the connection to a secure encrypted SSL/TLS connection 22 | server.login(sender_email, sender_password) 23 | 24 | # Send the email 25 | server.sendmail(sender_email, recipient_email, msg.as_string()) 26 | 27 | print("Email sent successfully!") 28 | except Exception as e: 29 | print(f"Failed to send email: {e}") 30 | finally: 31 | server.quit() 32 | 33 | 34 | if __name__ == "__main__": 35 | # Replace with your details 36 | sender_email = "" 37 | sender_password = "" 38 | recipient_email = "" 39 | subject = "ZK Email:" 40 | 41 | # Basic HTML content 42 | html_content = """ 43 | 44 | 45 |
ZK Email Command
46 | 47 | 48 | """ 49 | 50 | # Send the email 51 | send_html_email( 52 | sender_email, sender_password, recipient_email, subject, html_content 53 | ) 54 | -------------------------------------------------------------------------------- /packages/contracts/.env.example: -------------------------------------------------------------------------------- 1 | LOCALHOST_RPC_URL=http://127.0.0.1:8545 2 | SEPOLIA_RPC_URL=https://sepolia.base.org 3 | # For zksync 4 | # SEPOLIA_RPC_URL=https://sepolia.era.zksync.dev 5 | 6 | MAINNET_RPC_URL=https://mainnet.base.org 7 | 8 | # NEED 0x prefix 9 | PRIVATE_KEY= 10 | INITIAL_OWNER= 11 | 12 | CHAIN_ID=84532 13 | RPC_URL="https://sepolia.base.org" 14 | DKIM_SIGNER=0x6293a80bf4bd3fff995a0cab74cbf281d922da02 # Signer for the dkim oracle on IC (Don't change this) 15 | ETHERSCAN_API_KEY= 16 | # CHAIN_NAME="base_sepolia" 17 | 18 | PROXY_BYTECODE_HASH=0x0000000000000000000000000000000000000000000000000000000000000000 19 | 20 | # Open Zeppelin Defender 21 | DEFENDER_KEY="YOUR_KEY" 22 | DEFENDER_SECRET="YOUR_SECRET" 23 | FOUNDRY_OUT="artifacts" -------------------------------------------------------------------------------- /packages/contracts/.zksolc-libraries-cache/missing_library_dependencies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contract_name": "CommandUtils", 4 | "contract_path": "src/libraries/CommandUtils.sol", 5 | "missing_libraries": [ 6 | "src/libraries/DecimalUtils.sol:DecimalUtils" 7 | ] 8 | }, 9 | { 10 | "contract_name": "DecimalUtils", 11 | "contract_path": "src/libraries/DecimalUtils.sol", 12 | "missing_libraries": [] 13 | } 14 | ] -------------------------------------------------------------------------------- /packages/contracts/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "artifacts" 4 | libs = ["../../node_modules", "lib"] 5 | optimizer = true 6 | # The following line `via-ir = true` is needed to compile this project using zksync features 7 | # via-ir = true 8 | optimizer-runs = 20_000 9 | fs_permissions = [ 10 | { access = "read", path = "./artifacts/WETH9.sol/WETH9.json" }, 11 | { access = "read", path = "./test/build_integration" }, 12 | { access = "read", path = "./zkout/ERC1967Proxy.sol/ERC1967Proxy.json" }, 13 | { access = "read", path = "./artifacts" }, 14 | ] 15 | 16 | solc = "0.8.26" 17 | 18 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 19 | 20 | # OpenZeppelin 21 | ffi = true 22 | ast = true 23 | build_info = true 24 | extra_output = ["storageLayout"] 25 | 26 | # For missing libraries, please comment out following line and replace some placeholders if you use foundry-zksync 27 | #libraries = [ 28 | # "{PROJECT_DIR}/packages/contracts/src/libraries/DecimalUtils.sol:DecimalUtils:{DEPLOYED_ADDRESS}", 29 | # "{PROJECT_DIR}/packages/contracts/src/libraries/CommandUtils.sol:CommandUtils:{DEPLOYED_ADDRESS}", 30 | # "{PROJECT_DIR}/packages/contracts/src/libraries/StringUtils.sol:StringUtils:{DEPLOYED_ADDRESS}" 31 | #] 32 | 33 | [rpc_endpoints] 34 | localhost = "${LOCALHOST_RPC_URL}" 35 | sepolia = "${SEPOLIA_RPC_URL}" 36 | mainnet = "${MAINNET_RPC_URL}" 37 | 38 | [etherscan] 39 | # sepolia = { key = "${ETHERSCAN_API_KEY}" } 40 | # mainnet = { key = "${ETHERSCAN_API_KEY}" } 41 | 42 | [profile.default.zksync] 43 | src = 'src' 44 | libs = ["node_modules", "lib"] 45 | fallback_oz = true 46 | is_system = true 47 | mode = "3" 48 | -------------------------------------------------------------------------------- /packages/contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@zk-email/email-tx-builder-contracts", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "build": "forge build --skip '*ZKSync*'", 7 | "test": "forge test --force --no-match-test \"testIntegration\" --no-match-contract \".*Script.*\" --skip '*ZKSync*'", 8 | "zkbuild": "forge build --skip test --zksync", 9 | "zktest": "forge test --no-match-test \"testIntegration\" --no-match-contract \".*Script.*\" --system-mode=true --zksync --gas-limit 2000000000 --chain 300 --fork-url http://127.0.0.1:8011", 10 | "lint": "solhint 'src/**/*.sol'" 11 | }, 12 | "dependencies": { 13 | "@matterlabs/zksync-contracts": "^0.6.1", 14 | "@openzeppelin/contracts": "^5.0.0", 15 | "@openzeppelin/contracts-upgradeable": "^5.0.0", 16 | "@zk-email/contracts": "^6.3.2", 17 | "solady": "^0.0.123", 18 | "solidity-stringutils": "github:LayerZero-Labs/solidity-stringutils" 19 | }, 20 | "devDependencies": { 21 | "ds-test": "https://github.com/dapphub/ds-test", 22 | "forge-std": "https://github.com/foundry-rs/forge-std", 23 | "solhint": "^3.6.1" 24 | }, 25 | "files": [ 26 | "/src", 27 | "foundry.toml", 28 | "package.json", 29 | "README.md", 30 | "remappings.txt" 31 | ] 32 | } -------------------------------------------------------------------------------- /packages/contracts/remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=../../node_modules/@openzeppelin 2 | @openzeppelin/contracts-upgradeable/=../../node_modules/@openzeppelin/contracts-upgradeable 3 | @zk-email/=../../node_modules/@zk-email 4 | @uniswap/=../../node_modules/@uniswap 5 | @matterlabs/=../../node_modules/@matterlabs 6 | forge-std/=../../node_modules/forge-std/src 7 | ds-test/=../../node_modules/ds-test/src 8 | solady/=../../node_modules/solady/src/ 9 | accountabstraction/=../../node_modules/accountabstraction/ 10 | solidity-stringutils/=../../node_modules/solidity-stringutils/ 11 | openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ -------------------------------------------------------------------------------- /packages/contracts/script/BaseDeployScript.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script} from "forge-std/Script.sol"; 5 | import {console} from "forge-std/console.sol"; 6 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 7 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 8 | import {UserOverrideableDKIMRegistry} from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; 9 | 10 | import {ZKSyncCreate2Factory} from "../src/utils/ZKSyncCreate2Factory.sol"; 11 | import {Verifier} from "../src/utils/Verifier.sol"; 12 | import {Groth16Verifier} from "../src/utils/Groth16Verifier.sol"; 13 | import {ECDSAOwnedDKIMRegistry} from "../src/utils/ECDSAOwnedDKIMRegistry.sol"; 14 | import {EmailAuth} from "../src/EmailAuth.sol"; 15 | 16 | contract BaseDeployScript is Script { 17 | address initialOwner; 18 | uint256 deployerPrivateKey; 19 | 20 | /// @notice Sets up deployment configuration based on whether using Defender or private key 21 | function run() public virtual { 22 | deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 23 | if (deployerPrivateKey == 0) { 24 | console.log("PRIVATE_KEY env var not set"); 25 | return; 26 | } 27 | 28 | initialOwner = vm.envOr("INITIAL_OWNER", address(0)); 29 | if (initialOwner == address(0)) { 30 | initialOwner = vm.addr(deployerPrivateKey); 31 | } 32 | } 33 | 34 | /// @notice Deploys a UserOverrideableDKIMRegistry contract with a specified owner, dkim signer and time delay 35 | function deployUserOverrideableDKIMRegistry( 36 | address owner, 37 | address dkimSigner, 38 | uint256 timeDelay 39 | ) public returns (address) { 40 | address dkimProxyAddress; 41 | UserOverrideableDKIMRegistry dkimImpl = new UserOverrideableDKIMRegistry(); 42 | dkimProxyAddress = address( 43 | new ERC1967Proxy( 44 | address(dkimImpl), 45 | abi.encodeCall( 46 | UserOverrideableDKIMRegistry.initialize, 47 | (owner, dkimSigner, timeDelay) 48 | ) 49 | ) 50 | ); 51 | console.log( 52 | "UseroverrideableDKIMRegistry proxy deployed at: %s", 53 | dkimProxyAddress 54 | ); 55 | return dkimProxyAddress; 56 | } 57 | 58 | /// @notice Deploys an ECDSAOwnedDKIMRegistry contract with a specified owner and dkim signer 59 | function deployECDSAOwnedDKIMRegistry( 60 | address owner, 61 | address dkimSigner 62 | ) public returns (address) { 63 | address ecdsaDkimProxyAddress; 64 | ECDSAOwnedDKIMRegistry ecdsaDkimImpl = new ECDSAOwnedDKIMRegistry(); 65 | ecdsaDkimProxyAddress = address( 66 | new ERC1967Proxy( 67 | address(ecdsaDkimImpl), 68 | abi.encodeCall(ecdsaDkimImpl.initialize, (owner, dkimSigner)) 69 | ) 70 | ); 71 | console.log( 72 | "ECDSAOwnedDKIMRegistry proxy deployed at: %s", 73 | ecdsaDkimProxyAddress 74 | ); 75 | return ecdsaDkimProxyAddress; 76 | } 77 | 78 | /// @notice Deploys a Verifier contract with a specified owner and Groth16 verifier 79 | function deployVerifier(address owner) public returns (address) { 80 | address verifierProxyAddress; 81 | Verifier verifierImpl = new Verifier(); 82 | Groth16Verifier groth16Verifier = new Groth16Verifier(); 83 | console.log("Groth16Verifier deployed at: %s", address(groth16Verifier)); 84 | verifierProxyAddress = address( 85 | new ERC1967Proxy( 86 | address(verifierImpl), 87 | abi.encodeCall( 88 | verifierImpl.initialize, 89 | (owner, address(groth16Verifier)) 90 | ) 91 | ) 92 | ); 93 | console.log("Verifier deployed at: %s", verifierProxyAddress); 94 | return verifierProxyAddress; 95 | } 96 | 97 | /// @notice Deploys an EmailAuth implementation contract 98 | function deployEmailAuthImplementation() public returns (address) { 99 | address emailAuthImplAddress; 100 | emailAuthImplAddress = address(new EmailAuth()); 101 | console.log( 102 | "EmailAuth implementation deployed at: %s", 103 | emailAuthImplAddress 104 | ); 105 | return emailAuthImplAddress; 106 | } 107 | 108 | /// @notice Deploys a ZK Sync Create2 factory contract 109 | function deployZKSyncCreate2Factory() public returns (address) { 110 | address factoryImplAddress; 111 | factoryImplAddress = address(new ZKSyncCreate2Factory()); 112 | console.log("ZKSyncCreate2Factory deployed at: %s", factoryImplAddress); 113 | return factoryImplAddress; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /packages/contracts/script/ChangeOwners.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | import "../src/utils/Verifier.sol"; 7 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 8 | 9 | contract ChangeOwners is Script { 10 | function run() external { 11 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 12 | if (deployerPrivateKey == 0) { 13 | console.log("PRIVATE_KEY env var not set"); 14 | return; 15 | } 16 | address verifier = vm.envOr("VERIFIER", address(0)); 17 | address ecdsaDkim = vm.envOr("ECDSA_DKIM", address(0)); 18 | address dkim = vm.envOr("DKIM", address(0)); 19 | address newOwner = vm.envAddress("NEW_OWNER"); 20 | if (newOwner == address(0)) { 21 | console.log("NEW_OWNER env var not set"); 22 | return; 23 | } 24 | 25 | vm.startBroadcast(deployerPrivateKey); 26 | if (verifier != address(0)) { 27 | OwnableUpgradeable(verifier).transferOwnership(newOwner); 28 | } 29 | if (ecdsaDkim != address(0)) { 30 | OwnableUpgradeable(ecdsaDkim).transferOwnership(newOwner); 31 | } 32 | if (dkim != address(0)) { 33 | OwnableUpgradeable(dkim).transferOwnership(newOwner); 34 | } 35 | vm.stopBroadcast(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/contracts/script/ChangeSignerInECDSAOwnedDKIMRegistry.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | import {ECDSAOwnedDKIMRegistry} from "../src/utils/ECDSAOwnedDKIMRegistry.sol"; 7 | 8 | contract ChangeSigner is Script { 9 | function run() external { 10 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 11 | if (deployerPrivateKey == 0) { 12 | console.log("PRIVATE_KEY env var not set"); 13 | return; 14 | } 15 | address ecdsaDkimAddr = vm.envAddress("ECDSA_DKIM"); 16 | if (ecdsaDkimAddr == address(0)) { 17 | console.log("ECDSA_DKIM env var not set"); 18 | return; 19 | } 20 | address newSigner = vm.envAddress("NEW_SIGNER"); 21 | if (newSigner == address(0)) { 22 | console.log("NEW_SIGNER env var not set"); 23 | return; 24 | } 25 | vm.startBroadcast(deployerPrivateKey); 26 | ECDSAOwnedDKIMRegistry ecdsaDkim = ECDSAOwnedDKIMRegistry( 27 | ecdsaDkimAddr 28 | ); 29 | ecdsaDkim.changeSigner(newSigner); 30 | vm.stopBroadcast(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/contracts/script/DeployEmailAuthWithCreate2ZKSync.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 5 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 6 | import "../src/EmailAuth.sol"; 7 | import {ZKSyncCreate2Factory} from "../src/utils/ZKSyncCreate2Factory.sol"; 8 | import "./BaseDeployScript.sol"; 9 | 10 | contract Deploy is BaseDeployScript { 11 | using ECDSA for *; 12 | 13 | function run() public override { 14 | super.run(); 15 | vm.startBroadcast(deployerPrivateKey); 16 | 17 | EmailAuth emailAuth = EmailAuth(deployEmailAuthImplementation()); 18 | 19 | address recoveredAccount = address(0x1); 20 | bytes32 accountSalt = 0x0; 21 | address controller = address(this); 22 | bytes memory emailAuthInit = abi.encode( 23 | address(emailAuth), abi.encodeCall(EmailAuth.initialize, (recoveredAccount, accountSalt, controller)) 24 | ); 25 | 26 | ZKSyncCreate2Factory factory = ZKSyncCreate2Factory(deployZKSyncCreate2Factory()); 27 | string memory artifact = vm.readFile("zkout/ERC1967Proxy.sol/ERC1967Proxy.json"); 28 | bytes32 bytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); 29 | console.log("bytecodeHash"); 30 | console.logBytes32(bytes32(bytecodeHash)); 31 | 32 | address computedAddress = factory.computeAddress(accountSalt, bytecodeHash, emailAuthInit); 33 | console.log("computedAddress", computedAddress); 34 | 35 | (bool success, bytes memory returnData) = factory.deploy(accountSalt, bytecodeHash, emailAuthInit); 36 | 37 | address payable proxyAddress = abi.decode(returnData, (address)); 38 | console.log("proxyAddress %s", proxyAddress); 39 | 40 | EmailAuth emailAuthProxy = EmailAuth(proxyAddress); 41 | console.log("emailAuthProxy.controller() %s", emailAuthProxy.controller()); 42 | vm.stopBroadcast(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/contracts/script/DeployEmailSignerFactory.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script} from "forge-std/Script.sol"; 5 | import {console} from "forge-std/console.sol"; 6 | import {BaseDeployScript} from "./BaseDeployScript.sol"; 7 | import {EmailSigner} from "../src/EmailSigner.sol"; 8 | import {EmailSignerFactory} from "../src/EmailSignerFactory.sol"; 9 | 10 | contract DeployEmailSignerFactory is BaseDeployScript { 11 | uint256 constant TEMPLATE_ID = 1; // Template ID for sign hash command 12 | uint256 constant DKIM_REGISTRY_TIME_DELAY = 7 days; // Time delay for DKIM registry updates 13 | 14 | function run() public override { 15 | // Call parent run to set up deployment configuration 16 | super.run(); 17 | 18 | // Start broadcasting transactions 19 | vm.startBroadcast(deployerPrivateKey); 20 | 21 | // Deploy DKIM Registry with time delay 22 | address dkimRegistry = deployUserOverrideableDKIMRegistry( 23 | initialOwner, 24 | vm.envAddress("DKIM_SIGNER"), // DKIM signer from environment variable 25 | DKIM_REGISTRY_TIME_DELAY 26 | ); 27 | 28 | // Deploy Verifier with Groth16 29 | address verifier = deployVerifier(initialOwner); 30 | 31 | // Deploy EmailSigner implementation 32 | EmailSigner emailSignerImpl = new EmailSigner(); 33 | console.log( 34 | "EmailSigner implementation deployed at: %s", 35 | address(emailSignerImpl) 36 | ); 37 | 38 | // Deploy EmailSignerFactory 39 | EmailSignerFactory factory = new EmailSignerFactory( 40 | address(emailSignerImpl), 41 | dkimRegistry, 42 | verifier 43 | ); 44 | console.log("EmailSignerFactory deployed at: %s", address(factory)); 45 | 46 | vm.stopBroadcast(); 47 | 48 | // Log deployment summary 49 | console.log("\nDeployment Summary:"); 50 | console.log("-------------------"); 51 | console.log("DKIM Registry: %s", dkimRegistry); 52 | console.log("Verifier: %s", verifier); 53 | console.log("EmailSigner Implementation: %s", address(emailSignerImpl)); 54 | console.log("EmailSignerFactory: %s", address(factory)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/contracts/script/RenounceOwners.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | import "../src/utils/Verifier.sol"; 7 | import "../src/utils/ECDSAOwnedDKIMRegistry.sol"; 8 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 9 | 10 | contract RenounceOwners is Script { 11 | function run() external { 12 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 13 | if (deployerPrivateKey == 0) { 14 | console.log("PRIVATE_KEY env var not set"); 15 | return; 16 | } 17 | address verifier = vm.envOr("VERIFIER", address(0)); 18 | address ecdsaDkim = vm.envOr("ECDSA_DKIM", address(0)); 19 | address dkim = vm.envOr("DKIM", address(0)); 20 | 21 | vm.startBroadcast(deployerPrivateKey); 22 | if (verifier != address(0)) { 23 | OwnableUpgradeable(verifier).renounceOwnership(); 24 | } 25 | if (ecdsaDkim != address(0)) { 26 | OwnableUpgradeable(ecdsaDkim).renounceOwnership(); 27 | } 28 | if (dkim != address(0)) { 29 | OwnableUpgradeable(dkim).renounceOwnership(); 30 | } 31 | vm.stopBroadcast(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/contracts/script/Upgrades.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {console} from "forge-std/console.sol"; 5 | import "../src/utils/Verifier.sol"; 6 | import "../src/utils/ECDSAOwnedDKIMRegistry.sol"; 7 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 8 | import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; 9 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 10 | import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; 11 | import {UserOverrideableDKIMRegistry} from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; 12 | import {BaseDeployScript} from "./BaseDeployScript.sol"; 13 | 14 | contract Upgrades is BaseDeployScript { 15 | function run() public override { 16 | super.run(); 17 | 18 | address verifierProxy = vm.envOr("VERIFIER", address(0)); 19 | address ecdsaDkimProxy = vm.envOr("ECDSA_DKIM", address(0)); 20 | address dkimProxy = vm.envOr("DKIM", address(0)); 21 | 22 | vm.startBroadcast(deployerPrivateKey); 23 | if (verifierProxy != address(0)) { 24 | upgradeVerifier(verifierProxy); 25 | } 26 | if (ecdsaDkimProxy != address(0)) { 27 | upgradeECDSAOwnedDKIMRegistry(ecdsaDkimProxy); 28 | } 29 | if (dkimProxy != address(0)) { 30 | upgradeUserOverrideableDKIMRegistry(dkimProxy); 31 | } 32 | vm.stopBroadcast(); 33 | } 34 | 35 | function upgradeVerifier(address verifierProxy) public { 36 | Verifier newVerifierImpl = new Verifier(); 37 | UUPSUpgradeable(verifierProxy).upgradeToAndCall( 38 | address(newVerifierImpl), 39 | bytes("") 40 | ); 41 | } 42 | 43 | function upgradeECDSAOwnedDKIMRegistry(address ecdsaDkimProxy) public { 44 | ECDSAOwnedDKIMRegistry newECDSAOwnedDKIMRegistryImpl = new ECDSAOwnedDKIMRegistry(); 45 | UUPSUpgradeable(ecdsaDkimProxy).upgradeToAndCall( 46 | address(newECDSAOwnedDKIMRegistryImpl), 47 | bytes("") 48 | ); 49 | } 50 | 51 | function upgradeUserOverrideableDKIMRegistry(address dkimProxy) public { 52 | UserOverrideableDKIMRegistry newDkimImpl = new UserOverrideableDKIMRegistry(); 53 | UUPSUpgradeable(dkimProxy).upgradeToAndCall( 54 | address(newDkimImpl), 55 | bytes("") 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/contracts/src/EmailSignerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import {EmailSigner} from "./EmailSigner.sol"; 5 | import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; 6 | 7 | /// @title Email Signer Factory 8 | /// @notice Factory contract for deploying minimal proxy clones of EmailSigner 9 | /// @dev Uses EIP-1167 minimal proxy pattern for gas efficient deployment 10 | contract EmailSignerFactory { 11 | /// The address of the EmailSigner implementation contract 12 | address public immutable implementation; 13 | /// The DKIM registry contract address used by all clones 14 | address public immutable dkimRegistry; 15 | /// The verifier contract address used by all clones 16 | address public immutable verifier; 17 | 18 | event EmailSignerDeployed( 19 | address indexed emailSigner, 20 | bytes32 indexed accountSalt 21 | ); 22 | 23 | error InvalidImplementation(); 24 | error InvalidDKIMRegistry(); 25 | error InvalidVerifier(); 26 | 27 | /// @notice Constructor that sets the implementation contract and initialization parameters 28 | /// @param _implementation Address of the EmailSigner implementation contract 29 | /// @param _dkimRegistry Address of the DKIM registry contract 30 | /// @param _verifier Address of the verifier contract 31 | constructor( 32 | address _implementation, 33 | address _dkimRegistry, 34 | address _verifier 35 | ) { 36 | if (_implementation == address(0)) revert InvalidImplementation(); 37 | if (_dkimRegistry == address(0)) revert InvalidDKIMRegistry(); 38 | if (_verifier == address(0)) revert InvalidVerifier(); 39 | 40 | implementation = _implementation; 41 | dkimRegistry = _dkimRegistry; 42 | verifier = _verifier; 43 | } 44 | 45 | /// @notice Deploys a new EmailSigner clone 46 | /// @param accountSalt The account salt used to generate the clone's salt 47 | /// @return The address of the deployed EmailSigner clone 48 | function deploy(bytes32 accountSalt) external returns (address) { 49 | // salt is not strictly necessary to deploy the account with this given accountSalt, 50 | // but it is nice to have for deterministic deployments. The accountSalt itself is ultimately 51 | // derived from a hash of a secret random code and an email address. 52 | bytes32 salt = keccak256(abi.encodePacked(accountSalt)); 53 | address clone = Clones.cloneDeterministic(implementation, accountSalt); 54 | 55 | // Generate templateId dynamically based on accountSalt 56 | uint256 templateId = uint256( 57 | keccak256(abi.encodePacked("EMAIL-SIGNER", accountSalt)) 58 | ); 59 | 60 | EmailSigner(clone).initialize( 61 | accountSalt, 62 | dkimRegistry, 63 | verifier, 64 | templateId 65 | ); 66 | 67 | emit EmailSignerDeployed(clone, salt); 68 | return clone; 69 | } 70 | 71 | /// @notice Predicts the address where an EmailSigner clone would be deployed 72 | /// @param accountSalt The account salt used to generate the clone's salt 73 | /// @return The predicted address of the EmailSigner clone 74 | function predictAddress( 75 | bytes32 accountSalt 76 | ) external view returns (address) { 77 | return Clones.predictDeterministicAddress(implementation, accountSalt); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/contracts/src/interfaces/IERC1271.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IERC1271 { 5 | /** 6 | * @dev Validates if a signature is valid for the provided data. Used in older EIP1271 versions. 7 | * @param _data Raw data that was signed 8 | * @param _signature Signature of the data 9 | * @return Magic value 0x20c13b0b if signature is valid for the data, 0x0 otherwise 10 | * 11 | * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) 12 | * MUST allow external calls 13 | */ 14 | function isValidSignature( 15 | bytes calldata _data, 16 | bytes calldata _signature 17 | ) external view returns (bytes4); 18 | 19 | /** 20 | * @dev Validates if a signature is valid for the provided hash. Used in newer EIP1271 versions. 21 | * @param _hash Hash of the data that was signed 22 | * @param _signature Signature of the hash 23 | * @return magicValue Magic value 0x1626ba7e if signature is valid for the hash, 0x0 otherwise 24 | * 25 | * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) 26 | * MUST allow external calls 27 | */ 28 | function isValidSignature( 29 | bytes32 _hash, 30 | bytes calldata _signature 31 | ) external view returns (bytes4); 32 | } 33 | -------------------------------------------------------------------------------- /packages/contracts/src/interfaces/IEmailTypes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import {EmailProof} from "../utils/Verifier.sol"; 5 | 6 | /// @notice Struct to hold the email authentication/authorization message. 7 | struct EmailAuthMsg { 8 | /// @notice The ID of the command template that the command in the email body should satisfy. 9 | uint templateId; 10 | /// @notice The parameters in the command of the email body, which should be taken according to the specified command template. 11 | bytes[] commandParams; 12 | /// @notice The number of skipped bytes in the command. 13 | uint skippedCommandPrefix; 14 | /// @notice The email proof containing the zk proof and other necessary information for the email verification by the verifier contract. 15 | EmailProof proof; 16 | } 17 | -------------------------------------------------------------------------------- /packages/contracts/src/interfaces/IGroth16Verifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | interface IGroth16Verifier { 5 | function verifyProof( 6 | uint[2] calldata _pA, 7 | uint[2][2] calldata _pB, 8 | uint[2] calldata _pC, 9 | uint[34] calldata _pubSignals 10 | ) external view returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /packages/contracts/src/interfaces/IVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | struct EmailProof { 5 | string domainName; // Domain name of the sender's email 6 | bytes32 publicKeyHash; // Hash of the DKIM public key used in email/proof 7 | uint timestamp; // Timestamp of the email 8 | string maskedCommand; // Masked command of the email 9 | bytes32 emailNullifier; // Nullifier of the email to prevent its reuse. 10 | bytes32 accountSalt; // Create2 salt of the account 11 | bool isCodeExist; // Check if the account code is exist 12 | bytes proof; // ZK Proof of Email 13 | } 14 | 15 | interface IVerifier { 16 | function commandBytes() external view returns (uint256); 17 | 18 | function verifyEmailProof( 19 | EmailProof memory proof 20 | ) external view returns (bool); 21 | } 22 | -------------------------------------------------------------------------------- /packages/contracts/src/libraries/DecimalUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import "@openzeppelin/contracts/utils/Strings.sol"; 5 | 6 | /// @title DecimalUtils 7 | /// @notice DecimalUtils library for converting uint256 to string with decimal places 8 | library DecimalUtils { 9 | /// @notice Convert uint256 to human readable string with decimal places 10 | /// @param value uint256 value to convert 11 | /// @return string representation of value with decimal places 12 | function uintToDecimalString(uint256 value) internal pure returns (string memory) { 13 | return uintToDecimalString(value, 18); 14 | } 15 | 16 | /// @notice Convert uint256 to human readable string with decimal places 17 | /// @param value uint256 value to convert 18 | /// @param decimal number of decimal places 19 | /// @return string representation of value with decimal places 20 | function uintToDecimalString(uint256 value, uint decimal) internal pure returns (string memory) { 21 | // Convert value to string in wei format (no decimals) 22 | bytes memory valueBytes = bytes(Strings.toString(value)); 23 | uint8 valueLength = uint8(valueBytes.length); 24 | 25 | // Create result array with max length 26 | // If less than 18 decimals, then 2 extra for "0.", otherwise one extra for "." 27 | bytes memory result = new bytes(valueLength > decimal ? valueLength + 1 : decimal + 2); 28 | uint8 resultLength = uint8(result.length); 29 | 30 | // We will be populating result array by copying from value array from last to first index 31 | // Difference between result and value array index when copying 32 | // If more than 18, then 1 index diff for ".", otherwise actual diff in length 33 | uint delta = valueLength > decimal ? 1 : resultLength - valueLength; 34 | 35 | // Boolean to indicate if we found a non-zero digit when scanning from last to first index 36 | bool foundNonZeroDecimal; 37 | 38 | uint8 actualResultLen = 0; 39 | 40 | // In each iteration we fill one index of result array (starting from end) 41 | for (uint8 i = resultLength - 1; ; i--) { 42 | // Check if we have reached the index where we need to add decimal point 43 | if (i == resultLength - decimal - 1) { 44 | // No need to add "." if there was no value in decimal places 45 | if (foundNonZeroDecimal) { 46 | result[i] = "."; 47 | actualResultLen++; 48 | } 49 | // Set delta to 0, as we have already added decimal point (only for valueLength > 18) 50 | delta = 0; 51 | } 52 | // If valueLength < 18 and we have copied everything, fill zeros 53 | else if (valueLength <= decimal && i < resultLength - valueLength) { 54 | result[i] = "0"; 55 | actualResultLen++; 56 | } 57 | // If non-zero decimal is found, or decimal point inserted (delta == 0), copy from value array 58 | else if (foundNonZeroDecimal || delta == 0) { 59 | result[i] = valueBytes[i - delta]; 60 | actualResultLen++; 61 | } 62 | // If we find non-zero decumal for the first time (trailing zeros are skipped) 63 | else if (valueBytes[i - delta] != "0") { 64 | result[i] = valueBytes[i - delta]; 65 | actualResultLen++; 66 | foundNonZeroDecimal = true; 67 | } 68 | 69 | // To prevent the last i-- underflow 70 | if (i == 0) { 71 | break; 72 | } 73 | } 74 | 75 | // Create final result array with correct length 76 | bytes memory compactResult = new bytes(actualResultLen); 77 | for (uint8 i = 0; i < actualResultLen; i++) { 78 | compactResult[i] = result[i]; 79 | } 80 | 81 | return string(compactResult); 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /packages/contracts/src/libraries/StringUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.25; 3 | 4 | import {strings} from "solidity-stringutils/src/strings.sol"; 5 | import {CommandUtils} from "./CommandUtils.sol"; 6 | 7 | /** 8 | * @title StringUtils 9 | * @notice This library provides utility functions for converting hexadecimal strings to bytes. 10 | * @author Clave & ZK Email 11 | */ 12 | library StringUtils { 13 | using strings for *; 14 | using CommandUtils for string; 15 | 16 | /** 17 | * @dev Converts a hexadecimal string to bytes. 18 | * @param hexStr The hexadecimal string to convert. Must start with "0x" prefix. 19 | * @return bytes A bytes array representing the converted hexadecimal string. 20 | */ 21 | function hexToBytes( 22 | string memory hexStr 23 | ) internal pure returns (bytes memory) { 24 | require( 25 | hexStr.toSlice().startsWith("0x".toSlice()), 26 | "invalid hex prefix" 27 | ); 28 | string memory hexStrNoPrefix = hexStr.removePrefix(2); 29 | bytes memory hexBytes = bytes(hexStrNoPrefix); 30 | require( 31 | hexBytes.length != 0 && hexBytes.length % 2 == 0, 32 | "invalid hex string length" 33 | ); 34 | 35 | bytes memory result = new bytes(hexBytes.length / 2); 36 | for (uint256 i = 0; i < hexBytes.length / 2; i++) { 37 | result[i] = bytes1( 38 | (hexChar2Int(hexBytes[2 * i]) << 4) + 39 | hexChar2Int(hexBytes[2 * i + 1]) 40 | ); 41 | } 42 | return result; 43 | } 44 | 45 | /** 46 | * @dev Converts a hexadecimal string to a bytes32 value. 47 | * @param hexStr The hexadecimal string to convert. 48 | * @return bytes32 A bytes32 value representing the converted hexadecimal string. 49 | */ 50 | function hexToBytes32( 51 | string memory hexStr 52 | ) internal pure returns (bytes32) { 53 | bytes memory result = hexToBytes(hexStr); 54 | require(result.length == 32, "bytes length is not 32"); 55 | return bytes32(result); 56 | } 57 | 58 | /** 59 | * @dev Converts a single hexadecimal character to its integer equivalent. 60 | * @param char The hexadecimal character to convert (0-9, A-F, or a-f). 61 | * @return uint8 The integer value of the hexadecimal character (0-15). 62 | */ 63 | function hexChar2Int(bytes1 char) private pure returns (uint8) { 64 | uint8 charInt = uint8(char); 65 | if (charInt >= 48 && charInt <= 57) { 66 | return charInt - 48; // '0' - '9' 67 | } else if (charInt >= 65 && charInt <= 70) { 68 | return charInt - 55; // 'A' - 'F' 69 | } else if (charInt >= 97 && charInt <= 102) { 70 | return charInt - 87; // 'a' - 'f' 71 | } else { 72 | revert("invalid hex char"); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/contracts/src/utils/Verifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import "../interfaces/IGroth16Verifier.sol"; 5 | import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; 6 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 7 | import {IVerifier, EmailProof} from "../interfaces/IVerifier.sol"; 8 | 9 | contract Verifier is OwnableUpgradeable, UUPSUpgradeable, IVerifier { 10 | IGroth16Verifier groth16Verifier; 11 | 12 | uint256 public constant DOMAIN_FIELDS = 9; 13 | uint256 public constant DOMAIN_BYTES = 255; 14 | uint256 public constant COMMAND_FIELDS = 20; 15 | uint256 public constant COMMAND_BYTES = 605; 16 | 17 | // Base field size 18 | uint256 constant q = 19 | 21888242871839275222246405745257275088696311157297823662689037894645226208583; 20 | 21 | constructor() {} 22 | 23 | /// @notice Initialize the contract with the initial owner and deploy Groth16Verifier 24 | /// @param _initialOwner The address of the initial owner 25 | function initialize( 26 | address _initialOwner, 27 | address _groth16Verifier 28 | ) public initializer { 29 | __Ownable_init(_initialOwner); 30 | groth16Verifier = IGroth16Verifier(_groth16Verifier); 31 | } 32 | 33 | function commandBytes() external pure returns (uint256) { 34 | return COMMAND_BYTES; 35 | } 36 | 37 | function verifyEmailProof( 38 | EmailProof memory proof 39 | ) public view returns (bool) { 40 | ( 41 | uint256[2] memory pA, 42 | uint256[2][2] memory pB, 43 | uint256[2] memory pC 44 | ) = abi.decode(proof.proof, (uint256[2], uint256[2][2], uint256[2])); 45 | require(pA[0] < q && pA[1] < q, "invalid format of pA"); 46 | require( 47 | pB[0][0] < q && pB[0][1] < q && pB[1][0] < q && pB[1][1] < q, 48 | "invalid format of pB" 49 | ); 50 | require(pC[0] < q && pC[1] < q, "invalid format of pC"); 51 | uint256[DOMAIN_FIELDS + COMMAND_FIELDS + 5] memory pubSignals; 52 | uint256[] memory stringFields; 53 | stringFields = _packBytes2Fields(bytes(proof.domainName), DOMAIN_BYTES); 54 | for (uint256 i = 0; i < DOMAIN_FIELDS; i++) { 55 | pubSignals[i] = stringFields[i]; 56 | } 57 | pubSignals[DOMAIN_FIELDS] = uint256(proof.publicKeyHash); 58 | pubSignals[DOMAIN_FIELDS + 1] = uint256(proof.emailNullifier); 59 | pubSignals[DOMAIN_FIELDS + 2] = uint256(proof.timestamp); 60 | stringFields = _packBytes2Fields( 61 | bytes(proof.maskedCommand), 62 | COMMAND_BYTES 63 | ); 64 | for (uint256 i = 0; i < COMMAND_FIELDS; i++) { 65 | pubSignals[DOMAIN_FIELDS + 3 + i] = stringFields[i]; 66 | } 67 | pubSignals[DOMAIN_FIELDS + 3 + COMMAND_FIELDS] = uint256( 68 | proof.accountSalt 69 | ); 70 | pubSignals[DOMAIN_FIELDS + 3 + COMMAND_FIELDS + 1] = proof.isCodeExist 71 | ? 1 72 | : 0; 73 | 74 | return groth16Verifier.verifyProof(pA, pB, pC, pubSignals); 75 | } 76 | 77 | function updateGroth16Verifier(address _groth16Verifier) public onlyOwner { 78 | require( 79 | _groth16Verifier != address(0), 80 | "New groth16Verifier address is invalid" 81 | ); 82 | groth16Verifier = IGroth16Verifier(_groth16Verifier); 83 | } 84 | 85 | function _packBytes2Fields( 86 | bytes memory _bytes, 87 | uint256 _paddedSize 88 | ) private pure returns (uint256[] memory) { 89 | uint256 remain = _paddedSize % 31; 90 | uint256 numFields = (_paddedSize - remain) / 31; 91 | if (remain > 0) { 92 | numFields += 1; 93 | } 94 | uint256[] memory fields = new uint[](numFields); 95 | uint256 idx = 0; 96 | uint256 byteVal = 0; 97 | for (uint256 i = 0; i < numFields; i++) { 98 | for (uint256 j = 0; j < 31; j++) { 99 | idx = i * 31 + j; 100 | if (idx >= _paddedSize) { 101 | break; 102 | } 103 | if (idx >= _bytes.length) { 104 | byteVal = 0; 105 | } else { 106 | byteVal = uint256(uint8(_bytes[idx])); 107 | } 108 | if (j == 0) { 109 | fields[i] = byteVal; 110 | } else { 111 | fields[i] += (byteVal << (8 * j)); 112 | } 113 | } 114 | } 115 | return fields; 116 | } 117 | 118 | /// @notice Upgrade the implementation of the proxy. 119 | /// @param newImplementation Address of the new implementation. 120 | function _authorizeUpgrade( 121 | address newImplementation 122 | ) internal override onlyOwner {} 123 | } 124 | -------------------------------------------------------------------------------- /packages/contracts/src/utils/ZKSyncCreate2Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import {ZKSyncCreate2FactoryBase} from "./ZKSyncCreate2FactoryBase.sol"; 5 | // // FOR_ZKSYNC:START 6 | // import {L2ContractHelper} from "@matterlabs/zksync-contracts/l2/contracts/L2ContractHelper.sol"; 7 | // import {SystemContractsCaller} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; 8 | // import {DEPLOYER_SYSTEM_CONTRACT} from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; 9 | // // FOR_ZKSYNC:END 10 | 11 | contract ZKSyncCreate2Factory is ZKSyncCreate2FactoryBase { 12 | // // FOR_ZKSYNC:START 13 | // function computeAddress(bytes32 salt, bytes32 bytecodeHash, bytes memory input) external override view returns (address) { 14 | // return L2ContractHelper.computeCreate2Address(address(this), salt, bytes32(bytecodeHash), keccak256(input)); 15 | // } 16 | // function deploy(bytes32 salt, bytes32 bytecodeHash, bytes memory input) 17 | // external 18 | // override 19 | // returns (bool success, bytes memory returnData) 20 | // { 21 | // (success, returnData) = SystemContractsCaller.systemCallWithReturndata( 22 | // uint32(gasleft()), 23 | // address(DEPLOYER_SYSTEM_CONTRACT), 24 | // uint128(0), 25 | // abi.encodeCall(DEPLOYER_SYSTEM_CONTRACT.create2, (salt, bytecodeHash, input)) 26 | // ); 27 | // } 28 | // // FOR_ZKSYNC:END 29 | } 30 | -------------------------------------------------------------------------------- /packages/contracts/src/utils/ZKSyncCreate2FactoryBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | contract ZKSyncCreate2FactoryBase { 5 | function computeAddress(bytes32 salt, bytes32 bytecodeHash, bytes memory input) external virtual view returns (address) { 6 | return address(0); 7 | } 8 | 9 | function deploy(bytes32 salt, bytes32 bytecodeHash, bytes memory input) 10 | external 11 | virtual 12 | returns (bool success, bytes memory returnData) 13 | { 14 | return (false, ""); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/contracts/test/ComputeCreate2AddressZKSync.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "forge-std/Test.sol"; 5 | import "forge-std/console.sol"; 6 | 7 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 8 | import {ZKSyncCreate2Factory} from "../src/utils/ZKSyncCreate2Factory.sol"; 9 | import "../src/EmailAuth.sol"; 10 | import "./helpers/StructHelper.sol"; 11 | 12 | contract ComputeCreate2AddressZKSyncTest is StructHelper { 13 | constructor() {} 14 | 15 | function testComputeCreate2Address() public { 16 | // This test is not neccessary for non zkSync chains 17 | if (block.chainid != 324 && block.chainid != 300) { 18 | console.log("skip"); 19 | return; 20 | } 21 | 22 | address recoveredAccount = address(0x1); 23 | bytes32 accountSalt = 0x0; 24 | ZKSyncCreate2Factory factory = new ZKSyncCreate2Factory(); 25 | bytes memory emailAuthInit = abi.encode( 26 | address(emailAuth), abi.encodeCall(EmailAuth.initialize, (recoveredAccount, accountSalt, address(this))) 27 | ); 28 | 29 | // See the example code 30 | // https://github.com/matter-labs/foundry-zksync/blob/13497a550e4a097c57bec7430435ab810a6d10fc/zk-tests/src/Contracts.t.sol#L195 31 | string memory artifact = vm.readFile("zkout/ERC1967Proxy.sol/ERC1967Proxy.json"); 32 | bytes32 bytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); 33 | console.log("bytecodeHash"); 34 | console.logBytes32(bytes32(bytecodeHash)); 35 | 36 | address computedAddress = factory.computeAddress(accountSalt, bytecodeHash, emailAuthInit); 37 | console.log("computedAddress", computedAddress); 38 | 39 | (bool success, bytes memory returnData) = factory.deploy(accountSalt, bytecodeHash, emailAuthInit); 40 | 41 | address payable proxyAddress = abi.decode(returnData, (address)); 42 | assertEq(proxyAddress, computedAddress); 43 | } 44 | } -------------------------------------------------------------------------------- /packages/contracts/test/DKIMRegistryUpgrade.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "forge-std/Test.sol"; 5 | import "forge-std/console.sol"; 6 | 7 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 8 | import "../src/EmailAuth.sol"; 9 | import {EmailAuthMsg} from "../src/interfaces/IEmailTypes.sol"; 10 | import "../src/utils/Verifier.sol"; 11 | import "../src/utils/ECDSAOwnedDKIMRegistry.sol"; 12 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 13 | import "./helpers/StructHelper.sol"; 14 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 15 | import {UserOverrideableDKIMRegistry} from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; 16 | 17 | contract DKIMRegistryUpgradeTest is StructHelper { 18 | function setUp() public override { 19 | super.setUp(); 20 | vm.startPrank(deployer); 21 | emailAuth.initialize(deployer, accountSalt, deployer); 22 | vm.expectEmit(true, false, false, false); 23 | emit EmailAuth.VerifierUpdated(address(verifier)); 24 | emailAuth.updateVerifier(address(verifier)); 25 | vm.expectEmit(true, false, false, false); 26 | emit EmailAuth.DKIMRegistryUpdated(address(dkim)); 27 | emailAuth.updateDKIMRegistry(address(dkim)); 28 | UserOverrideableDKIMRegistry overrideableDkimImpl = new UserOverrideableDKIMRegistry(); 29 | ERC1967Proxy overrideableDkimProxy = new ERC1967Proxy( 30 | address(overrideableDkimImpl), 31 | abi.encodeCall( 32 | overrideableDkimImpl.initialize, 33 | (deployer, deployer, setTimestampDelay) 34 | ) 35 | ); 36 | UserOverrideableDKIMRegistry overrideableDkim = UserOverrideableDKIMRegistry( 37 | address(overrideableDkimProxy) 38 | ); 39 | overrideableDkim.setDKIMPublicKeyHash( 40 | domainName, 41 | publicKeyHash, 42 | deployer, 43 | new bytes(0) 44 | ); 45 | vm.stopPrank(); 46 | } 47 | function testDkimRegistryAddr() public view { 48 | address dkimAddr = emailAuth.dkimRegistryAddr(); 49 | assertEq(dkimAddr, address(dkim)); 50 | } 51 | function _testInsertCommandTemplate() private { 52 | emailAuth.insertCommandTemplate(templateId, commandTemplate); 53 | string[] memory result = emailAuth.getCommandTemplate(templateId); 54 | assertEq(result, commandTemplate); 55 | } 56 | function testAuthEmail() public { 57 | vm.startPrank(deployer); 58 | _testInsertCommandTemplate(); 59 | EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); 60 | vm.stopPrank(); 61 | assertEq( 62 | emailAuth.usedNullifiers(emailAuthMsg.proof.emailNullifier), 63 | false 64 | ); 65 | assertEq(emailAuth.lastTimestamp(), 0); 66 | vm.startPrank(deployer); 67 | vm.expectEmit(true, true, true, true); 68 | emit EmailAuth.EmailAuthed( 69 | emailAuthMsg.proof.emailNullifier, 70 | emailAuthMsg.proof.accountSalt, 71 | emailAuthMsg.proof.isCodeExist, 72 | emailAuthMsg.templateId 73 | ); 74 | emailAuth.authEmail(emailAuthMsg); 75 | vm.stopPrank(); 76 | assertEq( 77 | emailAuth.usedNullifiers(emailAuthMsg.proof.emailNullifier), 78 | true 79 | ); 80 | assertEq(emailAuth.lastTimestamp(), emailAuthMsg.proof.timestamp); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/contracts/test/fixtures/README.md: -------------------------------------------------------------------------------- 1 | # Test Fixtures 2 | 3 | This directory contains test fixtures for `EmailAuthMsg` proofs. These fixtures can be used to verify `EmailAuthMsg` in any context. For example, in these tests, we use both the `EmailSigner` contract and the lower-level primitive, `Verifier`, to validate the proofs. JSON-encoded versions of the fixtures are also provided, along with a Solidity library to easily import and use the data in smart contracts. 4 | 5 | 6 | ## Directory Structure 7 | 8 | ``` 9 | fixtures/ 10 | ├── case1_signHash/ # First sign hash command test case 11 | │ ├── raw.eml # Email sent by the relayer to the user (reference only, not used in verification) 12 | │ └── EmailAuthMsg.json # JSON-encoded `EmailAuthMsg` generated from the user's response to raw.eml 13 | ├── case2_signHash/ # Second sign hash command test case 14 | │ ├── raw.eml 15 | │ └── EmailAuthMsg.json 16 | ├── case3_sendEth/ # Send ETH command test case 17 | │ ├── raw.eml 18 | │ └── EmailAuthMsg.json 19 | ├── case4_acceptGuardian/ # Guardian acceptance test case 20 | │ ├── raw.eml 21 | │ └── EmailAuthMsg.json 22 | ├── EmailAuthMsgFixtures.sol # Solidity wrapper for test cases 23 | ├── Groth16Verifier.sol # Auto-generated zk proof verifier 24 | └── README.md 25 | 26 | 27 | ``` 28 | 29 | ## Test Cases 30 | 31 | The fixtures cover four main test scenarios: 32 | 33 | 1. **Sign Hash (Case 1)** - Tests signing of a specific hash value 34 | - Verified through EmailSigner contract 35 | - Command Template: `signHash {uint}` 36 | - Command: `signHash 98795965305811853593942095979598763998273224478639454298374304707044663517522` 37 | 38 | 3. **Sign Hash (Case 2)** - Tests signing with a different hash value 39 | - Same as Case 1 40 | - Command: `signHash 62817409320148730591830218376583920457123489321048213478932100011234567890123` 41 | 42 | 4. **Send ETH (Case 3)** - Tests ETH transfer command 43 | - Verified through Verifier contract 44 | - Command Template: `Send {decimals} ETH to {ethAddr}` 45 | - Command: `Send 0.1 ETH to 0xafBD210c60dD651892a61804A989eEF7bD63CBA0` 46 | 47 | 5. **Accept Guardian (Case 4)** - Tests guardian acceptance 48 | - Verified through Verifier contract 49 | - Command Template: `Accept guardian request for {ethAddr}` 50 | - Command: `Accept guardian request for 0xafBD210c60dD651892a61804A989eEF7bD63CBA0` 51 | 52 | ## Usage 53 | 54 | ### Solidity Interface 55 | 56 | The `EmailAuthMsgFixtures.sol` provides a convenient Solidity interface to access these test cases: 57 | 58 | ```solidity 59 | import {EmailAuthMsgFixtures} from "./fixtures/EmailAuthMsgFixtures.sol"; 60 | 61 | // Get test cases 62 | EmailAuthMsg memory case1 = EmailAuthMsgFixtures.getCase1(); // Sign Hash 63 | EmailAuthMsg memory case2 = EmailAuthMsgFixtures.getCase2(); // Sign Hash 64 | EmailAuthMsg memory case3 = EmailAuthMsgFixtures.getCase3(); // Send ETH 65 | EmailAuthMsg memory case4 = EmailAuthMsgFixtures.getCase4(); // Accept Guardian 66 | ``` 67 | 68 | you can also import `EmailAuthMsg.json` in hardhat tests or anywhere really 69 | 70 | ### Verification Process 71 | 72 | For refrence the fixtures are verified using two different contracts based on the command type: 73 | 74 | 1. **EmailSigner Contract** - Verifies sign hash commands (Case 1 & 2) 75 | ```solidity 76 | EmailSigner(signerAddr).verifyEmail(emailAuthMsg); 77 | ``` 78 | 79 | 2. **Verifier Contract** - Verifies send ETH and guardian commands (Case 3 & 4) 80 | ```solidity 81 | Verifier(verifierAddr).verifyEmailProof(emailAuthMsg.proof); 82 | ``` 83 | 84 | ## Gorth16Verifier 85 | 86 | The `Groth16Verifier.sol` is an auto-generated contract using snarkjs that handles the zero-knowledge proof verification. It's used internally by the EmailSigner and Verifier contracts to validate the proofs contained in the test fixtures. 87 | 88 | For examples of how these fixtures are used in tests, refer to `Fixtures.t.sol`. 89 | -------------------------------------------------------------------------------- /packages/contracts/test/fixtures/case1_signHash/EmailAuthMsg.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandParams": [ 3 | "0xda6c88e5c11236cd71d4733f45851c91257ba8d4c7f99145f72848e326126152" 4 | ], 5 | "proof": { 6 | "accountSalt": "0x0ea4525e6e02fdbb98062196b1130f73f6f36b460bb3fcb1e062e8722da28c4f", 7 | "domainName": "gmail.com", 8 | "emailNullifier": "0x154b7529c9ee0651a9c8a585644fdf514ada243bb3a13f439226f5f81acb0480", 9 | "isCodeExist": true, 10 | "maskedCommand": "signHash 98795965305811853593942095979598763998273224478639454298374304707044663517522", 11 | "proof": "0x2f5a59c14c784cc1a1372afd21321219f67b14d12f037f8e54e60d856b304cae030b497830f7680ab884bb9234182b76e497abc0799b971ff98284e8a9efed5b2f1286fb8dabbff86906bb61d93586040acac5a3a287d050087fbe5c7a703d501a43e86fff29aac12cb3515253e9cbcea55f5b20c519b8c379fe5cd0a950ed2c1e8ea2c1dbb5ee4a6a8ad92235f0873692a9647d5fbf2b1f1b3f078111d9609619025ca1dd38ce32d3ae908b8043f3fe159ceef2a5751374a7566906f4475b9c2f681a64aea3ca4aa1e24b3ca0644727f774b9963f6f88949aca93b72008811c03d2ea773c4af8c654f0ce4c8cab80faf7e6074d57cd44daef2b088ebe4b7f03", 12 | "publicKeyHash": "0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788", 13 | "timestamp": 1744555398 14 | }, 15 | "skippedCommandPrefix": 0, 16 | "templateId": "0x1bd88348ccb7396aa7a29d6f7107c793b5b24b8cec1ccfdd9de3f1d61ab6c1dd" 17 | } -------------------------------------------------------------------------------- /packages/contracts/test/fixtures/case2_signHash/EmailAuthMsg.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandParams": [ 3 | "0x8ae164ca3498628c6d5c2bcd904b80c4f93d886feb7da1bca6d74a59e9c7a4cb" 4 | ], 5 | "proof": { 6 | "accountSalt": "0x23c274a6e4d88ded1bf05d44762b855545ddc134f54750adfa25e3386ba979cd", 7 | "domainName": "gmail.com", 8 | "emailNullifier": "0x1a350f11b783eae6a5fddbf960c9bf7ca55b04727a8c4b61d77a3df1cce8f9d1", 9 | "isCodeExist": true, 10 | "maskedCommand": "signHash 62817409320148730591830218376583920457123489321048213478932100011234567890123", 11 | "proof": "0x2cbec20c8c6bdda378100088a7a5705aec7247d13b06cb4544bcba7f53d5170922c84a56f91e478cdf26ec642e855fbeaafe8bc8f78b30509274f8954078cfd5115957f6bc25cd50e4181b0c22e83e34f47456c424762c8bcf3b838972fcc2092d7a71b4285e7a23b826a6bc99e35dd1bb1bd9fcf1c2801941a0564753f5b35d2790ba0c9866085c647ae741886164617236584346d852b3e0b335833e35faea009fd83613b3971ab3410cdb0ae4b51098dc30b7bae91aaa0829ecaa9ef4b3aa0338d4faba1cb22c367811a6e52a3e6113e4f6574c27ba6b30d2a12edfa3975a140c8a4261a3cec8c0ff4b0ee3cf76941cb3d9b8c21d13a5f99f97d2a71c301a", 12 | "publicKeyHash": "0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788", 13 | "timestamp": 1744555928 14 | }, 15 | "skippedCommandPrefix": 0, 16 | "templateId": "0xc093a1009d021018079012b25b827ac3754eb0993625879a8b8ec3dd5ff8b3a7" 17 | } -------------------------------------------------------------------------------- /packages/contracts/test/fixtures/case3_sendEth/EmailAuthMsg.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandParams": [ 3 | "0x000000000000000000000000000000000000000000000000016345785d8a0000", 4 | "0x000000000000000000000000afbd210c60dd651892a61804a989eef7bd63cba0" 5 | ], 6 | "proof": { 7 | "accountSalt": "0x03ae56d1cac90dc5febde08736e6ce7f7e5d9db03239bff41f4ac929446f5f83", 8 | "domainName": "gmail.com", 9 | "emailNullifier": "0x0088956aa41bd520408c38c6e1749c7e7921d71af752f8360aa09ac808ef0100", 10 | "isCodeExist": true, 11 | "maskedCommand": "Send 0.1 ETH to 0xafBD210c60dD651892a61804A989eEF7bD63CBA0", 12 | "proof": "0x26c108b983baeb89b98e1c6dc2e6b8cdfcb51840c1a8fe066e731a6636b3276b29fc2fb2ab56bd26dee30daaebb45330d3c6a20176b38884488a772eb6c727da22c6b1ce7335547c18fd62684e8f8af915775c2e78e15fc869e15893911bba761d15863f0372f800d855d99742b44ef0bfb1de0565997cf155fc56afce7c4eca21744162e9b6293d500af7e13023274a75d057f1f875b90054b97d026157a56d12fe5b2cbf31f4c1cd82f510d8a5bf8ff46a142810a03f8f9aca71db1b97485a0ee7ccf3cd75ebff97fb7fa5d7dfbbed4b2e1508906ed179054596b67f83cde105d0d61ced5b447c7218906fff48588bd2ea2ed1d952dd56107cda7b156fb666", 13 | "publicKeyHash": "0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788", 14 | "timestamp": 1744556343 15 | }, 16 | "skippedCommandPrefix": 0, 17 | "templateId": "0xe986edec44591c33db673df2346f92142660eaa9c3e88e6bf744317d2b43516a" 18 | } -------------------------------------------------------------------------------- /packages/contracts/test/fixtures/case4_acceptGuardian/EmailAuthMsg.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandParams": [ 3 | "0x000000000000000000000000afbd210c60dd651892a61804a989eef7bd63cba0" 4 | ], 5 | "proof": { 6 | "accountSalt": "0x03ae56d1cac90dc5febde08736e6ce7f7e5d9db03239bff41f4ac929446f5f83", 7 | "domainName": "gmail.com", 8 | "emailNullifier": "0x2d40a9b0e331940f1b3df6f79833f591b4cbe88bcd6fab3d2033c8f0256d6a94", 9 | "isCodeExist": true, 10 | "maskedCommand": "Accept guardian request for 0xafBD210c60dD651892a61804A989eEF7bD63CBA0", 11 | "proof": "0x256ba69feff9fbf8567081e16e97d5e6a1e08582c2091c338cd7db713aa06baf1cd76c9f756fcee010af5c105f3dac267fd7c913e988309dc830b023b890856e0306c6f1a097f40c7d2207e0f08e2f12141e8a58c9dc0aeb44dea7a64aef7f092222e924a089c363d53a80c46ec7373a38e724c2c17fe90388a0f8fbc0274c6e29afdd61a8aa790014506233793f11955fab2a5ac233e15fcea7722b162d3927007f89769cdbcbb401ba7aeaa4e642e81c3ae0df3bc8e14dba1cbd8660d947d91f23af06fac604ab75090e1bec608478c9990b5832800d3bbfd0657b2b3ae53d230e90f3b30f5cbdbc725e00b2336594cc37172301b083644dc44eb10e60874a", 12 | "publicKeyHash": "0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788", 13 | "timestamp": 1744556534 14 | }, 15 | "skippedCommandPrefix": 0, 16 | "templateId": "0x462ab7844474673315f0e17f86abf5dd680a9086a372a8b8c8c0a3c7fae198f3" 17 | } -------------------------------------------------------------------------------- /packages/contracts/test/helpers/SignerDeploymentHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "./DeploymentHelper.sol"; 5 | import {EmailSigner} from "../../src/EmailSigner.sol"; 6 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 7 | 8 | contract SignerDeploymentHelper is DeploymentHelper { 9 | EmailSigner emailSigner; 10 | 11 | function setUp() public virtual override { 12 | super.setUp(); 13 | 14 | vm.startPrank(deployer); 15 | 16 | // Create EmailSigner implementation 17 | EmailSigner emailSignerImpl = new EmailSigner(); 18 | 19 | // Deploy proxy pointing to implementation 20 | bytes memory initData = abi.encodeWithSelector( 21 | EmailSigner.initialize.selector, 22 | accountSalt, 23 | address(dkim), 24 | address(verifier), 25 | templateId 26 | ); 27 | 28 | ERC1967Proxy proxy = new ERC1967Proxy( 29 | address(emailSignerImpl), 30 | initData 31 | ); 32 | 33 | emailSigner = EmailSigner(address(proxy)); 34 | 35 | // Override the command template for signer-specific needs 36 | templateId = uint256(keccak256(abi.encodePacked("TEST", uint256(0)))); 37 | commandTemplate = ["signHash", "{uint}"]; 38 | 39 | vm.stopPrank(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/contracts/test/helpers/SignerStructHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "./SignerDeploymentHelper.sol"; 5 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | contract SignerStructHelper is SignerDeploymentHelper { 8 | function buildEmailAuthMsg( 9 | bytes32 hash 10 | ) public returns (EmailAuthMsg memory emailAuthMsg) { 11 | bytes[] memory commandParams = new bytes[](1); 12 | commandParams[0] = abi.encode(hash); 13 | 14 | EmailProof memory emailProof = EmailProof({ 15 | domainName: "gmail.com", 16 | publicKeyHash: publicKeyHash, 17 | timestamp: 1694989812, 18 | maskedCommand: string.concat( 19 | "signHash ", 20 | Strings.toString(uint256(hash)) 21 | ), 22 | emailNullifier: emailNullifier, 23 | accountSalt: accountSalt, 24 | isCodeExist: true, 25 | proof: mockProof 26 | }); 27 | 28 | emailAuthMsg = EmailAuthMsg({ 29 | templateId: templateId, 30 | commandParams: commandParams, 31 | skippedCommandPrefix: 0, 32 | proof: emailProof 33 | }); 34 | 35 | vm.mockCall( 36 | address(verifier), 37 | abi.encodeCall(Verifier.verifyEmailProof, (emailProof)), 38 | abi.encode(true) 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/contracts/test/helpers/StructHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "./DeploymentHelper.sol"; 5 | 6 | contract StructHelper is DeploymentHelper { 7 | function buildEmailAuthMsg() 8 | public 9 | returns (EmailAuthMsg memory emailAuthMsg) 10 | { 11 | bytes[] memory commandParams = new bytes[](2); 12 | commandParams[0] = abi.encode(1 ether); 13 | commandParams[1] = abi.encode( 14 | "0x0000000000000000000000000000000000000020" 15 | ); 16 | 17 | EmailProof memory emailProof = EmailProof({ 18 | domainName: "gmail.com", 19 | publicKeyHash: publicKeyHash, 20 | timestamp: 1694989812, 21 | maskedCommand: "Send 1 ETH to 0x0000000000000000000000000000000000000020", 22 | emailNullifier: emailNullifier, 23 | accountSalt: accountSalt, 24 | isCodeExist: true, 25 | proof: mockProof 26 | }); 27 | 28 | emailAuthMsg = EmailAuthMsg({ 29 | templateId: templateId, 30 | commandParams: commandParams, 31 | skippedCommandPrefix: 0, 32 | proof: emailProof 33 | }); 34 | 35 | vm.mockCall( 36 | address(verifier), 37 | abi.encodeCall(Verifier.verifyEmailProof, (emailProof)), 38 | abi.encode(true) 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/contracts/test/libraries/StringUtils/fuzz/StringUtils_fuzz_hexToBytes.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.25; 3 | 4 | import {StringUtils} from "src/libraries/StringUtils.sol"; 5 | import {StructHelper} from "../../../helpers/StructHelper.sol"; 6 | 7 | contract StringUtils_HexToBytes_Fuzz_Test is StructHelper { 8 | function setUp() public override { 9 | super.setUp(); 10 | } 11 | 12 | /// @notice Helper function to convert bytes to hex string for testing 13 | function bytesToHexString( 14 | bytes memory data 15 | ) internal pure returns (string memory) { 16 | bytes memory alphabet = "0123456789abcdef"; 17 | bytes memory str = new bytes(2 + data.length * 2); 18 | str[0] = "0"; 19 | str[1] = "x"; 20 | for (uint256 i = 0; i < data.length; i++) { 21 | str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))]; 22 | str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))]; 23 | } 24 | return string(str); 25 | } 26 | 27 | function testFuzz_hexToBytes_ComputesExpectedBytes( 28 | bytes memory expectedBytes 29 | ) public { 30 | vm.assume(expectedBytes.length > 0); 31 | string memory bytesString = bytesToHexString(expectedBytes); 32 | 33 | if (bytes(bytesString).length % 2 != 0) { 34 | vm.expectRevert("invalid hex string length"); 35 | } 36 | bytes memory bytesResult = StringUtils.hexToBytes(bytesString); 37 | 38 | assertEq(bytesResult, expectedBytes); 39 | } 40 | 41 | function testFuzz_hexToBytes_ComputesExpectedAddressBytes( 42 | address addressToBytes 43 | ) public pure { 44 | bytes memory expectedBytes = abi.encodePacked(addressToBytes); 45 | string memory bytesString = bytesToHexString(expectedBytes); 46 | 47 | bytes memory bytesResult = StringUtils.hexToBytes(bytesString); 48 | 49 | assertEq(bytesResult, expectedBytes); 50 | } 51 | 52 | function testFuzz_hexToBytes_ComputesExpectedCalldataBytes( 53 | bytes4 selector, 54 | address addressValue1, 55 | address addressValue2, 56 | bytes memory bytesValue, 57 | uint256 uintValue 58 | ) public { 59 | bytes memory expectedCalldata = abi.encodeWithSelector( 60 | selector, 61 | addressValue1, 62 | addressValue2, 63 | bytesValue, 64 | uintValue 65 | ); 66 | string memory bytesString = bytesToHexString(expectedCalldata); 67 | 68 | bytes memory bytesResult = StringUtils.hexToBytes(bytesString); 69 | 70 | assertEq(bytesResult, expectedCalldata); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/contracts/test/libraries/StringUtils/fuzz/StringUtils_fuzz_hexToBytes32.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.25; 3 | 4 | import { StringUtils } from "src/libraries/StringUtils.sol"; 5 | import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; 6 | import { StructHelper } from "../../../helpers/StructHelper.sol"; 7 | 8 | // Currently, tests in this contract are disabled on zkSync 9 | // due to LLVM issues with Strings and StringUtils libraries 10 | contract StringUtils_HexToBytes32_Fuzz_Test is StructHelper { 11 | using Strings for uint256; 12 | 13 | function setUp() public override { 14 | super.setUp(); 15 | } 16 | 17 | function testFuzz_hexToBytes32_ComputesExpectedHash(bytes32 expectedHash) public { 18 | skipIfZkSync(); 19 | 20 | string memory hashString = uint256(expectedHash).toHexString(32); 21 | bytes32 hashResult = StringUtils.hexToBytes32(hashString); 22 | 23 | assertEq(hashResult, expectedHash); 24 | } 25 | 26 | function testFuzz_hexToBytes32_ComputesExpectedAddressHash(address addressToHash) public { 27 | skipIfZkSync(); 28 | 29 | bytes32 expectedHash = keccak256(abi.encodePacked(addressToHash)); 30 | string memory hashString = uint256(expectedHash).toHexString(32); 31 | bytes32 hashResult = StringUtils.hexToBytes32(hashString); 32 | 33 | assertEq(hashResult, expectedHash); 34 | } 35 | 36 | function testFuzz_hexToBytes32_ComputesExpectedCalldataHash(bytes memory calldataToHash) 37 | public 38 | { 39 | skipIfZkSync(); 40 | 41 | bytes32 expectedHash = keccak256(calldataToHash); 42 | string memory hashString = uint256(expectedHash).toHexString(32); 43 | 44 | bytes32 hashResult = StringUtils.hexToBytes32(hashString); 45 | 46 | assertEq(hashResult, expectedHash); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/contracts/test/utils/ECDSAOwnedDKIMRegistry/changeSigner.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../../../src/utils/ECDSAOwnedDKIMRegistry.sol"; 6 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 7 | 8 | contract ECDSAOwnedDKIMRegistryTest_changeSigner is Test { 9 | ECDSAOwnedDKIMRegistry dkim; 10 | 11 | function setUp() public { 12 | address signer = vm.addr(1); 13 | ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); 14 | ERC1967Proxy dkimProxy = new ERC1967Proxy( 15 | address(dkimImpl), 16 | abi.encodeCall(dkimImpl.initialize, (msg.sender, signer)) 17 | ); 18 | dkim = ECDSAOwnedDKIMRegistry(address(dkimProxy)); 19 | } 20 | 21 | function test_ChangeSigner() public { 22 | address owner = dkim.owner(); 23 | address newSigner = vm.addr(2); 24 | 25 | vm.startPrank(owner); 26 | dkim.changeSigner(newSigner); 27 | vm.stopPrank(); 28 | 29 | assertEq(dkim.signer(), newSigner, "Signer was not changed correctly"); 30 | } 31 | 32 | function test_Revert_IfNewSignerIsZeroAddress() public { 33 | address owner = dkim.owner(); 34 | vm.startPrank(owner); 35 | 36 | vm.expectRevert("Invalid signer"); 37 | dkim.changeSigner(address(0)); 38 | } 39 | 40 | function test_Revert_IfNewSignerIsSame() public { 41 | address owner = dkim.owner(); 42 | address currentSigner = dkim.signer(); 43 | 44 | vm.startPrank(owner); 45 | vm.expectRevert("Same signer"); 46 | dkim.changeSigner(currentSigner); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/contracts/test/utils/ECDSAOwnedDKIMRegistry/computeSignedMsg.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../../../src/utils/ECDSAOwnedDKIMRegistry.sol"; 6 | 7 | contract ECDSAOwnedDKIMRegistryTest_computeSignedMsg is Test { 8 | using Strings for *; 9 | 10 | ECDSAOwnedDKIMRegistry dkim; 11 | 12 | function setUp() public { 13 | address signer = vm.addr(1); 14 | ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); 15 | dkimImpl.initialize(msg.sender, signer); 16 | dkim = dkimImpl; 17 | } 18 | 19 | function test_computeSignedMsg() public { 20 | string memory prefix = "SET:"; 21 | string memory selector = "12345"; 22 | string memory domainName = "example.com"; 23 | bytes32 publicKeyHash = bytes32(uint256(1)); 24 | 25 | string memory expectedMsg = string.concat( 26 | prefix, 27 | "domain=", 28 | domainName, 29 | ";public_key_hash=", 30 | uint256(publicKeyHash).toHexString(), 31 | ";" 32 | ); 33 | 34 | string memory computedMsg = dkim.computeSignedMsg( 35 | prefix, 36 | domainName, 37 | publicKeyHash 38 | ); 39 | 40 | assertEq( 41 | computedMsg, 42 | expectedMsg, 43 | "Computed message does not match expected message" 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/contracts/test/utils/ECDSAOwnedDKIMRegistry/isDKIMPublicKeyHashValid.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../../../src/utils/ECDSAOwnedDKIMRegistry.sol"; 6 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 7 | 8 | contract ECDSAOwnedDKIMRegistryTest_isDKIMPublicKeyHashValid is Test { 9 | ECDSAOwnedDKIMRegistry dkim; 10 | 11 | string public selector = "12345"; 12 | string public domainName = "example.com"; 13 | bytes32 public publicKeyHash = bytes32(uint256(1)); 14 | 15 | function setUp() public { 16 | address signer = vm.addr(1); 17 | ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); 18 | ERC1967Proxy dkimProxy = new ERC1967Proxy( 19 | address(dkimImpl), 20 | abi.encodeCall(dkimImpl.initialize, (msg.sender, signer)) 21 | ); 22 | dkim = ECDSAOwnedDKIMRegistry(address(dkimProxy)); 23 | } 24 | 25 | function test_IsDKIMPublicKeyHashValid_True() public { 26 | // Set a valid public key hash 27 | string memory signedMsg = dkim.computeSignedMsg( 28 | dkim.SET_PREFIX(), 29 | domainName, 30 | publicKeyHash 31 | ); 32 | bytes32 digest = MessageHashUtils.toEthSignedMessageHash( 33 | bytes(signedMsg) 34 | ); 35 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, digest); 36 | bytes memory signature = abi.encodePacked(r, s, v); 37 | dkim.setDKIMPublicKeyHash( 38 | selector, 39 | domainName, 40 | publicKeyHash, 41 | signature 42 | ); 43 | 44 | // Check if the public key hash is valid 45 | bool isValid = dkim.isDKIMPublicKeyHashValid(domainName, publicKeyHash); 46 | require(isValid, "Public key hash should be valid"); 47 | } 48 | 49 | function test_IsDKIMPublicKeyHashValid_False() public { 50 | // Check if a non-set public key hash is invalid 51 | bytes32 nonExistentPublicKeyHash = bytes32(uint256(2)); 52 | bool isValid = dkim.isDKIMPublicKeyHashValid( 53 | domainName, 54 | nonExistentPublicKeyHash 55 | ); 56 | require(!isValid, "Public key hash should not be valid"); 57 | } 58 | 59 | function test_IsDKIMPublicKeyHashValid_AfterRevoke() public { 60 | // Set and then revoke a public key hash 61 | string memory signedMsg = dkim.computeSignedMsg( 62 | dkim.SET_PREFIX(), 63 | domainName, 64 | publicKeyHash 65 | ); 66 | bytes32 digest = MessageHashUtils.toEthSignedMessageHash( 67 | bytes(signedMsg) 68 | ); 69 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, digest); 70 | bytes memory signature = abi.encodePacked(r, s, v); 71 | dkim.setDKIMPublicKeyHash( 72 | selector, 73 | domainName, 74 | publicKeyHash, 75 | signature 76 | ); 77 | 78 | // Revoke the public key hash 79 | string memory revokeMsg = dkim.computeSignedMsg( 80 | dkim.REVOKE_PREFIX(), 81 | domainName, 82 | publicKeyHash 83 | ); 84 | bytes32 revokeDigest = MessageHashUtils.toEthSignedMessageHash( 85 | bytes(revokeMsg) 86 | ); 87 | (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(1, revokeDigest); 88 | bytes memory revokeSig = abi.encodePacked(r1, s1, v1); 89 | dkim.revokeDKIMPublicKeyHash( 90 | selector, 91 | domainName, 92 | publicKeyHash, 93 | revokeSig 94 | ); 95 | 96 | // Check if the public key hash is invalid after revocation 97 | bool isValid = dkim.isDKIMPublicKeyHashValid(domainName, publicKeyHash); 98 | require( 99 | !isValid, 100 | "Public key hash should not be valid after revocation" 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/contracts/test/utils/Verifier.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.12; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../../src/utils/Verifier.sol"; 6 | import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 7 | 8 | contract VerifierTest is Test { 9 | Verifier public verifier; 10 | address public deployer; 11 | 12 | function setUp() public { 13 | deployer = address(this); 14 | verifier = new Verifier(); 15 | } 16 | 17 | function testUpgradeVerifier() public { 18 | // Deploy new implementation 19 | Verifier newImplementation = new Verifier(); 20 | 21 | // Execute upgrade using proxy 22 | ERC1967Proxy proxy = new ERC1967Proxy( 23 | address(verifier), 24 | abi.encodeCall( 25 | verifier.initialize, 26 | (deployer, address(0)) // Assuming a dummy address for groth16Verifier 27 | ) 28 | ); 29 | Verifier verifierProxy = Verifier(address(proxy)); 30 | 31 | // Store initial values for comparison 32 | uint256 initialDomainFields = verifierProxy.DOMAIN_FIELDS(); 33 | uint256 initialDomainBytes = verifierProxy.DOMAIN_BYTES(); 34 | 35 | // Upgrade to new implementation through proxy 36 | verifierProxy.upgradeToAndCall( 37 | address(newImplementation), 38 | new bytes(0) 39 | ); 40 | 41 | // Verify the upgrade 42 | assertEq(verifierProxy.DOMAIN_FIELDS(), initialDomainFields); 43 | assertEq(verifierProxy.DOMAIN_BYTES(), initialDomainBytes); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/prover/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nvidia/cuda:12.4.0-devel-ubuntu22.04 2 | 3 | RUN apt-get update && apt-get upgrade -y 4 | # Update the package list and install necessary dependencies 5 | RUN apt-get update && \ 6 | DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends \ 7 | cmake \ 8 | build-essential \ 9 | pkg-config \ 10 | libssl-dev \ 11 | libgmp-dev \ 12 | libffi-dev \ 13 | libsodium-dev \ 14 | nasm \ 15 | git \ 16 | awscli \ 17 | gcc \ 18 | nodejs \ 19 | npm \ 20 | curl \ 21 | m4 \ 22 | python3 \ 23 | python3-pip \ 24 | python3-dev \ 25 | wget \ 26 | software-properties-common \ 27 | unzip \ 28 | && rm -rf /var/lib/apt/lists/* 29 | 30 | # Set Python 3 as the default python version 31 | RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 1 \ 32 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 33 | 34 | # Node install 35 | RUN npm install -g n 36 | RUN n 22 37 | RUN npm install -g yarn snarkjs 38 | 39 | 40 | RUN git clone https://github.com/zkemail/ether-email-auth.git 41 | WORKDIR /ether-email-auth/packages/prover 42 | RUN pip install -r requirements.txt 43 | RUN cp ./circom_proofgen.sh /root 44 | # RUN cp ./email_auth_with_body_parsing_with_qp_encoding /root 45 | WORKDIR /root 46 | RUN ls /root 47 | RUN mkdir params 48 | WORKDIR /root/params 49 | 50 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_prod1.zkey --output ./email_auth.zkey 51 | # RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth.wasm --output ./email_auth.wasm 52 | RUN mkdir ./email_auth_cpp 53 | WORKDIR /root/params/email_auth_cpp 54 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/email_auth --output ./email_auth 55 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/Makefile --output ./Makefile 56 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/calcwit.cpp --output ./calcwit.cpp 57 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/calcwit.hpp --output ./calcwit.hpp 58 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/calcwit.o --output ./calcwit.o 59 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/circom.hpp --output ./circom.hpp 60 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/email_auth.cpp --output ./email_auth.cpp 61 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/email_auth.dat --output ./email_auth.dat 62 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr.asm --output ./fr.asm 63 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr.cpp --output ./fr.cpp 64 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr.hpp --output ./fr.hpp 65 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr.o --output ./fr.o 66 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr_asm.o --output ./fr_asm.o 67 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/main.cpp --output ./main.cpp 68 | RUN curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/main.o --output ./main.o 69 | RUN chmod +x ./email_auth 70 | WORKDIR /root 71 | RUN ls params 72 | RUN ls params/email_auth_cpp 73 | RUN chmod +x circom_proofgen.sh 74 | RUN mkdir build 75 | 76 | RUN git clone https://github.com/Orbiter-Finance/rapidsnark.git rapidsnark 77 | WORKDIR /root/rapidsnark 78 | RUN yarn 79 | RUN git submodule init 80 | RUN git submodule update 81 | RUN ./build_gmp.sh host 82 | RUN mkdir build_prover 83 | WORKDIR /root/rapidsnark/build_prover 84 | RUN cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../package -DNVML_LIBRARY=/usr/local/cuda-12.4/targets/x86_64-linux/lib/stubs/libnvidia-ml.so 85 | RUN make -j$(nproc) && make install 86 | RUN chmod +x ../package/bin/prover_cuda 87 | WORKDIR /root -------------------------------------------------------------------------------- /packages/prover/circom_proofgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # Stop on error 3 | 4 | if [ $# -ne 5 ]; then 5 | echo "Usage: $0 " 6 | exit 1 7 | fi 8 | 9 | circuitName=$1 10 | nonce=$2 11 | paramsDir=$3 12 | buildDir=$4 13 | isLocal=$5 14 | SCRIPT_DIR=$(cd $(dirname $0); pwd) 15 | 16 | input_path="${buildDir}/input_${circuitName}_${nonce}.json" 17 | witness_path="${buildDir}/witness_${circuitName}_${nonce}.wtns" 18 | proof_path="${buildDir}/rapidsnark_proof_${circuitName}_${nonce}.json" 19 | public_path="${buildDir}/rapidsnark_public_${circuitName}_${nonce}.json" 20 | 21 | cd "${SCRIPT_DIR}" 22 | echo "entered zk email path: ${SCRIPT_DIR}" 23 | 24 | ${paramsDir}/${circuitName}_cpp/${circuitName} "${input_path}" "${witness_path}" 25 | # echo "NODE_OPTIONS='--max-old-space-size=644000' snarkjs wc "${paramsDir}/${circuitName}.wasm" "${input_path}" "${witness_path}"" 26 | # NODE_OPTIONS='--max-old-space-size=644000' snarkjs wc "${paramsDir}/${circuitName}.wasm" "${input_path}" "${witness_path}" | tee /dev/stderr 27 | status_jswitgen=$? 28 | echo "✓ Finished witness generation with c++! ${status_jswitgen}" 29 | 30 | if [ $isLocal = 1 ]; then 31 | # DEFAULT SNARKJS PROVER (SLOW) 32 | NODE_OPTIONS='--max-old-space-size=644000' snarkjs groth16 prove "${paramsDir}/${circuitName}.zkey" "${witness_path}" "${proof_path}" "${public_path}" 33 | status_prover=$? 34 | echo "✓ Finished slow proofgen! Status: ${status_prover}" 35 | else 36 | # RAPIDSNARK PROVER GPU 37 | echo "ldd ${SCRIPT_DIR}/rapidsnark/package/bin/prover_cuda" 38 | ldd "${SCRIPT_DIR}/rapidsnark/package/bin/prover_cuda" 39 | status_lld=$? 40 | echo "✓ lld prover dependencies present! ${status_lld}" 41 | 42 | echo "${SCRIPT_DIR}/rapidsnark/package/bin/prover_cuda ${paramsDir}/${circuitName}.zkey ${witness_path} ${proof_path} ${public_path}" 43 | "${SCRIPT_DIR}/rapidsnark/package/bin/prover_cuda" "${paramsDir}/${circuitName}.zkey" "${witness_path}" "${proof_path}" "${public_path}" 44 | status_prover=$? 45 | echo "✓ Finished rapid proofgen! Status: ${status_prover}" 46 | fi 47 | 48 | 49 | 50 | # TODO: Upgrade debug -> release and edit dockerfile to use release 51 | # echo "${HOME}/relayer/target/release/relayer chain false ${prover_output_path} ${nonce}" 52 | # "${HOME}/relayer/target/release/relayer" chain false "${prover_output_path}" "${nonce}" 2>&1 | tee /dev/stderr 53 | # status_chain=$? 54 | # echo "✓ Finished send to chain! Status: ${status_chain}" 55 | 56 | exit 0 -------------------------------------------------------------------------------- /packages/prover/core.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import json 4 | 5 | import logging 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def gen_email_auth_proof(nonce: str, is_local: bool, input: dict) -> dict: 11 | circuit_name = "email_auth" 12 | print("Store input") 13 | store_input(circuit_name, nonce, input) 14 | print("Generate proof") 15 | gen_proof(circuit_name, nonce, is_local) 16 | print("Load proof") 17 | proof = load_proof(circuit_name, nonce) 18 | pub_signals = load_pub_signals(circuit_name, nonce) 19 | return {"proof": proof, "pub_signals": pub_signals} 20 | 21 | 22 | def store_input(circuit_name: str, nonce: str, json_data: dict): 23 | cur_dir = get_cur_dir() 24 | build_dir = os.path.join(cur_dir, "build") 25 | # check if build_dir exists 26 | if not os.path.exists(build_dir): 27 | os.makedirs(build_dir) 28 | 29 | json_file_path = os.path.join( 30 | build_dir, "input_" + circuit_name + "_" + nonce + ".json" 31 | ) 32 | logger.info(f"Store user input to {json_file_path}") 33 | with open(json_file_path, "w") as json_file: 34 | json_file.write(json_data) 35 | # Read the file back 36 | with open(json_file_path, "r") as json_file: 37 | print(json_file.read()) 38 | print("Stored input") 39 | 40 | 41 | def load_proof(circuit_name: str, nonce: str) -> dict: 42 | cur_dir = get_cur_dir() 43 | build_dir = os.path.join(cur_dir, "build") 44 | json_file_path = os.path.join( 45 | build_dir, "rapidsnark_proof_" + circuit_name + "_" + nonce + ".json" 46 | ) 47 | logger.info(f"Loading proof from {json_file_path}") 48 | with open(json_file_path, "r") as json_file: 49 | return json.loads(json_file.read()) 50 | 51 | 52 | def load_pub_signals(circuit_name: str, nonce: str) -> dict: 53 | cur_dir = get_cur_dir() 54 | build_dir = os.path.join(cur_dir, "build") 55 | json_file_path = os.path.join( 56 | build_dir, "rapidsnark_public_" + circuit_name + "_" + nonce + ".json" 57 | ) 58 | logger.info(f"Loading public signals from {json_file_path}") 59 | with open(json_file_path, "r") as json_file: 60 | return json.loads(json_file.read()) 61 | 62 | 63 | def gen_proof(circuit_name: str, nonce: str, is_local: bool): 64 | is_local_int: int = 1 if is_local else 0 65 | cur_dir = get_cur_dir() 66 | params_dir = os.path.join(cur_dir, "params") 67 | logger.info(f"Params dir: {params_dir}") 68 | build_dir = os.path.join(cur_dir, "build") 69 | logger.info(f"Build dir: {build_dir}") 70 | result = subprocess.run( 71 | [ 72 | os.path.join(cur_dir, "circom_proofgen.sh"), 73 | circuit_name, 74 | nonce, 75 | params_dir, 76 | build_dir, 77 | str(is_local_int), 78 | ] 79 | ) 80 | logger.info(f"Proof generation result: {result.returncode}") 81 | if result.stderr is not None: 82 | logger.error(result.stderr) 83 | print(result.stdout) 84 | print(result.stderr) 85 | 86 | 87 | def get_cur_dir() -> str: 88 | return os.path.dirname(os.path.abspath(__file__)) 89 | -------------------------------------------------------------------------------- /packages/prover/local.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | from flask import Flask, request, jsonify 5 | import random 6 | import sys 7 | from core import ( 8 | gen_email_auth_proof, 9 | ) 10 | import logging 11 | 12 | app = Flask(__name__) 13 | 14 | 15 | @app.route("/prove/email_auth", methods=["POST"]) 16 | def prove_email_auth(): 17 | logger = logging.getLogger(__name__) 18 | req = request.get_json() 19 | logger.info(req) 20 | input = req["input"] 21 | # print(input) 22 | # print(type(input)) 23 | nonce = random.randint( 24 | 0, 25 | sys.maxsize, 26 | ) 27 | logger.info(nonce) 28 | proof = gen_email_auth_proof(str(nonce), True, input, logger) 29 | logger.info(proof) 30 | return jsonify(proof) 31 | 32 | 33 | if __name__ == "__main__": 34 | from waitress import serve 35 | 36 | serve(app, host="0.0.0.0", port=8080) 37 | -------------------------------------------------------------------------------- /packages/prover/local_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # Stop on error 3 | 4 | mkdir -p build 5 | 6 | npm install -g snarkjs@latest 7 | pip install -r requirements.txt 8 | mkdir build && cd build 9 | # gdown "https://drive.google.com/uc?id=1XDPFIL5YK8JzLGoTjmHLXO9zMDjSQcJH" 10 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_prod1.zkey --output ./email_auth.zkey 11 | mkdir ./email_auth_cpp 12 | cd ./email_auth_cpp 13 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth --output ./email_auth 14 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/Makefile --output ./Makefile 15 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/calcwit.cpp --output ./calcwit.cpp 16 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/calcwit.hpp --output ./calcwit.hpp 17 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/calcwit.o --output ./calcwit.o 18 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/circom.hpp --output ./circom.hpp 19 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/email_auth.cpp --output ./email_auth.cpp 20 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/email_auth.dat --output ./email_auth.dat 21 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr.asm --output ./fr.asm 22 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr.cpp --output ./fr.cpp 23 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr.hpp --output ./fr.hpp 24 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr.o --output ./fr.o 25 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/fr_asm.o --output ./fr_asm.o 26 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/main.cpp --output ./main.cpp 27 | curl https://storage.googleapis.com/circom-ether-email-auth/v1.0.2/email_auth_cpp/main.o --output ./main.o 28 | chmod +x ./email_auth 29 | cd ../../ 30 | # unzip params.zip 31 | # curl https://email-wallet-trusted-setup-ceremony-pse-p0tion-production.s3.eu-central-1.amazonaws.com/circuits/emailwallet-account-creation/contributions/emailwallet-account-creation_00019.zkey --output /root/params/account_creation.zkey 32 | # curl https://email-wallet-trusted-setup-ceremony-pse-p0tion-production.s3.eu-central-1.amazonaws.com/circuits/emailwallet-account-init/contributions/emailwallet-account-init_00007.zkey --output /root/params/account_init.zkey 33 | # curl https://email-wallet-trusted-setup-ceremony-pse-p0tion-production.s3.eu-central-1.amazonaws.com/circuits/emailwallet-account-transport/contributions/emailwallet-account-transport_00005.zkey --output /root/params/account_transport.zkey 34 | # curl https://email-wallet-trusted-setup-ceremony-pse-p0tion-production.s3.eu-central-1.amazonaws.com/circuits/emailwallet-claim/contributions/emailwallet-claim_00006.zkey --output /root/params/claim.zkey 35 | # curl https://email-wallet-trusted-setup-ceremony-pse-p0tion-production.s3.eu-central-1.amazonaws.com/circuits/emailwallet-email-sender/contributions/emailwallet-email-sender_00006.zkey --output /root/params/email_sender.zkey 36 | chmod +x circom_proofgen.sh -------------------------------------------------------------------------------- /packages/prover/modal_server.py: -------------------------------------------------------------------------------- 1 | import modal 2 | 3 | import logging 4 | from google.cloud.logging import Client 5 | from google.cloud.logging.handlers import CloudLoggingHandler 6 | from google.cloud.logging_v2.handlers import setup_logging 7 | from google.oauth2 import service_account 8 | 9 | app = modal.App("email-tx-builder-prover-v1.0.0") 10 | 11 | image = modal.Image.from_dockerfile("Dockerfile") 12 | 13 | 14 | @app.function( 15 | image=image, 16 | mounts=[ 17 | modal.Mount.from_local_python_packages("core"), 18 | ], 19 | cpu=2, 20 | gpu="t4", 21 | secrets=[modal.Secret.from_name("gc-ether-email-auth-prover")], 22 | keep_warm=True, 23 | timeout=3600, 24 | ) 25 | @modal.wsgi_app() 26 | def flask_app(): 27 | from flask import Flask, request, jsonify 28 | import random 29 | import sys 30 | import json 31 | import os 32 | from core import ( 33 | gen_email_auth_proof, 34 | ) 35 | 36 | app = Flask(__name__) 37 | service_account_info = json.loads(os.environ["SERVICE_ACCOUNT_JSON"]) 38 | credentials = service_account.Credentials.from_service_account_info( 39 | service_account_info 40 | ) 41 | logging_client = Client(project="zkairdrop", credentials=credentials) 42 | print(logging_client) 43 | handler = CloudLoggingHandler(logging_client, name="ether-email-auth-prover") 44 | print(handler) 45 | setup_logging(handler) 46 | 47 | @app.post("/prove/email_auth") 48 | def prove_email_auth(): 49 | print("prove_email_auth") 50 | req = request.get_json() 51 | input = req["input"] 52 | logger = logging.getLogger(__name__) 53 | logger.info(req) 54 | print(req) 55 | nonce = random.randint( 56 | 0, 57 | sys.maxsize, 58 | ) 59 | logger.info(nonce) 60 | print(nonce) 61 | proof = gen_email_auth_proof(str(nonce), False, input) 62 | logger.info(proof) 63 | print(proof) 64 | return jsonify(proof) 65 | 66 | return app 67 | -------------------------------------------------------------------------------- /packages/prover/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | waitress 3 | gdown 4 | jsonify 5 | requests 6 | modal 7 | google 8 | google-cloud-logging -------------------------------------------------------------------------------- /packages/relayer/.sqlx/query-4097c486517af7c203aedf19c79a27c94af3e79af391351d7f54832536b656c3.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO requests (email_tx_auth) VALUES ($1) RETURNING id", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Jsonb" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "4097c486517af7c203aedf19c79a27c94af3e79af391351d7f54832536b656c3" 22 | } 23 | -------------------------------------------------------------------------------- /packages/relayer/.sqlx/query-51babdcefaa164b8d2498552e2924c83c6394dc0c6ae546f72a5cb08c2fa5485.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE requests SET status = $1 WHERE id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | { 9 | "Custom": { 10 | "name": "status_enum", 11 | "kind": { 12 | "Enum": [ 13 | "Request received", 14 | "Email sent", 15 | "Email response received", 16 | "Proving", 17 | "Performing on chain transaction", 18 | "Finished" 19 | ] 20 | } 21 | } 22 | }, 23 | "Uuid" 24 | ] 25 | }, 26 | "nullable": [] 27 | }, 28 | "hash": "51babdcefaa164b8d2498552e2924c83c6394dc0c6ae546f72a5cb08c2fa5485" 29 | } 30 | -------------------------------------------------------------------------------- /packages/relayer/.sqlx/query-7aefad132b94f2d7cc3baf2e9cf9ee83b772ea1e914666757da17235fc27fa4c.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO email_auth_messages (request_id, response) VALUES ($1, $2)", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Jsonb" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "7aefad132b94f2d7cc3baf2e9cf9ee83b772ea1e914666757da17235fc27fa4c" 15 | } 16 | -------------------------------------------------------------------------------- /packages/relayer/.sqlx/query-87f63ec8051ab2fcd3fc963b1f3e0ec9d94a412ad0afd53e96c2c42d5c52ef36.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT response FROM email_auth_messages WHERE request_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "response", 9 | "type_info": "Jsonb" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Text" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "87f63ec8051ab2fcd3fc963b1f3e0ec9d94a412ad0afd53e96c2c42d5c52ef36" 22 | } 23 | -------------------------------------------------------------------------------- /packages/relayer/.sqlx/query-a8157c6e1edb1f3467740df878063d4727b69fac81fbe51b28c1fce828de51ea.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO expected_replies (message_id, request_id) VALUES ($1, $2)", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Varchar", 9 | "Varchar" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "a8157c6e1edb1f3467740df878063d4727b69fac81fbe51b28c1fce828de51ea" 15 | } 16 | -------------------------------------------------------------------------------- /packages/relayer/.sqlx/query-e884c0a99d266e857015603cdd7f29bfd5adf815dfcc1e2159c61276f262831e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n SELECT \n id, \n status as \"status: RequestStatus\", \n updated_at::timestamp as \"updated_at: NaiveDateTime\",\n email_tx_auth as \"email_tx_auth: Json\"\n FROM requests \n WHERE id = $1\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "status: RequestStatus", 14 | "type_info": { 15 | "Custom": { 16 | "name": "status_enum", 17 | "kind": { 18 | "Enum": [ 19 | "Request received", 20 | "Email sent", 21 | "Email response received", 22 | "Proving", 23 | "Performing on chain transaction", 24 | "Finished" 25 | ] 26 | } 27 | } 28 | } 29 | }, 30 | { 31 | "ordinal": 2, 32 | "name": "updated_at: NaiveDateTime", 33 | "type_info": "Timestamp" 34 | }, 35 | { 36 | "ordinal": 3, 37 | "name": "email_tx_auth: Json", 38 | "type_info": "Jsonb" 39 | } 40 | ], 41 | "parameters": { 42 | "Left": [ 43 | "Uuid" 44 | ] 45 | }, 46 | "nullable": [ 47 | false, 48 | false, 49 | null, 50 | false 51 | ] 52 | }, 53 | "hash": "e884c0a99d266e857015603cdd7f29bfd5adf815dfcc1e2159c61276f262831e" 54 | } 55 | -------------------------------------------------------------------------------- /packages/relayer/CODING_GUIDELINES.md: -------------------------------------------------------------------------------- 1 | # Coding Guidelines for Relayer 2 | 3 | This document outlines the coding guidelines for contributing to the Relayer. Following these guidelines will help maintain a consistent and high-quality codebase. 4 | 5 | ## 1. Code Formatting 6 | 7 | - **Tool**: Use `rustfmt` to automatically format your code. Ensure that all code is formatted before committing. Run `cargo fmt` to format your code according to the project's style guidelines. 8 | - **Indentation**: Use 4 spaces per indentation level. Do not use tabs. 9 | - **Line Length**: Aim to keep lines under 100 characters, but it's not a strict rule. Use your judgment to ensure readability. 10 | - **Imports**: Group imports into four sections: `extern crate`, `use`, `use crate`, and `use super`. 11 | - Example: 12 | ```rust 13 | extern crate serde; 14 | 15 | use std::collections::HashMap; 16 | use std::io::{self, Read}; 17 | 18 | use crate::utils::config; 19 | 20 | use super::super::common::logger; 21 | ``` 22 | - **Braces**: Use the Allman style for braces, where the opening brace is on the same line as the function signature. 23 | - Example: 24 | ```rust 25 | fn main() { 26 | // function body 27 | } 28 | ``` 29 | - **Comments**: Use `//` for single-line comments and `/* ... */` for multi-line comments. 30 | - **Whitespace**: Use a single space after commas and colons, and no space before commas and colons. 31 | - Example: 32 | ```rust 33 | let numbers = vec![1, 2, 3]; 34 | let user = User { name: "Alice", age: 30 }; 35 | ``` 36 | - **Function Length**: Aim to keep functions short and focused. If a function is too long, consider breaking it up into smaller functions. 37 | - **Code Duplication**: Avoid duplicating code. If you find yourself copying and pasting code, consider refactoring it into a shared function or module. 38 | - **No warnings**: Ensure that your code compiles without warnings. Fix any warnings before committing. 39 | 40 | ## 2. Code Linting 41 | 42 | - **Tool**: Use `cargo clippy` to lint your code and catch common mistakes and improve your Rust code. Run `cargo clippy` before committing your code to ensure it adheres to Rust's best practices and the project's specific requirements. 43 | - **Handling Lints**: Address all warnings and errors reported by `clippy`. If you must ignore a lint, use `#[allow(clippy::lint_name)]` and provide a comment explaining why. 44 | 45 | ## 3. Naming Conventions 46 | 47 | - **Variables and Functions**: Use `snake_case`. 48 | - Example: `let user_name = "Alice";` 49 | - **Structs and Enums**: Use `PascalCase`. 50 | - Example: `struct UserAccount { ... }` 51 | - **Constants**: Use `UPPER_SNAKE_CASE`. 52 | - Example: `const MAX_USERS: u32 = 100;` 53 | - **Module Names**: Use `snake_case`. 54 | - Example: `mod user_account;` 55 | 56 | ## 4. Documentation 57 | 58 | - **Public Items**: All public functions, structs, and modules must have documentation comments using `///`. 59 | - Example: 60 | ```rust 61 | /// Creates a new user account. 62 | /// 63 | /// # Arguments 64 | /// 65 | /// * `name` - The name of the user. 66 | /// 67 | /// # Returns 68 | /// 69 | /// A `UserAccount` struct. 70 | pub fn create_user_account(name: &str) -> UserAccount { 71 | // function body 72 | } 73 | ``` 74 | - **Private Items**: Document private items where the intent or functionality is not immediately clear. 75 | - **Module Documentation**: Each module should have a comment at the top explaining its purpose. 76 | - Example: 77 | ```rust 78 | //! This module contains utility functions for user management. 79 | 80 | // module contents 81 | ``` 82 | 83 | ## 5. Error Handling 84 | 85 | - **Use of `Result` and `Option`**: 86 | - Use `Result` for operations that can fail and `Option` for values that may or may not be present. 87 | - Example: 88 | ```rust 89 | fn find_user(id: u32) -> Option { 90 | // function body 91 | } 92 | 93 | fn open_file(path: &str) -> Result { 94 | // function body 95 | } 96 | ``` 97 | - **Custom Error Types**: When appropriate, define custom error types using `enum` and implement the `anyhow::Error` trait. 98 | - **Error Propagation**: Propagate errors using `?` where possible to simplify error handling. -------------------------------------------------------------------------------- /packages/relayer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "email-tx-builder-relayer" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.89" 8 | axum = "0.7.7" 9 | serde = { version = "1.0.210", features = ["derive"] } 10 | serde_json = "1.0.128" 11 | sqlx = { version = "0.8.2", features = [ 12 | "postgres", 13 | "runtime-tokio", 14 | "migrate", 15 | "uuid", 16 | "time", 17 | "chrono", 18 | "bigdecimal", 19 | ] } 20 | bigdecimal = "0.3.1" 21 | tokio = { version = "1.40.0", features = ["full"] } 22 | tower-http = { version = "0.6.1", features = ["cors"] } 23 | relayer-utils = { git = "https://github.com/zkemail/relayer-utils.git", branch = "main" } 24 | slog = { version = "2.7.0", features = [ 25 | "max_level_trace", 26 | "release_max_level_warn", 27 | ] } 28 | uuid = { version = "1.10.0", features = ["serde", "v4"] } 29 | chrono = { version = "0.4.38", features = ["serde"] } 30 | ethers = "2.0.14" 31 | reqwest = { version = "0.12.8", features = ["json"] } 32 | handlebars = "6.1.0" 33 | regex = "1.11.0" 34 | ic-agent = { version = "0.37.1", features = ["pem", "reqwest"] } 35 | ic-utils = "0.37.0" 36 | candid = "0.10.10" 37 | lazy_static = "1.5.0" 38 | 39 | [build-dependencies] 40 | ethers = "2.0.14" 41 | -------------------------------------------------------------------------------- /packages/relayer/README.md: -------------------------------------------------------------------------------- 1 | # Generic Relayer 2 | 3 | ## Build 4 | 5 | make sure to build the contracts before building the relayer 6 | 7 | ``` 8 | cd ../contracts 9 | yarn && yarn build 10 | ``` 11 | 12 | cd back into the relayer and build it 13 | 14 | ``` 15 | cargo build 16 | ``` 17 | 18 | ## Setup the local development environment 19 | 20 | cd into the root directory and run 21 | 22 | ``` 23 | docker compose up --build 24 | ``` 25 | 26 | This will build the docker images for the node, bundler, scanner, smtp, and imap services. 27 | 28 | ## Applying the migrations 29 | 30 | ``` 31 | cd packages/relayer 32 | DATABASE_URL=postgres://relayer:relayer_password@localhost:5432/relayer sqlx migrate run 33 | ``` 34 | -------------------------------------------------------------------------------- /packages/relayer/build.rs: -------------------------------------------------------------------------------- 1 | use ethers::contract::Abigen; 2 | 3 | fn main() { 4 | Abigen::new( 5 | "EmailAuth", 6 | "../contracts/artifacts/EmailAuth.sol/EmailAuth.json", 7 | ) 8 | .unwrap() 9 | .generate() 10 | .unwrap() 11 | .write_to_file("./src/abis/email_auth.rs") 12 | .unwrap(); 13 | // Abigen::new( 14 | // "ECDSAOwnedDKIMRegistry", 15 | // "../contracts/artifacts/ECDSAOwnedDKIMRegistry.sol/ECDSAOwnedDKIMRegistry.json", 16 | // ) 17 | // .unwrap() 18 | // .generate() 19 | // .unwrap() 20 | // .write_to_file("./src/abis/ecdsa_owned_dkim_registry.rs") 21 | // .unwrap(); 22 | Abigen::new( 23 | "UserOverridableDKIMRegistry", 24 | "../contracts/artifacts/UserOverrideableDKIMRegistry.sol/UserOverrideableDKIMRegistry.json", 25 | ) 26 | .unwrap() 27 | .generate() 28 | .unwrap() 29 | .write_to_file("./src/abis/user_overridable_dkim_registry.rs") 30 | .unwrap(); 31 | } 32 | -------------------------------------------------------------------------------- /packages/relayer/config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8000, 3 | "databaseUrl": "postgres://relayer:relayer_password@db:5432/relayer", 4 | "smtpUrl": "http://smtp_server_1:3000", 5 | "prover": { 6 | "url": "https://prover.zk.email/api/prove", 7 | "apiKey": "", 8 | "blueprintId": "7f3c3bc2-7c5d-4682-8d7f-f3d2f9046722", 9 | "zkeyDownloadUrl": "https://storage.googleapis.com/circom-ether-email-auth/v2.0.2-dev/circuit_zkey.zip", 10 | "circuitCppDownloadUrl": "https://storage.googleapis.com/circom-ether-email-auth/v2.0.2-dev/circuit.zip" 11 | }, 12 | "path": { 13 | "pem": "./.ic.pem", 14 | "emailTemplates": "./email_templates" 15 | }, 16 | "icp": { 17 | "dkim_canisterId": "fxmww-qiaaa-aaaaj-azu7a-cai", 18 | "wallet_canisterId": "", 19 | "icReplicaUrl": "https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=fxmww-qiaaa-aaaaj-azu7a-cai" 20 | }, 21 | "chains": { 22 | "baseSepolia": { 23 | "privateKey": "", 24 | "rpcUrl": "https://sepolia.base.org", 25 | "chainId": 84532 26 | }, 27 | "sepolia": { 28 | "privateKey": "", 29 | "rpcUrl": "https://rpc.sepolia.org", 30 | "chainId": 11155111 31 | }, 32 | "anvil": { 33 | "privateKey": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", 34 | "rpcUrl": "node:8545", 35 | "chainId": 1337 36 | } 37 | }, 38 | "jsonLogger": false 39 | } -------------------------------------------------------------------------------- /packages/relayer/migrations/20241008135456_init.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | 3 | DROP TABLE IF EXISTS requests; 4 | 5 | DROP TABLE IF EXISTS expected_replies; 6 | 7 | DO $$ 8 | BEGIN 9 | IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'status_enum') THEN 10 | DROP TYPE status_enum; 11 | END IF; 12 | END $$; -------------------------------------------------------------------------------- /packages/relayer/migrations/20241008135456_init.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | 3 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 4 | 5 | DO $$ 6 | BEGIN 7 | IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'status_enum') THEN 8 | CREATE TYPE status_enum AS ENUM ('Request received', 'Email sent', 'Email response received', 'Proving', 'Performing on chain transaction', 'Finished'); 9 | END IF; 10 | END $$; 11 | 12 | CREATE TABLE IF NOT EXISTS requests ( 13 | id UUID PRIMARY KEY NOT NULL DEFAULT (uuid_generate_v4()), 14 | status status_enum NOT NULL DEFAULT 'Request received', 15 | updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 16 | email_tx_auth JSONB NOT NULL 17 | ); 18 | 19 | CREATE TABLE IF NOT EXISTS expected_replies ( 20 | message_id VARCHAR(255) PRIMARY KEY, 21 | request_id VARCHAR(255), 22 | has_reply BOOLEAN DEFAULT FALSE, 23 | created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() 24 | ); -------------------------------------------------------------------------------- /packages/relayer/migrations/20241128143748_create_email_auth_messages.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS email_auth_messages; -------------------------------------------------------------------------------- /packages/relayer/migrations/20241128143748_create_email_auth_messages.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE email_auth_messages ( 2 | id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 3 | request_id TEXT NOT NULL, 4 | response JSONB NOT NULL 5 | ); 6 | 7 | CREATE INDEX idx_email_auth_messages_request_id ON email_auth_messages(request_id); -------------------------------------------------------------------------------- /packages/relayer/src/abis/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] 2 | 3 | 4 | #[cfg_attr(rustfmt, rustfmt::skip)] 5 | pub mod email_auth; 6 | #[cfg_attr(rustfmt, rustfmt::skip)] 7 | pub mod user_overridable_dkim_registry; 8 | 9 | pub use email_auth::*; 10 | pub use user_overridable_dkim_registry::UserOverridableDKIMRegistry; 11 | -------------------------------------------------------------------------------- /packages/relayer/src/chain.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use abis::UserOverridableDKIMRegistry; 3 | use anyhow::anyhow; 4 | use config::ChainConfig; 5 | use ethers::prelude::*; 6 | use ethers::signers::Signer; 7 | use ethers::utils::hex; 8 | use statics::SHARED_MUTEX; 9 | use std::collections::HashMap; 10 | 11 | // Number of confirmations required for a transaction to be considered confirmed 12 | const CONFIRMATIONS: usize = 1; 13 | 14 | // Type alias for a SignerMiddleware that combines a provider and a local wallet 15 | type SignerM = SignerMiddleware, LocalWallet>; 16 | 17 | /// Represents a client for interacting with the blockchain. 18 | #[derive(Debug, Clone)] 19 | pub struct ChainClient { 20 | // The client is an Arc (atomic reference counted) pointer to a SignerMiddleware 21 | pub client: Arc, 22 | } 23 | 24 | impl ChainClient { 25 | /// Sets up a new ChainClient. 26 | /// 27 | /// # Returns 28 | /// 29 | /// A `Result` containing the new `ChainClient` if successful, or an error if not. 30 | pub async fn setup(chain: String, chains: HashMap) -> Result { 31 | let chain_config = chains 32 | .get(&chain) 33 | .ok_or_else(|| anyhow!("Chain configuration not found"))?; 34 | let wallet: LocalWallet = chain_config.private_key.parse()?; 35 | let provider = Provider::::try_from(chain_config.rpc_url.clone())?; 36 | 37 | // Create a new SignerMiddleware with the provider and wallet 38 | let client = Arc::new(SignerMiddleware::new( 39 | provider, 40 | wallet.with_chain_id(chain_config.chain_id), 41 | )); 42 | 43 | Ok(Self { client }) 44 | } 45 | 46 | /// Sets the DKIM public key hash. 47 | /// 48 | /// # Arguments 49 | /// 50 | /// * `selector` - The selector string. 51 | /// * `domain_name` - The domain name. 52 | /// * `public_key_hash` - The public key hash as a 32-byte array. 53 | /// * `signature` - The signature as Bytes. 54 | /// * `dkim` - The ECDSA Owned DKIM Registry. 55 | /// 56 | /// # Returns 57 | /// 58 | /// A `Result` containing the transaction hash as a String if successful, or an error if not. 59 | pub async fn set_dkim_public_key_hash( 60 | &self, 61 | domain_name: String, 62 | public_key_hash: [u8; 32], 63 | signature: Bytes, 64 | dkim: UserOverridableDKIMRegistry, 65 | ) -> Result { 66 | // Mutex is used to prevent nonce conflicts. 67 | let mut mutex = SHARED_MUTEX.lock().await; 68 | *mutex += 1; 69 | 70 | // Call the contract method 71 | let main_authorizer = dkim.main_authorizer().call().await?; 72 | let call = 73 | dkim.set_dkim_public_key_hash(domain_name, public_key_hash, main_authorizer, signature); 74 | let tx = call.send().await?; 75 | 76 | // Wait for the transaction to be confirmed 77 | let receipt = tx 78 | .confirmations(CONFIRMATIONS) 79 | .await? 80 | .ok_or(anyhow!("No receipt"))?; 81 | 82 | // Format the transaction hash 83 | let tx_hash = receipt.transaction_hash; 84 | let tx_hash = format!("0x{}", hex::encode(tx_hash.as_bytes())); 85 | Ok(tx_hash) 86 | } 87 | 88 | /// Checks if a DKIM public key hash is valid. 89 | /// 90 | /// # Arguments 91 | /// 92 | /// * `domain_name` - The domain name. 93 | /// * `public_key_hash` - The public key hash as a 32-byte array. 94 | /// * `dkim` - The ECDSA Owned DKIM Registry. 95 | /// 96 | /// # Returns 97 | /// 98 | /// A `Result` containing a boolean indicating if the hash is valid. 99 | pub async fn check_if_dkim_public_key_hash_valid( 100 | &self, 101 | domain_name: ::std::string::String, 102 | public_key_hash: [u8; 32], 103 | dkim: UserOverridableDKIMRegistry, 104 | ) -> Result { 105 | // Call the contract method to check if the hash is valid 106 | let main_authorizer = dkim.main_authorizer().call().await?; 107 | let is_set = dkim 108 | .dkim_public_key_hashes(domain_name, public_key_hash, main_authorizer) 109 | .call() 110 | .await?; 111 | Ok(is_set) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /packages/relayer/src/command.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use ethers::types::{Bytes, U256}; 3 | use relayer_utils::{extract_template_vals_from_command, u256_to_bytes32_little}; 4 | 5 | use crate::{constants::COMMAND_FIELDS, model::RequestModel}; 6 | 7 | pub fn parse_command_template(template: &str, params: Vec) -> String { 8 | let mut parsed_string = template.to_string(); 9 | let mut param_iter = params.iter(); 10 | 11 | #[allow(clippy::while_let_on_iterator)] 12 | while let Some(value) = param_iter.next() { 13 | if let Some(start) = parsed_string.find('{') { 14 | if let Some(end) = parsed_string[start..].find('}') { 15 | parsed_string.replace_range(start..start + end + 1, value); 16 | } 17 | } 18 | } 19 | 20 | parsed_string 21 | } 22 | 23 | /// Retrieves and encodes the command parameters for the email authentication request. 24 | /// 25 | /// # Arguments 26 | /// 27 | /// * `params` - The `EmailRequestContext` containing request details. 28 | /// 29 | /// # Returns 30 | /// 31 | /// A `Result` containing a vector of encoded command parameters or an `EmailError`. 32 | pub async fn get_encoded_command_params(email: &str, request: RequestModel) -> Result> { 33 | let command_template = request 34 | .email_tx_auth 35 | .command_template 36 | .split_whitespace() 37 | .map(String::from) 38 | .collect(); 39 | 40 | // Remove \r\n from email 41 | let email = email.replace("=\r\n", ""); 42 | 43 | let command_params = extract_template_vals_from_command(&email, command_template)?; 44 | 45 | let command_params_encoded = command_params 46 | .iter() 47 | .map(|param| { 48 | param 49 | .abi_encode(None) 50 | .map_err(|e| anyhow::anyhow!(e.to_string())) 51 | }) 52 | .collect::>>()?; 53 | 54 | Ok(command_params_encoded) 55 | } 56 | 57 | /// Extracts the masked command from public signals. 58 | /// 59 | /// # Arguments 60 | /// 61 | /// * `public_signals` - The vector of public signals. 62 | /// * `start_idx` - The starting index for command extraction. 63 | /// 64 | /// # Returns 65 | /// 66 | /// A `Result` containing the masked command as a `String` or an error. 67 | pub fn get_masked_command(public_signals: Vec, start_idx: usize) -> Result { 68 | // Gather signals from start_idx to start_idx + COMMAND_FIELDS 69 | let command_bytes: Vec = public_signals 70 | .iter() 71 | .skip(start_idx) 72 | .take(COMMAND_FIELDS) 73 | .take_while(|&signal| *signal != U256::zero()) 74 | .flat_map(u256_to_bytes32_little) 75 | .collect(); 76 | 77 | // Bytes to string, removing null bytes 78 | let command = String::from_utf8(command_bytes.into_iter().filter(|&b| b != 0u8).collect()) 79 | .map_err(|e| anyhow!("Failed to convert bytes to string: {}", e))?; 80 | 81 | Ok(command) 82 | } 83 | -------------------------------------------------------------------------------- /packages/relayer/src/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use serde::Deserialize; 3 | use std::collections::HashMap; 4 | use std::env; 5 | use std::fs::File; 6 | use std::io::Read; 7 | 8 | #[derive(Deserialize, Debug, Clone)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct Config { 11 | /// The port number for the application to listen on. 12 | pub port: usize, 13 | /// The URL for the database connection. 14 | pub database_url: String, 15 | /// The URL for the SMTP server. 16 | pub smtp_url: String, 17 | /// Configuration for the prover service. 18 | pub prover: ProverConfig, 19 | /// Configuration for file paths. 20 | pub path: PathConfig, 21 | /// Configuration for ICP (Internet Computer Protocol). 22 | pub icp: IcpConfig, 23 | /// A map of chain configurations, keyed by chain name. 24 | pub chains: HashMap, 25 | /// Flag to enable JSON logging. 26 | pub json_logger: bool, 27 | } 28 | 29 | #[derive(Deserialize, Debug, Clone)] 30 | #[serde(rename_all = "camelCase")] 31 | pub struct PathConfig { 32 | /// Path to the PEM file. 33 | pub pem: String, 34 | /// Path to the email templates directory. 35 | pub email_templates: String, 36 | } 37 | 38 | #[derive(Deserialize, Debug, Clone)] 39 | #[serde(rename_all = "camelCase")] 40 | pub struct IcpConfig { 41 | /// The canister ID for DKIM (DomainKeys Identified Mail). 42 | pub dkim_canister_id: String, 43 | /// The canister ID for the wallet. 44 | pub wallet_canister_id: String, 45 | /// The URL for the IC (Internet Computer) replica. 46 | pub ic_replica_url: String, 47 | } 48 | 49 | #[derive(Deserialize, Debug, Clone)] 50 | #[serde(rename_all = "camelCase")] 51 | pub struct ChainConfig { 52 | /// The private key for the blockchain. 53 | pub private_key: String, 54 | /// The RPC (Remote Procedure Call) URL for the blockchain. 55 | pub rpc_url: String, 56 | /// The chain ID for the blockchain. 57 | pub chain_id: u32, 58 | } 59 | 60 | #[derive(Deserialize, Debug, Clone)] 61 | #[serde(rename_all = "camelCase")] 62 | pub struct ProverConfig { 63 | /// The URL for the prover service. 64 | pub url: String, 65 | /// The blueprint ID for the prover. 66 | pub blueprint_id: String, 67 | /// The URL for downloading zkey files. 68 | pub zkey_download_url: String, 69 | /// The URL for downloading circuit CPP files. 70 | pub circuit_cpp_download_url: String, 71 | /// The API key for the prover. 72 | pub api_key: String, 73 | } 74 | 75 | // Function to load the configuration from a JSON file 76 | pub fn load_config() -> Result { 77 | // Open the configuration file 78 | let mut file = File::open("config.json") 79 | .map_err(|e| anyhow::anyhow!("Failed to open config file: {}", e))?; 80 | 81 | // Read the file's content into a string 82 | let mut data = String::new(); 83 | file.read_to_string(&mut data) 84 | .map_err(|e| anyhow::anyhow!("Failed to read config file: {}", e))?; 85 | 86 | // Deserialize the JSON content into a Config struct 87 | let config: Config = serde_json::from_str(&data) 88 | .map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?; 89 | 90 | // Setting Logger ENV 91 | if config.json_logger { 92 | env::set_var("JSON_LOGGER", "true"); 93 | } 94 | 95 | Ok(config) 96 | } 97 | -------------------------------------------------------------------------------- /packages/relayer/src/constants.rs: -------------------------------------------------------------------------------- 1 | /// This pattern includes encoded newline characters and matches a `div` element with a specific ID pattern. 2 | 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)?)"#; 3 | 4 | /// Regex for capturing the request ID from an email body. 5 | pub const REQUEST_ID_REGEX: &str = r"(Your request ID is )([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"; 6 | 7 | /// The number of fields expected in a domain configuration. 8 | pub const DOMAIN_FIELDS: usize = 9; 9 | 10 | /// The number of fields expected in a command configuration. 11 | pub const COMMAND_FIELDS: usize = 20; 12 | -------------------------------------------------------------------------------- /packages/relayer/src/main.rs: -------------------------------------------------------------------------------- 1 | mod abis; 2 | mod chain; 3 | mod command; 4 | mod config; 5 | mod constants; 6 | mod dkim; 7 | mod handler; 8 | mod mail; 9 | mod model; 10 | mod prove; 11 | mod route; 12 | mod schema; 13 | mod statics; 14 | 15 | use std::sync::Arc; 16 | 17 | use anyhow::Result; 18 | use axum::http::{ 19 | header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}, 20 | Method, 21 | }; 22 | use relayer_utils::LOG; 23 | use reqwest::Client; 24 | use route::create_router; 25 | use slog::info; 26 | use sqlx::{postgres::PgPoolOptions, Pool, Postgres}; 27 | use tower_http::cors::CorsLayer; 28 | 29 | use config::Config; 30 | 31 | /// Represents the state of the relayer, including HTTP client, configuration, and database pool. 32 | #[derive(Debug, Clone)] 33 | pub struct RelayerState { 34 | /// The HTTP client used for making network requests. 35 | http_client: Client, 36 | /// The configuration settings for the relayer. 37 | config: Config, 38 | /// The connection pool for the PostgreSQL database. 39 | db: Pool, 40 | } 41 | 42 | /// The main entry point for the relayer application. 43 | /// 44 | /// This asynchronous function loads the configuration, establishes a database connection, 45 | /// sets up CORS, creates the router, and starts the server. 46 | /// 47 | /// # Returns 48 | /// 49 | /// A `Result` indicating the success or failure of the application startup. 50 | #[tokio::main] 51 | async fn main() -> Result<()> { 52 | // Load the configuration from the config file or environment 53 | let config = config::load_config()?; 54 | info!(LOG, "Loaded configuration: {:?}", config); 55 | 56 | // Establish a connection pool to the PostgreSQL database 57 | let pool = PgPoolOptions::new() 58 | .max_connections(10) 59 | .connect(&config.database_url) 60 | .await?; 61 | info!(LOG, "Database connection established."); 62 | 63 | // Set up CORS (Cross-Origin Resource Sharing) policy 64 | let cors = CorsLayer::new() 65 | .allow_origin(tower_http::cors::Any) 66 | .allow_methods([Method::GET, Method::POST]) 67 | .allow_headers([AUTHORIZATION, ACCEPT, CONTENT_TYPE]); 68 | 69 | // Create the router with the relayer state and apply the CORS layer 70 | let relayer = create_router(Arc::new(RelayerState { 71 | http_client: Client::new(), 72 | config: config.clone(), 73 | db: pool.clone(), 74 | })) 75 | .layer(cors); 76 | 77 | // Bind the server to the specified port and start listening for requests 78 | let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", config.port)).await?; 79 | info!(LOG, "Serving relayer on port: {}", config.port); 80 | axum::serve(listener, relayer).await?; 81 | 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /packages/relayer/src/prove.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use relayer_utils::{ 3 | generate_email_circuit_input, generate_proof_gpu, u256_to_bytes32, EmailCircuitParams, 4 | ParsedEmail, LOG, 5 | }; 6 | use slog::info; 7 | 8 | use crate::{ 9 | abis::EmailProof, 10 | command::get_masked_command, 11 | constants::{COMMAND_FIELDS, DOMAIN_FIELDS, SHA_PRECOMPUTE_SELECTOR}, 12 | model::{update_request, RequestModel, RequestStatus}, 13 | RelayerState, 14 | }; 15 | 16 | /// Generates the email proof for authentication. 17 | /// 18 | /// This asynchronous function updates the request status, parses the email, generates the circuit input, 19 | /// and produces a cryptographic proof. It returns the email proof and account salt. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `email` - The raw email content as a `&str`. 24 | /// * `request` - The `RequestModel` containing details of the request associated with the email. 25 | /// * `relayer_state` - The current state of the relayer, containing configuration and state information. 26 | /// 27 | /// # Returns 28 | /// 29 | /// A `Result` containing: 30 | /// - `Ok`: An `EmailProof` with the generated proof and account salt. 31 | /// - `Err`: An error if any step in the process fails. 32 | pub async fn generate_email_proof( 33 | email: &str, 34 | request: RequestModel, 35 | relayer_state: RelayerState, 36 | ) -> Result { 37 | // Update the request status to "Proving" in the database 38 | update_request(&relayer_state.db, request.id, RequestStatus::Proving).await?; 39 | 40 | // Parse the email from the raw content 41 | let parsed_email = ParsedEmail::new_from_raw_email(email).await?; 42 | 43 | // Generate the circuit input for the email proof 44 | let circuit_input = generate_email_circuit_input( 45 | email, 46 | &request.email_tx_auth.account_code, 47 | Some(EmailCircuitParams { 48 | max_header_length: Some(1024), 49 | max_body_length: Some(1024), 50 | sha_precompute_selector: Some(SHA_PRECOMPUTE_SELECTOR.to_string()), 51 | ignore_body_hash_check: Some(false), 52 | }), 53 | ) 54 | .await?; 55 | 56 | // Generate the proof and public signals using the circuit input 57 | let (proof, public_signals) = generate_proof_gpu( 58 | &circuit_input, 59 | &relayer_state.config.prover.blueprint_id, 60 | &uuid::Uuid::new_v4().to_string(), 61 | &relayer_state.config.prover.zkey_download_url, 62 | &relayer_state.config.prover.circuit_cpp_download_url, 63 | &relayer_state.config.prover.api_key, 64 | &relayer_state.config.prover.url, 65 | ) 66 | .await?; 67 | 68 | // Log the public signals for debugging purposes 69 | info!(LOG, "Public signals: {:?}", public_signals); 70 | 71 | // Extract the account salt from the public signals 72 | let account_salt = u256_to_bytes32(&public_signals[COMMAND_FIELDS + DOMAIN_FIELDS + 3]); 73 | // Determine if the code exists based on the public signals 74 | let is_code_exist = public_signals[COMMAND_FIELDS + DOMAIN_FIELDS + 4] == 1u8.into(); 75 | // Get the masked command from the public signals 76 | let masked_command = get_masked_command(public_signals.clone(), DOMAIN_FIELDS + 3)?; 77 | 78 | // Construct the email proof with the generated data 79 | let email_proof = EmailProof { 80 | proof, 81 | domain_name: parsed_email.get_email_domain()?, 82 | public_key_hash: u256_to_bytes32(&public_signals[DOMAIN_FIELDS]), 83 | timestamp: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 2]).into(), 84 | masked_command, 85 | email_nullifier: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 1]), 86 | account_salt, 87 | is_code_exist, 88 | }; 89 | 90 | // Return the constructed email proof 91 | Ok(email_proof) 92 | } 93 | -------------------------------------------------------------------------------- /packages/relayer/src/route.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::{ 4 | routing::{get, post}, 5 | Router, 6 | }; 7 | 8 | use crate::{ 9 | handler::{ 10 | account_salt_handler, get_status_handler, health_checker_handler, receive_email_handler, 11 | submit_handler, 12 | }, 13 | RelayerState, 14 | }; 15 | 16 | /// Creates and configures the router for the relayer service. 17 | /// 18 | /// This function sets up the routes for the API endpoints and associates them with their respective handlers. 19 | /// It also attaches the shared state to the router. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `relayer_state` - An `Arc` containing the shared state of the relayer, which includes configuration and resources. 24 | /// 25 | /// # Returns 26 | /// 27 | /// A `Router` configured with the necessary routes and state. 28 | pub fn create_router(relayer_state: Arc) -> Router { 29 | Router::new() 30 | // Route for health check endpoint 31 | .route("/api/healthz", get(health_checker_handler)) 32 | // Route for submitting email transaction authentication requests 33 | .route("/api/submit", post(submit_handler)) 34 | // Route for computing the account salt 35 | .route("/api/accountSalt", post(account_salt_handler)) 36 | // Route for receiving emails 37 | .route("/api/receiveEmail", post(receive_email_handler)) 38 | // Route for retrieving the status of a specific request 39 | .route("/api/status/:id", get(get_status_handler)) 40 | // Attach the shared state to the router 41 | .with_state(relayer_state) 42 | } 43 | -------------------------------------------------------------------------------- /packages/relayer/src/schema.rs: -------------------------------------------------------------------------------- 1 | use ethers::types::{Address, U256}; 2 | use relayer_utils::AccountCode; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Represents the schema for email transaction authentication. 6 | /// 7 | /// This struct is used to deserialize and serialize email transaction authentication data, 8 | /// which includes contract details, command templates, and email metadata. 9 | #[derive(Deserialize, Serialize, Debug, Clone)] 10 | #[serde(rename_all = "camelCase")] 11 | pub struct EmailTxAuthSchema { 12 | /// The account code associated with the transaction. 13 | pub account_code: AccountCode, 14 | /// Indicates whether the code exists in the email. 15 | pub code_exists_in_email: bool, 16 | pub command_template: String, 17 | /// The parameters for the command template. 18 | pub command_params: Vec, 19 | /// The ID of the template used in the transaction. 20 | pub template_id: U256, 21 | pub email_address: String, 22 | /// The subject of the email. 23 | pub subject: String, 24 | /// The body content of the email. 25 | pub body: String, 26 | /// The blockchain chain on which the transaction is to be executed. 27 | pub chain: Option, 28 | /// The address of the DKIM contract. 29 | pub dkim_contract_address: Option
, 30 | } 31 | 32 | #[derive(Deserialize, Serialize, Debug, Clone)] 33 | #[serde(rename_all = "camelCase")] 34 | pub struct AccountSaltSchema { 35 | pub account_code: String, 36 | pub email_address: String, 37 | } 38 | -------------------------------------------------------------------------------- /packages/relayer/src/statics.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use tokio::sync::Mutex; 3 | 4 | use lazy_static::lazy_static; 5 | 6 | lazy_static! { 7 | pub static ref SHARED_MUTEX: Arc> = Arc::new(Mutex::new(0)); 8 | } 9 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.80.1 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | use_small_heuristics = "Default" 3 | reorder_imports = true 4 | reorder_modules = true 5 | --------------------------------------------------------------------------------