diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml
index 1e3e7a3895..1a540c97bd 100644
--- a/.github/workflows/build-action.yml
+++ b/.github/workflows/build-action.yml
@@ -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/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 95bdd567cd..46f96c522a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,22 +19,19 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added
+-`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
-- Expose low-level conversion methods `Proof.{_proofToBase64,_proofFromBase64}` https://github.com/o1-labs/o1js/pull/1928
+- Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922
+- Increased maximum supported amount of methods in a `SmartContract` or `ZkProgram` to 30. https://github.com/o1-labs/o1js/pull/1918
### 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
-### Added
-
-- Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922
-
## [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/bindings b/src/bindings
index 2c62a9a755..34a05bc888 160000
--- a/src/bindings
+++ b/src/bindings
@@ -1 +1 @@
-Subproject commit 2c62a9a755f1b128f89cc2131814df7157f68109
+Subproject commit 34a05bc8888eb577aa73852dab5df90c7cae1050
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/proof-system/proof.ts b/src/lib/proof-system/proof.ts
index 1b5d6d0f49..345155cb7d 100644
--- a/src/lib/proof-system/proof.ts
+++ b/src/lib/proof-system/proof.ts
@@ -6,7 +6,7 @@ import {
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';
@@ -26,8 +26,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/proof-system/zkprogram.unit-test.ts b/src/lib/proof-system/zkprogram.unit-test.ts
new file mode 100644
index 0000000000..0b36bef28f
--- /dev/null
+++ b/src/lib/proof-system/zkprogram.unit-test.ts
@@ -0,0 +1,33 @@
+import { Field } from '../provable/wrapped.js';
+import { ZkProgram } from './zkprogram.js';
+
+const methodCount = 30;
+
+let MyProgram = ZkProgram({
+ name: 'large-program',
+ publicOutput: Field,
+ methods: nMethods(methodCount),
+});
+
+function nMethods(i: number) {
+ let methods: Record = {};
+ for (let j = 0; j < i; j++) {
+ methods['method' + j] = {
+ privateInputs: [Field],
+ async method(a: Field) {
+ return {
+ publicOutput: a.mul(1),
+ };
+ },
+ };
+ }
+ return methods;
+}
+
+try {
+ await MyProgram.compile();
+} catch (error) {
+ throw Error(
+ `Could not compile zkProgram with ${methodCount} branches: ${error}`
+ );
+}
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: {} });