-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow non-pure provable types as inputs/outputs in zkprogram #1828
base: main
Are you sure you want to change the base?
Changes from all commits
66bf44c
3a7c5d6
57e38d2
bc711c7
0bec8d0
b920736
e7c3485
55aafd5
dc35f8e
7d9b3c0
8145573
e81eea3
6999038
dbc9edf
0a6e584
c5ffd54
3c56586
da4a7c3
00f62e3
4eadd0c
4cb4b18
5d8047b
e9d936f
de947d4
ef8f3ce
16c4ea7
a56602b
0130410
1063de1
b980781
764c68b
c5fa6f7
921aee2
c27ab3b
08574eb
6077306
7d601c7
3a5e476
437286f
8b8a9ee
0a9a258
2d872e7
ee12afc
eb5676f
6e0e1e3
37a09be
7491a80
6a2856a
b77c5e1
df972be
c6a8260
efa1379
062d313
e9aa3eb
f741f98
ee71654
e65064f
5347ac3
5e68a11
b3521e9
ce84a0d
3c8d030
d0885f2
84df9aa
77146bf
e05f3c1
cc54133
dc2b2cb
0355f67
8b8c120
1fbe24f
36b92fc
a9ab6e1
9f5f39b
df1f615
67854f0
146c783
e4c3fb3
a836f0e
519c301
f661d91
3914a7d
4647ffb
b5eeee5
8cbe588
bb50596
974dfb0
cb41aaf
4390daf
9838622
1ee4236
80191e4
f23a636
291f109
b4f5dc7
4c7e5fe
340bbd6
8f9c71c
679e499
4b192d4
595478a
bbb52ea
87f1bf9
5fc295c
29fc2d8
a218e97
333efcf
b9c131f
5e5b842
138045f
8267677
fbc199d
4f37717
dddffad
3f65845
cd7bb42
80438a9
05ac4fe
a143585
04326e8
b6c146e
52699f0
f67d054
24ce22c
02b8b78
ff9852b
5687495
35420b1
a03f1f5
0303637
be54738
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { Field, Provable, Struct, ZkProgram, assert } from 'o1js'; | ||
|
||
class MyStruct extends Struct({ | ||
label: String, | ||
value: Field, | ||
}) {} | ||
|
||
let MyProgram = ZkProgram({ | ||
name: 'example-with-non-pure-inputs', | ||
publicInput: MyStruct, | ||
publicOutput: MyStruct, | ||
|
||
methods: { | ||
baseCase: { | ||
privateInputs: [], | ||
async method(input: MyStruct) { | ||
//update input in circuit | ||
input.label = 'inCircuit'; | ||
return { | ||
publicOutput: input, | ||
}; | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
// | ||
|
||
console.log('compiling MyProgram...'); | ||
await MyProgram.compile(); | ||
console.log('compile done'); | ||
|
||
let input = new MyStruct({ label: 'input', value: Field(5) }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is missing the |
||
|
||
let { proof } = await MyProgram.baseCase(input); | ||
let ok = await MyProgram.verify(proof); | ||
|
||
assert(ok, 'proof not valid!'); | ||
assert(proof.publicOutput.label === 'inCircuit'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,19 +162,36 @@ const FeatureFlags = { | |
|
||
function createProgramState() { | ||
let methodCache: Map<string, unknown> = new Map(); | ||
|
||
return { | ||
setNonPureInput(value: any[]) { | ||
methodCache.set('nonPureInput', value); | ||
}, | ||
getNonPureInput(): any[] { | ||
let entry = methodCache.get('nonPureInput'); | ||
if (entry === undefined) throw Error(`Non-pure input not defined`); | ||
return entry as any[]; | ||
}, | ||
setNonPureOutput(value: any[]) { | ||
methodCache.set('nonPureOutput', value); | ||
}, | ||
getNonPureOutput(): any[] { | ||
let entry = methodCache.get('nonPureOutput'); | ||
if (entry === undefined) throw Error(`Non-pure output not defined`); | ||
return entry as any[]; | ||
}, | ||
|
||
setAuxiliaryOutput(value: unknown, methodName: string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see here! already done |
||
methodCache.set(methodName, value); | ||
}, | ||
|
||
getAuxiliaryOutput(methodName: string): unknown { | ||
let entry = methodCache.get(methodName); | ||
if (entry === undefined) | ||
throw Error(`Auxiliary value for method ${methodName} not defined`); | ||
return entry; | ||
}, | ||
reset(methodName: string) { | ||
methodCache.delete(methodName); | ||
reset(key: string) { | ||
methodCache.delete(key); | ||
}, | ||
}; | ||
} | ||
|
@@ -234,8 +251,8 @@ async function featureFlagsfromFlatMethodIntfs( | |
} | ||
|
||
class ProofBase<Input, Output> { | ||
static publicInputType: FlexibleProvablePure<any> = undefined as any; | ||
static publicOutputType: FlexibleProvablePure<any> = undefined as any; | ||
static publicInputType: FlexibleProvable<any> = undefined as any; | ||
static publicOutputType: FlexibleProvable<any> = undefined as any; | ||
static tag: () => { name: string } = () => { | ||
throw Error( | ||
`You cannot use the \`Proof\` class directly. Instead, define a subclass:\n` + | ||
|
@@ -558,8 +575,8 @@ let SideloadedTag = { | |
|
||
function ZkProgram< | ||
Config extends { | ||
publicInput?: ProvableTypePure; | ||
publicOutput?: ProvableTypePure; | ||
publicInput?: ProvableType; | ||
publicOutput?: ProvableType; | ||
methods: { | ||
[I in string]: { | ||
privateInputs: Tuple<PrivateInput>; | ||
|
@@ -635,10 +652,10 @@ function ZkProgram< | |
let doProving = true; | ||
|
||
let methods = config.methods; | ||
let publicInputType: ProvablePure<any> = ProvableType.get( | ||
let publicInputType: Provable<any> = ProvableType.get( | ||
config.publicInput ?? Undefined | ||
); | ||
let publicOutputType: ProvablePure<any> = ProvableType.get( | ||
let publicOutputType: Provable<any> = ProvableType.get( | ||
config.publicOutput ?? Void | ||
); | ||
|
||
|
@@ -778,12 +795,35 @@ 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 nonPureInputExists = | ||
publicInputType.toAuxiliary(publicInput).length !== 0; | ||
console.log('nonPure Input Exists', nonPureInputExists); | ||
let publicInputFields, publicInputAux; | ||
if (nonPureInputExists) { | ||
// serialize publicInput into pure provable field elements and auxilary data | ||
({ publicInputFields, publicInputAux } = toFieldAndAuxConsts( | ||
publicInputType, | ||
publicInput | ||
)); | ||
|
||
// store publicInput auxilary data in programState cache | ||
programState.setNonPureInput(publicInputAux); | ||
} else { | ||
publicInputFields = toFieldConsts(publicInputType, publicInput); | ||
} | ||
|
||
let previousProofs = MlArray.to( | ||
getPreviousProofsForProver(args, methodIntfs[i]) | ||
); | ||
|
||
let id = snarkContext.enter({ witnesses: args, inProver: true }); | ||
console.log('auxdata before entering snarkContext ', publicInputAux); | ||
let id = snarkContext.enter({ | ||
witnesses: args, | ||
inProver: true, | ||
auxInputData: publicInputAux, | ||
}); | ||
|
||
let result: UnwrapPromise<ReturnType<typeof picklesProver>>; | ||
try { | ||
result = await picklesProver(publicInputFields, previousProofs); | ||
|
@@ -804,8 +844,21 @@ function ZkProgram< | |
programState.reset(methodIntfs[i].methodName); | ||
} | ||
|
||
let publicOutput; | ||
let [publicOutputFields, proof] = MlPair.from(result); | ||
let publicOutput = fromFieldConsts(publicOutputType, publicOutputFields); | ||
if (nonPureInputExists) { | ||
let nonPureOutput = programState.getNonPureOutput(); | ||
|
||
publicOutput = fromFieldConsts( | ||
publicOutputType, | ||
publicOutputFields, | ||
nonPureOutput | ||
); | ||
|
||
programState.reset('nonPureOutput'); | ||
} else { | ||
publicOutput = fromFieldConsts(publicOutputType, publicOutputFields); | ||
} | ||
|
||
return { | ||
proof: new ProgramProof({ | ||
|
@@ -1062,8 +1115,8 @@ async function compileProgram({ | |
overrideWrapDomain, | ||
state, | ||
}: { | ||
publicInputType: ProvablePure<any>; | ||
publicOutputType: ProvablePure<any>; | ||
publicInputType: Provable<any>; | ||
publicOutputType: Provable<any>; | ||
methodIntfs: MethodInterface[]; | ||
methods: ((...args: any) => unknown)[]; | ||
gates: Gate[][]; | ||
|
@@ -1175,7 +1228,7 @@ If you are using a SmartContract, make sure you are using the @method decorator. | |
} | ||
|
||
function analyzeMethod( | ||
publicInputType: ProvablePure<any>, | ||
publicInputType: Provable<any>, | ||
methodIntf: MethodInterface, | ||
method: (...args: any) => unknown | ||
) { | ||
|
@@ -1203,8 +1256,8 @@ function inCircuitVkHash(inCircuitVk: unknown): Field { | |
} | ||
|
||
function picklesRuleFromFunction( | ||
publicInputType: ProvablePure<unknown>, | ||
publicOutputType: ProvablePure<unknown>, | ||
publicInputType: Provable<unknown>, | ||
publicOutputType: Provable<unknown>, | ||
func: (...args: unknown[]) => unknown, | ||
proofSystemTag: { name: string }, | ||
{ | ||
|
@@ -1220,7 +1273,11 @@ function picklesRuleFromFunction( | |
async function main( | ||
publicInput: MlFieldArray | ||
): ReturnType<Pickles.Rule['main']> { | ||
let { witnesses: argsWithoutPublicInput, inProver } = snarkContext.get(); | ||
let { | ||
witnesses: argsWithoutPublicInput, | ||
inProver, | ||
auxInputData, | ||
} = snarkContext.get(); | ||
assert(!(inProver && argsWithoutPublicInput === undefined)); | ||
let finalArgs = []; | ||
let proofs: { | ||
|
@@ -1266,10 +1323,22 @@ function picklesRuleFromFunction( | |
if (publicInputType === Undefined || publicInputType === Void) { | ||
result = (await func(...finalArgs)) as any; | ||
} else { | ||
let input = fromFieldVars(publicInputType, publicInput); | ||
console.log('auxData before input', auxInputData); | ||
let input = fromFieldVars(publicInputType, publicInput, auxInputData); | ||
result = (await func(input, ...finalArgs)) as any; | ||
} | ||
|
||
console.log('result input', result); | ||
if (result?.publicOutput) { | ||
// store the nonPure auxiliary data in program state cache if it exists | ||
let nonPureOutput = publicOutputType.toAuxiliary(result.publicOutput); | ||
let nonPureOutputExists = nonPureOutput.length !== 0; | ||
|
||
if (state !== undefined && nonPureOutputExists) { | ||
state.setNonPureOutput(nonPureOutput); | ||
} | ||
} | ||
|
||
proofs.forEach(({ proofInstance, classReference }) => { | ||
if (!(proofInstance instanceof DynamicProof)) { | ||
return; | ||
|
@@ -1299,7 +1368,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) | ||
|
@@ -1487,23 +1556,40 @@ function getMaxProofsVerified(methodIntfs: MethodInterface[]) { | |
) as any as 0 | 1 | 2; | ||
} | ||
|
||
function fromFieldVars<T>(type: ProvablePure<T>, fields: MlFieldArray) { | ||
return type.fromFields(MlFieldArray.from(fields)); | ||
function fromFieldVars<T>( | ||
type: Provable<T>, | ||
fields: MlFieldArray, | ||
auxData: any[] = [] | ||
) { | ||
return type.fromFields(MlFieldArray.from(fields), auxData); | ||
} | ||
|
||
function toFieldVars<T>(type: ProvablePure<T>, value: T) { | ||
return MlFieldArray.to(type.toFields(value)); | ||
} | ||
|
||
function fromFieldConsts<T>(type: ProvablePure<T>, fields: MlFieldConstArray) { | ||
return type.fromFields(MlFieldConstArray.from(fields)); | ||
function fromFieldConsts<T>( | ||
type: Provable<T>, | ||
fields: MlFieldConstArray, | ||
aux: any[] = [] | ||
Trivo25 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) { | ||
return type.fromFields(MlFieldConstArray.from(fields), aux); | ||
} | ||
function toFieldConsts<T>(type: ProvablePure<T>, value: T) { | ||
|
||
function toFieldConsts<T>(type: Provable<T>, value: T) { | ||
return MlFieldConstArray.to(type.toFields(value)); | ||
} | ||
|
||
function toFieldAndAuxConsts<T>(type: Provable<T>, value: T) { | ||
return { | ||
publicInputFields: MlFieldConstArray.to(type.toFields(value)), | ||
publicInputAux: type.toAuxiliary(value), | ||
}; | ||
} | ||
|
||
ZkProgram.Proof = function < | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be adjusted too! We need to "derive" the proof class from our zkProgram correctly via |
||
PublicInputType extends FlexibleProvablePure<any>, | ||
PublicOutputType extends FlexibleProvablePure<any> | ||
PublicInputType extends FlexibleProvable<any>, | ||
PublicOutputType extends FlexibleProvable<any> | ||
>(program: { | ||
name: string; | ||
publicInputType: PublicInputType; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works! But right now we don't handle the case where the aux values are changed in the circuit, like this
so either we make this work or we explcitily tell developers not to touch the auxiliary values in the circuit; I prefer the first approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need them in the circuit!