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: {} });