diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
index f3673a3154..eb45a454cb 100644
--- a/.github/workflows/benchmarks.yml
+++ b/.github/workflows/benchmarks.yml
@@ -8,7 +8,7 @@ on:
workflow_dispatch: {}
concurrency:
- group: ${{ github.ref }}
+ group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml
index 402107ea05..1a540c97bd 100644
--- a/.github/workflows/build-action.yml
+++ b/.github/workflows/build-action.yml
@@ -10,7 +10,7 @@ on:
workflow_dispatch: {}
concurrency:
- group: ${{ github.ref }}
+ group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
@@ -301,13 +301,25 @@ jobs:
run: |
npm ci
npm run prepublishOnly
+
- name: Publish to NPM if version has changed
+ id: publish
uses: JS-DevTools/npm-publish@v3
with:
token: ${{ secrets.NPM_TOKEN }}
strategy: upgrade
env:
INPUT_TOKEN: ${{ secrets.NPM_TOKEN }}
+
+ - name: Tag new version
+ if: ${{ steps.publish.outputs.type }} # https://github.com/JS-DevTools/npm-publish?tab=readme-ov-file#action-output
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ RELEASED_VERSION: ${{ steps.publish.outputs.version }}
+ run: |
+ git tag $RELEASED_VERSION
+ git push origin $RELEASED_VERSION
+
Release-mina-signer-on-NPM:
if: github.ref == 'refs/heads/main'
diff --git a/.github/workflows/build-bindings.yml b/.github/workflows/build-bindings.yml
index 7ffc1a7cff..d1b6772a06 100644
--- a/.github/workflows/build-bindings.yml
+++ b/.github/workflows/build-bindings.yml
@@ -1,4 +1,4 @@
-# Purpose: We want to build the o1js bindings in CI so that people in the
+# Purpose: We want to build the o1js bindings in CI so that people in the
# community can change them without being scared of breaking things, or
# needing to do the complicated (without nix) build setup.
@@ -7,19 +7,30 @@ name: Build o1js bindings
on:
pull_request:
-jobs:
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
nix-build:
name: build-bindings-ubuntu
runs-on: [sdk-self-hosted-linux-amd64-build-system]
steps:
+ - name: Set up Nix
+ run: echo "PATH=$PATH:/nix/var/nix/profiles/default/bin" >> $GITHUB_ENV
- name: Disable smudging
run: echo "GIT_LFS_SKIP_SMUDGE=1" >> $GITHUB_ENV
- uses: actions/checkout@v4
with:
submodules: recursive
- - run: |
+ - name: Build the o1js bindings
+ run: |
set -Eeu
- # Until we restart the runner and the PATH gets updated from /etc/bash.bashrc
- export PATH="$PATH":/nix/var/nix/profiles/default/bin
./pin.sh
nix develop o1js --command bash -c "npm run build:update-bindings"
+ - name: Cleanup the Nix store
+ run: |
+ nix-env --delete-generations old
+ nix-collect-garbage -d --quiet
+ nix-store --gc --print-dead
+ nix-store --optimise
diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml
index 6bd4433615..b4f7eb790a 100644
--- a/.github/workflows/doc.yml
+++ b/.github/workflows/doc.yml
@@ -4,6 +4,10 @@ on:
branches:
- main
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
Build-Doc:
runs-on: ubuntu-latest
diff --git a/.github/workflows/live-tests.yml b/.github/workflows/live-tests.yml
index 3f14642714..dc7db700a1 100644
--- a/.github/workflows/live-tests.yml
+++ b/.github/workflows/live-tests.yml
@@ -13,7 +13,7 @@ on:
workflow_dispatch: {}
concurrency:
- group: ${{ github.ref }}
+ group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 67e89f95bb..707b5245a5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -58,13 +58,6 @@ jobs:
git add CHANGELOG.md
git commit -m "Update CHANGELOG for new version $NEW_VERSION"
- - name: Delete existing release tag
- run: |
- if git rev-parse $NEW_VERSION >/dev/null 2>&1; then
- git tag -d $NEW_VERSION
- git push origin :refs/tags/$NEW_VERSION
- fi
-
- name: Delete existing release branch
run: |
if git ls-remote --heads origin release/${NEW_VERSION} | grep release/${NEW_VERSION}; then
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3862a9083a..f895921488 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,16 +19,19 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added
-- Increased maximum supported amount of methods in a `SmartContract` or `ZkProgram` to 30. https://github.com/o1-labs/o1js/pull/1918
+-`ZkProgram` to support non-pure provable types as inputs and outputs https://github.com/o1-labs/o1js/pull/1828
+
+- Add `enforceTransactionLimits` parameter on Network https://github.com/o1-labs/o1js/issues/1910
+
+- Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922
### Fixed
- Compiling stuck in the browser for recursive zkprograms https://github.com/o1-labs/o1js/pull/1906
+- Error message in `rangeCheck16` gadget https://github.com/o1-labs/o1js/pull/1920
## [2.1.0](https://github.com/o1-labs/o1js/compare/b04520d...e1bac02) - 2024-11-13
-### Added
-
- Support secp256r1 in elliptic curve and ECDSA gadgets https://github.com/o1-labs/o1js/pull/1885
### Fixed
diff --git a/src/examples/zkprogram/program-with-non-pure-input.ts b/src/examples/zkprogram/program-with-non-pure-input.ts
new file mode 100644
index 0000000000..f217ed8721
--- /dev/null
+++ b/src/examples/zkprogram/program-with-non-pure-input.ts
@@ -0,0 +1,62 @@
+import { Field, Struct, ZkProgram, assert } from 'o1js';
+
+class MyStruct extends Struct({
+ label: String,
+ value: Field,
+}) {}
+
+let NonPureIOprogram = ZkProgram({
+ name: 'example-with-non-pure-io',
+ publicInput: MyStruct,
+ publicOutput: MyStruct,
+
+ methods: {
+ baseCase: {
+ privateInputs: [],
+ async method(input: MyStruct) {
+ //update input in circuit
+ input.label = 'in-circuit';
+ return {
+ publicOutput: input,
+ };
+ },
+ },
+ },
+});
+
+let NonPureOutputProgram = ZkProgram({
+ name: 'example-with-non-pure-output',
+ publicOutput: MyStruct,
+
+ methods: {
+ baseCase: {
+ privateInputs: [],
+ async method() {
+ return {
+ publicOutput: new MyStruct({ label: 'output', value: Field(5) }),
+ };
+ },
+ },
+ },
+});
+
+console.log('compiling NonPureIOprogram...');
+await NonPureIOprogram.compile();
+console.log('compile done');
+let input = new MyStruct({ label: 'input', value: Field(5) });
+let proof;
+({ proof } = await NonPureIOprogram.baseCase(input));
+let isProof1Valid = await NonPureIOprogram.verify(proof);
+assert(isProof1Valid, 'proof not valid!');
+assert(proof.publicOutput.label === 'in-circuit');
+console.log('i/o proof', proof);
+
+console.log('compiling NonPureOutputProgram...');
+await NonPureOutputProgram.compile();
+console.log('compile done');
+
+({ proof } = await NonPureOutputProgram.baseCase());
+let isProof2Valid = await NonPureOutputProgram.verify(proof);
+assert(isProof2Valid, 'proof not valid!');
+assert(proof.publicOutput.label === 'output');
+console.log('output proof', proof);
diff --git a/src/lib/mina/mina.network.unit-test.ts b/src/lib/mina/mina.network.unit-test.ts
new file mode 100644
index 0000000000..73b2a0e1d0
--- /dev/null
+++ b/src/lib/mina/mina.network.unit-test.ts
@@ -0,0 +1,202 @@
+import {
+ State,
+ state,
+ UInt64,
+ Bool,
+ SmartContract,
+ Mina,
+ AccountUpdate,
+ method,
+ PublicKey,
+ Permissions,
+ VerificationKey,
+ Field,
+ Int64,
+ TokenId,
+ TokenContract as TokenContractBase,
+ AccountUpdateForest,
+ PrivateKey,
+} from 'o1js';
+import { test, describe, it, before } from 'node:test';
+import { expect } from 'expect';
+
+
+
+const defaultNetwork = Mina.Network({
+ networkId: "testnet",
+ mina: "https://example.com/graphql",
+ archive: "https://example.com//graphql"
+});
+
+const enforcedNetwork = Mina.Network({
+ networkId: "testnet",
+ mina: "https://example.com/graphql",
+ archive: "https://example.com//graphql",
+ bypassTransactionLimits: false
+});
+
+const unlimitedNetwork = Mina.Network({
+ networkId: "testnet",
+ mina: "https://unlimited.com/graphql",
+ archive: "https://unlimited.com//graphql",
+ bypassTransactionLimits: true
+});
+
+describe('Test default network', () => {
+ let bobAccount: PublicKey,
+ bobKey: PrivateKey;
+
+ before(async () => {
+
+ Mina.setActiveInstance(defaultNetwork);
+ bobKey = PrivateKey.random();
+ bobAccount = bobKey.toPublicKey();
+ });
+
+
+ it('Simple account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(1));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ });
+ await txn.prove();
+ await txn.sign([bobKey]).safeSend();
+
+ });
+
+ it('Multiple account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ for (let index = 0; index < 2; index++) {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ }
+ });
+ await txn.prove();
+ await txn.sign([bobKey]).safeSend();
+
+ });
+
+ it('More than limit account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ for (let index = 0; index < 12; index++) {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ }
+ });
+ await txn.prove();
+ // failure with default bypassTransactionLimits value
+ await expect(txn.sign([bobKey]).safeSend()).rejects.toThrow();
+ });
+});
+
+describe('Test enforced network', () => {
+ let bobAccount: PublicKey,
+ bobKey: PrivateKey;
+
+ before(async () => {
+
+ Mina.setActiveInstance(enforcedNetwork);
+ bobKey = PrivateKey.random();
+ bobAccount = bobKey.toPublicKey();
+ });
+
+
+ it('Simple account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(1));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ });
+ await txn.prove();
+ await txn.sign([bobKey]).safeSend();
+
+ });
+
+ it('Multiple account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ for (let index = 0; index < 2; index++) {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ }
+ });
+ await txn.prove();
+ await txn.sign([bobKey]).safeSend();
+
+ });
+
+ it('More than limit account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ for (let index = 0; index < 12; index++) {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ }
+ });
+ await txn.prove();
+ // failure with bypassTransactionLimits = false
+ await expect(txn.sign([bobKey]).safeSend()).rejects.toThrow();
+ });
+});
+
+describe('Test unlimited network', () => {
+ let bobAccount: PublicKey,
+ bobKey: PrivateKey;
+
+ before(async () => {
+
+ Mina.setActiveInstance(unlimitedNetwork);
+ bobKey = PrivateKey.random();
+ bobAccount = bobKey.toPublicKey();
+ });
+
+
+ it('Simple account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(1));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ });
+ await txn.prove();
+ await txn.sign([bobKey]).safeSend();
+
+ });
+
+ it('Multiple account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ for (let index = 0; index < 2; index++) {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ }
+ });
+ await txn.prove();
+ await txn.sign([bobKey]).safeSend();
+
+ });
+
+ it('More than limit account update', async () => {
+
+ let txn = await Mina.transaction(async () => {
+ for (let index = 0; index < 12; index++) {
+ const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index));
+ accountUpdateBob.account.balance.requireEquals(UInt64.zero);
+ accountUpdateBob.balance.addInPlace(UInt64.one);
+ }
+ });
+ await txn.prove();
+ // success with bypassTransactionLimits = true
+ await txn.sign([bobKey]).safeSend();
+ });
+});
\ No newline at end of file
diff --git a/src/lib/mina/mina.ts b/src/lib/mina/mina.ts
index 2dc41d5422..67edd659e5 100644
--- a/src/lib/mina/mina.ts
+++ b/src/lib/mina/mina.ts
@@ -104,21 +104,24 @@ function Network(options: {
mina: string | string[];
archive?: string | string[];
lightnetAccountManager?: string;
+ bypassTransactionLimits?: boolean;
}): Mina;
function Network(
options:
| {
- networkId?: NetworkId;
- mina: string | string[];
- archive?: string | string[];
- lightnetAccountManager?: string;
- }
+ networkId?: NetworkId;
+ mina: string | string[];
+ archive?: string | string[];
+ lightnetAccountManager?: string;
+ bypassTransactionLimits?: boolean;
+ }
| string
): Mina {
let minaNetworkId: NetworkId = 'testnet';
let minaGraphqlEndpoint: string;
let archiveEndpoint: string;
let lightnetAccountManagerEndpoint: string;
+ let enforceTransactionLimits: boolean = true;
if (options && typeof options === 'string') {
minaGraphqlEndpoint = options;
@@ -158,6 +161,11 @@ function Network(
lightnetAccountManagerEndpoint = options.lightnetAccountManager;
Fetch.setLightnetAccountManagerEndpoint(lightnetAccountManagerEndpoint);
}
+
+ if (options.bypassTransactionLimits !== undefined &&
+ typeof options.bypassTransactionLimits === 'boolean') {
+ enforceTransactionLimits = !options.bypassTransactionLimits;
+ }
} else {
throw new Error(
"Network: malformed input. Please provide a string or an object with 'mina' and 'archive' endpoints."
@@ -251,7 +259,7 @@ function Network(
},
sendTransaction(txn) {
return toPendingTransactionPromise(async () => {
- verifyTransactionLimits(txn.transaction);
+ if (enforceTransactionLimits) verifyTransactionLimits(txn.transaction);
let [response, error] = await Fetch.sendZkapp(txn.toJSON());
let errors: string[] = [];
diff --git a/src/lib/proof-system/proof.ts b/src/lib/proof-system/proof.ts
index 9111482cf9..f19402fc8d 100644
--- a/src/lib/proof-system/proof.ts
+++ b/src/lib/proof-system/proof.ts
@@ -2,7 +2,7 @@ import { initializeBindings, withThreadPool } from '../../snarky.js';
import { Pickles } from '../../snarky.js';
import { Field, Bool } from '../provable/wrapped.js';
import type {
- FlexibleProvablePure,
+ FlexibleProvable,
InferProvable,
} from '../provable/types/struct.js';
import { FeatureFlags } from './feature-flags.js';
@@ -22,8 +22,8 @@ export { dummyProof, extractProofs, extractProofTypes, type ProofValue };
type MaxProofs = 0 | 1 | 2;
class ProofBase {
- static publicInputType: FlexibleProvablePure = undefined as any;
- static publicOutputType: FlexibleProvablePure = undefined as any;
+ static publicInputType: FlexibleProvable = undefined as any;
+ static publicOutputType: FlexibleProvable = undefined as any;
static tag: () => { name: string } = () => {
throw Error(
`You cannot use the \`Proof\` class directly. Instead, define a subclass:\n` +
diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts
index 26b06039eb..29cd5d663d 100644
--- a/src/lib/proof-system/zkprogram.ts
+++ b/src/lib/proof-system/zkprogram.ts
@@ -3,7 +3,7 @@ import { Snarky, initializeBindings, withThreadPool } from '../../snarky.js';
import { Pickles, Gate } from '../../snarky.js';
import { Field } from '../provable/wrapped.js';
import {
- FlexibleProvablePure,
+ FlexibleProvable,
InferProvable,
ProvablePureExtended,
Struct,
@@ -89,8 +89,15 @@ const Void: ProvablePureExtended = EmptyVoid();
function createProgramState() {
let methodCache: Map = new Map();
-
return {
+ setNonPureOutput(value: any[]) {
+ methodCache.set('__nonPureOutput__', value);
+ },
+ getNonPureOutput(): any[] {
+ let entry = methodCache.get('__nonPureOutput__');
+ if (entry === undefined) return [];
+ return entry as any[];
+ },
setAuxiliaryOutput(value: unknown, methodName: string) {
methodCache.set(methodName, value);
},
@@ -100,8 +107,8 @@ function createProgramState() {
throw Error(`Auxiliary value for method ${methodName} not defined`);
return entry;
},
- reset(methodName: string) {
- methodCache.delete(methodName);
+ reset(key: string) {
+ methodCache.delete(key);
},
};
}
@@ -173,8 +180,8 @@ let SideloadedTag = {
function ZkProgram<
Config extends {
- publicInput?: ProvableTypePure;
- publicOutput?: ProvableTypePure;
+ publicInput?: ProvableType;
+ publicOutput?: ProvableType;
methods: {
[I in string]: {
privateInputs: Tuple;
@@ -250,10 +257,10 @@ function ZkProgram<
let doProving = true;
let methods = config.methods;
- let publicInputType: ProvablePure = ProvableType.get(
+ let publicInputType: Provable = ProvableType.get(
config.publicInput ?? Undefined
);
- let publicOutputType: ProvablePure = ProvableType.get(
+ let publicOutputType: Provable = ProvableType.get(
config.publicOutput ?? Void
);
@@ -391,10 +398,20 @@ function ZkProgram<
`Try calling \`await program.compile()\` first, this will cache provers in the background.\nIf you compiled your zkProgram with proofs disabled (\`proofsEnabled = false\`), you have to compile it with proofs enabled first.`
);
}
- let publicInputFields = toFieldConsts(publicInputType, publicInput);
+
+ let { publicInputFields, publicInputAux } = toFieldAndAuxConsts(
+ publicInputType,
+ publicInput
+ );
+
let previousProofs = MlArray.to(getPreviousProofsForProver(args));
- let id = snarkContext.enter({ witnesses: args, inProver: true });
+ let id = snarkContext.enter({
+ witnesses: args,
+ inProver: true,
+ auxInputData: publicInputAux,
+ });
+
let result: UnwrapPromise>;
try {
result = await picklesProver(publicInputFields, previousProofs);
@@ -416,7 +433,16 @@ function ZkProgram<
}
let [publicOutputFields, proof] = MlPair.from(result);
- let publicOutput = fromFieldConsts(publicOutputType, publicOutputFields);
+
+ let nonPureOutput = programState.getNonPureOutput();
+
+ let publicOutput = fromFieldConsts(
+ publicOutputType,
+ publicOutputFields,
+ nonPureOutput
+ );
+
+ programState.reset('__nonPureOutput__');
return {
proof: new ProgramProof({
@@ -649,8 +675,8 @@ async function compileProgram({
overrideWrapDomain,
state,
}: {
- publicInputType: ProvablePure;
- publicOutputType: ProvablePure;
+ publicInputType: Provable;
+ publicOutputType: Provable;
methodIntfs: MethodInterface[];
methods: ((...args: any) => unknown)[];
gates: Gate[][];
@@ -762,7 +788,7 @@ If you are using a SmartContract, make sure you are using the @method decorator.
}
function analyzeMethod(
- publicInputType: ProvablePure,
+ publicInputType: Provable,
methodIntf: MethodInterface,
method: (...args: any) => unknown
) {
@@ -790,8 +816,8 @@ function inCircuitVkHash(inCircuitVk: unknown): Field {
}
function picklesRuleFromFunction(
- publicInputType: ProvablePure,
- publicOutputType: ProvablePure,
+ publicInputType: Provable,
+ publicOutputType: Provable,
func: (...args: unknown[]) => unknown,
proofSystemTag: { name: string },
{ methodName, args, auxiliaryType }: MethodInterface,
@@ -801,7 +827,11 @@ function picklesRuleFromFunction(
async function main(
publicInput: MlFieldArray
): ReturnType {
- let { witnesses: argsWithoutPublicInput, inProver } = snarkContext.get();
+ let {
+ witnesses: argsWithoutPublicInput,
+ inProver,
+ auxInputData,
+ } = snarkContext.get();
assert(!(inProver && argsWithoutPublicInput === undefined));
let finalArgs = [];
let proofs: {
@@ -837,10 +867,16 @@ function picklesRuleFromFunction(
if (publicInputType === Undefined || publicInputType === Void) {
result = (await func(...finalArgs)) as any;
} else {
- let input = fromFieldVars(publicInputType, publicInput);
+ let input = fromFieldVars(publicInputType, publicInput, auxInputData);
result = (await func(input, ...finalArgs)) as any;
}
+ if (result?.publicOutput) {
+ // store the nonPure auxiliary data in program state cache if it exists
+ let nonPureOutput = publicOutputType.toAuxiliary(result.publicOutput);
+ state?.setNonPureOutput(nonPureOutput);
+ }
+
proofs.forEach(({ Proof, proof }) => {
if (!(proof instanceof DynamicProof)) return;
@@ -869,7 +905,7 @@ function picklesRuleFromFunction(
Pickles.sideLoaded.inCircuit(computedTag, circuitVk);
});
- // if the public output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case
+ // if the output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case
let hasPublicOutput = publicOutputType.sizeInFields() !== 0;
let publicOutput = hasPublicOutput
? publicOutputType.toFields(result.publicOutput)
@@ -957,20 +993,36 @@ function getMaxProofsVerified(methodIntfs: MethodInterface[]) {
) as any as 0 | 1 | 2;
}
-function fromFieldVars(type: ProvablePure, fields: MlFieldArray) {
- return type.fromFields(MlFieldArray.from(fields));
+function fromFieldVars(
+ type: Provable,
+ fields: MlFieldArray,
+ auxData: any[] = []
+) {
+ return type.fromFields(MlFieldArray.from(fields), auxData);
}
-function fromFieldConsts(type: ProvablePure, fields: MlFieldConstArray) {
- return type.fromFields(MlFieldConstArray.from(fields));
+function fromFieldConsts(
+ type: Provable,
+ fields: MlFieldConstArray,
+ aux: any[] = []
+) {
+ return type.fromFields(MlFieldConstArray.from(fields), aux);
}
-function toFieldConsts(type: ProvablePure, value: T) {
+
+function toFieldConsts(type: Provable, value: T) {
return MlFieldConstArray.to(type.toFields(value));
}
+function toFieldAndAuxConsts(type: Provable, value: T) {
+ return {
+ publicInputFields: MlFieldConstArray.to(type.toFields(value)),
+ publicInputAux: type.toAuxiliary(value),
+ };
+}
+
ZkProgram.Proof = function <
- PublicInputType extends FlexibleProvablePure,
- PublicOutputType extends FlexibleProvablePure
+ PublicInputType extends FlexibleProvable,
+ PublicOutputType extends FlexibleProvable
>(program: {
name: string;
publicInputType: PublicInputType;
diff --git a/src/lib/provable/core/provable-context.ts b/src/lib/provable/core/provable-context.ts
index a697c5d00d..ee44ab9c5e 100644
--- a/src/lib/provable/core/provable-context.ts
+++ b/src/lib/provable/core/provable-context.ts
@@ -40,6 +40,7 @@ type SnarkContext = {
inCheckedComputation?: boolean;
inAnalyze?: boolean;
inWitnessBlock?: boolean;
+ auxInputData?: any[];
};
let snarkContext = Context.create({ default: {} });
diff --git a/src/lib/provable/gadgets/range-check.ts b/src/lib/provable/gadgets/range-check.ts
index da661c5def..f65c90e4db 100644
--- a/src/lib/provable/gadgets/range-check.ts
+++ b/src/lib/provable/gadgets/range-check.ts
@@ -332,7 +332,7 @@ function rangeCheck16(x: Field) {
if (x.isConstant()) {
assert(
x.toBigInt() < 1n << 16n,
- `rangeCheck16: expected field to fit in 8 bits, got ${x}`
+ `rangeCheck16: expected field to fit in 16 bits, got ${x}`
);
return;
}
diff --git a/src/lib/provable/option.ts b/src/lib/provable/option.ts
index cc82a568a3..edec98d60e 100644
--- a/src/lib/provable/option.ts
+++ b/src/lib/provable/option.ts
@@ -9,6 +9,7 @@ export { Option, OptionOrValue };
type Option = { isSome: Bool; value: T } & {
assertSome(message?: string): T;
+ assertNone(message?: string): void;
orElse(defaultValue: T | V): T;
};
@@ -104,6 +105,10 @@ function Option(
return this.value;
}
+ assertNone(message?: string): void {
+ this.isSome.assertFalse(message);
+ }
+
static from(value?: V | T) {
return value === undefined
? new Option_({
diff --git a/src/lib/util/arrays.ts b/src/lib/util/arrays.ts
index 891de81015..b8d11660f3 100644
--- a/src/lib/util/arrays.ts
+++ b/src/lib/util/arrays.ts
@@ -1,9 +1,12 @@
import { assert } from './errors.js';
-export { chunk, chunkString };
+export { chunk, chunkString, zip, pad };
function chunk(array: T[], size: number): T[][] {
- assert(array.length % size === 0, 'invalid input length');
+ assert(
+ array.length % size === 0,
+ `chunk(): invalid input length, it must be a multiple of ${size}`
+ );
return Array.from({ length: array.length / size }, (_, i) =>
array.slice(size * i, size * (i + 1))
);
@@ -12,3 +15,19 @@ function chunk(array: T[], size: number): T[][] {
function chunkString(str: string, size: number): string[] {
return chunk([...str], size).map((c) => c.join(''));
}
+
+function zip(a: T[], b: S[]) {
+ assert(
+ a.length <= b.length,
+ 'zip(): second array must be at least as long as the first array'
+ );
+ return a.map((a, i): [T, S] => [a, b[i]!]);
+}
+
+function pad(array: T[], size: number, value: T): T[] {
+ assert(
+ array.length <= size,
+ `target size ${size} should be greater or equal than the length of the array ${array.length}`
+ );
+ return array.concat(Array.from({ length: size - array.length }, () => value));
+}