├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── biome.jsonc ├── bonsai-pay ├── .env.example ├── .gitignore ├── .gitmodules ├── .solhint.json ├── .vscode │ └── settings.json ├── Cargo.toml ├── README.md ├── apps │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── bin │ │ └── pubsub.rs │ │ └── lib.rs ├── contracts │ ├── BonsaiPay.sol │ └── README.md ├── foundry.toml ├── methods │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── guest │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── bin │ │ │ └── jwt_validator.rs │ └── src │ │ └── lib.rs ├── oidc-validator │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── certs.rs │ │ └── lib.rs ├── remappings.txt ├── rust-toolchain.toml ├── script │ ├── Deploy.s.sol │ └── config.toml ├── tests │ └── BonsaiPay.t.sol └── ui │ ├── .env.example │ ├── .eslintrc.cjs │ ├── .eslintrc.yml │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ ├── BonsaiPayTermsofService2023.11.07.html │ ├── RISCZeroBonsaiWebsitePrivacyPolicy2023.11.07.html │ ├── eth.svg │ └── vite.svg │ ├── src │ ├── App.tsx │ ├── assets │ │ ├── react.svg │ │ └── tokens.json │ ├── components │ │ ├── Account.tsx │ │ ├── Balance.tsx │ │ ├── Claim.tsx │ │ ├── Deposit.tsx │ │ ├── Modal.tsx │ │ ├── Prove.tsx │ │ ├── SignInWithApple.tsx │ │ └── SignInWithGoogle.tsx │ ├── generated.ts │ ├── index.css │ ├── libs │ │ ├── types.ts │ │ └── utils.ts │ ├── main.tsx │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wagmi.config.ts ├── near-zk-light-client ├── .gitignore ├── .vscode │ └── settings.json ├── Cargo.toml ├── README.md ├── host │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── methods │ ├── Cargo.toml │ ├── build.rs │ ├── guest │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── src │ │ └── lib.rs ├── test-vectors │ └── mainnet-80000000-81000000.json └── types │ ├── Cargo.toml │ └── src │ ├── approval.rs │ ├── crypto.rs │ ├── hash.rs │ ├── lib.rs │ ├── serde_dec.rs │ └── validator_stake.rs ├── zk-kyc ├── .env.example ├── .gitignore ├── .gitmodules ├── .solhint.json ├── .vscode │ └── settings.json ├── Cargo.toml ├── README.md ├── apps │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── bin │ │ └── pubsub.rs │ │ └── lib.rs ├── contracts │ ├── README.md │ └── zkKYC.sol ├── deployment-guide.md ├── foundry.toml ├── methods │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── guest │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── bin │ │ │ └── jwt_validator.rs │ └── src │ │ └── lib.rs ├── oidc-validator │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── certs.rs │ │ └── lib.rs ├── remappings.txt ├── rust-toolchain.toml ├── script │ └── Deploy.s.sol ├── tests │ └── zkKYC.t.sol └── ui │ ├── .env.example │ ├── .eslintrc.cjs │ ├── .eslintrc.yml │ ├── LICENSE.md │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ ├── BonsaiPayTermsofService2023.11.07.html │ ├── RISCZeroBonsaiWebsitePrivacyPolicy2023.11.07.html │ ├── RISC_Zero_Logo_lightmode.png │ ├── ZRP.json │ ├── eth.svg │ ├── usdc.svg │ └── vite.svg │ ├── src │ ├── App.tsx │ ├── assets │ │ ├── react.svg │ │ └── tokens.json │ ├── components │ │ ├── Prove.tsx │ │ ├── SignInWithIDme.tsx │ │ └── Steps.tsx │ ├── generated.ts │ ├── index.css │ ├── libs │ │ ├── types.ts │ │ └── utils.ts │ ├── main.tsx │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wagmi.config.ts ├── zk-socials ├── .env.example ├── .gitignore ├── LICENSE.md ├── README.md ├── bun.lockb ├── next-env.d.ts ├── next.config.js ├── package.json ├── postcss.config.cjs ├── public │ ├── 404.svg │ ├── bonsai-logo-dark.svg │ ├── bonsai-logo-light.svg │ ├── favicon.png │ ├── fonts │ │ ├── EuropaGroteskSH-Lig.otf │ │ ├── EuropaGroteskSH-Med.otf │ │ └── EuropaGroteskSH-Reg.otf │ ├── google.svg │ ├── injected.svg │ ├── loading.gif │ └── risczero.svg ├── social_validator │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── bonsai_cli.sh │ ├── host │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── methods │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── guest │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ └── src │ │ │ └── lib.rs │ ├── oidc-validator │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── lib.rs │ └── rust-toolchain.toml ├── src │ ├── app │ │ ├── (demo) │ │ │ ├── _actions │ │ │ │ ├── bonsai-proving.ts │ │ │ │ ├── check-user-validity.ts │ │ │ │ └── register-user-via-web-authn.ts │ │ │ ├── _components │ │ │ │ ├── code-preview.tsx │ │ │ │ ├── confetti.tsx │ │ │ │ ├── connect-wallet-button.tsx │ │ │ │ ├── footer.tsx │ │ │ │ ├── header.tsx │ │ │ │ ├── prove-button.tsx │ │ │ │ ├── sign-in-button.tsx │ │ │ │ ├── sign-out-button.tsx │ │ │ │ ├── snark-table.tsx │ │ │ │ ├── stark-table.tsx │ │ │ │ ├── terms-and-services.tsx │ │ │ │ ├── user-infos.tsx │ │ │ │ └── webauthn-button.tsx │ │ │ ├── _utils │ │ │ │ ├── calculate-completion-percentage.ts │ │ │ │ ├── do-snark-proving.ts │ │ │ │ ├── do-stark-proving.ts │ │ │ │ ├── generate-challenge.ts │ │ │ │ └── get-avatar-initials.ts │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── error.tsx │ │ └── layout.tsx │ ├── client │ │ ├── providers │ │ │ └── providers.tsx │ │ └── theme │ │ │ └── theme-toggle.tsx │ ├── env.js │ ├── styles │ │ └── styles.css │ ├── types │ │ └── google.ts │ └── wagmi.ts ├── tailwind.config.ts ├── tsconfig.json └── tsconfig.tsbuildinfo └── zkdoom ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── e1m5sec.lmp ├── host ├── Cargo.toml └── src │ └── main.rs ├── methods ├── Cargo.toml ├── build.rs ├── guest │ ├── Cargo.toml │ └── src │ │ └── main.rs └── src │ └── lib.rs ├── puredoom-rs ├── Cargo.toml ├── README.md ├── build.rs ├── src │ ├── lib.rs │ └── sys │ │ └── puredoom.c └── zkvm-doom.patch ├── rust-toolchain.toml └── zkdoom-common ├── Cargo.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | pnpm-debug.log* 7 | lerna-debug.log* 8 | 9 | # Directories 10 | node_modules/ 11 | dist/ 12 | dist-ssr/ 13 | cache/ 14 | out/ 15 | build/ 16 | amplify/#current-cloud-backend 17 | amplify/.config/local-* 18 | amplify/logs 19 | amplify/mock-data 20 | amplify/mock-api-resources 21 | amplify/backend/.temp 22 | .svelte-kit/ 23 | target/ 24 | 25 | # Files 26 | *.local 27 | .DS_Store 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | *.p8 34 | aws-exports.js 35 | awsconfiguration.json 36 | amplifyconfiguration.json 37 | amplifyconfiguration.dart 38 | amplify-build-config.json 39 | amplify-gradle-config.json 40 | amplifytools.xcconfig 41 | .secret-* 42 | *.sample 43 | .env* 44 | !.env.example 45 | vite.config.js.timestamp-* 46 | vite.config.ts.timestamp-* 47 | Cargo.lock 48 | methods/guest/Cargo.lock 49 | 50 | # Editor directories and files 51 | .vscode/* 52 | !.vscode/extensions.json 53 | .idea/ 54 | 55 | # Broadcast directory 56 | broadcast/ 57 | 58 | # Autogenerated contracts 59 | **/contracts/ImageID.sol 60 | **/tests/Elf.sol 61 | 62 | # Documentation 63 | docs/ 64 | 65 | # next 66 | .next -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "bonsai-pay/lib/forge-std"] 2 | path = bonsai-pay/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "bonsai-pay/lib/murky"] 5 | path = bonsai-pay/lib/murky 6 | url = https://github.com/dmfxyz/murky 7 | [submodule "bonsai-pay/lib/openzeppelin-contracts"] 8 | path = bonsai-pay/lib/openzeppelin-contracts 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 10 | [submodule "bonsai-pay/lib/solidity-bytes-utils"] 11 | path = bonsai-pay/lib/solidity-bytes-utils 12 | url = https://github.com/GNSPS/solidity-bytes-utils 13 | [submodule "bonsai-pay/lib/risc0-ethereum"] 14 | path = bonsai-pay/lib/risc0-ethereum 15 | url = https://github.com/risc0/risc0-ethereum 16 | branch = release-0.9 17 | [submodule "zk-kyc/lib/forge-std"] 18 | path = zk-kyc/lib/forge-std 19 | url = https://github.com/foundry-rs/forge-std 20 | [submodule "zk-kyc/lib/murky"] 21 | path = zk-kyc/lib/murky 22 | url = https://github.com/dmfxyz/murky 23 | [submodule "zk-kyc/lib/openzeppelin-contracts"] 24 | path = zk-kyc/lib/openzeppelin-contracts 25 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 26 | [submodule "zk-kyc/lib/solidity-bytes-utils"] 27 | path = zk-kyc/lib/solidity-bytes-utils 28 | url = https://github.com/GNSPS/solidity-bytes-utils 29 | [submodule "zk-kyc/lib/risc0-ethereum"] 30 | path = zk-kyc/lib/risc0-ethereum 31 | url = https://github.com/risc0/risc0-ethereum 32 | branch = release-0.9 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # demos 2 | 3 | This repository contains the source code for RISC Zero demo applications. The projects are designed for demonstration purposes only and should not be used in production. 4 | 5 | To learn more about RISC Zero visit our [website] and developer [docs]. 6 | 7 | ### Warning / Security Notice 8 | Demo applications are provided as-is and have NOT BEEN AUDITED and the following demos contain KNOWN security findings: 9 | * bonsai-pay 10 | 11 | This code is provided as-is as a benefit to the public good, however should not be used with out a comprehensive security analyis. 12 | 13 | [website]: https://risczero.com 14 | [docs]: https://dev.risczero.com 15 | -------------------------------------------------------------------------------- /biome.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["./zk-socials/node_modules/@risc0/ui/biome.base.jsonc"], 3 | "files": { 4 | "include": ["zk-socials"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /bonsai-pay/.env.example: -------------------------------------------------------------------------------- 1 | CHAIN_ID= 2 | ETH_WALLET_PRIVATE_KEY= 3 | RPC_URL= 4 | CONTRACT_ADDRESS= 5 | -------------------------------------------------------------------------------- /bonsai-pay/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | broadcast/ 7 | 8 | # Autogenerated contracts 9 | contracts/ImageID.sol 10 | tests/Elf.sol 11 | 12 | # Dotenv file 13 | .env 14 | 15 | # Cargo 16 | target/ 17 | 18 | # Misc 19 | .DS_Store 20 | .idea 21 | -------------------------------------------------------------------------------- /bonsai-pay/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | [submodule "lib/risc0-ethereum"] 8 | path = lib/risc0-ethereum 9 | url = https://github.com/risc0/risc0-ethereum 10 | -------------------------------------------------------------------------------- /bonsai-pay/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default" 3 | } 4 | -------------------------------------------------------------------------------- /bonsai-pay/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "./apps/Cargo.toml", 4 | "./methods/Cargo.toml", 5 | "./methods/guest/Cargo.toml", 6 | ], 7 | "rust-analyzer.check.extraEnv": { 8 | "RISC0_SKIP_BUILD": "1", 9 | } 10 | } -------------------------------------------------------------------------------- /bonsai-pay/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["apps", "methods", "oidc-validator"] 4 | exclude = ["lib"] 5 | 6 | [workspace.package] 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [workspace.dependencies] 11 | alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } 12 | alloy-sol-types = { version = "0.6" } 13 | anyhow = { version = "1.0.75" } 14 | bincode = { version = "1.3" } 15 | bytemuck = { version = "1.14" } 16 | ethers = { version = "2.0" } 17 | log = { version = "0.4" } 18 | methods = { path = "./methods" } 19 | oidc-validator = { path = "./oidc-validator" } 20 | risc0-build = { version = "1.0.5", features = ["docker"] } 21 | risc0-build-ethereum = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } 22 | risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } 23 | risc0-zkvm = { version = "1.0.5", default-features = false } 24 | risc0-zkp = { version = "1.0.5", default-features = false } 25 | serde = { version = "1.0", features = ["derive", "std"] } 26 | 27 | [profile.release] 28 | debug = 1 29 | lto = true 30 | -------------------------------------------------------------------------------- /bonsai-pay/apps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "apps" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [dependencies] 7 | alloy-primitives = { workspace = true } 8 | alloy-sol-types = { workspace = true } 9 | anyhow = { workspace = true } 10 | bincode.workspace = true 11 | bonsai-sdk = "0.9" 12 | bytemuck.workspace = true 13 | clap = { version = "4.0", features = ["derive", "env"] } 14 | dotenv = "0.15.0" 15 | env_logger = { version = "0.10" } 16 | ethers = { workspace = true } 17 | hex = "0.4.3" 18 | log = { workspace = true } 19 | methods = { workspace = true } 20 | oidc-validator = { version = "0.1.0", path = "../oidc-validator" } 21 | reqwest = { version = "0.12.7", features = ["json"] } 22 | risc0-ethereum-contracts.workspace = true 23 | risc0-zkvm = { workspace = true, features = ["client"] } 24 | serde.workspace = true 25 | tokio = { version = "1.35", features = ["full"] } 26 | warp = "0.3.7" 27 | -------------------------------------------------------------------------------- /bonsai-pay/apps/README.md: -------------------------------------------------------------------------------- 1 | # Publisher/Subscriber Application 2 | 3 | The Publisher/Subscriber application is a simple application that listens for incoming JWT tokens, sends a proof request to the [Bonsai] proving service, and publishes the received proofs to a deployed Bonsai Pay contract on Ethereum. This application is useful for integrating the Bonsai proving service with a deployed Bonsai Pay contract and UI. 4 | 5 | ## Getting Started 6 | 7 | The [`pubsub` CLI][pubsub], is an application that listens for JWT tokens via HTTP, sends an off-chain proof request to the [Bonsai] proving service, and publishes the received proofs to the deployed Bonsai Pay contract. 8 | 9 | ### Usage 10 | 11 | Run the `pubsub` with: 12 | 13 | ```sh 14 | cargo run --bin pubsub 15 | ``` 16 | 17 | ```text 18 | $ cargo run --bin pubsub -- --help 19 | 20 | Usage: pubsub --chain-id --eth-wallet-private-key --rpc-url --contract 21 | 22 | Options: 23 | --chain-id 24 | Ethereum chain ID 25 | --eth-wallet-private-key 26 | Ethereum Node endpoint [env: ETH_WALLET_PRIVATE_KEY=] 27 | --rpc-url 28 | Ethereum Node endpoint 29 | --contract 30 | Bonsai Pay's contract address on Ethereum 31 | -h, --help 32 | Print help 33 | -V, --version 34 | Print version 35 | ``` 36 | 37 | The server listens on `http://localhost:8080` for incoming requests with a JWT token in the X-Auth-Token header. Upon receiving a token, it sends a proof request to Bonsai and publishes the received proof to the specified contract on Ethereum. 38 | 39 | #### Example Request 40 | 41 | ```sh 42 | curl -H "X-Auth-Token: " http://localhost:8080/auth 43 | ``` 44 | 45 | ## Library 46 | 47 | A small rust [library] containing utility functions to help with sending off-chain proof requests to the Bonsai proving service and publish the received proofs directly to a deployed app contract on Ethereum. 48 | 49 | [pubsub]: ./src/bin/pubsub.rs 50 | [Bonsai]: https://dev.bonsai.xyz/ 51 | [library]: ./src/lib.rs 52 | -------------------------------------------------------------------------------- /bonsai-pay/contracts/BonsaiPay.sol: -------------------------------------------------------------------------------- 1 | // Copyright 2024 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | pragma solidity ^0.8.20; 17 | 18 | import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; 19 | import {ImageID} from "./ImageID.sol"; 20 | 21 | contract BonsaiPay { 22 | IRiscZeroVerifier public immutable verifier; 23 | bytes32 public constant imageId = ImageID.JWT_VALIDATOR_ID; 24 | 25 | enum ClaimStatus { 26 | Pending, 27 | Claimed 28 | } 29 | 30 | struct Deposit { 31 | ClaimStatus status; 32 | bytes32 claimId; 33 | uint256 amount; 34 | } 35 | 36 | Deposit[] private deposits; 37 | mapping(bytes32 => uint256[]) private claimRecords; 38 | 39 | event Deposited(bytes32 indexed claimId, uint256 amount); 40 | event Claimed(address indexed recipient, bytes32 indexed claimId, uint256 amount); 41 | 42 | error InvalidDeposit(string message); 43 | error InvalidClaim(string message); 44 | error TransferFailed(); 45 | 46 | constructor(IRiscZeroVerifier _verifier) { 47 | verifier = _verifier; 48 | } 49 | 50 | function deposit(bytes32 claimId) public payable { 51 | if (claimId == bytes32(0)) revert InvalidDeposit("Empty claimId"); 52 | if (msg.value == 0) revert InvalidDeposit("Zero deposit amount"); 53 | 54 | deposits.push(Deposit({status: ClaimStatus.Pending, claimId: claimId, amount: msg.value})); 55 | claimRecords[claimId].push(deposits.length - 1); 56 | 57 | emit Deposited(claimId, msg.value); 58 | } 59 | 60 | function claim(address payable to, bytes32 claimId, bytes calldata seal) public { 61 | if (to == address(0)) revert InvalidClaim("Invalid recipient address"); 62 | if (claimId == bytes32(0)) revert InvalidClaim("Empty claimId"); 63 | 64 | verifier.verify(seal, imageId, sha256(abi.encode(to, claimId))); 65 | 66 | uint256[] storage depositIndices = claimRecords[claimId]; 67 | uint256 balance = _processDeposits(depositIndices); 68 | 69 | if (balance == 0) revert InvalidClaim("No claimable balance"); 70 | 71 | (bool success,) = to.call{value: balance}(""); 72 | if (!success) revert TransferFailed(); 73 | 74 | emit Claimed(to, claimId, balance); 75 | } 76 | 77 | function balanceOf(bytes32 claimId) public view returns (uint256) { 78 | if (claimId == bytes32(0)) revert InvalidClaim("Empty claimId"); 79 | 80 | uint256[] storage depositIndices = claimRecords[claimId]; 81 | return _calculateBalance(depositIndices); 82 | } 83 | 84 | function _processDeposits(uint256[] storage depositIndices) private returns (uint256) { 85 | uint256 balance = 0; 86 | 87 | for (uint256 i = 0; i < depositIndices.length; ++i) { 88 | Deposit storage dep = deposits[depositIndices[i]]; 89 | if (dep.status == ClaimStatus.Pending) { 90 | dep.status = ClaimStatus.Claimed; 91 | balance += dep.amount; 92 | } 93 | } 94 | 95 | return balance; 96 | } 97 | 98 | function _calculateBalance(uint256[] storage depositIndices) private view returns (uint256) { 99 | uint256 balance = 0; 100 | 101 | for (uint256 i = 0; i < depositIndices.length; ++i) { 102 | Deposit storage dep = deposits[depositIndices[i]]; 103 | if (dep.status == ClaimStatus.Pending) { 104 | balance += dep.amount; 105 | } 106 | } 107 | 108 | return balance; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /bonsai-pay/contracts/README.md: -------------------------------------------------------------------------------- 1 | # Solidity Contracts 2 | 3 | This directory contains the Solidity contracts for an application with [RISC Zero](https://risczero.com) on Ethereum. 4 | 5 | The main contract is [`BonsaiPay.sol`](./BonsaiPay.sol), which allows users to deposit funds associated with a claim ID and for recipients to claim those funds by providing a valid proof. 6 | 7 | The Solidity libraries for RISC Zero can be found at [github.com/risc0/risc0-ethereum](https://github.com/risc0/risc0-ethereum/tree/main/contracts). 8 | 9 | Contracts are built and tested with [forge](https://github.com/foundry-rs/foundry#forge), which is part of the [Foundry](https://getfoundry.sh/) toolkit. 10 | 11 | Tests are defined in the `tests` directory in the root of this template. 12 | 13 | ## BonsaiPay Contract 14 | 15 | The `BonsaiPay` contract has the following main functions: 16 | 17 | - `deposit(bytes32 claimId)`: Allows users to deposit funds associated with a `claimId`. Emits a `Deposited` event. 18 | - `claim(address payable to, bytes32 claimId, bytes32 postStateDigest, bytes calldata seal)`: Allows recipients to claim funds associated with a `claimId` by providing a valid proof. Emits a `Claimed` event. 19 | - `balanceOf(bytes32 claimId)`: Returns the claimable balance for a given `claimId`. 20 | 21 | The contract uses the RISC Zero verifier to validate the proof provided during the claim process. The `ImageID` used for verification is generated during the build process. 22 | 23 | ## Generated Contracts 24 | 25 | As part of the build process, this template generates the `ImageID.sol` and `Elf.sol` contracts. 26 | 27 | Running `cargo build` will generate these contracts with up to date references to your guest code. 28 | 29 | - `ImageID.sol`: contains the [Image IDs](https://dev.risczero.com/terminology#image-id) for the guests implemented in the [methods](../methods/README.md) directory. 30 | - `Elf.sol`: contains the path of the guest binaries implemented in the [methods](../methods/README.md) directory. 31 | 32 | This contract is saved in the `tests` directory in the root of this template. -------------------------------------------------------------------------------- /bonsai-pay/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "contracts" 3 | out = "out" 4 | libs = ["lib"] 5 | test = "tests" 6 | ffi = true 7 | fs_permissions = [{ access = "read-write", path = "./"}] 8 | 9 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 10 | -------------------------------------------------------------------------------- /bonsai-pay/methods/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "methods" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [package.metadata.risc0] 7 | methods = ["guest"] 8 | 9 | [build-dependencies] 10 | risc0-build = { workspace = true } 11 | risc0-build-ethereum = { workspace = true } 12 | risc0-zkp = { workspace = true } 13 | 14 | [dev-dependencies] 15 | alloy-primitives = { workspace = true } 16 | alloy-sol-types = { workspace = true } 17 | risc0-zkvm = { workspace = true, features = ["client"] } 18 | oidc-validator = { workspace = true } -------------------------------------------------------------------------------- /bonsai-pay/methods/README.md: -------------------------------------------------------------------------------- 1 | # zkVM Methods 2 | 3 | This directory contains the [zkVM] portion of your [RISC Zero] application. 4 | This is where you will define one or more [guest programs] to act as a coprocessor to your [on-chain logic]. 5 | 6 | > In typical use cases, the only code in this directory that you will need to edit is inside [guest/src/bin]. 7 | 8 | 9 | ### Writing Guest Code 10 | 11 | To learn to write code for the zkVM, we recommend [Guest Code 101]. 12 | 13 | Examples of what you can do in the guest can be found in the [RISC Zero examples]. 14 | 15 | 16 | ### From Guest Code to Binary File 17 | 18 | Code in the `methods/guest` directory will be compiled into one or more binaries. 19 | 20 | Build configuration for the methods is included in `methods/build.rs`. 21 | 22 | Each will have a corresponding image ID, which is a hash identifying the program. 23 | 24 | 25 | [zkVM]: https://dev.risczero.com/zkvm 26 | [RISC Zero]: https://www.risczero.com/ 27 | [guest programs]: https://dev.risczero.com/terminology#guest-program 28 | [on-chain logic]: ../contracts/ 29 | [guest/src/bin]: ./guest/src/bin/ 30 | [Guest Code 101]: https://dev.risczero.com/zkvm/developer-guide/guest-code-101 31 | [RISC Zero examples]: https://github.com/risc0/tree/v0.18.0/examples -------------------------------------------------------------------------------- /bonsai-pay/methods/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::{collections::HashMap, env}; 16 | 17 | use risc0_build::{embed_methods_with_options, DockerOptions, GuestOptions}; 18 | use risc0_build_ethereum::generate_solidity_files; 19 | 20 | // Paths where the generated Solidity files will be written. 21 | const SOLIDITY_IMAGE_ID_PATH: &str = "../contracts/ImageID.sol"; 22 | const SOLIDITY_ELF_PATH: &str = "../tests/Elf.sol"; 23 | 24 | fn main() { 25 | // Builds can be made deterministic, and thereby reproducible, by using Docker to build the 26 | // guest. Check the RISC0_USE_DOCKER variable and use Docker to build the guest if set. 27 | let use_docker = env::var("RISC0_USE_DOCKER").ok().map(|_| DockerOptions { 28 | root_dir: Some("../".into()), 29 | }); 30 | 31 | // Generate Rust source files for the methods crate. 32 | let guests = embed_methods_with_options(HashMap::from([( 33 | "guests", 34 | GuestOptions { 35 | features: Vec::new(), 36 | use_docker, 37 | }, 38 | )])); 39 | 40 | // Generate Solidity source files for use with Forge. 41 | let solidity_opts = risc0_build_ethereum::Options::default() 42 | .with_image_id_sol_path(SOLIDITY_IMAGE_ID_PATH) 43 | .with_elf_sol_path(SOLIDITY_ELF_PATH); 44 | 45 | generate_solidity_files(guests.as_slice(), &solidity_opts).unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /bonsai-pay/methods/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "jwt_validator" 8 | path = "src/bin/jwt_validator.rs" 9 | 10 | [workspace] 11 | 12 | [dependencies] 13 | alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } 14 | alloy-sol-types = { version = "0.6" } 15 | risc0-zkvm = { version = "1.0.5", default-features = false, features = ['std'] } 16 | oidc-validator = { version = "0.1.0", path = "../../oidc-validator" } 17 | 18 | [profile.release] 19 | lto = "thin" 20 | -------------------------------------------------------------------------------- /bonsai-pay/methods/guest/README.md: -------------------------------------------------------------------------------- 1 | # Guest Programs 2 | 3 | Each file in the [`src/bin`](./src/bin) folder defines a program for the zkVM. 4 | We refer to the program running in the zkVM as the "[guest]". 5 | 6 | To learn more about writing guest programs, check out the zkVM [developer docs]. 7 | For zkVM API documentation, see the [guest module] of the [`risc0-zkvm`] crate. 8 | 9 | [guest]: https://dev.risczero.com/terminology#guest 10 | [developer docs]: https://dev.risczero.com/zkvm 11 | [guest module]: https://docs.rs/risc0-zkvm/latest/risc0_zkvm/guest/index.html 12 | [`risc0-zkvm`]: https://docs.rs/risc0-zkvm/latest/risc0_zkvm/index.html 13 | -------------------------------------------------------------------------------- /bonsai-pay/methods/guest/src/bin/jwt_validator.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use alloy_primitives::{Address, FixedBytes}; 16 | use alloy_sol_types::SolValue; 17 | use oidc_validator::IdentityProvider; 18 | use risc0_zkvm::guest::env; 19 | use risc0_zkvm::sha::rust_crypto::{Digest as _, Sha256}; 20 | use std::io::Read; 21 | 22 | alloy_sol_types::sol! { 23 | struct Input { 24 | uint256 identity_provider; 25 | string jwt; 26 | } 27 | 28 | struct ClaimsData { 29 | address msg_sender; 30 | bytes32 claim_id; 31 | } 32 | } 33 | 34 | fn main() { 35 | let mut input_bytes = Vec::::new(); 36 | env::stdin().read_to_end(&mut input_bytes).unwrap(); 37 | let input: Input = ::abi_decode(&input_bytes, true).unwrap(); 38 | 39 | let identity_provider: IdentityProvider = input.identity_provider.into(); 40 | let jwt: String = input.jwt; 41 | let (claim_id, msg_sender) = identity_provider.validate(&jwt).unwrap(); 42 | let msg_sender: Address = Address::parse_checksummed(msg_sender, None).unwrap(); 43 | let claim_id: FixedBytes<32> = 44 | FixedBytes::from_slice(Sha256::digest(claim_id.as_bytes()).as_slice()); 45 | 46 | let output = ClaimsData { 47 | msg_sender, 48 | claim_id, 49 | }; 50 | let output = output.abi_encode(); 51 | env::commit_slice(&output); 52 | } 53 | -------------------------------------------------------------------------------- /bonsai-pay/methods/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Generated crate containing the image ID and ELF binary of the build guest. 16 | include!(concat!(env!("OUT_DIR"), "/methods.rs")); 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use alloy_primitives::U256; 21 | use alloy_sol_types::{sol, SolValue}; 22 | use risc0_zkvm::{default_executor, ExecutorEnv}; 23 | 24 | sol! { 25 | interface IBonsaiPay { 26 | function claim(address payable to, bytes32 claim_id, bytes32 post_state_digest, bytes calldata seal); 27 | } 28 | 29 | struct Input { 30 | uint256 identity_provider; 31 | string jwt; 32 | } 33 | 34 | struct ClaimsData { 35 | address msg_sender; 36 | bytes32 claim_id; 37 | } 38 | } 39 | 40 | #[test] 41 | fn test_jwt_validator() { 42 | let jwt: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ijg3OTJlN2MyYTJiN2MxYWI5MjRlMTU4YTRlYzRjZjUxIn0.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIiwibm9uY2UiOiIweDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifQ.TPUrmStwY2iuqMLXn3WvpiJY1W-bbrU12WGuv0nK9NJ6Q0bT8D_Ags8qj8LPOGGE1CdHn2isBcHgSxaEbNbW8Pz0fVWpFiehj8BwrC47Rld5dwazsxghF84D3q2So5ZBQslWqq1PRGEFKfx4AOgnS375oKi2jAZ3jN_58UNdgtUUdFhuOGHvGbWnr_fEWIbrEcfNFIWahngQ2dbU-sSNZFZ5L3L46bXUkBlbGGNztr6OiAHUwxqH2A02h1EceUol2m6_GTvPfdXKzd0Z34CJNW_loAEheH69hkmkGPbt3ta_XAFWRHgmVN7gFjErRmPiB818YgAFBBIuhZnjvGmC5Q"; 43 | 44 | let input = Input { 45 | identity_provider: U256::from(1), 46 | jwt: jwt.to_string(), 47 | }; 48 | 49 | let env = ExecutorEnv::builder() 50 | .write_slice(&input.abi_encode()) 51 | .build() 52 | .unwrap(); 53 | 54 | let session_info = default_executor() 55 | .execute(env, super::JWT_VALIDATOR_ELF) 56 | .unwrap(); 57 | 58 | let output = ClaimsData::abi_decode(&session_info.journal.bytes, true).unwrap(); 59 | 60 | assert_eq!( 61 | output.msg_sender.to_string(), 62 | "0x0000000000000000000000000000000000000000" 63 | ); 64 | 65 | assert_eq!( 66 | output.claim_id.to_string(), 67 | "0x73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2" 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /bonsai-pay/oidc-validator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oidc-validator" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [dependencies] 7 | jwt-compact = { version = "0.7", default-features = false, features = ["rsa"] } 8 | lazy_static = "1.4.0" 9 | serde = "1.0" 10 | serde_json = "1.0" 11 | thiserror = "1.0" 12 | alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } 13 | alloy-sol-types = { version = "0.6" } 14 | -------------------------------------------------------------------------------- /bonsai-pay/oidc-validator/README.md: -------------------------------------------------------------------------------- 1 | # OIDC Validator 2 | 3 | This directory serves as the core library used in the guest and host. The library allows for the validation of Google issued JWTs using the [jwt-compact] crate. 4 | 5 | [jwt-compact]: https://github.com/slowli/jwt-compact 6 | -------------------------------------------------------------------------------- /bonsai-pay/oidc-validator/src/certs.rs: -------------------------------------------------------------------------------- 1 | pub static GOOGLE_PUB_JWK: &str = r#" 2 | { 3 | "keys": [ 4 | { 5 | "e": "AQAB", 6 | "n": "1rT4OUZfqfra6qUP07ZInRj7GsobWmDcmEu5K4HdYJoqCw_lxejxqRyUNtbnJXiKoJwFbnl_KHwTHA04v3Vkqh-zy03Yk8Ft4uGi7PYs1niZU6ZbpW3V8QqY1TMFVLRJgartw0Pjg7hIWoyv5QnQkuVjVZZ-MhnuQRxKghizCVl6IZWV4vemm5_llbtuN0ex4DmgA1GI8wrjdtWOoff1ybl_AwBwQlyQ9XmWru_Ea3MWHhii7sepWLk21wu_7iyUHw0VUBL8E-eM4ZeCfkTUt6ZpTw45aPIgIJC0m-cpscK2lmC-DG_irif14NtyrxfwF-cwSOLhzQVg1diPeWPH1Q", 7 | "alg": "RS256", 8 | "kty": "RSA", 9 | "use": "sig", 10 | "kid": "b2f80c634600ed13072101a8b4220443433db282" 11 | }, 12 | { 13 | "alg": "RS256", 14 | "n": "wNHgGSG5B5xOEQNFPW2p_6ZxZbfPoAU5VceBUuNwQWLop0ohW0vpoZLU1tAsq_S9s5iwy27rJw4EZAOGBR9oTRq1Y6Li5pDVJfmzyRNtmWCWndR-bPqhs_dkJU7MbGwcvfLsN9FSHESFrS9sfGtUX-lZfLoGux23TKdYV9EE-H-NDASxrVFUk2GWc3rL6UEMWrMnOqV9-tghybDU3fcRdNTDuXUr9qDYmhmNegYjYu4REGjqeSyIG1tuQxYpOBH-tohtcfGY-oRTS09kgsSS9Q5BRM4qqCkGP28WhlSf4ui0-norS0gKMMI1P_ZAGEsLn9p2TlYMpewvIuhjJs1thw", 15 | "kty": "RSA", 16 | "e": "AQAB", 17 | "use": "sig", 18 | "kid": "d7b939771a7800c413f90051012d975981916d71" 19 | } 20 | ] 21 | } 22 | "#; 23 | 24 | pub static TEST_PUB_JWK: &str = r#" 25 | { 26 | "keys" : [ 27 | { 28 | "alg": "RS256", 29 | "e": "AQAB", 30 | "kty": "RSA", 31 | "n": "y-jiMQRB9zDOYbIaCoA4ppJ4prXbLhsM6upxCiip_6niQM_LHcCZxt_cFe88yi29Rgj1iEkOIJgXosydJLAtiOJHh1n7-FdSWEgKn3EfzI_VSncT2jnW6r3TtApzmHdDQnZmRKLB4mGXvnkwK-xzkpTRRM8r-m2A9dAylx0mGMqUabYYNg0n8x3EFG9ciFI5c3JwmMm8bHDw8BkhiHtG09nr7FkrEpn4tbhX9d7OeL-rbYLb2_H49BSX9L4O1vCOqf0cQMpSfhWiw7UjLjECzKlo0HNtELrpubBQbgZc9UbNlfCiaK4QO_fLog_YhY5Taxu05MViQvV_rxCi4ZwddQ", 32 | "use": "sig", 33 | "kid": "8792e7c2a2b7c1ab924e158a4ec4cf51" 34 | } 35 | ] 36 | } 37 | "#; 38 | -------------------------------------------------------------------------------- /bonsai-pay/remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | openzeppelin/=lib/openzeppelin-contracts/ 3 | risc0/=lib/risc0-ethereum/contracts/src/ 4 | risc0-test/=lib/risc0-ethereum/contracts/src/test/ 5 | -------------------------------------------------------------------------------- /bonsai-pay/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustfmt", "rust-src"] 4 | profile = "minimal" -------------------------------------------------------------------------------- /bonsai-pay/script/config.toml: -------------------------------------------------------------------------------- 1 | [profile.mainnet] 2 | # RISC Zero Verifier contract deployed on mainnet (see https://dev.risczero.com/api/blockchain-integration/contracts/verifier#deployed-verifiers) 3 | chainId = 1 4 | riscZeroVerifierAddress = "0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319" 5 | 6 | [profile.sepolia] 7 | # RISC Zero Verifier contract deployed on sepolia (see https://dev.risczero.com/api/blockchain-integration/contracts/verifier#deployed-verifiers) 8 | chainId = 11155111 9 | riscZeroVerifierAddress = "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187" 10 | 11 | # You can add additional profiles here 12 | # [profile.custom] 13 | # chainId = 11155111 14 | # riscZeroVerifierAddress = 15 | -------------------------------------------------------------------------------- /bonsai-pay/ui/.env.example: -------------------------------------------------------------------------------- 1 | VITE_GOOGLE_CLIENT_ID= 2 | VITE_REDIRECT_URI= 3 | VITE_API_HOST= 4 | ETHERSCAN_APIKEY= 5 | VITE_CUSTODY_ADDRESS= 6 | -------------------------------------------------------------------------------- /bonsai-pay/ui/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | ], 9 | ignorePatterns: ["dist", ".eslintrc.cjs"], 10 | parser: "@typescript-eslint/parser", 11 | plugins: ["react-refresh"], 12 | rules: { 13 | "react-refresh/only-export-components": [ 14 | "warn", 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /bonsai-pay/ui/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es2021: true 4 | extends: 5 | - standard-with-typescript 6 | - plugin:react/recommended 7 | parserOptions: 8 | ecmaVersion: latest 9 | sourceType: module 10 | plugins: 11 | - react 12 | rules: {} 13 | -------------------------------------------------------------------------------- /bonsai-pay/ui/README.md: -------------------------------------------------------------------------------- 1 | # Bonsai Pay 2 | -------------------------------------------------------------------------------- /bonsai-pay/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bonsai Pay 8 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /bonsai-pay/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bonsai-pay", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build --mode production", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "format": "prettier --plugin-search-dir . --write .", 12 | "clean": "rm -rf node_modules && rm -rf dist && rm -rf .vite" 13 | }, 14 | "dependencies": { 15 | "@noble/hashes": "^1.4.0", 16 | "connectkit": "^1.5.3", 17 | "dotenv": "^16.3.1", 18 | "jwt-decode": "3.1.2", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-toastify": "^9.1.3", 22 | "use-debounce": "^9.0.4", 23 | "viem": "^1.18.4", 24 | "wagmi": "^1.4.5" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "^18.2.15", 28 | "@types/react-dom": "^18.2.7", 29 | "@typescript-eslint/eslint-plugin": "^6.9.1", 30 | "@typescript-eslint/parser": "^6.9.1", 31 | "@vitejs/plugin-react": "^4.0.3", 32 | "@wagmi/cli": "^1.5.2", 33 | "eslint": "^8.53.0", 34 | "eslint-config-standard-with-typescript": "^39.1.1", 35 | "eslint-plugin-import": "^2.25.2", 36 | "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", 37 | "eslint-plugin-promise": "^6.0.0", 38 | "eslint-plugin-react": "^7.33.2", 39 | "eslint-plugin-react-hooks": "^4.6.0", 40 | "eslint-plugin-react-refresh": "^0.4.3", 41 | "typescript": "^5.2.2", 42 | "vite": "^4.4.5", 43 | "vite-plugin-node-polyfills": "^0.16.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bonsai-pay/ui/public/eth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /bonsai-pay/ui/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/assets/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "sepolia": [ 3 | { 4 | "address": "0x0000000000000000000000000000000000000000", 5 | "name": "ETH", 6 | "icon": "/eth.svg", 7 | "decimals": 18 8 | } 9 | ], 10 | "goerli": [ 11 | { 12 | "address": "0x0000000000000000000000000000000000000000", 13 | "name": "ETH", 14 | "icon": "/eth.svg", 15 | "decimals": 18 16 | } 17 | ], 18 | "localhost": [ 19 | { 20 | "address": "0x0000000000000000000000000000000000000000", 21 | "name": "ETH", 22 | "icon": "/eth.svg", 23 | "decimals": 18 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/components/Account.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { zeroAddress, toHex } from "viem"; 3 | import tokens from "../assets/tokens.json"; 4 | import { Balance } from "./Balance"; 5 | import { Token } from "../libs/types"; 6 | import { sha256 } from "@noble/hashes/sha256"; 7 | 8 | interface AccountProps { 9 | email: string | null; 10 | disabled: boolean; 11 | hideClaim?: boolean; 12 | } 13 | 14 | const Account: React.FC = (props) => { 15 | const { email, disabled, hideClaim } = props; 16 | 17 | const claimId = toHex(sha256(email ?? "")); 18 | 19 | return ( 20 | <> 21 |
22 | 31 |
32 | 33 | ); 34 | }; 35 | 36 | export default Account; 37 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/components/Balance.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { formatUnits } from "viem"; 3 | import { Token } from "../libs/types"; 4 | import { useBonsaiPayBalanceOf } from "../generated"; 5 | 6 | interface BalanceProps { 7 | identity: `0x${string}`; 8 | token: Token; 9 | disabled: boolean; 10 | hideClaim?: boolean; 11 | } 12 | 13 | export const Balance: React.FC = ({ 14 | identity, 15 | token, 16 | disabled, 17 | hideClaim, 18 | }) => { 19 | 20 | const { data: balance, refetch: refetchBalance 21 | } = useBonsaiPayBalanceOf({ 22 | args: [identity], 23 | }); 24 | 25 | // refresh balance every 5 seconds 26 | useEffect(() => { 27 | const intervalId = setInterval(() => { 28 | refetchBalance?.(); 29 | }, 5000); 30 | 31 | return () => clearInterval(intervalId); 32 | }); 33 | 34 | return ( 35 | <> 36 |
37 |

Availiable Balance:

38 |
39 |
40 | {token.name} 41 |

42 | {` ${formatUnits(balance || 0n, token.decimals)} ${token.name} 43 | `} 44 |

45 |
46 | 52 | 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/components/Claim.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Prove from "./Prove"; 3 | import { useAccount } from "wagmi"; 4 | import { GoogleTokenPayload } from "../libs/types"; 5 | import { SignInWithGoogle } from "./SignInWithGoogle"; 6 | 7 | interface ClaimProps {} 8 | 9 | const Claim: React.FC = () => { 10 | const { isConnected } = useAccount(); 11 | const [jwtExists, setJwtExists] = useState(false); 12 | const [email, setEmail] = useState(null); 13 | const [currentStep, setCurrentStep] = useState(1); 14 | 15 | useEffect(() => { 16 | const intervalId = setInterval(() => { 17 | // Check for JWT cookie 18 | const jwt = document.cookie 19 | .split("; ") 20 | .find((row) => row.startsWith("jwt=")); 21 | const jwtValue = jwt && jwt.split("=")[1]; 22 | let newJwtExists = false; 23 | 24 | if (jwtValue && jwtValue !== "") { 25 | newJwtExists = true; 26 | const payload: GoogleTokenPayload = JSON.parse( 27 | atob(jwtValue.split(".")[1]) 28 | ); 29 | setEmail(payload.email); 30 | } else { 31 | setEmail(null); 32 | } 33 | 34 | setJwtExists(newJwtExists); 35 | 36 | 37 | }, 1000); 38 | 39 | return () => clearInterval(intervalId); 40 | }, []); 41 | 42 | const stepDescriptions = [ 43 | "Connect Wallet", 44 | "Sign In", 45 | "Prove & Claim", 46 | ]; 47 | 48 | const renderStepIndicator = () => { 49 | return ( 50 |
51 | {stepDescriptions.map((description, index) => ( 52 |
56 | {index + 1}: {description} 57 |
58 | ))} 59 |
60 | ); 61 | }; 62 | 63 | useEffect(() => { 64 | if (!isConnected) { 65 | setCurrentStep(1); 66 | } else if (isConnected && !jwtExists) { 67 | setCurrentStep(2); 68 | } else if (isConnected && jwtExists) { 69 | setCurrentStep(3); 70 | } 71 | }, [isConnected, jwtExists]); 72 | 73 | const renderCurrentStep = () => { 74 | switch (currentStep) { 75 | case 1: 76 | return

Please connect your wallet to proceed.

; 77 | case 2: 78 | return ( 79 | <> 80 |

Sign in to your account

81 | 82 | {/*

or

83 | */} 84 | 85 | ); 86 | case 3: 87 | return ( 88 | <> 89 |

Prove account ownership

90 | 91 | {email &&
{`Welcome, ${email}`}
} 92 | 93 |
94 | Proving ownership will automatically 95 |

96 | deposit the balance to your wallet. 97 |
98 | 99 | ); 100 | default: 101 | return

Unknown step

; 102 | } 103 | }; 104 | 105 | return ( 106 |
107 | {renderStepIndicator()} 108 |
{renderCurrentStep()}
109 |
110 | ); 111 | }; 112 | 113 | export default Claim; 114 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from "react"; 2 | 3 | interface ModalProps { 4 | isOpen: boolean; 5 | onClose: () => void; 6 | title?: string; 7 | children: ReactNode; 8 | } 9 | 10 | const Modal: FC = ({ isOpen, onClose, title, children }) => { 11 | if (!isOpen) return null; 12 | 13 | return ( 14 |
15 |
16 | {title &&

{title}

} 17 |
{children}
18 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default Modal; 27 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/components/Prove.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useEffect } from "react"; 2 | import Account from "./Account"; 3 | import { useBonsaiPayClaimedEvent, useBonsaiPayBalanceOf } from "../generated"; 4 | import { sha256 } from "@noble/hashes/sha256"; 5 | import { toHex } from "viem"; 6 | 7 | interface ProveProps { 8 | disabled: boolean; 9 | email: string | null; 10 | } 11 | 12 | const Prove: React.FC = ({ disabled, email }) => { 13 | const [isLoading, setIsLoading] = useState(false); 14 | const [isClaimed, setIsClaimed] = useState(false); 15 | const [isNonZeroBalance, setIsNonZeroBalance] = useState(false); 16 | 17 | useBonsaiPayClaimedEvent({ 18 | listener: () => { 19 | setIsClaimed(true); 20 | }, 21 | }); 22 | 23 | const { data: balance } = useBonsaiPayBalanceOf({ 24 | args: [toHex(sha256(email ?? ""))], 25 | }); 26 | 27 | useEffect(() => { 28 | setIsNonZeroBalance(balance !== 0n); 29 | }, [balance]); 30 | 31 | 32 | const { VITE_API_HOST } = import.meta.env; 33 | 34 | const handleClick = useCallback(async () => { 35 | setIsLoading(true); 36 | 37 | const jwtCookie = document.cookie 38 | .split("; ") 39 | .find((row) => row.startsWith("jwt=")); 40 | const jwt = jwtCookie?.split("=")[1]; 41 | 42 | if (!jwt) { 43 | console.error("JWT not found"); 44 | setIsLoading(false); 45 | return; 46 | } 47 | 48 | try { 49 | const response = await fetch(`${VITE_API_HOST}/auth`, { 50 | method: "GET", 51 | headers: { 52 | "X-Auth-Token": jwt, 53 | }, 54 | }); 55 | 56 | if (response.ok) { 57 | await response.body; 58 | } else { 59 | throw new Error("Response not OK"); 60 | } 61 | } catch (error) { 62 | console.error("Error fetching data:", error); 63 | } finally { 64 | setIsLoading(false); 65 | } 66 | // eslint-disable-next-line react-hooks/exhaustive-deps 67 | }, []); 68 | 69 | return ( 70 | <> 71 | 72 | 75 | {isLoading ?

This will take a few moments...

:

} 76 | 77 | ); 78 | }; 79 | 80 | export default Prove; -------------------------------------------------------------------------------- /bonsai-pay/ui/src/components/SignInWithApple.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | NOTE: This component is not used in the demo, but it is a good example of how to implement Apple Sign In. 3 | */ 4 | import { useEffect, useState } from "react"; 5 | import { useAccount } from "wagmi"; 6 | 7 | declare global { 8 | interface Window { 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | AppleID?: any; 11 | } 12 | } 13 | 14 | export const SignInWithApple = ({ disabled }) => { 15 | const { address } = useAccount(); 16 | const [appleAuth, setAppleAuth] = useState(null); 17 | 18 | useEffect(() => { 19 | const loadAppleAuthScript = () => { 20 | if (!window.AppleID) { 21 | const script = document.createElement("script"); 22 | script.src = 23 | "https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"; 24 | script.onload = initializeAppleSignIn; 25 | document.body.appendChild(script); 26 | } else { 27 | initializeAppleSignIn(); 28 | } 29 | }; 30 | 31 | loadAppleAuthScript(); 32 | }); 33 | 34 | const initializeAppleSignIn = () => { 35 | const { VITE_APPLE_SERVICE_ID, VITE_APPLE_REDIRECT_URI } = import.meta.env; 36 | if (window.AppleID) { 37 | try { 38 | setAppleAuth( 39 | window.AppleID.auth.init({ 40 | clientId: VITE_APPLE_SERVICE_ID, 41 | scope: "email", 42 | redirectURI: VITE_APPLE_REDIRECT_URI, 43 | state: address, 44 | usePopup: false, 45 | }) 46 | ); 47 | } catch (error) { 48 | console.error("Error initializing Apple Sign In:", error); 49 | } 50 | } 51 | }; 52 | 53 | useEffect(() => { 54 | const onSignInSuccess = (event) => { 55 | handleAppleResponse(event.detail.data); 56 | }; 57 | 58 | const onSignInFailure = (event) => { 59 | console.error("Apple sign in failed:", event.detail.error); 60 | }; 61 | 62 | window.addEventListener("AppleIDSignInOnSuccess", onSignInSuccess); 63 | window.addEventListener("AppleIDSignInOnFailure", onSignInFailure); 64 | 65 | return () => { 66 | window.removeEventListener("AppleIDSignInOnSuccess", onSignInSuccess); 67 | window.removeEventListener("AppleIDSignInOnFailure", onSignInFailure); 68 | }; 69 | }, []); 70 | 71 | const handleAppleResponse = async (response) => { 72 | console.log("Apple sign in response received:", response); 73 | // TODO 74 | }; 75 | 76 | useEffect(() => { 77 | if (appleAuth && document.getElementById("appleid-signin")) { 78 | window.AppleID.auth.renderButton({ 79 | element: "#appleid-signin", 80 | theme: "black", // or 'white' 81 | type: "sign in", 82 | border: true, 83 | disabled: disabled, 84 | }); 85 | } 86 | }, [appleAuth, disabled]); 87 | 88 | return ( 89 |
90 |
91 |
92 | ); 93 | }; 94 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/components/SignInWithGoogle.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useAccount } from "wagmi"; 3 | 4 | declare global { 5 | interface Window { 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 7 | google?: any; 8 | } 9 | } 10 | 11 | export const SignInWithGoogle = ({ disabled }) => { 12 | const { VITE_GOOGLE_CLIENT_ID } = import.meta.env; 13 | const { address } = useAccount(); 14 | 15 | useEffect(() => { 16 | // Ensure that the initGsi function is idempotent 17 | const initGsi = () => { 18 | if (window.google?.accounts) { 19 | // Clean up the existing button 20 | const existingButton = document.getElementById("googleSignInButton"); 21 | if (existingButton) { 22 | existingButton.innerHTML = ""; 23 | } 24 | 25 | // Initialize the button 26 | window.google.accounts.id.initialize({ 27 | client_id: VITE_GOOGLE_CLIENT_ID, 28 | callback: handleCredentialResponse, 29 | nonce: address, 30 | scope: "email", 31 | auto_select: false, 32 | state: "hello", 33 | }); 34 | 35 | // Render the button 36 | window.google.accounts.id.renderButton( 37 | document.getElementById("googleSignInButton"), 38 | { theme: "outline", size: "large" } 39 | ); 40 | } 41 | }; 42 | 43 | // Create a script to load Google's accounts library 44 | const createScript = () => { 45 | const script = document.createElement("script"); 46 | script.src = "https://accounts.google.com/gsi/client"; 47 | script.async = true; 48 | script.defer = true; 49 | script.onload = () => { 50 | // Call initGsi on script load 51 | initGsi(); 52 | }; 53 | document.body.appendChild(script); 54 | }; 55 | 56 | // Check if Google's accounts library is available 57 | if (window.google?.accounts) { 58 | initGsi(); 59 | } else { 60 | createScript(); 61 | } 62 | 63 | return () => { 64 | const script = document.querySelector( 65 | 'script[src="https://accounts.google.com/gsi/client"]' 66 | ); 67 | if (script) { 68 | script.remove(); 69 | } 70 | initGsi(); 71 | }; 72 | }, [VITE_GOOGLE_CLIENT_ID, address]); 73 | 74 | const handleCredentialResponse = async (response) => { 75 | const { credential } = response; 76 | 77 | document.cookie = `jwt=${credential}; path=/; samesite=Strict`; 78 | }; 79 | 80 | return ( 81 |
82 | 88 |
89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/libs/types.ts: -------------------------------------------------------------------------------- 1 | export type Token = { 2 | address: `0x${string}`; 3 | name: string; 4 | icon: string; 5 | decimals: number; 6 | }; 7 | 8 | export type GoogleTokenPayload = { 9 | iss: string; 10 | azp: string; 11 | aud: string; 12 | sub: string; 13 | hd: string; 14 | email: string; 15 | email_verified: boolean; 16 | at_hash: string; 17 | nonce: string; 18 | iat: number; 19 | exp: number; 20 | }; 21 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/libs/utils.ts: -------------------------------------------------------------------------------- 1 | export const clearCookies = () => { 2 | document.cookie.split(";").map((cookie) => { 3 | const eqPos = cookie.indexOf("="); 4 | const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie.trim(); 5 | document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root")!).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /bonsai-pay/ui/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /bonsai-pay/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": false, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /bonsai-pay/ui/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /bonsai-pay/ui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import { nodePolyfills } from "vite-plugin-node-polyfills"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | build: { 8 | target: "es2020", 9 | }, 10 | plugins: [react(), nodePolyfills()], 11 | }); 12 | -------------------------------------------------------------------------------- /bonsai-pay/ui/wagmi.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@wagmi/cli"; 2 | import { etherscan, react } from "@wagmi/cli/plugins"; 3 | import { erc20ABI } from "wagmi"; 4 | import { sepolia } from "wagmi/chains"; 5 | import "dotenv/config"; 6 | 7 | if (!process.env.ETHERSCAN_APIKEY) { 8 | throw new Error("Missing ETHERSCAN_API_KEY"); 9 | } 10 | 11 | if (!process.env.VITE_CUSTODY_ADDRESS) { 12 | throw new Error("Missing BONSAIPAY_ADDRESS"); 13 | } 14 | 15 | export default defineConfig({ 16 | out: "src/generated.ts", 17 | contracts: [ 18 | { 19 | name: "erc20", 20 | abi: erc20ABI, 21 | }, 22 | ], 23 | plugins: [ 24 | etherscan({ 25 | apiKey: process.env.ETHERSCAN_APIKEY, 26 | chainId: sepolia.id, 27 | contracts: [ 28 | { 29 | name: "BonsaiPay", 30 | address: { 31 | [sepolia.id]: process.env.VITE_CUSTODY_ADDRESS as `0x${string}`, 32 | }, 33 | }, 34 | ], 35 | }), 36 | react(), 37 | ], 38 | }); 39 | -------------------------------------------------------------------------------- /near-zk-light-client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Cargo.lock 3 | methods/guest/Cargo.lock 4 | target/ 5 | -------------------------------------------------------------------------------- /near-zk-light-client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "./methods/guest/Cargo.toml", 4 | "./host/Cargo.toml" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /near-zk-light-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["host", "methods", "types"] 4 | 5 | # Always optimize; building and running the guest takes much longer without optimization. 6 | [profile.dev] 7 | opt-level = 3 8 | 9 | [profile.dev.build-override] 10 | opt-level = 3 11 | 12 | [profile.release] 13 | debug = 1 14 | lto = true 15 | 16 | [profile.release.build-override] 17 | opt-level = 3 18 | 19 | [patch.crates-io] 20 | crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.2-risc0" } 21 | ed25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.0-risczero.1" } 22 | -------------------------------------------------------------------------------- /near-zk-light-client/README.md: -------------------------------------------------------------------------------- 1 | # Near ZK Light Client 2 | 3 | > WARNING: this project is experimental and should not be used for any production use cases. Use at your own risk. 4 | 5 | This demo contains an example of ZK proving NEAR light client blocks. 6 | 7 | Light clients allow for verifying consensus of a network without executing the state machine transactions within. Full nodes require much more storage, compute, and bandwidth which is unnecessary for some use cases. 8 | 9 | Light clients form the basis for a lot of different interoperability use cases as well as efficient state sync. For those use cases, it usually relies on either the light client block validation to be done on-chain or for users to verify blocks from an arbitrary point in the past, depending on trust assumptions. This is unfortunate because it's either too computationally or financially expensive and sometimes relies on a trusted provider for certain use cases. 10 | 11 | Generating a ZK proof of the light client verification is ideal in this case, especially if recursively proven, because you could only verify a single succinct proof and have guarantees about the correctness of the entire history or range of blocks. This not only dramatically minimizes the compute for users or protocols, but also dramatically minimizes the amount of data to verify, since you don't need to commit data for all validators in the committee, including their signatures on each block. 12 | 13 | ## Quick Start 14 | 15 | First, [install] `v0.20` of the RISC Zero zkVM. 16 | > Note: This demo only works with `v0.20`. 17 | 18 | To build all methods and execute the method within the zkVM, run the following 19 | command: 20 | 21 | ```bash 22 | cargo run 23 | ``` 24 | 25 | This is an empty template, and so there is no expected output (until you modify 26 | the code). 27 | 28 | ### Executing the project locally in development mode 29 | 30 | During development, faster iteration upon code changes can be achieved by leveraging [dev-mode], we strongly suggest activating it during your early development phase. Furthermore, you might want to get insights into the execution statistics of your project, and this can be achieved by specifying the environment variable `RUST_LOG="executor=info"` before running your project. 31 | 32 | Put together, the command to run your project in development mode while getting execution statistics is: 33 | 34 | ```bash 35 | RUST_LOG="executor=info" RISC0_DEV_MODE=1 cargo run 36 | ``` 37 | 38 | ### Running proofs remotely on Bonsai 39 | 40 | _Note: The Bonsai proving service is still in early Alpha; an API key is 41 | required for access. [Click here to request access][bonsai access]._ 42 | 43 | If you have access to the URL and API key to Bonsai you can run your proofs 44 | remotely. To prove in Bonsai mode, invoke `cargo run` with two additional 45 | environment variables: 46 | 47 | ```bash 48 | BONSAI_API_KEY="YOUR_API_KEY" BONSAI_API_URL="BONSAI_URL" cargo run 49 | ``` 50 | 51 | ## Directory Structure 52 | 53 | It is possible to organize the files for these components in various ways. 54 | However, in this starter template we use a standard directory structure for zkVM 55 | applications, which we think is a good starting point for your applications. 56 | 57 | ```text 58 | project_name 59 | ├── Cargo.toml 60 | ├── host 61 | │ ├── Cargo.toml 62 | │ └── src 63 | │ └── main.rs <-- [Host code goes here] 64 | └── methods 65 | ├── Cargo.toml 66 | ├── build.rs 67 | ├── guest 68 | │ ├── Cargo.toml 69 | │ └── src 70 | │ └── bin 71 | │ └── method_name.rs <-- [Guest code goes here] 72 | └── src 73 | └── lib.rs 74 | ``` 75 | 76 | ## Video Tutorial 77 | 78 | For a walk-through of how to build with this template, check out this [excerpt 79 | from our workshop at ZK HACK III][zkhack-iii]. 80 | 81 | ## Questions, Feedback, and Collaborations 82 | 83 | We'd love to hear from you on [Discord][discord] or [Twitter][twitter]. 84 | 85 | [bonsai access]: https://bonsai.xyz/apply 86 | [cargo-risczero]: https://docs.rs/cargo-risczero 87 | [crates]: https://github.com/risc0/risc0/blob/main/README.md#rust-binaries 88 | [dev-docs]: https://dev.risczero.com 89 | [dev-mode]: https://dev.risczero.com/api/zkvm/dev-mode 90 | [discord]: https://discord.gg/risczero 91 | [docs.rs]: https://docs.rs/releases/search?query=risc0 92 | [examples]: https://github.com/risc0/risc0/tree/main/examples 93 | [install]: https://dev.risczero.com/api/zkvm/install 94 | [risc0-build]: https://docs.rs/risc0-build 95 | [risc0-repo]: https://www.github.com/risc0/risc0 96 | [risc0-zkvm]: https://docs.rs/risc0-zkvm 97 | [rustup]: https://rustup.rs 98 | [rust-toolchain]: rust-toolchain.toml 99 | [twitter]: https://twitter.com/risczero 100 | [zkvm-overview]: https://dev.risczero.com/zkvm 101 | [zkhack-iii]: https://www.youtube.com/watch?v=Yg_BGqj_6lg&list=PLcPzhUaCxlCgig7ofeARMPwQ8vbuD6hC5&index=5 102 | -------------------------------------------------------------------------------- /near-zk-light-client/host/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "host" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | methods = { path = "../methods" } 8 | risc0-zkvm = { version = "0.20.1" } 9 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 10 | serde = "1.0" 11 | near-zk-types = { path = "../types" } 12 | serde_json = "1.0" 13 | borsh = "1.3.0" 14 | anyhow = "1.0" 15 | -------------------------------------------------------------------------------- /near-zk-light-client/host/src/main.rs: -------------------------------------------------------------------------------- 1 | use methods::{LIGHT_CLIENT_ELF, LIGHT_CLIENT_ID}; 2 | use near_zk_types::{ 3 | LightClientBlockLiteView, LightClientBlockView, PrevBlockContext, ValidatorStakeView, 4 | }; 5 | use risc0_zkvm::{default_prover, ExecutorEnv, Receipt}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[derive(Debug, Deserialize, Serialize)] 9 | struct ExpectedParams { 10 | is_valid: bool, 11 | } 12 | 13 | #[derive(Debug, Deserialize, Serialize)] 14 | struct Params { 15 | previous_block: LightClientBlockLiteView, 16 | current_bps: Vec, 17 | new_block: LightClientBlockView, 18 | } 19 | 20 | #[derive(Debug, Deserialize, Serialize)] 21 | struct TestCase { 22 | description: String, 23 | expected: ExpectedParams, 24 | params: Params, 25 | } 26 | 27 | fn main() -> anyhow::Result<()> { 28 | // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run` 29 | tracing_subscriber::fmt() 30 | .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) 31 | .init(); 32 | 33 | // NOTE: These test vectors come from https://github.com/austinabell/near-light-client-tests 34 | // and are generated using mainnet data pulled from RPC. 35 | let contents = include_str!("../../test-vectors/mainnet-80000000-81000000.json"); 36 | 37 | let test_cases: Vec = serde_json::from_str(&contents)?; 38 | let initial_block_params = &test_cases[0].params; 39 | let mut prev_context: PrevBlockContext = PrevBlockContext::Block { 40 | prev_block: initial_block_params.previous_block.clone(), 41 | current_bps: initial_block_params.current_bps.clone(), 42 | }; 43 | let mut prev_proof: Option = None; 44 | 45 | for test_case in test_cases { 46 | println!("Test description: {}", test_case.description); 47 | let test_case = test_case.params; 48 | let borsh_buffer = borsh::to_vec(&(&LIGHT_CLIENT_ID, &prev_context, &test_case.new_block))?; 49 | 50 | let mut builder = ExecutorEnv::builder(); 51 | if let Some(ref receipt) = prev_proof { 52 | // Verifying a proof recursively requires adding the previous proof as an assumption. 53 | builder.add_assumption(receipt.clone()); 54 | } 55 | let env = builder.write_slice(&borsh_buffer).build()?; 56 | 57 | // Obtain the default prover. 58 | let prover = default_prover(); 59 | 60 | // Produce a receipt by proving the specified ELF binary. 61 | let receipt = prover.prove(env, LIGHT_CLIENT_ELF)?; 62 | 63 | receipt.verify(LIGHT_CLIENT_ID)?; 64 | 65 | // Update the previous context to verify off the last proof. 66 | prev_context = PrevBlockContext::Proof { 67 | journal: receipt.journal.bytes.clone(), 68 | }; 69 | prev_proof = Some(receipt); 70 | } 71 | 72 | Ok(()) 73 | } 74 | -------------------------------------------------------------------------------- /near-zk-light-client/methods/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "methods" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [build-dependencies] 7 | risc0-build = { version = "0.20.1" } 8 | 9 | [package.metadata.risc0] 10 | methods = ["guest"] 11 | -------------------------------------------------------------------------------- /near-zk-light-client/methods/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | risc0_build::embed_methods(); 3 | } 4 | -------------------------------------------------------------------------------- /near-zk-light-client/methods/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "light_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | 8 | [dependencies] 9 | risc0-zkvm = { version = "0.20.1", default-features = false, features = ["std"] } 10 | near-zk-types = { path = "../../types" } 11 | borsh = "1.3" 12 | sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.6-risczero.0" } 13 | 14 | [patch.crates-io] 15 | crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.2-risc0" } 16 | ed25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.0-risczero.1" } 17 | -------------------------------------------------------------------------------- /near-zk-light-client/methods/src/lib.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/methods.rs")); 2 | -------------------------------------------------------------------------------- /near-zk-light-client/types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "near-zk-types" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | borsh = { version = "1.3.0", features = ["derive"] } 8 | bs58 = "0.4.0" 9 | derive_more = "0.99.17" 10 | serde = "1.0.195" 11 | sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.6-risczero.0" } 12 | near-primitives-core = "0.19" 13 | thiserror = "1.0.56" 14 | ed25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.0-risczero.1" } 15 | 16 | [dev-dependencies] 17 | serde_json = "1.0" 18 | 19 | [patch.crates-io] 20 | crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.2-risc0" } -------------------------------------------------------------------------------- /near-zk-light-client/types/src/approval.rs: -------------------------------------------------------------------------------- 1 | use crate::CryptoHash; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use near_primitives_core::types::BlockHeight; 4 | 5 | /// The part of the block approval that is different for endorsements and skips 6 | #[derive(BorshSerialize, BorshDeserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Hash)] 7 | pub enum ApprovalInner { 8 | Endorsement(CryptoHash), 9 | Skip(BlockHeight), 10 | } 11 | 12 | impl ApprovalInner { 13 | pub fn get_data_for_sig(&self, target_height: BlockHeight) -> Vec { 14 | [ 15 | borsh::to_vec(self).unwrap().as_ref(), 16 | target_height.to_le_bytes().as_ref(), 17 | ] 18 | .concat() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /near-zk-light-client/types/src/validator_stake.rs: -------------------------------------------------------------------------------- 1 | use super::crypto::PublicKey; 2 | use super::serde_dec; 3 | use borsh::{BorshDeserialize, BorshSerialize}; 4 | use near_primitives_core::types::{AccountId, Balance}; 5 | use serde::Deserialize; 6 | 7 | #[derive( 8 | BorshSerialize, BorshDeserialize, serde::Serialize, Deserialize, Debug, Clone, Eq, PartialEq, 9 | )] 10 | #[serde(tag = "validator_stake_struct_version")] 11 | pub enum ValidatorStakeView { 12 | V1(ValidatorStakeViewV1), 13 | } 14 | 15 | #[derive( 16 | BorshSerialize, 17 | BorshDeserialize, 18 | Debug, 19 | Clone, 20 | Eq, 21 | PartialEq, 22 | serde::Serialize, 23 | serde::Deserialize, 24 | )] 25 | pub struct ValidatorStakeViewV1 { 26 | pub account_id: AccountId, 27 | pub public_key: PublicKey, 28 | #[serde(with = "serde_dec")] 29 | pub stake: Balance, 30 | } 31 | -------------------------------------------------------------------------------- /zk-kyc/.env.example: -------------------------------------------------------------------------------- 1 | CHAIN_ID= 2 | ETH_WALLET_PRIVATE_KEY= 3 | RPC_URL= 4 | CONTRACT_ADDRESS= -------------------------------------------------------------------------------- /zk-kyc/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/*/11155111/ 9 | /broadcast/**/dry-run/ 10 | 11 | # Autogenerated contracts 12 | contracts/ImageID.sol 13 | tests/Elf.sol 14 | 15 | # Dotenv file 16 | .env 17 | 18 | # Cargo 19 | target/ 20 | 21 | # Misc 22 | .DS_Store 23 | .idea 24 | -------------------------------------------------------------------------------- /zk-kyc/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/openzeppelin/openzeppelin-contracts 7 | [submodule "lib/risc0-ethereum"] 8 | path = lib/risc0-ethereum 9 | url = https://github.com/risc0/risc0-ethereum 10 | -------------------------------------------------------------------------------- /zk-kyc/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default" 3 | } 4 | -------------------------------------------------------------------------------- /zk-kyc/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "./apps/Cargo.toml", 4 | "./methods/Cargo.toml", 5 | "./methods/guest/Cargo.toml", 6 | ] 7 | } -------------------------------------------------------------------------------- /zk-kyc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["apps", "methods"] 4 | exclude = ["lib"] 5 | 6 | [workspace.package] 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [workspace.dependencies] 11 | alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } 12 | alloy-sol-types = { version = "0.6" } 13 | anyhow = { version = "1.0.75" } 14 | bincode = { version = "1.3" } 15 | bonsai-sdk = { version = "0.7" } 16 | bytemuck = { version = "1.14" } 17 | ethers = { version = "2.0" } 18 | hex = { version = "0.4" } 19 | log = { version = "0.4" } 20 | methods = { path = "./methods" } 21 | oidc-validator = { path = "./oidc-validator" } 22 | risc0-build = { version = "1.0.5", features = ["docker"] } 23 | risc0-build-ethereum = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } 24 | risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } 25 | risc0-zkvm = { version = "1.0.5", default-features = false } 26 | risc0-zkp = { version = "1.0.5", default-features = false } 27 | serde = { version = "1.0", features = ["derive", "std"] } 28 | 29 | [profile.release] 30 | debug = 1 31 | lto = true 32 | -------------------------------------------------------------------------------- /zk-kyc/apps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "apps" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [dependencies] 7 | alloy-primitives = { workspace = true } 8 | alloy-sol-types = { workspace = true } 9 | anyhow = { workspace = true } 10 | bincode = { workspace = true } 11 | bytemuck = { workspace = true } 12 | bonsai-sdk = "0.9" 13 | clap = { version = "4.0", features = ["derive", "env"] } 14 | env_logger = { version = "0.10" } 15 | ethers = { workspace = true } 16 | hex = { workspace = true } 17 | log = { workspace = true } 18 | methods = { workspace = true } 19 | risc0-ethereum-contracts = { workspace = true } 20 | risc0-zkvm = { workspace = true } 21 | serde = { workspace = true } 22 | tokio = { version = "1.35", features = ["full"] } 23 | warp = "0.3.7" 24 | reqwest = { version = "0.12.7", features = ["json"] } 25 | serde_json = "1.0.128" 26 | dotenv = "0.15.0" 27 | -------------------------------------------------------------------------------- /zk-kyc/apps/README.md: -------------------------------------------------------------------------------- 1 | # Publisher/Subscriber Application 2 | 3 | The Publisher/Subscriber application is a simple application that listens for incoming JWT tokens, sends a proof request to the [Bonsai] proving service, and publishes the received proofs to a deployed zk-KYC contract on Ethereum. This application is useful for integrating the Bonsai proving service with a deployed zk-KYC contract and UI. 4 | 5 | ## Getting Started 6 | 7 | The [`pubsub` CLI][pubsub], is an application that listens for JWT tokens via HTTP, sends an off-chain proof request to the [Bonsai] proving service, and publishes the received proofs to the deployed zk-KYC contract. 8 | 9 | ### Usage 10 | 11 | Run the `pubsub` with: 12 | 13 | ```sh 14 | cargo run --bin pubsub 15 | ``` 16 | 17 | ```text 18 | $ cargo run --bin pubsub -- --help 19 | 20 | Usage: pubsub --chain-id --eth-wallet-private-key --rpc-url --contract 21 | 22 | Options: 23 | --chain-id 24 | Ethereum chain ID 25 | --eth-wallet-private-key 26 | Ethereum Node endpoint [env: ETH_WALLET_PRIVATE_KEY=] 27 | --rpc-url 28 | Ethereum Node endpoint 29 | --contract 30 | zkKYC's contract address on Ethereum 31 | -h, --help 32 | Print help 33 | -V, --version 34 | Print version 35 | ``` 36 | 37 | The server listens on `http://localhost:8080` for incoming requests with a JWT token in the X-Auth-Token header. Upon receiving a token, it sends a proof request to Bonsai and publishes the received proof to the specified contract on Ethereum. 38 | 39 | #### Example Request 40 | 41 | ```sh 42 | curl -H "X-Auth-Token: " http://localhost:8080/auth 43 | ``` 44 | 45 | ## Library 46 | 47 | A small rust [library] containing utility functions to help with sending off-chain proof requests to the Bonsai proving service and publish the received proofs directly to a deployed app contract on Ethereum. 48 | 49 | [pubsub]: ./src/bin/pubsub.rs 50 | [Bonsai]: https://dev.bonsai.xyz/ 51 | [library]: ./src/lib.rs 52 | -------------------------------------------------------------------------------- /zk-kyc/apps/src/bin/pubsub.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use alloy_primitives::U256; 16 | use alloy_sol_types::{sol, SolInterface, SolValue}; 17 | use anyhow::Context; 18 | use apps::{BonsaiProver, TxSender}; 19 | use dotenv::dotenv; 20 | use log::info; 21 | use methods::JWT_VALIDATOR_ELF; 22 | use std::env; 23 | use tokio::sync::oneshot; 24 | use warp::Filter; 25 | 26 | sol! { 27 | interface IBonsaiPay { 28 | function claim(address payable to, bytes32 claim_id, bytes calldata seal); 29 | } 30 | 31 | struct Input { 32 | uint256 identity_provider; 33 | string jwt; 34 | } 35 | 36 | struct ClaimsData { 37 | address msg_sender; 38 | bytes32 claim_id; 39 | } 40 | } 41 | 42 | const HEADER_XAUTH: &str = "X-Auth-Token"; 43 | 44 | async fn handle_jwt_authentication(token: String) -> Result<(), warp::Rejection> { 45 | if token.is_empty() { 46 | return Err(warp::reject::reject()); 47 | } 48 | 49 | info!("Token received: {}", token); 50 | 51 | let (tx, rx) = oneshot::channel(); 52 | 53 | // Spawn a new thread for the Bonsai Prover computation 54 | std::thread::spawn(move || { 55 | prove_and_send_transaction(token, tx); 56 | }); 57 | 58 | match rx.await { 59 | Ok(_result) => Ok(()), 60 | Err(_) => Err(warp::reject::reject()), 61 | } 62 | } 63 | 64 | fn prove_and_send_transaction(token: String, tx: oneshot::Sender<(Vec, Vec)>) { 65 | dotenv().ok(); 66 | 67 | let input = Input { 68 | identity_provider: U256::ZERO, // Google as the identity provider 69 | jwt: token, 70 | }; 71 | 72 | let (journal, seal) = BonsaiProver::prove(JWT_VALIDATOR_ELF, &input.abi_encode()) 73 | .expect("failed to prove on bonsai"); 74 | 75 | println!("journal: {:?}", journal); 76 | println!("seal: {:?}", seal); 77 | 78 | // let input = input.abi_encode(); 79 | 80 | let chain_id: u64 = env::var("CHAIN_ID") 81 | .expect("CHAIN_ID must be set") 82 | .parse() 83 | .expect("CHAIN_ID must be a valid u64"); 84 | let rpc_url = env::var("RPC_URL").expect("RPC_URL must be set"); 85 | let eth_wallet_private_key = 86 | env::var("ETH_WALLET_PRIVATE_KEY").expect("ETH_WALLET_PRIVATE_KEY must be set"); 87 | let contract = env::var("CONTRACT_ADDRESS").expect("CONTRACT_ADDRESS must be set"); 88 | 89 | let tx_sender = TxSender::new(chain_id, &rpc_url, ð_wallet_private_key, &contract) 90 | .expect("failed to create tx sender"); 91 | 92 | let claims = ClaimsData::abi_decode(&journal, true) 93 | .context("decoding journal data") 94 | .expect("failed to decode"); 95 | 96 | info!("Claim ID: {:?}", claims.claim_id); 97 | info!("Msg Sender: {:?}", claims.msg_sender); 98 | 99 | let calldata = IBonsaiPay::IBonsaiPayCalls::claim(IBonsaiPay::claimCall { 100 | to: claims.msg_sender, 101 | claim_id: claims.claim_id, 102 | seal: seal.clone(), 103 | }) 104 | .abi_encode(); 105 | 106 | // Send the calldata to Ethereum. 107 | let runtime = tokio::runtime::Runtime::new().expect("failed to start new tokio runtime"); 108 | runtime 109 | .block_on(tx_sender.send(calldata)) 110 | .expect("failed to send tx"); 111 | 112 | tx.send((journal, seal)) 113 | .expect("failed to send over channel"); 114 | } 115 | 116 | fn jwt_authentication_filter() -> impl Filter + Clone { 117 | warp::any() 118 | .and(warp::header::(HEADER_XAUTH)) 119 | .and_then(handle_jwt_authentication) 120 | } 121 | 122 | fn auth_filter() -> impl Filter + Clone { 123 | let cors = warp::cors() 124 | .allow_any_origin() 125 | .allow_methods(vec!["GET", "POST", "DELETE"]) 126 | .allow_headers(vec!["content-type", "x-auth-token"]) 127 | .max_age(3600); 128 | 129 | warp::path("auth") 130 | .and(warp::get()) 131 | .and(warp::path::end()) 132 | .and(jwt_authentication_filter().untuple_one()) 133 | .map(|| warp::reply()) 134 | .with(cors) 135 | } 136 | 137 | #[tokio::main] 138 | async fn main() { 139 | env_logger::init(); 140 | 141 | let api = auth_filter(); 142 | 143 | warp::serve(api).run(([127, 0, 0, 1], 8080)).await; 144 | } 145 | -------------------------------------------------------------------------------- /zk-kyc/contracts/README.md: -------------------------------------------------------------------------------- 1 | # Solidity Contracts 2 | 3 | This directory contains the Solidity contracts for an application with [RISC Zero] on Ethereum. 4 | The example contract included within the template is [`zkKYC.sol`](./zkKYC.sol). 5 | It holds a number, guaranteed to be even. 6 | 7 | The Solidity libraries for RISC Zero can be found at [github.com/risc0/risc0-ethereum]. 8 | 9 | Contracts are built and tested with [forge], which is part of the [Foundry] toolkit. 10 | Tests are defined in the `tests` directory in the root of this template. 11 | 12 | ## Generated Contracts 13 | 14 | As part of the build process, this template generates the `ImageID.sol` and `Elf.sol` contracts. 15 | Running `cargo build` will generate these contracts with up to date references to your guest code. 16 | 17 | - `ImageID.sol`: contains the [Image IDs][image-id] for the guests implemented in the [methods] directory. 18 | - `Elf.sol`: contains the path of the guest binaries implemented in the [methods] directory. 19 | This contract is saved in the `tests` directory in the root of this template. 20 | 21 | [Foundry]: https://getfoundry.sh/ 22 | [RISC Zero]: https://risczero.com 23 | [forge]: https://github.com/foundry-rs/foundry#forge 24 | [github.com/risc0/risc0-ethereum]: https://github.com/risc0/risc0-ethereum/tree/main/contracts 25 | [image-id]: https://dev.risczero.com/terminology#image-id 26 | [methods]: ../methods/README.md 27 | -------------------------------------------------------------------------------- /zk-kyc/contracts/zkKYC.sol: -------------------------------------------------------------------------------- 1 | // Copyright 2024 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | pragma solidity ^0.8.20; 17 | 18 | import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; 19 | import {ImageID} from "./ImageID.sol"; // auto-generated contract after running `cargo build`. 20 | import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 21 | import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 22 | 23 | contract zkKYC is ERC721 { 24 | IRiscZeroVerifier public immutable verifier; 25 | 26 | bytes32 public constant imageId = ImageID.JWT_VALIDATOR_ID; 27 | 28 | event Minted(address indexed to, bytes32 indexed claimId); 29 | event Burned(uint256 indexed tokenId); 30 | 31 | error InvalidMint(string message); 32 | error NotTokenOwner(string message); 33 | error TokenNotTransferable(string message); 34 | 35 | constructor(IRiscZeroVerifier _verifier, string memory name, string memory symbol) ERC721(name, symbol) { 36 | verifier = _verifier; 37 | } 38 | 39 | function mint(address to, bytes32 claimId, bytes calldata seal) public { 40 | if (to == address(0)) revert InvalidMint("mint::Invalid recipient address"); 41 | if (claimId == bytes32(0)) revert InvalidMint("mint::Empty claimId"); 42 | if (balanceOf(to) != 0) revert InvalidMint("mint::Already Minted"); 43 | verifier.verify(seal, imageId, sha256(abi.encode(to, claimId))); 44 | 45 | _mint(to, uint256(claimId)); 46 | 47 | emit Minted(to, claimId); 48 | } 49 | 50 | function burn(uint256 tokenId) public { 51 | if (msg.sender != ownerOf(tokenId)) revert NotTokenOwner("burn::Not Owner"); 52 | _burn(tokenId); 53 | 54 | emit Burned(tokenId); 55 | } 56 | 57 | function transferFrom(address from, address to, uint256 tokenId) public virtual override { 58 | revert TokenNotTransferable("transferFrom::Transfer Disabled"); 59 | } 60 | 61 | function approve(address to, uint256 tokenId) public virtual override { 62 | revert TokenNotTransferable("approve::Transfer Disabled"); 63 | } 64 | 65 | function setApprovalForAll(address operator, bool approved) public virtual override { 66 | revert TokenNotTransferable("setApprovalForAll::Approval Disabled"); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /zk-kyc/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "contracts" 3 | out = "out" 4 | libs = ["lib"] 5 | test = "tests" 6 | ffi = true 7 | fs_permissions = [{ access = "read-write", path = "./"}] 8 | 9 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 10 | -------------------------------------------------------------------------------- /zk-kyc/methods/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "methods" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [package.metadata.risc0] 7 | methods = ["guest"] 8 | 9 | [build-dependencies] 10 | hex = { workspace = true } 11 | risc0-build = { workspace = true } 12 | risc0-build-ethereum = { workspace = true } 13 | risc0-zkp = { workspace = true } 14 | 15 | [dev-dependencies] 16 | alloy-primitives = { workspace = true } 17 | alloy-sol-types = { workspace = true } 18 | risc0-zkvm = { workspace = true, features = ["client"] } 19 | -------------------------------------------------------------------------------- /zk-kyc/methods/README.md: -------------------------------------------------------------------------------- 1 | # zkVM Methods 2 | 3 | This directory contains the [zkVM] portion of your [RISC Zero] application. 4 | This is where you will define one or more [guest programs] to act as a coprocessor to your [on-chain logic]. 5 | 6 | > In typical use cases, the only code in this directory that you will need to edit is inside [guest/src/bin]. 7 | 8 | 9 | ### Writing Guest Code 10 | 11 | To learn to write code for the zkVM, we recommend [Guest Code 101]. 12 | 13 | Examples of what you can do in the guest can be found in the [RISC Zero examples]. 14 | 15 | 16 | ### From Guest Code to Binary File 17 | 18 | Code in the `methods/guest` directory will be compiled into one or more binaries. 19 | 20 | Build configuration for the methods is included in `methods/build.rs`. 21 | 22 | Each will have a corresponding image ID, which is a hash identifying the program. 23 | 24 | 25 | [zkVM]: https://dev.risczero.com/zkvm 26 | [RISC Zero]: https://www.risczero.com/ 27 | [guest programs]: https://dev.risczero.com/terminology#guest-program 28 | [on-chain logic]: ../contracts/ 29 | [guest/src/bin]: ./guest/src/bin/ 30 | [Guest Code 101]: https://dev.risczero.com/zkvm/developer-guide/guest-code-101 31 | [RISC Zero examples]: https://github.com/risc0/tree/v0.18.0/examples -------------------------------------------------------------------------------- /zk-kyc/methods/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::{collections::HashMap, env}; 16 | 17 | use risc0_build::{embed_methods_with_options, DockerOptions, GuestOptions}; 18 | use risc0_build_ethereum::generate_solidity_files; 19 | 20 | // Paths where the generated Solidity files will be written. 21 | const SOLIDITY_IMAGE_ID_PATH: &str = "../contracts/ImageID.sol"; 22 | const SOLIDITY_ELF_PATH: &str = "../tests/Elf.sol"; 23 | 24 | fn main() { 25 | // Builds can be made deterministic, and thereby reproducible, by using Docker to build the 26 | // guest. Check the RISC0_USE_DOCKER variable and use Docker to build the guest if set. 27 | let use_docker = env::var("RISC0_USE_DOCKER").ok().map(|_| DockerOptions { 28 | root_dir: Some("../".into()), 29 | }); 30 | 31 | // Generate Rust source files for the methods crate. 32 | let guests = embed_methods_with_options(HashMap::from([( 33 | "guests", 34 | GuestOptions { 35 | features: Vec::new(), 36 | use_docker, 37 | }, 38 | )])); 39 | 40 | // Generate Solidity source files for use with Forge. 41 | let solidity_opts = risc0_build_ethereum::Options::default() 42 | .with_image_id_sol_path(SOLIDITY_IMAGE_ID_PATH) 43 | .with_elf_sol_path(SOLIDITY_ELF_PATH); 44 | 45 | generate_solidity_files(guests.as_slice(), &solidity_opts).unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /zk-kyc/methods/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "jwt_validator" 8 | path = "src/bin/jwt_validator.rs" 9 | 10 | [workspace] 11 | 12 | [dependencies] 13 | alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } 14 | alloy-sol-types = { version = "0.6" } 15 | risc0-zkvm = { version = "0.21", default-features = false, features = ['std'] } 16 | oidc-validator = { version = "0.1.0", path = "../../oidc-validator" } 17 | 18 | [profile.release] 19 | lto = "thin" 20 | -------------------------------------------------------------------------------- /zk-kyc/methods/guest/README.md: -------------------------------------------------------------------------------- 1 | # Guest Programs 2 | 3 | Each file in the [`src/bin`](./src/bin) folder defines a program for the zkVM. 4 | We refer to the program running in the zkVM as the "[guest]". 5 | 6 | To learn more about writing guest programs, check out the zkVM [developer docs]. 7 | For zkVM API documentation, see the [guest module] of the [`risc0-zkvm`] crate. 8 | 9 | [guest]: https://dev.risczero.com/terminology#guest 10 | [developer docs]: https://dev.risczero.com/zkvm 11 | [guest module]: https://docs.rs/risc0-zkvm/latest/risc0_zkvm/guest/index.html 12 | [`risc0-zkvm`]: https://docs.rs/risc0-zkvm/latest/risc0_zkvm/index.html 13 | -------------------------------------------------------------------------------- /zk-kyc/methods/guest/src/bin/jwt_validator.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use alloy_primitives::{Address, FixedBytes}; 16 | use alloy_sol_types::SolValue; 17 | use oidc_validator::IdentityProvider; 18 | use risc0_zkvm::guest::env; 19 | use risc0_zkvm::sha::rust_crypto::{Digest as _, Sha256}; 20 | use std::io::Read; 21 | 22 | alloy_sol_types::sol! { 23 | struct ClaimsData { 24 | address msg_sender; 25 | bytes32 claim_id; 26 | } 27 | struct Input { 28 | uint256 identity_provider; 29 | string jwt; 30 | } 31 | } 32 | 33 | fn main() { 34 | let mut input_bytes = Vec::::new(); 35 | env::stdin().read_to_end(&mut input_bytes).unwrap(); 36 | 37 | let input: Input = ::abi_decode(&input_bytes, true).unwrap(); 38 | 39 | let identity_provider: IdentityProvider = input.identity_provider.into(); 40 | let jwt: String = input.jwt; 41 | 42 | let (claim_id, msg_sender) = identity_provider.validate(&jwt).unwrap(); 43 | let msg_sender: Address = Address::parse_checksummed(msg_sender, None).unwrap(); 44 | let claim_id: FixedBytes<32> = 45 | FixedBytes::from_slice(Sha256::digest(claim_id.as_bytes()).as_slice()); 46 | let output = ClaimsData { 47 | msg_sender, 48 | claim_id, 49 | }; 50 | let output = output.abi_encode(); 51 | 52 | env::commit_slice(&output); 53 | } 54 | -------------------------------------------------------------------------------- /zk-kyc/methods/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Generated crate containing the image ID and ELF binary of the build guest. 16 | include!(concat!(env!("OUT_DIR"), "/methods.rs")); 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use alloy_primitives::{Address, FixedBytes, U256}; 21 | use alloy_sol_types::SolValue; 22 | use risc0_zkvm::sha::rust_crypto::{Digest as _, Sha256}; 23 | use risc0_zkvm::{default_executor, ExecutorEnv}; 24 | 25 | // Mimic the `ClaimsData` and `Input` structs from the guest code. 26 | alloy_sol_types::sol! { 27 | struct ClaimsData { 28 | address msg_sender; 29 | bytes32 claim_id; 30 | } 31 | struct Input { 32 | uint256 identity_provider; 33 | string jwt; 34 | } 35 | } 36 | 37 | #[test] 38 | fn test_validate_jwt() { 39 | let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ijg3OTJlN2MyYTJiN2MxYWI5MjRlMTU4YTRlYzRjZjUxIn0.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIiwibm9uY2UiOiIweDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifQ.TPUrmStwY2iuqMLXn3WvpiJY1W-bbrU12WGuv0nK9NJ6Q0bT8D_Ags8qj8LPOGGE1CdHn2isBcHgSxaEbNbW8Pz0fVWpFiehj8BwrC47Rld5dwazsxghF84D3q2So5ZBQslWqq1PRGEFKfx4AOgnS375oKi2jAZ3jN_58UNdgtUUdFhuOGHvGbWnr_fEWIbrEcfNFIWahngQ2dbU-sSNZFZ5L3L46bXUkBlbGGNztr6OiAHUwxqH2A02h1EceUol2m6_GTvPfdXKzd0Z34CJNW_loAEheH69hkmkGPbt3ta_XAFWRHgmVN7gFjErRmPiB818YgAFBBIuhZnjvGmC5Q"; 40 | let input_data: Input = Input { 41 | identity_provider: U256::from(1), 42 | jwt: jwt.to_string(), 43 | }; 44 | let env = ExecutorEnv::builder() 45 | .write_slice(&input_data.abi_encode()) 46 | .build() 47 | .unwrap(); 48 | 49 | // NOTE: Use the executor to run tests without proving. 50 | let session_info = default_executor() 51 | .execute(env, super::JWT_VALIDATOR_ELF) 52 | .unwrap(); 53 | 54 | let output = ClaimsData::abi_decode(&session_info.journal.bytes, true).unwrap(); 55 | 56 | let test_claim_id = 57 | FixedBytes::from_slice(Sha256::digest("test@email.com".as_bytes()).as_slice()); 58 | 59 | assert_eq!(output.claim_id, test_claim_id); 60 | assert_eq!( 61 | output.msg_sender, 62 | Address::parse_checksummed("0x0000000000000000000000000000000000000000", None).unwrap() 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /zk-kyc/oidc-validator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oidc-validator" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [dependencies] 7 | jwt-compact = { version = "0.7", default-features = false, features = ["rsa"] } 8 | lazy_static = "1.4.0" 9 | serde = "1.0" 10 | serde_json = "1.0" 11 | thiserror = "1.0" 12 | alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } 13 | alloy-sol-types = { version = "0.6" } 14 | -------------------------------------------------------------------------------- /zk-kyc/oidc-validator/README.md: -------------------------------------------------------------------------------- 1 | # OIDC Validator 2 | 3 | This directory serves as the core library used in the guest and host. The library allows for the validation of Google issued JWTs using the [jwt-compact] crate. 4 | 5 | [jwt-compact]: https://github.com/slowli/jwt-compact 6 | -------------------------------------------------------------------------------- /zk-kyc/remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | openzeppelin/=lib/openzeppelin-contracts/ 3 | risc0/=lib/risc0-ethereum/contracts/src/ 4 | risc0-test/=lib/risc0-ethereum/contracts/src/test/ -------------------------------------------------------------------------------- /zk-kyc/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustfmt", "rust-src"] 4 | profile = "minimal" -------------------------------------------------------------------------------- /zk-kyc/ui/.env.example: -------------------------------------------------------------------------------- 1 | VITE_REDIRECT_URI= 2 | VITE_API_HOST= 3 | ETHERSCAN_APIKEY= 4 | VITE_CUSTODY_ADDRESS= 5 | VITE_IDME_CLIENT_ID= 6 | 7 | -------------------------------------------------------------------------------- /zk-kyc/ui/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | ], 9 | ignorePatterns: ["dist", ".eslintrc.cjs"], 10 | parser: "@typescript-eslint/parser", 11 | plugins: ["react-refresh"], 12 | rules: { 13 | "react-refresh/only-export-components": [ 14 | "warn", 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /zk-kyc/ui/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es2021: true 4 | extends: 5 | - standard-with-typescript 6 | - plugin:react/recommended 7 | parserOptions: 8 | ecmaVersion: latest 9 | sourceType: module 10 | plugins: 11 | - react 12 | rules: {} 13 | -------------------------------------------------------------------------------- /zk-kyc/ui/README.md: -------------------------------------------------------------------------------- 1 | # zk-KYC -------------------------------------------------------------------------------- /zk-kyc/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | zk-KYC 9 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /zk-kyc/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bonsai-pay", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000", 8 | "build": "tsc && vite build --mode production", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "format": "prettier --plugin-search-dir . --write .", 12 | "clean": "rm -rf node_modules && rm -rf dist && rm -rf .vite" 13 | }, 14 | "dependencies": { 15 | "connectkit": "^1.5.3", 16 | "dotenv": "^16.3.1", 17 | "js-cookie": "^3.0.5", 18 | "js-sha256": "^0.10.1", 19 | "jwt-decode": "3.1.2", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "react-toastify": "^9.1.3", 23 | "use-debounce": "^9.0.4", 24 | "viem": "^1.18.4", 25 | "wagmi": "^1.4.5" 26 | }, 27 | "devDependencies": { 28 | "@types/react": "^18.2.15", 29 | "@types/react-dom": "^18.2.7", 30 | "@typescript-eslint/eslint-plugin": "^6.9.1", 31 | "@typescript-eslint/parser": "^6.9.1", 32 | "@vitejs/plugin-react": "^4.0.3", 33 | "@wagmi/cli": "^1.5.2", 34 | "eslint": "^8.53.0", 35 | "eslint-config-standard-with-typescript": "^39.1.1", 36 | "eslint-plugin-import": "^2.25.2", 37 | "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", 38 | "eslint-plugin-promise": "^6.0.0", 39 | "eslint-plugin-react": "^7.33.2", 40 | "eslint-plugin-react-hooks": "^4.6.0", 41 | "eslint-plugin-react-refresh": "^0.4.3", 42 | "typescript": "^5.2.2", 43 | "vite": "^4.4.5", 44 | "vite-plugin-node-polyfills": "^0.16.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /zk-kyc/ui/public/RISC_Zero_Logo_lightmode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/risc0/demos/fccf0dae15b26e962429087050d5986d58b27bf0/zk-kyc/ui/public/RISC_Zero_Logo_lightmode.png -------------------------------------------------------------------------------- /zk-kyc/ui/public/eth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /zk-kyc/ui/public/usdc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /zk-kyc/ui/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /zk-kyc/ui/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { WagmiConfig, createConfig, sepolia } from "wagmi"; 2 | import { 3 | ConnectKitProvider, 4 | ConnectKitButton, 5 | getDefaultConfig, 6 | } from "connectkit"; 7 | import { useEffect } from "react"; 8 | import { ToastContainer } from "react-toastify"; 9 | import Steps from "./components/Steps"; 10 | import "react-toastify/dist/ReactToastify.css"; 11 | import { clearCookies } from "./libs/utils"; 12 | 13 | const { VITE_ALCHEMY_ID, VITE_WALLET_CONNECT_ID } = import.meta.env; 14 | 15 | const config = createConfig( 16 | getDefaultConfig({ 17 | alchemyId: VITE_ALCHEMY_ID, 18 | walletConnectProjectId: VITE_WALLET_CONNECT_ID, 19 | appName: "zk-KYC", 20 | chains: [sepolia], 21 | }) 22 | ); 23 | 24 | function App() { 25 | useEffect(() => { 26 | clearCookies(); 27 | }); 28 | 29 | return ( 30 | 31 | 32 | 33 |
34 |

zk-KYC

35 |

Verify and mint your onchain identity.

36 | 37 | 38 |

This is for demo purposes only.

39 |

40 | Please read our{" "} 41 | 46 | docs 47 | {" "} 48 | for more information. 49 |

50 |
51 |