Skip to content

Commit 933051c

Browse files
committed
Merge branch 'main' into feature/expose-proof-base64
2 parents 66df4f2 + 5fe68ef commit 933051c

File tree

9 files changed

+194
-44
lines changed

9 files changed

+194
-44
lines changed

.github/workflows/build-action.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,13 +301,25 @@ jobs:
301301
run: |
302302
npm ci
303303
npm run prepublishOnly
304+
304305
- name: Publish to NPM if version has changed
306+
id: publish
305307
uses: JS-DevTools/npm-publish@v3
306308
with:
307309
token: ${{ secrets.NPM_TOKEN }}
308310
strategy: upgrade
309311
env:
310312
INPUT_TOKEN: ${{ secrets.NPM_TOKEN }}
313+
314+
- name: Tag new version
315+
if: ${{ steps.publish.outputs.type }} # https://github.com/JS-DevTools/npm-publish?tab=readme-ov-file#action-output
316+
env:
317+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
318+
RELEASED_VERSION: ${{ steps.publish.outputs.version }}
319+
run: |
320+
git tag $RELEASED_VERSION
321+
git push origin $RELEASED_VERSION
322+
311323
312324
Release-mina-signer-on-NPM:
313325
if: github.ref == 'refs/heads/main'

.github/workflows/release.yml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,6 @@ jobs:
5858
git add CHANGELOG.md
5959
git commit -m "Update CHANGELOG for new version $NEW_VERSION"
6060
61-
- name: Delete existing release tag
62-
run: |
63-
if git rev-parse $NEW_VERSION >/dev/null 2>&1; then
64-
git tag -d $NEW_VERSION
65-
git push origin :refs/tags/$NEW_VERSION
66-
fi
67-
6861
- name: Delete existing release branch
6962
run: |
7063
if git ls-remote --heads origin release/${NEW_VERSION} | grep release/${NEW_VERSION}; then

CHANGELOG.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,19 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1919

2020
### Added
2121

22+
-`ZkProgram` to support non-pure provable types as inputs and outputs https://github.com/o1-labs/o1js/pull/1828
23+
2224
- Add `enforceTransactionLimits` parameter on Network https://github.com/o1-labs/o1js/issues/1910
23-
- Expose low-level conversion methods `Proof.{_proofToBase64,_proofFromBase64}` https://github.com/o1-labs/o1js/pull/1928
25+
- Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922
26+
- Increased maximum supported amount of methods in a `SmartContract` or `ZkProgram` to 30. https://github.com/o1-labs/o1js/pull/1918
2427

2528
### Fixed
2629

2730
- Compiling stuck in the browser for recursive zkprograms https://github.com/o1-labs/o1js/pull/1906
2831
- Error message in `rangeCheck16` gadget https://github.com/o1-labs/o1js/pull/1920
2932

30-
### Added
31-
32-
- Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922
33-
3433
## [2.1.0](https://github.com/o1-labs/o1js/compare/b04520d...e1bac02) - 2024-11-13
3534

36-
### Added
37-
3835
- Support secp256r1 in elliptic curve and ECDSA gadgets https://github.com/o1-labs/o1js/pull/1885
3936

4037
### Fixed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Field, Struct, ZkProgram, assert } from 'o1js';
2+
3+
class MyStruct extends Struct({
4+
label: String,
5+
value: Field,
6+
}) {}
7+
8+
let NonPureIOprogram = ZkProgram({
9+
name: 'example-with-non-pure-io',
10+
publicInput: MyStruct,
11+
publicOutput: MyStruct,
12+
13+
methods: {
14+
baseCase: {
15+
privateInputs: [],
16+
async method(input: MyStruct) {
17+
//update input in circuit
18+
input.label = 'in-circuit';
19+
return {
20+
publicOutput: input,
21+
};
22+
},
23+
},
24+
},
25+
});
26+
27+
let NonPureOutputProgram = ZkProgram({
28+
name: 'example-with-non-pure-output',
29+
publicOutput: MyStruct,
30+
31+
methods: {
32+
baseCase: {
33+
privateInputs: [],
34+
async method() {
35+
return {
36+
publicOutput: new MyStruct({ label: 'output', value: Field(5) }),
37+
};
38+
},
39+
},
40+
},
41+
});
42+
43+
console.log('compiling NonPureIOprogram...');
44+
await NonPureIOprogram.compile();
45+
console.log('compile done');
46+
let input = new MyStruct({ label: 'input', value: Field(5) });
47+
let proof;
48+
({ proof } = await NonPureIOprogram.baseCase(input));
49+
let isProof1Valid = await NonPureIOprogram.verify(proof);
50+
assert(isProof1Valid, 'proof not valid!');
51+
assert(proof.publicOutput.label === 'in-circuit');
52+
console.log('i/o proof', proof);
53+
54+
console.log('compiling NonPureOutputProgram...');
55+
await NonPureOutputProgram.compile();
56+
console.log('compile done');
57+
58+
({ proof } = await NonPureOutputProgram.baseCase());
59+
let isProof2Valid = await NonPureOutputProgram.verify(proof);
60+
assert(isProof2Valid, 'proof not valid!');
61+
assert(proof.publicOutput.label === 'output');
62+
console.log('output proof', proof);

src/lib/proof-system/proof.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
import { Pickles } from '../../snarky.js';
77
import { Field, Bool } from '../provable/wrapped.js';
88
import type {
9-
FlexibleProvablePure,
9+
FlexibleProvable,
1010
InferProvable,
1111
} from '../provable/types/struct.js';
1212
import { FeatureFlags } from './feature-flags.js';
@@ -26,8 +26,8 @@ export { dummyProof, extractProofs, extractProofTypes, type ProofValue };
2626
type MaxProofs = 0 | 1 | 2;
2727

2828
class ProofBase<Input = any, Output = any> {
29-
static publicInputType: FlexibleProvablePure<any> = undefined as any;
30-
static publicOutputType: FlexibleProvablePure<any> = undefined as any;
29+
static publicInputType: FlexibleProvable<any> = undefined as any;
30+
static publicOutputType: FlexibleProvable<any> = undefined as any;
3131
static tag: () => { name: string } = () => {
3232
throw Error(
3333
`You cannot use the \`Proof\` class directly. Instead, define a subclass:\n` +

src/lib/proof-system/zkprogram.ts

Lines changed: 78 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Snarky, initializeBindings, withThreadPool } from '../../snarky.js';
33
import { Pickles, Gate } from '../../snarky.js';
44
import { Field } from '../provable/wrapped.js';
55
import {
6-
FlexibleProvablePure,
6+
FlexibleProvable,
77
InferProvable,
88
ProvablePureExtended,
99
Struct,
@@ -89,8 +89,15 @@ const Void: ProvablePureExtended<void, void, null> = EmptyVoid<Field>();
8989

9090
function createProgramState() {
9191
let methodCache: Map<string, unknown> = new Map();
92-
9392
return {
93+
setNonPureOutput(value: any[]) {
94+
methodCache.set('__nonPureOutput__', value);
95+
},
96+
getNonPureOutput(): any[] {
97+
let entry = methodCache.get('__nonPureOutput__');
98+
if (entry === undefined) return [];
99+
return entry as any[];
100+
},
94101
setAuxiliaryOutput(value: unknown, methodName: string) {
95102
methodCache.set(methodName, value);
96103
},
@@ -100,8 +107,8 @@ function createProgramState() {
100107
throw Error(`Auxiliary value for method ${methodName} not defined`);
101108
return entry;
102109
},
103-
reset(methodName: string) {
104-
methodCache.delete(methodName);
110+
reset(key: string) {
111+
methodCache.delete(key);
105112
},
106113
};
107114
}
@@ -173,8 +180,8 @@ let SideloadedTag = {
173180

174181
function ZkProgram<
175182
Config extends {
176-
publicInput?: ProvableTypePure;
177-
publicOutput?: ProvableTypePure;
183+
publicInput?: ProvableType;
184+
publicOutput?: ProvableType;
178185
methods: {
179186
[I in string]: {
180187
privateInputs: Tuple<PrivateInput>;
@@ -250,10 +257,10 @@ function ZkProgram<
250257
let doProving = true;
251258

252259
let methods = config.methods;
253-
let publicInputType: ProvablePure<any> = ProvableType.get(
260+
let publicInputType: Provable<any> = ProvableType.get(
254261
config.publicInput ?? Undefined
255262
);
256-
let publicOutputType: ProvablePure<any> = ProvableType.get(
263+
let publicOutputType: Provable<any> = ProvableType.get(
257264
config.publicOutput ?? Void
258265
);
259266

@@ -391,10 +398,20 @@ function ZkProgram<
391398
`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.`
392399
);
393400
}
394-
let publicInputFields = toFieldConsts(publicInputType, publicInput);
401+
402+
let { publicInputFields, publicInputAux } = toFieldAndAuxConsts(
403+
publicInputType,
404+
publicInput
405+
);
406+
395407
let previousProofs = MlArray.to(getPreviousProofsForProver(args));
396408

397-
let id = snarkContext.enter({ witnesses: args, inProver: true });
409+
let id = snarkContext.enter({
410+
witnesses: args,
411+
inProver: true,
412+
auxInputData: publicInputAux,
413+
});
414+
398415
let result: UnwrapPromise<ReturnType<typeof picklesProver>>;
399416
try {
400417
result = await picklesProver(publicInputFields, previousProofs);
@@ -416,7 +433,16 @@ function ZkProgram<
416433
}
417434

418435
let [publicOutputFields, proof] = MlPair.from(result);
419-
let publicOutput = fromFieldConsts(publicOutputType, publicOutputFields);
436+
437+
let nonPureOutput = programState.getNonPureOutput();
438+
439+
let publicOutput = fromFieldConsts(
440+
publicOutputType,
441+
publicOutputFields,
442+
nonPureOutput
443+
);
444+
445+
programState.reset('__nonPureOutput__');
420446

421447
return {
422448
proof: new ProgramProof({
@@ -649,8 +675,8 @@ async function compileProgram({
649675
overrideWrapDomain,
650676
state,
651677
}: {
652-
publicInputType: ProvablePure<any>;
653-
publicOutputType: ProvablePure<any>;
678+
publicInputType: Provable<any>;
679+
publicOutputType: Provable<any>;
654680
methodIntfs: MethodInterface[];
655681
methods: ((...args: any) => unknown)[];
656682
gates: Gate[][];
@@ -762,7 +788,7 @@ If you are using a SmartContract, make sure you are using the @method decorator.
762788
}
763789

764790
function analyzeMethod(
765-
publicInputType: ProvablePure<any>,
791+
publicInputType: Provable<any>,
766792
methodIntf: MethodInterface,
767793
method: (...args: any) => unknown
768794
) {
@@ -790,8 +816,8 @@ function inCircuitVkHash(inCircuitVk: unknown): Field {
790816
}
791817

792818
function picklesRuleFromFunction(
793-
publicInputType: ProvablePure<unknown>,
794-
publicOutputType: ProvablePure<unknown>,
819+
publicInputType: Provable<unknown>,
820+
publicOutputType: Provable<unknown>,
795821
func: (...args: unknown[]) => unknown,
796822
proofSystemTag: { name: string },
797823
{ methodName, args, auxiliaryType }: MethodInterface,
@@ -801,7 +827,11 @@ function picklesRuleFromFunction(
801827
async function main(
802828
publicInput: MlFieldArray
803829
): ReturnType<Pickles.Rule['main']> {
804-
let { witnesses: argsWithoutPublicInput, inProver } = snarkContext.get();
830+
let {
831+
witnesses: argsWithoutPublicInput,
832+
inProver,
833+
auxInputData,
834+
} = snarkContext.get();
805835
assert(!(inProver && argsWithoutPublicInput === undefined));
806836
let finalArgs = [];
807837
let proofs: {
@@ -837,10 +867,16 @@ function picklesRuleFromFunction(
837867
if (publicInputType === Undefined || publicInputType === Void) {
838868
result = (await func(...finalArgs)) as any;
839869
} else {
840-
let input = fromFieldVars(publicInputType, publicInput);
870+
let input = fromFieldVars(publicInputType, publicInput, auxInputData);
841871
result = (await func(input, ...finalArgs)) as any;
842872
}
843873

874+
if (result?.publicOutput) {
875+
// store the nonPure auxiliary data in program state cache if it exists
876+
let nonPureOutput = publicOutputType.toAuxiliary(result.publicOutput);
877+
state?.setNonPureOutput(nonPureOutput);
878+
}
879+
844880
proofs.forEach(({ Proof, proof }) => {
845881
if (!(proof instanceof DynamicProof)) return;
846882

@@ -869,7 +905,7 @@ function picklesRuleFromFunction(
869905
Pickles.sideLoaded.inCircuit(computedTag, circuitVk);
870906
});
871907

872-
// if the public output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case
908+
// if the output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case
873909
let hasPublicOutput = publicOutputType.sizeInFields() !== 0;
874910
let publicOutput = hasPublicOutput
875911
? publicOutputType.toFields(result.publicOutput)
@@ -957,20 +993,36 @@ function getMaxProofsVerified(methodIntfs: MethodInterface[]) {
957993
) as any as 0 | 1 | 2;
958994
}
959995

960-
function fromFieldVars<T>(type: ProvablePure<T>, fields: MlFieldArray) {
961-
return type.fromFields(MlFieldArray.from(fields));
996+
function fromFieldVars<T>(
997+
type: Provable<T>,
998+
fields: MlFieldArray,
999+
auxData: any[] = []
1000+
) {
1001+
return type.fromFields(MlFieldArray.from(fields), auxData);
9621002
}
9631003

964-
function fromFieldConsts<T>(type: ProvablePure<T>, fields: MlFieldConstArray) {
965-
return type.fromFields(MlFieldConstArray.from(fields));
1004+
function fromFieldConsts<T>(
1005+
type: Provable<T>,
1006+
fields: MlFieldConstArray,
1007+
aux: any[] = []
1008+
) {
1009+
return type.fromFields(MlFieldConstArray.from(fields), aux);
9661010
}
967-
function toFieldConsts<T>(type: ProvablePure<T>, value: T) {
1011+
1012+
function toFieldConsts<T>(type: Provable<T>, value: T) {
9681013
return MlFieldConstArray.to(type.toFields(value));
9691014
}
9701015

1016+
function toFieldAndAuxConsts<T>(type: Provable<T>, value: T) {
1017+
return {
1018+
publicInputFields: MlFieldConstArray.to(type.toFields(value)),
1019+
publicInputAux: type.toAuxiliary(value),
1020+
};
1021+
}
1022+
9711023
ZkProgram.Proof = function <
972-
PublicInputType extends FlexibleProvablePure<any>,
973-
PublicOutputType extends FlexibleProvablePure<any>
1024+
PublicInputType extends FlexibleProvable<any>,
1025+
PublicOutputType extends FlexibleProvable<any>
9741026
>(program: {
9751027
name: string;
9761028
publicInputType: PublicInputType;

0 commit comments

Comments
 (0)