diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..d00f1b6996 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +./pin.sh +use flake o1js#default diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml index 92623aad28..111b302c67 100644 --- a/.github/workflows/build-action.yml +++ b/.github/workflows/build-action.yml @@ -34,9 +34,15 @@ jobs: dist key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.js') }} - - name: Build o1js + - name: Build examples if: steps.cache.outputs.cache-hit != 'true' run: | + npm ci + npm run build:examples + - name: Build o1js clean + if: steps.cache.outputs.cache-hit != 'true' + run: | + rm -fr dist npm ci npm run build @@ -67,7 +73,8 @@ jobs: 'DEX integration tests', 'DEX integration test with proofs', 'Voting integration tests', - 'Verification Key Regression Check', + 'Verification Key Regression Check 1', + 'Verification Key Regression Check 2', 'CommonJS test', ] steps: @@ -161,9 +168,11 @@ jobs: shopt -s globstar test_files=(./dist/node/**/*.unit-test.js) + set -o pipefail + for ((i=start_index; i` interface to protect against incompleteness of certain operations on malicious witness inputs https://github.com/o1-labs/o1js/pull/1759 diff --git a/CHANGELOG.md b/CHANGELOG.md index baf5beaf6a..e8c149a480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,36 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm _Security_ in case of vulnerabilities. --> -## [Unreleased](https://github.com/o1-labs/o1js/compare/d6abf1d97...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/450943...HEAD) + +## [1.8.0](https://github.com/o1-labs/o1js/compare/5006e4f...450943) - 2024-09-18 + +### Added + +- Added `verifyEthers` method to verify Ethereum signatures using the EIP-191 message hashing standard https://github.com/o1-labs/o1js/pull/1815 + - Added `fromEthers` method for parsing and converting Ethereum public keys into `ForeignCurve` points, supporting both compressed and uncompressed formats. + - Added `fromHex` method for converting hexadecimal strings into `ForeignCurve` points. + +### Fixes + +- Fix incorrect behavior of optional proving for zkPrograms where `myProgram.setProofsEnabled(false)` wouldn't work when called before `myProgram.compile()` https://github.com/o1-labs/o1js/pull/1827 + +## [1.7.0](https://github.com/o1-labs/o1js/compare/d6abf1d97...5006e4f) - 2024-09-04 + +### Added + +- Added `Encryption.encryptV2()` and `Encryption.decryptV2()` for an updated encryption algorithm that guarantees cipher text integrity. + - Also added `Encryption.encryptBytes()` and `Encryption.decryptBytes()` using the same algorithm. +- New option `proofsEnabled` for `zkProgram` (default value: `true`), to quickly test circuit logic with proofs disabled https://github.com/o1-labs/o1js/pull/1805 + - Additionally added `MyProgram.proofsEnabled` to get the internal value of `proofsEnabled` and `MyProgram.setProofsEnabled(proofsEnabled)` to set the value dynamically. + +### Deprecated + +- `this.sender.getAndRequireSignature()` / `getUnconstrained()` deprecated in favor of `V2` versions due to a vulnerability https://github.com/o1-labs/o1js/pull/1799 + +### Fixes + +- Fix behavior of `Int64.modV2()` when the input is negative and the remainder should be 0 https://github.com/o1-labs/o1js/pull/1797 ## [1.6.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...d6abf1d97) - 2024-07-23 diff --git a/README-dev.md b/README-dev.md index f00bd9c88b..7d8e1c8f59 100644 --- a/README-dev.md +++ b/README-dev.md @@ -35,6 +35,12 @@ npm run build This command compiles the TypeScript source files, making them ready for use. The compiled OCaml and WebAssembly artifacts are version-controlled to simplify the build process for end users. These artifacts are stored under `src/bindings/compiled` and contain the artifacts needed for both node and web builds. These files only have to be regenerated if there are changes to the OCaml or Rust source files. +## Building with nix + +Much like the mina repo, we use the nix registry to conveniently handle git submodules. +You can enter the devshell with `./pin.sh` and `nix develop o1js#default` or by using +direnv with the `.envrc` provided. This devshell provides all the dependencies required for npm scripts including `npm run:update-bindings`. + ## Building Bindings To regenerate the OCaml and WebAssembly artifacts, you can do so within the o1js repo. The [bindings](https://github.com/o1-labs/o1js-bindings) and [Mina](https://github.com/MinaProtocol/mina) repos are both submodules of o1js so you can build them from within the o1js repo. @@ -89,8 +95,8 @@ o1js uses these types to ensure that the constants used in the protocol are cons | main | **Yes** | | develop | No | -When you start your work on o1js, please create the feature branch off of one of the above base branches. -It's encouraged to submit your work-in-progress as a draft PR to raise visibility! +When you start your work on o1js, please create the feature branch off of one of the above base branches. +It's encouraged to submit your work-in-progress as a draft PR to raise visibility! When working with submodules and various interconnected parts of the stack, ensure you are on the correct branches that are compatible with each other. **Default to `main` as the base branch**. @@ -202,8 +208,8 @@ docker run --rm --pull=missing -it \ See the [Docker Hub repository](https://hub.docker.com/r/o1labs/mina-local-network) for more information. -Next up, get the Mina blockchain accounts information to be used in your zkApp. -After the local network is up and running, you can use the [Lightnet](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/lib/fetch.ts#L1012) `o1js API namespace` to get the accounts information. +Next up, get the Mina blockchain accounts information to be used in your zkApp. +After the local network is up and running, you can use the [Lightnet](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/lib/fetch.ts#L1012) `o1js API namespace` to get the accounts information. See the corresponding example in [src/examples/zkapps/hello-world/run-live.ts](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/examples/zkapps/hello-world/run-live.ts). ### Profiling o1js diff --git a/README.md b/README.md index b985a04327..d3e77f8fce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# o1js   [![npm version](https://img.shields.io/npm/v/o1js.svg?style=flat)](https://www.npmjs.com/package/o1js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/o1-labs/o1js/blob/main/CONTRIBUTING.md) +# o1js   [![npm version](https://img.shields.io/npm/v/o1js.svg?style=flat)](https://www.npmjs.com/package/o1js) [![npm](https://img.shields.io/npm/dm/o1js)](https://www.npmjs.com/package/o1js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/o1-labs/o1js/blob/main/CONTRIBUTING.md) ℹ️ **o1js** is an evolution of [SnarkyJS](https://www.npmjs.com/package/snarkyjs) which saw 49 updated versions over two years of development with 43,141 downloads. diff --git a/audits/VAR_o1js_240318_o1js_V3.pdf b/audits/VAR_o1js_240318_o1js_V3.pdf new file mode 100644 index 0000000000..e75c28613c Binary files /dev/null and b/audits/VAR_o1js_240318_o1js_V3.pdf differ diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..df34277a7d --- /dev/null +++ b/flake.lock @@ -0,0 +1,604 @@ +{ + "nodes": { + "describe-dune": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1724407960, + "narHash": "sha256-pUKTVMYEtsD1AGlHFTdPourowt+tJ3htKhRu7VALFzc=", + "owner": "o1-labs", + "repo": "describe-dune", + "rev": "be828239c05671209e979f9d5c2e3094e3be7a46", + "type": "github" + }, + "original": { + "owner": "o1-labs", + "repo": "describe-dune", + "type": "github" + } + }, + "describe-dune_2": { + "inputs": { + "flake-utils": [ + "mina", + "utils" + ], + "nixpkgs": [ + "mina", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1724407960, + "narHash": "sha256-pUKTVMYEtsD1AGlHFTdPourowt+tJ3htKhRu7VALFzc=", + "owner": "o1-labs", + "repo": "describe-dune", + "rev": "be828239c05671209e979f9d5c2e3094e3be7a46", + "type": "github" + }, + "original": { + "owner": "o1-labs", + "repo": "describe-dune", + "type": "github" + } + }, + "dune-nix": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1723653683, + "narHash": "sha256-n7h3X1PCp+pPOu/bwUQ0Gwv0yXrdjA+h3Zp6CQSS4jQ=", + "owner": "o1-labs", + "repo": "dune-nix", + "rev": "303e4cea2e035c84d4067754a9275a053dac1564", + "type": "github" + }, + "original": { + "owner": "o1-labs", + "repo": "dune-nix", + "type": "github" + } + }, + "dune-nix_2": { + "inputs": { + "flake-utils": [ + "mina", + "utils" + ], + "nixpkgs": [ + "mina", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1724418497, + "narHash": "sha256-HjTh7o02QhGXmbxmpE5HRWei3H/pyh/NKCMCnucDaYs=", + "owner": "o1-labs", + "repo": "dune-nix", + "rev": "26dc164a4c3976888e13eabd73a210b78505820f", + "type": "github" + }, + "original": { + "owner": "o1-labs", + "repo": "dune-nix", + "type": "github" + } + }, + "flake-buildkite-pipeline": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1665561509, + "narHash": "sha256-+9KQw0ftpNjezYwbUIcLAvDWekuGSBAs8I2XulcUkT4=", + "owner": "tweag", + "repo": "flake-buildkite-pipeline", + "rev": "c836a5a449973dd04a80f6741863ceb43ec414c9", + "type": "github" + }, + "original": { + "owner": "tweag", + "repo": "flake-buildkite-pipeline", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1668681692, + "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "009399224d5e398d03b22badca40a37ac85412a1", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1627913399, + "narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1623875721, + "narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "f7e004a55b120c02ecb6219596820fcd32ca8772", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { + "locked": { + "lastModified": 1638122382, + "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "74f7e4319258e287b0f9cb95426c9853b282730b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_4": { + "locked": { + "lastModified": 1644229661, + "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flockenzeit": { + "locked": { + "lastModified": 1671184471, + "narHash": "sha256-oTsIo40BxHG8ZcMNwJybV68F8CLNdfY9HKFzEBzeJ6A=", + "owner": "balsoft", + "repo": "Flockenzeit", + "rev": "0409dcd0cc87feebd211c529171d61b89f10d9b3", + "type": "github" + }, + "original": { + "owner": "balsoft", + "repo": "Flockenzeit", + "type": "github" + } + }, + "gitignore-nix": { + "inputs": { + "nixpkgs": [ + "mina", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "mina": { + "inputs": { + "describe-dune": "describe-dune_2", + "dune-nix": "dune-nix_2", + "flake-buildkite-pipeline": "flake-buildkite-pipeline", + "flake-compat": "flake-compat", + "flockenzeit": "flockenzeit", + "gitignore-nix": "gitignore-nix", + "mix-to-nix": "mix-to-nix", + "nix-filter": "nix-filter", + "nix-npm-buildPackage": "nix-npm-buildPackage", + "nix-utils": "nix-utils", + "nixpkgs": "nixpkgs_2", + "nixpkgs-mozilla": "nixpkgs-mozilla", + "o1-opam-repository": "o1-opam-repository", + "opam-nix": "opam-nix", + "opam-repository": "opam-repository", + "utils": "utils" + }, + "locked": { + "lastModified": 1726508134, + "narHash": "sha256-rAyggDaasWFcgoYi1aNrGZxPK/wH5+cleIV453xuUYY=", + "path": "src/mina", + "type": "path" + }, + "original": { + "path": "src/mina", + "type": "path" + } + }, + "mirage-opam-overlays": { + "flake": false, + "locked": { + "lastModified": 1661959605, + "narHash": "sha256-CPTuhYML3F4J58flfp3ZbMNhkRkVFKmBEYBZY5tnQwA=", + "owner": "dune-universe", + "repo": "mirage-opam-overlays", + "rev": "05f1c1823d891ce4d8adab91f5db3ac51d86dc0b", + "type": "github" + }, + "original": { + "owner": "dune-universe", + "repo": "mirage-opam-overlays", + "type": "github" + } + }, + "mix-to-nix": { + "locked": { + "lastModified": 1643291492, + "narHash": "sha256-B+VIFF8qDJhF5hVMc8PbY/WPzUtbGgjsV1eAxTt5GuQ=", + "owner": "serokell", + "repo": "mix-to-nix", + "rev": "f6f0172b3ac4d32c0e6050a7d805734de0b7adef", + "type": "github" + }, + "original": { + "owner": "serokell", + "repo": "mix-to-nix", + "type": "github" + } + }, + "nix-filter": { + "locked": { + "lastModified": 1666547822, + "narHash": "sha256-razwnAybPHyoAyhkKCwXdxihIqJi1G6e1XP4FQOJTEs=", + "owner": "numtide", + "repo": "nix-filter", + "rev": "1a3b735e13e90a8d2fd5629f2f8363bd7ffbbec7", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "nix-filter", + "type": "github" + } + }, + "nix-npm-buildPackage": { + "inputs": { + "nixpkgs": [ + "mina", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1670813451, + "narHash": "sha256-v0IvQ35CMKtPreGlxWb1FvFUraJNZd144+MbiDwGoAA=", + "owner": "serokell", + "repo": "nix-npm-buildpackage", + "rev": "ec0365cd14a3359a23b80a9e2531a09afc3488fc", + "type": "github" + }, + "original": { + "owner": "serokell", + "repo": "nix-npm-buildpackage", + "type": "github" + } + }, + "nix-utils": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1632973430, + "narHash": "sha256-9G8zo+0nfYAALV5umCyQR/2hVUFNH10JropBkyxZGGw=", + "owner": "juliosueiras-nix", + "repo": "nix-utils", + "rev": "b44e1ffd726aa03056db9df469efb497d8b9871b", + "type": "github" + }, + "original": { + "owner": "juliosueiras-nix", + "repo": "nix-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1629252929, + "narHash": "sha256-Aj20gmGBs8TG7pyaQqgbsqAQ6cB+TVuL18Pk3DPBxcQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "3788c68def67ca7949e0864c27638d484389363d", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1649551420, + "narHash": "sha256-J/Pn38rBZTszdtTHhhxroQaHADhd5TgbJ53F9j1WTIE=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "3a16841c57b0d3025b21b869cc921a119ce73033", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-mozilla": { + "flake": false, + "locked": { + "lastModified": 1704373101, + "narHash": "sha256-+gi59LRWRQmwROrmE1E2b3mtocwueCQqZ60CwLG+gbg=", + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "rev": "9b11a87c0cc54e308fa83aac5b4ee1816d5418a2", + "type": "github" + }, + "original": { + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "type": "github" + } + }, + "nixpkgs-mozilla_2": { + "flake": false, + "locked": { + "lastModified": 1704373101, + "narHash": "sha256-+gi59LRWRQmwROrmE1E2b3mtocwueCQqZ60CwLG+gbg=", + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "rev": "9b11a87c0cc54e308fa83aac5b4ee1816d5418a2", + "type": "github" + }, + "original": { + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1713180868, + "narHash": "sha256-5CSnPSCEWeUmrFiLuYIQIPQzPrpCB8x3VhE+oXLRO3k=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "140546acf30a8212a03a88ded8506413fa3b5d21", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.11-small", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1720535198, + "narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.11-small", + "repo": "nixpkgs", + "type": "github" + } + }, + "o1-opam-repository": { + "flake": false, + "locked": { + "lastModified": 1715294616, + "narHash": "sha256-W2p9Vs8PqpKGvMByxVqpxAljjpEMqNcuNnjMBAAKicI=", + "owner": "o1-labs", + "repo": "opam-repository", + "rev": "38d6995e307c82b3c0b3bc86a867213db724d1c5", + "type": "github" + }, + "original": { + "owner": "o1-labs", + "repo": "opam-repository", + "type": "github" + } + }, + "opam-nix": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_3", + "mirage-opam-overlays": "mirage-opam-overlays", + "nixpkgs": [ + "mina", + "nixpkgs" + ], + "opam-overlays": "opam-overlays", + "opam-repository": [ + "mina", + "opam-repository" + ], + "opam2json": "opam2json" + }, + "locked": { + "lastModified": 1712645768, + "narHash": "sha256-9dUh8nElGtC74Q4gIDV6DM0FKgF1oXh0PUkCxdbp+sg=", + "owner": "tweag", + "repo": "opam-nix", + "rev": "464863fba44c7ecc50bd1a2967274482a2c33daf", + "type": "github" + }, + "original": { + "owner": "tweag", + "repo": "opam-nix", + "type": "github" + } + }, + "opam-overlays": { + "flake": false, + "locked": { + "lastModified": 1654162756, + "narHash": "sha256-RV68fUK+O3zTx61iiHIoS0LvIk0E4voMp+0SwRg6G6c=", + "owner": "dune-universe", + "repo": "opam-overlays", + "rev": "c8f6ef0fc5272f254df4a971a47de7848cc1c8a4", + "type": "github" + }, + "original": { + "owner": "dune-universe", + "repo": "opam-overlays", + "type": "github" + } + }, + "opam-repository": { + "flake": false, + "locked": { + "lastModified": 1708601497, + "narHash": "sha256-mDYINTjOiYLN4wT5fGlWTvHFQdWkzY46XUuZWKgmJxY=", + "owner": "ocaml", + "repo": "opam-repository", + "rev": "90d8c520a4f0b035ac51e267a8b33739c5a78b5a", + "type": "github" + }, + "original": { + "owner": "ocaml", + "repo": "opam-repository", + "type": "github" + } + }, + "opam2json": { + "inputs": { + "nixpkgs": [ + "mina", + "opam-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1671540003, + "narHash": "sha256-5pXfbUfpVABtKbii6aaI2EdAZTjHJ2QntEf0QD2O5AM=", + "owner": "tweag", + "repo": "opam2json", + "rev": "819d291ea95e271b0e6027679de6abb4d4f7f680", + "type": "github" + }, + "original": { + "owner": "tweag", + "repo": "opam2json", + "type": "github" + } + }, + "root": { + "inputs": { + "describe-dune": "describe-dune", + "dune-nix": "dune-nix", + "flake-utils": "flake-utils", + "mina": "mina", + "nixpkgs": "nixpkgs_3", + "nixpkgs-mozilla": "nixpkgs-mozilla_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "flake-utils": "flake-utils_4" + }, + "locked": { + "lastModified": 1657226504, + "narHash": "sha256-GIYNjuq4mJlFgqKsZ+YrgzWm0IpA4axA3MCrdKYj7gs=", + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "rev": "2bf0f91643c2e5ae38c1b26893ac2927ac9bd82a", + "type": "github" + }, + "original": { + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..889fa51931 --- /dev/null +++ b/flake.nix @@ -0,0 +1,144 @@ +{ + description = "o1js - TypeScript framework for zk-SNARKs and zkApps"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11-small"; + mina.url = "path:src/mina"; + nixpkgs-mozilla.url = "github:mozilla/nixpkgs-mozilla"; + nixpkgs-mozilla.flake = false; + describe-dune.url = "github:o1-labs/describe-dune"; + describe-dune.inputs.nixpkgs.follows = "nixpkgs"; + describe-dune.inputs.flake-utils.follows = "flake-utils"; + dune-nix.url = "github:o1-labs/dune-nix"; + dune-nix.inputs.nixpkgs.follows = "nixpkgs"; + dune-nix.inputs.flake-utils.follows = "flake-utils"; + flake-utils.url = "github:numtide/flake-utils"; + }; + outputs = { self, nixpkgs, flake-utils, ... }@inputs: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs= (nixpkgs.legacyPackages."${system}".extend + (import inputs.nixpkgs-mozilla) + ).extend inputs.mina.overlays.rust; + dune-nix = inputs.dune-nix.lib.${system}; + describe-dune = inputs.describe-dune.defaultPackage.${system}; + dune-description = pkgs.stdenv.mkDerivation { + pname = "dune-description"; + version = "dev"; + src = with pkgs.lib.fileset; + (toSource { + root = ./src/bindings; + fileset = unions [ + ./src/bindings/ocaml/dune + ./src/bindings/ocaml/lib/dune + ./src/bindings/ocaml/dune-project + ./src/bindings/ocaml/jsoo_exports/dune + ]; + }); + phases = [ "unpackPhase" "buildPhase" ]; + buildPhase = '' + ${describe-dune}/bin/describe-dune > $out + ''; + }; + desc = builtins.fromJSON (builtins.readFile dune-description); + allOcamlDeps_ = pkgs.lib.concatMap (duneSpec: + pkgs.lib.concatMap (unitSpec: unitSpec.deps or [ ]) + (duneSpec.units or [ ])) desc; + allOcamlDeps = + builtins.map (d: builtins.head (pkgs.lib.splitString "." d)) + allOcamlDeps_; + mina = inputs.mina.packages."${system}"; + minaDeps_ = + builtins.intersectAttrs (pkgs.lib.genAttrs allOcamlDeps (_: { })) + mina.info.raw.deps.units; + minaDeps = builtins.attrNames (builtins.foldl' + (acc: pkg: acc // dune-nix.deps.packageDeps minaDeps_ "pkgs" pkg) + minaDeps_ (builtins.attrNames minaDeps_)); + commonOverrides = { + DUNE_PROFILE = "dev"; + buildInputs = [ mina.base-libs ] ++ mina.external-libs + ++ pkgs.lib.attrVals minaDeps mina.pkgs; + }; + info = dune-nix.info desc; + allDeps = dune-nix.allDeps info; + noTestSkipping = _: false; + prj_ = dune-nix.outputs' commonOverrides (pkgs.lib.fileset.toSource { + root = ./src/bindings; + fileset = ./src/bindings/ocaml; + }) allDeps info noTestSkipping prj; + prj = prj_ // { + pkgs = prj_.pkgs // { + __ocaml-js__ = prj_.pkgs.__ocaml-js__.overrideAttrs { + PREBUILT_KIMCHI_BINDINGS_JS_WEB = + "${mina.files.src-lib-crypto-kimchi_bindings-js-web}/src/lib/crypto/kimchi_bindings/js/web"; + PREBUILT_KIMCHI_BINDINGS_JS_NODE_JS = + "${mina.files.src-lib-crypto-kimchi_bindings-js-node_js}/src/lib/crypto/kimchi_bindings/js/node_js"; + }; + }; + }; + rust-channel = + ((pkgs.rustChannelOf + { channel = "nightly"; + date = "2023-09-01"; + sha256 = "sha256-zek9JAnRaoX8V0U2Y5ssXVe9tvoQ0ERGXfUCUGYdrMA="; + }).rust.override + { targets = ["wasm32-unknown-unknown" "x86_64-unknown-linux-gnu" ]; + extensions = [ "rust-src" ]; + }); + in { + formatter = pkgs.nixfmt; + inherit mina; + devShells = { + # This seems to work better for macos + mina-shell = inputs.mina.devShells."${system}".with-lsp; + default = pkgs.mkShell { + shellHook = + '' + RUSTUP_HOME=$(pwd)/.rustup + export RUSTUP_HOME + rustup toolchain link nix ${rust-channel} + ''; + packages = with pkgs; + [ nodejs + nodePackages.npm + typescript + nodePackages.typescript-language-server + + #Rustup doesn't allow local toolchains to contain 'nightly' in the name + #so the toolchain is linked with the name nix and rustup is wrapped in a shellscript + #which calls the nix toolchain instead of the nightly one + (writeShellApplication + { name = "rustup"; + text = + '' + if [ "$1" = run ] && [ "$2" = nightly-2023-09-01 ] + then + ${rustup}/bin/rustup run nix "''${@:3}" + else + ${rustup}/bin/rustup "$@" + fi + ''; + } + ) + rustup + wasm-pack + binaryen # provides wasm-opt + + dune_3 + ] ++ commonOverrides.buildInputs ; + }; + }; + # TODO build from ./ocaml root, not ./. (after fixing a bug in dune-nix) + packages = { + kim = pkgs.kimchi-rust-wasm; + inherit dune-description; + bindings = prj.pkgs.o1js_bindings; + ocaml-js = prj.pkgs.__ocaml-js__; + default = pkgs.buildNpmPackage + { name = "o1js"; + src = ./.; + npmDepsHash = "sha256-++MTGDUVBccYN8LA2Xb0FkbrZ14ZyVCrDPESXa52AwQ="; + # TODO ideally re-build bindings here + }; + }; + }); +} diff --git a/package-lock.json b/package-lock.json index 3a8ecdd703..164cde435d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "1.6.0", + "version": "1.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "1.6.0", + "version": "1.8.0", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index 424ff0bfe8..b859336f1d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "1.6.0", + "version": "1.8.0", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "repository": { diff --git a/pin.sh b/pin.sh new file mode 100755 index 0000000000..6815dbac8e --- /dev/null +++ b/pin.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Set up flake registry to get o1js with all the submodules + +# Find the root of the o1js repo +ROOT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +pushd "$ROOT" +# Update the submodules +git submodule sync && git submodule update --init --recursive +# Add the flake registry entry +nix registry add o1js "git+file://$ROOT?submodules=1" +# update mina input to local submodule +nix flake update mina --override-input mina 'path:src/mina' --flake '.?submodules=1' +popd diff --git a/run-ci-live-tests.sh b/run-ci-live-tests.sh index 3d6a53de9c..d49f1dbc6a 100755 --- a/run-ci-live-tests.sh +++ b/run-ci-live-tests.sh @@ -15,6 +15,8 @@ echo "" ./run src/examples/zkapps/hello-world/run-live.ts --bundle | add_prefix "HELLO_WORLD" & HELLO_WORLD_PROC=$! +./run src/examples/zkapps/reducer/run-live.ts --bundle | add_prefix "REDUCER" & +REDUCER_FLOW_PROC=$! ./run src/examples/zkapps/dex/run-live.ts --bundle | add_prefix "DEX" & DEX_PROC=$! ./run src/examples/fetch-live.ts --bundle | add_prefix "FETCH" & @@ -52,6 +54,13 @@ if [ $? -ne 0 ]; then echo "" FAILURE=1 fi +wait $REDUCER_FLOW_PROC +if [ $? -ne 0 ]; then + echo "" + echo "REDUCER_FLOW test failed." + echo "" + FAILURE=1 +fi # Exit with failure if any process failed if [ $FAILURE -ne 0 ]; then diff --git a/run-ci-tests.sh b/run-ci-tests.sh index c5153b514e..c4aa4a133d 100755 --- a/run-ci-tests.sh +++ b/run-ci-tests.sh @@ -14,8 +14,7 @@ case $TEST_TYPE in "Reducer integration tests") echo "Running reducer integration tests" - ./run src/examples/zkapps/reducer/actions-as-merkle-list.ts --bundle - ./run src/examples/zkapps/reducer/actions-as-merkle-list-iterator.ts --bundle + ./run src/examples/zkapps/reducer/run.ts --bundle ;; "Voting integration tests") @@ -34,11 +33,16 @@ case $TEST_TYPE in ./run src/examples/zkapps/dex/happy-path-with-proofs.ts --bundle ;; -"Verification Key Regression Check") - echo "Running Regression checks" - ./run ./tests/vk-regression/vk-regression.ts --bundle +"Verification Key Regression Check 1") + echo "Running Regression checks part 1" + VK_TEST=1 ./run ./tests/vk-regression/vk-regression.ts --bundle ;; +"Verification Key Regression Check 2") + echo "Running Regression checks part 2" + VK_TEST=2 ./run ./tests/vk-regression/vk-regression.ts --bundle + ;; + "CommonJS test") echo "Testing CommonJS version" node src/examples/commonjs.cjs diff --git a/run-jest-tests.sh b/run-jest-tests.sh index 54b84b73c5..545ffa1c9a 100755 --- a/run-jest-tests.sh +++ b/run-jest-tests.sh @@ -2,6 +2,13 @@ set -e shopt -s globstar # to expand '**' into nested directories +if ! [ -f ./mina-signer/dist ] +then + pushd src/mina-signer + npm run build + popd +fi + for f in ./src/**/*.test.ts; do NODE_OPTIONS=--experimental-vm-modules npx jest $f; done diff --git a/src/bindings b/src/bindings index 5cb7ede52c..a7ef04f8ef 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 5cb7ede52c0209d2fa6d8c878343676457f7aec5 +Subproject commit a7ef04f8ef98cd34ced00a6945fad5eef6a23b71 diff --git a/src/examples/benchmarks/mul-web.ts b/src/examples/benchmarks/mul-web.ts index 1ad1d31912..48525f1346 100644 --- a/src/examples/benchmarks/mul-web.ts +++ b/src/examples/benchmarks/mul-web.ts @@ -65,7 +65,7 @@ if (withPickles) { toc(); tic('prove'); - let p = await circuit.run(); + let { proof: p } = await circuit.run(); toc(); tic('verify'); diff --git a/src/examples/benchmarks/mul.ts b/src/examples/benchmarks/mul.ts index 97211864d7..5fbced7a0d 100644 --- a/src/examples/benchmarks/mul.ts +++ b/src/examples/benchmarks/mul.ts @@ -61,7 +61,7 @@ if (withPickles) { toc(); tic('prove'); - let p = await circuit.run(); + let { proof: p } = await circuit.run(); toc(); tic('verify'); diff --git a/src/examples/crypto/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts index bdc24dcd6c..b625a2993e 100644 --- a/src/examples/crypto/ecdsa/ecdsa.ts +++ b/src/examples/crypto/ecdsa/ecdsa.ts @@ -7,7 +7,7 @@ import { Bytes, } from 'o1js'; -export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Bytes32 }; +export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Bytes32, ecdsaEthers }; class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} class Scalar extends Secp256k1.Scalar {} @@ -23,7 +23,9 @@ const keccakAndEcdsa = ZkProgram({ verifyEcdsa: { privateInputs: [Ecdsa, Secp256k1], async method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) { - return signature.verify(message, publicKey); + return { + publicOutput: signature.verify(message, publicKey), + }; }, }, }, @@ -38,7 +40,24 @@ const ecdsa = ZkProgram({ verifySignedHash: { privateInputs: [Ecdsa, Secp256k1], async method(message: Scalar, signature: Ecdsa, publicKey: Secp256k1) { - return signature.verifySignedHash(message, publicKey); + return { + publicOutput: signature.verifySignedHash(message, publicKey), + }; + }, + }, + }, +}); + +const ecdsaEthers = ZkProgram({ + name: 'ecdsa-ethers', + publicInput: Bytes32, + publicOutput: Bool, + + methods: { + verifyEthers: { + privateInputs: [Ecdsa, Secp256k1], + async method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) { + return { publicOutput: signature.verifyEthers(message, publicKey) }; }, }, }, diff --git a/src/examples/crypto/ecdsa/run.ts b/src/examples/crypto/ecdsa/run.ts index c342a6433d..75fbc24d4e 100644 --- a/src/examples/crypto/ecdsa/run.ts +++ b/src/examples/crypto/ecdsa/run.ts @@ -1,4 +1,11 @@ -import { Secp256k1, Ecdsa, keccakAndEcdsa, ecdsa, Bytes32 } from './ecdsa.js'; +import { + Secp256k1, + Ecdsa, + keccakAndEcdsa, + ecdsa, + ecdsaEthers, + Bytes32, +} from './ecdsa.js'; import assert from 'assert'; // create an example ecdsa signature @@ -29,8 +36,51 @@ await keccakAndEcdsa.compile(); console.timeEnd('keccak + ecdsa verify (compile)'); console.time('keccak + ecdsa verify (prove)'); -let proof = await keccakAndEcdsa.verifyEcdsa(message, signature, publicKey); +let { proof } = await keccakAndEcdsa.verifyEcdsa(message, signature, publicKey); console.timeEnd('keccak + ecdsa verify (prove)'); proof.publicOutput.assertTrue('signature verifies'); assert(await keccakAndEcdsa.verify(proof), 'proof verifies'); + +// Hardcoded ethers.js signature and inputs for verification in o1js + +// message signed using ethers.js +const msg = 'Secrets hidden, truth in ZKPs ;)'; + +// uncompressed public key generated by ethers.js +const uncompressedPublicKey = + '0x040957928494c38660d254dc03ba78f091a4aea0270afb447f193c4daf6648f02b720071af9b5bda4936998ec186e632f4be82886914851d7c753747b0a949d1a4'; + +// compressed public key generated by ethers.js +const compressedPublicKey = + '0x020957928494c38660d254dc03ba78f091a4aea0270afb447f193c4daf6648f02b'; + +// ECDSA signature generated by ethers.js +const rawSignature = + '0x6fada464c3bc2ae127f8c907c0c4bccbd05ba83a584156edb808b7400346b4c9558598d9c7869f5fd75d81128711f6621e4cb5ba2f52a2a51c46c859f49a833a1b'; + +const publicKeyE = Secp256k1.fromEthers(compressedPublicKey); +const signatureE = Ecdsa.fromHex(rawSignature); +const msgBytes = Bytes32.fromString(msg); + +// investigate the constraint system generated by ECDSA verifyEthers +console.time('ethers verify only (build constraint system)'); +let csEcdsaEthers = await ecdsaEthers.analyzeMethods(); +console.timeEnd('ethers verify only (build constraint system)'); +console.log(csEcdsaEthers.verifyEthers.summary()); + +// compile and prove +console.time('ecdsa / ethers verify (compile)'); +await ecdsaEthers.compile(); +console.timeEnd('ecdsa / ethers verify (compile)'); + +console.time('ecdsa / ethers verify (prove)'); +let { proof: proofE } = await ecdsaEthers.verifyEthers( + msgBytes, + signatureE, + publicKeyE +); +console.timeEnd('ecdsa / ethers verify (prove)'); + +proofE.publicOutput.assertTrue('signature verifies'); +assert(await ecdsaEthers.verify(proofE), 'proof verifies'); diff --git a/src/examples/crypto/rsa/run.ts b/src/examples/crypto/rsa/run.ts index 3d27fb7ed9..e38035c35b 100644 --- a/src/examples/crypto/rsa/run.ts +++ b/src/examples/crypto/rsa/run.ts @@ -38,7 +38,7 @@ const modulus = Bigint2048.from(params.n); console.timeEnd('generate RSA parameters and inputs (2048 bits)'); console.time('prove'); -let proof = await rsaZkProgram.verifyRsa65537(message, signature, modulus); +let { proof } = await rsaZkProgram.verifyRsa65537(message, signature, modulus); console.timeEnd('prove'); console.time('verify'); diff --git a/src/examples/crypto/sha256/run.ts b/src/examples/crypto/sha256/run.ts index f506f7fe5d..ae5502dd0c 100644 --- a/src/examples/crypto/sha256/run.ts +++ b/src/examples/crypto/sha256/run.ts @@ -9,7 +9,7 @@ let preimage = Bytes12.fromString('hello world!'); console.log('sha256 rows:', (await SHA256Program.analyzeMethods()).sha256.rows); console.time('prove'); -let proof = await SHA256Program.sha256(preimage); +let { proof } = await SHA256Program.sha256(preimage); console.timeEnd('prove'); let isValid = await SHA256Program.verify(proof); diff --git a/src/examples/crypto/sha256/sha256.ts b/src/examples/crypto/sha256/sha256.ts index 5ffe43c333..f546d26fa9 100644 --- a/src/examples/crypto/sha256/sha256.ts +++ b/src/examples/crypto/sha256/sha256.ts @@ -11,7 +11,9 @@ let SHA256Program = ZkProgram({ sha256: { privateInputs: [Bytes12], async method(xs: Bytes12) { - return Gadgets.SHA256.hash(xs); + return { + publicOutput: Gadgets.SHA256.hash(xs), + }; }, }, }, diff --git a/src/examples/utils/network-configuration.ts b/src/examples/utils/network-configuration.ts new file mode 100644 index 0000000000..1fb26628a9 --- /dev/null +++ b/src/examples/utils/network-configuration.ts @@ -0,0 +1,9 @@ +export { + DEFAULT_LIGHTNET_CONFIG +} + +const DEFAULT_LIGHTNET_CONFIG = { + mina: 'http://localhost:8080/graphql', + archive: 'http://localhost:8282', + lightnetAccountManager: 'http://localhost:8181', +} \ No newline at end of file diff --git a/src/examples/utils/random-accounts.ts b/src/examples/utils/random-accounts.ts new file mode 100644 index 0000000000..c1ca28265e --- /dev/null +++ b/src/examples/utils/random-accounts.ts @@ -0,0 +1,23 @@ +import { PrivateKey, PublicKey } from 'o1js'; + +export { + randomAccounts +} + +/** + * Predefined accounts keys, labeled by the input strings. Useful for testing/debugging with consistent keys. + */ +function randomAccounts( + ...names: [K, ...K[]] +): { keys: Record; addresses: Record } { + let base58Keys = Array(names.length) + .fill('') + .map(() => PrivateKey.random().toBase58()); + let keys = Object.fromEntries( + names.map((name, idx) => [name, PrivateKey.fromBase58(base58Keys[idx])]) + ) as Record; + let addresses = Object.fromEntries( + names.map((name) => [name, keys[name].toPublicKey()]) + ) as Record; + return { keys, addresses }; +} diff --git a/src/examples/zkapps/dex/dex-with-actions.ts b/src/examples/zkapps/dex/dex-with-actions.ts index bb0fe5583f..87456419cf 100644 --- a/src/examples/zkapps/dex/dex-with-actions.ts +++ b/src/examples/zkapps/dex/dex-with-actions.ts @@ -2,11 +2,11 @@ * This DEX implementation differs from ./dex.ts in two ways: * - More minimal & realistic; stuff designed only for testing protocol features was removed * - Uses an async pattern with actions that lets users claim funds later and reduces account updates - * - * Warning: The reducer API in o1js is currently not safe to use in production applications. The `reduce()` - * method breaks if more than the hard-coded number (default: 32) of actions are pending. Work is actively + * + * Warning: The reducer API in o1js is currently not safe to use in production applications. The `reduce()` + * method breaks if more than the hard-coded number (default: 32) of actions are pending. Work is actively * in progress to mitigate this limitation. - */ + */ import { Account, AccountUpdate, @@ -86,7 +86,7 @@ class Dex extends TokenContract { @method async createAccount() { this.internal.mint({ // unconstrained because we don't care which account is created - address: this.sender.getUnconstrained(), + address: this.sender.getUnconstrainedV2(), amount: UInt64.from(0), }); } @@ -104,7 +104,7 @@ class Dex extends TokenContract { @method.returns(UInt64) async supplyLiquidityBase(dx: UInt64, dy: UInt64) { // unconstrained because `transfer()` requires sender signature anyway - let user = this.sender.getUnconstrained(); + let user = this.sender.getUnconstrainedV2(); let tokenX = new TrivialCoin(this.tokenX); let tokenY = new TrivialCoin(this.tokenY); @@ -175,7 +175,7 @@ class Dex extends TokenContract { * contracts pay you tokens when reducing the action. */ @method async redeemInitialize(dl: UInt64) { - let sender = this.sender.getUnconstrained(); // unconstrained because `burn()` requires sender signature anyway + let sender = this.sender.getUnconstrainedV2(); // unconstrained because `burn()` requires sender signature anyway this.reducer.dispatch(new RedeemAction({ address: sender, dl })); this.internal.burn({ address: sender, amount: dl }); // TODO: preconditioning on the state here ruins concurrent interactions, @@ -209,7 +209,7 @@ class Dex extends TokenContract { * the called methods which requires proof authorization. */ async swapX(dx: UInt64) { - let user = this.sender.getUnconstrained(); // unconstrained because `swap()` requires sender signature anyway + let user = this.sender.getUnconstrainedV2(); // unconstrained because `swap()` requires sender signature anyway let tokenY = new TrivialCoin(this.tokenY); let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); let dy = await dexY.swap(user, dx, this.tokenX); @@ -228,7 +228,7 @@ class Dex extends TokenContract { * the called methods which requires proof authorization. */ async swapY(dy: UInt64) { - let user = this.sender.getUnconstrained(); // unconstrained because `swap()` requires sender signature anyway + let user = this.sender.getUnconstrainedV2(); // unconstrained because `swap()` requires sender signature anyway let tokenX = new TrivialCoin(this.tokenX); let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); let dx = await dexX.swap(user, dy, this.tokenY); diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index 3292ee81d4..7d61d842ee 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -20,7 +20,7 @@ import { export { TokenContract, addresses, createDex, keys, randomAccounts, tokenIds }; -class UInt64x2 extends Struct([UInt64, UInt64]) {} +class UInt64x2 extends Struct({ values: [UInt64, UInt64] }) {} function createDex({ lockedLiquiditySlots, @@ -56,7 +56,7 @@ function createDex({ */ @method.returns(UInt64) async supplyLiquidityBase(dx: UInt64, dy: UInt64) { - let user = this.sender.getUnconstrained(); // unconstrained because transfer() requires the signature anyway + let user = this.sender.getUnconstrainedV2(); // unconstrained because transfer() requires the signature anyway let tokenX = new TokenContract(this.tokenX); let tokenY = new TokenContract(this.tokenY); @@ -155,10 +155,14 @@ function createDex({ */ async redeemLiquidity(dl: UInt64) { // call the token X holder inside a token X-approved callback - let sender = this.sender.getUnconstrained(); // unconstrained because redeemLiquidity() requires the signature anyway + let sender = this.sender.getUnconstrainedV2(); // unconstrained because redeemLiquidity() requires the signature anyway let tokenX = new TokenContract(this.tokenX); let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); - let dxdy = await dexX.redeemLiquidity(sender, dl, this.tokenY); + let { values: dxdy } = await dexX.redeemLiquidity( + sender, + dl, + this.tokenY + ); let dx = dxdy[0]; await tokenX.transfer(dexX.self, sender, dx); return dxdy; @@ -173,7 +177,7 @@ function createDex({ */ @method.returns(UInt64) async swapX(dx: UInt64) { - let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway + let sender = this.sender.getUnconstrainedV2(); // unconstrained because swap() requires the signature anyway let tokenY = new TokenContract(this.tokenY); let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); let dy = await dexY.swap(sender, dx, this.tokenX); @@ -190,7 +194,7 @@ function createDex({ */ @method.returns(UInt64) async swapY(dy: UInt64) { - let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway + let sender = this.sender.getUnconstrainedV2(); // unconstrained because swap() requires the signature anyway let tokenX = new TokenContract(this.tokenX); let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); let dx = await dexX.swap(sender, dy, this.tokenY); @@ -229,7 +233,7 @@ function createDex({ @method.returns(UInt64) async swapX(dx: UInt64) { - let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway + let sender = this.sender.getUnconstrainedV2(); // unconstrained because swap() requires the signature anyway let tokenY = new TokenContract(this.tokenY); let dexY = new ModifiedDexTokenHolder( this.address, @@ -264,7 +268,7 @@ function createDex({ this.self.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken; // return l, dy so callers don't have to walk their child account updates to get it - return [l, dy]; + return { values: [l, dy] }; } // more complicated circuit, where we trigger the Y(other)-lqXY trade in our child account updates and then add the X(our) part @@ -277,9 +281,9 @@ function createDex({ // first call the Y token holder, approved by the Y token contract; this makes sure we get dl, the user's lqXY let tokenY = new TokenContract(otherTokenAddress); let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); - let result = await dexY.redeemLiquidityPartial(user, dl); - let l = result[0]; - let dy = result[1]; + let { values } = await dexY.redeemLiquidityPartial(user, dl); + let l = values[0]; + let dy = values[1]; await tokenY.transfer(dexY.self, user, dy); // in return for dl, we give back dx, the X token part @@ -289,7 +293,7 @@ function createDex({ // just subtract the balance, user gets their part one level higher this.balance.subInPlace(dx); - return [dx, dy]; + return { values: [dx, dy] }; } // this works for both directions (in our case where both tokens use the same contract) diff --git a/src/examples/zkapps/escrow/token-escrow.ts b/src/examples/zkapps/escrow/token-escrow.ts index 39f01a9917..7a1bb49d79 100644 --- a/src/examples/zkapps/escrow/token-escrow.ts +++ b/src/examples/zkapps/escrow/token-escrow.ts @@ -29,7 +29,7 @@ class TokenEscrow extends SmartContract { */ @method async withdraw(amount: UInt64) { // only the admin can withdraw - this.sender.getAndRequireSignature().assertEquals(admin); + this.sender.getAndRequireSignatureV2().assertEquals(admin); // withdraw the amount let receiverAU = this.send({ to: admin, amount }); diff --git a/src/examples/zkapps/reducer/actions-as-merkle-list-iterator.ts b/src/examples/zkapps/reducer/actions-as-merkle-list-iterator.ts index 0f7b7c4a23..15b9c6e673 100644 --- a/src/examples/zkapps/reducer/actions-as-merkle-list-iterator.ts +++ b/src/examples/zkapps/reducer/actions-as-merkle-list-iterator.ts @@ -4,11 +4,11 @@ * * This is mainly intended as an example for using `Iterator` and `MerkleList`, but it might also be useful as * a blueprint for processing actions in a custom and more explicit way. - * - * Warning: The reducer API in o1js is currently not safe to use in production applications. The `reduce()` - * method breaks if more than the hard-coded number (default: 32) of actions are pending. Work is actively + * + * Warning: The reducer API in o1js is currently not safe to use in production applications. The `reduce()` + * method breaks if more than the hard-coded number (default: 32) of actions are pending. Work is actively * in progress to mitigate this limitation. - */ + */ import { Field, Mina, @@ -20,6 +20,8 @@ import { assert, } from 'o1js'; +export { ActionsContract, testLocal }; + // constants for our static-sized provable code const MAX_UPDATES_WITH_ACTIONS = 100; const MAX_ACTIONS_PER_UPDATE = 2; @@ -81,44 +83,46 @@ class ActionsContract extends SmartContract { // TESTS -// set up a local blockchain +async function testLocal() { + // set up a local blockchain -let Local = await Mina.LocalBlockchain({ proofsEnabled: false }); -Mina.setActiveInstance(Local); + let Local = await Mina.LocalBlockchain({ proofsEnabled: true }); + Mina.setActiveInstance(Local); -let [sender, contractAddress] = Local.testAccounts; + let [sender, contractAddress] = Local.testAccounts.slice(4); -let contract = new ActionsContract(contractAddress); + let contract = new ActionsContract(contractAddress); -// deploy the contract + // deploy the contract -await ActionsContract.compile(); -console.log( - `rows for ${MAX_UPDATES_WITH_ACTIONS} updates with actions`, - (await ActionsContract.analyzeMethods()).accumulate.rows -); -let deployTx = await Mina.transaction(sender, async () => contract.deploy()); -await deployTx.sign([sender.key, contractAddress.key]).send(); + await ActionsContract.compile(); + console.log( + `rows for ${MAX_UPDATES_WITH_ACTIONS} updates with actions`, + (await ActionsContract.analyzeMethods()).accumulate.rows + ); + let deployTx = await Mina.transaction(sender, async () => contract.deploy()); + await deployTx.sign([sender.key, contractAddress.key]).send(); -// push some actions + // push some actions -let dispatchTx = await Mina.transaction(sender, async () => { - await contract.increment(Field(1)); - await contract.increment(Field(3)); - await contract.increment(Field(5)); - await contract.increment(Field(9)); - await contract.twoIncrements(Field(18), Field(19)); -}); -await dispatchTx.prove(); -await dispatchTx.sign([sender.key]).send(); + let dispatchTx = await Mina.transaction(sender, async () => { + await contract.increment(Field(1)); + await contract.increment(Field(3)); + await contract.increment(Field(5)); + await contract.increment(Field(9)); + await contract.twoIncrements(Field(18), Field(19)); + }); + await dispatchTx.prove(); + await dispatchTx.sign([sender.key]).send(); -assert(contract.reducer.getActions().data.get().length === 5); + assert(contract.reducer.getActions().data.get().length === 5); -// accumulate actions + // accumulate actions -Local.setProofsEnabled(true); -let accTx = await Mina.transaction(sender, () => contract.accumulate()); -await accTx.prove(); -await accTx.sign([sender.key]).send(); + Local.setProofsEnabled(true); + let accTx = await Mina.transaction(sender, () => contract.accumulate()); + await accTx.prove(); + await accTx.sign([sender.key]).send(); -assert(contract.counter.get().toBigInt() === 55n); + assert(contract.counter.get().toBigInt() === 55n); +} diff --git a/src/examples/zkapps/reducer/actions-as-merkle-list.ts b/src/examples/zkapps/reducer/actions-as-merkle-list.ts index 1eb813dbb5..4d66e92b48 100644 --- a/src/examples/zkapps/reducer/actions-as-merkle-list.ts +++ b/src/examples/zkapps/reducer/actions-as-merkle-list.ts @@ -4,11 +4,11 @@ * * This is mainly intended as an example for using `MerkleList`, but it might also be useful as * a blueprint for processing actions in a custom and more explicit way. - * - * Warning: The reducer API in o1js is currently not safe to use in production applications. The `reduce()` - * method breaks if more than the hard-coded number (default: 32) of actions are pending. Work is actively + * + * Warning: The reducer API in o1js is currently not safe to use in production applications. The `reduce()` + * method breaks if more than the hard-coded number (default: 32) of actions are pending. Work is actively * in progress to mitigate this limitation. - */ + */ import { Bool, Mina, @@ -19,6 +19,8 @@ import { assert, } from 'o1js'; +export { MerkleListReducing, testLocal }; + // in this example, an action is just a public key type Action = PublicKey; const Action = PublicKey; @@ -64,7 +66,6 @@ class MerkleListReducing extends SmartContract { hasAddress = hasAddress.or(action.equals(address)); } } - assert(actions.isEmpty()); // we processed all actions assert(hasAddress); // we found the address } @@ -74,43 +75,45 @@ class MerkleListReducing extends SmartContract { // set up a local blockchain -let Local = await Mina.LocalBlockchain({ proofsEnabled: false }); -Mina.setActiveInstance(Local); +async function testLocal() { + let Local = await Mina.LocalBlockchain({ proofsEnabled: true }); + Mina.setActiveInstance(Local); -let [sender, contractAccount, otherAddress, anotherAddress] = - Local.testAccounts; + let [sender, contractAccount, otherAddress, anotherAddress] = + Local.testAccounts; -let contract = new MerkleListReducing(contractAccount); + let contract = new MerkleListReducing(contractAccount); -// deploy the contract + // deploy the contract -await MerkleListReducing.compile(); -console.log( - `rows for ${MAX_UPDATES_WITH_ACTIONS} updates with actions`, - (await MerkleListReducing.analyzeMethods()).assertContainsAddress.rows -); -let deployTx = await Mina.transaction(sender, async () => contract.deploy()); -await deployTx.sign([sender.key, contractAccount.key]).send(); + await MerkleListReducing.compile(); + console.log( + `rows for ${MAX_UPDATES_WITH_ACTIONS} updates with actions`, + (await MerkleListReducing.analyzeMethods()).assertContainsAddress.rows + ); + let deployTx = await Mina.transaction(sender, async () => contract.deploy()); + await deployTx.sign([sender.key, contractAccount.key]).send(); -// push some actions + // push some actions -let dispatchTx = await Mina.transaction(sender, async () => { - await contract.postAddress(otherAddress); - await contract.postAddress(contractAccount); - await contract.postTwoAddresses(anotherAddress, sender); - await contract.postAddress(anotherAddress); - await contract.postTwoAddresses(contractAccount, otherAddress); -}); -await dispatchTx.prove(); -await dispatchTx.sign([sender.key]).send(); + let dispatchTx = await Mina.transaction(sender, async () => { + await contract.postAddress(otherAddress); + await contract.postAddress(contractAccount); + await contract.postTwoAddresses(anotherAddress, sender); + await contract.postAddress(anotherAddress); + await contract.postTwoAddresses(contractAccount, otherAddress); + }); + await dispatchTx.prove(); + await dispatchTx.sign([sender.key]).send(); -assert(contract.reducer.getActions().data.get().length === 5); + assert(contract.reducer.getActions().data.get().length === 5); -// check if the actions contain the `sender` address + // check if the actions contain the `sender` address -Local.setProofsEnabled(true); -let containsTx = await Mina.transaction(sender, () => - contract.assertContainsAddress(sender) -); -await containsTx.prove(); -await containsTx.sign([sender.key]).send(); + Local.setProofsEnabled(true); + let containsTx = await Mina.transaction(sender, () => + contract.assertContainsAddress(sender) + ); + await containsTx.prove(); + await containsTx.sign([sender.key]).send(); +} diff --git a/src/examples/zkapps/reducer/run-live.ts b/src/examples/zkapps/reducer/run-live.ts new file mode 100644 index 0000000000..dc291dba01 --- /dev/null +++ b/src/examples/zkapps/reducer/run-live.ts @@ -0,0 +1,139 @@ +import { + AccountUpdate, + Lightnet, + Mina, + PrivateKey, +} from 'o1js'; +import { DEFAULT_LIGHTNET_CONFIG } from '../../utils/network-configuration.js'; +import { randomAccounts } from '../../utils/random-accounts.js'; +import { tic, toc } from '../../utils/tic-toc.node.js'; +import { MerkleListReducing } from './actions-as-merkle-list.js'; + +tic('Run reducer examples against real network.'); +console.log(); +const network = Mina.Network(DEFAULT_LIGHTNET_CONFIG); +Mina.setActiveInstance(network); + +let { keys, addresses } = randomAccounts('contract', 'user1', 'user2'); + +let pendingTx: Mina.PendingTransaction; + +// compile contracts & wait for fee payer to be funded +const senderKey = (await Lightnet.acquireKeyPair()).privateKey +const sender = senderKey.toPublicKey(); + +const sender2Key = (await Lightnet.acquireKeyPair()).privateKey +const sender2 = sender2Key.toPublicKey(); + +tic('Compiling Merkle List Reducer Smart Contract'); +await MerkleListReducing.compile(); +toc(); +const merkleListReducerContract = new MerkleListReducing(addresses.contract); + +let senderSpec = { sender, fee: 1000000000 }; +let sender2Spec = { sender: sender2, fee: 1000000000 }; + +tic('deploy contracts'); +let deployTx = await Mina.transaction(senderSpec, async () => { + AccountUpdate.createSigned(sender).balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee + ); + await merkleListReducerContract.deploy(); +}); +pendingTx = await deployTx.sign([senderKey, keys.contract]).send(); + +await pendingTx.wait(); +toc(); +// push some actions + +tic('dispatch transactions'); +let dispatchTx = await Mina.transaction(senderSpec, async () => { + await merkleListReducerContract.postAddress(addresses.user1); + await merkleListReducerContract.postAddress(addresses.contract); + await merkleListReducerContract.postTwoAddresses(addresses.user2, sender); + await merkleListReducerContract.postAddress(addresses.user2); + await merkleListReducerContract.postTwoAddresses( + addresses.contract, + addresses.user1 + ); +}); +await dispatchTx.prove(); +pendingTx = await dispatchTx.sign([senderKey]).send(); +await pendingTx.wait(); +toc(); + +tic('proving inclusion'); +// check if the actions contain the `sender` address +let containsTx = await Mina.transaction(senderSpec, async () => { + await merkleListReducerContract.assertContainsAddress(sender); + await merkleListReducerContract.assertContainsAddress(addresses.user1); + await merkleListReducerContract.assertContainsAddress(addresses.user2); + await merkleListReducerContract.assertContainsAddress(addresses.contract); +}); +await containsTx.prove(); +pendingTx = await containsTx.sign([senderKey]).send(); +await pendingTx.wait(); +toc(); + +tic('dispatch transactions (multi-transaction)'); + +tic('building'); +const txs = []; +let dispatchTx1 = await Mina.transaction(senderSpec, async () => { + await merkleListReducerContract.postAddress(addresses.user1); +}); +await dispatchTx1.prove(); +txs.push({ tx: dispatchTx1, privateKey: senderKey }); + +let dispatchTx2 = await Mina.transaction(sender2Spec, async () => { + await merkleListReducerContract.postTwoAddresses( + addresses.user2, + addresses.contract + ); +}); +await dispatchTx2.prove(); +txs.push({ tx: dispatchTx2, privateKey: sender2Key }); +toc(); + +tic('sending'); +const txPromises = []; +for (let i = 0; i < txs.length; i++) { + txPromises.push(txs[i].tx.sign([txs[i].privateKey]).send()); +} +await txPromises[0].wait(); +await txPromises[1].wait(); +toc(); + +tic('waiting'); +await new Promise((_r) => setTimeout(_r, 60_000)); +toc(); + +toc(); + +tic('proving inclusion (multi-transaction)'); +// check if the actions contain the `sender` address +let containsTx2 = await Mina.transaction(senderSpec, async () => { + await merkleListReducerContract.assertContainsAddress(addresses.user1); + await merkleListReducerContract.assertContainsAddress(addresses.user2); + await merkleListReducerContract.assertContainsAddress(addresses.contract); +}); +await containsTx2.prove(); +pendingTx = await containsTx2.sign([senderKey]).send(); +await pendingTx.wait(); +toc(); + +// ---- +toc(); + +console.log('Success!'); + +// Tear down +const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ + publicKey: sender.toBase58(), +}); +if (keyPairReleaseMessage) console.info(keyPairReleaseMessage); + +const keyPairReleaseMessage2 = await Lightnet.releaseKeyPair({ + publicKey: sender2.toBase58(), +}); +if (keyPairReleaseMessage2) console.info(keyPairReleaseMessage2); diff --git a/src/examples/zkapps/reducer/run.ts b/src/examples/zkapps/reducer/run.ts new file mode 100644 index 0000000000..d029c6f9ae --- /dev/null +++ b/src/examples/zkapps/reducer/run.ts @@ -0,0 +1,5 @@ +import { testLocal as testActionsContract } from './actions-as-merkle-list-iterator.js'; +import { testLocal as testMerkleListContract } from './actions-as-merkle-list.js'; + +await testMerkleListContract(); +await testActionsContract(); diff --git a/src/examples/zkapps/simple-zkapp-payment.ts b/src/examples/zkapps/simple-zkapp-payment.ts index 0563193679..eb58b51daa 100644 --- a/src/examples/zkapps/simple-zkapp-payment.ts +++ b/src/examples/zkapps/simple-zkapp-payment.ts @@ -18,12 +18,12 @@ class PaymentContainer extends SmartContract { @method async withdraw(amount: UInt64) { // unconstrained because we don't care where the user wants to withdraw to - let to = this.sender.getUnconstrained(); + let to = this.sender.getUnconstrainedV2(); this.send({ to, amount }); } @method async deposit(amount: UInt64) { - let sender = this.sender.getUnconstrained(); // unconstrained because we're already requiring a signature in the next line + let sender = this.sender.getUnconstrainedV2(); // unconstrained because we're already requiring a signature in the next line let senderUpdate = AccountUpdate.createSigned(sender); senderUpdate.send({ to: this, amount }); } diff --git a/src/examples/zkprogram/dynamic-keys-merkletree.ts b/src/examples/zkprogram/dynamic-keys-merkletree.ts index ec551c7510..d71e8b034a 100644 --- a/src/examples/zkprogram/dynamic-keys-merkletree.ts +++ b/src/examples/zkprogram/dynamic-keys-merkletree.ts @@ -28,7 +28,9 @@ const sideloadedProgram = ZkProgram({ compute: { privateInputs: [Field], async method(publicInput: Field, privateInput: Field) { - return publicInput.add(privateInput); + return { + publicOutput: publicInput.add(privateInput), + }; }, }, assertAndAdd: { @@ -36,7 +38,7 @@ const sideloadedProgram = ZkProgram({ async method(publicInput: Field, privateInput: Field) { // this uses assert to test range check gates and their feature flags publicInput.assertLessThanOrEqual(privateInput); - return publicInput.add(privateInput); + return { publicOutput: publicInput.add(privateInput) }; }, }, }, @@ -82,10 +84,12 @@ const mainProgram = ZkProgram({ ); const newRoot = merkleWitness.calculateRoot(vk.hash); - return new MainProgramState({ - state: publicInput.state, - treeRoot: newRoot, - }); + return { + publicOutput: new MainProgramState({ + state: publicInput.state, + treeRoot: newRoot, + }), + }; }, }, validateUsingTree: { @@ -118,10 +122,12 @@ const mainProgram = ZkProgram({ // Compute new state proof.publicInput.assertEquals(publicInput.state); const newState = proof.publicOutput; - return new MainProgramState({ - treeRoot: publicInput.treeRoot, - state: newState, - }); + return { + publicOutput: new MainProgramState({ + treeRoot: publicInput.treeRoot, + state: newState, + }), + }; }, }, }, @@ -136,7 +142,7 @@ const rootBefore = tree.getRoot(); tree.setLeaf(1n, programVk.hash); const witness = new MerkleTreeWitness(tree.getWitness(1n)); -const proof1 = await mainProgram.addSideloadedProgram( +const { proof: proof1 } = await mainProgram.addSideloadedProgram( new MainProgramState({ treeRoot: rootBefore, state: Field(0), @@ -146,10 +152,13 @@ const proof1 = await mainProgram.addSideloadedProgram( ); console.log('Proving child program execution'); -const childProof = await sideloadedProgram.compute(Field(0), Field(10)); +const { proof: childProof } = await sideloadedProgram.compute( + Field(0), + Field(10) +); console.log('Proving verification inside main program'); -const proof2 = await mainProgram.validateUsingTree( +const { proof: proof2 } = await mainProgram.validateUsingTree( proof1.publicOutput, proof1, programVk, @@ -161,7 +170,10 @@ const validProof2 = await verify(proof2, mainVk); console.log('ok?', validProof2); console.log('Proving different method of child program'); -const childProof2 = await sideloadedProgram.assertAndAdd(Field(0), Field(10)); +const { proof: childProof2 } = await sideloadedProgram.assertAndAdd( + Field(0), + Field(10) +); console.log('Proving verification inside main program'); const proof3 = await mainProgram.validateUsingTree( diff --git a/src/examples/zkprogram/gadgets.ts b/src/examples/zkprogram/gadgets.ts index 36a63f3c7a..0b8ec05fc1 100644 --- a/src/examples/zkprogram/gadgets.ts +++ b/src/examples/zkprogram/gadgets.ts @@ -63,16 +63,16 @@ console.timeEnd('compile'); console.log('proving..'); console.time('rotation prove'); -let rotProof = await BitwiseProver.rot(); +let { proof: rotProof } = await BitwiseProver.rot(); console.timeEnd('rotation prove'); if (!(await BitwiseProver.verify(rotProof))) throw Error('rot: Invalid proof'); console.time('xor prove'); -let xorProof = await BitwiseProver.xor(); +let { proof: xorProof } = await BitwiseProver.xor(); console.timeEnd('xor prove'); if (!(await BitwiseProver.verify(xorProof))) throw Error('xor: Invalid proof'); console.time('and prove'); -let andProof = await BitwiseProver.and(); +let { proof: andProof } = await BitwiseProver.and(); console.timeEnd('and prove'); if (!(await BitwiseProver.verify(andProof))) throw Error('and: Invalid proof'); diff --git a/src/examples/zkprogram/mututal-recursion.ts b/src/examples/zkprogram/mututal-recursion.ts index c2cc463d4b..542bae1c2b 100644 --- a/src/examples/zkprogram/mututal-recursion.ts +++ b/src/examples/zkprogram/mututal-recursion.ts @@ -41,7 +41,7 @@ const add = ZkProgram({ proof.verifyIf(vk, multiplyResult.equals(Field(0)).not()); const additionResult = multiplyResult.add(field); - return additionResult; + return { publicOutput: additionResult }; }, }, }, @@ -59,7 +59,7 @@ const multiply = ZkProgram({ async method(field: Field, addProof: Proof) { addProof.verify(); const multiplicationResult = addProof.publicOutput.mul(field); - return multiplicationResult; + return { publicOutput: multiplicationResult }; }, }, }, @@ -71,19 +71,26 @@ const multiplyVk = (await multiply.compile()).verificationKey; console.log('Proving basecase'); const dummyProof = await DynamicMultiplyProof.dummy(undefined, Field(0), 1); -const baseCase = await add.performAddition(Field(5), dummyProof, multiplyVk); +const { proof: baseCase } = await add.performAddition( + Field(5), + dummyProof, + multiplyVk +); const validBaseCase = await verify(baseCase, addVk); console.log('ok?', validBaseCase); console.log('Proving first multiplication'); -const multiply1 = await multiply.performMultiplication(Field(3), baseCase); +const { proof: multiply1 } = await multiply.performMultiplication( + Field(3), + baseCase +); const validMultiplication = await verify(multiply1, multiplyVk); console.log('ok?', validMultiplication); console.log('Proving second (recursive) addition'); -const add2 = await add.performAddition( +const { proof: add2 } = await add.performAddition( Field(4), DynamicMultiplyProof.fromProof(multiply1), multiplyVk diff --git a/src/examples/zkprogram/program-no-proving.ts b/src/examples/zkprogram/program-no-proving.ts new file mode 100644 index 0000000000..17a3862729 --- /dev/null +++ b/src/examples/zkprogram/program-no-proving.ts @@ -0,0 +1,50 @@ +import { + SelfProof, + Field, + ZkProgram, + verify, + Proof, + JsonProof, + Provable, + Empty, + Cache, +} from 'o1js'; + +let MyProgram = ZkProgram({ + name: 'example-without-proving', + publicOutput: Field, + publicInput: Field, + methods: { + baseCase: { + privateInputs: [], + async method(publicInput: Field) { + return { publicOutput: publicInput.add(4) }; + }, + }, + }, +}); + +console.log('program digest', await MyProgram.digest()); + +// enable proofs to compile the program +const proofsEnabled = true; + +await MyProgram.compile({ + proofsEnabled, +}); + +console.log('proofs enabled?', MyProgram.proofsEnabled); + +console.log('proving base case... (proofs enabled)'); +console.time('proving'); +let { proof } = await MyProgram.baseCase(Field(2)); +console.timeEnd('proving'); +proof.publicOutput.assertEquals(Field(2).add(4)); + +console.log('disable proofs, generate dummy proof'); +MyProgram.setProofsEnabled(false); +console.log('proofs enabled?', MyProgram.proofsEnabled); +console.time('noProving'); +({ proof } = await MyProgram.baseCase(Field(2))); +console.timeEnd('noProving'); +proof.publicOutput.assertEquals(Field(2).add(4)); diff --git a/src/examples/zkprogram/program-with-aux-output.ts b/src/examples/zkprogram/program-with-aux-output.ts new file mode 100644 index 0000000000..76537e4631 --- /dev/null +++ b/src/examples/zkprogram/program-with-aux-output.ts @@ -0,0 +1,37 @@ +import { Field, Provable, Struct, ZkProgram, assert } from 'o1js'; + +class MyStruct extends Struct({ + label: String, + value: Field, +}) {} + +let MyProgram = ZkProgram({ + name: 'example-with-aux-output', + publicOutput: Field, + methods: { + baseCase: { + privateInputs: [MyStruct], + auxiliaryOutput: MyStruct, + async method(input: MyStruct) { + return { + publicOutput: input.value, + auxiliaryOutput: new MyStruct({ + label: input.label, + value: Field(1).add(input.value), + }), + }; + }, + }, + }, +}); + +await MyProgram.compile(); +console.log('compile done'); +let result = await MyProgram.baseCase( + new MyStruct({ label: 'input-struct', value: Field(2) }) +); + +Provable.log('auxiliary result', result.auxiliaryOutput); + +let ok = await MyProgram.verify(result.proof); +assert(ok, 'proof not valid!'); diff --git a/src/examples/zkprogram/program-with-input.ts b/src/examples/zkprogram/program-with-input.ts index 8d2fc29e02..a6117771b3 100644 --- a/src/examples/zkprogram/program-with-input.ts +++ b/src/examples/zkprogram/program-with-input.ts @@ -32,6 +32,8 @@ let MyProgram = ZkProgram({ // type sanity checks MyProgram.publicInputType satisfies typeof Field; MyProgram.publicOutputType satisfies Provable; +MyProgram.privateInputTypes; +MyProgram.auxiliaryOutputTypes; let MyProof = ZkProgram.Proof(MyProgram); @@ -42,7 +44,7 @@ let { verificationKey } = await MyProgram.compile(); console.log('verification key', verificationKey.data.slice(0, 10) + '..'); console.log('proving base case...'); -let proof = await MyProgram.baseCase(Field(0)); +let { proof } = await MyProgram.baseCase(Field(0)); proof = await testJsonRoundtrip(MyProof, proof); // type sanity check @@ -57,25 +59,25 @@ ok = await MyProgram.verify(proof); console.log('ok (alternative)?', ok); console.log('proving step 1...'); -proof = await MyProgram.inductiveCase(Field(1), proof); -proof = await testJsonRoundtrip(MyProof, proof); +let { proof: proof1 } = await MyProgram.inductiveCase(Field(1), proof); +proof1 = await testJsonRoundtrip(MyProof, proof1); console.log('verify...'); -ok = await verify(proof, verificationKey); +ok = await verify(proof1, verificationKey); console.log('ok?', ok); console.log('verify alternative...'); -ok = await MyProgram.verify(proof); +ok = await MyProgram.verify(proof1); console.log('ok (alternative)?', ok); console.log('proving step 2...'); -proof = await MyProgram.inductiveCase(Field(2), proof); -proof = await testJsonRoundtrip(MyProof, proof); +let { proof: proof2 } = await MyProgram.inductiveCase(Field(2), proof1); +proof2 = await testJsonRoundtrip(MyProof, proof2); console.log('verify...'); -ok = await verify(proof.toJSON(), verificationKey); +ok = await verify(proof2.toJSON(), verificationKey); -console.log('ok?', ok && proof.publicInput.toString() === '2'); +console.log('ok?', ok && proof2.publicInput.toString() === '2'); function testJsonRoundtrip< P extends Proof, diff --git a/src/examples/zkprogram/program.ts b/src/examples/zkprogram/program.ts index 4bce8c3dc8..47cb4a919a 100644 --- a/src/examples/zkprogram/program.ts +++ b/src/examples/zkprogram/program.ts @@ -12,83 +12,18 @@ import { let MyProgram = ZkProgram({ name: 'example-with-output', publicOutput: Field, - methods: { baseCase: { privateInputs: [], async method() { - return Field(0); - }, - }, - - inductiveCase: { - privateInputs: [SelfProof], - async method(earlierProof: SelfProof) { - earlierProof.verify(); - return earlierProof.publicOutput.add(1); + return { + publicOutput: Field(1), + }; }, }, }, }); -// type sanity checks -MyProgram.publicInputType satisfies Provable; -MyProgram.publicOutputType satisfies typeof Field; - -let MyProof = ZkProgram.Proof(MyProgram); - -console.log('program digest', await MyProgram.digest()); - -console.log('compiling MyProgram...'); -let { verificationKey } = await MyProgram.compile(); -console.log('verification key', verificationKey.data.slice(0, 10) + '..'); - -console.log('proving base case...'); -let proof = await MyProgram.baseCase(); -proof = await testJsonRoundtrip(MyProof, proof); - -// type sanity check -proof satisfies Proof; - -console.log('verify...'); -let ok = await verify(proof.toJSON(), verificationKey); -console.log('ok?', ok); - -console.log('verify alternative...'); -ok = await MyProgram.verify(proof); -console.log('ok (alternative)?', ok); - -console.log('proving step 1...'); -proof = await MyProgram.inductiveCase(proof); -proof = await testJsonRoundtrip(MyProof, proof); - -console.log('verify...'); -ok = await verify(proof, verificationKey); -console.log('ok?', ok); - -console.log('verify alternative...'); -ok = await MyProgram.verify(proof); -console.log('ok (alternative)?', ok); - -console.log('proving step 2...'); -proof = await MyProgram.inductiveCase(proof); -proof = await testJsonRoundtrip(MyProof, proof); - -console.log('verify...'); -ok = await verify(proof.toJSON(), verificationKey); -console.log('ok?', ok && proof.publicOutput.toString() === '2'); +await MyProgram.compile(); -function testJsonRoundtrip< - P extends Proof, - MyProof extends { fromJSON(jsonProof: JsonProof): Promise

} ->(MyProof: MyProof, proof: P) { - let jsonProof = proof.toJSON(); - console.log( - 'json proof', - JSON.stringify({ - ...jsonProof, - proof: jsonProof.proof.slice(0, 10) + '..', - }) - ); - return MyProof.fromJSON(jsonProof); -} +let result = await MyProgram.baseCase(); diff --git a/src/lib/mina/account-update-layout.unit-test.ts b/src/lib/mina/account-update-layout.unit-test.ts index 3c6fcc237a..8c05c49bc9 100644 --- a/src/lib/mina/account-update-layout.unit-test.ts +++ b/src/lib/mina/account-update-layout.unit-test.ts @@ -7,13 +7,13 @@ import { SmartContract, method } from './zkapp.js'; class NestedCall extends SmartContract { @method async deposit() { - let sender = this.sender.getUnconstrained(); + let sender = this.sender.getUnconstrainedV2(); let payerUpdate = AccountUpdate.createSigned(sender); payerUpdate.send({ to: this.address, amount: UInt64.one }); } @method async depositUsingTree() { - let sender = this.sender.getUnconstrained(); + let sender = this.sender.getUnconstrainedV2(); let payerUpdate = AccountUpdate.createSigned(sender); let receiverUpdate = AccountUpdate.create(this.address); payerUpdate.send({ to: receiverUpdate, amount: UInt64.one }); diff --git a/src/lib/mina/actions/batch-reducer-program.unit-test.ts b/src/lib/mina/actions/batch-reducer-program.unit-test.ts index dedbf2f8a8..2ff266cd20 100644 --- a/src/lib/mina/actions/batch-reducer-program.unit-test.ts +++ b/src/lib/mina/actions/batch-reducer-program.unit-test.ts @@ -63,7 +63,6 @@ await describe('action stack prover', async () => { await it('does 250 actions', async () => { let { witnesses, endActionState } = randomActionWitnesses(250); - console.time('prove'); let { isEmpty, proof } = await proveActionStack( endActionState, diff --git a/src/lib/mina/actions/batch-reducer.ts b/src/lib/mina/actions/batch-reducer.ts index f497468f64..88ea0a947e 100644 --- a/src/lib/mina/actions/batch-reducer.ts +++ b/src/lib/mina/actions/batch-reducer.ts @@ -697,12 +697,12 @@ async function proveActionStack( for (let i = nChunks - 1; i >= 0; i--) { let isRecursive = Bool(i < nChunks - 1); - proof = await program.proveChunk( + ({ proof } = await program.proveChunk( endActionState, proof, isRecursive, chunks[i] - ); + )); } // sanity check proof.publicOutput.stack.assertEquals(stack.hash, 'Stack hash mismatch'); @@ -737,7 +737,7 @@ type ActionStackProgram = { proofSoFar: ActionStackProof, isRecursive: Bool, actionWitnesses: Unconstrained - ): Promise; + ): Promise<{ proof: ActionStackProof }>; maxUpdatesPerProof: number; }; @@ -791,7 +791,7 @@ function actionStackProgram(maxUpdatesPerProof: number) { proofSoFar: ActionStackProof, isRecursive: Bool, witnesses: Unconstrained - ): Promise { + ) { // make this proof extend proofSoFar proofSoFar.verifyIf(isRecursive); Provable.assertEqualIf( @@ -807,8 +807,12 @@ function actionStackProgram(maxUpdatesPerProof: number) { proofSoFar.publicOutput, initialState ); - - return actionStackChunk(maxUpdatesPerProof, startState, witnesses); + let publicOutput = actionStackChunk( + maxUpdatesPerProof, + startState, + witnesses + ); + return { publicOutput }; }, }, }, diff --git a/src/lib/mina/actions/batch-reducer.unit-test.ts b/src/lib/mina/actions/batch-reducer.unit-test.ts index ff06ffd90f..6c0d5a18d4 100644 --- a/src/lib/mina/actions/batch-reducer.unit-test.ts +++ b/src/lib/mina/actions/batch-reducer.unit-test.ts @@ -72,7 +72,7 @@ class UnsafeAirdrop extends SmartContract { */ @method async claim() { - let address = this.sender.getUnconstrained(); + let address = this.sender.getUnconstrainedV2(); // ensure that the MINA account already exists and that the sender knows its private key let au = AccountUpdate.createSigned(address); diff --git a/src/lib/mina/actions/offchain-state-rollup.ts b/src/lib/mina/actions/offchain-state-rollup.ts index d69595ad7b..cc443a85b0 100644 --- a/src/lib/mina/actions/offchain-state-rollup.ts +++ b/src/lib/mina/actions/offchain-state-rollup.ts @@ -18,6 +18,7 @@ import { updateMerkleMap, } from './offchain-state-serialization.js'; import { getProofsEnabled } from '../mina.js'; +import { Cache } from '../../../lib/proof-system/cache.js'; export { OffchainStateRollup, OffchainStateCommitments }; @@ -35,6 +36,7 @@ class ActionIterator extends MerkleListIterator.create( * * Fields: * - `root`: The root of the current Merkle tree + * - `length`: The number of elements in the current Merkle tree * - `actionState`: The hash pointing to the list of actions that have been applied to form the current Merkle tree */ class OffchainStateCommitments extends Struct({ @@ -71,7 +73,7 @@ function merkleUpdateBatch( stateA: OffchainStateCommitments, actions: ActionIterator, tree: IndexedMerkleMapBase -): OffchainStateCommitments { +): { commitments: OffchainStateCommitments; tree: IndexedMerkleMapBase } { // this would be unnecessary if the iterator could just be the public input actions.currentHash.assertEquals(stateA.actionState); @@ -131,9 +133,12 @@ function merkleUpdateBatch( }); return { - root: tree.root, - length: tree.length, - actionState: actions.currentHash, + commitments: { + root: tree.root, + length: tree.length, + actionState: actions.currentHash, + }, + tree, }; } @@ -170,18 +175,23 @@ function OffchainStateRollup({ firstBatch: { // [actions, tree] privateInputs: [ActionIterator, IndexedMerkleMapN], + auxiliaryOutput: IndexedMerkleMapN, async method( stateA: OffchainStateCommitments, actions: ActionIterator, tree: IndexedMerkleMapN - ): Promise { - return merkleUpdateBatch( + ) { + let result = merkleUpdateBatch( { maxActionsPerProof, maxActionsPerUpdate }, stateA, actions, tree ); + return { + publicOutput: result.commitments, + auxiliaryOutput: result.tree, + }; }, }, /** @@ -190,6 +200,7 @@ function OffchainStateRollup({ nextBatch: { // [actions, tree, proof] privateInputs: [ActionIterator, IndexedMerkleMapN, SelfProof], + auxiliaryOutput: IndexedMerkleMapN, async method( stateA: OffchainStateCommitments, @@ -199,7 +210,7 @@ function OffchainStateRollup({ OffchainStateCommitments, OffchainStateCommitments > - ): Promise { + ) { recursiveProof.verify(); // in the recursive case, the recursive proof's initial state has to match this proof's initial state @@ -212,12 +223,16 @@ function OffchainStateRollup({ // the state we start with let stateB = recursiveProof.publicOutput; - return merkleUpdateBatch( + let result = merkleUpdateBatch( { maxActionsPerProof, maxActionsPerUpdate }, stateB, actions, tree ); + return { + publicOutput: result.commitments, + auxiliaryOutput: result.tree, + }; }, }, }, @@ -231,9 +246,13 @@ function OffchainStateRollup({ Proof: RollupProof, program: offchainStateRollup, - async compile() { + async compile(options?: { + cache?: Cache; + forceRecompile?: boolean; + proofsEnabled?: boolean; + }) { if (isCompiled) return; - let result = await offchainStateRollup.compile(); + let result = await offchainStateRollup.compile(options); isCompiled = true; return result; }, @@ -284,12 +303,14 @@ function OffchainStateRollup({ // base proof let slice = sliceActions(iterator, maxActionsPerProof); - let proof = await offchainStateRollup.firstBatch(inputState, slice, tree); + let { proof, auxiliaryOutput } = await offchainStateRollup.firstBatch( + inputState, + slice, + tree + ); - // update tree root/length again, they aren't mutated :( - // TODO: this shows why the full tree should be the public output - tree.root = proof.publicOutput.root; - tree.length = proof.publicOutput.length; + // overwrite the tree with its updated version + tree = auxiliaryOutput; // recursive proofs let nProofs = 1; @@ -298,16 +319,14 @@ function OffchainStateRollup({ nProofs++; let slice = sliceActions(iterator, maxActionsPerProof); - proof = await offchainStateRollup.nextBatch( + + // overwrite tree, proof + ({ proof, auxiliaryOutput: tree } = await offchainStateRollup.nextBatch( inputState, slice, tree, proof - ); - - // update tree root/length again, they aren't mutated :( - tree.root = proof.publicOutput.root; - tree.length = proof.publicOutput.length; + )); } return { proof, tree, nProofs }; diff --git a/src/lib/mina/fetch.ts b/src/lib/mina/fetch.ts index c9058e36c9..8165bad6f7 100644 --- a/src/lib/mina/fetch.ts +++ b/src/lib/mina/fetch.ts @@ -17,6 +17,7 @@ import { type LastBlockQueryResponse, type GenesisConstantsResponse, type LastBlockQueryFailureCheckResponse, + type FetchedAction, type FetchedBlock, type TransactionStatus, type TransactionStatusQueryResponse, @@ -64,6 +65,7 @@ export { sendZkapp, fetchEvents, fetchActions, + makeGraphqlRequest, Lightnet, type GenesisConstants, type ActionStatesStringified, @@ -77,6 +79,12 @@ type NetworkConfig = { lightnetAccountManagerEndpoint: string; }; +type ActionsQueryInputs = { + publicKey: string; + actionStates: ActionStatesStringified; + tokenId?: string; +}; + let networkConfig = { minaEndpoint: '', minaFallbackEndpoints: [] as string[], @@ -684,11 +692,7 @@ async function fetchEvents( } async function fetchActions( - accountInfo: { - publicKey: string; - actionStates: ActionStatesStringified; - tokenId?: string; - }, + accountInfo: ActionsQueryInputs, graphqlEndpoint = networkConfig.archiveEndpoint ) { if (!graphqlEndpoint) @@ -700,6 +704,7 @@ async function fetchActions( actionStates, tokenId = TokenId.toBase58(TokenId.default), } = accountInfo; + let [response, error] = await makeGraphqlRequest( getActionsQuery(publicKey, actionStates, tokenId), graphqlEndpoint, @@ -716,6 +721,22 @@ async function fetchActions( }; } + const actionsList = createActionsList(accountInfo, fetchedActions); + addCachedActions({ publicKey, tokenId }, actionsList, graphqlEndpoint); + + return actionsList; +} + +/** + * Given a graphQL response from #getActionsQuery, process the actions into a canonical actions list + */ +export function createActionsList( + accountInfo: ActionsQueryInputs, + fetchedActions: FetchedAction[] +) { + const _fetchedActions = fetchedActions; + const { publicKey, actionStates } = accountInfo; + let actionsList: { actions: string[][]; hash: string }[] = []; // correct for archive node sending one block too many if ( @@ -736,6 +757,10 @@ async function fetchActions( `No action data was found for the account ${publicKey} with the latest action state ${actionState}` ); + actionData = actionData.sort((a1, a2) => { + return Number(a1.accountUpdateId) < Number(a2.accountUpdateId) ? -1 : 1; + }); + // split actions by account update let actionsByAccountUpdate: string[][][] = []; let currentAccountUpdateId = 'none'; @@ -769,7 +794,6 @@ async function fetchActions( ); } }); - addCachedActions({ publicKey, tokenId }, actionsList, graphqlEndpoint); return actionsList; } diff --git a/src/lib/mina/fetch.unit-test.ts b/src/lib/mina/fetch.unit-test.ts index e61a7155a6..3f0d662cec 100644 --- a/src/lib/mina/fetch.unit-test.ts +++ b/src/lib/mina/fetch.unit-test.ts @@ -1,3 +1,7 @@ +import { PrivateKey, TokenId } from 'o1js'; +import { createActionsList } from './fetch.js'; +import { mockFetchActionsResponse } from './fixtures/fetch-actions-response.js'; +import { test, describe } from 'node:test'; import { removeJsonQuotes } from './graphql.js'; import { expect } from 'expect'; @@ -118,3 +122,110 @@ actual = removeJsonQuotes(input); expect(actual).toEqual(expected); console.log('regex tests complete 🎉'); + +describe('Fetch', async (t) => { + describe('#createActionsList with default params', async (t) => { + const defaultPublicKey = PrivateKey.random().toPublicKey().toBase58(); + const defaultActionStates = { + fromActionState: undefined, + endActionState: undefined, + }; + const defaultAccountInfo = { + publicKey: defaultPublicKey, + actionStates: defaultActionStates, + tokenId: TokenId.default.toString(), + }; + + const actionsList = createActionsList( + defaultAccountInfo, + mockFetchActionsResponse.data.actions + ); + + await test('orders the actions correctly', async () => { + expect(actionsList).toEqual([ + { + actions: [ + [ + '20374659537065244088703638031937922870146667362923279084491778322749365537089', + '1', + ], + ], + hash: '10619825168606131449407092474314250900469658818945385329390497057469974757422', + }, + { + actions: [ + [ + '20503089751358270987184701275168489753952341816059774976784079526478451099801', + '1', + ], + ], + hash: '25525130517416993227046681664758665799110129890808721833148757111140891481208', + }, + { + actions: [ + [ + '3374074164183544078218789545772953663729921088152354292852793744356608231707', + '0', + ], + ], + hash: '290963518424616502946790040851348455652296009700336010663574777600482385855', + }, + { + actions: [ + [ + '12630758077588166643924428865613845067150916064939816120404808842510620524633', + '1', + ], + ], + hash: '20673199655841577810393943638910551364027795297920791498278816237738641857371', + }, + { + actions: [ + [ + '5643224648393140391519847064914429159616501351124129591669928700148350171602', + '0', + ], + ], + hash: '5284016523143033193387918577616839424871122381326995145988133445906503263869', + }, + { + actions: [ + [ + '15789351988619560045401465240113496854401074115453702466673859303925517061263', + '0', + ], + ], + hash: '16944163018367910067334012882171366051616125936127175065464614786387687317044', + }, + { + actions: [ + [ + '27263309408256888453299195755797013857604561285332380691270111409680109142128', + '1', + ], + ], + hash: '23662159967366296714544063539035629952291787828104373633198732070740691309118', + }, + { + actions: [ + [ + '3378367318331499715304980508337843233019278703665446829424824679144818589558', + '1', + ], + ], + hash: '1589729766029695153975344283092689798747741638003354620355672853210932754595', + }, + { + actions: [ + [ + '17137397755795687855356639427474789131368991089558570411893673365904353943290', + '1', + ], + ], + hash: '10964420428484427410756859799314206378989718180435238943573393516522086219419', + }, + ]); + }); + }); +}); +``; diff --git a/src/lib/mina/fixtures/fetch-actions-response.ts b/src/lib/mina/fixtures/fetch-actions-response.ts new file mode 100644 index 0000000000..3e280bbff2 --- /dev/null +++ b/src/lib/mina/fixtures/fetch-actions-response.ts @@ -0,0 +1,118 @@ +export { + mockFetchActionsResponse +} + +const mockFetchActionsResponse = +{ + "data": { + "actions": [ + { + "blockInfo": { + "distanceFromMaxBlockHeight": 11 + }, + "actionState": { + "actionStateOne": "25525130517416993227046681664758665799110129890808721833148757111140891481208", + "actionStateTwo": "25079927036070901246064867767436987657692091363973573142121686150614948079097" + }, + "actionData": [ + { + "accountUpdateId": "5", + "data": [ + "20503089751358270987184701275168489753952341816059774976784079526478451099801", + "1" + ] + }, + { + "accountUpdateId": "3", + "data": [ + "20374659537065244088703638031937922870146667362923279084491778322749365537089", + "1" + ] + } + ] + }, + { + "blockInfo": { + "distanceFromMaxBlockHeight": 5 + }, + "actionState": { + "actionStateOne": "290963518424616502946790040851348455652296009700336010663574777600482385855", + "actionStateTwo": "25525130517416993227046681664758665799110129890808721833148757111140891481208" + }, + "actionData": [ + { + "accountUpdateId": "7", + "data": [ + "3374074164183544078218789545772953663729921088152354292852793744356608231707", + "0" + ] + } + ] + }, + { + "blockInfo": { + "distanceFromMaxBlockHeight": 3 + }, + "actionState": { + "actionStateOne": "20673199655841577810393943638910551364027795297920791498278816237738641857371", + "actionStateTwo": "290963518424616502946790040851348455652296009700336010663574777600482385855" + }, + "actionData": [ + { + "accountUpdateId": "9", + "data": [ + "12630758077588166643924428865613845067150916064939816120404808842510620524633", + "1" + ] + } + ] + }, + { + "blockInfo": { + "distanceFromMaxBlockHeight": 2 + }, + "actionState": { + "actionStateOne": "10964420428484427410756859799314206378989718180435238943573393516522086219419", + "actionStateTwo": "20673199655841577810393943638910551364027795297920791498278816237738641857371" + }, + "actionData": [ + { + "accountUpdateId": "19", + "data": [ + "17137397755795687855356639427474789131368991089558570411893673365904353943290", + "1" + ] + }, + { + "accountUpdateId": "17", + "data": [ + "3378367318331499715304980508337843233019278703665446829424824679144818589558", + "1" + ] + }, + { + "accountUpdateId": "15", + "data": [ + "27263309408256888453299195755797013857604561285332380691270111409680109142128", + "1" + ] + }, + { + "accountUpdateId": "13", + "data": [ + "15789351988619560045401465240113496854401074115453702466673859303925517061263", + "0" + ] + }, + { + "accountUpdateId": "11", + "data": [ + "5643224648393140391519847064914429159616501351124129591669928700148350171602", + "0" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/lib/mina/graphql.ts b/src/lib/mina/graphql.ts index 5d20a2c71b..04e0340a61 100644 --- a/src/lib/mina/graphql.ts +++ b/src/lib/mina/graphql.ts @@ -9,6 +9,7 @@ export { type GenesisConstantsResponse, type FailureReasonResponse, type LastBlockQueryFailureCheckResponse, + type FetchedAction, type FetchedBlock, type TransactionStatus, type TransactionStatusQueryResponse, @@ -250,22 +251,24 @@ type EventQueryResponse = { }[]; }; -type ActionQueryResponse = { - actions: { - blockInfo: { - distanceFromMaxBlockHeight: number; - }; - actionState: { - actionStateOne: string; - actionStateTwo: string; - }; - actionData: { - accountUpdateId: string; - data: string[]; - }[]; +type FetchedAction = { + blockInfo: { + distanceFromMaxBlockHeight: number; + }; + actionState: { + actionStateOne: string; + actionStateTwo: string; + }; + actionData: { + accountUpdateId: string; + data: string[]; }[]; }; +type ActionQueryResponse = { + actions: FetchedAction[]; +}; + type EventActionFilterOptions = { to?: UInt32; from?: UInt32; diff --git a/src/lib/mina/mina.ts b/src/lib/mina/mina.ts index a30c290bc5..2dc41d5422 100644 --- a/src/lib/mina/mina.ts +++ b/src/lib/mina/mina.ts @@ -448,7 +448,7 @@ function Network( if (actions !== undefined) return actions; } throw Error( - `getActions: Could not find actions for the public key ${publicKey}` + `getActions: Could not find actions for the public key ${publicKey.toBase58()}` ); }, proofsEnabled: true, diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index c57b5855d1..d27e1c495d 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -17,7 +17,7 @@ export { TokenContract, TokenContractV2 }; /** * @deprecated Use {@link TokenContractV2} instead, which has the right max account update limit. - * + * * Base token contract which * - implements the `Approvable` API, with the `approveBase()` method left to be defined by subclasses * - implements the `Transferable` API as a wrapper around the `Approvable` API @@ -99,7 +99,11 @@ abstract class TokenContract extends SmartContract { ); // iterate through the forest and apply user-defined logic - for (let i = 0; i < (this.constructor as typeof TokenContract).MAX_ACCOUNT_UPDATES; i++) { + for ( + let i = 0; + i < (this.constructor as typeof TokenContract).MAX_ACCOUNT_UPDATES; + i++ + ) { let { accountUpdate, usesThisToken } = iterator.next(); callback(accountUpdate, usesThisToken); } @@ -107,7 +111,9 @@ abstract class TokenContract extends SmartContract { // prove that we checked all updates iterator.assertFinished( `Number of account updates to approve exceed ` + - `the supported limit of ${(this.constructor as typeof TokenContract).MAX_ACCOUNT_UPDATES}.\n` + `the supported limit of ${ + (this.constructor as typeof TokenContract).MAX_ACCOUNT_UPDATES + }.\n` ); // skip hashing our child account updates in the method wrapper @@ -164,12 +170,12 @@ abstract class TokenContract extends SmartContract { // coerce the inputs to AccountUpdate and pass to `approveBase()` let tokenId = this.deriveTokenId(); if (from instanceof PublicKey) { - from = AccountUpdate.defaultAccountUpdate(from, tokenId); + from = AccountUpdate.default(from, tokenId); from.requireSignature(); from.label = `${this.constructor.name}.transfer() (from)`; } if (to instanceof PublicKey) { - to = AccountUpdate.defaultAccountUpdate(to, tokenId); + to = AccountUpdate.default(to, tokenId); to.label = `${this.constructor.name}.transfer() (to)`; } @@ -182,7 +188,7 @@ abstract class TokenContract extends SmartContract { } /** Version of `TokenContract` with the precise number of `MAX_ACCOUNT_UPDATES` - * + * * The value of 20 in `TokenContract` was a rough upper limit, the precise upper * bound is 9. */ diff --git a/src/lib/mina/zkapp.ts b/src/lib/mina/zkapp.ts index 7b58bfc361..e1d4f046eb 100644 --- a/src/lib/mina/zkapp.ts +++ b/src/lib/mina/zkapp.ts @@ -16,6 +16,7 @@ import { AccountUpdateLayout, AccountUpdateTree, } from './account-update.js'; +import type { EventActionFilterOptions } from './graphql.js'; import { cloneCircuitValue, FlexibleProvablePure, @@ -134,6 +135,7 @@ function method( ZkappClass.name, methodName, paramTypes, + undefined, SelfProof ); // add witness arguments for the publicKey (address) and tokenId @@ -141,6 +143,7 @@ function method( ZkappClass.name, methodName, [PublicKey, Field, ...paramTypes], + undefined, SelfProof ); @@ -875,12 +878,10 @@ super.init(); sender = { self: this as SmartContract, /** - * The public key of the current transaction's sender account. - * - * Throws an error if not inside a transaction, or the sender wasn't passed in. - * - * **Warning**: The fact that this public key equals the current sender is not part of the proof. - * A malicious prover could use any other public key without affecting the validity of the proof. + * @deprecated + * Deprecated in favor of `this.sender.getUnconstrainedV2()`. + * This method is vulnerable because it allows the prover to return a dummy (empty) public key, + * which would cause an account update with that public key to not be included. */ getUnconstrained(): PublicKey { // TODO this logic now has some overlap with this.self, we should combine them somehow @@ -900,11 +901,46 @@ super.init(); } }, + /** + * The public key of the current transaction's sender account. + * + * Throws an error if not inside a transaction, or the sender wasn't passed in. + * + * **Warning**: The fact that this public key equals the current sender is not part of the proof. + * A malicious prover could use any other public key without affecting the validity of the proof. + * + * Consider using `this.sender.getAndRequireSignatureV2()` if you need to prove that the sender controls this account. + */ + getUnconstrainedV2(): PublicKey { + let sender = this.getUnconstrained(); + // we prove that the returned public key is not the empty key, in which case + // `createSigned()` would skip adding the account update, and nothing is proved + sender.x.assertNotEquals(0); + return sender; + }, + + /** + * @deprecated + * Deprecated in favor of `this.sender.getAndRequireSignatureV2()`. + * This method is vulnerable because it allows the prover to return a dummy (empty) public key. + */ getAndRequireSignature(): PublicKey { let sender = this.getUnconstrained(); AccountUpdate.createSigned(sender); return sender; }, + + /** + * Return a public key that is forced to sign this transaction. + * + * Note: This doesn't prove that the return value is the transaction sender, but it proves that whoever created + * the transaction controls the private key associated with the returned public key. + */ + getAndRequireSignatureV2(): PublicKey { + let sender = this.getUnconstrainedV2(); + AccountUpdate.createSigned(sender); + return sender; + }, }; /** @@ -1058,21 +1094,32 @@ super.init(); chainStatus: string; }[] > { + // used to match field values back to their original type + const sortedEventTypes = Object.keys(this.events).sort(); + if (sortedEventTypes.length === 0) { + throw Error( + 'fetchEvents: You are trying to fetch events without having declared the types of your events.\n' + + `Make sure to add a property \`events\` on ${this.constructor.name}, for example: \n` + + `class ${this.constructor.name} extends SmartContract {\n` + + ` events = { 'my-event': Field }\n` + + `}\n` + + `Or, if you want to access the events from the zkapp account ${this.address.toBase58()} without casting their types\n` + + `then try Mina.fetchEvents('${this.address.toBase58()}') instead.` + ); + } + + const queryFilterOptions: EventActionFilterOptions = {}; + if(start.greaterThan(UInt32.from(0)).toBoolean()) { + queryFilterOptions.from = start; + } + if(end) { + queryFilterOptions.to = end; + } // filters all elements so that they are within the given range // only returns { type: "", event: [] } in a flat format let events = ( - await Mina.fetchEvents(this.address, this.self.body.tokenId, { - from: start, - to: end, - }) + await Mina.fetchEvents(this.address, this.self.body.tokenId, queryFilterOptions) ) - .filter((eventData) => { - let height = UInt32.from(eventData.blockHeight); - return end === undefined - ? start.lessThanOrEqual(height).toBoolean() - : start.lessThanOrEqual(height).toBoolean() && - height.lessThanOrEqual(end).toBoolean(); - }) .map((event) => { return event.events.map((eventData) => { let { events, ...rest } = event; @@ -1084,9 +1131,6 @@ super.init(); }) .flat(); - // used to match field values back to their original type - let sortedEventTypes = Object.keys(this.events).sort(); - return events.map((eventData) => { // if there is only one event type, the event structure has no index and can directly be matched to the event type if (sortedEventTypes.length === 1) { diff --git a/src/lib/proof-system/proof-system.unit-test.ts b/src/lib/proof-system/proof-system.unit-test.ts index b2e7501f94..82e266c21b 100644 --- a/src/lib/proof-system/proof-system.unit-test.ts +++ b/src/lib/proof-system/proof-system.unit-test.ts @@ -35,12 +35,20 @@ it('pickles rule creation', async () => { // a rule that verifies a proof conditionally, and returns the proof's input as output function main(proof: EmptyProof, shouldVerify: Bool) { proof.verifyIf(shouldVerify); - return proof.publicInput; + return { + publicOutput: proof.publicInput, + }; } let privateInputs = [EmptyProof, Bool]; // collect method interface - let methodIntf = sortMethodArguments('mock', 'main', privateInputs, Proof); + let methodIntf = sortMethodArguments( + 'mock', + 'main', + privateInputs, + undefined, + Proof + ); expect(methodIntf).toEqual({ methodName: 'main', diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index 8568eb78d6..9b4a5e5a0d 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -53,7 +53,7 @@ function parseHeader( methods: MethodInterface[], header: SnarkKeyHeader ): CacheHeader { - let hash = Pickles.util.fromMlString(header[1][2][7]); + let hash = Pickles.util.fromMlString(header[1][2][6]); switch (header[0]) { case KeyType.StepProvingKey: case KeyType.StepVerificationKey: { @@ -211,7 +211,6 @@ type MlSnarkKeysHeader = [ headerVersion: number, kind: [_: 0, type: MlString, identifier: MlString], constraintConstants: unknown, - commit: MlString, length: number, constraintSystemHash: MlString, identifyingHash: MlString diff --git a/src/lib/proof-system/sideloaded.unit-test.ts b/src/lib/proof-system/sideloaded.unit-test.ts index 88bb40fca8..7b97809aee 100644 --- a/src/lib/proof-system/sideloaded.unit-test.ts +++ b/src/lib/proof-system/sideloaded.unit-test.ts @@ -35,7 +35,9 @@ const program2 = ZkProgram({ foo: { privateInputs: [Field], async method(publicInput: Program2Struct, field: Field) { - return publicInput.field1.add(publicInput.field2).add(field); + return { + publicOutput: publicInput.field1.add(publicInput.field2).add(field), + }; }, }, }, @@ -140,8 +142,8 @@ describe('sideloaded', async () => { let program2Vk = (await program2.compile()).verificationKey; // Generate sample proofs - const program1Proof = await program1.foo(Field(1), Field(1)); - const program2Proof = await program2.foo( + const { proof: program1Proof } = await program1.foo(Field(1), Field(1)); + const { proof: program2Proof } = await program2.foo( { field1: Field(1), field2: Field(2) }, Field(3) ); @@ -159,7 +161,7 @@ describe('sideloaded', async () => { it('recurse one proof with zkprogram', async () => { const proof = SampleSideloadedProof.fromProof(program1Proof); - const finalProof = await sideloadedProgram.recurseOneSideloaded( + const { proof: finalProof } = await sideloadedProgram.recurseOneSideloaded( Field(1), proof, program1Vk diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index fe497a7a65..0ac4a96acd 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -160,6 +160,25 @@ const FeatureFlags = { fromZkProgramList, }; +function createProgramState() { + let methodCache: Map = new Map(); + + return { + setAuxiliaryOutput(value: unknown, methodName: string) { + methodCache.set(methodName, value); + }, + getAuxiliaryOutput(methodName: string): unknown { + let entry = methodCache.get(methodName); + if (entry === undefined) + throw Error(`Auxiliary value for method ${methodName} not defined`); + return entry; + }, + reset(methodName: string) { + methodCache.delete(methodName); + }, + }; +} + async function fromZkProgramList(programs: Array) { let flatMethodIntfs: Array>> = []; @@ -538,65 +557,83 @@ let SideloadedTag = { }; function ZkProgram< - StatementType extends { + Config extends { publicInput?: ProvableTypePure; publicOutput?: ProvableTypePure; + methods: { + [I in string]: { + privateInputs: Tuple; + auxiliaryOutput?: ProvableType; + }; + }; + }, + Methods extends { + [I in keyof Config['methods']]: Method< + InferProvableOrUndefined>, + InferProvableOrVoid>, + Config['methods'][I] + >; }, - Types extends { - // TODO: how to prevent a method called `compile` from type-checking? - // TODO: solution: put method calls on a separate namespace! like `await program.prove.myMethod()` - [I in string]: Tuple; + // derived types for convenience + MethodSignatures extends Config['methods'] = Config['methods'], + PrivateInputs extends { + [I in keyof MethodSignatures]: MethodSignatures[I]['privateInputs']; + } = { + [I in keyof MethodSignatures]: MethodSignatures[I]['privateInputs']; + }, + AuxiliaryOutputs extends { + [I in keyof MethodSignatures]: Get; + } = { + [I in keyof MethodSignatures]: Get; } >( - config: StatementType & { + config: Config & { name: string; methods: { - [I in keyof Types]: Method< - InferProvableOrUndefined>, - InferProvableOrVoid>, - Types[I] - >; + [I in keyof Config['methods']]: Methods[I]; }; overrideWrapDomain?: 0 | 1 | 2; } ): { name: string; - compile: (options?: { cache?: Cache; forceRecompile?: boolean }) => Promise<{ + compile: (options?: { + cache?: Cache; + forceRecompile?: boolean; + proofsEnabled?: boolean; + }) => Promise<{ verificationKey: { data: string; hash: Field }; }>; verify: ( proof: Proof< - InferProvableOrUndefined>, - InferProvableOrVoid> + InferProvableOrUndefined>, + InferProvableOrVoid> > ) => Promise; digest: () => Promise; analyzeMethods: () => Promise<{ - [I in keyof Types]: UnwrapPromise>; + [I in keyof Config['methods']]: UnwrapPromise< + ReturnType + >; }>; - publicInputType: ProvableOrUndefined>; - publicOutputType: ProvableOrVoid>; - privateInputTypes: { - [I in keyof Types]: Method< - InferProvableOrUndefined>, - InferProvableOrVoid>, - Types[I] - >['privateInputs']; - }; + publicInputType: ProvableOrUndefined>; + publicOutputType: ProvableOrVoid>; + privateInputTypes: PrivateInputs; + auxiliaryOutputTypes: AuxiliaryOutputs; rawMethods: { - [I in keyof Types]: Method< - InferProvableOrUndefined>, - InferProvableOrVoid>, - Types[I] - >['method']; + [I in keyof Config['methods']]: Methods[I]['method']; }; + proofsEnabled: boolean; + setProofsEnabled(proofsEnabled: boolean): void; } & { - [I in keyof Types]: Prover< - InferProvableOrUndefined>, - InferProvableOrVoid>, - Types[I] + [I in keyof Config['methods']]: Prover< + InferProvableOrUndefined>, + InferProvableOrVoid>, + PrivateInputs[I], + InferProvableOrUndefined >; } { + let doProving = true; + let methods = config.methods; let publicInputType: ProvablePure = ProvableType.get( config.publicInput ?? Undefined @@ -606,10 +643,8 @@ function ZkProgram< ); let selfTag = { name: config.name }; - type PublicInput = InferProvableOrUndefined< - Get - >; - type PublicOutput = InferProvableOrVoid>; + type PublicInput = InferProvableOrUndefined>; + type PublicOutput = InferProvableOrVoid>; class SelfProof extends Proof { static publicInputType = publicInputType; @@ -618,9 +653,15 @@ function ZkProgram< } // TODO remove sort()! Object.keys() has a deterministic order - let methodKeys: (keyof Types & string)[] = Object.keys(methods).sort(); // need to have methods in (any) fixed order + let methodKeys: (keyof Methods & string)[] = Object.keys(methods).sort(); // need to have methods in (any) fixed order let methodIntfs = methodKeys.map((key) => - sortMethodArguments('program', key, methods[key].privateInputs, SelfProof) + sortMethodArguments( + 'program', + key, + methods[key].privateInputs, + ProvableType.get(methods[key].auxiliaryOutput) ?? Undefined, + SelfProof + ) ); let methodFunctions = methodKeys.map((key) => methods[key].method); let maxProofsVerified = getMaxProofsVerified(methodIntfs); @@ -639,7 +680,7 @@ function ZkProgram< ); } return methodsMeta as { - [I in keyof Types]: UnwrapPromise>; + [I in keyof Methods]: UnwrapPromise>; }; } @@ -653,40 +694,88 @@ function ZkProgram< } | undefined; + const programState = createProgramState(); + async function compile({ cache = Cache.FileSystemDefault, forceRecompile = false, + proofsEnabled = undefined, } = {}) { - let methodsMeta = await analyzeMethods(); - let gates = methodKeys.map((k) => methodsMeta[k].gates); - let { provers, verify, verificationKey } = await compileProgram({ - publicInputType, - publicOutputType, - methodIntfs, - methods: methodFunctions, - gates, - proofSystemTag: selfTag, - cache, - forceRecompile, - overrideWrapDomain: config.overrideWrapDomain, - }); - compileOutput = { provers, verify }; - return { verificationKey }; + doProving = proofsEnabled ?? doProving; + + if (doProving) { + let methodsMeta = await analyzeMethods(); + let gates = methodKeys.map((k) => methodsMeta[k].gates); + + let { provers, verify, verificationKey } = await compileProgram({ + publicInputType, + publicOutputType, + methodIntfs, + methods: methodFunctions, + gates, + proofSystemTag: selfTag, + cache, + forceRecompile, + overrideWrapDomain: config.overrideWrapDomain, + state: programState, + }); + + compileOutput = { provers, verify }; + return { verificationKey }; + } else { + return { + verificationKey: VerificationKey.empty(), + }; + } } - function toProver( + function toProver( key: K, i: number - ): [K, Prover] { + ): [ + K, + Prover< + PublicInput, + PublicOutput, + PrivateInputs[K], + InferProvableOrUndefined + > + ] { async function prove_( publicInput: PublicInput, - ...args: TupleToInstances - ): Promise> { + ...args: TupleToInstances + ): Promise<{ + proof: Proof; + auxiliaryOutput: any; + }> { + class ProgramProof extends Proof { + static publicInputType = publicInputType; + static publicOutputType = publicOutputType; + static tag = () => selfTag; + } + + if (!doProving) { + let previousProofs = MlArray.to( + getPreviousProofsForProver(args, methodIntfs[i]) + ); + + let { publicOutput, auxiliaryOutput } = + (await (methods[key].method as any)(publicInput, previousProofs)) ?? + {}; + + let proof = await ProgramProof.dummy( + publicInput, + publicOutput, + maxProofsVerified + ); + return { proof, auxiliaryOutput }; + } + let picklesProver = compileOutput?.provers?.[i]; if (picklesProver === undefined) { throw Error( `Cannot prove execution of program.${key}(), no prover found. ` + - `Try calling \`await program.compile()\` first, this will cache provers in the background.` + `Try calling \`await program.compile()\` first, this will cache provers in the background.\nIf you compiled your zkProgram with proofs disabled (\`proofsEnabled = false\`), you have to compile it with proofs enabled first.` ); } let publicInputFields = toFieldConsts(publicInputType, publicInput); @@ -701,37 +790,64 @@ function ZkProgram< } finally { snarkContext.leave(id); } + + let auxiliaryType = methodIntfs[i].auxiliaryType; + let auxiliaryOutputExists = + auxiliaryType && auxiliaryType.sizeInFields() !== 0; + + let auxiliaryOutput; + if (auxiliaryOutputExists) { + auxiliaryOutput = programState.getAuxiliaryOutput( + methodIntfs[i].methodName + ); + + programState.reset(methodIntfs[i].methodName); + } + let [publicOutputFields, proof] = MlPair.from(result); let publicOutput = fromFieldConsts(publicOutputType, publicOutputFields); - class ProgramProof extends Proof { - static publicInputType = publicInputType; - static publicOutputType = publicOutputType; - static tag = () => selfTag; - } - return new ProgramProof({ - publicInput, - publicOutput, - proof, - maxProofsVerified, - }); + + return { + proof: new ProgramProof({ + publicInput, + publicOutput, + proof, + maxProofsVerified, + }), + auxiliaryOutput, + }; } - let prove: Prover; + + let prove: Prover< + PublicInput, + PublicOutput, + PrivateInputs[K], + InferProvableOrUndefined + >; if ( (publicInputType as any) === Undefined || (publicInputType as any) === Void ) { - prove = ((...args: TupleToInstances) => - (prove_ as any)(undefined, ...args)) as any; + prove = ((...args: any) => prove_(undefined as any, ...args)) as any; } else { prove = prove_ as any; } return [key, prove]; } + let provers = Object.fromEntries(methodKeys.map(toProver)) as { - [I in keyof Types]: Prover; + [I in keyof Config['methods']]: Prover< + PublicInput, + PublicOutput, + PrivateInputs[I], + InferProvableOrUndefined + >; }; function verify(proof: Proof) { + if (!doProving) { + return Promise.resolve(true); + } if (compileOutput?.verify === undefined) { throw Error( `Cannot verify proof, verification key not found. Try calling \`await program.compile()\` first.` @@ -752,7 +868,7 @@ function ZkProgram< return hashConstant(digests).toBigInt().toString(16); } - return Object.assign( + const program = Object.assign( selfTag, { compile, @@ -760,33 +876,54 @@ function ZkProgram< digest, analyzeMethods, publicInputType: publicInputType as ProvableOrUndefined< - Get + Get >, publicOutputType: publicOutputType as ProvableOrVoid< - Get + Get >, privateInputTypes: Object.fromEntries( methodKeys.map((key) => [key, methods[key].privateInputs]) ) as any, + auxiliaryOutputTypes: Object.fromEntries( + methodKeys.map((key) => [key, methods[key].auxiliaryOutput]) + ) as any, rawMethods: Object.fromEntries( methodKeys.map((key) => [key, methods[key].method]) ) as any, + setProofsEnabled(proofsEnabled: boolean) { + doProving = proofsEnabled; + }, }, provers ); + + // Object.assign only shallow-copies, hence we cant use this getter and have to define it explicitly + Object.defineProperty(program, 'proofsEnabled', { + get: () => doProving, + }); + + return program as any; } type ZkProgram< - S extends { + Config extends { publicInput?: ProvableTypePure; publicOutput?: ProvableTypePure; + methods: { + [I in string]: { + privateInputs: Tuple; + auxiliaryOutput?: ProvableType; + }; + }; }, - T extends { - [I in string]: Tuple; + Methods extends { + [I in keyof Config['methods']]: Method< + InferProvableOrUndefined>, + InferProvableOrVoid>, + Config['methods'][I] + >; } -> = ReturnType>; - -let i = 0; +> = ReturnType>; class SelfProof extends Proof< PublicInput, @@ -804,6 +941,7 @@ function sortMethodArguments( programName: string, methodName: string, privateInputs: unknown[], + auxiliaryType: Provable | undefined, selfProof: Subclass ): MethodInterface { let witnessArgs: Provable[] = []; @@ -849,7 +987,13 @@ function sortMethodArguments( `Suggestion: You can merge more than two proofs by merging two at a time in a binary tree.` ); } - return { methodName, witnessArgs, proofArgs, allArgs }; + return { + methodName, + witnessArgs, + proofArgs, + allArgs, + auxiliaryType, + }; } function isAsFields( @@ -900,6 +1044,7 @@ type MethodInterface = { proofArgs: Subclass[]; allArgs: { type: 'witness' | 'proof'; index: number }[]; returnType?: Provable; + auxiliaryType?: Provable; }; // reasonable default choice for `overrideWrapDomain` @@ -915,6 +1060,7 @@ async function compileProgram({ cache, forceRecompile, overrideWrapDomain, + state, }: { publicInputType: ProvablePure; publicOutputType: ProvablePure; @@ -925,10 +1071,11 @@ async function compileProgram({ cache: Cache; forceRecompile: boolean; overrideWrapDomain?: 0 | 1 | 2; + state?: ReturnType; }) { await initializeBindings(); if (methodIntfs.length === 0) - throw Error(`The Program you are trying to compile has no methods. + throw Error(`The Program you are trying to compile has no methods. Try adding a method to your ZkProgram or SmartContract. If you are using a SmartContract, make sure you are using the @method decorator.`); @@ -939,7 +1086,8 @@ If you are using a SmartContract, make sure you are using the @method decorator. methods[i], proofSystemTag, methodEntry, - gates[i] + gates[i], + state ) ); let maxProofs = getMaxProofsVerified(methodIntfs); @@ -1059,8 +1207,15 @@ function picklesRuleFromFunction( publicOutputType: ProvablePure, func: (...args: unknown[]) => unknown, proofSystemTag: { name: string }, - { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface, - gates: Gate[] + { + methodName, + witnessArgs, + proofArgs, + allArgs, + auxiliaryType, + }: MethodInterface, + gates: Gate[], + state?: ReturnType ): Pickles.Rule { async function main( publicInput: MlFieldArray @@ -1104,12 +1259,15 @@ function picklesRuleFromFunction( previousStatements.push(MlPair(input, output)); } } - let result: any; + let result: { + publicOutput?: any; + auxiliaryOutput?: any; + }; if (publicInputType === Undefined || publicInputType === Void) { - result = await func(...finalArgs); + result = (await func(...finalArgs)) as any; } else { let input = fromFieldVars(publicInputType, publicInput); - result = await func(input, ...finalArgs); + result = (await func(input, ...finalArgs)) as any; } proofs.forEach(({ proofInstance, classReference }) => { @@ -1143,7 +1301,28 @@ function picklesRuleFromFunction( // if the public output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case let hasPublicOutput = publicOutputType.sizeInFields() !== 0; - let publicOutput = hasPublicOutput ? publicOutputType.toFields(result) : []; + let publicOutput = hasPublicOutput + ? publicOutputType.toFields(result.publicOutput) + : []; + + if ( + state !== undefined && + auxiliaryType !== undefined && + auxiliaryType.sizeInFields() !== 0 + ) { + Provable.asProver(() => { + let { auxiliaryOutput } = result; + assert( + auxiliaryOutput !== undefined, + `${proofSystemTag.name}.${methodName}(): Auxiliary output is undefined even though the method declares it.` + ); + state.setAuxiliaryOutput( + Provable.toConstant(auxiliaryType, auxiliaryOutput), + methodName + ); + }); + } + return { publicOutput: MlFieldArray.to(publicOutput), previousStatements: MlArray.to(previousStatements), @@ -1456,35 +1635,68 @@ type Subclass any> = (new ( type PrivateInput = ProvableType | Subclass; +type MethodReturnType = PublicOutput extends void + ? AuxiliaryOutput extends undefined + ? void + : { + auxiliaryOutput: AuxiliaryOutput; + } + : AuxiliaryOutput extends undefined + ? { + publicOutput: PublicOutput; + } + : { + publicOutput: PublicOutput; + auxiliaryOutput: AuxiliaryOutput; + }; + type Method< PublicInput, PublicOutput, - Args extends Tuple + MethodSignature extends { + privateInputs: Tuple; + auxiliaryOutput?: ProvableType; + } > = PublicInput extends undefined ? { - privateInputs: Args; - method(...args: TupleToInstances): Promise; + method( + ...args: TupleToInstances + ): Promise< + MethodReturnType< + PublicOutput, + InferProvableOrUndefined> + > + >; } : { - privateInputs: Args; method( publicInput: PublicInput, - ...args: TupleToInstances - ): Promise; + ...args: TupleToInstances + ): Promise< + MethodReturnType< + PublicOutput, + InferProvableOrUndefined> + > + >; }; type Prover< PublicInput, PublicOutput, - Args extends Tuple + Args extends Tuple, + AuxiliaryOutput > = PublicInput extends undefined - ? ( - ...args: TupleToInstances - ) => Promise> + ? (...args: TupleToInstances) => Promise<{ + proof: Proof; + auxiliaryOutput: AuxiliaryOutput; + }> : ( publicInput: PublicInput, ...args: TupleToInstances - ) => Promise>; + ) => Promise<{ + proof: Proof; + auxiliaryOutput: AuxiliaryOutput; + }>; type ProvableOrUndefined = A extends undefined ? typeof Undefined @@ -1493,7 +1705,9 @@ type ProvableOrVoid = A extends undefined ? typeof Void : ToProvable; type InferProvableOrUndefined = A extends undefined ? undefined - : InferProvable; + : A extends ProvableType + ? InferProvable + : InferProvable | undefined; type InferProvableOrVoid = A extends undefined ? void : InferProvable; type UnwrapPromise

= P extends Promise ? T : never; diff --git a/src/lib/provable/crypto/foreign-curve.ts b/src/lib/provable/crypto/foreign-curve.ts index 0da5136554..2698dcbd83 100644 --- a/src/lib/provable/crypto/foreign-curve.ts +++ b/src/lib/provable/crypto/foreign-curve.ts @@ -12,6 +12,7 @@ import { assert } from '../gadgets/common.js'; import { Provable } from '../provable.js'; import { provableFromClass } from '../types/provable-derivers.js'; import { l2Mask, multiRangeCheck } from '../gadgets/range-check.js'; +import { Bytes } from '../bytes.js'; // external API export { createForeignCurve, ForeignCurve }; @@ -34,6 +35,8 @@ class ForeignCurve { /** * Create a new {@link ForeignCurve} from an object representing the (affine) x and y coordinates. * + * Note: Inputs must be range checked if they originate from a different field with a different modulus or if they are not constants. Please refer to the {@link ForeignField} constructor comments for more details. + * * @example * ```ts * let x = new ForeignCurve({ x: 1n, y: 1n }); @@ -64,6 +67,124 @@ class ForeignCurve { return new this(g); } + /** + * Parses a hexadecimal string representing an uncompressed elliptic curve point and coerces it into a {@link ForeignCurveV2} point. + * + * The method extracts the x and y coordinates from the provided hex string and verifies that the resulting point lies on the curve. + * + * **Note:** This method only supports uncompressed elliptic curve points, which are 65 bytes in total (1-byte prefix + 32 bytes for x + 32 bytes for y). + * + * @param hex - The hexadecimal string representing the uncompressed elliptic curve point. + * @returns - A point on the foreign curve, parsed from the given hexadecimal string. + * + * @throws - Throws an error if the input is not a valid public key. + * + * @example + * ```ts + * class Secp256k1 extends createForeignCurveV2(Crypto.CurveParams.Secp256k1) {} + * + * const publicKeyHex = '04f8b8db25c619d0c66b2dc9e97ecbafafae...'; // Example hex string for uncompressed point + * const point = Secp256k1.fromHex(publicKeyHex); + * ``` + * + * **Important:** This method is only designed to handle uncompressed elliptic curve points in hex format. + */ + static fromHex(hex: string) { + // trim the '0x' prefix if present + if (hex.startsWith('0x')) { + hex = hex.slice(2); + } + + const bytes = Bytes.fromHex(hex).toBytes(); + const sizeInBytes = Math.ceil(this.Bigint.Field.sizeInBits / 8); + + // extract x and y coordinates from the byte array + const tail = bytes.subarray(1); // skip the first byte (prefix) + const xBytes = tail.subarray(0, sizeInBytes); // first `sizeInBytes` bytes for x-coordinate + const yBytes = tail.subarray(sizeInBytes, 2 * sizeInBytes); // next `sizeInBytes` bytes for y-coordinate + + // convert byte arrays to bigint + const x = BigInt('0x' + Bytes.from(xBytes).toHex()); + const y = BigInt('0x' + Bytes.from(yBytes).toHex()); + + // construct the point on the curve using the x and y coordinates + let P = this.from({ x, y }); + + // ensure that the point is on the curve + P.assertOnCurve(); + + return P; + } + + /** + * Create a new {@link ForeignCurveV2} instance from an Ethereum public key in hex format, which may be either compressed or uncompressed. + * This method is designed to handle the parsing of public keys as used by the ethers.js library. + * + * The input should represent the affine x and y coordinates of the point, in hexadecimal format. + * Compressed keys are 33 bytes long and begin with 0x02 or 0x03, while uncompressed keys are 65 bytes long and begin with 0x04. + * + * **Warning:** This method is specifically designed for use with the Secp256k1 curve. Using it with other curves may result in incorrect behavior or errors. + * Ensure that the curve setup matches Secp256k1, as shown in the example, to avoid unintended issues. + * + * @example + * ```ts + * import { Wallet, Signature, getBytes } from 'ethers'; + * + * class Secp256k1 extends createForeignCurveV2(Crypto.CurveParams.Secp256k1) {} + * + * const wallet = Wallet.createRandom(); + * + * const publicKey = Secp256k1.fromEthers(wallet.publicKey.slice(2)); + * ``` + * + * @param hex - The public key as a hexadecimal string (without the "0x" prefix). + * @returns A new instance of the curve representing the given public key. + */ + static fromEthers(hex: string) { + // trim the '0x' prefix if present + if (hex.startsWith('0x')) { + hex = hex.slice(2); + } + + const bytes = Bytes.fromHex(hex).toBytes(); // convert hex string to Uint8Array + const len = bytes.length; + const head = bytes[0]; // first byte is the prefix (compression identifier) + const tail = bytes.slice(1); // remaining bytes contain the coordinates + + const xBytes = tail.slice(0, 32); // extract the x-coordinate (first 32 bytes) + const x = BigInt('0x' + Bytes.from(xBytes).toHex()); // convert Uint8Array to bigint + + let p: { x: bigint; y: bigint } | undefined = undefined; + + // handle compressed points (33 bytes, prefix 0x02 or 0x03) + if (len === 33 && [0x02, 0x03].includes(head)) { + // ensure x is within the valid field range + assert(0n < x && x < this.Bigint.Field.modulus); + + // compute the right-hand side of the curve equation: x³ + ax + b + const crvX = this.Bigint.Field.mod( + this.Bigint.Field.mod(x * x) * x + this.Bigint.b + ); + // compute the square root (y-coordinate) + let y = this.Bigint.Field.sqrt(crvX)!; + const isYOdd = (y & 1n) === 1n; // determine whether y is odd + const headOdd = (head & 1) === 1; // determine whether the prefix indicates an odd y + if (headOdd !== isYOdd) y = this.Bigint.Field.mod(-y); // adjust y if necessary + p = { x, y }; + } + + // handle uncompressed points (65 bytes, prefix 0x04) + if (len === 65 && head === 0x04) { + const yBytes = tail.slice(32, 64); // extract the y-coordinate (next 32 bytes) + p = { x, y: BigInt('0x' + Bytes.from(yBytes).toHex()) }; + } + + const P = this.from(p!); // create the curve point from the parsed coordinates + P.assertOnCurve(); // verify the point lies on the curve + + return P; + } + /** * The constant generator point. */ diff --git a/src/lib/provable/crypto/foreign-ecdsa.ts b/src/lib/provable/crypto/foreign-ecdsa.ts index 1251ffc364..ac0da1186c 100644 --- a/src/lib/provable/crypto/foreign-ecdsa.ts +++ b/src/lib/provable/crypto/foreign-ecdsa.ts @@ -33,7 +33,8 @@ class EcdsaSignature { /** * Create a new {@link EcdsaSignature} from an object containing the scalars r and s. - * @param signature + * + * Note: Inputs must be range checked if they originate from a different field with a different modulus or if they are not constants. Please refer to the {@link ForeignField} constructor comments for more details. */ constructor(signature: { r: AlmostForeignField | Field3 | bigint | number; @@ -107,6 +108,58 @@ class EcdsaSignature { return this.verifySignedHash(msgHash, publicKey); } + /** + * Verify an ECDSA signature generated by the ethers.js library, given the message (as a byte array) and a public key (a {@link Curve} point). + * The message digest used for signing follows the format defined in EIP-191, with the Ethereum-specific prefix. + * + * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. + * So, to actually prove validity of a signature, you need to assert that the result is true. + * + * **Note:** This method is specifically designed to verify signatures generated by ethers.js. + * Ensure that the curve being used is Secp256k1, as demonstrated in the example. + * + * @throws An error will be thrown if one of the signature scalars is zero or if the public key does not lie on the curve. + * + * @example + * ```ts + * import { Wallet } from 'ethers'; + * + * // create the class for Secp256k1 curve + * class Secp256k1 extends createForeignCurveV2(Crypto.CurveParams.Secp256k1) {} + * class Ecdsa extends createEcdsaV2(Secp256k1) {} + * + * // outside provable code: create inputs + * let message = 'my message'; + * let signatureRaw = await wallet.signMessage(message); + * let compressedPublicKey = wallet.signingKey.compressedPublicKey; + * + * // this also works for uncompressed public keys (wallet.signingKey.publicKey) + * let publicKey = Secp256k1.fromEthers(compressedPublicKey.slice(2)); + * let signature = Ecdsa.fromHex(signatureRaw); + * + * // ... + * // in provable code: create input witnesses (or use method inputs, or constants) + * // and verify the signature + * let isValid = signature.verifyEthers(Bytes.fromString(message), publicKey); + * isValid.assertTrue('signature verifies'); + * ``` + * + * @param message - The original message as a byte array. + * @param publicKey - The public key as a point on the Secp256k1 elliptic curve. + * @returns - A {@link Bool} indicating the validity of the signature. + */ + verifyEthers(message: Bytes, publicKey: FlexiblePoint): Bool { + const MessagePrefix = '\x19Ethereum Signed Message:\n'; // Ethereum-specific prefix for signing + const msgHashBytes = Keccak.ethereum([ + ...Bytes.fromString(MessagePrefix).bytes, // prefix for Ethereum signed messages + ...Bytes.fromString(String(message.length)).bytes, // message length as string + ...message.bytes, // actual message bytes + ]); + + let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve); + return this.verifySignedHash(msgHash, publicKey); + } + /** * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). * diff --git a/src/lib/provable/foreign-field.ts b/src/lib/provable/foreign-field.ts index 22e99b27ef..e13911a543 100644 --- a/src/lib/provable/foreign-field.ts +++ b/src/lib/provable/foreign-field.ts @@ -8,9 +8,8 @@ import { Field, checkBitLength, withMessage } from './field.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { Tuple, TupleMap, TupleN } from '../util/types.js'; -import { Field3 } from './gadgets/foreign-field.js'; import { Gadgets } from './gadgets/gadgets.js'; -import { ForeignField as FF } from './gadgets/foreign-field.js'; +import { ForeignField as FF, Field3 } from './gadgets/foreign-field.js'; import { assert } from './gadgets/common.js'; import { l3, l } from './gadgets/range-check.js'; import { ProvablePureExtended } from './types/struct.js'; @@ -92,10 +91,21 @@ class ForeignField { * ```ts * let x = new ForeignField(5); * ``` + * + * Note: Inputs must be range checked if they originate from a different field with a different modulus or if they are not constants. + * + * - When constructing from another {@link ForeignField} instance, ensure the modulus matches. If not, check the modulus using `Gadgets.ForeignField.assertLessThan()` and handle appropriately. + * - When constructing from a {@link Field3} array, ensure all elements are valid Field elements and range checked. + * - Ensure constants are correctly reduced to the modulus of the field. */ constructor(x: ForeignField | Field3 | bigint | number | string) { const p = this.modulus; if (x instanceof ForeignField) { + if (x.modulus !== p) { + throw new Error( + `ForeignField constructor: modulus mismatch. Expected ${p}, got ${x.modulus}. Please provide a value with the correct modulus. You can use 'Gadgets.ForeignField.assertLessThan()' to check it.` + ); + } this.value = x.value; return; } diff --git a/src/lib/provable/int.ts b/src/lib/provable/int.ts index c8297faebc..635f2e7c7a 100644 --- a/src/lib/provable/int.ts +++ b/src/lib/provable/int.ts @@ -939,8 +939,8 @@ class UInt32 extends CircuitValue { if (this.value.isConstant() && y.value.isConstant()) { return Bool(this.value.toBigInt() < y.value.toBigInt()); } - return lessThanGeneric(this.value, y.value, 1n << 64n, (v) => - RangeCheck.rangeCheckN(UInt64.NUM_BITS, v) + return lessThanGeneric(this.value, y.value, 1n << 32n, (v) => + RangeCheck.rangeCheckN(UInt32.NUM_BITS, v) ); } @@ -1185,14 +1185,22 @@ class Int64 extends CircuitValue implements BalanceChange { return Int64.create(UInt64.from(obj.magnitude), Sign.fromValue(obj.sgn)); } + /** + * Turns the {@link Int64} into a {@link BigInt}. + */ + toBigint() { + let abs = this.magnitude.toBigInt(); + let sgn = this.sgn.isPositive().toBoolean() ? 1n : -1n; + return sgn * abs; + } + /** * Turns the {@link Int64} into a string. */ toString() { - let abs = this.magnitude.toString(); - let sgn = this.isPositive().toBoolean() || abs === '0' ? '' : '-'; - return sgn + abs; + return this.toBigint().toString(); } + isConstant() { return this.magnitude.value.isConstant() && this.sgn.isConstant(); } @@ -1331,7 +1339,11 @@ class Int64 extends CircuitValue implements BalanceChange { let y_ = UInt64.from(y); let rest = this.magnitude.divMod(y_).rest.value; let isNonNegative = this.isNonNegative(); - rest = Provable.if(isNonNegative, rest, y_.value.sub(rest)); + rest = Provable.if( + isNonNegative.or(rest.equals(0)), + rest, + y_.value.sub(rest) + ); return new Int64(new UInt64(rest.value)); } diff --git a/src/lib/provable/string.ts b/src/lib/provable/string.ts index 5e727d2870..9480685652 100644 --- a/src/lib/provable/string.ts +++ b/src/lib/provable/string.ts @@ -92,7 +92,7 @@ class CircuitString extends Struct(RawCircuitString) { let isNull = this.values[i].isNull(); mask[i] = isNull.and(wasntNullAlready); wasntNullAlready = isNull.not().and(wasntNullAlready); - length.add(wasntNullAlready.toField()); + length = length.add(wasntNullAlready.toField()); } // mask has length n+1, the last element is true when `this` has no null char mask[n] = wasntNullAlready; diff --git a/src/lib/provable/test/arithmetic.unit-test.ts b/src/lib/provable/test/arithmetic.unit-test.ts index 490dd81e55..248529b0e2 100644 --- a/src/lib/provable/test/arithmetic.unit-test.ts +++ b/src/lib/provable/test/arithmetic.unit-test.ts @@ -21,7 +21,7 @@ let Arithmetic = ZkProgram({ divMod32: { privateInputs: [Field], async method(a: Field) { - return Gadgets.divMod32(a); + return { publicOutput: Gadgets.divMod32(a) }; }, }, divMod64: { @@ -83,7 +83,7 @@ await equivalentAsync({ from: [field], to: divModOutput }, { runs: 3 })( return divMod32Helper(x); }, async (x) => { - return (await Arithmetic.divMod32(x)).publicOutput; + return (await Arithmetic.divMod32(x)).proof.publicOutput; } ); diff --git a/src/lib/provable/test/bitwise.unit-test.ts b/src/lib/provable/test/bitwise.unit-test.ts index 04b463205d..33b7fe18f5 100644 --- a/src/lib/provable/test/bitwise.unit-test.ts +++ b/src/lib/provable/test/bitwise.unit-test.ts @@ -36,25 +36,25 @@ let Bitwise = ZkProgram({ xor: { privateInputs: [Field, Field], async method(a: Field, b: Field) { - return Gadgets.xor(a, b, 240); + return { publicOutput: Gadgets.xor(a, b, 240) }; }, }, notUnchecked: { privateInputs: [Field], async method(a: Field) { - return Gadgets.not(a, 240, false); + return { publicOutput: Gadgets.not(a, 240, false) }; }, }, notChecked: { privateInputs: [Field], async method(a: Field) { - return Gadgets.not(a, 240, true); + return { publicOutput: Gadgets.not(a, 240, true) }; }, }, and: { privateInputs: [Field, Field], async method(a: Field, b: Field) { - return Gadgets.and(a, b, 64); + return { publicOutput: Gadgets.and(a, b, 64) }; }, }, or: { @@ -66,32 +66,32 @@ let Bitwise = ZkProgram({ rot32: { privateInputs: [Field], async method(a: Field) { - return Gadgets.rotate32(a, 12, 'left'); + return { publicOutput: Gadgets.rotate32(a, 12, 'left') }; }, }, rot64: { privateInputs: [Field], async method(a: Field) { - return Gadgets.rotate64(a, 12, 'left'); + return { publicOutput: Gadgets.rotate64(a, 12, 'left') }; }, }, leftShift64: { privateInputs: [Field], async method(a: Field) { - return Gadgets.leftShift64(a, 12); + return { publicOutput: Gadgets.leftShift64(a, 12) }; }, }, leftShift32: { privateInputs: [Field], async method(a: Field) { Gadgets.rangeCheck32(a); - return Gadgets.leftShift32(a, 12); + return { publicOutput: Gadgets.leftShift32(a, 12) }; }, }, rightShift64: { privateInputs: [Field], async method(a: Field) { - return Gadgets.rightShift64(a, 12); + return { publicOutput: Gadgets.rightShift64(a, 12) }; }, }, }, @@ -156,7 +156,7 @@ await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs })( return x ^ y; }, async (x, y) => { - let proof = await Bitwise.xor(x, y); + let { proof } = await Bitwise.xor(x, y); return proof.publicOutput; } ); @@ -166,7 +166,7 @@ await equivalentAsync({ from: [maybeField], to: field }, { runs })( return Fp.not(x, 240); }, async (x) => { - let proof = await Bitwise.notUnchecked(x); + let { proof } = await Bitwise.notUnchecked(x); return proof.publicOutput; } ); diff --git a/src/lib/provable/test/custom-gates-recursion.unit-test.ts b/src/lib/provable/test/custom-gates-recursion.unit-test.ts index 51891195d1..4d14fecc18 100644 --- a/src/lib/provable/test/custom-gates-recursion.unit-test.ts +++ b/src/lib/provable/test/custom-gates-recursion.unit-test.ts @@ -43,7 +43,14 @@ let program = ZkProgram({ let msgHash_ = Provable.witness(Field3, () => msgHash); let publicKey_ = Provable.witness(Point, () => publicKey); - return Ecdsa.verify(Secp256k1, signature_, msgHash_, publicKey_); + return { + publicOutput: Ecdsa.verify( + Secp256k1, + signature_, + msgHash_, + publicKey_ + ), + }; }, }, }, @@ -55,8 +62,8 @@ await program.compile(); console.timeEnd('ecdsa verify (compile)'); console.time('ecdsa verify (prove)'); -let emptyProof = await emptyProgram.run(); -let proof = await program.ecdsa(emptyProof); +let { proof: emptyProof } = await emptyProgram.run(); +let { proof } = await program.ecdsa(emptyProof); console.timeEnd('ecdsa verify (prove)'); assert(await program.verify(proof), 'proof verifies'); diff --git a/src/lib/provable/test/ecdsa.unit-test.ts b/src/lib/provable/test/ecdsa.unit-test.ts index 531c7dfb81..c2633b05ed 100644 --- a/src/lib/provable/test/ecdsa.unit-test.ts +++ b/src/lib/provable/test/ecdsa.unit-test.ts @@ -151,13 +151,15 @@ let program = ZkProgram({ let msgHash_ = Provable.witness(Field3, () => msgHash); let publicKey_ = Provable.witness(Point, () => publicKey); - return Ecdsa.verify( - Secp256k1, - signature_, - msgHash_, - publicKey_, - config - ); + return { + publicOutput: Ecdsa.verify( + Secp256k1, + signature_, + msgHash_, + publicKey_, + config + ), + }; }, }, }, @@ -182,7 +184,7 @@ await program.compile(); console.timeEnd('ecdsa verify (compile)'); console.time('ecdsa verify (prove)'); -let proof = await program.ecdsa(); +let { proof } = await program.ecdsa(); console.timeEnd('ecdsa verify (prove)'); assert(await program.verify(proof), 'proof verifies'); diff --git a/src/lib/provable/test/foreign-field-gadgets.unit-test.ts b/src/lib/provable/test/foreign-field-gadgets.unit-test.ts index 5dbb68932e..86b58ea5fc 100644 --- a/src/lib/provable/test/foreign-field-gadgets.unit-test.ts +++ b/src/lib/provable/test/foreign-field-gadgets.unit-test.ts @@ -185,39 +185,41 @@ let ffProgram = ZkProgram({ sumchain: { privateInputs: [Provable.Array(Field3, chainLength)], async method(xs) { - return ForeignField.sum(xs, signs, F.modulus); + return { + publicOutput: ForeignField.sum(xs, signs, F.modulus), + }; }, }, mulWithBoundsCheck: { privateInputs: [Field3, Field3], async method(x, y) { ForeignField.assertAlmostReduced([x, y], F.modulus); - return ForeignField.mul(x, y, F.modulus); + return { publicOutput: ForeignField.mul(x, y, F.modulus) }; }, }, mul: { privateInputs: [Field3, Field3], async method(x, y) { - return ForeignField.mul(x, y, F.modulus); + return { publicOutput: ForeignField.mul(x, y, F.modulus) }; }, }, inv: { privateInputs: [Field3], async method(x) { - return ForeignField.inv(x, F.modulus); + return { publicOutput: ForeignField.inv(x, F.modulus) }; }, }, div: { privateInputs: [Field3, Field3], async method(x, y) { - return ForeignField.div(x, y, F.modulus); + return { publicOutput: ForeignField.div(x, y, F.modulus) }; }, }, assertLessThan: { privateInputs: [Field3, Field3], async method(x, y) { ForeignField.assertLessThan(x, y); - return x; + return { publicOutput: x }; }, }, }, @@ -268,7 +270,7 @@ await ffProgram.compile(); await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs })( (xs) => sum(xs, signs, F), async (xs) => { - let proof = await ffProgram.sumchain(xs); + let { proof } = await ffProgram.sumchain(xs); assert(await ffProgram.verify(proof), 'verifies'); return proof.publicOutput; }, @@ -282,7 +284,7 @@ await equivalentAsync({ from: [big264, big264], to: f }, { runs })( return F.mul(x, y); }, async (x, y) => { - let proof = await ffProgram.mulWithBoundsCheck(x, y); + let { proof } = await ffProgram.mulWithBoundsCheck(x, y); assert(await ffProgram.verify(proof), 'verifies'); return proof.publicOutput; }, @@ -292,7 +294,7 @@ await equivalentAsync({ from: [big264, big264], to: f }, { runs })( await equivalentAsync({ from: [f, f], to: f }, { runs })( (x, y) => F.div(x, y) ?? throwError('no inverse'), async (x, y) => { - let proof = await ffProgram.div(x, y); + let { proof } = await ffProgram.div(x, y); assert(await ffProgram.verify(proof), 'verifies'); return proof.publicOutput; }, @@ -302,7 +304,7 @@ await equivalentAsync({ from: [f, f], to: f }, { runs })( await equivalentAsync({ from: [f, f], to: unit }, { runs })( (x, y) => assert(x < y, 'not less than'), async (x, y) => { - let proof = await ffProgram.assertLessThan(x, y); + let { proof } = await ffProgram.assertLessThan(x, y); assert(await ffProgram.verify(proof), 'verifies'); }, 'prove less than' diff --git a/src/lib/provable/test/keccak.unit-test.ts b/src/lib/provable/test/keccak.unit-test.ts index 92e9eb8854..45ae54f173 100644 --- a/src/lib/provable/test/keccak.unit-test.ts +++ b/src/lib/provable/test/keccak.unit-test.ts @@ -127,13 +127,17 @@ const KeccakProgram = ZkProgram({ nistSha3: { privateInputs: [], async method(preImage: Bytes) { - return Keccak.nistSha3(digestLength, preImage); + return { + publicOutput: Keccak.nistSha3(digestLength, preImage), + }; }, }, preNist: { privateInputs: [], async method(preImage: Bytes) { - return Keccak.preNist(digestLength, preImage); + return { + publicOutput: Keccak.preNist(digestLength, preImage), + }; }, }, }, @@ -149,7 +153,7 @@ await equivalentAsync( }, { runs: RUNS } )(testImplementations.sha3[digestLength], async (x) => { - const proof = await KeccakProgram.nistSha3(x); + const { proof } = await KeccakProgram.nistSha3(x); await KeccakProgram.verify(proof); return proof.publicOutput; }); @@ -162,7 +166,7 @@ await equivalentAsync( }, { runs: RUNS } )(testImplementations.preNist[digestLength], async (x) => { - const proof = await KeccakProgram.preNist(x); + const { proof } = await KeccakProgram.preNist(x); await KeccakProgram.verify(proof); return proof.publicOutput; }); diff --git a/src/lib/provable/test/lookup.unit-test.ts b/src/lib/provable/test/lookup.unit-test.ts index 583a38ec18..0e0f0caeca 100644 --- a/src/lib/provable/test/lookup.unit-test.ts +++ b/src/lib/provable/test/lookup.unit-test.ts @@ -55,7 +55,7 @@ await equivalentAsync( return true; }, async (x, y, z) => { - let proof = await Lookup.three12Bit(x, y, z); + let { proof } = await Lookup.three12Bit(x, y, z); return await Lookup.verify(proof); } ); diff --git a/src/lib/provable/test/range-check.unit-test.ts b/src/lib/provable/test/range-check.unit-test.ts index fbb65befb7..01b5e393f5 100644 --- a/src/lib/provable/test/range-check.unit-test.ts +++ b/src/lib/provable/test/range-check.unit-test.ts @@ -112,7 +112,7 @@ await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs })( return true; }, async (x) => { - let proof = await RangeCheck.check64(x); + let { proof } = await RangeCheck.check64(x); return await RangeCheck.verify(proof); } ); @@ -123,7 +123,7 @@ await equivalentAsync({ from: [maybeUint(8)], to: boolean }, { runs })( return true; }, async (x) => { - let proof = await RangeCheck.check8(x); + let { proof } = await RangeCheck.check8(x); return await RangeCheck.verify(proof); } ); @@ -137,7 +137,7 @@ await equivalentAsync( return true; }, async (x, y, z) => { - let proof = await RangeCheck.checkMulti(x, y, z); + let { proof } = await RangeCheck.checkMulti(x, y, z); return await RangeCheck.verify(proof); } ); @@ -151,7 +151,7 @@ await equivalentAsync( return true; }, async (xy, z) => { - let proof = await RangeCheck.checkCompact(xy, z); + let { proof } = await RangeCheck.checkCompact(xy, z); return await RangeCheck.verify(proof); } ); diff --git a/src/lib/provable/test/sha256.unit-test.ts b/src/lib/provable/test/sha256.unit-test.ts index 16233721e9..e3165581cc 100644 --- a/src/lib/provable/test/sha256.unit-test.ts +++ b/src/lib/provable/test/sha256.unit-test.ts @@ -28,7 +28,9 @@ const Sha256Program = ZkProgram({ sha256: { privateInputs: [Bytes(192)], async method(preImage: Bytes) { - return Gadgets.SHA256.hash(preImage); + return { + publicOutput: Gadgets.SHA256.hash(preImage), + }; }, }, }, @@ -45,7 +47,7 @@ await equivalentAsync( }, { runs: RUNS } )(nobleSha256, async (x) => { - const proof = await Sha256Program.sha256(x); + const { proof } = await Sha256Program.sha256(x); await Sha256Program.verify(proof); return proof.publicOutput; }); diff --git a/src/lib/provable/test/string.unit-test.ts b/src/lib/provable/test/string.unit-test.ts index 8eccb4bee8..4360baddce 100644 --- a/src/lib/provable/test/string.unit-test.ts +++ b/src/lib/provable/test/string.unit-test.ts @@ -150,6 +150,18 @@ describe('Circuit String', () => { }); }); + describe('#length', () => { + test('length', async () => { + const str1 = CircuitString.fromString('abcd'); + expect(str1.length()).toEqual(Field(4)); + + await Provable.runAndCheck(() => { + const str1 = CircuitString.fromString('abcd'); + expect(str1.length()).toEqual(Field(4)); + }); + }); + }); + /* describe('CircuitString8', async () => { test('cannot create more than 8 chars', () => { expect(() => { diff --git a/src/mina b/src/mina index b6b427ab9c..9bdaae9990 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit b6b427ab9c2c30f76e4dbaa51925f8eb1a66552f +Subproject commit 9bdaae99904c13e3e10841ec6b778e45cdacabfb diff --git a/src/mina-signer/tests/verify-in-snark.unit-test.ts b/src/mina-signer/tests/verify-in-snark.unit-test.ts index 5ae1559006..fc76072b78 100644 --- a/src/mina-signer/tests/verify-in-snark.unit-test.ts +++ b/src/mina-signer/tests/verify-in-snark.unit-test.ts @@ -44,7 +44,7 @@ const MyProgram = ZkProgram({ }); await MyProgram.compile(); -let proof = await MyProgram.verifySignature(signature, fieldsSnarky); +let { proof } = await MyProgram.verifySignature(signature, fieldsSnarky); ok = await MyProgram.verify(proof); expect(ok).toEqual(true); diff --git a/src/tests/fake-proof.ts b/src/tests/fake-proof.ts index e1c3ebc232..68a4272f66 100644 --- a/src/tests/fake-proof.ts +++ b/src/tests/fake-proof.ts @@ -59,7 +59,7 @@ let { verificationKey: contractVk } = await RecursiveContract.compile(); let { verificationKey: programVk } = await RecursiveProgram.compile(); // proof that should be rejected -const fakeProof = await FakeProgram.make(UInt64.from(99999)); +const { proof: fakeProof } = await FakeProgram.make(UInt64.from(99999)); const dummyProof = await RealProof.dummy(undefined, undefined, 0); for (let proof of [fakeProof, dummyProof]) { @@ -76,10 +76,10 @@ for (let proof of [fakeProof, dummyProof]) { } // proof that should be accepted -const realProof = await RealProgram.make(UInt64.from(34)); +const { proof: realProof } = await RealProgram.make(UInt64.from(34)); // zkprogram accepts proof -const brokenProof = await RecursiveProgram.verifyReal(realProof); +const { proof: brokenProof } = await RecursiveProgram.verifyReal(realProof); assert( await verify(brokenProof, programVk.data), 'recursive program accepts real proof' diff --git a/tests/vk-regression/diverse-zk-program-run.ts b/tests/vk-regression/diverse-zk-program-run.ts index 7aa57007dc..9db2e4f59b 100644 --- a/tests/vk-regression/diverse-zk-program-run.ts +++ b/tests/vk-regression/diverse-zk-program-run.ts @@ -4,8 +4,8 @@ import { diverse, Bytes128 } from './diverse-zk-program.js'; console.log('testing proof generation for diverse program'); await diverse.compile(); -let proof1 = await diverse.sha3(Bytes128.fromString('hello')); +let { proof: proof1 } = await diverse.sha3(Bytes128.fromString('hello')); assert(await diverse.verify(proof1), 'verifies'); -let proof2 = await diverse.recursive(proof1); +let { proof: proof2 } = await diverse.recursive(proof1); assert(await diverse.verify(proof2), 'verifies'); diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 0fa1795096..ed79c06172 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -1,4 +1,30 @@ { + "ecdsa-only": { + "digest": "19133e0612c2ca2116e67ea9955f71be09c53a2bbf2e5973639cfa5fa022ad3b", + "methods": { + "verifySignedHash": { + "rows": 31723, + "digest": "7f7068290459ac8114b1387d24f2625f" + } + }, + "verificationKey": { + "data": "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", + "hash": "8404826415197066458581554790856706704824768794891307453589930251855855541742" + } + }, + "ecdsa": { + "digest": "1d55da663014c4a67c32905ab7b3af6b0ec7682931048131f9ef4454eabe9b8f", + "methods": { + "verifyEcdsa": { + "rows": 46221, + "digest": "d0f4f1d987923683410ebeeee0e3cca1" + } + }, + "verificationKey": { + "data": "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", + "hash": "17783922953240968838867755545493795808631617074054894211315824411009689671778" + } + }, "Voting_": { "digest": "2d12b9235eac7fe8188447969463455160abb01ddd682efdd4147fe28fc8c608", "methods": { @@ -84,7 +110,7 @@ } }, "Dex": { - "digest": "3efb15c159820ef627a46e636c3768e5687853503bbc0ed6a40ccf56ad6c32dd", + "digest": "c4f3529db60868ed3a04dc48a8e09fbb9e5814755f479570dede6a3fb84821", "methods": { "approveBase": { "rows": 13292, @@ -92,15 +118,15 @@ }, "supplyLiquidityBase": { "rows": 2872, - "digest": "a9ab9ae0fbd241891385e762d8fd62ae" + "digest": "532a9120613a7204a72c74769f77f7c0" }, "swapX": { - "rows": 1531, - "digest": "9f11174b5c26f9cbfbfff9161558906a" + "rows": 1532, + "digest": "c4f96b4cfded7cae567c8bc674ffd18d" }, "swapY": { - "rows": 1531, - "digest": "9623dfa8a815b7d3ff5e1c9016b4234e" + "rows": 1532, + "digest": "60d123f69c83b6a2a6a6559bf38b5d44" }, "burnLiquidity": { "rows": 706, @@ -108,8 +134,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "11527984898950277801428921124080901337689573696672171015789096481626287756722" + "data": "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", + "hash": "19613037700896478935099708875657216340283002926344380158542484760921628654473" } }, "Group Primitive": { @@ -269,30 +295,17 @@ "hash": "" } }, - "ecdsa-only": { - "digest": "19133e0612c2ca2116e67ea9955f71be09c53a2bbf2e5973639cfa5fa022ad3b", + "ecdsa-ethers": { + "digest": "1e5226693cb4d6e6de8d9ffa1b8d5c845912b03d8e64a3b11120dffa30100571", "methods": { - "verifySignedHash": { - "rows": 31723, - "digest": "7f7068290459ac8114b1387d24f2625f" + "verifyEthers": { + "rows": 46248, + "digest": "2278723f3c48ee7041964ebfc58f9f0f" } }, "verificationKey": { - "data": "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", - "hash": "8404826415197066458581554790856706704824768794891307453589930251855855541742" - } - }, - "ecdsa": { - "digest": "1d55da663014c4a67c32905ab7b3af6b0ec7682931048131f9ef4454eabe9b8f", - "methods": { - "verifyEcdsa": { - "rows": 46221, - "digest": "d0f4f1d987923683410ebeeee0e3cca1" - } - }, - "verificationKey": { - "data": "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", - "hash": "17783922953240968838867755545493795808631617074054894211315824411009689671778" + "data": "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", + "hash": "24484167779607701468930060142561645630114715000442156022185160719190573844352" } }, "sha256": { diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index 24a7370b50..c9ae32cdb4 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -6,6 +6,7 @@ import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; import { ecdsa, keccakAndEcdsa, + ecdsaEthers, } from '../../src/examples/crypto/ecdsa/ecdsa.js'; import { SHA256Program } from '../../src/examples/crypto/sha256/sha256.js'; import { BLAKE2BProgram } from '../../src/examples/crypto/blake2b/blake2b.js' @@ -27,6 +28,7 @@ const forceRecompile = false; // usage ./run ./tests/vk-regression/vk-regression.ts --bundle --dump ./tests/vk-regression/vk-regression.json let dump = process.argv[4] === '--dump'; let jsonPath = process.argv[dump ? 5 : 4]; +let vkTest = process.env.VK_TEST; type MinimumConstraintSystem = { analyzeMethods(): Promise< @@ -49,6 +51,8 @@ type MinimumConstraintSystem = { }; const ConstraintSystems: MinimumConstraintSystem[] = [ + ecdsa, + keccakAndEcdsa, Voting_, Membership_, HelloWorld, @@ -59,13 +63,24 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ HashCS, BasicCS, CryptoCS, - ecdsa, - keccakAndEcdsa, + ecdsaEthers, SHA256Program, BLAKE2BProgram, diverse, ]; +let selectedConstraintSystems: MinimumConstraintSystem[] = []; + +if (vkTest === '1') { + selectedConstraintSystems = ConstraintSystems.slice(0, 10); + console.log('Running regression checks - Part 1'); +} else if (vkTest === '2') { + selectedConstraintSystems = ConstraintSystems.slice(10); + console.log('Running regression checks - Part 2'); +} else if (!dump) { + throw new Error('Invalid VK_TEST value. Please set VK_TEST to 1 or 2!'); +} + let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; let RegressionJson: { [contractName: string]: { @@ -173,4 +188,4 @@ async function dumpVk(contracts: typeof ConstraintSystems) { } if (dump) await dumpVk(ConstraintSystems); -else await checkVk(ConstraintSystems); +else await checkVk(selectedConstraintSystems);