From 0ae823f283db885656d4a4f0f7ab1fb011e4a2e4 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 16 Jul 2024 09:54:02 -0700 Subject: [PATCH 1/4] chore(bindings): update subproject commit reference to 4303e9ebbc94f248489404ce174417ad08f49260 --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index df8c87ed68..4303e9ebbc 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit df8c87ed6804465f79196fdff84e5147ae71e92d +Subproject commit 4303e9ebbc94f248489404ce174417ad08f49260 From 3dfc510bedfa91dc770f214c1ac7bf1b307a08c8 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 16 Jul 2024 11:28:02 -0700 Subject: [PATCH 2/4] feat(elliptic-curve.ts): specify the type of 'sum' variable as a GroupAffine type for clarity and better code readability --- src/lib/provable/gadgets/elliptic-curve.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/provable/gadgets/elliptic-curve.ts b/src/lib/provable/gadgets/elliptic-curve.ts index afab631f6e..3805b49b8a 100644 --- a/src/lib/provable/gadgets/elliptic-curve.ts +++ b/src/lib/provable/gadgets/elliptic-curve.ts @@ -11,6 +11,7 @@ import { } from '../../../bindings/crypto/bigint-helpers.js'; import { CurveAffine, + GroupAffine, affineAdd, affineDouble, } from '../../../bindings/crypto/elliptic-curve.js'; @@ -387,7 +388,7 @@ function multiScalarMulConstant( // TODO dedicated MSM let s = scalars.map(Field3.toBigint); let P = points.map(Point.toBigint); - let sum = Curve.zero; + let sum: GroupAffine = Curve.zero; for (let i = 0; i < n; i++) { if (useGlv) { sum = Curve.add(sum, Curve.Endo.scale(P[i], s[i])); From b6b90fcde723d6c31bc45a279e2153fd2dd8dfd0 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 16 Jul 2024 11:50:55 -0700 Subject: [PATCH 3/4] chore(bindings): update subproject commit reference to 73554aa95814a8098c28fb34c1d10238d79c04b1 --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 4303e9ebbc..73554aa958 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4303e9ebbc94f248489404ce174417ad08f49260 +Subproject commit 73554aa95814a8098c28fb34c1d10238d79c04b1 From f0156e2a993f7c77ef547921e89a3110dc3f38a9 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 26 Jul 2024 10:41:53 -0600 Subject: [PATCH 4/4] Merge branch main into feature/infinity-flag --- .github/actions/live-tests-shared/action.yml | 2 +- .github/workflows/benchmarks.yml | 2 +- .github/workflows/build-action.yml | 220 ++++++++++++++++-- .github/workflows/doc.yml | 2 +- .github/workflows/pkg-pr-new-publish.yml | 4 +- CHANGELOG.md | 11 +- benchmark/benchmarks/ecdsa.ts | 6 +- package-lock.json | 4 +- package.json | 2 +- run-ci-tests.sh | 9 - src/bindings | 2 +- src/examples/benchmarks/foreign-field.ts | 10 +- src/examples/benchmarks/keccak-witness.ts | 2 +- src/examples/crypto/ecdsa/ecdsa.ts | 8 +- src/examples/crypto/foreign-field.ts | 2 +- src/examples/crypto/rsa/rsa.ts | 14 +- src/examples/crypto/sha256/sha256.ts | 4 +- src/lib/mina/account-update.ts | 103 +++++--- src/lib/mina/account-update.unit-test.ts | 20 ++ src/lib/mina/actions/action-types.ts | 25 +- src/lib/mina/actions/batch-reducer.ts | 18 +- .../mina/actions/batch-reducer.unit-test.ts | 6 +- src/lib/mina/actions/offchain-state-rollup.ts | 14 +- .../actions/offchain-state-serialization.ts | 20 +- src/lib/mina/actions/offchain-state.ts | 6 +- src/lib/mina/actions/reducer.ts | 8 +- src/lib/mina/errors.ts | 27 ++- src/lib/mina/mina.ts | 5 +- src/lib/mina/state.ts | 10 +- src/lib/mina/token/forest-iterator.ts | 9 +- src/lib/mina/zkapp.ts | 13 +- src/lib/proof-system/zkprogram.ts | 42 +++- src/lib/provable/crypto/foreign-curve.ts | 12 +- src/lib/provable/crypto/foreign-ecdsa.ts | 12 +- src/lib/provable/crypto/poseidon.ts | 5 +- src/lib/provable/foreign-field.ts | 12 +- src/lib/provable/gadgets/bitwise.ts | 53 +++-- src/lib/provable/gadgets/comparison.ts | 2 +- src/lib/provable/gadgets/elliptic-curve.ts | 33 ++- src/lib/provable/gadgets/foreign-field.ts | 3 +- src/lib/provable/gadgets/gadgets.ts | 22 +- src/lib/provable/merkle-list.ts | 43 ++-- src/lib/provable/merkle-tree-indexed.ts | 14 +- src/lib/provable/option.ts | 5 +- src/lib/provable/packed.ts | 31 +-- src/lib/provable/provable.ts | 64 +++-- src/lib/provable/scalar-field.ts | 4 +- src/lib/provable/test/arithmetic.unit-test.ts | 5 +- src/lib/provable/test/bitwise.unit-test.ts | 16 +- .../test/custom-gates-recursion.unit-test.ts | 9 +- src/lib/provable/test/ecdsa.unit-test.ts | 18 +- .../provable/test/foreign-curve.unit-test.ts | 10 +- .../test/foreign-field-gadgets.unit-test.ts | 24 +- src/lib/provable/test/keccak.unit-test.ts | 4 +- .../provable/test/merkle-tree.unit-test.ts | 4 +- src/lib/provable/test/sha256.unit-test.ts | 4 +- src/lib/provable/test/struct.unit-test.ts | 8 +- src/lib/provable/types/provable-derivers.ts | 69 +++++- src/lib/provable/types/provable-intf.ts | 46 +++- src/lib/provable/types/struct.ts | 4 +- src/lib/provable/types/unconstrained.ts | 25 +- src/lib/provable/types/witness.ts | 54 +++-- src/lib/testing/random.ts | 20 ++ src/mina | 2 +- src/tests/inductive-proofs-small.ts | 7 +- src/tests/inductive-proofs.ts | 18 +- tests/vk-regression/diverse-zk-program.ts | 8 +- .../vk-regression/plain-constraint-system.ts | 8 +- 68 files changed, 869 insertions(+), 439 deletions(-) diff --git a/.github/actions/live-tests-shared/action.yml b/.github/actions/live-tests-shared/action.yml index 15576fbfe5..6dda1b2f85 100644 --- a/.github/actions/live-tests-shared/action.yml +++ b/.github/actions/live-tests-shared/action.yml @@ -21,7 +21,7 @@ runs: env: USE_CUSTOM_LOCAL_NETWORK: 'true' run: | - git submodule update --init --recursive + GIT_LFS_SKIP_SMUDGE=1 git submodule update --init --recursive npm ci npm run build touch profiling.md diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index b234c94a07..ec1a4d969d 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -33,7 +33,7 @@ jobs: METRICS_SOURCE_ENVIRONMENT: 'o1js GitHub Actions' METRICS_BASE_BRANCH_FOR_COMPARISON: 'main' run: | - git submodule update --init --recursive + GIT_LFS_SKIP_SMUDGE=1 git submodule update --init --recursive npm ci npm run build echo 'Running o1js benchmarks.' diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml index b01291da79..6aa2ee3974 100644 --- a/.github/workflows/build-action.yml +++ b/.github/workflows/build-action.yml @@ -8,7 +8,53 @@ on: workflow_dispatch: {} jobs: + Prepare: + runs-on: ubuntu-latest + outputs: + test_count: ${{ steps.count_tests.outputs.test_count }} + chunk_count: 8 # This is hardcoded to 8, but it can be changed to any number. + steps: + - name: Checkout repository with submodules + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Cache dependencies and build + uses: actions/cache@v4 + id: cache + with: + path: | + ~/.npm + node_modules + dist + key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.js') }} + + - name: Build o1js + if: steps.cache.outputs.cache-hit != 'true' + run: | + npm ci + npm run build + + - name: Count tests + id: count_tests + run: | + TEST_COUNT=$(find ./dist/node -name "*.unit-test.js" | wc -l) + echo "test_count=${TEST_COUNT}" >> $GITHUB_OUTPUT + echo "Total test count: ${TEST_COUNT}" + + - name: Cache repository + uses: actions/cache@v4 + with: + path: . + key: repo-${{ github.sha }} + Build-And-Test-Server: + needs: Prepare timeout-minutes: 210 runs-on: ubuntu-latest strategy: @@ -21,51 +67,167 @@ jobs: 'DEX integration tests', 'DEX integration test with proofs', 'Voting integration tests', - 'Unit tests', 'Verification Key Regression Check', 'CommonJS test', ] steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Restore repository + uses: actions/cache@v4 + with: + path: . + key: repo-${{ github.sha }} + - name: Setup Node uses: actions/setup-node@v4 with: node-version: '18' - - name: Build o1js and execute tests + + - name: Restore cache + uses: actions/cache@v4 + with: + path: | + ~/.npm + node_modules + dist + key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.js') }} + + - name: Prepare for tests + run: touch profiling.md + + - name: Execute tests env: TEST_TYPE: ${{ matrix.test_type }} + run: sh run-ci-tests.sh + + - name: Add to job summary + if: always() + run: | + echo "### Test Results for ${{ matrix.test_type }}" >> $GITHUB_STEP_SUMMARY + cat profiling.md >> $GITHUB_STEP_SUMMARY + + Run-Unit-Tests: + needs: Prepare + name: Run unit tests parallel + timeout-minutes: 210 + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3, 4, 5, 6, 7, 8] + steps: + - name: Restore repository + uses: actions/cache@v4 + with: + path: . + key: repo-${{ github.sha }} + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Restore cache + uses: actions/cache@v4 + with: + path: | + ~/.npm + node_modules + dist + key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.js') }} + + - name: Prepare for tests + run: touch profiling.md + + - name: Run unit tests + env: + TOTAL_TESTS: ${{ needs.Prepare.outputs.test_count }} + CHUNK: ${{ matrix.chunk }} + CHUNKS: 8 + run: | + echo "Total tests: $TOTAL_TESTS" + echo "Current chunk: $CHUNK" + echo "Total chunks: $CHUNKS" + + if [ -z "$TOTAL_TESTS" ] || [ "$TOTAL_TESTS" -eq 0 ]; then + echo "Error: TOTAL_TESTS is not set or is zero. Exiting." + exit 1 + fi + + start_index=$(( (TOTAL_TESTS * (CHUNK - 1) / CHUNKS) )) + end_index=$(( (TOTAL_TESTS * CHUNK / CHUNKS) )) + + echo "Running tests from index $start_index to $end_index" + + shopt -s globstar + test_files=(./dist/node/**/*.unit-test.js) + + for ((i=start_index; i> $GITHUB_STEP_SUMMARY cat profiling.md >> $GITHUB_STEP_SUMMARY + Build-And-Test-Server-Unit-Tests: + name: Build-And-Test-Server (Unit tests) + needs: [Run-Unit-Tests] + runs-on: ubuntu-latest + steps: + - run: echo "All unit tests completed successfully" + Build-And-Test-Web: + needs: Prepare timeout-minutes: 90 runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Restore repository + uses: actions/cache@v4 + with: + path: . + key: repo-${{ github.sha }} + - name: Setup Node uses: actions/setup-node@v4 with: node-version: '18' - - name: Install Node dependencies - run: | - git submodule update --init --recursive - npm ci + + - name: Restore npm cache + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} + + - name: Cache Playwright browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.OS }}-playwright-${{ hashFiles('**/package-lock.json') }} + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' run: npm run e2e:install + - name: Build o1js and prepare the web server run: | npm run build:web npm run e2e:prepare-server + - name: Execute E2E tests run: npm run test:e2e + - name: Upload E2E test artifacts uses: actions/upload-artifact@v4 continue-on-error: true @@ -80,19 +242,24 @@ jobs: if: github.ref == 'refs/heads/main' timeout-minutes: 180 runs-on: ubuntu-latest - needs: [Build-And-Test-Server, Build-And-Test-Web] + needs: [Build-And-Test-Server, Run-Unit-Tests, Build-And-Test-Web] steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Restore repository + uses: actions/cache@v4 + with: + path: . + key: repo-${{ github.sha }} + - name: Setup Node uses: actions/setup-node@v4 with: node-version: '18' + - name: Build o1js run: | - git submodule update --init --recursive npm ci - npm run build + npm run prepublishOnly + - name: Publish to NPM if version has changed uses: JS-DevTools/npm-publish@v3 with: @@ -105,21 +272,26 @@ jobs: if: github.ref == 'refs/heads/main' timeout-minutes: 180 runs-on: ubuntu-latest - needs: [Build-And-Test-Server, Build-And-Test-Web] + needs: [Build-And-Test-Server, Run-Unit-Tests, Build-And-Test-Web] steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Restore repository + uses: actions/cache@v4 + with: + path: . + key: repo-${{ github.sha }} + - name: Setup Node uses: actions/setup-node@v4 with: node-version: '18' + - name: Build mina-signer run: | - git submodule update --init --recursive npm ci cd src/mina-signer npm ci npm run prepublishOnly + - name: Publish to NPM if version has changed uses: JS-DevTools/npm-publish@v3 with: diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 1d266fa3c9..6bd4433615 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -16,7 +16,7 @@ jobs: node-version: '18' - name: Run typedoc run: | - git submodule update --init --recursive + GIT_LFS_SKIP_SMUDGE=1 git submodule update --init --recursive npm ci npx typedoc --tsconfig tsconfig.node.json src/index.ts - name: Deploy diff --git a/.github/workflows/pkg-pr-new-publish.yml b/.github/workflows/pkg-pr-new-publish.yml index f075ad0da6..134650eec4 100644 --- a/.github/workflows/pkg-pr-new-publish.yml +++ b/.github/workflows/pkg-pr-new-publish.yml @@ -24,9 +24,9 @@ jobs: node-version: ${{ matrix.node }} - name: Build o1js and mina-signer run: | - git submodule update --init --recursive + GIT_LFS_SKIP_SMUDGE=1 git submodule update --init --recursive npm ci - npm run build + npm run prepublishOnly cd src/mina-signer npm ci npm run prepublishOnly diff --git a/CHANGELOG.md b/CHANGELOG.md index b78c69636e..674377a9e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,21 @@ 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/1c736add...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/d6abf1d97...HEAD) + +## [1.6.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...d6abf1d97) - 2024-07-23 ### Added - `SmartContract.emitEventIf()` to conditionally emit an event https://github.com/o1-labs/o1js/pull/1746 +### Changed + +- Reduced maximum bit length for `xor`, `not`, and `and`, operations from 254 to 240 bits to prevent overflow vulnerabilities. https://github.com/o1-labs/o1js/pull/1745 +- Allow using `Type` instead of `Type.provable` in APIs that expect a provable type https://github.com/o1-labs/o1js/pull/1751 + - Example: `Provable.witness(Bytes32, () => bytes)` +- Automatically wrap and unwrap `Unconstrained` in `fromValue` and `toValue`, so that we don't need to deal with "unconstrained" values outside provable code https://github.com/o1-labs/o1js/pull/1751 + ## [1.5.0](https://github.com/o1-labs/o1js/compare/ed198f305...1c736add) - 2024-07-09 ### Breaking changes diff --git a/benchmark/benchmarks/ecdsa.ts b/benchmark/benchmarks/ecdsa.ts index 35f54b9871..ed7cf80d21 100644 --- a/benchmark/benchmarks/ecdsa.ts +++ b/benchmark/benchmarks/ecdsa.ts @@ -27,9 +27,9 @@ const EcdsaBenchmarks = benchmark( tic('witness generation'); await Provable.runAndCheck(async () => { - let message_ = Provable.witness(Bytes32.provable, () => message); - let signature_ = Provable.witness(Ecdsa.provable, () => signature); - let publicKey_ = Provable.witness(Secp256k1.provable, () => publicKey); + let message_ = Provable.witness(Bytes32, () => message); + let signature_ = Provable.witness(Ecdsa, () => signature); + let publicKey_ = Provable.witness(Secp256k1, () => publicKey); await keccakAndEcdsa.rawMethods.verifyEcdsa( message_, signature_, diff --git a/package-lock.json b/package-lock.json index e24c0e6277..3a8ecdd703 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "1.5.0", + "version": "1.6.0", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index ba746fd441..424ff0bfe8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "1.5.0", + "version": "1.6.0", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "repository": { diff --git a/run-ci-tests.sh b/run-ci-tests.sh index f005d78e0f..c5153b514e 100755 --- a/run-ci-tests.sh +++ b/run-ci-tests.sh @@ -34,15 +34,6 @@ case $TEST_TYPE in ./run src/examples/zkapps/dex/happy-path-with-proofs.ts --bundle ;; -"Unit tests") - echo "Running unit tests" - cd src/mina-signer - npm run build - cd ../.. - npm run test:unit - npm run test - ;; - "Verification Key Regression Check") echo "Running Regression checks" ./run ./tests/vk-regression/vk-regression.ts --bundle diff --git a/src/bindings b/src/bindings index 73554aa958..d779882a38 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 73554aa95814a8098c28fb34c1d10238d79c04b1 +Subproject commit d779882a38eac79703cc7215cf9bc5fc0cd5cd65 diff --git a/src/examples/benchmarks/foreign-field.ts b/src/examples/benchmarks/foreign-field.ts index 30190324ef..8f7f8a31fe 100644 --- a/src/examples/benchmarks/foreign-field.ts +++ b/src/examples/benchmarks/foreign-field.ts @@ -5,14 +5,8 @@ class ForeignScalar extends createForeignField( ) {} function main() { - let s = Provable.witness( - ForeignScalar.Canonical.provable, - ForeignScalar.random - ); - let t = Provable.witness( - ForeignScalar.Canonical.provable, - ForeignScalar.random - ); + let s = Provable.witness(ForeignScalar.Canonical, ForeignScalar.random); + let t = Provable.witness(ForeignScalar.Canonical, ForeignScalar.random); s.mul(t); } diff --git a/src/examples/benchmarks/keccak-witness.ts b/src/examples/benchmarks/keccak-witness.ts index e4509c5019..f37c16893f 100644 --- a/src/examples/benchmarks/keccak-witness.ts +++ b/src/examples/benchmarks/keccak-witness.ts @@ -4,7 +4,7 @@ let Bytes32 = Bytes(32); console.time('keccak witness'); await Provable.runAndCheck(() => { - let bytes = Provable.witness(Bytes32.provable, () => Bytes32.random()); + let bytes = Provable.witness(Bytes32, () => Bytes32.random()); Hash.Keccak256.hash(bytes); }); console.timeEnd('keccak witness'); diff --git a/src/examples/crypto/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts index 3d7c1a9d5c..020dbf9d83 100644 --- a/src/examples/crypto/ecdsa/ecdsa.ts +++ b/src/examples/crypto/ecdsa/ecdsa.ts @@ -16,12 +16,12 @@ class Bytes32 extends Bytes(32) {} const keccakAndEcdsa = ZkProgram({ name: 'ecdsa', - publicInput: Bytes32.provable, + publicInput: Bytes32, publicOutput: Bool, methods: { verifyEcdsa: { - privateInputs: [Ecdsa.provable, Secp256k1.provable], + privateInputs: [Ecdsa, Secp256k1], async method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) { return signature.verifyV2(message, publicKey); }, @@ -31,12 +31,12 @@ const keccakAndEcdsa = ZkProgram({ const ecdsa = ZkProgram({ name: 'ecdsa-only', - publicInput: Scalar.provable, + publicInput: Scalar, publicOutput: Bool, methods: { verifySignedHash: { - privateInputs: [Ecdsa.provable, Secp256k1.provable], + privateInputs: [Ecdsa, Secp256k1], async method(message: Scalar, signature: Ecdsa, publicKey: Secp256k1) { return signature.verifySignedHashV2(message, publicKey); }, diff --git a/src/examples/crypto/foreign-field.ts b/src/examples/crypto/foreign-field.ts index 044d8ebda6..c8860275c8 100644 --- a/src/examples/crypto/foreign-field.ts +++ b/src/examples/crypto/foreign-field.ts @@ -88,7 +88,7 @@ assert(uCanonical instanceof SmallField.Canonical); class AlmostSmallField extends SmallField.AlmostReduced {} class MyContract extends SmartContract { - @state(AlmostSmallField.provable) x = State(); + @state(AlmostSmallField) x = State(); @method async myMethod(y: AlmostSmallField) { let x = y.mul(2); diff --git a/src/examples/crypto/rsa/rsa.ts b/src/examples/crypto/rsa/rsa.ts index 8d54376426..7e2a17395d 100644 --- a/src/examples/crypto/rsa/rsa.ts +++ b/src/examples/crypto/rsa/rsa.ts @@ -1,14 +1,7 @@ /** * RSA signature verification with o1js */ -import { - Field, - Gadgets, - Provable, - Struct, - Unconstrained, - provable, -} from 'o1js'; +import { Field, Gadgets, Provable, Struct, Unconstrained } from 'o1js'; export { Bigint2048, rsaVerify65537 }; @@ -21,7 +14,7 @@ const Field18 = Provable.Array(Field, 18); class Bigint2048 extends Struct({ fields: Field18, - value: Unconstrained.provable as Provable>, + value: Unconstrained.withEmpty(0n), }) { modMul(x: Bigint2048, y: Bigint2048) { return multiply(x, y, this); @@ -66,7 +59,8 @@ function multiply( // witness q, r so that x*y = q*p + r // this also adds the range checks in `check()` let { q, r } = Provable.witness( - provable({ q: Bigint2048, r: Bigint2048 }), + // TODO Struct() should be unnecessary + Struct({ q: Bigint2048, r: Bigint2048 }), () => { let xy = x.toBigint() * y.toBigint(); let p0 = p.toBigint(); diff --git a/src/examples/crypto/sha256/sha256.ts b/src/examples/crypto/sha256/sha256.ts index a6e335fd7f..5ffe43c333 100644 --- a/src/examples/crypto/sha256/sha256.ts +++ b/src/examples/crypto/sha256/sha256.ts @@ -6,10 +6,10 @@ class Bytes12 extends Bytes(12) {} let SHA256Program = ZkProgram({ name: 'sha256', - publicOutput: Bytes(32).provable, + publicOutput: Bytes(32), methods: { sha256: { - privateInputs: [Bytes12.provable], + privateInputs: [Bytes12], async method(xs: Bytes12) { return Gadgets.SHA256.hash(xs); }, diff --git a/src/lib/mina/account-update.ts b/src/lib/mina/account-update.ts index f40b7b7c78..f50c95da36 100644 --- a/src/lib/mina/account-update.ts +++ b/src/lib/mina/account-update.ts @@ -3,7 +3,11 @@ import { FlexibleProvable, StructNoJson, } from '../provable/types/struct.js'; -import { provable, provablePure } from '../provable/types/provable-derivers.js'; +import { + provable, + provableExtends, + provablePure, +} from '../provable/types/provable-derivers.js'; import { memoizationContext, memoizeWitness, @@ -39,6 +43,7 @@ import { Memo } from '../../mina-signer/src/memo.js'; import { Events as BaseEvents, Actions as BaseActions, + MayUseToken as BaseMayUseToken, } from '../../bindings/mina-transaction/transaction-leaves.js'; import { TokenId as Base58TokenId } from './base58-encodings.js'; import { @@ -131,7 +136,30 @@ type AuthRequired = Types.Json.AuthRequired; type AccountUpdateBody = Types.AccountUpdate['body']; type Update = AccountUpdateBody['update']; -type MayUseToken = AccountUpdateBody['mayUseToken']; +type MayUseToken = BaseMayUseToken; +const MayUseToken = { + type: BaseMayUseToken, + No: { + parentsOwnToken: Bool(false), + inheritFromParent: Bool(false), + }, + ParentsOwnToken: { + parentsOwnToken: Bool(true), + inheritFromParent: Bool(false), + }, + InheritFromParent: { + parentsOwnToken: Bool(false), + inheritFromParent: Bool(true), + }, + isNo: ({ + body: { + mayUseToken: { parentsOwnToken, inheritFromParent }, + }, + }: AccountUpdate) => parentsOwnToken.or(inheritFromParent).not(), + isParentsOwnToken: (a: AccountUpdate) => a.body.mayUseToken.parentsOwnToken, + isInheritFromParent: (a: AccountUpdate) => + a.body.mayUseToken.inheritFromParent, +}; type Events = BaseEvents; const Events = { @@ -1200,33 +1228,7 @@ class AccountUpdate implements Types.AccountUpdate { return Provable.witnessAsync(combinedType, compute); } - static get MayUseToken() { - return { - type: provablePure({ parentsOwnToken: Bool, inheritFromParent: Bool }), - No: { parentsOwnToken: Bool(false), inheritFromParent: Bool(false) }, - ParentsOwnToken: { - parentsOwnToken: Bool(true), - inheritFromParent: Bool(false), - }, - InheritFromParent: { - parentsOwnToken: Bool(false), - inheritFromParent: Bool(true), - }, - isNo({ - body: { - mayUseToken: { parentsOwnToken, inheritFromParent }, - }, - }: AccountUpdate) { - return parentsOwnToken.or(inheritFromParent).not(); - }, - isParentsOwnToken(a: AccountUpdate) { - return a.body.mayUseToken.parentsOwnToken; - }, - isInheritFromParent(a: AccountUpdate) { - return a.body.mayUseToken.inheritFromParent; - }, - }; - } + static MayUseToken = MayUseToken; /** * Returns a JSON representation of only the fields that differ from the @@ -1349,7 +1351,7 @@ type AccountUpdateForestBase = MerkleListBase; const AccountUpdateTreeBase = StructNoJson({ id: RandomId, - accountUpdate: HashedAccountUpdate.provable, + accountUpdate: HashedAccountUpdate, children: MerkleListBase(), }); @@ -1370,10 +1372,29 @@ class AccountUpdateForest extends MerkleList.create( AccountUpdateTreeBase, merkleListHash ) { + static provable = provableExtends(AccountUpdateForest, super.provable); + + push(update: AccountUpdate | AccountUpdateTreeBase) { + return super.push( + update instanceof AccountUpdate ? AccountUpdateTree.from(update) : update + ); + } + pushIf(condition: Bool, update: AccountUpdate | AccountUpdateTreeBase) { + return super.pushIf( + condition, + update instanceof AccountUpdate ? AccountUpdateTree.from(update) : update + ); + } + static fromFlatArray(updates: AccountUpdate[]): AccountUpdateForest { let simpleForest = accountUpdatesToCallForest(updates); return this.fromSimpleForest(simpleForest); } + + toFlatArray(mutate = true, depth = 0) { + return AccountUpdateForest.toFlatArray(this, mutate, depth); + } + static toFlatArray( forest: AccountUpdateForestBase, mutate = true, @@ -1412,6 +1433,17 @@ class AccountUpdateForest extends MerkleList.create( }); }); } + + // fix static methods + static empty() { + return AccountUpdateForest.provable.empty(); + } + static from(array: AccountUpdateTreeBase[]) { + return new AccountUpdateForest(super.from(array)); + } + static fromReverse(array: AccountUpdateTreeBase[]) { + return new AccountUpdateForest(super.fromReverse(array)); + } } /** @@ -1429,8 +1461,8 @@ class AccountUpdateForest extends MerkleList.create( */ class AccountUpdateTree extends StructNoJson({ id: RandomId, - accountUpdate: HashedAccountUpdate.provable, - children: AccountUpdateForest.provable, + accountUpdate: HashedAccountUpdate, + children: AccountUpdateForest, }) { /** * Create a tree of account updates which only consists of a root. @@ -1572,9 +1604,7 @@ class UnfinishedForest { } witnessHash(): UnfinishedForestFinal { - let final = Provable.witness(AccountUpdateForest.provable, () => - this.finalize() - ); + let final = Provable.witness(AccountUpdateForest, () => this.finalize()); return this.setFinal(final); } @@ -1617,8 +1647,7 @@ class UnfinishedForest { } toFlatArray(mutate = true, depth = 0): AccountUpdate[] { - if (this.isFinal()) - return AccountUpdateForest.toFlatArray(this.final, mutate, depth); + if (this.isFinal()) return this.final.toFlatArray(mutate, depth); assert(this.isMutable(), 'final or mutable'); let flatUpdates: AccountUpdate[] = []; for (let node of this.mutable) { diff --git a/src/lib/mina/account-update.unit-test.ts b/src/lib/mina/account-update.unit-test.ts index fca2d0490a..335ab806b6 100644 --- a/src/lib/mina/account-update.unit-test.ts +++ b/src/lib/mina/account-update.unit-test.ts @@ -3,6 +3,7 @@ import { AccountUpdate, PrivateKey, Field, + Bool, Mina, Int64, Types, @@ -20,6 +21,14 @@ function createAccountUpdate() { return accountUpdate; } +function createAccountUpdateWithMayUseToken( + mayUseToken: AccountUpdate['body']['mayUseToken'] +) { + let accountUpdate = AccountUpdate.defaultAccountUpdate(address); + accountUpdate.body.mayUseToken = mayUseToken; + return accountUpdate; +} + // can convert account update to fields consistently { let accountUpdate = createAccountUpdate(); @@ -122,3 +131,14 @@ function createAccountUpdate() { 'Check signature: Invalid signature on fee payer for key' ); } + +// correctly identifies when neither flag is set +{ + let accountUpdate = createAccountUpdateWithMayUseToken({ + parentsOwnToken: Bool(false), + inheritFromParent: Bool(false), + }); + expect(AccountUpdate.MayUseToken.isNo(accountUpdate).toBoolean()).toEqual( + true + ); +} diff --git a/src/lib/mina/actions/action-types.ts b/src/lib/mina/actions/action-types.ts index 604e344f51..fc0d26b00a 100644 --- a/src/lib/mina/actions/action-types.ts +++ b/src/lib/mina/actions/action-types.ts @@ -6,6 +6,7 @@ import { Actions } from '../account-update.js'; import { Hashed } from '../../provable/packed.js'; import { hashWithPrefix } from '../../provable/crypto/poseidon.js'; import { prefixes } from '../../../bindings/crypto/constants.js'; +import { ProvableType } from '../../provable/types/provable-intf.js'; export { MerkleActions, MerkleActionHashes, HashedAction, FlatActions }; export { emptyActionState, emptyActionsHash }; @@ -23,7 +24,7 @@ function MerkleActions>( fromActionState?: Field ) { return MerkleList.create( - MerkleActionList(actionType).provable, + MerkleActionList(actionType), (hash, actions) => hashWithPrefix(prefixes.sequenceEvents, [hash, actions.hash]), fromActionState ?? emptyActionState @@ -35,7 +36,7 @@ type MerkleActionList = MerkleList>; function MerkleActionList>(actionType: A) { return MerkleList.create( - HashedAction(actionType).provable, + HashedAction(actionType), (hash, action) => hashWithPrefix(prefixes.sequenceEvents, [hash, action.hash]), emptyActionsHash @@ -45,8 +46,9 @@ function MerkleActionList>(actionType: A) { type HashedAction = Hashed; function HashedAction>(actionType: A) { - return Hashed.create(actionType as Actionable>, (action) => - hashWithPrefix(prefixes.event, actionType.toFields(action)) + let type = ProvableType.get(actionType as Actionable>); + return Hashed.create(type, (action) => + hashWithPrefix(prefixes.event, type.toFields(action)) ); } @@ -55,14 +57,15 @@ function actionFieldsToMerkleList( fields: bigint[][][], fromActionState?: bigint ) { - const HashedActionT = HashedAction(actionType); - const MerkleActionListT = MerkleActionList(actionType); + let type = ProvableType.get(actionType); + const HashedActionT = HashedAction(type); + const MerkleActionListT = MerkleActionList(type); const MerkleActionsT = MerkleActions( - actionType, + type, fromActionState ? Field(fromActionState) : undefined ); let actions = fields.map((event) => - event.map((action) => actionType.fromFields(action.map(Field))) + event.map((action) => type.fromFields(action.map(Field))) ); let hashes = actions.map((as) => as.map((a) => HashedActionT.hash(a))); return MerkleActionsT.from(hashes.map((h) => MerkleActionListT.from(h))); @@ -92,9 +95,5 @@ function MerkleActionHashes(fromActionState?: Field) { type FlatActions = MerkleList>; function FlatActions>(actionType: A) { - const HashedAction = Hashed.create( - actionType as Actionable>, - (action) => hashWithPrefix(prefixes.event, actionType.toFields(action)) - ); - return MerkleList.create(HashedAction.provable); + return MerkleList.create(HashedAction(actionType)); } diff --git a/src/lib/mina/actions/batch-reducer.ts b/src/lib/mina/actions/batch-reducer.ts index b02351c25e..c29cb040e1 100644 --- a/src/lib/mina/actions/batch-reducer.ts +++ b/src/lib/mina/actions/batch-reducer.ts @@ -24,6 +24,11 @@ import { MerkleActions, emptyActionState, } from './action-types.js'; +import { + ProvableHashable, + ProvablePure, + ProvableType, +} from '../../provable/types/provable-intf.js'; // external API export { BatchReducer, ActionBatch }; @@ -57,7 +62,7 @@ class BatchReducer< Action = InferProvable > { batchSize: BatchSize; - actionType: Actionable; + actionType: ProvableHashable & ProvablePure; Batch: ReturnType; program: ActionStackProgram; @@ -133,7 +138,8 @@ class BatchReducer< maxActionsPerUpdate?: number; }) { this.batchSize = batchSize; - this.actionType = actionType as Actionable; + this.actionType = ProvableType.get(actionType) as ProvableHashable & + ProvablePure; this.Batch = ActionBatch(this.actionType); this.maxUpdatesFinalProof = maxUpdatesFinalProof; @@ -385,7 +391,7 @@ class BatchReducer< // we make it easier to write the reducer code by making sure dummy actions have dummy values hashedAction = Provable.if( isDummy, - HashedActionT.provable, + HashedActionT, emptyHashedAction, hashedAction ); @@ -549,9 +555,9 @@ function ActionBatch>(actionType: A) { processedActionState: Field, onchainActionState: Field, onchainStack: Field, - stack: MerkleActions(actionType).provable, + stack: MerkleActions(actionType), isRecursive: Bool, - witnesses: Unconstrained.provableWithEmpty([]), + witnesses: Unconstrained.withEmpty([]), }); } @@ -769,7 +775,7 @@ function actionStackProgram(maxUpdatesPerProof: number) { privateInputs: [ SelfProof, Bool, - Unconstrained.provableWithEmpty([]), + Unconstrained.withEmpty([]), ], async method( diff --git a/src/lib/mina/actions/batch-reducer.unit-test.ts b/src/lib/mina/actions/batch-reducer.unit-test.ts index 98b35e3bb3..ff06ffd90f 100644 --- a/src/lib/mina/actions/batch-reducer.unit-test.ts +++ b/src/lib/mina/actions/batch-reducer.unit-test.ts @@ -86,12 +86,10 @@ class UnsafeAirdrop extends SmartContract { * * Note: This two-step process is necessary so that multiple users can claim concurrently. */ - @method.returns(MerkleMap.provable) + @method.returns(MerkleMap) async settleClaims(batch: Batch, proof: BatchProof) { // witness merkle map and require that it matches the onchain root - let eligibleMap = Provable.witness(MerkleMap.provable, () => - eligible.clone() - ); + let eligibleMap = Provable.witness(MerkleMap, () => eligible.clone()); this.eligibleRoot.requireEquals(eligibleMap.root); this.eligibleLength.requireEquals(eligibleMap.length); diff --git a/src/lib/mina/actions/offchain-state-rollup.ts b/src/lib/mina/actions/offchain-state-rollup.ts index 133d5d75bb..d69595ad7b 100644 --- a/src/lib/mina/actions/offchain-state-rollup.ts +++ b/src/lib/mina/actions/offchain-state-rollup.ts @@ -22,7 +22,7 @@ import { getProofsEnabled } from '../mina.js'; export { OffchainStateRollup, OffchainStateCommitments }; class ActionIterator extends MerkleListIterator.create( - ActionList.provable, + ActionList, (hash: Field, actions: ActionList) => Actions.updateSequenceState(hash, actions.hash), // we don't have to care about the initial hash here because we will just step forward @@ -42,7 +42,7 @@ class OffchainStateCommitments extends Struct({ root: Field, length: Field, // TODO: make zkprogram support auxiliary data in public inputs - // actionState: ActionIterator.provable, + // actionState: ActionIterator, actionState: Field, }) { static emptyFromHeight(height: number) { @@ -169,7 +169,7 @@ function OffchainStateRollup({ */ firstBatch: { // [actions, tree] - privateInputs: [ActionIterator.provable, IndexedMerkleMapN.provable], + privateInputs: [ActionIterator, IndexedMerkleMapN], async method( stateA: OffchainStateCommitments, @@ -189,11 +189,7 @@ function OffchainStateRollup({ */ nextBatch: { // [actions, tree, proof] - privateInputs: [ - ActionIterator.provable, - IndexedMerkleMapN.provable, - SelfProof, - ], + privateInputs: [ActionIterator, IndexedMerkleMapN, SelfProof], async method( stateA: OffchainStateCommitments, @@ -323,7 +319,7 @@ function OffchainStateRollup({ // also moves the original iterator forward to start after the slice function sliceActions(actions: ActionIterator, batchSize: number) { class ActionListsList extends MerkleList.create( - ActionList.provable, + ActionList, (hash: Field, actions: ActionList) => Actions.updateSequenceState(hash, actions.hash), actions.currentHash diff --git a/src/lib/mina/actions/offchain-state-serialization.ts b/src/lib/mina/actions/offchain-state-serialization.ts index 2bc1df8dd9..bf76b73311 100644 --- a/src/lib/mina/actions/offchain-state-serialization.ts +++ b/src/lib/mina/actions/offchain-state-serialization.ts @@ -6,7 +6,11 @@ * if we only need to prove that (key, value) are part of it. */ -import { ProvablePure } from '../../provable/types/provable-intf.js'; +import { + ProvablePure, + ProvableType, + WithProvable, +} from '../../provable/types/provable-intf.js'; import { Poseidon, ProvableHashable, @@ -45,7 +49,9 @@ export { }; type Action = [...Field[], Field, Field]; -type Actionable = ProvableHashable & ProvablePure; +type Actionable = WithProvable< + ProvableHashable & ProvablePure +>; function toKeyHash | undefined>( prefix: Field, @@ -70,6 +76,7 @@ function toAction | undefined>({ value: V; previousValue?: Option; }): Action { + valueType = ProvableType.get(valueType); let valueSize = valueType.sizeInFields(); let padding = valueSize % 2 === 0 ? [] : [Field(0)]; @@ -100,6 +107,7 @@ function fromActionWithoutHashes( valueType: Actionable, action: Field[] ): V { + valueType = ProvableType.get(valueType); let valueSize = valueType.sizeInFields(); let paddingSize = valueSize % 2 === 0 ? 0 : 1; assert(action.length === valueSize + paddingSize, 'invalid action size'); @@ -121,7 +129,7 @@ function hashPackedWithPrefix | undefined>( // hash value if a type was passed in if (type !== undefined) { - let input = type.toInput(value as T); + let input = ProvableType.get(type).toInput(value as T); let packed = packToFields(input); state = Poseidon.update(state, packed); } @@ -137,7 +145,7 @@ class MerkleLeaf extends Struct({ value: Field, usesPreviousValue: Bool, previousValue: Field, - prefix: Unconstrained.provableWithEmpty([]), + prefix: Unconstrained.withEmpty([]), }) { static fromAction(action: Field[]) { assert(action.length >= 4, 'invalid action size'); @@ -230,7 +238,7 @@ async function fetchMerkleLeaves( } ): Promise>> { class MerkleActions extends MerkleList.create( - ActionList.provable, + ActionList, (hash: Field, actions: ActionList) => Actions.updateSequenceState(hash, actions.hash), // if no "start" action hash was specified, this means we are fetching the entire history of actions, which started from the empty action state hash @@ -314,7 +322,7 @@ function updateMerkleMap( // update the intermediate tree, save updates for final tree intermediateTree.set(key, value); - updates.push({ key, fullValue: prefix.get() }); + updates.push({ key, fullValue: prefix }); } if (isValidUpdate) { diff --git a/src/lib/mina/actions/offchain-state.ts b/src/lib/mina/actions/offchain-state.ts index 56a1d0d1ff..090b7d3496 100644 --- a/src/lib/mina/actions/offchain-state.ts +++ b/src/lib/mina/actions/offchain-state.ts @@ -27,6 +27,7 @@ import { Poseidon } from '../../provable/crypto/poseidon.js'; import { contract } from '../smart-contract-context.js'; import { IndexedMerkleMap } from '../../provable/merkle-tree-indexed.js'; import { assertDefined } from '../../util/assert.js'; +import { ProvableType } from '../../provable/types/provable-intf.js'; // external API export { OffchainState, OffchainStateCommitments }; @@ -256,7 +257,7 @@ function OffchainState< // witness the merkle map & anchor against the onchain root let map = await Provable.witnessAsync( - IndexedMerkleMapN.provable, + IndexedMerkleMapN, async () => (await merkleMaps()).merkleMap ); map.root.assertEquals(state.root, 'root mismatch'); @@ -291,6 +292,7 @@ function OffchainState< index: number, type: Actionable ): OffchainField { + type = ProvableType.get(type); const prefix = Field(index); let optionType = Option(type); @@ -340,6 +342,8 @@ function OffchainState< keyType: Actionable, valueType: Actionable ): OffchainMap { + keyType = ProvableType.get(keyType); + valueType = ProvableType.get(valueType); const prefix = Field(index); let optionType = Option(valueType); diff --git a/src/lib/mina/actions/reducer.ts b/src/lib/mina/actions/reducer.ts index 0118e0028e..106bb9cb11 100644 --- a/src/lib/mina/actions/reducer.ts +++ b/src/lib/mina/actions/reducer.ts @@ -65,8 +65,8 @@ type ReducerReturn = { * ); * ``` * - * 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. */ reduce( @@ -262,7 +262,7 @@ class ${contract.constructor.name} extends SmartContract { ) {} class MerkleActions extends MerkleList.create( - ActionList.provable, + ActionList, (hash: Field, actions: ActionList) => Actions.updateSequenceState(hash, actions.hash), // if no "start" action hash was specified, this means we are fetching the entire history of actions, which started from the empty action state hash @@ -271,7 +271,7 @@ class ${contract.constructor.name} extends SmartContract { config?.fromActionState ?? Actions.emptyActionState() ) {} - let actions = Provable.witness(MerkleActions.provable, () => { + let actions = Provable.witness(MerkleActions, () => { let actionFields = Mina.getActions( contract.address, config, diff --git a/src/lib/mina/errors.ts b/src/lib/mina/errors.ts index 08c0fc6014..2a2beb199b 100644 --- a/src/lib/mina/errors.ts +++ b/src/lib/mina/errors.ts @@ -1,7 +1,7 @@ import { Types } from '../../bindings/mina-transaction/types.js'; import { TokenId } from './account-update.js'; -export { invalidTransactionError }; +export { humanizeErrors, invalidTransactionError }; const ErrorHandlers = { Invalid_fee_excess({ @@ -44,6 +44,13 @@ ${(-Number(accountCreationFee) * 1e-9).toFixed( )} times the number of newly created accounts.`; }, }; +const defaultErrorReplacementRules: ErrorReplacementRule[] = [ + { + pattern: /\(invalid \(Invalid_proof \\"In progress\\"\)\)/g, + replacement: + 'Stale verification key detected. Please make sure that deployed verification key reflects latest zkApp changes.', + }, +]; type ErrorHandlerArgs = { transaction: Types.ZkappCommand; @@ -52,6 +59,11 @@ type ErrorHandlerArgs = { accountCreationFee: string | number; }; +type ErrorReplacementRule = { + pattern: RegExp; + replacement: string; +}; + function invalidTransactionError( transaction: Types.ZkappCommand, errors: string[][][], @@ -102,3 +114,16 @@ function invalidTransactionError( // fallback if we don't have a good error message yet return rawErrors; } + +function humanizeErrors( + errors: string[], + replacements: ErrorReplacementRule[] = defaultErrorReplacementRules +): string[] { + return errors.map((error) => { + let modifiedError = error; + replacements.forEach(({ pattern, replacement }) => { + modifiedError = modifiedError.replace(pattern, replacement); + }); + return modifiedError; + }); +} diff --git a/src/lib/mina/mina.ts b/src/lib/mina/mina.ts index 2529a59e41..a30c290bc5 100644 --- a/src/lib/mina/mina.ts +++ b/src/lib/mina/mina.ts @@ -4,7 +4,7 @@ import { UInt64 } from '../provable/int.js'; import { PublicKey } from '../provable/crypto/signature.js'; import { TokenId, Authorization } from './account-update.js'; import * as Fetch from './fetch.js'; -import { invalidTransactionError } from './errors.js'; +import { humanizeErrors, invalidTransactionError } from './errors.js'; import { Types } from '../../bindings/mina-transaction/types.js'; import { Account } from './account.js'; import { NetworkId } from '../../mina-signer/src/types.js'; @@ -260,6 +260,7 @@ function Network( } else if (response && response.errors && response.errors.length > 0) { response?.errors.forEach((e: any) => errors.push(JSON.stringify(e))); } + const updatedErrors = humanizeErrors(errors); const status: PendingTransactionStatus = errors.length === 0 ? 'pending' : 'rejected'; @@ -271,7 +272,7 @@ function Network( > = { status, data: response?.data, - errors, + errors: updatedErrors, transaction: txn.transaction, hash, toJSON: txn.toJSON, diff --git a/src/lib/mina/state.ts b/src/lib/mina/state.ts index 9c9f39eb09..43b480cb2e 100644 --- a/src/lib/mina/state.ts +++ b/src/lib/mina/state.ts @@ -7,7 +7,11 @@ import { SmartContract } from './zkapp.js'; import { Account } from './account.js'; import { Provable } from '../provable/provable.js'; import { Field } from '../provable/wrapped.js'; -import { ProvablePure } from '../provable/types/provable-intf.js'; +import { + ProvablePure, + ProvableType, + ProvableTypePure, +} from '../provable/types/provable-intf.js'; import { ensureConsistentPrecondition } from './precondition.js'; import { Bool } from '../provable/wrapped.js'; @@ -98,7 +102,9 @@ function State(defaultValue?: A): State { * ``` * */ -function state(stateType: FlexibleProvablePure) { +function state(type: ProvableTypePure | FlexibleProvablePure) { + let stateType = ProvableType.get(type); + return function ( target: SmartContract & { constructor: any }, key: string, diff --git a/src/lib/mina/token/forest-iterator.ts b/src/lib/mina/token/forest-iterator.ts index 7918cc629a..257a19c984 100644 --- a/src/lib/mina/token/forest-iterator.ts +++ b/src/lib/mina/token/forest-iterator.ts @@ -12,11 +12,14 @@ import { MerkleListIterator, MerkleList } from '../../provable/merkle-list.js'; export { TokenAccountUpdateIterator }; -const AccountUpdateIterator = - MerkleListIterator.createFromList(AccountUpdateForest); +const AccountUpdateIterator = MerkleListIterator.create( + AccountUpdateForest.prototype.innerProvable, + AccountUpdateForest._nextHash, + AccountUpdateForest.emptyHash +); class Layer extends Struct({ - forest: AccountUpdateIterator.provable, + forest: AccountUpdateIterator, mayUseToken: AccountUpdate.MayUseToken.type, }) {} const ParentLayers = MerkleList.create(Layer); diff --git a/src/lib/mina/zkapp.ts b/src/lib/mina/zkapp.ts index e453c561e1..7b58bfc361 100644 --- a/src/lib/mina/zkapp.ts +++ b/src/lib/mina/zkapp.ts @@ -74,7 +74,7 @@ import { smartContractContext, } from './smart-contract-context.js'; import { assertPromise } from '../util/assert.js'; -import { ProvablePure } from '../provable/types/provable-intf.js'; +import { ProvablePure, ProvableType } from '../provable/types/provable-intf.js'; import { getReducer, Reducer } from './actions/reducer.js'; import { provable } from '../provable/types/provable-derivers.js'; @@ -174,7 +174,7 @@ function method( method.returns = function < K extends string, T extends SmartContract, - R extends Provable + R extends ProvableType >(returnType: R) { return function decorateMethod( target: T & { @@ -183,7 +183,12 @@ method.returns = function < methodName: K & string & keyof T, descriptor: PropertyDescriptor ) { - return method(target as any, methodName, descriptor, returnType); + return method( + target as any, + methodName, + descriptor, + ProvableType.get(returnType) + ); }; }; @@ -462,7 +467,7 @@ function wrapMethod( }>( provable({ result: methodIntf.returnType ?? provable(null), - children: AccountUpdateForest.provable, + children: AccountUpdateForest, }), runCalledContract, { skipCheck: true } diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 8a36c4559c..fe497a7a65 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -9,7 +9,11 @@ import { ProvablePureExtended, Struct, } from '../provable/types/struct.js'; -import { provable, provablePure } from '../provable/types/provable-derivers.js'; +import { + InferProvableType, + provable, + provablePure, +} from '../provable/types/provable-derivers.js'; import { Provable } from '../provable/provable.js'; import { assert, prettifyStacktracePromise } from '../util/errors.js'; import { snarkContext } from '../provable/core/provable-context.js'; @@ -34,7 +38,12 @@ import { setSrsCache, unsetSrsCache, } from '../../bindings/crypto/bindings/srs.js'; -import { ProvablePure } from '../provable/types/provable-intf.js'; +import { + ProvablePure, + ProvableType, + ProvableTypePure, + ToProvable, +} from '../provable/types/provable-intf.js'; import { prefixToField } from '../../bindings/lib/binable.js'; import { prefixes } from '../../bindings/crypto/constants.js'; @@ -530,11 +539,12 @@ let SideloadedTag = { function ZkProgram< StatementType extends { - publicInput?: FlexibleProvablePure; - publicOutput?: FlexibleProvablePure; + publicInput?: ProvableTypePure; + publicOutput?: ProvableTypePure; }, 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; } >( @@ -588,8 +598,12 @@ function ZkProgram< >; } { let methods = config.methods; - let publicInputType: ProvablePure = config.publicInput ?? Undefined; - let publicOutputType: ProvablePure = config.publicOutput ?? Void; + let publicInputType: ProvablePure = ProvableType.get( + config.publicInput ?? Undefined + ); + let publicOutputType: ProvablePure = ProvableType.get( + config.publicOutput ?? Void + ); let selfTag = { name: config.name }; type PublicInput = InferProvableOrUndefined< @@ -764,8 +778,8 @@ function ZkProgram< type ZkProgram< S extends { - publicInput?: FlexibleProvablePure; - publicOutput?: FlexibleProvablePure; + publicInput?: ProvableTypePure; + publicOutput?: ProvableTypePure; }, T extends { [I in string]: Tuple; @@ -1425,7 +1439,9 @@ function Prover() { type Infer = T extends Subclass ? InstanceType - : InferProvable; + : T extends ProvableType + ? InferProvableType + : never; type Tuple = [T, ...T[]] | []; type TupleToInstances = { @@ -1438,7 +1454,7 @@ type Subclass any> = (new ( [K in keyof Class]: Class[K]; } & { prototype: InstanceType }; -type PrivateInput = Provable | Subclass; +type PrivateInput = ProvableType | Subclass; type Method< PublicInput, @@ -1470,8 +1486,10 @@ type Prover< ...args: TupleToInstances ) => Promise>; -type ProvableOrUndefined = A extends undefined ? typeof Undefined : A; -type ProvableOrVoid = A extends undefined ? typeof Void : A; +type ProvableOrUndefined = A extends undefined + ? typeof Undefined + : ToProvable; +type ProvableOrVoid = A extends undefined ? typeof Void : ToProvable; type InferProvableOrUndefined = A extends undefined ? undefined diff --git a/src/lib/provable/crypto/foreign-curve.ts b/src/lib/provable/crypto/foreign-curve.ts index 1c8ba95be2..ef8d7eb136 100644 --- a/src/lib/provable/crypto/foreign-curve.ts +++ b/src/lib/provable/crypto/foreign-curve.ts @@ -97,7 +97,7 @@ class ForeignCurve { * See {@link FieldVar} to understand constants vs variables. */ isConstant() { - return Provable.isConstant(this.Constructor.provable, this); + return Provable.isConstant(this.Constructor, this); } /** @@ -323,10 +323,7 @@ function createForeignCurve(params: CurveParams): typeof ForeignCurve { static _Bigint = BigintCurve; static _Field = Field; static _Scalar = Scalar; - static _provable = provableFromClass(Curve, { - x: Field.provable, - y: Field.provable, - }); + static _provable = provableFromClass(Curve, { x: Field, y: Field }); } return Curve; @@ -364,10 +361,7 @@ function createForeignCurveV2(params: CurveParams): typeof ForeignCurveV2 { static _Bigint = BigintCurve; static _Field = Field; static _Scalar = Scalar; - static _provable = provableFromClass(Curve, { - x: Field.provable, - y: Field.provable, - }); + static _provable = provableFromClass(Curve, { x: Field, y: Field }); } return Curve; diff --git a/src/lib/provable/crypto/foreign-ecdsa.ts b/src/lib/provable/crypto/foreign-ecdsa.ts index d9840edfe2..300da146c2 100644 --- a/src/lib/provable/crypto/foreign-ecdsa.ts +++ b/src/lib/provable/crypto/foreign-ecdsa.ts @@ -106,9 +106,9 @@ class EcdsaSignature { * * // ... * // in provable code: create input witnesses (or use method inputs, or constants) - * let pk = Provable.witness(Secp256k1.provable, () => publicKey); + * let pk = Provable.witness(Secp256k1, () => publicKey); * let msg = Provable.witness(Provable.Array(Field, 9), () => messageBytes.map(Field)); - * let sig = Provable.witness(Ecdsa.provable, () => signature); + * let sig = Provable.witness(Ecdsa, () => signature); * * // verify signature * let isValid = sig.verify(msg, pk); @@ -245,8 +245,8 @@ function createEcdsa( class Signature extends EcdsaSignature { static _Curve = Curve; static _provable = provableFromClass(Signature, { - r: Curve.Scalar.provable, - s: Curve.Scalar.provable, + r: Curve.Scalar, + s: Curve.Scalar, }); } @@ -266,8 +266,8 @@ function createEcdsaV2( class Signature extends EcdsaSignatureV2 { static _Curve = Curve; static _provable = provableFromClass(Signature, { - r: Curve.Scalar.provable, - s: Curve.Scalar.provable, + r: Curve.Scalar, + s: Curve.Scalar, }); } diff --git a/src/lib/provable/crypto/poseidon.ts b/src/lib/provable/crypto/poseidon.ts index b35df880d9..5f4686c691 100644 --- a/src/lib/provable/crypto/poseidon.ts +++ b/src/lib/provable/crypto/poseidon.ts @@ -9,6 +9,7 @@ import { assert } from '../../util/errors.js'; import { rangeCheckN } from '../gadgets/range-check.js'; import { TupleN } from '../../util/types.js'; import { Group } from '../group.js'; +import { ProvableType, WithProvable } from '../types/provable-intf.js'; // external API export { Poseidon, TokenSymbol }; @@ -136,8 +137,8 @@ const Poseidon = { * field elements as possible. This saves constraints because packing has a much * lower per-field element cost than hashing. */ - hashPacked(type: Hashable, value: T) { - let input = type.toInput(value); + hashPacked(type: WithProvable>, value: T) { + let input = ProvableType.get(type).toInput(value); let packed = packToFields(input); return Poseidon.hash(packed); }, diff --git a/src/lib/provable/foreign-field.ts b/src/lib/provable/foreign-field.ts index 802d064a61..4f6a736c5a 100644 --- a/src/lib/provable/foreign-field.ts +++ b/src/lib/provable/foreign-field.ts @@ -303,11 +303,7 @@ class ForeignField { } return new this.Constructor.Canonical(this.value); } - Provable.assertEqual( - this.Constructor.provable, - this, - new this.Constructor(y) - ); + Provable.assertEqual(this.Constructor, this, new this.Constructor(y)); if (isConstant(y) || y instanceof this.Constructor.Canonical) { return new this.Constructor.Canonical(this.value); } else if (y instanceof this.Constructor.AlmostReduced) { @@ -612,9 +608,9 @@ function isConstant(x: bigint | number | string | ForeignField) { * * This function returns the `Unreduced` class, which will cause the minimum amount of range checks to be created by default. * If you want to do multiplication, you have two options: - * - create your field elements using the {@link ForeignField.AlmostReduced} constructor, or using the `.provable` type on that class. + * - create your field elements using the {@link ForeignField.AlmostReduced} constructor. * ```ts - * let x = Provable.witness(ForeignField.AlmostReduced.provable, () => ForeignField.from(5)); + * let x = Provable.witness(ForeignField.AlmostReduced, () => 5n); * ``` * - create your field elements normally and convert them using `x.assertAlmostReduced()`. * ```ts @@ -622,7 +618,7 @@ function isConstant(x: bigint | number | string | ForeignField) { * ``` * * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced, "canonical" field elements. - * To convert to a canonical field element, use {@link ForeignField.assertCanonical}: + * To convert to a canonical field element, use `ForeignField.assertCanonical()`: * * ```ts * x.assertCanonical(); // asserts x < p; returns `CanonicalForeignField` diff --git a/src/lib/provable/gadgets/bitwise.ts b/src/lib/provable/gadgets/bitwise.ts index b1b3a025e0..fdd4ec8a8e 100644 --- a/src/lib/provable/gadgets/bitwise.ts +++ b/src/lib/provable/gadgets/bitwise.ts @@ -19,14 +19,9 @@ export { }; function not(a: Field, length: number, checked: boolean = false) { - // check that input length is positive - assert(length > 0, `Input length needs to be positive values.`); - - // Check that length does not exceed maximum field size in bits - assert( - length < Field.sizeInBits, - `Length ${length} exceeds maximum of ${Field.sizeInBits} bits.` - ); + // Validate at 240 bits to ensure padLength (next multiple of 16) doesn't exceed 254 bits, + // preventing potential underconstraint issues in the circuit + validateBitLength(length, 240, 'not'); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table let padLength = Math.ceil(length / 16) * 16; @@ -52,11 +47,9 @@ function not(a: Field, length: number, checked: boolean = false) { } function xor(a: Field, b: Field, length: number) { - // check that both input lengths are positive - assert(length > 0, `Input lengths need to be positive values.`); - - // check that length does not exceed maximum 254 size in bits - assert(length <= 254, `Length ${length} exceeds maximum of 254 bits.`); + // Validate at 240 bits to ensure padLength (next multiple of 16) doesn't exceed 254 bits, + // preventing potential underconstraint issues in the circuit + validateBitLength(length, 240, 'xor'); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table let padLength = Math.ceil(length / 16) * 16; @@ -150,14 +143,9 @@ function buildXor(a: Field, b: Field, out: Field, padLength: number) { } function and(a: Field, b: Field, length: number) { - // check that both input lengths are positive - assert(length > 0, `Input lengths need to be positive values.`); - - // check that length does not exceed maximum field size in bits - assert( - length <= Field.sizeInBits, - `Length ${length} exceeds maximum of ${Field.sizeInBits} bits.` - ); + // Validate at 240 bits to ensure padLength (next multiple of 16) doesn't exceed 254 bits, + // preventing potential underconstraint issues in the circuit + validateBitLength(length, 240, 'and'); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table let padLength = Math.ceil(length / 16) * 16; @@ -343,3 +331,26 @@ function leftShift32(field: Field, bits: number) { let { remainder: shifted } = divMod32(field.mul(1n << BigInt(bits))); return shifted; } + +/** + * Validates the bit length for bitwise operations. + * + * @param length - The input length to validate. + * @param maxLength - The maximum allowed length. + * @param functionName - The name of the calling function for error messages. + * + * @throws {Error} If the input length is not positive or exceeds the maximum length. + */ +function validateBitLength( + length: number, + maxLength: number, + functionName: string +) { + // check that both input lengths are positive + assert(length > 0, `${functionName}: Input length must be a positive value.`); + // check that length does not exceed maximum `maxLength` size in bits + assert( + length <= maxLength, + `${functionName}: Length ${length} exceeds maximum of ${maxLength} bits.` + ); +} diff --git a/src/lib/provable/gadgets/comparison.ts b/src/lib/provable/gadgets/comparison.ts index e784f54caf..648b7507bd 100644 --- a/src/lib/provable/gadgets/comparison.ts +++ b/src/lib/provable/gadgets/comparison.ts @@ -235,7 +235,7 @@ function isOddAndHigh(x: Field) { function fieldToField3(x: Field) { if (x.isConstant()) return Field3.from(x.toBigInt()); - let xBig = witness(Field3.provable, () => Field3.from(x.toBigInt())); + let xBig = witness(Field3, () => x.toBigInt()); multiRangeCheck(xBig); let [x0, x1, x2] = xBig; diff --git a/src/lib/provable/gadgets/elliptic-curve.ts b/src/lib/provable/gadgets/elliptic-curve.ts index 7772d6565e..d278f3cd16 100644 --- a/src/lib/provable/gadgets/elliptic-curve.ts +++ b/src/lib/provable/gadgets/elliptic-curve.ts @@ -22,6 +22,7 @@ import { arrayGet, assertNotVectorEquals } from './basic.js'; import { sliceField3 } from './bit-slices.js'; import { Hashed } from '../packed.js'; import { exists } from '../core/exists.js'; +import { ProvableType } from '../types/provable-intf.js'; // external API export { EllipticCurve, Point, Ecdsa }; @@ -287,7 +288,7 @@ function verifyEcdsaGeneric( // if we allowed non-canonical Rx, the prover could make verify() return false on a valid signature, by adding a multiple of `Curve.order` to Rx. ForeignField.assertLessThan(Rx, Curve.order); - return Provable.equal(Field3.provable, Rx, r); + return Provable.equal(Field3, Rx, r); } /** @@ -508,7 +509,7 @@ function multiScalarMul( // hash points to make array access more efficient // a Point is 6 field elements, the hash is just 1 field element - const HashedPoint = Hashed.create(Point.provable); + const HashedPoint = Hashed.create(Point); // initialize sum to the initial aggregator, which is expected to be unrelated to any point that this gadget is used with // note: this is a trick to ensure _completeness_ of the gadget @@ -535,23 +536,19 @@ function multiScalarMul( sjP = windowSize === 1 ? points[j] - : arrayGetGeneric( - HashedPoint.provable, - hashedTables[j], - sj - ).unhash(); + : arrayGetGeneric(HashedPoint, hashedTables[j], sj).unhash(); } else { sjP = windowSize === 1 ? points[j] - : arrayGetGeneric(Point.provable, tables[j], sj); + : arrayGetGeneric(Point, tables[j], sj); } // ec addition let added = add(sum, sjP, Curve); // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) - sum = Provable.if(sj.equals(0), Point.provable, sum, added); + sum = Provable.if(sj.equals(0), Point, sum, added); } } @@ -582,7 +579,7 @@ function multiScalarMul( function negateIf(condition: Field, P: Point, f: bigint) { let y = Provable.if( Bool.Unsafe.fromField(condition), - Field3.provable, + Field3, ForeignField.negate(P.y, f), P.y ); @@ -627,13 +624,13 @@ function decomposeNoRangeCheck(Curve: CurveAffine, s: Field3) { // prove that s1*lambda = s - s0 let lambda = Provable.if( Bool.Unsafe.fromField(s1Negative), - Field3.provable, + Field3, Field3.from(Curve.Scalar.negate(Curve.Endo.scalar)), Field3.from(Curve.Endo.scalar) ); let rhs = Provable.if( Bool.Unsafe.fromField(s0Negative), - Field3.provable, + Field3, ForeignField.Sum(s).add(s0).finish(Curve.order), ForeignField.Sum(s).sub(s0).finish(Curve.order) ); @@ -749,7 +746,8 @@ function simpleMapToCurve(x: bigint, Curve: CurveAffine) { * * Assumes that index is in [0, n), returns an unconstrained result otherwise. */ -function arrayGetGeneric(type: Provable, array: T[], index: Field) { +function arrayGetGeneric(type: ProvableType, array: T[], index: Field) { + type = ProvableType.get(type); // witness result let a = Provable.witness(type, () => array[Number(index)]); let aFields = type.toFields(a); @@ -774,7 +772,7 @@ const Point = { toBigint({ x, y }: Point) { return { x: Field3.toBigint(x), y: Field3.toBigint(y), infinity: false }; }, - isConstant: (P: Point) => Provable.isConstant(Point.provable, P), + isConstant: (P: Point) => Provable.isConstant(Point, P), /** * Random point on the curve. @@ -783,7 +781,7 @@ const Point = { return Point.from(random(Curve)); }, - provable: provable({ x: Field3.provable, y: Field3.provable }), + provable: provable({ x: Field3, y: Field3 }), }; const EcdsaSignature = { @@ -793,8 +791,7 @@ const EcdsaSignature = { toBigint({ r, s }: Ecdsa.Signature): Ecdsa.signature { return { r: Field3.toBigint(r), s: Field3.toBigint(s) }; }, - isConstant: (S: Ecdsa.Signature) => - Provable.isConstant(EcdsaSignature.provable, S), + isConstant: (S: Ecdsa.Signature) => Provable.isConstant(EcdsaSignature, S), /** * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in @@ -813,7 +810,7 @@ const EcdsaSignature = { return EcdsaSignature.from({ r, s }); }, - provable: provable({ r: Field3.provable, s: Field3.provable }), + provable: provable({ r: Field3, s: Field3 }), }; const Ecdsa = { diff --git a/src/lib/provable/gadgets/foreign-field.ts b/src/lib/provable/gadgets/foreign-field.ts index 611189c18a..948a9b77f7 100644 --- a/src/lib/provable/gadgets/foreign-field.ts +++ b/src/lib/provable/gadgets/foreign-field.ts @@ -431,7 +431,8 @@ function equals(x: Field3, c: bigint, f: bigint) { return x012.equals(c); } } - +// TODO: remove this in v2!!! +// having a `toInput()` method without a corresponding `check()` is begging for a vulnerability (which the current code has) const provableLimb = modifiedField({ toInput(x) { return { packed: [[x, Number(l)]] }; diff --git a/src/lib/provable/gadgets/gadgets.ts b/src/lib/provable/gadgets/gadgets.ts index c28c3d8ce5..205a0990a6 100644 --- a/src/lib/provable/gadgets/gadgets.ts +++ b/src/lib/provable/gadgets/gadgets.ts @@ -554,8 +554,8 @@ const Gadgets = { * * @example * ```ts - * let x = Provable.witness(Field3.provable, () => Field3.from(9n)); - * let y = Provable.witness(Field3.provable, () => Field3.from(10n)); + * let x = Provable.witness(Field3, () => 9n); + * let y = Provable.witness(Field3, () => 10n); * * // range check x and y * Gadgets.multiRangeCheck(x); @@ -616,9 +616,9 @@ const Gadgets = { * * @example * ```ts - * let x = Provable.witness(Field3.provable, () => Field3.from(4n)); - * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); - * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); + * let x = Provable.witness(Field3, () => 4n); + * let y = Provable.witness(Field3, () => 5n); + * let z = Provable.witness(Field3, () => 10n); * * // range check x, y, z * Gadgets.multiRangeCheck(x); @@ -656,8 +656,8 @@ const Gadgets = { * // example modulus: secp256k1 prime * let f = (1n << 256n) - (1n << 32n) - 0b1111010001n; * - * let x = Provable.witness(Field3.provable, () => Field3.from(f - 1n)); - * let y = Provable.witness(Field3.provable, () => Field3.from(f - 2n)); + * let x = Provable.witness(Field3, () => f - 1n); + * let y = Provable.witness(Field3, () => f - 2n); * * // range check x, y and prove additional bounds x[2] <= f[2] * ForeignField.assertAlmostReduced([x, y], f); @@ -758,9 +758,9 @@ const Gadgets = { * * @example * ```ts - * let x = Provable.witness(Field3.provable, () => Field3.from(4n)); - * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); - * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); + * let x = Provable.witness(Field3, () => 4n); + * let y = Provable.witness(Field3, () => 5n); + * let z = Provable.witness(Field3, () => 10n); * * ForeignField.assertAlmostReduced([x, y, z], f); * @@ -790,7 +790,7 @@ const Gadgets = { * * @example * ```ts - * let x = Provable.witness(Field3.provable, () => Field3.from(0x1235n)); + * let x = Provable.witness(Field3, () => 0x1235n); * * // range check limbs of x * Gadgets.multiRangeCheck(x); diff --git a/src/lib/provable/merkle-list.ts b/src/lib/provable/merkle-list.ts index dbebac0c02..24b3ae7851 100644 --- a/src/lib/provable/merkle-list.ts +++ b/src/lib/provable/merkle-list.ts @@ -5,6 +5,7 @@ import { assert } from './gadgets/common.js'; import { provableFromClass } from './types/provable-derivers.js'; import { Poseidon, packToFields, ProvableHashable } from './crypto/poseidon.js'; import { Unconstrained } from './types/unconstrained.js'; +import { ProvableType, WithProvable } from './types/provable-intf.js'; export { MerkleListBase, @@ -43,7 +44,7 @@ type MerkleListBase = { }; function MerkleListBase(): ProvableHashable> { - return class extends Struct({ hash: Field, data: Unconstrained.provable }) { + return class extends Struct({ hash: Field, data: Unconstrained }) { static empty(): MerkleListBase { return { hash: emptyHash, data: Unconstrained.from([]) }; } @@ -280,8 +281,10 @@ class MerkleList implements MerkleListBase { * ``` */ static create( - type: ProvableHashable, - nextHash: (hash: Field, value: T) => Field = merkleListHash(type), + type: WithProvable>, + nextHash: (hash: Field, value: T) => Field = merkleListHash( + ProvableType.get(type) + ), emptyHash_ = emptyHash ): typeof MerkleList & { // override static methods with strict types @@ -290,13 +293,17 @@ class MerkleList implements MerkleListBase { fromReverse: (array: T[]) => MerkleList; provable: ProvableHashable>; } { + let provable = ProvableType.get(type); + class MerkleListTBase extends MerkleList { - static _innerProvable = type; + static _innerProvable = provable; static _provable = provableFromClass(MerkleListTBase, { hash: Field, - data: Unconstrained.provable, - }) as ProvableHashable>; + data: Unconstrained, + }) satisfies ProvableHashable> as ProvableHashable< + MerkleList + >; static _nextHash = nextHash; static _emptyHash = emptyHash_; @@ -309,7 +316,7 @@ class MerkleList implements MerkleListBase { array = [...array].reverse(); let { hash, data } = withHashes(array, nextHash, emptyHash_); let unconstrained = Unconstrained.witness(() => - data.map((x) => toConstant(type, x)) + data.map((x) => toConstant(provable, x)) ); return new this({ data: unconstrained, hash }); } @@ -317,7 +324,7 @@ class MerkleList implements MerkleListBase { static fromReverse(array: T[]): MerkleList { let { hash, data } = withHashes(array, nextHash, emptyHash_); let unconstrained = Unconstrained.witness(() => - data.map((x) => toConstant(type, x)) + data.map((x) => toConstant(provable, x)) ); return new this({ data: unconstrained, hash }); } @@ -326,6 +333,9 @@ class MerkleList implements MerkleListBase { assert(this._provable !== undefined, 'MerkleList not initialized'); return this._provable; } + static set provable(_provable: ProvableHashable>) { + this._provable = _provable; + } } // override `instanceof` for subclasses return class MerkleListT extends MerkleListTBase { @@ -615,8 +625,10 @@ class MerkleListIterator implements MerkleListIteratorBase { * Create a Merkle array type */ static create( - type: ProvableHashable, - nextHash: (hash: Field, value: T) => Field = merkleListHash(type), + type: WithProvable>, + nextHash: (hash: Field, value: T) => Field = merkleListHash( + ProvableType.get(type) + ), emptyHash_ = emptyHash ): typeof MerkleListIterator & { from: (array: T[]) => MerkleListIterator; @@ -625,14 +637,15 @@ class MerkleListIterator implements MerkleListIteratorBase { empty: () => MerkleListIterator; provable: ProvableHashable>; } { + let provable = ProvableType.get(type); return class Iterator extends MerkleListIterator { - static _innerProvable = type; + static _innerProvable = ProvableType.get(provable); static _provable = provableFromClass(Iterator, { hash: Field, - data: Unconstrained.provable, + data: Unconstrained, currentHash: Field, - currentIndex: Unconstrained.provable, + currentIndex: Unconstrained, }) satisfies ProvableHashable> as ProvableHashable< MerkleListIterator >; @@ -643,7 +656,7 @@ class MerkleListIterator implements MerkleListIteratorBase { static from(array: T[]): MerkleListIterator { let { hash, data } = withHashes(array, nextHash, emptyHash_); let unconstrained = Unconstrained.witness(() => - data.map((x) => toConstant(type, x)) + data.map((x) => toConstant(provable, x)) ); return this.startIterating({ data: unconstrained, hash }); } @@ -652,7 +665,7 @@ class MerkleListIterator implements MerkleListIteratorBase { array = [...array].reverse(); let { hash, data } = withHashes(array, nextHash, emptyHash_); let unconstrained = Unconstrained.witness(() => - data.map((x) => toConstant(type, x)) + data.map((x) => toConstant(provable, x)) ); return this.startIteratingFromLast({ data: unconstrained, hash }); } diff --git a/src/lib/provable/merkle-tree-indexed.ts b/src/lib/provable/merkle-tree-indexed.ts index f9f90ce714..0389a85f68 100644 --- a/src/lib/provable/merkle-tree-indexed.ts +++ b/src/lib/provable/merkle-tree-indexed.ts @@ -36,7 +36,7 @@ export { Leaf }; * ZkProgram({ * methods: { * test: { - * privateInputs: [MerkleMap.provable, Field], + * privateInputs: [MerkleMap, Field], * * method(map: MerkleMap, key: Field) { * // get the value associated with `key` @@ -73,7 +73,7 @@ function IndexedMerkleMap(height: number): typeof IndexedMerkleMapBase { const provableBase = { root: Field, length: Field, - data: Unconstrained.provableWithEmpty({ + data: Unconstrained.withEmpty({ nodes: [] as (bigint | undefined)[][], sortedLeaves: [] as StoredLeaf[], }), @@ -656,8 +656,8 @@ class Leaf extends Struct({ nextKey: Field, // auxiliary data that tells us where the leaf is stored - index: Unconstrained.provableWithEmpty(0), - sortedIndex: Unconstrained.provableWithEmpty(0), + index: Unconstrained.withEmpty(0), + sortedIndex: Unconstrained.withEmpty(0), }) { /** * Compute a leaf node: the hash of a leaf that becomes part of the Merkle tree. @@ -693,11 +693,7 @@ class Leaf extends Struct({ } static fromStored(leaf: StoredLeaf, sortedIndex: number) { - return { - ...leaf, - index: Unconstrained.from(leaf.index), - sortedIndex: Unconstrained.from(sortedIndex), - }; + return { ...leaf, sortedIndex }; } } diff --git a/src/lib/provable/option.ts b/src/lib/provable/option.ts index cfd324e07f..86a71fb524 100644 --- a/src/lib/provable/option.ts +++ b/src/lib/provable/option.ts @@ -4,6 +4,7 @@ import { Provable } from './provable.js'; import { InferProvable, Struct } from './types/struct.js'; import { provable, ProvableInferPureFrom } from './types/provable-derivers.js'; import { Bool } from './wrapped.js'; +import { ProvableType } from './types/provable-intf.js'; export { Option, OptionOrValue }; @@ -34,7 +35,7 @@ type OptionOrValue = * let zero: UInt64 = none.orElse(0n); // specify a default value * ``` */ -function Option>( +function Option( type: A ): ProvableInferPureFrom< A, @@ -59,7 +60,7 @@ function Option>( } { type T = InferProvable; type V = InferValue; - let strictType: Provable = type; + let strictType: Provable = ProvableType.get(type); // construct a provable with a JS type of `T | undefined` const PlainOption: Provable< diff --git a/src/lib/provable/packed.ts b/src/lib/provable/packed.ts index aa59809ff0..e1b4cfe682 100644 --- a/src/lib/provable/packed.ts +++ b/src/lib/provable/packed.ts @@ -6,6 +6,7 @@ import { assert } from './gadgets/common.js'; import { Poseidon, ProvableHashable, packToFields } from './crypto/poseidon.js'; import { Provable } from './provable.js'; import { fields, modifiedField } from './types/fields.js'; +import { ProvableType, WithProvable } from './types/provable-intf.js'; export { Packed, Hashed }; @@ -49,22 +50,25 @@ class Packed { /** * Create a packed representation of `type`. You can then use `PackedType.pack(x)` to pack a value. */ - static create(type: ProvableExtended): typeof Packed & { + static create( + type: WithProvable> + ): typeof Packed & { provable: ProvableHashable>; } { + let provable = ProvableType.get(type); // compute size of packed representation - let input = type.toInput(type.empty()); + let input = provable.toInput(provable.empty()); let packedSize = countFields(input); return class Packed_ extends Packed { - static _innerProvable = type; + static _innerProvable = provable; static _provable = provableFromClass(Packed_, { packed: fields(packedSize), - value: Unconstrained.provable, - }) as ProvableHashable>; + value: Unconstrained, + }) satisfies ProvableHashable> as ProvableHashable>; static empty(): Packed { - return Packed_.pack(type.empty()); + return Packed_.pack(provable.empty()); } static get provable() { @@ -179,25 +183,26 @@ class Hashed { * Create a hashed representation of `type`. You can then use `HashedType.hash(x)` to wrap a value in a `Hashed`. */ static create( - type: ProvableHashable, + type: WithProvable>, hash?: (t: T) => Field ): typeof Hashed & { provable: ProvableHashable>; empty(): Hashed; } { - let _hash = hash ?? ((t: T) => Poseidon.hashPacked(type, t)); + let provable = ProvableType.get(type); + let _hash = hash ?? ((t: T) => Poseidon.hashPacked(provable, t)); return class Hashed_ extends Hashed { - static _innerProvable = type; + static _innerProvable = provable; static _provable = provableFromClass(Hashed_, { - hash: modifiedField({ empty: () => _hash(type.empty()) }), - value: Unconstrained.provable, - }) as ProvableHashable>; + hash: modifiedField({ empty: () => _hash(provable.empty()) }), + value: Unconstrained, + }) satisfies ProvableHashable> as ProvableHashable>; static _hash = _hash satisfies (t: T) => Field; static empty(): Hashed { - let empty = type.empty(); + let empty = provable.empty(); return new this(_hash(empty), Unconstrained.from(empty)); } diff --git a/src/lib/provable/provable.ts b/src/lib/provable/provable.ts index 58f98565b7..456ea56007 100644 --- a/src/lib/provable/provable.ts +++ b/src/lib/provable/provable.ts @@ -5,13 +5,17 @@ */ import { Bool } from './bool.js'; import { Field } from './field.js'; -import type { Provable as Provable_ } from './types/provable-intf.js'; -import type { FlexibleProvable, ProvableExtended } from './types/struct.js'; +import { Provable as Provable_, ProvableType } from './types/provable-intf.js'; +import type { + FlexibleProvable, + FlexibleProvableType, + ProvableExtended, +} from './types/struct.js'; import { Context } from '../util/global-context.js'; import { HashInput, InferJson, - InferProvable, + InferProvableType, InferredProvable, } from './types/provable-derivers.js'; import { @@ -23,6 +27,7 @@ import { } from './core/provable-context.js'; import { witness, witnessAsync, witnessFields } from './types/witness.js'; import { InferValue } from '../../bindings/lib/provable-generic.js'; +import { ToProvable } from '../../lib/provable/types/provable-intf.js'; // external API export { Provable }; @@ -229,7 +234,8 @@ const Provable = { /** * Returns a constant version of a provable type. */ - toConstant(type: Provable, value: T) { + toConstant(type: ProvableType, value: T) { + type = ProvableType.get(type); return type.fromFields( type.toFields(value).map((x) => x.toConstant()), type.toAuxiliary(value) @@ -241,7 +247,7 @@ type ToFieldable = { toFields(): Field[] }; // general provable methods -function assertEqual(type: FlexibleProvable, x: T, y: T): void; +function assertEqual(type: FlexibleProvableType, x: T, y: T): void; function assertEqual(x: T, y: T): void; function assertEqual(typeOrX: any, xOrY: any, yOrUndefined?: any) { if (yOrUndefined === undefined) { @@ -258,7 +264,8 @@ function assertEqualImplicit(x: T, y: T) { xs[i].assertEquals(ys[i]); } } -function assertEqualExplicit(type: Provable, x: T, y: T) { +function assertEqualExplicit(type: ProvableType, x: T, y: T) { + type = ProvableType.get(type); let xs = type.toFields(x); let ys = type.toFields(y); for (let i = 0; i < xs.length; i++) { @@ -266,7 +273,7 @@ function assertEqualExplicit(type: Provable, x: T, y: T) { } } -function equal(type: FlexibleProvable, x: T, y: T): Bool; +function equal(type: FlexibleProvableType, x: T, y: T): Bool; function equal(x: T, y: T): Bool; function equal(typeOrX: any, xOrY: any, yOrUndefined?: any) { if (yOrUndefined === undefined) { @@ -284,13 +291,14 @@ function equalImplicit(x: T, y: T) { checkLength('Provable.equal', xs, ys); return xs.map((x, i) => x.equals(ys[i])).reduce(Bool.and); } -function equalExplicit(type: Provable, x: T, y: T) { +function equalExplicit(type: ProvableType, x: T, y: T) { + type = ProvableType.get(type); let xs = type.toFields(x); let ys = type.toFields(y); return xs.map((x, i) => x.equals(ys[i])).reduce(Bool.and); } -function if_(condition: Bool, type: FlexibleProvable, x: T, y: T): T; +function if_(condition: Bool, type: FlexibleProvableType, x: T, y: T): T; function if_(condition: Bool, x: T, y: T): T; function if_(condition: Bool, typeOrX: any, xOrY: any, yOrUndefined?: any) { if (yOrUndefined === undefined) { @@ -312,7 +320,8 @@ function ifField(b: Field, x: Field, y: Field) { return b.mul(x.sub(y)).add(y).seal(); } -function ifExplicit(condition: Bool, type: Provable, x: T, y: T): T { +function ifExplicit(condition: Bool, type: ProvableType, x: T, y: T): T { + type = ProvableType.get(type); let xs = type.toFields(x); let ys = type.toFields(y); let b = condition.toField(); @@ -352,12 +361,13 @@ function ifImplicit(condition: Bool, x: T, y: T): T { return ifExplicit(condition, type as any as Provable, x, y); } -function switch_>( +function switch_>( mask: Bool[], type: A, values: T[], { allowNonExclusive = false } = {} ): T { + let type_ = ProvableType.get(type as ProvableType); // picks the value at the index where mask is true let nValues = values.length; if (mask.length !== nValues) @@ -375,35 +385,37 @@ function switch_>( }; if (mask.every((b) => b.toField().isConstant())) checkMask(); else Provable.asProver(checkMask); - let size = type.sizeInFields(); + let size = type_.sizeInFields(); let fields = Array(size).fill(new Field(0)); for (let i = 0; i < nValues; i++) { - let valueFields = type.toFields(values[i]); + let valueFields = type_.toFields(values[i]); let maskField = mask[i].toField(); for (let j = 0; j < size; j++) { let maybeField = valueFields[j].mul(maskField); fields[j] = fields[j].add(maybeField); } } - let aux = auxiliary(type as Provable, () => { + let aux = auxiliary(type_, () => { let i = mask.findIndex((b) => b.toBoolean()); if (i === -1) return undefined; return values[i]; }); - return (type as Provable).fromFields(fields, aux); + return type_.fromFields(fields, aux); } function assertEqualIf< - A extends Provable, - T extends InferProvable = InferProvable + A extends ProvableType, + T extends InferProvableType = InferProvableType >(enabled: Bool, type: A, x: T, y: T) { // if the condition is disabled, we check the trivial identity x === x instead let xOrY = ifExplicit(enabled, type, y, x); assertEqual(type, x, xOrY); } -function isConstant(type: Provable, x: T): boolean { - return type.toFields(x).every((x) => x.isConstant()); +function isConstant(type: ProvableType, x: T): boolean { + return ProvableType.get(type) + .toFields(x) + .every((x) => x.isConstant()); } // logging in provable code @@ -498,14 +510,16 @@ function getBlindingValue() { } // TODO this should return a class, like Struct, so you can just use `class Array3 extends Provable.Array(Field, 3) {}` -function provableArray>( +function provableArray>( elementType: A, length: number -): InferredProvable { - type T = InferProvable; - type TValue = InferValue; - type TJson = InferJson; - let type = elementType as ProvableExtended; +): InferredProvable[]> { + type T = InferProvableType; + type TValue = InferValue>; + type TJson = InferJson>; + let type = ProvableType.get( + elementType as ProvableType + ) as ProvableExtended; return { /** * Returns the size of this structure in {@link Field} elements. diff --git a/src/lib/provable/scalar-field.ts b/src/lib/provable/scalar-field.ts index 91c085a025..b1f3343ced 100644 --- a/src/lib/provable/scalar-field.ts +++ b/src/lib/provable/scalar-field.ts @@ -35,9 +35,7 @@ class ScalarField extends createForeignField(Fq.modulus) { if (s.lowBit.isConstant() && s.high254.isConstant()) { return new ScalarField(s.toBigInt()); } - const field = Provable.witness(ScalarField.provable, () => { - return s.toBigInt(); - }); + const field = Provable.witness(ScalarField, () => s.toBigInt()); const foreignField = new ScalarField(field); const scalar = foreignField.toScalar(); Provable.assertEqual(Scalar, s, scalar); diff --git a/src/lib/provable/test/arithmetic.unit-test.ts b/src/lib/provable/test/arithmetic.unit-test.ts index ec02667fc3..3ce9ce07f0 100644 --- a/src/lib/provable/test/arithmetic.unit-test.ts +++ b/src/lib/provable/test/arithmetic.unit-test.ts @@ -12,10 +12,7 @@ import { assert } from '../gadgets/common.js'; let Arithmetic = ZkProgram({ name: 'arithmetic', - publicOutput: provable({ - remainder: Field, - quotient: Field, - }), + publicOutput: provable({ remainder: Field, quotient: Field }), methods: { divMod32: { privateInputs: [Field], diff --git a/src/lib/provable/test/bitwise.unit-test.ts b/src/lib/provable/test/bitwise.unit-test.ts index 1c3bbc8240..23b3c38bc7 100644 --- a/src/lib/provable/test/bitwise.unit-test.ts +++ b/src/lib/provable/test/bitwise.unit-test.ts @@ -36,19 +36,19 @@ let Bitwise = ZkProgram({ xor: { privateInputs: [Field, Field], async method(a: Field, b: Field) { - return Gadgets.xor(a, b, 254); + return Gadgets.xor(a, b, 240); }, }, notUnchecked: { privateInputs: [Field], async method(a: Field) { - return Gadgets.not(a, 254, false); + return Gadgets.not(a, 240, false); }, }, notChecked: { privateInputs: [Field], async method(a: Field) { - return Gadgets.not(a, 254, true); + return Gadgets.not(a, 240, true); }, }, and: { @@ -153,7 +153,7 @@ await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs })( await equivalentAsync({ from: [maybeField], to: field }, { runs })( (x) => { - return Fp.not(x, 254); + return Fp.not(x, 240); }, async (x) => { let proof = await Bitwise.notUnchecked(x); @@ -162,8 +162,8 @@ await equivalentAsync({ from: [maybeField], to: field }, { runs })( ); await equivalentAsync({ from: [maybeField], to: field }, { runs })( (x) => { - if (x > 2n ** 254n) throw Error('Does not fit into 254 bit'); - return Fp.not(x, 254); + if (x > 2n ** 240n) throw Error('Does not fit into 240 bit'); + return Fp.not(x, 240); }, async (x) => { let proof = await Bitwise.notChecked(x); @@ -246,13 +246,13 @@ function xorChain(bits: number) { constraintSystem.fromZkProgram( Bitwise, 'xor', - ifNotAllConstant(contains(xorChain(254))) + ifNotAllConstant(contains(xorChain(240))) ); constraintSystem.fromZkProgram( Bitwise, 'notChecked', - ifNotAllConstant(contains(xorChain(254))) + ifNotAllConstant(contains(xorChain(240))) ); constraintSystem.fromZkProgram( 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 7d1a06eb55..34ed06c60e 100644 --- a/src/lib/provable/test/custom-gates-recursion.unit-test.ts +++ b/src/lib/provable/test/custom-gates-recursion.unit-test.ts @@ -39,12 +39,9 @@ let program = ZkProgram({ privateInputs: [EmptyProof], async method(proof: EmptyProof) { proof.verify(); - let signature_ = Provable.witness( - Ecdsa.Signature.provable, - () => signature - ); - let msgHash_ = Provable.witness(Field3.provable, () => msgHash); - let publicKey_ = Provable.witness(Point.provable, () => publicKey); + let signature_ = Provable.witness(Ecdsa.Signature, () => signature); + let msgHash_ = Provable.witness(Field3, () => msgHash); + let publicKey_ = Provable.witness(Point, () => publicKey); return Ecdsa.verifyV2(Secp256k1, signature_, msgHash_, publicKey_); }, diff --git a/src/lib/provable/test/ecdsa.unit-test.ts b/src/lib/provable/test/ecdsa.unit-test.ts index 89545d9645..68067a60cb 100644 --- a/src/lib/provable/test/ecdsa.unit-test.ts +++ b/src/lib/provable/test/ecdsa.unit-test.ts @@ -147,12 +147,9 @@ let deprecatedVerify = ZkProgram({ ecdsa: { privateInputs: [], async method() { - let signature_ = Provable.witness( - Ecdsa.Signature.provable, - () => signature - ); - let msgHash_ = Provable.witness(Field3.provable, () => msgHash); - let publicKey_ = Provable.witness(Point.provable, () => publicKey); + let signature_ = Provable.witness(Ecdsa.Signature, () => signature); + let msgHash_ = Provable.witness(Field3, () => msgHash); + let publicKey_ = Provable.witness(Point, () => publicKey); return Ecdsa.verify( Secp256k1, @@ -173,12 +170,9 @@ let program = ZkProgram({ ecdsa: { privateInputs: [], async method() { - let signature_ = Provable.witness( - Ecdsa.Signature.provable, - () => signature - ); - let msgHash_ = Provable.witness(Field3.provable, () => msgHash); - let publicKey_ = Provable.witness(Point.provable, () => publicKey); + let signature_ = Provable.witness(Ecdsa.Signature, () => signature); + let msgHash_ = Provable.witness(Field3, () => msgHash); + let publicKey_ = Provable.witness(Point, () => publicKey); return Ecdsa.verifyV2( Secp256k1, diff --git a/src/lib/provable/test/foreign-curve.unit-test.ts b/src/lib/provable/test/foreign-curve.unit-test.ts index 24ce76d979..80921d0ea0 100644 --- a/src/lib/provable/test/foreign-curve.unit-test.ts +++ b/src/lib/provable/test/foreign-curve.unit-test.ts @@ -14,17 +14,17 @@ let scalar = Field.random().toBigInt(); let p = V.toAffine(V.scale(V.fromAffine(h), scalar)); function main() { - let g0 = Provable.witness(Vesta.provable, () => new Vesta(g)); - let one = Provable.witness(Vesta.provable, () => Vesta.generator); + let g0 = Provable.witness(Vesta, () => g); + let one = Provable.witness(Vesta, () => Vesta.generator); let h0 = g0.add(one).double().negate(); - Provable.assertEqual(Vesta.provable, h0, new Vesta(h)); + Provable.assertEqual(Vesta, h0, new Vesta(h)); h0.assertOnCurve(); h0.assertInSubgroup(); - let scalar0 = Provable.witness(Fp.provable, () => new Fp(scalar)); + let scalar0 = Provable.witness(Fp, () => scalar); let p0 = h0.scale(scalar0); - Provable.assertEqual(Vesta.provable, p0, new Vesta(p)); + Provable.assertEqual(Vesta, p0, new Vesta(p)); } console.time('running constant version'); 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 c4f4c517d1..d5ec5724ba 100644 --- a/src/lib/provable/test/foreign-field-gadgets.unit-test.ts +++ b/src/lib/provable/test/foreign-field-gadgets.unit-test.ts @@ -173,41 +173,41 @@ let signs = [1n, -1n, -1n, 1n] satisfies (-1n | 1n)[]; let ffProgram = ZkProgram({ name: 'foreign-field', - publicOutput: Field3.provable, + publicOutput: Field3, methods: { sumchain: { - privateInputs: [Provable.Array(Field3.provable, chainLength)], + privateInputs: [Provable.Array(Field3, chainLength)], async method(xs) { return ForeignField.sum(xs, signs, F.modulus); }, }, mulWithBoundsCheck: { - privateInputs: [Field3.provable, Field3.provable], + privateInputs: [Field3, Field3], async method(x, y) { ForeignField.assertAlmostReduced([x, y], F.modulus); return ForeignField.mul(x, y, F.modulus); }, }, mul: { - privateInputs: [Field3.provable, Field3.provable], + privateInputs: [Field3, Field3], async method(x, y) { return ForeignField.mul(x, y, F.modulus); }, }, inv: { - privateInputs: [Field3.provable], + privateInputs: [Field3], async method(x) { return ForeignField.inv(x, F.modulus); }, }, div: { - privateInputs: [Field3.provable, Field3.provable], + privateInputs: [Field3, Field3], async method(x, y) { return ForeignField.div(x, y, F.modulus); }, }, assertLessThan: { - privateInputs: [Field3.provable, Field3.provable], + privateInputs: [Field3, Field3], async method(x, y) { ForeignField.assertLessThan(x, y); return x; @@ -306,8 +306,8 @@ await equivalentAsync({ from: [f, f], to: unit }, { runs })( function assertMulExample(x: Field3, y: Field3, f: bigint) { // witness x^2, y^2 - let x2 = Provable.witness(Field3.provable, () => ForeignField.mul(x, x, f)); - let y2 = Provable.witness(Field3.provable, () => ForeignField.mul(y, y, f)); + let x2 = Provable.witness(Field3, () => ForeignField.mul(x, x, f)); + let y2 = Provable.witness(Field3, () => ForeignField.mul(y, y, f)); // assert (x - y) * (x + y) = x^2 - y^2 let xMinusY = ForeignField.Sum(x).sub(y); @@ -318,8 +318,8 @@ function assertMulExample(x: Field3, y: Field3, f: bigint) { function assertMulExampleNaive(x: Field3, y: Field3, f: bigint) { // witness x^2, y^2 - let x2 = Provable.witness(Field3.provable, () => ForeignField.mul(x, x, f)); - let y2 = Provable.witness(Field3.provable, () => ForeignField.mul(y, y, f)); + let x2 = Provable.witness(Field3, () => ForeignField.mul(x, x, f)); + let y2 = Provable.witness(Field3, () => ForeignField.mul(y, y, f)); // assert (x - y) * (x + y) = x^2 - y^2 let lhs = ForeignField.mul( @@ -328,7 +328,7 @@ function assertMulExampleNaive(x: Field3, y: Field3, f: bigint) { f ); let rhs = ForeignField.sub(x2, y2, f); - Provable.assertEqual(Field3.provable, lhs, rhs); + Provable.assertEqual(Field3, lhs, rhs); } let from2 = { from: [f, f] satisfies AnyTuple }; diff --git a/src/lib/provable/test/keccak.unit-test.ts b/src/lib/provable/test/keccak.unit-test.ts index 1678b0a231..92e9eb8854 100644 --- a/src/lib/provable/test/keccak.unit-test.ts +++ b/src/lib/provable/test/keccak.unit-test.ts @@ -121,8 +121,8 @@ const preImageLength = 32; // No need to test Ethereum because it's just a special case of preNist const KeccakProgram = ZkProgram({ name: `keccak-test-${digestLength}`, - publicInput: Bytes(preImageLength).provable, - publicOutput: Bytes(digestLengthBytes).provable, + publicInput: Bytes(preImageLength), + publicOutput: Bytes(digestLengthBytes), methods: { nistSha3: { privateInputs: [], diff --git a/src/lib/provable/test/merkle-tree.unit-test.ts b/src/lib/provable/test/merkle-tree.unit-test.ts index 010706179e..427fdfef53 100644 --- a/src/lib/provable/test/merkle-tree.unit-test.ts +++ b/src/lib/provable/test/merkle-tree.unit-test.ts @@ -209,7 +209,7 @@ test( // pass the map to a circuit runAndCheckSync(() => { - map = Provable.witness(MerkleMap.provable, () => map); + map = Provable.witness(MerkleMap, () => map); let initialKeysF = initialKeys.map(witness); let keysF = keys.map(witness); let valuesF = values.map(witness); @@ -249,7 +249,7 @@ test( // move the map back to constants Provable.asProver(() => { - map = Provable.toConstant(MerkleMap.provable, map); + map = Provable.toConstant(MerkleMap, map); }); }); diff --git a/src/lib/provable/test/sha256.unit-test.ts b/src/lib/provable/test/sha256.unit-test.ts index c18d661752..16233721e9 100644 --- a/src/lib/provable/test/sha256.unit-test.ts +++ b/src/lib/provable/test/sha256.unit-test.ts @@ -23,10 +23,10 @@ sample(Random.nat(400), 5).forEach((preimageLength) => { const Sha256Program = ZkProgram({ name: `sha256`, - publicOutput: Bytes(32).provable, + publicOutput: Bytes(32), methods: { sha256: { - privateInputs: [Bytes(192).provable], + privateInputs: [Bytes(192)], async method(preImage: Bytes) { return Gadgets.SHA256.hash(preImage); }, diff --git a/src/lib/provable/test/struct.unit-test.ts b/src/lib/provable/test/struct.unit-test.ts index 8aaef12fe8..0acbd21192 100644 --- a/src/lib/provable/test/struct.unit-test.ts +++ b/src/lib/provable/test/struct.unit-test.ts @@ -24,8 +24,8 @@ import { modifiedField } from '../types/fields.js'; let type = provable({ nested: { a: Number, b: Boolean }, other: String, - pk: PublicKey, - bool: Bool, + pk: { provable: PublicKey }, + bool: { provable: Bool }, uint: [UInt32, UInt32], }); @@ -132,14 +132,14 @@ class MyStruct extends Struct({ nested: { a: Number, b: Boolean }, other: String, pk: PublicKey, - uint: [UInt32, UInt32], + uint: [UInt32, { provable: UInt32 }], }) {} class MyStructPure extends Struct({ nested: { a: Field, b: UInt32 }, other: Field, pk: PublicKey, - uint: [UInt32, UInt32], + uint: [UInt32, { provable: UInt32 }], }) {} // Struct.fromValue() works on both js and provable inputs diff --git a/src/lib/provable/types/provable-derivers.ts b/src/lib/provable/types/provable-derivers.ts index 8df54c7c0f..4cb5178d47 100644 --- a/src/lib/provable/types/provable-derivers.ts +++ b/src/lib/provable/types/provable-derivers.ts @@ -1,4 +1,10 @@ -import { Provable, ProvablePure } from './provable-intf.js'; +import { + Provable, + ProvableHashable, + ProvablePure, + ProvableType, + ToProvable, +} from './provable-intf.js'; import type { Field } from '../wrapped.js'; import { createDerivers, @@ -7,9 +13,13 @@ import { InferJson, InferredProvable as GenericInferredProvable, IsPure as GenericIsPure, + NestedProvable as GenericNestedProvable, createHashInput, Constructor, InferValue, + InferJsonNested, + InferValueNested, + InferProvableNested, } from '../../../bindings/lib/provable-generic.js'; import { Tuple } from '../../util/types.js'; import { GenericHashInput } from '../../../bindings/lib/generic.js'; @@ -22,6 +32,7 @@ export { provablePure, provableTuple, provableFromClass, + provableExtends, }; // internal API @@ -29,9 +40,11 @@ export { NonMethods, HashInput, InferProvable, + InferProvableType, InferJson, InferredProvable, IsPure, + NestedProvable, }; type ProvableExtension = { @@ -49,6 +62,7 @@ type ProvablePureExtended = ProvablePure< ProvableExtension; type InferProvable = GenericInferProvable; +type InferProvableType = InferProvable>; type InferredProvable = GenericInferredProvable; type IsPure = GenericIsPure; type ProvableInferPureFrom = IsPure extends true @@ -58,6 +72,8 @@ type ProvableInferPureFrom = IsPure extends true type HashInput = GenericHashInput; const HashInput = createHashInput(); +type NestedProvable = GenericNestedProvable; + const { provable } = createDerivers(); function provablePure( @@ -70,13 +86,18 @@ function provableTuple>(types: T): InferredProvable { return provable(types) as any; } -function provableFromClass>( +function provableFromClass< + A extends NestedProvable, + T extends InferProvableNested, + V extends InferValueNested, + J extends InferJsonNested +>( Class: Constructor & { check?: (x: T) => void; empty?: () => T }, typeObj: A ): IsPure extends true - ? ProvablePureExtended, InferJson> - : ProvableExtended, InferJson> { - let raw = provable(typeObj); + ? ProvablePureExtended + : ProvableExtended { + let raw: ProvableExtended = provable(typeObj) as any; return { sizeInFields: raw.sizeInFields, toFields: raw.toFields, @@ -105,10 +126,46 @@ function provableFromClass>( ? Class.empty() : construct(Class, raw.empty()); }, - } satisfies ProvableExtended, InferJson> as any; + } satisfies ProvableExtended as any; } function construct(Class: Constructor, value: Raw): T { let instance = Object.create(Class.prototype); return Object.assign(instance, value); } + +function provableExtends< + A extends ProvableHashable, + T extends InferProvable, + S extends T +>(S: new (t: T) => S, base: A) { + return { + sizeInFields() { + return base.sizeInFields(); + }, + toFields(value: S | T) { + return base.toFields(value); + }, + toAuxiliary(value?: S | T) { + return base.toAuxiliary(value); + }, + fromFields(fields, aux) { + return new S(base.fromFields(fields, aux)); + }, + check(value: S | T) { + base.check(value); + }, + toValue(value: S | T) { + return base.toValue(value); + }, + fromValue(value) { + return new S(base.fromValue(value)); + }, + empty() { + return new S(base.empty()); + }, + toInput(value: S | T) { + return base.toInput(value); + }, + } satisfies ProvableHashable>; +} diff --git a/src/lib/provable/types/provable-intf.ts b/src/lib/provable/types/provable-intf.ts index e3c1ea5beb..0a1b582017 100644 --- a/src/lib/provable/types/provable-intf.ts +++ b/src/lib/provable/types/provable-intf.ts @@ -1,6 +1,15 @@ import type { Field } from '../field.js'; -export { Provable, ProvablePure }; +export { + Provable, + ProvablePure, + ProvableWithEmpty, + ProvableHashable, + ProvableType, + ProvableTypePure, + ToProvable, + WithProvable, +}; /** * `Provable` is the general interface for provable types in o1js. @@ -86,3 +95,38 @@ type Provable = { type ProvablePure = Omit, 'fromFields'> & { fromFields: (fields: Field[]) => T; }; + +type ProvableWithEmpty = Provable & { + empty: () => T; +}; + +type HashInput = { fields?: Field[]; packed?: [Field, number][] }; + +type ProvableHashable = ProvableWithEmpty & { + toInput: (x: T) => HashInput; +}; + +// helpers to accept { provable: Type } instead of Type + +type WithProvable = { provable: A } | A; + +type ProvableType = WithProvable>; +type ProvableTypePure = WithProvable>; + +type ToProvable> = A extends { + provable: infer P; +} + ? P + : A; + +const ProvableType = { + get>(type: A): ToProvable { + return ( + (typeof type === 'object' || typeof type === 'function') && + type !== null && + 'provable' in type + ? type.provable + : type + ) as ToProvable; + }, +}; diff --git a/src/lib/provable/types/struct.ts b/src/lib/provable/types/struct.ts index 96d962d4e6..4569997eeb 100644 --- a/src/lib/provable/types/struct.ts +++ b/src/lib/provable/types/struct.ts @@ -13,7 +13,7 @@ import type { } from './provable-derivers.js'; import { Provable } from '../provable.js'; import { DynamicProof, Proof } from '../../proof-system/zkprogram.js'; -import { ProvablePure } from './provable-intf.js'; +import { ProvablePure, ProvableType } from './provable-intf.js'; import { From, InferValue } from '../../../bindings/lib/provable-generic.js'; // external API @@ -23,6 +23,7 @@ export { Struct, FlexibleProvable, FlexibleProvablePure, + FlexibleProvableType, }; // internal API @@ -59,6 +60,7 @@ type StructPure = ProvablePureExtended> & Constructor & { _isStruct: true }; type FlexibleProvable = Provable | Struct; type FlexibleProvablePure = ProvablePure | StructPure; +type FlexibleProvableType = ProvableType | Struct; type Constructor = new (...args: any) => T; type AnyConstructor = Constructor; diff --git a/src/lib/provable/types/unconstrained.ts b/src/lib/provable/types/unconstrained.ts index 857a1d9665..efb3cd4eba 100644 --- a/src/lib/provable/types/unconstrained.ts +++ b/src/lib/provable/types/unconstrained.ts @@ -95,10 +95,7 @@ and Provable.asProver() blocks, which execute outside the proof. * Create an `Unconstrained` from a witness computation. */ static witness(compute: () => T): Unconstrained { - return witness( - Unconstrained.provable, - () => new Unconstrained(true, compute()) - ); + return witness(Unconstrained, compute); } /** @@ -111,7 +108,7 @@ and Provable.asProver() blocks, which execute outside the proof. }); } - static provable: Provable, Unconstrained> & { + static provable: UnconstrainedProvable & { toInput: (x: Unconstrained) => { fields?: Field[]; packed?: [Field, number][]; @@ -123,18 +120,15 @@ and Provable.asProver() blocks, which execute outside the proof. toAuxiliary: (t?: any) => [t ?? new Unconstrained(false)], fromFields: (_, [t]) => t, check: () => {}, - toValue: (t) => t, - fromValue: (t) => t, + toValue: (t) => t.get(), + fromValue: (t) => Unconstrained.from(t), toInput: () => ({}), empty: (): any => { throw Error('There is no default empty value for Unconstrained.'); }, }; - static provableWithEmpty(empty: T): Provable< - Unconstrained, - Unconstrained - > & { + static withEmpty(empty: T): Provable, T> & { toInput: (x: Unconstrained) => { fields?: Field[]; packed?: [Field, number][]; @@ -146,4 +140,13 @@ and Provable.asProver() blocks, which execute outside the proof. empty: () => Unconstrained.from(empty), }; } + + /** + * @deprecated + */ + static provableWithEmpty(empty: T) { + return Unconstrained.withEmpty(empty); + } } + +type UnconstrainedProvable = Provable, T>; diff --git a/src/lib/provable/types/witness.ts b/src/lib/provable/types/witness.ts index 15efb266f4..2e86cac798 100644 --- a/src/lib/provable/types/witness.ts +++ b/src/lib/provable/types/witness.ts @@ -1,6 +1,6 @@ import type { Field } from '../field.js'; import type { FlexibleProvable, InferProvable } from './struct.js'; -import type { Provable } from './provable-intf.js'; +import { Provable, ProvableType, ToProvable } from './provable-intf.js'; import { inCheckedComputation, snarkContext, @@ -12,25 +12,27 @@ import { createField } from '../core/field-constructor.js'; export { witness, witnessAsync, witnessFields }; -function witness, T extends From = From>( - type: A, - compute: () => T -): InferProvable { - type S = InferProvable; +function witness< + A extends ProvableType, + T extends From> = From> +>(type: A, compute: () => T): InferProvable> { + type S = InferProvable>; + const provable: Provable = ProvableType.get(type); let ctx = snarkContext.get(); // outside provable code, we just call the callback and return its cloned result if (!inCheckedComputation() || ctx.inWitnessBlock) { - return clone(type, type.fromValue(compute())); + return clone(provable, provable.fromValue(compute())); } let proverValue: S | undefined = undefined; let fields: Field[]; let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); try { - fields = exists(type.sizeInFields(), () => { - proverValue = type.fromValue(compute()); - let fields = type.toFields(proverValue); + fields = exists(provable.sizeInFields(), () => { + let value = provable.fromValue(compute()); + proverValue = value; + let fields = provable.toFields(value); return fields.map((x) => x.toBigInt()); }); } finally { @@ -38,35 +40,39 @@ function witness, T extends From = From>( } // rebuild the value from its fields (which are now variables) and aux data - let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); + let aux = provable.toAuxiliary(proverValue); + let value = provable.fromFields(fields, aux); // add type-specific constraints - type.check(value); + provable.check(value); return value; } async function witnessAsync< - T, - S extends FlexibleProvable = FlexibleProvable ->(type: S, compute: () => Promise): Promise { + A extends ProvableType, + T extends From> = From> +>(type: A, compute: () => Promise): Promise { + type S = InferProvable>; + const provable: Provable = ProvableType.get(type); + let ctx = snarkContext.get(); // outside provable code, we just call the callback and return its cloned result if (!inCheckedComputation() || ctx.inWitnessBlock) { let value: T = await compute(); - return clone(type, value); + return clone(provable, provable.fromValue(value)); } - let proverValue: T | undefined = undefined; + let proverValue: S | undefined = undefined; let fields: Field[]; // call into `existsAsync` to witness the raw field elements let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); try { - fields = await existsAsync(type.sizeInFields(), async () => { - proverValue = await compute(); - let fields = type.toFields(proverValue); + fields = await existsAsync(provable.sizeInFields(), async () => { + let value: S = provable.fromValue(await compute()); + proverValue = value; + let fields = provable.toFields(value); return fields.map((x) => x.toBigInt()); }); } finally { @@ -74,11 +80,11 @@ async function witnessAsync< } // rebuild the value from its fields (which are now variables) and aux data - let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); + let aux = provable.toAuxiliary(proverValue); + let value = provable.fromFields(fields, aux); // add type-specific constraints - type.check(value); + provable.check(value); return value; } diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 7111e92d2d..4f10821845 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -22,6 +22,7 @@ import { ZkappUri, PublicKey, StateHash, + MayUseToken, } from '../../bindings/mina-transaction/transaction-leaves-bigint.js'; import { genericLayoutFold } from '../../bindings/lib/from-layout.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; @@ -99,6 +100,23 @@ const authRequired = map( ), AuthRequired.fromJSON ); +const mayUseToken = map( + oneOf( + { + parentsOwnToken: true, + inheritFromParent: false, + }, + { + parentsOwnToken: false, + inheritFromParent: true, + }, + { + parentsOwnToken: false, + inheritFromParent: false, + } + ), + MayUseToken.fromJSON +); const tokenSymbolString = reject( string(nat(tokenSymbolLength)), (s) => stringLengthInBytes(s) > 6 @@ -145,6 +163,7 @@ const Generators: Generators = { string: base58(nat(50)), // TODO replace various strings, like signature, with parsed types number: nat(3), TransactionVersion: uint32, + MayUseToken: mayUseToken, }; let typeToBigintGenerator = new Map, Random>( [TypeMap, primitiveTypeMap, customTypes] @@ -255,6 +274,7 @@ const JsonGenerators: JsonGenerators = { string: base58(nat(50)), number: nat(3), TransactionVersion: json_.uint32, + MayUseToken: mapWithInvalid(mayUseToken, MayUseToken.toJSON), }; let typeToJsonGenerator = new Map, Random>( [TypeMap, primitiveTypeMap, customTypes] diff --git a/src/mina b/src/mina index fe17617563..02accb5bc5 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit fe17617563897f041a73530ad98a118b37eec665 +Subproject commit 02accb5bc5bd152b5b849d864b3339ada56ef891 diff --git a/src/tests/inductive-proofs-small.ts b/src/tests/inductive-proofs-small.ts index acde6a86f6..f20f7b4fc8 100644 --- a/src/tests/inductive-proofs-small.ts +++ b/src/tests/inductive-proofs-small.ts @@ -9,7 +9,7 @@ let MaxProofsVerifiedOne = ZkProgram({ baseCase: { privateInputs: [], - method(publicInput: Field) { + async method(publicInput: Field) { publicInput.assertEquals(Field(0)); }, }, @@ -17,7 +17,10 @@ let MaxProofsVerifiedOne = ZkProgram({ mergeOne: { privateInputs: [SelfProof], - method(publicInput: Field, earlierProof: SelfProof) { + async method( + publicInput: Field, + earlierProof: SelfProof + ) { earlierProof.verify(); earlierProof.publicInput.add(1).assertEquals(publicInput); }, diff --git a/src/tests/inductive-proofs.ts b/src/tests/inductive-proofs.ts index 101487dae5..ebf22754f5 100644 --- a/src/tests/inductive-proofs.ts +++ b/src/tests/inductive-proofs.ts @@ -9,7 +9,7 @@ let MaxProofsVerifiedZero = ZkProgram({ baseCase: { privateInputs: [], - method(publicInput: Field) { + async method(publicInput: Field) { publicInput.assertEquals(Field(0)); }, }, @@ -24,7 +24,7 @@ let MaxProofsVerifiedOne = ZkProgram({ baseCase: { privateInputs: [], - method(publicInput: Field) { + async method(publicInput: Field) { publicInput.assertEquals(Field(0)); }, }, @@ -32,7 +32,10 @@ let MaxProofsVerifiedOne = ZkProgram({ mergeOne: { privateInputs: [SelfProof], - method(publicInput: Field, earlierProof: SelfProof) { + async method( + publicInput: Field, + earlierProof: SelfProof + ) { earlierProof.verify(); earlierProof.publicInput.add(1).assertEquals(publicInput); }, @@ -48,7 +51,7 @@ let MaxProofsVerifiedTwo = ZkProgram({ baseCase: { privateInputs: [], - method(publicInput: Field) { + async method(publicInput: Field) { publicInput.assertEquals(Field(0)); }, }, @@ -56,7 +59,10 @@ let MaxProofsVerifiedTwo = ZkProgram({ mergeOne: { privateInputs: [SelfProof], - method(publicInput: Field, earlierProof: SelfProof) { + async method( + publicInput: Field, + earlierProof: SelfProof + ) { earlierProof.verify(); earlierProof.publicInput.add(1).assertEquals(publicInput); }, @@ -65,7 +71,7 @@ let MaxProofsVerifiedTwo = ZkProgram({ mergeTwo: { privateInputs: [SelfProof, SelfProof], - method( + async method( publicInput: Field, p1: SelfProof, p2: SelfProof diff --git a/tests/vk-regression/diverse-zk-program.ts b/tests/vk-regression/diverse-zk-program.ts index 69333903cf..08649dc844 100644 --- a/tests/vk-regression/diverse-zk-program.ts +++ b/tests/vk-regression/diverse-zk-program.ts @@ -32,11 +32,7 @@ const diverse = ZkProgram({ methods: { // foreign field / curve ops, multi-range checks ecdsa: { - privateInputs: [ - Secp256k1Scalar.provable, - Secp256k1Signature.provable, - Secp256k1.provable, - ], + privateInputs: [Secp256k1Scalar, Secp256k1Signature, Secp256k1], async method( message: Secp256k1Scalar, signature: Secp256k1Signature, @@ -48,7 +44,7 @@ const diverse = ZkProgram({ // bitwise gadgets sha3: { - privateInputs: [Bytes128.provable], + privateInputs: [Bytes128], async method(xs: Bytes128) { Hash.SHA3_256.hash(xs); }, diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index 740abf170b..b0030807db 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -107,22 +107,22 @@ const bytes32 = Bytes32.from([]); const HashCS = constraintSystem('Hashes', { SHA256() { - let xs = Provable.witness(Bytes32.provable, () => bytes32); + let xs = Provable.witness(Bytes32, () => bytes32); Hash.SHA3_256.hash(xs); }, SHA384() { - let xs = Provable.witness(Bytes32.provable, () => bytes32); + let xs = Provable.witness(Bytes32, () => bytes32); Hash.SHA3_384.hash(xs); }, SHA512() { - let xs = Provable.witness(Bytes32.provable, () => bytes32); + let xs = Provable.witness(Bytes32, () => bytes32); Hash.SHA3_512.hash(xs); }, Keccak256() { - let xs = Provable.witness(Bytes32.provable, () => bytes32); + let xs = Provable.witness(Bytes32, () => bytes32); Hash.Keccak256.hash(xs); },