├── packages ├── ui │ ├── src │ │ ├── routes │ │ │ └── +page.svelte │ │ ├── lib │ │ │ ├── index.ts │ │ │ ├── components │ │ │ │ ├── toast │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── ConfirmToast.svelte │ │ │ │ │ └── toast.ts │ │ │ │ ├── ui │ │ │ │ │ ├── label │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── label.svelte │ │ │ │ │ ├── scroll-area │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── scroll-area-scrollbar.svelte │ │ │ │ │ │ └── scroll-area.svelte │ │ │ │ │ ├── form │ │ │ │ │ │ ├── form-button.svelte │ │ │ │ │ │ ├── form-legend.svelte │ │ │ │ │ │ ├── form-description.svelte │ │ │ │ │ │ ├── form-label.svelte │ │ │ │ │ │ ├── form-field-errors.svelte │ │ │ │ │ │ ├── form-fieldset.svelte │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── form-field.svelte │ │ │ │ │ │ └── form-element-field.svelte │ │ │ │ │ ├── dropdown-menu │ │ │ │ │ │ ├── dropdown-menu-radio-group.svelte │ │ │ │ │ │ ├── dropdown-menu-separator.svelte │ │ │ │ │ │ ├── dropdown-menu-shortcut.svelte │ │ │ │ │ │ ├── dropdown-menu-label.svelte │ │ │ │ │ │ ├── dropdown-menu-content.svelte │ │ │ │ │ │ ├── dropdown-menu-sub-content.svelte │ │ │ │ │ │ ├── dropdown-menu-item.svelte │ │ │ │ │ │ ├── dropdown-menu-sub-trigger.svelte │ │ │ │ │ │ ├── dropdown-menu-radio-item.svelte │ │ │ │ │ │ ├── dropdown-menu-checkbox-item.svelte │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── card │ │ │ │ │ │ ├── card-content.svelte │ │ │ │ │ │ ├── card-footer.svelte │ │ │ │ │ │ ├── card-header.svelte │ │ │ │ │ │ ├── card-description.svelte │ │ │ │ │ │ ├── card.svelte │ │ │ │ │ │ ├── card-title.svelte │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── select │ │ │ │ │ │ ├── select-separator.svelte │ │ │ │ │ │ ├── select-label.svelte │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── select-trigger.svelte │ │ │ │ │ │ ├── select-content.svelte │ │ │ │ │ │ └── select-item.svelte │ │ │ │ │ ├── sheet │ │ │ │ │ │ ├── sheet-portal.svelte │ │ │ │ │ │ ├── sheet-header.svelte │ │ │ │ │ │ ├── sheet-title.svelte │ │ │ │ │ │ ├── sheet-description.svelte │ │ │ │ │ │ ├── sheet-footer.svelte │ │ │ │ │ │ ├── sheet-overlay.svelte │ │ │ │ │ │ ├── sheet-content.svelte │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── badge │ │ │ │ │ │ ├── badge.svelte │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── textarea │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── textarea.svelte │ │ │ │ │ ├── input │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── input.svelte │ │ │ │ │ └── button │ │ │ │ │ │ ├── button.svelte │ │ │ │ │ │ └── index.ts │ │ │ │ ├── form │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── CurrencyInput.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── SubmitButton.svelte │ │ │ │ │ ├── Checkbox.svelte │ │ │ │ │ └── Form.svelte │ │ │ │ ├── GapContainer.svelte │ │ │ │ ├── UserAvatar.svelte │ │ │ │ ├── ui.ts │ │ │ │ ├── LoadingButton.svelte │ │ │ │ ├── CopyButton.svelte │ │ │ │ └── Query.svelte │ │ │ ├── utils.svelte.ts │ │ │ └── utils.ts │ │ ├── app.d.ts │ │ └── app.html │ ├── postcss.config.js │ ├── .prettierignore │ ├── vite.config.ts │ ├── .gitignore │ ├── components.json │ ├── tsconfig.json │ ├── svelte.config.js │ ├── .svelte-kit │ │ └── tsconfig.json │ ├── package.json │ └── tailwind.config.ts ├── utils │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── package.json └── contracts │ ├── noir │ ├── .gitignore │ ├── erc20 │ │ ├── Nargo.toml │ │ └── src │ │ │ └── lib.nr │ ├── erc20_transfer │ │ ├── Prover2.toml │ │ ├── Nargo.toml │ │ └── src │ │ │ └── main.nr │ ├── lob_router │ │ ├── Nargo.toml │ │ └── src │ │ │ └── lib.nr │ ├── Nargo.toml │ ├── erc20_join │ │ ├── Nargo.toml │ │ └── src │ │ │ └── main.nr │ ├── erc20_shield │ │ ├── Nargo.toml │ │ └── src │ │ │ └── main.nr │ ├── erc20_unshield │ │ ├── Nargo.toml │ │ └── src │ │ │ └── main.nr │ ├── lob_router_swap │ │ ├── Nargo.toml │ │ └── src │ │ │ └── main.nr │ ├── rollup │ │ ├── Nargo.toml │ │ └── src │ │ │ └── main.nr │ ├── common │ │ ├── Nargo.toml │ │ └── src │ │ │ ├── note.nr │ │ │ ├── owned_note.nr │ │ │ ├── context.nr │ │ │ ├── erc20_note.nr │ │ │ ├── lib.nr │ │ │ └── uint253.nr │ ├── timer.sh │ └── run.sh │ ├── deployments │ └── .gitignore │ ├── sdk │ ├── mpc │ │ ├── .gitignore │ │ ├── split-inputs.sh │ │ ├── run-party.sh │ │ ├── utils.ts │ │ └── MpcNetworkService.ts │ ├── index.ts │ ├── RemoteTreesService.ts │ ├── backendSdk.ts │ ├── sdk.ts │ ├── EncryptionService.ts │ ├── NativeUltraHonkBackend.ts │ ├── utils.ts │ ├── NonMembershipTree.ts │ └── TreesService.ts │ ├── .gitignore │ ├── .editorconfig │ ├── contracts │ ├── _mock │ │ ├── MockERC20.sol │ │ └── MockSwap.sol │ ├── Fr.sol │ └── Utils.sol │ ├── tsconfig.json │ ├── envConfig.ts │ ├── deployments.json │ ├── deploy │ ├── 01_deploy_mock_tokens.ts │ └── 00_deploy.ts │ ├── hardhat.config.ts │ ├── test │ └── NonMembershipTree.test.ts │ ├── shared │ └── utils.ts │ └── package.json ├── pnpm-workspace.yaml ├── apps └── interface │ ├── postcss.config.js │ ├── tailwind.config.ts │ ├── .prettierrc │ ├── src │ ├── routes │ │ ├── polyfills.ts │ │ ├── api │ │ │ ├── rollup │ │ │ │ └── +server.ts │ │ │ └── trees │ │ │ │ └── +server.ts │ │ ├── +layout.svelte │ │ ├── connect │ │ │ └── +page.svelte │ │ └── Header.svelte │ ├── app.d.ts │ ├── lib │ │ ├── services │ │ │ ├── QueriesService.svelte.ts │ │ │ ├── CurrencyListService.svelte.ts │ │ │ └── EvmAccountService.svelte.ts │ │ ├── server │ │ │ └── index.ts │ │ ├── utils.test.ts │ │ ├── components │ │ │ ├── CurrencySelect.svelte │ │ │ ├── ShieldForm.svelte │ │ │ └── SendForm.svelte │ │ ├── index.ts │ │ ├── reown.ts │ │ ├── utils.ts │ │ └── ROUTES.ts │ ├── app.html │ └── app.css │ ├── .prettierignore │ ├── .gitignore │ ├── tsconfig.json │ ├── vite.config.ts │ ├── svelte.config.js │ └── package.json ├── .prettierrc ├── .gitattributes ├── package.json ├── .gitignore ├── .prettierignore ├── .editorconfig ├── tsconfig.json ├── .github └── workflows │ └── test.yml └── README.md /packages/ui/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/utils/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/contracts/noir/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /packages/contracts/deployments/.gitignore: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /packages/contracts/sdk/mpc/.gitignore: -------------------------------------------------------------------------------- 1 | work-dirs 2 | configs 3 | -------------------------------------------------------------------------------- /packages/contracts/sdk/index.ts: -------------------------------------------------------------------------------- 1 | export * as sdk from "./sdk.js"; 2 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * as utils from "./utils.js"; 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'apps/*' 3 | - 'packages/*' 4 | -------------------------------------------------------------------------------- /packages/ui/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * as Ui from "./components/ui.js"; 2 | -------------------------------------------------------------------------------- /apps/interface/postcss.config.js: -------------------------------------------------------------------------------- 1 | export { default } from "@repo/ui/postcss.config.js"; 2 | -------------------------------------------------------------------------------- /apps/interface/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | export { default } from "@repo/ui/tailwind.config.js"; 2 | -------------------------------------------------------------------------------- /apps/interface/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/toast/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ConfirmToast } from "./ConfirmToast.svelte"; 2 | export * from "./toast"; 3 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-svelte"], 3 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 4 | } 5 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/label/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./label.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Label, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/interface/src/routes/polyfills.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer"; 2 | 3 | // @ts-ignore 4 | globalThis.process ??= { env: {} }; 5 | globalThis.Buffer ??= Buffer; 6 | -------------------------------------------------------------------------------- /packages/ui/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore files for PNPM, NPM and YARN 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | 6 | .svelte-kit 7 | .vercel 8 | ROUTES.ts 9 | -------------------------------------------------------------------------------- /packages/ui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from "@sveltejs/kit/vite"; 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | }); 7 | -------------------------------------------------------------------------------- /apps/interface/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore files for PNPM, NPM and YARN 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | 6 | .svelte-kit 7 | .vercel 8 | ROUTES.ts 9 | 10 | target 11 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc20" 3 | type = "lib" 4 | authors = ["Oleh Misarosh "] 5 | 6 | [dependencies] 7 | common = { path = "../common" } 8 | -------------------------------------------------------------------------------- /apps/interface/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | .svelte-kit/* 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /apps/interface/src/routes/api/rollup/+server.ts: -------------------------------------------------------------------------------- 1 | import { serverLib } from "$lib/server"; 2 | 3 | export async function POST() { 4 | serverLib.rollup.rollup(); 5 | return new Response("", { status: 200 }); 6 | } 7 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20_transfer/Prover2.toml: -------------------------------------------------------------------------------- 1 | to_randomness = "0x0d71211e0e89af974bf816f24ba78f9c99ab50ab9f19367490cedb993d0526fe" 2 | [to] 3 | inner = "0x2b7bd70beb13e310f4593dbc807332acba0f01c4586f17cf984eedd7e1437414" 4 | -------------------------------------------------------------------------------- /packages/ui/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | .svelte-kit/* 5 | !.svelte-kit/tsconfig.json 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | vite.config.js.timestamp-* 11 | vite.config.ts.timestamp-* 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/target/** filter=lfs diff=lfs merge=lfs -text 2 | 3 | **/typechain-types/** linguist-generated=true 4 | **/target/** linguist-generated=true 5 | **/dist/** linguist-generated=true 6 | **/lib/ROUTES.ts linguist-generated=true 7 | -------------------------------------------------------------------------------- /packages/contracts/noir/lob_router/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lob_router" 3 | type = "lib" 4 | authors = ["Oleh Misarosh "] 5 | 6 | [dependencies] 7 | common = { path = "../common" } 8 | erc20 = { path = "../erc20" } 9 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/form/types.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from "svelte-typed-context"; 2 | import type { SuperForm } from "sveltekit-superforms"; 3 | 4 | export const formContextKey: InjectionKey> = Symbol("form"); 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/monorepo", 3 | "type": "module", 4 | "private": true, 5 | "packageManager": "pnpm@9.15.4", 6 | "devDependencies": { 7 | "prettier-plugin-svelte": "^3.2.7", 8 | "typescript": "^5.6.3" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/contracts/noir/Nargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "erc20", 4 | "erc20_shield", 5 | "erc20_unshield", 6 | "erc20_join", 7 | "erc20_transfer", 8 | "lob_router", 9 | "lob_router_swap", 10 | "rollup", 11 | "common", 12 | ] 13 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20_join/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc20_join" 3 | type = "bin" 4 | authors = ["Oleh Misarosh "] 5 | compiler_version = ">=0.39.0" 6 | 7 | [dependencies] 8 | common = { path = "../common" } 9 | erc20 = { path = "../erc20" } 10 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20_unshield/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc20_unshield" 3 | type = "bin" 4 | authors = ["Oleh Misarosh 2 | import * as Button from "#lib/components/ui/button/index.js"; 3 | 4 | type $$Props = Button.Props; 5 | type $$Events = Button.Events; 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/contracts/noir/rollup/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rollup" 3 | type = "bin" 4 | authors = ["Oleh Misarosh 2 | import { cn } from "#lib/utils.js"; 3 | import type { HTMLAttributes } from "svelte/elements"; 4 | let { children, ...props }: HTMLAttributes = $props(); 5 | 6 | 7 |
8 | {@render children?.()} 9 |
10 | -------------------------------------------------------------------------------- /packages/contracts/sdk/mpc/split-inputs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | if [ $# -ne 2 ]; then 6 | echo "Usage: $0 " 7 | exit 1 8 | fi 9 | 10 | PROVER_TOML=$1 11 | CIRCUIT=$2 12 | 13 | WORK_DIR=$(dirname $PROVER_TOML) 14 | 15 | co-noir split-input --circuit $CIRCUIT --input $PROVER_TOML --protocol REP3 --out-dir $WORK_DIR 16 | -------------------------------------------------------------------------------- /packages/contracts/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.sol] 15 | indent_size = 4 16 | 17 | [*.nr] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/form/CurrencyInput.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /apps/interface/src/lib/services/QueriesService.svelte.ts: -------------------------------------------------------------------------------- 1 | import { type QueryClient } from "@tanstack/svelte-query"; 2 | 3 | export class QueriesService { 4 | constructor( 5 | /** 6 | * # For use only in provider 7 | */ 8 | readonly queryClient: QueryClient, 9 | ) {} 10 | 11 | async invalidateAll() { 12 | await this.queryClient.invalidateQueries(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/contracts/noir/common/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | type = "lib" 4 | authors = ["Oleh Misarosh "] 5 | 6 | [dependencies] 7 | protocol_types = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.86.0", directory = "noir-projects/noir-protocol-circuits/crates/types" } 8 | nodash = { tag = "v0.41.2", git = "https://github.com/olehmisar/nodash/" } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.sol] 15 | indent_size = 4 16 | 17 | [*.nr] 18 | indent_size = 4 19 | 20 | [*.rs] 21 | indent_size = 4 22 | -------------------------------------------------------------------------------- /apps/interface/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.json", "./.svelte-kit/tsconfig.json"], 3 | "compilerOptions": {} 4 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 5 | // 6 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 7 | // from the referenced tsconfig.json - TypeScript does not merge them in 8 | } 9 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20_shield/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main( 2 | tree_roots: pub common::TreeRoots, 3 | owner: common::WaAddress, 4 | amount: pub common::TokenAmount, 5 | randomness: Field, 6 | ) -> pub common::Result<1, 0> { 7 | let mut context = common::Context::from(tree_roots); 8 | 9 | erc20::Token::mint(&mut context, owner, amount, randomness); 10 | 11 | context.finish() 12 | } 13 | -------------------------------------------------------------------------------- /packages/contracts/contracts/_mock/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20 { 7 | constructor(string memory name, string memory symbol) ERC20(name, symbol) {} 8 | 9 | function mintForTests(address to, uint256 amount) public { 10 | _mint(to, amount); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/card/card-content.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /apps/interface/src/lib/server/index.ts: -------------------------------------------------------------------------------- 1 | import { lib } from "$lib"; 2 | import { sdk } from "@repo/contracts/sdk"; 3 | import { createBackendSdk } from "@repo/contracts/sdk/backendSdk"; 4 | 5 | const trees = new sdk.TreesService(lib.contract); 6 | const backendSdk = createBackendSdk(lib, trees, { 7 | rollup: import("@repo/contracts/noir/target/rollup.json"), 8 | }); 9 | 10 | export const serverLib = { 11 | ...backendSdk, 12 | trees, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/select/select-separator.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/ui/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | UI 8 | %sveltekit.head% 9 | 10 | 11 |
%sveltekit.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/card/card-footer.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/sheet/sheet-portal.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/interface/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | App 8 | %sveltekit.head% 9 | 10 | 11 |
%sveltekit.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/card/card-header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/card/card-description.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |

12 | 13 |

14 | -------------------------------------------------------------------------------- /packages/contracts/noir/timer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -A TIMERS 4 | 5 | timeStart() { 6 | TIMERS["$1"]=$(date +%s) # Use seconds instead 7 | } 8 | 9 | timeEnd() { 10 | local start=${TIMERS["$1"]} 11 | if [[ -z "$start" ]]; then 12 | echo "Timer '$1' not found" 13 | return 1 14 | fi 15 | local end=$(date +%s) 16 | local duration=$(( end - start )) 17 | echo "$1 took ${duration}s" 18 | unset TIMERS["$1"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "noFallthroughCasesInSwitch": true, 9 | "noImplicitThis": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "sourceMap": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true 14 | }, 15 | "include": ["."] 16 | } 17 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.json", "./.svelte-kit/tsconfig.json"], 3 | "compilerOptions": { 4 | "paths": { 5 | "#lib/*": ["./src/lib/*"] 6 | } 7 | } 8 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 9 | // 10 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 11 | // from the referenced tsconfig.json - TypeScript does not merge them in 12 | } 13 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/form/index.ts: -------------------------------------------------------------------------------- 1 | import { omit } from "lodash-es"; 2 | import * as FormShadcn from "../ui/form"; 3 | import Checkbox from "./Checkbox.svelte"; 4 | import CurrencyInput from "./CurrencyInput.svelte"; 5 | import FormComponent from "./Form.svelte"; 6 | import SubmitButton from "./SubmitButton.svelte"; 7 | 8 | export const Form = Object.assign(FormComponent, { 9 | SubmitButton, 10 | Checkbox, 11 | CurrencyInput, 12 | ...omit(FormShadcn, ["Form"]), 13 | }); 14 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/sheet/sheet-header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
15 | 16 |
17 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/select/select-label.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/sheet/sheet-title.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/card/card.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/sheet/sheet-description.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/sheet/sheet-footer.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /packages/ui/src/lib/utils.svelte.ts: -------------------------------------------------------------------------------- 1 | import { get, type Writable } from "svelte/store"; 2 | 3 | export function toRuneObject(store: Writable): T { 4 | let value = $state(get(store)); 5 | store.subscribe((s) => { 6 | value = s; 7 | }); 8 | return new Proxy(value, { 9 | get(target, prop) { 10 | return Reflect.get(target, prop); 11 | }, 12 | set(target, prop, value) { 13 | Reflect.set(target, prop, value); 14 | store.set(target); 15 | return true; 16 | }, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /apps/interface/src/routes/api/trees/+server.ts: -------------------------------------------------------------------------------- 1 | import { serverLib } from "$lib/server/index.js"; 2 | import { sdk } from "@repo/contracts/sdk/index.js"; 3 | import { z } from "zod"; 4 | 5 | const schema = z.object({ 6 | method: z.enum(sdk.REMOTE_TREES_ALLOWED_METHODS), 7 | args: z.array(z.any()), 8 | }); 9 | export async function POST({ request }) { 10 | const inputs = schema.parse(await request.json()); 11 | const result = await serverLib.trees[inputs.method]( 12 | ...(inputs.args as [any, any, any]), 13 | ); 14 | return Response.json(result); 15 | } 16 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/badge/badge.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/form/form-legend.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/UserAvatar.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20_join/src/main.nr: -------------------------------------------------------------------------------- 1 | // Note: keep in sync with other languages 2 | global MAX_NOTES_TO_JOIN: u32 = 2; 3 | 4 | fn main( 5 | tree_roots: pub common::TreeRoots, 6 | // params 7 | from_secret_key: Field, 8 | notes: [erc20::Erc20NoteConsumptionInputs; MAX_NOTES_TO_JOIN], 9 | to: common::WaAddress, 10 | join_randomness: Field, 11 | ) -> pub common::Result<1, MAX_NOTES_TO_JOIN> { 12 | let mut context = common::Context::from(tree_roots); 13 | 14 | erc20::Token::join(&mut context, from_secret_key, notes, to, join_randomness); 15 | 16 | context.finish() 17 | } 18 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/form/form-description.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/ui/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-vercel"; 2 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 3 | 4 | /** @type {import("@sveltejs/kit").Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 12 | adapter: adapter(), 13 | }, 14 | 15 | vitePlugin: { 16 | inspector: true, 17 | }, 18 | }; 19 | 20 | export default config; 21 | -------------------------------------------------------------------------------- /apps/interface/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from "@sveltejs/kit/vite"; 2 | import { defineConfig } from "vite"; 3 | import { kitRoutes } from "vite-plugin-kit-routes"; 4 | import resolve from "vite-plugin-resolve"; 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | kitRoutes(), 9 | sveltekit(), 10 | resolve({ 11 | util: "export const inspect = {}", 12 | }), 13 | ], 14 | build: { 15 | target: "esnext", 16 | }, 17 | optimizeDeps: { 18 | exclude: ["@noir-lang/noirc_abi", "@noir-lang/acvm_js"], 19 | esbuildOptions: { 20 | target: "esnext", 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/contracts/noir/common/src/note.nr: -------------------------------------------------------------------------------- 1 | use protocol_types::hash::poseidon2_hash_with_separator; 2 | 3 | /// A marker trait to mark structs as notes 4 | pub trait Note: crate::Serialize<_> { 5 | 6 | fn emit(self, context: &mut crate::Context) { 7 | context.push_note_hash(crate::compute_note_hash(self)); 8 | } 9 | } 10 | 11 | pub fn compute_note_hash(note: T) -> Field 12 | where 13 | T: Note, 14 | { 15 | let serialized = note.serialize(); 16 | // TODO(security): add note type to the hash (erc20, erc721, etc.) 17 | poseidon2_hash_with_separator(serialized, crate::GENERATOR_INDEX__NOTE_HASH) 18 | } 19 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/label/label.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /apps/interface/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-node"; 2 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 3 | 4 | /** @type {import("@sveltejs/kit").Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 12 | adapter: adapter(), 13 | appDir: "app", 14 | }, 15 | 16 | vitePlugin: { 17 | inspector: true, 18 | }, 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "moduleResolution": "NodeNext", 5 | "module": "NodeNext", 6 | 7 | // compatibility 8 | "allowJs": true, 9 | "checkJs": true, 10 | "esModuleInterop": true, 11 | "resolveJsonModule": true, 12 | "skipLibCheck": true, 13 | "verbatimModuleSyntax": true, 14 | 15 | // lib 16 | "sourceMap": true, 17 | "emitDeclarationOnly": true, 18 | "declaration": true, 19 | "declarationMap": true, 20 | 21 | // strictness 22 | "strict": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "noUncheckedIndexedAccess": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/contracts/contracts/_mock/MockSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.23; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | contract MockSwap { 8 | using SafeERC20 for IERC20; 9 | 10 | function swap( 11 | IERC20 tokenIn, 12 | IERC20 tokenOut, 13 | uint256 amountIn, 14 | uint256 amountOut 15 | ) external { 16 | tokenOut.safeTransferFrom(msg.sender, address(this), amountIn); 17 | tokenIn.safeTransfer(msg.sender, amountOut); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/card/card-title.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/form/form-label.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/card/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./card.svelte"; 2 | import Content from "./card-content.svelte"; 3 | import Description from "./card-description.svelte"; 4 | import Footer from "./card-footer.svelte"; 5 | import Header from "./card-header.svelte"; 6 | import Title from "./card-title.svelte"; 7 | 8 | export { 9 | Root, 10 | Content, 11 | Description, 12 | Footer, 13 | Header, 14 | Title, 15 | // 16 | Root as Card, 17 | Content as CardContent, 18 | Description as CardDescription, 19 | Footer as CardFooter, 20 | Header as CardHeader, 21 | Title as CardTitle, 22 | }; 23 | 24 | export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; 25 | -------------------------------------------------------------------------------- /packages/contracts/envConfig.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { z } from "zod"; 3 | import { zPrivateKey } from "./shared/utils"; 4 | 5 | /** 6 | * Parse env variables in a typesafe way. 7 | */ 8 | const EnvConfigSchema = z.object({ 9 | DEPLOYER_PRIVATE_KEY: zPrivateKey().default( 10 | // random key 11 | "0x845074c29c6487a92fa902e06" + "f1a1fe1ac0eb801fd6624b0a486e3c04a75721f", 12 | ), 13 | }); 14 | 15 | let envConfig: z.infer; 16 | try { 17 | envConfig = EnvConfigSchema.parse(process.env); 18 | } catch (e: any) { 19 | throw new Error( 20 | `Error parsing .env file: ${e.errors[0].path} ${e.errors[0].message}`, 21 | ); 22 | } 23 | export default envConfig; 24 | -------------------------------------------------------------------------------- /packages/contracts/deployments.json: -------------------------------------------------------------------------------- 1 | { 2 | "31337": { 3 | "chainId": "31337", 4 | "contracts": { 5 | "ExecuteVerifier": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", 6 | "MockBTC": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", 7 | "MockUSDC": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", 8 | "PoolERC20": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", 9 | "RollupVerifier": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", 10 | "RouterERC20": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", 11 | "ShieldVerifier": "0x5FbDB2315678afecb367f032d93F642f64180aa3", 12 | "TransferVerifier": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" 13 | }, 14 | "name": "localhost" 15 | } 16 | } -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [20] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: pnpm/action-setup@v4 15 | 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | cache: "pnpm" 21 | 22 | - name: Install dependencies 23 | run: pnpm install --frozen-lockfile 24 | 25 | - name: Run tests 26 | # TODO(security): test all packages & apps (js, noir & rust) 27 | run: cd packages/contracts && pnpm test 28 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/sheet/sheet-overlay.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20_transfer/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main( 2 | tree_roots: pub common::TreeRoots, 3 | // from 4 | from_secret_key: Field, 5 | from_note_inputs: erc20::Erc20NoteConsumptionInputs, 6 | // transfer params 7 | to: common::WaAddress, 8 | amount: common::TokenAmount, 9 | to_randomness: Field, 10 | // change 11 | change_randomness: Field, 12 | ) -> pub common::Result<2, 1> { 13 | let mut context = common::Context::from(tree_roots); 14 | 15 | erc20::Token::transfer( 16 | &mut context, 17 | from_secret_key, 18 | from_note_inputs, 19 | to, 20 | amount, 21 | to_randomness, 22 | change_randomness, 23 | ); 24 | 25 | context.finish() 26 | } 27 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/form/SubmitButton.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui.ts: -------------------------------------------------------------------------------- 1 | export { cn } from "../utils.js"; 2 | export { default as CopyButton } from "./CopyButton.svelte"; 3 | export * from "./form"; 4 | export { default as GapContainer } from "./GapContainer.svelte"; 5 | export { default as LoadingButton } from "./LoadingButton.svelte"; 6 | export { default as Query } from "./Query.svelte"; 7 | export * from "./toast"; 8 | export { Badge } from "./ui/badge"; 9 | export { Button } from "./ui/button"; 10 | export * as Card from "./ui/card"; 11 | export { Input } from "./ui/input"; 12 | export * as ScrollArea from "./ui/scroll-area"; 13 | export * as Select from "./ui/select"; 14 | export * as Sheet from "./ui/sheet"; 15 | export { Textarea } from "./ui/textarea"; 16 | export { default as UserAvatar } from "./UserAvatar.svelte"; 17 | -------------------------------------------------------------------------------- /apps/interface/src/lib/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | import { fromNoirU256, toNoirU256 } from "./utils"; 3 | 4 | describe("U256", () => { 5 | test("to and from", () => { 6 | const values = [ 7 | // random values 8 | 0n, 9 | 1n, 10 | 2n, 11 | 3n, 12 | 1234n, 13 | 4958341723432n, 14 | 2n ** 128n - 1234n, 15 | // edge cases 16 | 2n ** 120n - 1n, 17 | 2n ** 120n, 18 | 2n ** 120n + 1n, 19 | 2n * 240n - 1n, 20 | 2n * 240n, 21 | 2n * 240n + 1n, 22 | 2n * 256n - 1n, 23 | 2n * 256n, 24 | ]; 25 | 26 | for (const value of values) { 27 | const limbs = toNoirU256(value); 28 | const recovered = fromNoirU256(limbs); 29 | expect(recovered).toEqual(value); 30 | } 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/form/Checkbox.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/LoadingButton.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 35 | -------------------------------------------------------------------------------- /packages/contracts/deploy/01_deploy_mock_tokens.ts: -------------------------------------------------------------------------------- 1 | import { DeployFunction } from "hardhat-deploy/types"; 2 | 3 | declare module "hardhat/types/runtime" { 4 | interface TypedHardhatDeployNames { 5 | MockUSDC: "MockERC20"; 6 | MockBTC: "MockERC20"; 7 | } 8 | } 9 | 10 | const deploy: DeployFunction = async ({ 11 | typedDeployments, 12 | safeGetNamedAccounts, 13 | }) => { 14 | const { deployer } = await safeGetNamedAccounts({ deployer: true }); 15 | 16 | await typedDeployments.deploy("MockUSDC", { 17 | from: deployer, 18 | log: true, 19 | args: ["USD Coin", "USDC"], 20 | contract: "MockERC20", 21 | }); 22 | 23 | await typedDeployments.deploy("MockBTC", { 24 | from: deployer, 25 | log: true, 26 | args: ["Bitcoin", "BTC"], 27 | contract: "MockERC20", 28 | }); 29 | }; 30 | 31 | export default deploy; 32 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/form/form-field-errors.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | 22 | {#each errors as error} 23 |
{error}
24 | {/each} 25 |
26 |
27 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20_unshield/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main( 2 | tree_roots: pub common::TreeRoots, 3 | // from 4 | from_secret_key: Field, 5 | from_note_inputs: erc20::Erc20NoteConsumptionInputs, 6 | // to params 7 | // TODO(security): check if specifying a different to in the EVM contract will bypass the proof 8 | to: pub common::EthAddress, 9 | amount: pub common::TokenAmount, 10 | // change 11 | change_randomness: Field, 12 | ) -> pub common::Result<1, 1> { 13 | let mut context = common::Context::from(tree_roots); 14 | 15 | // disabled because nullifiers are no longer checked on tx level 16 | panic(f"not implemented"); 17 | 18 | erc20::Token::burn( 19 | &mut context, 20 | from_secret_key, 21 | from_note_inputs, 22 | amount, 23 | change_randomness, 24 | ); 25 | 26 | context.finish() 27 | } 28 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/CopyButton.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 35 | -------------------------------------------------------------------------------- /packages/contracts/noir/lob_router_swap/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main( 2 | tree_roots: pub common::TreeRoots, 3 | // seller 4 | seller_secret_key: Field, 5 | seller_note: erc20::Erc20NoteConsumptionInputs, 6 | seller_order: lob_router::Order, 7 | seller_randomness: Field, 8 | // buyer 9 | buyer_secret_key: Field, 10 | buyer_note: erc20::Erc20NoteConsumptionInputs, 11 | buyer_order: lob_router::Order, 12 | buyer_randomness: Field, 13 | ) -> pub common::Result<4, 2> { 14 | let mut context = common::Context::from(tree_roots); 15 | 16 | lob_router::LobRouter::swap( 17 | &mut context, 18 | seller_secret_key, 19 | seller_note, 20 | seller_order, 21 | seller_randomness, 22 | buyer_secret_key, 23 | buyer_note, 24 | buyer_order, 25 | buyer_randomness, 26 | ); 27 | 28 | context.finish() 29 | } 30 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/select/index.ts: -------------------------------------------------------------------------------- 1 | import { Select as SelectPrimitive } from "bits-ui"; 2 | 3 | import Label from "./select-label.svelte"; 4 | import Item from "./select-item.svelte"; 5 | import Content from "./select-content.svelte"; 6 | import Trigger from "./select-trigger.svelte"; 7 | import Separator from "./select-separator.svelte"; 8 | 9 | const Root = SelectPrimitive.Root; 10 | const Group = SelectPrimitive.Group; 11 | const Input = SelectPrimitive.Input; 12 | const Value = SelectPrimitive.Value; 13 | 14 | export { 15 | Root, 16 | Group, 17 | Input, 18 | Label, 19 | Item, 20 | Value, 21 | Content, 22 | Trigger, 23 | Separator, 24 | // 25 | Root as Select, 26 | Group as SelectGroup, 27 | Input as SelectInput, 28 | Label as SelectLabel, 29 | Item as SelectItem, 30 | Value as SelectValue, 31 | Content as SelectContent, 32 | Trigger as SelectTrigger, 33 | Separator as SelectSeparator, 34 | }; 35 | -------------------------------------------------------------------------------- /apps/interface/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 |
26 |
27 |
28 | 29 | {@render children()} 30 |
31 |
32 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/textarea/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./textarea.svelte"; 2 | 3 | type FormTextareaEvent = T & { 4 | currentTarget: EventTarget & HTMLTextAreaElement; 5 | }; 6 | 7 | type TextareaEvents = { 8 | blur: FormTextareaEvent; 9 | change: FormTextareaEvent; 10 | click: FormTextareaEvent; 11 | focus: FormTextareaEvent; 12 | keydown: FormTextareaEvent; 13 | keypress: FormTextareaEvent; 14 | keyup: FormTextareaEvent; 15 | mouseover: FormTextareaEvent; 16 | mouseenter: FormTextareaEvent; 17 | mouseleave: FormTextareaEvent; 18 | paste: FormTextareaEvent; 19 | input: FormTextareaEvent; 20 | }; 21 | 22 | export { 23 | Root, 24 | // 25 | Root as Textarea, 26 | type TextareaEvents, 27 | type FormTextareaEvent, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/contracts/contracts/Fr.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.23; 3 | 4 | // TODO(security): use Fr from generated solidity verifiers 5 | type Fr is bytes32; 6 | 7 | library FrLib { 8 | function tryFrom(bytes32 value) internal pure returns (Fr ret) { 9 | require( 10 | value < 11 | 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001, 12 | "field value out of range" 13 | ); 14 | 15 | assembly { 16 | ret := value 17 | } 18 | } 19 | 20 | function toBytes32(Fr self) internal pure returns (bytes32 ret) { 21 | assembly { 22 | ret := self 23 | } 24 | } 25 | } 26 | 27 | function keccak256ToFr(bytes memory data) pure returns (Fr) { 28 | return 29 | FrLib.tryFrom( 30 | bytes32(bytes.concat(bytes1(0), bytes31(keccak256(data)))) 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/toast/ConfirmToast.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 |

{confirmText}

16 | 17 |
18 | { 20 | toast_.dismiss(toast.id); 21 | onresult(false); 22 | }} 23 | > 24 | {no} 25 | 26 | 27 | { 30 | toast_.dismiss(toast.id); 31 | onresult(true); 32 | }} 33 | > 34 | {yes} 35 | 36 |
37 |
38 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/form/form-fieldset.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /packages/contracts/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox"; 2 | import "hardhat-deploy"; 3 | import "hardhat-noir"; 4 | import { HardhatUserConfig } from "hardhat/config"; 5 | import envConfig from "./envConfig"; 6 | import "./shared/typed-hardhat-deploy"; 7 | 8 | const config: HardhatUserConfig = { 9 | solidity: { 10 | version: "0.8.28", 11 | settings: { 12 | optimizer: { 13 | enabled: true, 14 | runs: 100000000, 15 | }, 16 | }, 17 | }, 18 | noir: { 19 | version: "1.0.0-beta.5", 20 | }, 21 | networks: {}, 22 | etherscan: { 23 | apiKey: { 24 | sepolia: "BSFWY85F56JH998I6GBM1R4YZJTM6G5WGA", 25 | }, 26 | }, 27 | namedAccounts: { 28 | deployer: { 29 | hardhat: 0, 30 | localhost: 0, 31 | baseSepolia: `privatekey://${envConfig.DEPLOYER_PRIVATE_KEY}`, 32 | }, 33 | }, 34 | mocha: { 35 | timeout: 999999999, 36 | }, 37 | }; 38 | 39 | export default config; 40 | -------------------------------------------------------------------------------- /packages/contracts/sdk/RemoteTreesService.ts: -------------------------------------------------------------------------------- 1 | import ky from "ky"; 2 | import type { ElementOf } from "ts-essentials"; 3 | import type { TreesService } from "./TreesService"; 4 | 5 | export const REMOTE_TREES_ALLOWED_METHODS = [ 6 | "getTreeRoots", 7 | "getNoteConsumptionInputs", 8 | "noteExistsAndNotNullified", 9 | ] satisfies (keyof TreesService)[]; 10 | export type ITreesService = Pick< 11 | TreesService, 12 | ElementOf 13 | >; 14 | 15 | export interface RemoteTreesService extends ITreesService {} 16 | export class RemoteTreesService { 17 | constructor(private url: string) { 18 | for (const method of REMOTE_TREES_ALLOWED_METHODS) { 19 | (this as any)[method] = async (...args: any[]) => { 20 | return await ky 21 | .post(this.url, { 22 | json: { 23 | method, 24 | args, 25 | }, 26 | }) 27 | .json(); 28 | }; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/badge/index.ts: -------------------------------------------------------------------------------- 1 | import { type VariantProps, tv } from "tailwind-variants"; 2 | export { default as Badge } from "./badge.svelte"; 3 | 4 | export const badgeVariants = tv({ 5 | base: "focus:ring-ring inline-flex select-none items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2", 6 | variants: { 7 | variant: { 8 | default: 9 | "bg-primary text-primary-foreground hover:bg-primary/80 border-transparent", 10 | secondary: 11 | "bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent", 12 | destructive: 13 | "bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent", 14 | outline: "text-foreground", 15 | }, 16 | }, 17 | defaultVariants: { 18 | variant: "default", 19 | }, 20 | }); 21 | 22 | export type Variant = VariantProps["variant"]; 23 | -------------------------------------------------------------------------------- /apps/interface/src/lib/components/CurrencySelect.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./input.svelte"; 2 | 3 | export type FormInputEvent = T & { 4 | currentTarget: EventTarget & HTMLInputElement; 5 | }; 6 | export type InputEvents = { 7 | blur: FormInputEvent; 8 | change: FormInputEvent; 9 | click: FormInputEvent; 10 | focus: FormInputEvent; 11 | focusin: FormInputEvent; 12 | focusout: FormInputEvent; 13 | keydown: FormInputEvent; 14 | keypress: FormInputEvent; 15 | keyup: FormInputEvent; 16 | mouseover: FormInputEvent; 17 | mouseenter: FormInputEvent; 18 | mouseleave: FormInputEvent; 19 | mousemove: FormInputEvent; 20 | paste: FormInputEvent; 21 | input: FormInputEvent; 22 | wheel: FormInputEvent; 23 | }; 24 | 25 | export { 26 | Root, 27 | // 28 | Root as Input, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/form/index.ts: -------------------------------------------------------------------------------- 1 | import * as FormPrimitive from "formsnap"; 2 | import Button from "./form-button.svelte"; 3 | import Description from "./form-description.svelte"; 4 | import ElementField from "./form-element-field.svelte"; 5 | import FieldErrors from "./form-field-errors.svelte"; 6 | import Field from "./form-field.svelte"; 7 | import Fieldset from "./form-fieldset.svelte"; 8 | import Label from "./form-label.svelte"; 9 | import Legend from "./form-legend.svelte"; 10 | 11 | const Control = FormPrimitive.Control; 12 | 13 | export { 14 | Button, 15 | Control, 16 | Description, 17 | ElementField, 18 | Field, 19 | FieldErrors, 20 | Fieldset, 21 | Button as FormButton, 22 | Control as FormControl, 23 | Description as FormDescription, 24 | ElementField as FormElementField, 25 | // 26 | Field as FormField, 27 | FieldErrors as FormFieldErrors, 28 | Fieldset as FormFieldset, 29 | Label as FormLabel, 30 | Legend as FormLegend, 31 | Label, 32 | Legend, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/form/form-field.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 32 |
33 | 34 |
35 |
36 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/form/form-element-field.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | 25 | 33 |
34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /packages/ui/.svelte-kit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "$lib": [ 5 | "../src/lib" 6 | ], 7 | "$lib/*": [ 8 | "../src/lib/*" 9 | ] 10 | }, 11 | "rootDirs": [ 12 | "..", 13 | "./types" 14 | ], 15 | "verbatimModuleSyntax": true, 16 | "isolatedModules": true, 17 | "lib": [ 18 | "esnext", 19 | "DOM", 20 | "DOM.Iterable" 21 | ], 22 | "moduleResolution": "bundler", 23 | "module": "esnext", 24 | "noEmit": true, 25 | "target": "esnext" 26 | }, 27 | "include": [ 28 | "ambient.d.ts", 29 | "non-ambient.d.ts", 30 | "./types/**/$types.d.ts", 31 | "../vite.config.js", 32 | "../vite.config.ts", 33 | "../src/**/*.js", 34 | "../src/**/*.ts", 35 | "../src/**/*.svelte", 36 | "../tests/**/*.js", 37 | "../tests/**/*.ts", 38 | "../tests/**/*.svelte" 39 | ], 40 | "exclude": [ 41 | "../node_modules/**", 42 | "../src/service-worker.js", 43 | "../src/service-worker/**/*.js", 44 | "../src/service-worker.ts", 45 | "../src/service-worker/**/*.ts", 46 | "../src/service-worker.d.ts", 47 | "../src/service-worker/**/*.d.ts" 48 | ] 49 | } -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/select/select-trigger.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | span]:text-muted-foreground flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", 16 | className 17 | )} 18 | {...$$restProps} 19 | let:builder 20 | on:click 21 | on:keydown 22 | > 23 | 24 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/button/button.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 29 | {#if loading} 30 | 31 | {/if} 32 | 33 | 34 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/utils", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "private": true, 6 | "main": "src/index.ts", 7 | "module": "src/index.ts", 8 | "publishConfig": { 9 | "types": "dist/index.d.ts", 10 | "access": "public" 11 | }, 12 | "files": [ 13 | "dist", 14 | "src" 15 | ], 16 | "scripts": { 17 | "dev": "pnpm _chore && tsc -w", 18 | "build": "pnpm _chore && rm -rf dist && tsc", 19 | "test": "pnpm test:lint && pnpm test:unit", 20 | "test:unit": "pnpm _chore && vitest run", 21 | "test:lint": "pnpm _chore && tsc --noEmit --emitDeclarationOnly false && prettier --check .", 22 | "test:lint:fix": "pnpm _chore && prettier --write . && eslint . --fix", 23 | "prepublishOnly": "pnpm test:lint && pnpm build", 24 | "_chore": "pnpm i" 25 | }, 26 | "dependencies": { 27 | "@uniswap/sdk-core": "^6.0.0", 28 | "ethers": "^6.13.4", 29 | "ms": "^2.1.3", 30 | "ts-essentials": "^9.4.1", 31 | "ufo": "^1.5.4", 32 | "zod": "^3.23.8" 33 | }, 34 | "devDependencies": { 35 | "@types/ms": "^0.7.34", 36 | "prettier": "^3.2.5", 37 | "typescript": "^5.5.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /apps/interface/src/routes/connect/+page.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | Connect an app 19 | 20 | 21 | 22 | {#snippet children(form, formData)} 23 | 24 | 25 | 26 | App URI 27 | 28 | 29 | 30 | 31 | 32 | Connect 33 | 34 | 35 | {/snippet} 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /packages/contracts/test/NonMembershipTree.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fr } from "@aztec/aztec.js"; 2 | import chai, { expect } from "chai"; 3 | import chaiAsPromised from "chai-as-promised"; 4 | import type { sdk } from "../sdk"; 5 | const { tsImport } = require("tsx/esm/api"); // TODO: remove when hardhat supports ESM 6 | chai.use(chaiAsPromised); 7 | 8 | describe("NonMembershipTree", () => { 9 | const depth = 32; 10 | let leaves: Fr[]; 11 | let tree: sdk.NonMembershipTree; 12 | beforeEach(async () => { 13 | const { Fr } = await eval(`import("@aztec/aztec.js")`); 14 | const { sdk } = (await tsImport( 15 | "../sdk", 16 | __filename, 17 | )) as typeof import("../sdk"); 18 | leaves = [1, 3, 4, 8].map((x) => new Fr(BigInt(x))); 19 | tree = await sdk.NonMembershipTree.new(leaves, depth); 20 | }); 21 | 22 | it("proves non-membership", async () => { 23 | const { Fr } = await eval(`import("@aztec/aztec.js")`); 24 | await tree.getNonMembershipWitness(new Fr(2)); 25 | }); 26 | 27 | it("fails for members", async () => { 28 | for (const leaf of leaves) { 29 | await expect(tree.getNonMembershipWitness(leaf)).rejectedWith( 30 | `key already present: "${leaf}"`, 31 | ); 32 | } 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/select/select-content.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 36 |
37 | 38 |
39 |
40 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/scroll-area/scroll-area.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {#if orientation === "vertical" || orientation === "both"} 26 | 27 | {/if} 28 | {#if orientation === "horizontal" || orientation === "both"} 29 | 30 | {/if} 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/textarea/textarea.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 39 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/Query.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | {#if query.status === "pending"} 35 | {#if !hideArray.includes("pending")} 36 | {#if pending} 37 | {@render pending()} 38 | {:else} 39 | Loading... 40 | {/if} 41 | {/if} 42 | {:else if query.status === "error"} 43 | {#if !hideArray.includes("error")} 44 | {#if error} 45 | {@render error(query.error)} 46 | {:else} 47 | Error: {String(query.error)} 48 | {/if} 49 | {/if} 50 | {:else if query.status === "success"} 51 | {@render success(query.data)} 52 | {/if} 53 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/contracts/sdk/backendSdk.ts: -------------------------------------------------------------------------------- 1 | import { UltraHonkBackend } from "@aztec/bb.js"; 2 | import type { CompiledCircuit } from "@noir-lang/noir_js"; 3 | import { utils } from "@repo/utils"; 4 | import os from "node:os"; 5 | import type { AsyncOrSync } from "ts-essentials"; 6 | import { NativeUltraHonkBackend } from "./NativeUltraHonkBackend"; 7 | import { RollupService } from "./RollupService"; 8 | import { createCoreSdk } from "./sdk"; 9 | import type { TreesService } from "./TreesService"; 10 | 11 | export function createBackendSdk( 12 | coreSdk: ReturnType, 13 | trees: TreesService, 14 | compiledCircuits: Record<"rollup", AsyncOrSync>, 15 | ) { 16 | const rollup = new RollupService(coreSdk.contract, trees, { 17 | rollup: utils.iife(async () => { 18 | const { Noir } = await import("@noir-lang/noir_js"); 19 | const circuit = await compiledCircuits.rollup; 20 | const noir = new Noir(circuit); 21 | const backend = process.env.CI 22 | ? new UltraHonkBackend(circuit.bytecode, { threads: os.cpus().length }) 23 | : (new NativeUltraHonkBackend( 24 | `${process.env.HOME}/.bb/bb`, 25 | circuit, 26 | ) as unknown as UltraHonkBackend); 27 | return { circuit, noir, backend }; 28 | }), 29 | }); 30 | return { 31 | rollup, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/select/select-item.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {label || value} 39 | 40 | 41 | -------------------------------------------------------------------------------- /packages/contracts/noir/common/src/owned_note.nr: -------------------------------------------------------------------------------- 1 | use protocol_types::{hash::poseidon2_hash_with_separator, merkle_tree}; 2 | 3 | pub trait OwnedNote: crate::Note { 4 | fn owner(self) -> crate::WaAddress; 5 | } 6 | 7 | fn compute_nullifier_of_owned_note(note: T, secret_key: Field) -> Field 8 | where 9 | T: OwnedNote, 10 | { 11 | // TODO(perf): pass note hash as an argument to avoid hashing twice? 12 | assert_eq(note.owner(), crate::WaAddress::from_secret_key(secret_key), "invalid secret key"); 13 | poseidon2_hash_with_separator( 14 | [crate::compute_note_hash(note), secret_key], 15 | crate::GENERATOR_INDEX__NOTE_NULLIFIER, 16 | ) 17 | } 18 | 19 | pub struct NoteConsumptionInputs { 20 | pub note: T, 21 | pub note_index: Field, 22 | pub note_sibling_path: [Field; crate::NOTE_HASH_TREE_HEIGHT], 23 | } 24 | 25 | impl NoteConsumptionInputs 26 | where 27 | T: OwnedNote, 28 | { 29 | pub fn consume(self, context: &mut crate::Context, secret_key: Field) { 30 | merkle_tree::assert_check_membership( 31 | crate::compute_note_hash(self.note), 32 | self.note_index, 33 | self.note_sibling_path, 34 | context.tree_roots().note_hash_root, 35 | ); 36 | let nullifier = compute_nullifier_of_owned_note(self.note, secret_key); 37 | context.push_nullifier(nullifier); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/input/input.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 43 | -------------------------------------------------------------------------------- /packages/contracts/noir/common/src/context.nr: -------------------------------------------------------------------------------- 1 | pub struct Context { 2 | tree_roots: crate::TreeRoots, 3 | note_hashes: [Field], 4 | nullifiers: [Field], 5 | } 6 | 7 | impl std::convert::From for Context { 8 | fn from(tree_roots: crate::TreeRoots) -> Self { 9 | Self { tree_roots, note_hashes: [], nullifiers: [] } 10 | } 11 | } 12 | 13 | impl Context { 14 | pub fn push_note_hash(&mut self, note_hash: Field) { 15 | self.note_hashes = self.note_hashes.push_back(note_hash); 16 | } 17 | 18 | pub fn push_nullifier(&mut self, nullifier: Field) { 19 | self.nullifiers = self.nullifiers.push_back(nullifier); 20 | } 21 | 22 | pub fn tree_roots(self) -> crate::TreeRoots { 23 | self.tree_roots 24 | } 25 | 26 | pub fn finish(self) -> Result { 27 | Result { 28 | note_hashes: slice_to_exact_array(self.note_hashes), 29 | nullifiers: slice_to_exact_array(self.nullifiers), 30 | } 31 | } 32 | } 33 | 34 | pub struct Result { 35 | pub note_hashes: [Field; NH_LEN], 36 | pub nullifiers: [Field; N_LEN], 37 | } 38 | 39 | fn slice_to_exact_array(x: [T]) -> [T; N] { 40 | if x.len() != N { 41 | let l = x.len(); 42 | panic( 43 | f"failed to convert slice of length {l} to exact array of length {N}", 44 | ); 45 | } 46 | x.as_array() 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Mezcal 4 | 5 | Mezcal (Nahuatl: mexcalli - agave booze) - on-chain dark pool implementation using [Noir](https://noir-lang.org) and [Taceo coNoir](https://taceo.io). Hides EVERYTHING about orders and traders(tokens, amounts and addresses of traders are completely hidden). Trades settled on an EVM chain using a very simplified version of [Aztec Protocol](https://aztec.network). The tradeoff is O(N^2) order matching engine. 6 | 7 | The code is highly experimental. The core code is located in `packages/contracts`. 8 | 9 | ## Install coSnarks 10 | 11 | ```sh 12 | cargo install --git https://github.com/TaceoLabs/co-snarks co-noir --rev 1b2db005ee550c028af824b3ec4e811d6e8a3705 13 | ``` 14 | 15 | ## TODO 16 | 17 | ### contracts and circuits 18 | 19 | - [x] split contract into a generic rollup and ERC20 specific 20 | - [x] extract PoolGeneric storage into a struct 21 | - [x] join Erc20Note 22 | - [ ] split Erc20Note 23 | - [ ] negative tests 24 | - [x] use bignumber for amounts 25 | - [ ] support ETH 26 | - [ ] fees 27 | - [ ] prove against a historical note hash tree root 28 | - [x] PublicInputsBuilder 29 | - [ ] deploy as proxy 30 | - [ ] test contracts with larger token amounts 31 | - [ ] TODO(security): parse inputs to circuits instead of assuming they are correct. Same applies to types returned from `unconstrained` functions. 32 | -------------------------------------------------------------------------------- /packages/contracts/noir/common/src/erc20_note.nr: -------------------------------------------------------------------------------- 1 | // fails to compile if this file is moved to erc20 crate 2 | 3 | use super::note::Note; 4 | use protocol_types::traits::Serialize; 5 | 6 | pub struct Erc20Note { 7 | pub owner: crate::WaAddress, 8 | pub amount: crate::TokenAmount, 9 | pub randomness: Field, 10 | } 11 | 12 | impl crate::Note for Erc20Note {} 13 | 14 | impl Erc20Note { 15 | pub fn sub_and_emit_change( 16 | context: &mut crate::Context, 17 | notes: [Erc20NoteConsumptionInputs; N], 18 | amount: crate::TokenAmount, 19 | change_randomness: Field, 20 | secret_key: Field, 21 | ) { 22 | let mut subtracted = crate::TokenAmount::zero(amount.token); 23 | for note in notes { 24 | note.consume(context, secret_key); 25 | subtracted += note.note.amount; 26 | } 27 | Self { 28 | owner: crate::WaAddress::from_secret_key(secret_key), 29 | amount: subtracted - amount, 30 | randomness: change_randomness, 31 | } 32 | .emit(context); 33 | } 34 | } 35 | 36 | impl crate::Serialize<4> for Erc20Note { 37 | fn serialize(self) -> [Field; 4] { 38 | self 39 | .owner 40 | .serialize() 41 | .concat(self.amount.token.serialize()) 42 | .concat([self.amount.amount.to_integer()]) 43 | .concat([self.randomness]) 44 | } 45 | } 46 | 47 | impl crate::OwnedNote for Erc20Note { 48 | fn owner(self) -> crate::WaAddress { 49 | self.owner 50 | } 51 | } 52 | 53 | pub type Erc20NoteConsumptionInputs = crate::NoteConsumptionInputs; 54 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/dropdown-menu/index.ts: -------------------------------------------------------------------------------- 1 | import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; 2 | import Item from "./dropdown-menu-item.svelte"; 3 | import Label from "./dropdown-menu-label.svelte"; 4 | import Content from "./dropdown-menu-content.svelte"; 5 | import Shortcut from "./dropdown-menu-shortcut.svelte"; 6 | import RadioItem from "./dropdown-menu-radio-item.svelte"; 7 | import Separator from "./dropdown-menu-separator.svelte"; 8 | import RadioGroup from "./dropdown-menu-radio-group.svelte"; 9 | import SubContent from "./dropdown-menu-sub-content.svelte"; 10 | import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; 11 | import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; 12 | 13 | const Sub = DropdownMenuPrimitive.Sub; 14 | const Root = DropdownMenuPrimitive.Root; 15 | const Trigger = DropdownMenuPrimitive.Trigger; 16 | const Group = DropdownMenuPrimitive.Group; 17 | 18 | export { 19 | Sub, 20 | Root, 21 | Item, 22 | Label, 23 | Group, 24 | Trigger, 25 | Content, 26 | Shortcut, 27 | Separator, 28 | RadioItem, 29 | SubContent, 30 | SubTrigger, 31 | RadioGroup, 32 | CheckboxItem, 33 | // 34 | Root as DropdownMenu, 35 | Sub as DropdownMenuSub, 36 | Item as DropdownMenuItem, 37 | Label as DropdownMenuLabel, 38 | Group as DropdownMenuGroup, 39 | Content as DropdownMenuContent, 40 | Trigger as DropdownMenuTrigger, 41 | Shortcut as DropdownMenuShortcut, 42 | RadioItem as DropdownMenuRadioItem, 43 | Separator as DropdownMenuSeparator, 44 | RadioGroup as DropdownMenuRadioGroup, 45 | SubContent as DropdownMenuSubContent, 46 | SubTrigger as DropdownMenuSubTrigger, 47 | CheckboxItem as DropdownMenuCheckboxItem, 48 | }; 49 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/form/Form.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 |
50 | {@render children(form, formData)} 51 |
52 | -------------------------------------------------------------------------------- /packages/contracts/deploy/00_deploy.ts: -------------------------------------------------------------------------------- 1 | import { DeployFunction } from "hardhat-deploy/types"; 2 | 3 | declare module "hardhat/types/runtime" { 4 | interface TypedHardhatDeployNames { 5 | PoolERC20: "PoolERC20"; 6 | } 7 | } 8 | 9 | const deploy: DeployFunction = async ({ 10 | deployments, 11 | typedDeployments, 12 | safeGetNamedAccounts, 13 | }) => { 14 | const { deployer } = await safeGetNamedAccounts({ deployer: true }); 15 | 16 | async function deployVerifier(name: string, circuitName: string) { 17 | return await deployments.deploy(name, { 18 | from: deployer, 19 | log: true, 20 | args: [], 21 | contract: `noir/target/${circuitName}.sol:HonkVerifier`, 22 | }); 23 | } 24 | const shieldVerifier = await deployVerifier( 25 | "Erc20ShieldVerifier", 26 | "erc20_shield", 27 | ); 28 | const unshieldVerifier = await deployVerifier( 29 | "Erc20UnshieldVerifier", 30 | "erc20_unshield", 31 | ); 32 | const joinVerifier = await deployVerifier("Erc20JoinVerifier", "erc20_join"); 33 | const transferVerifier = await deployVerifier( 34 | "Erc20TransferVerifier", 35 | "erc20_transfer", 36 | ); 37 | const swapVerifier = await deployVerifier( 38 | "LobRouterSwapVerifier", 39 | "lob_router_swap", 40 | ); 41 | const rollupVerifier = await deployVerifier("RollupVerifier", "rollup"); 42 | 43 | const pool = await typedDeployments.deploy("PoolERC20", { 44 | from: deployer, 45 | log: true, 46 | args: [ 47 | shieldVerifier.address, 48 | unshieldVerifier.address, 49 | joinVerifier.address, 50 | transferVerifier.address, 51 | swapVerifier.address, 52 | rollupVerifier.address, 53 | ], 54 | }); 55 | }; 56 | 57 | export default deploy; 58 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/sheet/sheet-content.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | 31 | 39 | 40 | 43 | 44 | Close 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /packages/contracts/sdk/mpc/run-party.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | source ../../noir/timer.sh 6 | 7 | if [ $# -ne 3 ]; then 8 | echo "Usage: $0 " 9 | exit 1 10 | fi 11 | WORK_DIR=$1 12 | CIRCUIT=$2 13 | PARTY_INDEX=$3 14 | 15 | PROVER0_TOML=$WORK_DIR/Prover0.toml 16 | PROVER1_TOML=$WORK_DIR/Prover1.toml 17 | # copy from https://github.com/TaceoLabs/co-snarks/tree/e96a712dfa987fb39e17232ef11d067b29b62aef/co-noir/co-noir/examples/configs 18 | PARTY_CONFIGS_DIR=configs 19 | 20 | # merge inputs into single input file 21 | timeStart "merge-input-shares" 22 | co-noir merge-input-shares --inputs $PROVER0_TOML.$PARTY_INDEX.shared --inputs $PROVER1_TOML.$PARTY_INDEX.shared --protocol REP3 --out $WORK_DIR/Prover.toml.$PARTY_INDEX.shared 23 | timeEnd "merge-input-shares" 24 | 25 | # run witness extension in MPC 26 | timeStart "mpc-generate-witness" 27 | co-noir generate-witness --input $WORK_DIR/Prover.toml.$PARTY_INDEX.shared --circuit $CIRCUIT --protocol REP3 --config $PARTY_CONFIGS_DIR/party$PARTY_INDEX.toml --out $WORK_DIR/witness.gz.$PARTY_INDEX.shared 28 | timeEnd "mpc-generate-witness" 29 | 30 | # run proving in MPC 31 | timeStart "mpc-build-proving-key" 32 | co-noir build-proving-key --witness $WORK_DIR/witness.gz.$PARTY_INDEX.shared --circuit $CIRCUIT --protocol REP3 --config $PARTY_CONFIGS_DIR/party$PARTY_INDEX.toml --out $WORK_DIR/proving_key.$PARTY_INDEX 33 | timeEnd "mpc-build-proving-key" 34 | 35 | timeStart "mpc-generate-proof" 36 | co-noir generate-proof --proving-key $WORK_DIR/proving_key.$PARTY_INDEX --protocol REP3 --hasher keccak --crs ~/.bb-crs/bn254_g1.dat --config $PARTY_CONFIGS_DIR/party$PARTY_INDEX.toml --out $WORK_DIR/proof.$PARTY_INDEX.proof --public-input $WORK_DIR/public_input.json 37 | timeEnd "mpc-generate-proof" 38 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/ui", 3 | "type": "module", 4 | "private": true, 5 | "main": "src/lib/index.ts", 6 | "module": "src/lib/index.ts", 7 | "types": "src/lib/index.ts", 8 | "scripts": { 9 | "dev": "pnpm _chore && vite dev", 10 | "build": "pnpm _chore && pnpm test:lint && vite build", 11 | "preview": "vite preview", 12 | "test": "pnpm test:lint && pnpm test:unit", 13 | "test:unit": "pnpm _chore && vitest run", 14 | "test:lint": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && prettier --check .", 15 | "test:lint:fix": "prettier --write .", 16 | "_chore": "pnpm i" 17 | }, 18 | "imports": { 19 | "#lib/*": "./src/lib/*" 20 | }, 21 | "peerDependencies": { 22 | "@sveltejs/kit": "^2.7.2", 23 | "svelte": "^5.0.0" 24 | }, 25 | "devDependencies": { 26 | "@sveltejs/adapter-vercel": "^4.0.0", 27 | "@sveltejs/kit": "^2.7.2", 28 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 29 | "@tailwindcss/typography": "^0.5.14", 30 | "@types/lodash-es": "^4.17.12", 31 | "autoprefixer": "^10.4.20", 32 | "bits-ui": "^0.21.16", 33 | "clsx": "^2.1.1", 34 | "lucide-svelte": "^0.441.0", 35 | "prettier": "^3.1.1", 36 | "prettier-plugin-svelte": "^3.1.2", 37 | "svelte-check": "^4.0.5", 38 | "tailwind-merge": "^2.5.2", 39 | "tailwind-variants": "^0.2.1", 40 | "tailwindcss": "^3.4.9", 41 | "tslib": "^2.4.1", 42 | "typescript": "^5.0.0", 43 | "vite": "^5.2.11" 44 | }, 45 | "dependencies": { 46 | "@metamask/jazzicon": "^2.0.0", 47 | "formsnap": "^1.0.1", 48 | "lodash-es": "^4.17.21", 49 | "svelte-french-toast": "^1.2.0", 50 | "svelte-typed-context": "^1.0.1", 51 | "sveltekit-superforms": "^2.19.1", 52 | "ts-essentials": "^10.0.2", 53 | "zod": "^3.23.8" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import type { Button as ButtonPrimitive } from "bits-ui"; 2 | import { type VariantProps, tv } from "tailwind-variants"; 3 | import Root from "./button.svelte"; 4 | 5 | const buttonVariants = tv({ 6 | base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 7 | variants: { 8 | variant: { 9 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 10 | destructive: 11 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 12 | outline: 13 | "border-input bg-background hover:bg-accent hover:text-accent-foreground border", 14 | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | ghost: "hover:bg-accent hover:text-accent-foreground", 16 | link: "text-primary underline-offset-4 hover:underline", 17 | }, 18 | size: { 19 | default: "h-10 px-4 py-2", 20 | sm: "h-9 rounded-md px-3", 21 | lg: "h-11 rounded-md px-8", 22 | icon: "h-10 w-10", 23 | }, 24 | }, 25 | defaultVariants: { 26 | variant: "default", 27 | size: "default", 28 | }, 29 | }); 30 | 31 | type Variant = VariantProps["variant"]; 32 | type Size = VariantProps["size"]; 33 | 34 | type Props = ButtonPrimitive.Props & { 35 | variant?: Variant; 36 | size?: Size; 37 | }; 38 | 39 | type Events = ButtonPrimitive.Events; 40 | 41 | export { 42 | // 43 | Root as Button, 44 | Root, 45 | buttonVariants, 46 | type Events as ButtonEvents, 47 | type Props as ButtonProps, 48 | type Events, 49 | type Props, 50 | }; 51 | -------------------------------------------------------------------------------- /apps/interface/src/lib/services/CurrencyListService.svelte.ts: -------------------------------------------------------------------------------- 1 | import deployments from "@repo/contracts/deployments.json"; 2 | import { utils } from "@repo/utils"; 3 | import { Token } from "@uniswap/sdk-core"; 4 | import ky from "ky"; 5 | import { z } from "zod"; 6 | 7 | export class CurrencyListService { 8 | #currenciesOnAllChains: Token[] = $state([]); 9 | 10 | constructor(private chainId: number) {} 11 | 12 | get currencies(): Token[] { 13 | if (this.chainId === 31337) { 14 | const chainId = this.chainId; 15 | return [ 16 | new Token(chainId, deployments[chainId].contracts.MockUSDC, 6, "USDC"), 17 | new Token(chainId, deployments[chainId].contracts.MockBTC, 8, "BTC"), 18 | ] as const; 19 | } 20 | return this.#currenciesOnAllChains 21 | .filter((currency) => { 22 | return currency.chainId === this.chainId; 23 | }) 24 | .filter((c) => ["USDC", "cbBTC"].includes(c.symbol!)); 25 | } 26 | 27 | getByAddress(address: string) { 28 | return this.currencies.find((c) => 29 | utils.isAddressEqual(c.address, address), 30 | ); 31 | } 32 | 33 | async load() { 34 | const schema = z.object({ 35 | tokens: z.array( 36 | z.object({ 37 | chainId: z.number(), 38 | address: z.string(), 39 | decimals: z.number(), 40 | symbol: z.string().nullish(), 41 | name: z.string().nullish(), 42 | }), 43 | ), 44 | }); 45 | 46 | const response = schema.parse( 47 | await ky.get("https://ipfs.io/ipns/tokens.uniswap.org").json(), 48 | ); 49 | 50 | this.#currenciesOnAllChains = response.tokens.map( 51 | (token) => 52 | new Token( 53 | token.chainId, 54 | token.address, 55 | token.decimals, 56 | token.symbol ?? undefined, 57 | token.name ?? undefined, 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/contracts/shared/utils.ts: -------------------------------------------------------------------------------- 1 | import * as hnh from "@nomicfoundation/hardhat-network-helpers"; 2 | import { ethers } from "ethers"; 3 | import ms from "ms"; 4 | import { assert } from "ts-essentials"; 5 | import { z } from "zod"; 6 | 7 | /** 8 | * Runs `fn` once, saves EVM state and restores it before each tests. 9 | */ 10 | export function snapshottedBeforeEach(fn: () => Promise) { 11 | const snapshots: hnh.SnapshotRestorer[] = []; 12 | 13 | before(async () => { 14 | snapshots.push(await hnh.takeSnapshot()); 15 | await fn(); 16 | }); 17 | 18 | beforeEach(async () => { 19 | snapshots.push(await hnh.takeSnapshot()); 20 | }); 21 | 22 | async function restoreLatestSnapshot() { 23 | const snapshot = snapshots.pop(); 24 | assert(snapshot, "no snapshot"); 25 | snapshot.restore(); 26 | } 27 | afterEach(restoreLatestSnapshot); 28 | after(restoreLatestSnapshot); 29 | } 30 | 31 | /** 32 | * Executes `fn`, and, after execution, reverts any changes made to 33 | * the blockchain inside the `fn`. 34 | */ 35 | export async function revertAfterExecution(fn: () => Promise) { 36 | let snapshot: hnh.SnapshotRestorer = await hnh.takeSnapshot(); 37 | try { 38 | await fn(); 39 | } finally { 40 | await snapshot.restore(); 41 | } 42 | } 43 | 44 | export function zPrivateKey() { 45 | return z 46 | .string() 47 | .refine((s) => /^0x[0-9-a-fA-F]{64}$/.test(s), "Invalid private key"); 48 | } 49 | 50 | export function sec(s: string): number { 51 | return Math.floor(ms(s) / 1000); 52 | } 53 | 54 | export async function parseUnits( 55 | token: { decimals: () => Promise }, 56 | value: string, 57 | ) { 58 | return ethers.parseUnits(value, await token.decimals()); 59 | } 60 | 61 | export async function formatUnits( 62 | token: { decimals: () => Promise }, 63 | units: ethers.BigNumberish, 64 | ) { 65 | return ethers.formatUnits(units, await token.decimals()); 66 | } 67 | -------------------------------------------------------------------------------- /apps/interface/src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --muted: 210 40% 96.1%; 11 | --muted-foreground: 215.4 16.3% 46.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 222.2 84% 4.9%; 18 | 19 | --border: 214.3 31.8% 91.4%; 20 | --input: 214.3 31.8% 91.4%; 21 | 22 | --primary: 222.2 47.4% 11.2%; 23 | --primary-foreground: 210 40% 98%; 24 | 25 | --secondary: 210 40% 96.1%; 26 | --secondary-foreground: 222.2 47.4% 11.2%; 27 | 28 | --accent: 210 40% 96.1%; 29 | --accent-foreground: 222.2 47.4% 11.2%; 30 | 31 | --destructive: 0 72.2% 50.6%; 32 | --destructive-foreground: 210 40% 98%; 33 | 34 | --ring: 222.2 84% 4.9%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | --background: 222.2 84% 4.9%; 41 | --foreground: 210 40% 98%; 42 | 43 | --muted: 217.2 32.6% 17.5%; 44 | --muted-foreground: 215 20.2% 65.1%; 45 | 46 | --popover: 222.2 84% 4.9%; 47 | --popover-foreground: 210 40% 98%; 48 | 49 | --card: 222.2 84% 4.9%; 50 | --card-foreground: 210 40% 98%; 51 | 52 | --border: 217.2 32.6% 17.5%; 53 | --input: 217.2 32.6% 17.5%; 54 | 55 | --primary: 210 40% 98%; 56 | --primary-foreground: 222.2 47.4% 11.2%; 57 | 58 | --secondary: 217.2 32.6% 17.5%; 59 | --secondary-foreground: 210 40% 98%; 60 | 61 | --accent: 217.2 32.6% 17.5%; 62 | --accent-foreground: 210 40% 98%; 63 | 64 | --destructive: 0 62.8% 30.6%; 65 | --destructive-foreground: 210 40% 98%; 66 | 67 | --ring: 212.7 26.8% 83.9%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | body { 76 | @apply bg-background text-foreground; 77 | } 78 | } 79 | 80 | html, 81 | body { 82 | height: 100%; 83 | } 84 | -------------------------------------------------------------------------------- /packages/ui/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { cubicOut } from "svelte/easing"; 3 | import type { TransitionConfig } from "svelte/transition"; 4 | import { twMerge } from "tailwind-merge"; 5 | 6 | export function cn(...inputs: ClassValue[]) { 7 | return twMerge(clsx(inputs)); 8 | } 9 | 10 | type FlyAndScaleParams = { 11 | y?: number; 12 | x?: number; 13 | start?: number; 14 | duration?: number; 15 | }; 16 | 17 | export const flyAndScale = ( 18 | node: Element, 19 | params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }, 20 | ): TransitionConfig => { 21 | const style = getComputedStyle(node); 22 | const transform = style.transform === "none" ? "" : style.transform; 23 | 24 | const scaleConversion = ( 25 | valueA: number, 26 | scaleA: [number, number], 27 | scaleB: [number, number], 28 | ) => { 29 | const [minA, maxA] = scaleA; 30 | const [minB, maxB] = scaleB; 31 | 32 | const percentage = (valueA - minA) / (maxA - minA); 33 | const valueB = percentage * (maxB - minB) + minB; 34 | 35 | return valueB; 36 | }; 37 | 38 | const styleToString = ( 39 | style: Record, 40 | ): string => { 41 | return Object.keys(style).reduce((str, key) => { 42 | if (style[key] === undefined) return str; 43 | return str + `${key}:${style[key]};`; 44 | }, ""); 45 | }; 46 | 47 | return { 48 | duration: params.duration ?? 200, 49 | delay: 0, 50 | css: (t) => { 51 | const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); 52 | const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); 53 | const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); 54 | 55 | return styleToString({ 56 | transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, 57 | opacity: t, 58 | }); 59 | }, 60 | easing: cubicOut, 61 | }; 62 | }; 63 | 64 | export function errorToString(error: any) { 65 | return String(error?.message || error); 66 | } 67 | -------------------------------------------------------------------------------- /apps/interface/src/lib/services/EvmAccountService.svelte.ts: -------------------------------------------------------------------------------- 1 | import { utils } from "@repo/utils"; 2 | import { ethers } from "ethers"; 3 | import { keccak256ToFr } from "../utils"; 4 | 5 | export class EvmAccountService { 6 | readonly provider!: ethers.BrowserProvider; 7 | address: string | undefined = $state(); 8 | 9 | #secretKeys: Record> = {}; 10 | 11 | constructor() { 12 | if (typeof window !== "undefined") { 13 | this.provider = new ethers.BrowserProvider((window as any).ethereum); 14 | this.#fetchAddress(); 15 | (window as any).ethereum.on("accountsChanged", (accounts: string[]) => { 16 | this.address = accounts[0]; 17 | }); 18 | } 19 | } 20 | 21 | async connect() { 22 | await this.provider.send("eth_requestAccounts", []); 23 | await this.#fetchAddress(); 24 | } 25 | 26 | async getSigner() { 27 | if (!this.address) { 28 | return undefined; 29 | } 30 | try { 31 | return await this.provider.getSigner(this.address); 32 | } catch (e) { 33 | console.error(e); 34 | return undefined; 35 | } 36 | } 37 | 38 | async #fetchAddress() { 39 | this.address = (await this.provider.send("eth_accounts", []))[0]; 40 | } 41 | 42 | async getSecretKey(account: ethers.Signer) { 43 | const message = "Sign this message to derive a Mezcal secret key"; // TODO(security): put the correct domain here 44 | const address = (await account.getAddress()).toLowerCase(); 45 | if (!this.#secretKeys[address]) { 46 | this.#secretKeys[address] = utils.iife(async () => { 47 | const signature0 = await account.signMessage(message); 48 | const signature1 = await account.signMessage(message); 49 | if (signature0 !== signature1) { 50 | throw new Error( 51 | "Secret key cannot be generated because your wallet signatures are not deterministic", 52 | ); 53 | } 54 | return (await keccak256ToFr(signature0)).toString(); 55 | }); 56 | } 57 | return this.#secretKeys[address]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /apps/interface/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | import { browser } from "$app/environment"; 2 | import deployments from "@repo/contracts/deployments.json"; 3 | import { sdk } from "@repo/contracts/sdk"; 4 | import { PoolERC20__factory } from "@repo/contracts/typechain-types/index.js"; 5 | import { QueryClient } from "@tanstack/svelte-query"; 6 | import { ethers } from "ethers"; 7 | import { ReownService } from "./reown.js"; 8 | import { route } from "./ROUTES.js"; 9 | import { CurrencyListService } from "./services/CurrencyListService.svelte.js"; 10 | import { EvmAccountService } from "./services/EvmAccountService.svelte.js"; 11 | import { QueriesService } from "./services/QueriesService.svelte.js"; 12 | 13 | const queryClient = new QueryClient({ 14 | defaultOptions: { 15 | queries: { 16 | enabled: browser, 17 | }, 18 | }, 19 | }); 20 | 21 | const queries = new QueriesService(queryClient); 22 | 23 | // TODO: remove this provider 24 | const provider = new ethers.JsonRpcProvider("http://localhost:8545"); 25 | 26 | const chainId = 31337; 27 | 28 | const currencyList = new CurrencyListService(chainId); 29 | 30 | const relayer = new ethers.Wallet( 31 | "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", 32 | provider, 33 | ); 34 | const contract = PoolERC20__factory.connect( 35 | deployments[chainId].contracts.PoolERC20, 36 | relayer, 37 | ); 38 | const coreSdk = sdk.createCoreSdk(contract); 39 | const trees = new sdk.RemoteTreesService(route("POST /api/trees")); 40 | const interfaceSdk = sdk.createInterfaceSdk(coreSdk, trees, { 41 | shield: import("@repo/contracts/noir/target/erc20_shield.json"), 42 | unshield: import("@repo/contracts/noir/target/erc20_unshield.json"), 43 | join: import("@repo/contracts/noir/target/erc20_join.json"), 44 | transfer: import("@repo/contracts/noir/target/erc20_transfer.json"), 45 | }); 46 | const reown = new ReownService(contract); 47 | const evm = new EvmAccountService(); 48 | 49 | export const lib = { 50 | queries, 51 | chainId, 52 | relayer, 53 | currencyList, 54 | provider, 55 | reown, 56 | evm, 57 | ...coreSdk, 58 | ...interfaceSdk, 59 | }; 60 | -------------------------------------------------------------------------------- /apps/interface/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/interface", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "pnpm _chore && vite dev", 8 | "build": "pnpm _chore && pnpm test:lint && vite build", 9 | "preview": "vite preview", 10 | "test": "pnpm test:lint && pnpm test:unit", 11 | "test:unit": "pnpm _chore && vitest run", 12 | "test:lint": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && prettier --check .", 13 | "test:lint:fix": "prettier --write .", 14 | "_chore": "pnpm i" 15 | }, 16 | "dependencies": { 17 | "@aztec/aztec.js": "0.86.0", 18 | "@aztec/foundation": "0.86.0", 19 | "@hpke/core": "^1.7.1", 20 | "@hpke/dhkem-x25519": "^1.6.1", 21 | "@noir-lang/acvm_js": "1.0.0-beta.5", 22 | "@noir-lang/noir_js": "1.0.0-beta.5", 23 | "@noir-lang/noirc_abi": "1.0.0-beta.5", 24 | "@reown/walletkit": "^1.1.1", 25 | "@repo/contracts": "workspace:*", 26 | "@repo/ui": "workspace:*", 27 | "@repo/utils": "workspace:*", 28 | "@tanstack/svelte-query": "^5.59.10", 29 | "@uniswap/sdk-core": "^6.0.0", 30 | "@vercel/analytics": "^1.3.1", 31 | "@walletconnect/core": "^2.17.2", 32 | "@walletconnect/utils": "^2.17.2", 33 | "buffer": "^6.0.3", 34 | "ethers": "^6.13.4", 35 | "ky": "^1.7.2", 36 | "lodash-es": "^4.17.21", 37 | "lucide-svelte": "^0.441.0", 38 | "ts-essentials": "^10.0.2", 39 | "zod": "^3.23.8" 40 | }, 41 | "devDependencies": { 42 | "@shieldswap/vite-plugin-aztec": "^0.0.2", 43 | "@sveltejs/adapter-node": "^5.2.9", 44 | "@sveltejs/kit": "^2.7.2", 45 | "@sveltejs/vite-plugin-svelte": "^4.0.0", 46 | "@types/lodash-es": "^4.17.12", 47 | "prettier": "^3.1.1", 48 | "prettier-plugin-svelte": "^3.1.2", 49 | "prettier-plugin-tailwindcss": "^0.6.5", 50 | "svelte": "^5.0.5", 51 | "svelte-check": "^4.0.5", 52 | "tslib": "^2.4.1", 53 | "typescript": "^5.6.2", 54 | "vite": "^5.2.11", 55 | "vite-plugin-kit-routes": "^0.6.5", 56 | "vite-plugin-resolve": "^2.5.2", 57 | "vitest": "^2.1.3" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/contracts/noir/lob_router/src/lib.nr: -------------------------------------------------------------------------------- 1 | mod LobRouter { 2 | use common::OwnedNote; 3 | 4 | pub fn swap( 5 | context: &mut common::Context, 6 | seller_secret_key: Field, 7 | seller_note: erc20::Erc20NoteConsumptionInputs, 8 | seller_order: crate::Order, 9 | seller_randomness: Field, 10 | buyer_secret_key: Field, 11 | buyer_note: erc20::Erc20NoteConsumptionInputs, 12 | buyer_order: crate::Order, 13 | buyer_randomness: Field, 14 | ) { 15 | // TODO(security): orders must be signed by parties 16 | 17 | assert( 18 | seller_order.sell_amount == buyer_order.buy_amount, 19 | "seller order amount does not match buyer order amount", 20 | ); 21 | assert( 22 | seller_order.buy_amount == buyer_order.sell_amount, 23 | "buyer order amount does not match seller order amount", 24 | ); 25 | let seller_amount = seller_order.sell_amount; 26 | let buyer_amount = seller_order.buy_amount; 27 | assert(seller_amount.token == seller_note.note.amount.token, "invalid seller note token"); 28 | assert(buyer_amount.token == buyer_note.note.amount.token, "invalid buyer note token"); 29 | 30 | erc20::Token::transfer( 31 | context, 32 | seller_secret_key, 33 | seller_note, 34 | buyer_note.note.owner(), 35 | seller_amount, 36 | buyer_randomness, 37 | seller_randomness, 38 | ); 39 | 40 | erc20::Token::transfer( 41 | context, 42 | buyer_secret_key, 43 | buyer_note, 44 | seller_note.note.owner(), 45 | buyer_amount, 46 | seller_randomness, 47 | buyer_randomness, 48 | ); 49 | } 50 | } 51 | 52 | pub struct Order { 53 | pub sell_amount: common::TokenAmount, 54 | pub buy_amount: common::TokenAmount, 55 | /// Hide order contents from other parties and outside world 56 | // TODO(perf): not sure if this is needed because orders are secret shared in an MPC network 57 | pub randomness: Field, 58 | } 59 | -------------------------------------------------------------------------------- /packages/contracts/sdk/sdk.ts: -------------------------------------------------------------------------------- 1 | import type { CompiledCircuit } from "@noir-lang/noir_js"; 2 | import { ethers } from "ethers"; 3 | import { mapValues } from "lodash-es"; 4 | import type { AsyncOrSync } from "ts-essentials"; 5 | import type { PoolERC20 } from "../typechain-types/index.js"; 6 | import { EncryptionService } from "./EncryptionService.js"; 7 | import { LobService } from "./LobService.js"; 8 | import { MpcProverService } from "./mpc/MpcNetworkService.js"; 9 | import { PoolErc20Service } from "./PoolErc20Service.js"; 10 | import { type ITreesService } from "./RemoteTreesService.js"; 11 | 12 | export * from "./EncryptionService.js"; 13 | export * from "./NonMembershipTree.js"; 14 | export * from "./PoolErc20Service.js"; 15 | export * from "./RemoteTreesService.js"; 16 | export * from "./TreesService.js"; 17 | 18 | export function createCoreSdk(contract: PoolERC20) { 19 | const encryption = EncryptionService.getSingleton(); 20 | return { 21 | contract, 22 | encryption, 23 | }; 24 | } 25 | 26 | export function createInterfaceSdk( 27 | coreSdk: ReturnType, 28 | trees: ITreesService, 29 | compiledCircuits: Record< 30 | "shield" | "unshield" | "join" | "transfer" | "swap", 31 | AsyncOrSync 32 | >, 33 | ) { 34 | const circuits = ethers.resolveProperties( 35 | mapValues(compiledCircuits, getCircuit), 36 | ); 37 | const poolErc20 = new PoolErc20Service( 38 | coreSdk.contract, 39 | coreSdk.encryption, 40 | trees, 41 | circuits, 42 | ); 43 | const mpcProver = new MpcProverService(); 44 | const lob = new LobService( 45 | coreSdk.contract, 46 | trees, 47 | poolErc20, 48 | mpcProver, 49 | circuits, 50 | ); 51 | 52 | return { 53 | poolErc20, 54 | lob, 55 | }; 56 | } 57 | 58 | async function getCircuit(artifact: AsyncOrSync) { 59 | const { Noir } = await import("@noir-lang/noir_js"); 60 | const { UltraHonkBackend } = await import("@aztec/bb.js"); 61 | artifact = await artifact; 62 | const noir = new Noir(artifact); 63 | const backend = new UltraHonkBackend(artifact.bytecode); 64 | return { circuit: artifact, noir, backend }; 65 | } 66 | -------------------------------------------------------------------------------- /apps/interface/src/lib/reown.ts: -------------------------------------------------------------------------------- 1 | import { lib } from "$lib"; 2 | import { WalletKit } from "@reown/walletkit"; 3 | import type { PoolERC20 } from "@repo/contracts/typechain-types"; 4 | import { Core } from "@walletconnect/core"; 5 | import { buildApprovedNamespaces, getSdkError } from "@walletconnect/utils"; 6 | 7 | const core = new Core({ 8 | projectId: "64614c892df35675580b5cb21249418b", 9 | }); 10 | 11 | const metadata = { 12 | name: "Mezcal", 13 | description: "Private anything", 14 | url: "https://reown.com/appkit", // origin must match your domain & subdomain 15 | icons: ["https://assets.reown.com/reown-profile-pic.png"], 16 | }; 17 | 18 | export class ReownService { 19 | private readonly kit: Promise>; 20 | 21 | constructor(private contract: PoolERC20) { 22 | this.kit = WalletKit.init({ 23 | core, 24 | metadata, 25 | }).then((kit) => { 26 | kit.on("session_proposal", async (proposal) => { 27 | console.log("session_proposal", proposal); 28 | try { 29 | const namespaces = buildApprovedNamespaces({ 30 | proposal: proposal.params, 31 | supportedNamespaces: { 32 | eip155: { 33 | chains: [`eip155:${lib.chainId}`], 34 | methods: ["eth_sendTransaction"], 35 | events: ["accountsChanged", "chainChanged"], 36 | accounts: [ 37 | `eip155:${lib.chainId}:${await this.contract.getAddress()}`, 38 | ], 39 | }, 40 | }, 41 | }); 42 | await kit.approveSession({ 43 | id: proposal.id, 44 | namespaces, 45 | }); 46 | } catch (e) { 47 | console.error(e); 48 | await kit.rejectSession({ 49 | id: proposal.id, 50 | reason: getSdkError("USER_REJECTED"), 51 | }); 52 | return; 53 | } 54 | }); 55 | 56 | kit.on("session_request", async (request) => { 57 | console.log("session_request", request); 58 | }); 59 | 60 | return kit; 61 | }); 62 | } 63 | 64 | async pair(uri: string) { 65 | const kit = await this.kit; 66 | await kit.pair({ uri }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /apps/interface/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Fr } from "@aztec/aztec.js"; 2 | import { ethers } from "ethers"; 3 | import ky from "ky"; 4 | import { assert } from "ts-essentials"; 5 | import { route } from "./ROUTES"; 6 | 7 | // TODO: move to a service 8 | export function requestRollup() { 9 | return ky.post(route("POST /api/rollup")); 10 | } 11 | 12 | export function printPublicInputs(publicInputs: string[]) { 13 | console.log("publicInputs js", publicInputs.length); 14 | for (const publicInput of publicInputs) { 15 | console.log(publicInput); 16 | } 17 | console.log(); 18 | } 19 | 20 | export async function keccak256ToFr(value: string): Promise { 21 | const { Fr } = await import("@aztec/aztec.js"); 22 | const { truncateAndPad } = await import("@aztec/foundation/serialize"); 23 | const hash = ethers.keccak256(value); 24 | return Fr.fromBuffer(truncateAndPad(Buffer.from(ethers.getBytes(hash)))); 25 | } 26 | 27 | function splitBigIntToLimbs( 28 | bigInt: bigint, 29 | limbSize: number, 30 | numLimbs: number, 31 | ): bigint[] { 32 | const limbs: bigint[] = []; 33 | const mask = (1n << BigInt(limbSize)) - 1n; 34 | for (let i = 0; i < numLimbs; i++) { 35 | const limb = (bigInt / (1n << (BigInt(i) * BigInt(limbSize)))) & mask; 36 | limbs.push(limb); 37 | } 38 | return limbs; 39 | } 40 | 41 | function unsplitBigIntFromLimbs(limbs: bigint[], limbSize: number): bigint { 42 | let bigInt = 0n; 43 | for (let i = 0; i < limbs.length; i++) { 44 | bigInt += limbs[i]! << (BigInt(i) * BigInt(limbSize)); 45 | } 46 | return bigInt; 47 | } 48 | 49 | // Note: keep in sync with other languages 50 | export const U256_LIMBS = 3; 51 | // Note: keep in sync with other languages 52 | export const U256_LIMB_SIZE = 120; 53 | 54 | export function toNoirU256(value: bigint) { 55 | assert(value >= 0n && value < 2n ** 256n, "invalid U256 value"); 56 | const limbs = splitBigIntToLimbs(value, U256_LIMB_SIZE, U256_LIMBS).map( 57 | (x) => "0x" + x.toString(16), 58 | ); 59 | return { limbs }; 60 | } 61 | 62 | export function fromNoirU256(value: { limbs: (bigint | string)[] }) { 63 | assert(value.limbs.length === U256_LIMBS, "invalid U256 limbs"); 64 | return unsplitBigIntFromLimbs( 65 | value.limbs.map((x) => BigInt(x)), 66 | 120, 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /packages/contracts/noir/erc20/src/lib.nr: -------------------------------------------------------------------------------- 1 | pub use common::erc20_note::{Erc20Note, Erc20NoteConsumptionInputs}; 2 | 3 | pub mod Token { 4 | use common::Note; 5 | 6 | pub fn mint( 7 | context: &mut common::Context, 8 | to: common::WaAddress, 9 | amount: common::TokenAmount, 10 | randomness: Field, 11 | ) { 12 | crate::Erc20Note { owner: to, amount, randomness }.emit(context); 13 | } 14 | 15 | pub fn burn( 16 | context: &mut common::Context, 17 | from_secret_key: Field, 18 | from_note_inputs: crate::Erc20NoteConsumptionInputs, 19 | amount: common::TokenAmount, 20 | change_randomness: Field, 21 | ) { 22 | crate::Erc20Note::sub_and_emit_change( 23 | context, 24 | [from_note_inputs], 25 | amount, 26 | change_randomness, 27 | from_secret_key, 28 | ); 29 | } 30 | 31 | pub fn join( 32 | context: &mut common::Context, 33 | from_secret_key: Field, 34 | notes: [crate::Erc20NoteConsumptionInputs; N], 35 | to: common::WaAddress, 36 | join_randomness: Field, 37 | ) { 38 | for note in notes { 39 | note.consume(context, from_secret_key); 40 | } 41 | 42 | let mut joined_amount = common::TokenAmount::zero(notes[0].note.amount.token); 43 | for i in 0..N { 44 | joined_amount += notes[i].note.amount; 45 | } 46 | 47 | crate::Erc20Note { owner: to, amount: joined_amount, randomness: join_randomness }.emit( 48 | context, 49 | ); 50 | } 51 | 52 | pub fn transfer( 53 | context: &mut common::Context, 54 | from_secret_key: Field, 55 | from_note_inputs: crate::Erc20NoteConsumptionInputs, 56 | to: common::WaAddress, 57 | amount: common::TokenAmount, 58 | to_randomness: Field, 59 | change_randomness: Field, 60 | ) { 61 | crate::Erc20Note::sub_and_emit_change( 62 | context, 63 | [from_note_inputs], 64 | amount, 65 | change_randomness, 66 | from_secret_key, 67 | ); 68 | crate::Erc20Note { owner: to, amount, randomness: to_randomness }.emit(context); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /apps/interface/src/lib/components/ShieldForm.svelte: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 | {#snippet children(form, formData)} 43 | 44 | 45 | Token 46 | 47 | 48 | 49 | 50 | 51 | 52 | Amount 53 | 54 | 55 | 56 | 57 | 58 | 59 | Shield 60 | {/snippet} 61 | 62 | -------------------------------------------------------------------------------- /apps/interface/src/routes/Header.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
20 | 25 | 26 | 27 | 33 | 34 | Toggle navigation menu 35 | 36 | 37 | 38 | 41 | 42 | 43 |
46 |
47 | {#if lib.evm.address} 48 | {utils.shortAddress(lib.evm.address)} 49 | {:else} 50 | lib.evm.connect()}> 51 | Connect 52 | 53 | {/if} 54 |
55 |
56 | 57 | {#snippet navbar()} 58 | 62 | App 63 | 64 | {@render link({ text: "Home", href: route("/") })} 65 | {/snippet} 66 | 67 | {#snippet link({ text, href }: { text: string; href: string })} 68 | 75 | {text} 76 | 77 | {/snippet} 78 | -------------------------------------------------------------------------------- /packages/contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/contracts", 3 | "private": true, 4 | "scripts": { 5 | "compile": "hardhat compile", 6 | "test": "hardhat test", 7 | "deploy": "pnpm hardhat deploy-and-export", 8 | "deploy:localhost": "rm -rf deployments/localhost && pnpm hardhat deploy-and-export --network localhost --gasprice 1gwei" 9 | }, 10 | "devDependencies": { 11 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.2", 12 | "@nomicfoundation/hardhat-ethers": "^3.0.5", 13 | "@nomicfoundation/hardhat-network-helpers": "^1.0.10", 14 | "@nomicfoundation/hardhat-toolbox": "^5.0.0", 15 | "@nomicfoundation/hardhat-verify": "^2.0.4", 16 | "@openzeppelin/contracts": "5.1.0", 17 | "@typechain/ethers-v6": "^0.5.1", 18 | "@typechain/hardhat": "^9.1.0", 19 | "@types/chai": "^4.3.11", 20 | "@types/chai-as-promised": "^7.1.8", 21 | "@types/lodash": "^4.14.202", 22 | "@types/lodash-es": "^4.17.12", 23 | "@types/mocha": "^10.0.1", 24 | "@types/ms": "^0.7.31", 25 | "@types/node": "^20.10.5", 26 | "chai": "^4.3.6", 27 | "chai-as-promised": "^7.1.2", 28 | "dotenv": "^16.0.3", 29 | "hardhat": "^2.22.16", 30 | "hardhat-deploy": "^0.11.45", 31 | "hardhat-gas-reporter": "^1.0.10", 32 | "hardhat-noir": "^0.5.0", 33 | "json-stringify-deterministic": "^1.0.12", 34 | "lodash": "^4.17.21", 35 | "ms": "^2.1.3", 36 | "prettier": "^3.2.5", 37 | "prettier-plugin-organize-imports": "^3.2.4", 38 | "prettier-plugin-solidity": "^1.3.1", 39 | "solidity-coverage": "^0.8.7", 40 | "ts-node": "^10.9.2", 41 | "tsx": "^4.19.2", 42 | "typechain": "^8.3.2", 43 | "typescript": "^5.3.3" 44 | }, 45 | "dependencies": { 46 | "@aztec/aztec.js": "0.86.0", 47 | "@aztec/bb.js": "0.86.0", 48 | "@aztec/foundation": "0.86.0", 49 | "@aztec/kv-store": "0.86.0", 50 | "@aztec/merkle-tree": "0.86.0", 51 | "@aztec/stdlib": "0.86.0", 52 | "@hpke/core": "^1.7.1", 53 | "@hpke/dhkem-x25519": "^1.6.1", 54 | "@noir-lang/noir_js": "1.0.0-beta.5", 55 | "@repo/utils": "workspace:*", 56 | "ethers": "^6.13.4", 57 | "ky": "^1.7.2", 58 | "lodash-es": "^4.17.21", 59 | "ox": "^0.7.2", 60 | "p-queue": "^8.1.0", 61 | "smol-toml": "^1.3.1", 62 | "ts-essentials": "^9.4.1", 63 | "zod": "^3.23.8" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/ui/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import typography from "@tailwindcss/typography"; 2 | import type { Config } from "tailwindcss"; 3 | import { fontFamily } from "tailwindcss/defaultTheme"; 4 | 5 | const config: Config = { 6 | darkMode: ["class"], 7 | content: [ 8 | "./src/**/*.{html,js,svelte,ts}", 9 | "../../packages/ui/src/**/*.{html,js,svelte,ts}", 10 | ], 11 | safelist: ["dark"], 12 | plugins: [typography()], 13 | theme: { 14 | container: { 15 | center: true, 16 | padding: "2rem", 17 | screens: { 18 | "2xl": "1400px", 19 | }, 20 | }, 21 | extend: { 22 | colors: { 23 | border: "hsl(var(--border) / )", 24 | input: "hsl(var(--input) / )", 25 | ring: "hsl(var(--ring) / )", 26 | background: "hsl(var(--background) / )", 27 | foreground: "hsl(var(--foreground) / )", 28 | primary: { 29 | DEFAULT: "hsl(var(--primary) / )", 30 | foreground: "hsl(var(--primary-foreground) / )", 31 | }, 32 | secondary: { 33 | DEFAULT: "hsl(var(--secondary) / )", 34 | foreground: "hsl(var(--secondary-foreground) / )", 35 | }, 36 | destructive: { 37 | DEFAULT: "hsl(var(--destructive) / )", 38 | foreground: "hsl(var(--destructive-foreground) / )", 39 | }, 40 | muted: { 41 | DEFAULT: "hsl(var(--muted) / )", 42 | foreground: "hsl(var(--muted-foreground) / )", 43 | }, 44 | accent: { 45 | DEFAULT: "hsl(var(--accent) / )", 46 | foreground: "hsl(var(--accent-foreground) / )", 47 | }, 48 | popover: { 49 | DEFAULT: "hsl(var(--popover) / )", 50 | foreground: "hsl(var(--popover-foreground) / )", 51 | }, 52 | card: { 53 | DEFAULT: "hsl(var(--card) / )", 54 | foreground: "hsl(var(--card-foreground) / )", 55 | }, 56 | }, 57 | borderRadius: { 58 | lg: "var(--radius)", 59 | md: "calc(var(--radius) - 2px)", 60 | sm: "calc(var(--radius) - 4px)", 61 | }, 62 | fontFamily: { 63 | sans: [...fontFamily.sans], 64 | }, 65 | }, 66 | }, 67 | }; 68 | 69 | export default config; 70 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/ui/sheet/index.ts: -------------------------------------------------------------------------------- 1 | import { Dialog as SheetPrimitive } from "bits-ui"; 2 | import { type VariantProps, tv } from "tailwind-variants"; 3 | 4 | import Portal from "./sheet-portal.svelte"; 5 | import Overlay from "./sheet-overlay.svelte"; 6 | import Content from "./sheet-content.svelte"; 7 | import Header from "./sheet-header.svelte"; 8 | import Footer from "./sheet-footer.svelte"; 9 | import Title from "./sheet-title.svelte"; 10 | import Description from "./sheet-description.svelte"; 11 | 12 | const Root = SheetPrimitive.Root; 13 | const Close = SheetPrimitive.Close; 14 | const Trigger = SheetPrimitive.Trigger; 15 | 16 | export { 17 | Root, 18 | Close, 19 | Trigger, 20 | Portal, 21 | Overlay, 22 | Content, 23 | Header, 24 | Footer, 25 | Title, 26 | Description, 27 | // 28 | Root as Sheet, 29 | Close as SheetClose, 30 | Trigger as SheetTrigger, 31 | Portal as SheetPortal, 32 | Overlay as SheetOverlay, 33 | Content as SheetContent, 34 | Header as SheetHeader, 35 | Footer as SheetFooter, 36 | Title as SheetTitle, 37 | Description as SheetDescription, 38 | }; 39 | 40 | export const sheetVariants = tv({ 41 | base: "bg-background fixed z-50 gap-4 p-6 shadow-lg", 42 | variants: { 43 | side: { 44 | top: "inset-x-0 top-0 border-b", 45 | bottom: "inset-x-0 bottom-0 border-t", 46 | left: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm", 47 | right: "inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", 48 | }, 49 | }, 50 | defaultVariants: { 51 | side: "right", 52 | }, 53 | }); 54 | 55 | export const sheetTransitions = { 56 | top: { 57 | in: { 58 | y: "-100%", 59 | duration: 500, 60 | opacity: 1, 61 | }, 62 | out: { 63 | y: "-100%", 64 | duration: 300, 65 | opacity: 1, 66 | }, 67 | }, 68 | bottom: { 69 | in: { 70 | y: "100%", 71 | duration: 500, 72 | opacity: 1, 73 | }, 74 | out: { 75 | y: "100%", 76 | duration: 300, 77 | opacity: 1, 78 | }, 79 | }, 80 | left: { 81 | in: { 82 | x: "-100%", 83 | duration: 500, 84 | opacity: 1, 85 | }, 86 | out: { 87 | x: "-100%", 88 | duration: 300, 89 | opacity: 1, 90 | }, 91 | }, 92 | right: { 93 | in: { 94 | x: "100%", 95 | duration: 500, 96 | opacity: 1, 97 | }, 98 | out: { 99 | x: "100%", 100 | duration: 300, 101 | opacity: 1, 102 | }, 103 | }, 104 | }; 105 | 106 | export type Side = VariantProps["side"]; 107 | -------------------------------------------------------------------------------- /packages/contracts/sdk/EncryptionService.ts: -------------------------------------------------------------------------------- 1 | import { Aes128Gcm, CipherSuite, HkdfSha256 } from "@hpke/core"; 2 | import { DhkemX25519HkdfSha256, X25519 } from "@hpke/dhkem-x25519"; 3 | import { utils } from "@repo/utils"; 4 | import { ethers } from "ethers"; 5 | import { assert } from "ts-essentials"; 6 | 7 | // TODO(security): Constrain encryption and nuke this service. 8 | export class EncryptionService { 9 | #suite: CipherSuite; 10 | 11 | private constructor() { 12 | this.#suite = new CipherSuite({ 13 | kem: new DhkemX25519HkdfSha256(), 14 | kdf: new HkdfSha256(), 15 | aead: new Aes128Gcm(), 16 | }); 17 | } 18 | 19 | static getSingleton = utils.lazyValue(() => new EncryptionService()); 20 | 21 | async encrypt(publicKey: ethers.BytesLike, messageBytes: ethers.BytesLike) { 22 | const importedPublicKey = await this.#importPublicKey(publicKey); 23 | const sender = await this.#suite.createSenderContext({ 24 | recipientPublicKey: importedPublicKey, 25 | }); 26 | const enc = new Uint8Array(sender.enc); 27 | assert(enc.length === ECC_LEN, "invalid encryption context length"); 28 | 29 | const encrypted = new Uint8Array( 30 | await sender.seal(ethers.getBytes(messageBytes)), 31 | ); 32 | return ethers.concat([enc, encrypted]); 33 | } 34 | 35 | async decrypt(privateKey: ethers.BytesLike, ciphertext: ethers.BytesLike) { 36 | const importedPrivateKey = await this.#importPrivateKey(privateKey); 37 | ciphertext = ethers.getBytes(ciphertext); 38 | const recipient = await this.#suite.createRecipientContext({ 39 | recipientKey: importedPrivateKey, 40 | enc: ciphertext.subarray(0, ECC_LEN), 41 | }); 42 | const plaintext = await recipient.open(ciphertext.subarray(ECC_LEN)); 43 | return ethers.hexlify(new Uint8Array(plaintext)); 44 | } 45 | 46 | async derivePublicKey(privateKey: ethers.BytesLike) { 47 | const importedPrivateKey = await this.#importPrivateKey(privateKey); 48 | const publicKey = await new X25519(this.#suite.kdf).derivePublicKey( 49 | importedPrivateKey, 50 | ); 51 | return ethers.hexlify( 52 | new Uint8Array(await this.#suite.kem.serializePublicKey(publicKey)), 53 | ); 54 | } 55 | 56 | async #importPrivateKey(privateKey: ethers.BytesLike) { 57 | return await this.#suite.kem.importKey( 58 | "raw", 59 | ethers.getBytes(privateKey), 60 | false, // isPublic 61 | ); 62 | } 63 | 64 | async #importPublicKey(publicKey: ethers.BytesLike) { 65 | return await this.#suite.kem.importKey( 66 | "raw", 67 | ethers.getBytes(publicKey), 68 | true, // isPublic 69 | ); 70 | } 71 | } 72 | 73 | const ECC_LEN = 32; 74 | -------------------------------------------------------------------------------- /packages/contracts/sdk/mpc/utils.ts: -------------------------------------------------------------------------------- 1 | import type { CompiledCircuit, InputMap } from "@noir-lang/noir_js"; 2 | import { ethers } from "ethers"; 3 | import { range } from "lodash"; 4 | import fs from "node:fs"; 5 | import path from "node:path"; 6 | import toml from "smol-toml"; 7 | import type { PartyIndex } from "./MpcNetworkService.js"; 8 | 9 | export async function splitInput(circuit: CompiledCircuit, input: InputMap) { 10 | return await inWorkingDir(async (workingDir) => { 11 | const proverPath = path.join(workingDir, "ProverX.toml"); 12 | fs.writeFileSync(proverPath, toml.stringify(input)); 13 | const circuitPath = path.join(workingDir, "circuit.json"); 14 | fs.writeFileSync(circuitPath, JSON.stringify(circuit)); 15 | const runCommand = makeRunCommand(__dirname); 16 | await runCommand("./split-inputs.sh", [proverPath, circuitPath]); 17 | const shared = range(3).map((i) => { 18 | const x = Uint8Array.from(fs.readFileSync(`${proverPath}.${i}.shared`)); 19 | return ethers.hexlify(x); 20 | }); 21 | return Array.from(shared.entries()).map(([partyIndex, inputShared]) => ({ 22 | partyIndex: partyIndex as PartyIndex, 23 | inputShared, 24 | })); 25 | }); 26 | } 27 | 28 | export async function inWorkingDir(f: (workingDir: string) => Promise) { 29 | const id = crypto.randomUUID(); 30 | const workingDir = path.join(__dirname, "work-dirs", id); 31 | fs.mkdirSync(workingDir, { recursive: true }); 32 | try { 33 | return await f(workingDir); 34 | } finally { 35 | fs.rmSync(workingDir, { recursive: true }); 36 | } 37 | } 38 | 39 | export const makeRunCommand = 40 | (cwd?: string) => 41 | async (command: string, args: (string | number)[] = []) => { 42 | const { spawn } = await import("node:child_process"); 43 | 44 | const spawned = spawn( 45 | command, 46 | args.map((arg) => arg.toString()), 47 | { cwd }, 48 | ); 49 | spawned.stdout.on("data", (data) => { 50 | process.stdout.write(data); 51 | }); 52 | 53 | spawned.stderr.on("data", (data) => { 54 | process.stderr.write(data); 55 | }); 56 | 57 | return await new Promise((resolve, reject) => { 58 | spawned.on("close", (code: number) => { 59 | if (code !== 0) { 60 | reject(new Error(`Process exited with code ${code}`)); 61 | return; 62 | } 63 | 64 | resolve(); 65 | }); 66 | 67 | spawned.on("error", (err) => { 68 | reject( 69 | new Error( 70 | `Error executing command \`${ 71 | command + " " + args.join(" ") 72 | }\`: ${err.message}`, 73 | ), 74 | ); 75 | }); 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /apps/interface/src/lib/components/SendForm.svelte: -------------------------------------------------------------------------------- 1 | 50 | 51 | 52 | {#snippet children(form, formData)} 53 | 54 | 55 | Token 56 | 57 | 58 | 59 | 60 | 61 | 62 | Amount 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | To 72 | 73 | 74 | 75 | 76 | Send 77 | {/snippet} 78 | 79 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/toast/toast.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, SvelteComponent } from "svelte"; 2 | import frenchToast, { 3 | type Toast as FrenchToast, 4 | type Renderable, 5 | type ToastOptions, 6 | } from "svelte-french-toast"; 7 | import { errorToString } from "../../utils.js"; 8 | import ConfirmToast from "./ConfirmToast.svelte"; 9 | 10 | export { Toaster } from "svelte-french-toast"; 11 | 12 | export type Toast = FrenchToast & { props: TProps }; 13 | 14 | const duration = 5_000; 15 | export const toast = { 16 | success: (msg: Renderable, options?: ToastOptions) => { 17 | return frenchToast.success(msg, { duration, ...options }); 18 | }, 19 | log(msg: Renderable, options?: ToastOptions) { 20 | if (typeof msg === "string") { 21 | console.log(msg); 22 | } 23 | return frenchToast(msg, { duration, ...options }); 24 | }, 25 | error(error: unknown, options?: ToastOptions) { 26 | console.error(error); 27 | const errStr = errorToString(error); 28 | return frenchToast.error(errStr, { duration, ...options }); 29 | }, 30 | promise: async ( 31 | promise: Promise, 32 | msgs: string | Parameters[1], 33 | options?: Parameters[2], 34 | ) => { 35 | if (typeof msgs === "string") { 36 | msgs = { 37 | loading: `Loading ${msgs}...`, 38 | success: `Loaded ${msgs}!`, 39 | error: `Error loading ${msgs}`, 40 | }; 41 | } 42 | if (typeof msgs.loading === "string") { 43 | console.log(msgs.loading); 44 | } 45 | try { 46 | const value = await frenchToast.promise(promise, msgs, { 47 | ...options, 48 | success: { duration }, 49 | error: { duration }, 50 | }); 51 | console.log( 52 | typeof msgs.success !== "function" 53 | ? msgs.success 54 | : `Success ${msgs.loading}`, 55 | ); 56 | return value; 57 | } catch (err) { 58 | console.error( 59 | typeof msgs.error !== "function" ? msgs.error : `Error ${msgs.loading}`, 60 | ); 61 | console.error(err); 62 | throw err; 63 | } 64 | }, 65 | confirm( 66 | props: Omit["toast"]["props"], "onresult">, 67 | { duration = Infinity, ...options }: ToastOptions = {}, 68 | ) { 69 | return new Promise((resolve) => { 70 | toastCustomTypesafe(ConfirmToast, { 71 | ...options, 72 | duration, 73 | props: { 74 | ...props, 75 | onresult: resolve, 76 | }, 77 | }); 78 | }); 79 | }, 80 | custom: toastCustomTypesafe, 81 | dismiss: frenchToast.dismiss.bind(frenchToast), 82 | }; 83 | 84 | function toastCustomTypesafe< 85 | T extends typeof SvelteComponent<{ toast: Toast }>, 86 | >( 87 | component: T, 88 | options: ToastOptions & { 89 | props: ComponentProps>["toast"]["props"]; 90 | }, 91 | ) { 92 | return frenchToast(component, options); 93 | } 94 | -------------------------------------------------------------------------------- /packages/contracts/sdk/NativeUltraHonkBackend.ts: -------------------------------------------------------------------------------- 1 | import type { ProofData } from "@aztec/bb.js"; 2 | import type { CompiledCircuit } from "@noir-lang/noir_js"; 3 | import { spawn } from "node:child_process"; 4 | import fs from "node:fs"; 5 | import path from "node:path"; 6 | import { readNativeHonkProof } from "./utils.js"; 7 | 8 | export class NativeUltraHonkBackend { 9 | constructor( 10 | readonly bbPath: string, 11 | readonly circuit: CompiledCircuit, 12 | ) { 13 | this.bbPath = path.normalize(bbPath); 14 | } 15 | 16 | async generateProof(witness: Uint8Array) { 17 | const targetDir = await this.#makeTargetDir(); 18 | 19 | const circuitHash = await this.#getCircuitHash(); 20 | const witnessHash = await this.#getWitnessHash(witness); 21 | 22 | const circuitJsonPath = path.join(targetDir, `${circuitHash}_circuit.json`); 23 | const witnessOutputPath = path.join( 24 | targetDir, 25 | `${circuitHash}_${witnessHash}_witness.gz`, 26 | ); 27 | const proofOutputPath = path.join( 28 | targetDir, 29 | `${circuitHash}_${witnessHash}_proof`, 30 | ); 31 | 32 | fs.writeFileSync(circuitJsonPath, JSON.stringify(this.circuit)); 33 | fs.writeFileSync(witnessOutputPath, witness); 34 | const args = [ 35 | "prove", 36 | "--scheme", 37 | "ultra_honk", 38 | "-b", 39 | circuitJsonPath, 40 | "-w", 41 | witnessOutputPath, 42 | "-o", 43 | proofOutputPath, 44 | "--oracle_hash", 45 | "keccak", 46 | ]; 47 | 48 | const bbProcess = spawn(this.bbPath, args); 49 | bbProcess.stdout.on("data", (data: string) => { 50 | console.log(`stdout: ${data}`); 51 | }); 52 | 53 | bbProcess.stderr.on("data", (data: string) => { 54 | console.error(`stderr: ${data}`); 55 | }); 56 | 57 | return await new Promise((resolve, reject) => { 58 | bbProcess.on("close", (code: number) => { 59 | if (code !== 0) { 60 | reject(new Error(`Process exited with code ${code}`)); 61 | return; 62 | } 63 | resolve(readNativeHonkProof(proofOutputPath)); 64 | }); 65 | 66 | bbProcess.on("error", (err) => { 67 | reject(new Error(`Failed to start process: ${err.message}`)); 68 | }); 69 | }); 70 | } 71 | 72 | async #getCircuitHash() { 73 | const input = new TextEncoder().encode(JSON.stringify(this.circuit)); 74 | return ( 75 | "0x" + 76 | Buffer.from(await crypto.subtle.digest("SHA-256", input)).toString("hex") 77 | ); 78 | } 79 | 80 | async #getWitnessHash(witness: Uint8Array) { 81 | return ( 82 | "0x" + 83 | Buffer.from(await crypto.subtle.digest("SHA-256", witness)).toString( 84 | "hex", 85 | ) 86 | ); 87 | } 88 | 89 | async #makeTargetDir() { 90 | const dirname = typeof __dirname === "string" ? __dirname : ""; 91 | const targetDir = path.normalize(path.join(dirname, "target")); 92 | fs.mkdirSync(targetDir, { recursive: true }); 93 | return targetDir; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/utils/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { CurrencyAmount, type Currency } from "@uniswap/sdk-core"; 2 | import { ethers } from "ethers"; 3 | import ms from "ms"; 4 | import { assert } from "ts-essentials"; 5 | import { z } from "zod"; 6 | 7 | export { joinURL as joinUrl } from "ufo"; 8 | 9 | export function sleep(duration: number | string) { 10 | const durationMs = typeof duration === "number" ? duration : ms(duration); 11 | return new Promise((resolve) => setTimeout(resolve, durationMs)); 12 | } 13 | 14 | export function arrayPadEnd( 15 | array: T[], 16 | targetLength: number, 17 | padValue: T, 18 | ): T[] { 19 | assert(array.length <= targetLength, "arrayPadEnd: array too long"); 20 | const newArray = array.slice(); 21 | while (newArray.length < targetLength) { 22 | newArray.push(padValue); 23 | } 24 | return newArray; 25 | } 26 | 27 | export function shortAddress(address: string) { 28 | return `${address.slice(0, 6)}...${address.slice(-4)}`; 29 | } 30 | 31 | export function assertConnected(account: unknown): asserts account { 32 | assert(account, "account is not connected"); 33 | } 34 | 35 | export function lazyValue(value: () => T): () => T { 36 | let initialized = false; 37 | let result: T; 38 | return () => { 39 | if (!initialized) { 40 | initialized = true; 41 | result = value(); 42 | } 43 | return result; 44 | }; 45 | } 46 | 47 | export function isAddressEqual(a: string, b: string) { 48 | return a.toLowerCase() === b.toLowerCase(); 49 | } 50 | 51 | /** 52 | * Prefer this over `value.toString()` because it's typesafe(you can't accidentally call .toString() on another type when refactoring) 53 | */ 54 | export function bigIntToString(value: bigint) { 55 | return value.toString(); 56 | } 57 | 58 | export function errorToString(error: any) { 59 | return String(error?.message || error); 60 | } 61 | 62 | export function removePrefixOrThrow(str: string, prefix: string) { 63 | if (!str.startsWith(prefix)) { 64 | throw new Error(`string does not start with "${prefix}"`); 65 | } 66 | 67 | return str.slice(prefix.length); 68 | } 69 | 70 | /** 71 | * Converts user input to a CurrencyAmount. Takes into account the token's decimals. 72 | * E.g., for a token with 9 decimals, `"1.23"` becomes `CurrencyAmount(1230000000)`. 73 | */ 74 | export function parseCurrencyAmount( 75 | token: T, 76 | userAmount: string | number, 77 | ): CurrencyAmount { 78 | let rawAmount: bigint; 79 | try { 80 | rawAmount = ethers.parseUnits(userAmount.toString(), token.decimals); 81 | } catch (e) { 82 | throw new Error(`${userAmount} is too small or too large.`); 83 | } 84 | if (rawAmount === 0n && userAmount !== "0") { 85 | throw new Error(`${userAmount} is too small`); 86 | } 87 | return CurrencyAmount.fromRawAmount(token, rawAmount.toString()); 88 | } 89 | 90 | /** 91 | * Converts a CurrencyAmount to a string that can be used in calculations. 92 | */ 93 | export function formatCurrencyAmount(amount: CurrencyAmount): string { 94 | return ethers.formatUnits( 95 | BigInt(amount.quotient.toString()), 96 | amount.currency.decimals, 97 | ); 98 | } 99 | 100 | export const DECIMAL_INPUT_REGEX = String.raw`^[0-9]*[.,]?[0-9]*$`; 101 | 102 | export const CurrencyAmountInputSchema = z 103 | .string() 104 | .regex(new RegExp(DECIMAL_INPUT_REGEX)) 105 | .refine((value) => Number(value) > 0, { 106 | message: "Must be greater than 0", 107 | }); 108 | 109 | export { assert, UnreachableCaseError } from "ts-essentials"; 110 | 111 | export function iife(fn: () => T): T { 112 | return fn(); 113 | } 114 | -------------------------------------------------------------------------------- /packages/contracts/noir/common/src/lib.nr: -------------------------------------------------------------------------------- 1 | use protocol_types::hash::poseidon2_hash_with_separator; 2 | 3 | mod context; 4 | mod uint253; 5 | mod erc20_note; 6 | pub(crate) mod note; 7 | mod owned_note; 8 | 9 | pub use context::{Context, Result}; 10 | pub use note::{compute_note_hash, Note}; 11 | pub use owned_note::{NoteConsumptionInputs, OwnedNote}; 12 | pub use protocol_types::{address::EthAddress, traits::Serialize}; 13 | 14 | // Note: keep in sync with other languages 15 | pub global NOTE_HASH_TREE_HEIGHT: u32 = 40; 16 | // Note: keep in sync with other languages 17 | pub global NOTE_HASH_SUBTREE_HEIGHT: u32 = 6; 18 | // Note: keep in sync with other languages 19 | pub global NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH: u32 = 20 | NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT; 21 | // Note: keep in sync with other languages 22 | pub global MAX_NOTES_PER_ROLLUP: u32 = 64; // 2^NOTE_HASH_SUBTREE_HEIGHT 23 | 24 | // Note: keep in sync with other languages 25 | pub global NULLIFIER_TREE_HEIGHT: u32 = 40; 26 | // Note: keep in sync with other languages 27 | pub global NULLIFIER_SUBTREE_HEIGHT: u32 = 6; 28 | // Note: keep in sync with other languages 29 | pub global MAX_NULLIFIERS_PER_ROLLUP: u32 = 64; // 2^NULLIFIER_SUBTREE_HEIGHT 30 | // Note: keep in sync with other languages 31 | pub global NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH: u32 = 32 | NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT; 33 | 34 | // Note: keep in sync with other languages 35 | pub global MAX_TOKENS_IN_PER_EXECUTION: u32 = 4; 36 | // Note: keep in sync with other languages 37 | pub global MAX_TOKENS_OUT_PER_EXECUTION: u32 = 4; 38 | 39 | // Note: keep in sync with other languages 40 | pub global GENERATOR_INDEX__WA_ADDRESS: Field = 1; 41 | // Note: keep in sync with other languages 42 | pub global GENERATOR_INDEX__NOTE_NULLIFIER: Field = 2; 43 | // Note: keep in sync with other languages 44 | pub global GENERATOR_INDEX__NOTE_HASH: Field = 3; 45 | 46 | // Note: keep in sync with other languages 47 | pub global U256_LIMBS: u32 = 3; 48 | 49 | pub type U256 = uint253::U253; 50 | 51 | /// User address within the rollup 52 | #[derive(Eq, Serialize)] 53 | pub struct WaAddress { 54 | inner: Field, 55 | } 56 | 57 | impl WaAddress { 58 | pub fn zero() -> Self { 59 | Self { inner: 0 } 60 | } 61 | 62 | pub fn from_secret_key(secret_key: Field) -> Self { 63 | // TODO(security): wtf is this? Use an actual private/public key cryptography 64 | let inner = poseidon2_hash_with_separator([secret_key], GENERATOR_INDEX__WA_ADDRESS); 65 | Self { inner } 66 | } 67 | } 68 | 69 | #[derive(Eq)] 70 | pub struct TokenAmount { 71 | pub token: crate::EthAddress, 72 | pub amount: crate::U256, 73 | } 74 | 75 | impl TokenAmount { 76 | pub fn zero(token: crate::EthAddress) -> Self { 77 | Self { token, amount: U256::zero() } 78 | } 79 | 80 | fn _check(self, other: Self) { 81 | assert_eq(self.token, other.token, "invalid token"); 82 | } 83 | } 84 | 85 | impl std::ops::Add for TokenAmount { 86 | fn add(self, other: Self) -> Self { 87 | self._check(other); 88 | Self { token: self.token, amount: self.amount + other.amount } 89 | } 90 | } 91 | 92 | impl std::ops::Sub for TokenAmount { 93 | fn sub(self, other: Self) -> Self { 94 | self._check(other); 95 | Self { token: self.token, amount: self.amount - other.amount } 96 | } 97 | } 98 | 99 | impl std::cmp::Ord for TokenAmount { 100 | fn cmp(self, other: Self) -> std::cmp::Ordering { 101 | self._check(other); 102 | self.amount.cmp(other.amount) 103 | } 104 | } 105 | 106 | pub struct TreeRoots { 107 | pub note_hash_root: Field, 108 | } 109 | -------------------------------------------------------------------------------- /packages/contracts/sdk/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Fr } from "@aztec/aztec.js"; 2 | import type { InputMap } from "@noir-lang/noir_js"; 3 | import { ethers } from "ethers"; 4 | import { chunk } from "lodash-es"; 5 | import fs from "node:fs"; 6 | import path from "node:path"; 7 | import { Hex } from "ox"; 8 | import { assert } from "ts-essentials"; 9 | import type { NoirAndBackend } from "./sdk.js"; 10 | 11 | export function printPublicInputs(publicInputs: string[]) { 12 | console.log("publicInputs js", publicInputs.length); 13 | for (const publicInput of publicInputs) { 14 | console.log(publicInput); 15 | } 16 | console.log(); 17 | } 18 | 19 | export async function keccak256ToFr(value: string): Promise { 20 | const { Fr } = await import("@aztec/aztec.js"); 21 | // @ts-ignore 22 | const { truncateAndPad } = await import("@aztec/foundation/serialize"); 23 | const hash = ethers.keccak256(value); 24 | return Fr.fromBuffer(truncateAndPad(Buffer.from(ethers.getBytes(hash)))); 25 | } 26 | 27 | function splitBigIntToLimbs( 28 | bigInt: bigint, 29 | limbSize: number, 30 | numLimbs: number, 31 | ): bigint[] { 32 | const limbs: bigint[] = []; 33 | const mask = (1n << BigInt(limbSize)) - 1n; 34 | for (let i = 0; i < numLimbs; i++) { 35 | const limb = (bigInt / (1n << (BigInt(i) * BigInt(limbSize)))) & mask; 36 | limbs.push(limb); 37 | } 38 | return limbs; 39 | } 40 | 41 | function unsplitBigIntFromLimbs(limbs: bigint[], limbSize: number): bigint { 42 | let bigInt = 0n; 43 | for (let i = 0; i < limbs.length; i++) { 44 | bigInt += limbs[i]! << (BigInt(i) * BigInt(limbSize)); 45 | } 46 | return bigInt; 47 | } 48 | 49 | // Note: keep in sync with other languages 50 | export const U256_LIMBS = 3; 51 | // Note: keep in sync with other languages 52 | export const U256_LIMB_SIZE = 120; 53 | 54 | export function toNoirU256(value: bigint) { 55 | return { value: value.toString() }; 56 | // assert(value >= 0n && value < 2n ** 256n, "invalid U256 value"); 57 | // const limbs = splitBigIntToLimbs(value, U256_LIMB_SIZE, U256_LIMBS).map( 58 | // (x) => "0x" + x.toString(16), 59 | // ); 60 | // return { limbs }; 61 | } 62 | 63 | export function fromNoirU256(value: { limbs: (bigint | string)[] }) { 64 | assert(value.limbs.length === U256_LIMBS, "invalid U256 limbs"); 65 | return unsplitBigIntFromLimbs( 66 | value.limbs.map((x) => BigInt(x)), 67 | 120, 68 | ); 69 | } 70 | 71 | export async function prove( 72 | name: string, 73 | { noir, backend }: NoirAndBackend, 74 | input: InputMap, 75 | ) { 76 | console.time(`${name} generateProof`); 77 | const { witness, returnValue } = await noir.execute(input); 78 | let { proof, publicInputs } = await backend.generateProof(witness, { 79 | keccak: true, 80 | }); 81 | console.timeEnd(`${name} generateProof`); 82 | return { proof, witness, returnValue, publicInputs }; 83 | } 84 | 85 | export function promiseWithResolvers(): { 86 | promise: Promise; 87 | resolve: (value: T) => void; 88 | reject: (reason: unknown) => void; 89 | } { 90 | const ret: any = {}; 91 | ret.promise = new Promise((resolve, reject) => { 92 | ret.resolve = resolve; 93 | ret.reject = reject; 94 | }); 95 | return ret; 96 | } 97 | 98 | export function readNativeHonkProof(pathToProofDir: string) { 99 | const proof = fs.readFileSync(path.join(pathToProofDir, "proof")); 100 | const publicInputs = fs.readFileSync( 101 | path.join(pathToProofDir, "public_inputs"), 102 | ); 103 | assert( 104 | publicInputs.length % 32 === 0, 105 | "publicInputs length must be divisible by 32", 106 | ); 107 | return { 108 | proof, 109 | // TODO: not sure if this publicInputs decoding is correct 110 | publicInputs: chunk(Array.from(publicInputs), 32).map((x) => 111 | Hex.fromBytes(Uint8Array.from(x)), 112 | ), 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /apps/interface/src/lib/ROUTES.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was generated by 'vite-plugin-kit-routes' 4 | * 5 | * >> DO NOT EDIT THIS FILE MANUALLY << 6 | */ 7 | 8 | /** 9 | * PAGES 10 | */ 11 | const PAGES = { 12 | "/": `/`, 13 | "/connect": `/connect` 14 | } 15 | 16 | /** 17 | * SERVERS 18 | */ 19 | const SERVERS = { 20 | "POST /api/rollup": `/api/rollup`, 21 | "POST /api/trees": `/api/trees` 22 | } 23 | 24 | /** 25 | * ACTIONS 26 | */ 27 | const ACTIONS = { 28 | 29 | } 30 | 31 | /** 32 | * LINKS 33 | */ 34 | const LINKS = { 35 | 36 | } 37 | 38 | type ParamValue = string | number | undefined 39 | 40 | /** 41 | * Append search params to a string 42 | */ 43 | export const appendSp = (sp?: Record, prefix: '?' | '&' = '?') => { 44 | if (sp === undefined) return '' 45 | 46 | const params = new URLSearchParams() 47 | const append = (n: string, v: ParamValue) => { 48 | if (v !== undefined) { 49 | params.append(n, String(v)) 50 | } 51 | } 52 | 53 | for (const [name, val] of Object.entries(sp)) { 54 | if (Array.isArray(val)) { 55 | for (const v of val) { 56 | append(name, v) 57 | } 58 | } else { 59 | append(name, val) 60 | } 61 | } 62 | 63 | const formatted = params.toString() 64 | if (formatted) { 65 | return `${prefix}${formatted}` 66 | } 67 | return '' 68 | } 69 | 70 | /** 71 | * get the current search params 72 | * 73 | * Could be use like this: 74 | * ``` 75 | * route("/cities", { page: 2 }, { ...currentSP() }) 76 | * ``` 77 | */ 78 | export const currentSp = () => { 79 | const params = new URLSearchParams(window.location.search) 80 | const record: Record = {} 81 | for (const [key, value] of params.entries()) { 82 | record[key] = value 83 | } 84 | return record 85 | } 86 | 87 | // route function helpers 88 | type NonFunctionKeys = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T] 89 | type FunctionKeys = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] 90 | type FunctionParams = T extends (...args: infer P) => any ? P : never 91 | 92 | const AllObjs = { ...PAGES, ...ACTIONS, ...SERVERS, ...LINKS } 93 | type AllTypes = typeof AllObjs 94 | 95 | export type Routes = keyof AllTypes extends `${string}/${infer Route}` ? `/${Route}` : keyof AllTypes 96 | export const routes = [ 97 | ...new Set(Object.keys(AllObjs).map((route) => /^\/.*|[^ ]?\/.*$/.exec(route)?.[0] ?? route)), 98 | ] as Routes[] 99 | 100 | /** 101 | * To be used like this: 102 | * ```ts 103 | * import { route } from './ROUTES' 104 | * 105 | * route('site_id', { id: 1 }) 106 | * ``` 107 | */ 108 | export function route>(key: T, ...params: FunctionParams): string 109 | export function route>(key: T): string 110 | export function route(key: T, ...params: any[]): string { 111 | if (AllObjs[key] as any instanceof Function) { 112 | const element = (AllObjs as any)[key] as (...args: any[]) => string 113 | return element(...params) 114 | } else { 115 | return AllObjs[key] as string 116 | } 117 | } 118 | 119 | /** 120 | * Add this type as a generic of the vite plugin `kitRoutes`. 121 | * 122 | * Full example: 123 | * ```ts 124 | * import type { KIT_ROUTES } from './ROUTES' 125 | * import { kitRoutes } from 'vite-plugin-kit-routes' 126 | * 127 | * kitRoutes({ 128 | * PAGES: { 129 | * // here, key of object will be typed! 130 | * } 131 | * }) 132 | * ``` 133 | */ 134 | export type KIT_ROUTES = { 135 | PAGES: { '/': never, '/connect': never } 136 | SERVERS: { 'POST /api/rollup': never, 'POST /api/trees': never } 137 | ACTIONS: Record 138 | LINKS: Record 139 | Params: Record 140 | } 141 | -------------------------------------------------------------------------------- /packages/contracts/sdk/NonMembershipTree.ts: -------------------------------------------------------------------------------- 1 | import type { Fr } from "@aztec/aztec.js"; 2 | import type { StandardIndexedTree } from "@aztec/merkle-tree"; 3 | import { ethers } from "ethers"; 4 | import { assert } from "ts-essentials"; 5 | import { 6 | INCLUDE_UNCOMMITTED, 7 | NULLIFIER_SUBTREE_HEIGHT, 8 | } from "./PoolErc20Service"; 9 | 10 | export class NonMembershipTree { 11 | private constructor(readonly _tree: StandardIndexedTree) {} 12 | 13 | static async new(leaves: Fr[], depth: number) { 14 | const { newTree, Poseidon, StandardIndexedTreeWithAppend } = await import( 15 | "@aztec/merkle-tree" 16 | ); 17 | const { NullifierLeaf, NullifierLeafPreimage } = await import( 18 | // @ts-ignore hardhat does not support ESM 19 | "@aztec/stdlib/trees" 20 | ); 21 | // @ts-ignore hardhat does not support ESM 22 | const { AztecLmdbStore } = await import("@aztec/kv-store/lmdb"); 23 | const db = AztecLmdbStore.open(); 24 | class NullifierTree extends StandardIndexedTreeWithAppend { 25 | constructor( 26 | store: any, 27 | hasher: any, 28 | name: string, 29 | depth: number, 30 | size: bigint = 0n, 31 | _noop: any, 32 | root?: Buffer, 33 | ) { 34 | super( 35 | store, 36 | hasher, 37 | name, 38 | depth, 39 | size, 40 | NullifierLeafPreimage, 41 | NullifierLeaf, 42 | root, 43 | ); 44 | } 45 | } 46 | const tree = await newTree( 47 | NullifierTree, 48 | db, 49 | new Poseidon(), 50 | "my-tree", 51 | { fromBuffer: (b: Buffer) => b }, 52 | depth, 53 | ); 54 | await tree.batchInsert( 55 | leaves.map((l) => l.toBuffer()), 56 | NULLIFIER_SUBTREE_HEIGHT, 57 | ); 58 | await tree.commit(); 59 | return new NonMembershipTree(tree); 60 | } 61 | 62 | getRoot() { 63 | return ethers.hexlify(this._tree.getRoot(INCLUDE_UNCOMMITTED)); 64 | } 65 | 66 | getDepth() { 67 | return this._tree.getDepth(); 68 | } 69 | 70 | async getNonMembershipWitness(key: Fr) { 71 | const keyAsBigInt = key.toBigInt(); 72 | const lowLeafIndexData = this._tree.findIndexOfPreviousKey( 73 | keyAsBigInt, 74 | INCLUDE_UNCOMMITTED, 75 | ); 76 | assert(lowLeafIndexData != null, `low leaf not found for key: "${key}"`); 77 | assert(!lowLeafIndexData.alreadyPresent, `key already present: "${key}"`); 78 | const lowLeafIndex = lowLeafIndexData.index; 79 | const lowLeafSiblingPath = await this._tree.getSiblingPath( 80 | lowLeafIndex, 81 | INCLUDE_UNCOMMITTED, 82 | ); 83 | const lowLeafPreimage = this._tree.getLatestLeafPreimageCopy( 84 | lowLeafIndex, 85 | INCLUDE_UNCOMMITTED, 86 | ); 87 | assert(lowLeafPreimage != null, "leafPreimage not found"); 88 | 89 | const witness = { 90 | key: bigIntToString(keyAsBigInt), 91 | low_leaf_preimage: { 92 | nullifier: bigIntToString(lowLeafPreimage.getKey()), 93 | next_nullifier: bigIntToString(lowLeafPreimage.getNextKey()), 94 | next_index: bigIntToString(lowLeafPreimage.getNextIndex()), 95 | }, 96 | low_leaf_membership_witness: { 97 | leaf_index: bigIntToString(lowLeafIndex), 98 | sibling_path: lowLeafSiblingPath 99 | .toFields() 100 | .map((x: Fr) => bigIntToString(x.toBigInt())), 101 | }, 102 | }; 103 | return witness; 104 | } 105 | 106 | async findLeafIndex(key: Fr) { 107 | return await this._tree.findLeafIndex(key.toBuffer(), INCLUDE_UNCOMMITTED); 108 | } 109 | } 110 | 111 | export interface NonMembershipWitness 112 | extends Awaited> {} 113 | 114 | /** 115 | * Type safe wrapper around `BigInt.toString`, so you can't pass a non bigint value. 116 | */ 117 | function bigIntToString(x: bigint) { 118 | return x.toString(); 119 | } 120 | -------------------------------------------------------------------------------- /packages/contracts/contracts/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.23; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {Fr, FrLib} from "./Fr.sol"; 6 | import {console} from "hardhat/console.sol"; 7 | 8 | using FrLib for Fr; 9 | 10 | // Note: keep in sync with other languages 11 | uint32 constant MAX_TOKENS_IN_PER_EXECUTION = 4; 12 | // Note: keep in sync with other languages 13 | uint32 constant MAX_TOKENS_OUT_PER_EXECUTION = 4; 14 | 15 | uint256 constant U256_LIMBS = 3; 16 | uint256 constant U256_LIMB_SIZE = 120; 17 | 18 | function toNoirU256(uint256 value) pure returns (uint256[U256_LIMBS] memory) { 19 | uint256[U256_LIMBS] memory limbs; 20 | uint256 mask = (1 << U256_LIMB_SIZE) - 1; 21 | for (uint256 i = 0; i < limbs.length; i++) { 22 | limbs[i] = (value / (1 << (i * U256_LIMB_SIZE))) & mask; 23 | } 24 | return limbs; 25 | } 26 | 27 | function castAddressToBytes32(address x) pure returns (bytes32) { 28 | return bytes32(uint256(uint160(address(x)))); 29 | } 30 | 31 | struct NoteInput { 32 | bytes32 noteHash; 33 | // TODO(security): constrain note encryption in Noir 34 | bytes encryptedNote; 35 | } 36 | 37 | struct Call { 38 | address to; 39 | bytes data; 40 | // TODO: support ETH 41 | // uint256 value; 42 | } 43 | 44 | struct TokenAmount { 45 | IERC20 token; 46 | uint256 amount; 47 | } 48 | 49 | struct Execution { 50 | Call[] calls; 51 | TokenAmount[MAX_TOKENS_IN_PER_EXECUTION] amountsIn; 52 | TokenAmount[MAX_TOKENS_OUT_PER_EXECUTION] amountsOut; 53 | } 54 | 55 | library PublicInputs { 56 | struct Type { 57 | bytes32[] publicInputs; 58 | uint256 index; 59 | } 60 | 61 | function create(uint256 len) internal pure returns (Type memory) { 62 | Type memory publicInputs; 63 | publicInputs.publicInputs = new bytes32[](len); 64 | return publicInputs; 65 | } 66 | 67 | // TODO(security): remove this function 68 | function push(Type memory publicInputs, bytes32 value) internal pure { 69 | publicInputs.publicInputs[publicInputs.index] = value; 70 | unchecked { 71 | publicInputs.index++; 72 | } 73 | } 74 | 75 | // TODO(security): remove this function 76 | function push(Type memory publicInputs, uint256 value) internal pure { 77 | push(publicInputs, bytes32(value)); 78 | } 79 | 80 | function push(Type memory publicInputs, address value) internal pure { 81 | push(publicInputs, castAddressToBytes32(value)); 82 | } 83 | 84 | function push(Type memory publicInputs, Fr value) internal pure { 85 | push(publicInputs, value.toBytes32()); 86 | } 87 | 88 | function pushUint256Limbs( 89 | Type memory publicInputs, 90 | uint256 value 91 | ) internal pure { 92 | push(publicInputs, value); 93 | // uint256[U256_LIMBS] memory limbs = toNoirU256(value); 94 | // for (uint256 i = 0; i < limbs.length; i++) { 95 | // push(publicInputs, limbs[i]); 96 | // } 97 | } 98 | 99 | function finish( 100 | Type memory publicInputs 101 | ) internal pure returns (bytes32[] memory) { 102 | require( 103 | publicInputs.index == publicInputs.publicInputs.length, 104 | "Did not fill all public inputs" 105 | ); 106 | return publicInputs.publicInputs; 107 | } 108 | 109 | function print(Type memory pi) internal pure { 110 | bytes32[] memory publicInputs = PublicInputs.finish(pi); 111 | console.log("publicInputs sol", publicInputs.length); 112 | for (uint256 i = 0; i < publicInputs.length; i++) { 113 | console.logBytes32(publicInputs[i]); 114 | } 115 | console.log(); 116 | } 117 | } 118 | 119 | struct AppendOnlyTreeSnapshot { 120 | bytes32 root; 121 | uint32 nextAvailableLeafIndex; 122 | } 123 | 124 | interface IVerifier { 125 | function verify( 126 | bytes calldata proof, 127 | bytes32[] calldata publicInputs 128 | ) external view returns (bool); 129 | } 130 | -------------------------------------------------------------------------------- /packages/contracts/noir/rollup/src/main.nr: -------------------------------------------------------------------------------- 1 | use protocol_types::{ 2 | abis::{ 3 | append_only_tree_snapshot::AppendOnlyTreeSnapshot, 4 | nullifier_leaf_preimage::NullifierLeafPreimage, 5 | }, 6 | merkle_tree, 7 | }; 8 | 9 | fn main( 10 | // note hashes 11 | new_note_hashes: pub [Field; common::MAX_NOTES_PER_ROLLUP], 12 | note_hash_subtree_sibling_path: [Field; common::NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH], 13 | note_hash_tree: pub AppendOnlyTreeSnapshot, 14 | expected_new_note_hash_tree: pub AppendOnlyTreeSnapshot, 15 | // nullifiers 16 | new_nullifiers: pub [Field; common::MAX_NULLIFIERS_PER_ROLLUP], 17 | sorted_nullifiers: [Field; common::MAX_NULLIFIERS_PER_ROLLUP], 18 | sorted_nullifiers_indexes: [u32; common::MAX_NULLIFIERS_PER_ROLLUP], 19 | nullifier_subtree_sibling_path: [Field; common::NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], 20 | nullifier_low_leaf_preimages: [NullifierLeafPreimage; common::MAX_NOTES_PER_ROLLUP], 21 | nullifier_low_leaf_membership_witnesses: [merkle_tree::MembershipWitness; common::MAX_NOTES_PER_ROLLUP], 22 | nullifier_tree: pub AppendOnlyTreeSnapshot, 23 | expected_new_nullifier_tree: pub AppendOnlyTreeSnapshot, 24 | ) { 25 | let new_note_hash_tree = compute_new_note_hash_tree_snapshot::( 26 | note_hash_tree, 27 | new_note_hashes, 28 | note_hash_subtree_sibling_path, 29 | ); 30 | assert(new_note_hash_tree == expected_new_note_hash_tree, "Invalid new note hash tree"); 31 | 32 | // Safety: TODO(security): constrain this call. This is unconstrained because it's very expensive, so we mark it unconstrained while in active development 33 | let new_nullifier_tree = unsafe { 34 | compute_new_nullifier_tree_snapshot( 35 | nullifier_tree, 36 | new_nullifiers, 37 | sorted_nullifiers, 38 | sorted_nullifiers_indexes, 39 | nullifier_subtree_sibling_path, 40 | nullifier_low_leaf_preimages, 41 | nullifier_low_leaf_membership_witnesses, 42 | ) 43 | }; 44 | assert(new_nullifier_tree == expected_new_nullifier_tree, "Invalid new nullifier tree"); 45 | } 46 | 47 | fn compute_new_note_hash_tree_snapshot( 48 | tree: AppendOnlyTreeSnapshot, 49 | new_leaves: [Field; LEAVES_LENGTH], 50 | subtree_sibling_path: [Field; SUBTREE_SIBLING_PATH_LENGTH], 51 | ) -> AppendOnlyTreeSnapshot { 52 | assert(LEAVES_LENGTH == 1 << SUBTREE_HEIGHT as u8, "Invalid subtree height"); 53 | let empty_commitments_subtree_root = merkle_tree::calculate_empty_tree_root(SUBTREE_HEIGHT); 54 | let commitments_tree_subroot = merkle_tree::calculate_subtree_root(new_leaves); 55 | 56 | // TODO(security): is there a need to check if note hashes are not in the tree before inserting? 57 | merkle_tree::append_only_tree::insert_subtree_to_snapshot_tree( 58 | tree, 59 | subtree_sibling_path, 60 | empty_commitments_subtree_root, 61 | commitments_tree_subroot, 62 | SUBTREE_HEIGHT as u8, 63 | ) 64 | } 65 | 66 | // TODO(security): remove `unconstrained`. It's very expensive to prove in tests, so we mark it unconstrained while in active development 67 | unconstrained fn compute_new_nullifier_tree_snapshot( 68 | tree: AppendOnlyTreeSnapshot, 69 | new_nullifiers: [Field; common::MAX_NULLIFIERS_PER_ROLLUP], 70 | sorted_nullifiers: [Field; common::MAX_NULLIFIERS_PER_ROLLUP], 71 | sorted_nullifiers_indexes: [u32; common::MAX_NULLIFIERS_PER_ROLLUP], 72 | subtree_sibling_path: [Field; common::NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], 73 | nullifier_low_leaf_preimages: [NullifierLeafPreimage; common::MAX_NOTES_PER_ROLLUP], 74 | nullifier_low_leaf_membership_witnesses: [merkle_tree::MembershipWitness; common::MAX_NOTES_PER_ROLLUP], 75 | ) -> AppendOnlyTreeSnapshot { 76 | merkle_tree::indexed_tree::batch_insert::<_, _, _, _, common::NULLIFIER_SUBTREE_HEIGHT, common::NULLIFIER_TREE_HEIGHT>( 77 | tree, 78 | new_nullifiers, 79 | sorted_nullifiers, 80 | sorted_nullifiers_indexes, 81 | subtree_sibling_path, 82 | nullifier_low_leaf_preimages, 83 | nullifier_low_leaf_membership_witnesses, 84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /packages/contracts/noir/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | source timer.sh 6 | 7 | nargo compile --silence-warnings 8 | echo "Compiled" 9 | 10 | CIRCUIT_NAME=lob_router_swap 11 | CIRCUIT=target/$CIRCUIT_NAME.json 12 | 13 | # merge Prover1.toml and Prover2.toml into Prover.toml 14 | # Convert TOML to JSON 15 | dasel -f "$CIRCUIT_NAME/Prover1.toml" -r toml -w json >prover1.json 16 | dasel -f "$CIRCUIT_NAME/Prover2.toml" -r toml -w json >prover2.json 17 | # Merge JSON with jq 18 | jq -s '.[0] * .[1]' prover1.json prover2.json >merged.json 19 | # Convert back to TOML 20 | dasel -f merged.json -r json -w toml >"$CIRCUIT_NAME/Prover.toml" 21 | rm prover1.json prover2.json merged.json 22 | 23 | # split input into shares 24 | co-noir split-input --circuit $CIRCUIT --input $CIRCUIT_NAME/Prover1.toml --protocol REP3 --out-dir target 25 | co-noir split-input --circuit $CIRCUIT --input $CIRCUIT_NAME/Prover2.toml --protocol REP3 --out-dir target 26 | echo "Inputs split" 27 | 28 | # merge inputs into single input file 29 | timeStart "merge-input-shares" 30 | co-noir merge-input-shares --inputs target/Prover1.toml.0.shared --inputs target/Prover2.toml.0.shared --protocol REP3 --out target/Prover.toml.0.shared 31 | co-noir merge-input-shares --inputs target/Prover1.toml.1.shared --inputs target/Prover2.toml.1.shared --protocol REP3 --out target/Prover.toml.1.shared 32 | co-noir merge-input-shares --inputs target/Prover1.toml.2.shared --inputs target/Prover2.toml.2.shared --protocol REP3 --out target/Prover.toml.2.shared 33 | timeEnd "merge-input-shares" 34 | 35 | # run witness extension in MPC 36 | timeStart "mpc-generate-witness" 37 | co-noir generate-witness --input target/Prover.toml.0.shared --circuit $CIRCUIT --protocol REP3 --config configs/party0.toml --out target/witness.gz.0.shared & 38 | co-noir generate-witness --input target/Prover.toml.1.shared --circuit $CIRCUIT --protocol REP3 --config configs/party1.toml --out target/witness.gz.1.shared & 39 | co-noir generate-witness --input target/Prover.toml.2.shared --circuit $CIRCUIT --protocol REP3 --config configs/party2.toml --out target/witness.gz.2.shared 40 | wait $(jobs -p) 41 | timeEnd "mpc-generate-witness" 42 | 43 | # run proving in MPC 44 | timeStart "mpc-build-proving-key" 45 | co-noir build-proving-key --witness target/witness.gz.0.shared --circuit $CIRCUIT --protocol REP3 --config configs/party0.toml --out target/proving_key.0 & 46 | co-noir build-proving-key --witness target/witness.gz.1.shared --circuit $CIRCUIT --protocol REP3 --config configs/party1.toml --out target/proving_key.1 & 47 | co-noir build-proving-key --witness target/witness.gz.2.shared --circuit $CIRCUIT --protocol REP3 --config configs/party2.toml --out target/proving_key.2 48 | wait $(jobs -p) 49 | timeEnd "mpc-build-proving-key" 50 | 51 | timeStart "mpc-generate-proof" 52 | co-noir generate-proof --proving-key target/proving_key.0 --protocol REP3 --hasher keccak --crs ~/.bb-crs/bn254_g1.dat --config configs/party0.toml --out target/proof.0.proof --public-input target/public_input.json & 53 | co-noir generate-proof --proving-key target/proving_key.1 --protocol REP3 --hasher keccak --crs ~/.bb-crs/bn254_g1.dat --config configs/party1.toml --out target/proof.1.proof & 54 | co-noir generate-proof --proving-key target/proving_key.2 --protocol REP3 --hasher keccak --crs ~/.bb-crs/bn254_g1.dat --config configs/party2.toml --out target/proof.2.proof 55 | wait $(jobs -p) 56 | timeEnd "mpc-generate-proof" 57 | 58 | timeStart "bb-generate-witness" 59 | nargo execute --package $CIRCUIT_NAME --silence-warnings 60 | timeEnd "bb-generate-witness" 61 | timeStart "bb-generate-proof" 62 | bb prove_ultra_keccak_honk -b $CIRCUIT -w target/$CIRCUIT_NAME.gz -o target/proof_bb.proof 63 | timeEnd "bb-generate-proof" 64 | 65 | # Create verification key 66 | co-noir create-vk --circuit $CIRCUIT --crs bn254_g1.dat --hasher keccak --vk target/verification_key 67 | echo "Verification key created" 68 | 69 | # verify proof 70 | co-noir verify --proof target/proof.0.proof --vk target/verification_key --hasher keccak --crs bn254_g2.dat 71 | echo "Proof verified" 72 | 73 | bb write_vk_ultra_keccak_honk -b $CIRCUIT -o target/verification_key_bb 74 | echo "Verification key created with bb" 75 | 76 | # check if verification keys are the same (yes/no) 77 | cmp -s target/verification_key target/verification_key_bb && echo "Verification keys are the same" || echo "Verification keys are different" 78 | cmp -s target/proof.0.proof target/proof_bb.proof && echo "Proofs are the same" || echo "Proofs are different" 79 | 80 | # Double check with bb 81 | echo "Verifying with bb" 82 | bb verify_ultra_keccak_honk -p target/proof.0.proof -k target/verification_key_bb 83 | echo "Proof verified with bb" 84 | 85 | # Check the bb proof 86 | bb verify_ultra_keccak_honk -p target/proof_bb.proof -k target/verification_key_bb 87 | -------------------------------------------------------------------------------- /packages/contracts/sdk/TreesService.ts: -------------------------------------------------------------------------------- 1 | import type { Fr } from "@aztec/aztec.js"; 2 | import type { StandardTree } from "@aztec/merkle-tree"; 3 | import { ethers } from "ethers"; 4 | import { isEqual, orderBy, range, times } from "lodash-es"; 5 | import { assert } from "ts-essentials"; 6 | import { z } from "zod"; 7 | import type { PoolERC20 } from "../typechain-types"; 8 | import { NonMembershipTree } from "./NonMembershipTree"; 9 | import { 10 | INCLUDE_UNCOMMITTED, 11 | MAX_NULLIFIERS_PER_ROLLUP, 12 | NOTE_HASH_TREE_HEIGHT, 13 | NULLIFIER_TREE_HEIGHT, 14 | } from "./PoolErc20Service"; 15 | 16 | export class TreesService { 17 | constructor(private contract: PoolERC20) {} 18 | 19 | getTreeRoots = z 20 | .function() 21 | .args() 22 | .implement(async () => { 23 | const { noteHashTree, nullifierTree } = await this.getTrees(); 24 | // console.log("nullifier_tree_root", nullifierTree.getRoot()); 25 | return { 26 | note_hash_root: ethers.hexlify( 27 | noteHashTree.getRoot(INCLUDE_UNCOMMITTED), 28 | ), 29 | }; 30 | }); 31 | 32 | // TODO(security): this reveals link between noteHash and nullifier to the backend. Can we move this to frontend or put backend inside a TEE? 33 | getNoteConsumptionInputs = z 34 | .function() 35 | .args(z.object({ noteHash: z.string(), nullifier: z.string() })) 36 | .implement(async (params) => { 37 | const { Fr } = await import("@aztec/aztec.js"); 38 | 39 | const { noteHashTree } = await this.getTrees(); 40 | 41 | const noteIndex = noteHashTree.findLeafIndex( 42 | new Fr(BigInt(params.noteHash)), 43 | INCLUDE_UNCOMMITTED, 44 | ); 45 | assert(noteIndex != null, "note not found"); 46 | return { 47 | note_sibling_path: ( 48 | await noteHashTree.getSiblingPath(noteIndex, INCLUDE_UNCOMMITTED) 49 | ) 50 | .toTuple() 51 | .map((x: Fr) => x.toString()), 52 | note_index: ethers.toQuantity(noteIndex), 53 | }; 54 | }); 55 | 56 | // TODO(security): this reveals link between noteHash and nullifier to the backend. Can we move this to frontend or put backend inside a TEE? 57 | noteExistsAndNotNullified = z 58 | .function() 59 | .args(z.object({ noteHash: z.string(), nullifier: z.string() })) 60 | .returns(z.promise(z.boolean())) 61 | .implement(async ({ noteHash, nullifier }) => { 62 | const { Fr } = await import("@aztec/aztec.js"); 63 | const { noteHashTree, nullifierTree } = await this.getTrees(); 64 | const noteHashIndex = await noteHashTree.findLeafIndex( 65 | new Fr(BigInt(noteHash)), 66 | INCLUDE_UNCOMMITTED, 67 | ); 68 | if (noteHashIndex == null) { 69 | // note does not exist 70 | return false; 71 | } 72 | const nullifierIndex = await nullifierTree.findLeafIndex( 73 | new Fr(BigInt(nullifier)), 74 | ); 75 | if (nullifierIndex != null) { 76 | // note is nullified 77 | return false; 78 | } 79 | return true; 80 | }); 81 | 82 | async getTrees() { 83 | return await ethers.resolveProperties({ 84 | noteHashTree: this.#getNoteHashTree(), 85 | nullifierTree: this.#getNullifierTree(), 86 | }); 87 | } 88 | 89 | async #getNoteHashTree() { 90 | const { Fr } = await import("@aztec/aztec.js"); 91 | const noteHashes = sortEventsWithIndex( 92 | await this.contract.queryFilter(this.contract.filters.NoteHashes()), 93 | ).map((x) => x.noteHashes); 94 | 95 | const noteHashTree = await createMerkleTree(NOTE_HASH_TREE_HEIGHT); 96 | if (noteHashes.length > 0) { 97 | await noteHashTree.appendLeaves( 98 | noteHashes.flat().map((h) => new Fr(BigInt(h))), 99 | ); 100 | await noteHashTree.commit(); 101 | } 102 | return noteHashTree; 103 | } 104 | 105 | async #getNullifierTree() { 106 | const { Fr } = await import("@aztec/aztec.js"); 107 | 108 | const nullifiers = sortEventsWithIndex( 109 | await this.contract.queryFilter(this.contract.filters.Nullifiers()), 110 | ).map((x) => x.nullifiers.map((n) => new Fr(BigInt(n)))); 111 | 112 | // add 1 to the nullifier tree, so it's possible to add new nullifiers to it(adding requires a non-zero low leaf) 113 | const initialNullifiers = [new Fr(1)].concat( 114 | // sub 2 because `0` and `1` are the first 2 leaves 115 | times(MAX_NULLIFIERS_PER_ROLLUP - 2, () => new Fr(0)), 116 | ); 117 | const allNullifiers = initialNullifiers.concat(nullifiers.flat()); 118 | const nullifierTree = await NonMembershipTree.new( 119 | allNullifiers, 120 | NULLIFIER_TREE_HEIGHT, 121 | ); 122 | return nullifierTree; 123 | } 124 | } 125 | 126 | function sortEventsWithIndex( 127 | events: T[], 128 | ): T["args"][] { 129 | const ordered = orderBy( 130 | events.map((e) => e.args), 131 | (x) => x.index, 132 | ); 133 | assert( 134 | isEqual( 135 | ordered.map((x) => x.index), 136 | range(0, ordered.length).map((x) => BigInt(x)), 137 | ), 138 | `missing some events: ${ordered.map((x) => x.index).join(", ")} | ${ordered.length}`, 139 | ); 140 | return ordered; 141 | } 142 | 143 | async function createMerkleTree(height: number) { 144 | const { StandardTree, newTree, Poseidon } = await import( 145 | "@aztec/merkle-tree" 146 | ); 147 | 148 | const { Fr } = await import("@aztec/aztec.js"); 149 | // @ts-ignore hardhat does not support ESM 150 | const { AztecLmdbStore } = await import("@aztec/kv-store/lmdb"); 151 | const store = AztecLmdbStore.open(); 152 | const tree: StandardTree = await newTree( 153 | StandardTree, 154 | store, 155 | new Poseidon(), 156 | `tree-name`, 157 | Fr, 158 | height, 159 | ); 160 | return tree; 161 | } 162 | -------------------------------------------------------------------------------- /packages/contracts/noir/common/src/uint253.nr: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Clarified Labs, Inc. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::cmp::{Eq, Ord, Ordering}; 5 | use std::ops::{Add, Div, Mul, Rem, Sub}; 6 | 7 | // Maximum value for U253 (2^253 - 1), chosen to fit within Aztec's field arithmetic bounds 8 | pub global MAX_U253: Field = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; 9 | 10 | pub global U253_PACKED_LEN: u32 = 1; 11 | 12 | pub struct U253 { 13 | value: Field, 14 | } 15 | 16 | impl U253 { 17 | pub fn new(value: Field) -> Self { 18 | value.assert_max_bit_size::<253>(); 19 | Self { value } 20 | } 21 | 22 | pub fn new_unchecked(value: Field) -> Self { 23 | Self { value } 24 | } 25 | 26 | pub fn from_integer(value: Field) -> Self { 27 | value.assert_max_bit_size::<253>(); 28 | Self { value } 29 | } 30 | 31 | pub fn to_integer(self) -> Field { 32 | self.value 33 | } 34 | 35 | pub fn zero() -> Self { 36 | Self { value: 0 } 37 | } 38 | 39 | pub fn one() -> Self { 40 | Self { value: 1 } 41 | } 42 | 43 | pub fn max() -> Self { 44 | Self { value: MAX_U253 } 45 | } 46 | 47 | pub fn is_zero(self) -> bool { 48 | self.value == 0 49 | } 50 | 51 | // Performs division with remainder using binary long division algorithm 52 | // Returns (quotient, remainder) tuple 53 | pub unconstrained fn div_rem_unconstrained(self, other: Self) -> (Self, Self) { 54 | assert(!(other.value == 0), "Division by zero"); 55 | 56 | self.value.assert_max_bit_size::<253>(); 57 | other.value.assert_max_bit_size::<253>(); 58 | 59 | let bits: [u1; 253] = self.value.to_be_bits(); 60 | let divisor = other.value; 61 | 62 | let mut quotient: Field = 0; 63 | let mut remainder: Field = 0; 64 | 65 | // Process each bit from MSB to LSB, similar to paper-and-pencil division 66 | for i in 0..253 { 67 | // Shift remainder left by 1 bit and add next bit 68 | remainder = remainder * 2 + (bits[i] as Field); 69 | 70 | // Single comparison to determine if we should subtract divisor 71 | // Changed to just !remainder.lt(divisor) which means remainder >= divisor 72 | if !remainder.lt(divisor) { 73 | remainder = remainder - divisor; 74 | quotient = quotient * 2 + 1; 75 | } else { 76 | quotient = quotient * 2; 77 | } 78 | } 79 | (Self { value: quotient }, Self { value: remainder }) 80 | } 81 | 82 | // Performs division with remainder using unconstrained binary long division algorithm, then 83 | // constrains the result via multiplicative properties 84 | // Returns (quotient, remainder) tuple 85 | pub fn div_rem(self, other: Self) -> (Self, Self) { 86 | assert(!(other.value == 0), "Division by zero"); 87 | 88 | if self.value == other.value { 89 | (Self::one(), Self::zero()) 90 | } else if self.is_zero() { 91 | (Self::zero(), Self::zero()) 92 | } else if other.value == 1 { 93 | (self, Self::zero()) 94 | } else if self.value.lt(other.value) { 95 | (Self::zero(), self) 96 | } else { 97 | //Safety: constraining this immediately after by checking the division property 98 | let (quotient, remainder) = unsafe { self.div_rem_unconstrained(other) }; 99 | 100 | // Verify quotient * other + remainder == self 101 | assert( 102 | quotient * other + remainder == self, 103 | "Unconstrained division result is incorrect", 104 | ); 105 | 106 | (quotient, remainder) 107 | } 108 | } 109 | 110 | // Adds two U253 values without overflow checks - use with caution 111 | pub fn add_unchecked(self, other: Self) -> Self { 112 | Self { value: self.value + other.value } 113 | } 114 | 115 | // Subtracts two U253 values without underflow checks - use with caution 116 | pub fn sub_unchecked(self, other: Self) -> Self { 117 | Self { value: self.value - other.value } 118 | } 119 | } 120 | 121 | impl Add for U253 { 122 | fn add(self, other: Self) -> Self { 123 | let result = self.value + other.value; 124 | result.assert_max_bit_size::<253>(); 125 | 126 | assert(!MAX_U253.lt(result), "U253 addition overflow"); 127 | assert(!result.lt(self.value), "U253 addition overflow"); 128 | assert(!result.lt(other.value), "U253 addition overflow"); 129 | Self { value: result } 130 | } 131 | } 132 | 133 | impl Sub for U253 { 134 | fn sub(self, other: Self) -> Self { 135 | assert( 136 | other.value.lt(self.value) | other.value.eq(self.value), 137 | "U253 subtraction underflow", 138 | ); 139 | let result = self.value - other.value; 140 | result.assert_max_bit_size::<253>(); 141 | Self { value: result } 142 | } 143 | } 144 | 145 | impl Mul for U253 { 146 | fn mul(self, other: Self) -> Self { 147 | let result = self.value * other.value; 148 | 149 | result.assert_max_bit_size::<253>(); 150 | // Allow multiplication by 1 without additional checks, otherwise check for overflow 151 | assert( 152 | (self.value == 1) 153 | | (other.value == 1) 154 | | (result.lt(MAX_U253 + 1) & !result.lt(self.value) & !result.lt(other.value)), 155 | "U253 multiplication overflow", 156 | ); 157 | Self { value: result } 158 | } 159 | } 160 | 161 | impl Div for U253 { 162 | fn div(self, other: Self) -> Self { 163 | let (quotient, _) = self.div_rem(other); 164 | quotient 165 | } 166 | } 167 | 168 | impl Rem for U253 { 169 | fn rem(self, other: Self) -> Self { 170 | let (_, remainder) = self.div_rem(other); 171 | remainder 172 | } 173 | } 174 | 175 | impl Ord for U253 { 176 | fn cmp(self, other: Self) -> Ordering { 177 | if self.value.lt(other.value) { 178 | Ordering::less() 179 | } else if self.value.eq(other.value) { 180 | Ordering::equal() 181 | } else { 182 | Ordering::greater() 183 | } 184 | } 185 | } 186 | 187 | impl Eq for U253 { 188 | fn eq(self, other: Self) -> bool { 189 | self.value.eq(other.value) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /packages/contracts/sdk/mpc/MpcNetworkService.ts: -------------------------------------------------------------------------------- 1 | import { UltraHonkBackend } from "@aztec/bb.js"; 2 | import type { CompiledCircuit } from "@noir-lang/noir_js"; 3 | import { utils } from "@repo/utils"; 4 | import { ethers } from "ethers"; 5 | import fs from "node:fs"; 6 | import os from "node:os"; 7 | import path from "node:path"; 8 | import PQueue, { type QueueAddOptions } from "p-queue"; 9 | import { promiseWithResolvers } from "../utils.js"; 10 | import { inWorkingDir, makeRunCommand, splitInput } from "./utils.js"; 11 | 12 | export class MpcProverService { 13 | readonly #parties = { 14 | 0: new MpcProverPartyService(0), 15 | 1: new MpcProverPartyService(1), 16 | 2: new MpcProverPartyService(2), 17 | }; 18 | 19 | async prove( 20 | inputsShared: Awaited>, 21 | params: { 22 | orderId: OrderId; 23 | side: Side; 24 | circuit: CompiledCircuit; 25 | }, 26 | ) { 27 | return await Promise.all( 28 | inputsShared.map(async ({ partyIndex, inputShared }) => { 29 | return await this.#parties[partyIndex].requestProveAsParty({ 30 | ...params, 31 | inputShared, 32 | }); 33 | }), 34 | ); 35 | } 36 | } 37 | 38 | class MpcProverPartyService { 39 | #storage: Map = new Map(); 40 | #queue = new PQueue({ concurrency: 1 }); 41 | 42 | constructor(readonly partyIndex: PartyIndex) {} 43 | 44 | async requestProveAsParty(params: { 45 | orderId: OrderId; 46 | side: Side; 47 | inputShared: string; 48 | circuit: CompiledCircuit; 49 | }) { 50 | // TODO(security): authorization 51 | if (this.#storage.has(params.orderId)) { 52 | throw new Error(`order already exists ${params.orderId}`); 53 | } 54 | const order: Order = { 55 | id: params.orderId, 56 | inputShared: params.inputShared, 57 | side: params.side, 58 | result: promiseWithResolvers(), 59 | }; 60 | this.#storage.set(params.orderId, order); 61 | 62 | // add this order to other order's queue 63 | // TODO(perf): this is O(N^2) but we should do better 64 | for (const otherOrder of this.#storage.values()) { 65 | this.#addOrdersToQueue({ 66 | orderAId: order.id, 67 | orderBId: otherOrder.id, 68 | circuit: params.circuit, 69 | }); 70 | } 71 | 72 | return await order.result.promise; 73 | } 74 | 75 | #addOrdersToQueue(params: { 76 | orderAId: OrderId; 77 | orderBId: OrderId; 78 | circuit: CompiledCircuit; 79 | }) { 80 | const options: QueueAddOptions = { 81 | throwOnTimeout: true, 82 | // this is a hack to enforce the order of execution matches across all MPC parties 83 | priority: Number( 84 | ethers.getBigInt( 85 | ethers.id([params.orderAId, params.orderBId].sort().join("")), 86 | ) % BigInt(Number.MAX_SAFE_INTEGER), 87 | ), 88 | }; 89 | this.#queue.add(async () => { 90 | await utils.sleep(500); // just to make sure all parties got the order over network 91 | const orderA = this.#storage.get(params.orderAId); 92 | const orderB = this.#storage.get(params.orderBId); 93 | if (!orderA || !orderB) { 94 | // one of the orders was already matched 95 | return; 96 | } 97 | if (orderA.id === orderB.id) { 98 | // can't match with itself 99 | return; 100 | } 101 | if (orderA.side === orderB.side) { 102 | // pre-check that orders are on opposite sides 103 | return; 104 | } 105 | 106 | // deterministic ordering 107 | const [order0, order1] = 108 | orderA.side === "seller" ? [orderA, orderB] : [orderB, orderA]; 109 | console.log("executing orders", this.partyIndex, order0.id, order1.id); 110 | try { 111 | const { proof } = await proveAsParty({ 112 | circuit: params.circuit, 113 | partyIndex: this.partyIndex, 114 | input0Shared: order0.inputShared, 115 | input1Shared: order1.inputShared, 116 | }); 117 | const proofHex = ethers.hexlify(proof); 118 | order0.result.resolve(proofHex); 119 | order1.result.resolve(proofHex); 120 | this.#storage.delete(order0.id); 121 | this.#storage.delete(order1.id); 122 | console.log( 123 | `orders matched: ${this.partyIndex} ${order0.id} ${order1.id}`, 124 | ); 125 | } catch (error) { 126 | console.log( 127 | `orders did not match: ${this.partyIndex} ${order0.id} ${order1.id}`, 128 | ); 129 | } 130 | }, options); 131 | } 132 | } 133 | 134 | async function proveAsParty(params: { 135 | partyIndex: number; 136 | circuit: CompiledCircuit; 137 | input0Shared: string; 138 | input1Shared: string; 139 | }) { 140 | return await inWorkingDir(async (workingDir) => { 141 | for (const [traderIndex, inputShared] of [ 142 | params.input0Shared, 143 | params.input1Shared, 144 | ].entries()) { 145 | fs.writeFileSync( 146 | path.join( 147 | workingDir, 148 | `Prover${traderIndex}.toml.${params.partyIndex}.shared`, 149 | ), 150 | ethers.getBytes(inputShared), 151 | ); 152 | } 153 | 154 | const circuitPath = path.join(workingDir, "circuit.json"); 155 | fs.writeFileSync(circuitPath, JSON.stringify(params.circuit)); 156 | 157 | const runCommand = makeRunCommand(__dirname); 158 | await runCommand("./run-party.sh", [ 159 | workingDir, 160 | circuitPath, 161 | params.partyIndex, 162 | ]); 163 | 164 | const proof = fs.readFileSync( 165 | path.join(workingDir, `proof.${params.partyIndex}.proof`), 166 | ); 167 | const publicInputs = JSON.parse( 168 | fs.readFileSync(path.join(workingDir, "public-input.json"), "utf-8"), 169 | ); 170 | 171 | // pre-verify proof 172 | const backend = new UltraHonkBackend(params.circuit.bytecode, { 173 | threads: os.cpus().length, 174 | }); 175 | let verified: boolean; 176 | try { 177 | verified = await backend.verifyProof( 178 | { proof, publicInputs }, 179 | { keccak: true }, 180 | ); 181 | } catch (e: any) { 182 | if (e.message?.includes("unreachable")) { 183 | throw new Error("mpc generated invalid proof: failed in runtime"); 184 | } 185 | throw e; 186 | } finally { 187 | await backend.destroy(); 188 | } 189 | if (!verified) { 190 | throw new Error("mpc generated invalid proof: returned false"); 191 | } 192 | 193 | return { 194 | proof: proof.slice(4), // remove length 195 | publicInputs, 196 | }; 197 | }); 198 | } 199 | 200 | export type OrderId = string & { __brand: "OrderId" }; 201 | export type PartyIndex = 0 | 1 | 2; 202 | /** 203 | * Deterministically determined based on the tokens being swapped 204 | */ 205 | export type Side = "seller" | "buyer"; 206 | 207 | type Order = { 208 | side: Side; 209 | id: OrderId; 210 | inputShared: string; 211 | result: ReturnType>; 212 | }; 213 | --------------------------------------------------------------------------------