Skip to content

Commit eee5d2f

Browse files
authored
Merge pull request #1828 from o1-labs/feature/non-pure-zkprogram-inputs
Allow non-pure provable types as inputs/outputs in zkprogram
2 parents cb0b270 + b9a09c4 commit eee5d2f

File tree

5 files changed

+148
-36
lines changed

5 files changed

+148
-36
lines changed

CHANGELOG.md

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

1818
## [Unreleased](https://github.com/o1-labs/o1js/compare/e1bac02...HEAD)
1919

20-
2120
### Added
2221

22+
-`ZkProgram` to support non-pure provable types as inputs and outputs https://github.com/o1-labs/o1js/pull/1828
23+
2324
- Add `enforceTransactionLimits` parameter on Network https://github.com/o1-labs/o1js/issues/1910
2425

26+
- Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922
27+
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
@@ -2,7 +2,7 @@ import { initializeBindings, withThreadPool } from '../../snarky.js';
22
import { Pickles } from '../../snarky.js';
33
import { Field, Bool } from '../provable/wrapped.js';
44
import type {
5-
FlexibleProvablePure,
5+
FlexibleProvable,
66
InferProvable,
77
} from '../provable/types/struct.js';
88
import { FeatureFlags } from './feature-flags.js';
@@ -22,8 +22,8 @@ export { dummyProof, extractProofs, extractProofTypes, type ProofValue };
2222
type MaxProofs = 0 | 1 | 2;
2323

2424
class ProofBase<Input = any, Output = any> {
25-
static publicInputType: FlexibleProvablePure<any> = undefined as any;
26-
static publicOutputType: FlexibleProvablePure<any> = undefined as any;
25+
static publicInputType: FlexibleProvable<any> = undefined as any;
26+
static publicOutputType: FlexibleProvable<any> = undefined as any;
2727
static tag: () => { name: string } = () => {
2828
throw Error(
2929
`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;

src/lib/provable/core/provable-context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type SnarkContext = {
4040
inCheckedComputation?: boolean;
4141
inAnalyze?: boolean;
4242
inWitnessBlock?: boolean;
43+
auxInputData?: any[];
4344
};
4445
let snarkContext = Context.create<SnarkContext>({ default: {} });
4546

0 commit comments

Comments
 (0)