-
Notifications
You must be signed in to change notification settings - Fork 107
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
Expose sideloaded verification keys #1606
Changes from 7 commits
d29b5cb
2189f4b
c2f6891
40c927d
4fff643
095fb28
964ef71
c32ca2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
+1 −1 | MINA_COMMIT | |
+89,616 −89,541 | compiled/node_bindings/o1js_node.bc.cjs | |
+1 −1 | compiled/node_bindings/o1js_node.bc.map | |
+284 −284 | compiled/node_bindings/plonk_wasm.cjs | |
+142 −142 | compiled/node_bindings/plonk_wasm.d.cts | |
+ − | compiled/node_bindings/plonk_wasm_bg.wasm | |
+122 −122 | compiled/node_bindings/plonk_wasm_bg.wasm.d.ts | |
+21 −21 | compiled/web_bindings/o1js_web.bc.js | |
+264 −264 | compiled/web_bindings/plonk_wasm.d.ts | |
+284 −284 | compiled/web_bindings/plonk_wasm.js | |
+ − | compiled/web_bindings/plonk_wasm_bg.wasm | |
+122 −122 | compiled/web_bindings/plonk_wasm_bg.wasm.d.ts | |
+2 −1 | crypto/constants.ts | |
+76 −0 | ocaml/lib/pickles_bindings.ml | |
+23 −1 | ocaml/lib/pickles_bindings.mli |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { | ||
DynamicProof, | ||
Field, | ||
MerkleTree, | ||
MerkleWitness, | ||
Proof, | ||
SelfProof, | ||
Struct, | ||
VerificationKey, | ||
ZkProgram, | ||
verify, | ||
} from 'o1js'; | ||
|
||
/** | ||
* This example showcases how DynamicProofs can be used along with a merkletree that stores | ||
* the verification keys that can be used to verify it. | ||
* The MainProgram has two methods, addSideloadedProgram that adds a given verification key | ||
* to the tree, and validateUsingTree that uses a given tree leaf to verify a given child-proof | ||
* using the verification tree stored under that leaf. | ||
*/ | ||
|
||
const sideloadedProgram = ZkProgram({ | ||
name: 'childProgram', | ||
publicInput: Field, | ||
publicOutput: Field, | ||
methods: { | ||
compute: { | ||
privateInputs: [Field], | ||
async method(publicInput: Field, privateInput: Field) { | ||
return publicInput.add(privateInput); | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
export class SideloadedProgramProof extends DynamicProof<Field, Field> { | ||
static publicInputType = Field; | ||
static publicOutputType = Field; | ||
static maxProofsVerified = 0 as const; | ||
} | ||
|
||
const tree = new MerkleTree(64); | ||
class MerkleTreeWitness extends MerkleWitness(64) {} | ||
|
||
class MainProgramState extends Struct({ | ||
treeRoot: Field, | ||
state: Field, | ||
}) {} | ||
|
||
const mainProgram = ZkProgram({ | ||
name: 'mainProgram', | ||
publicInput: MainProgramState, | ||
publicOutput: MainProgramState, | ||
methods: { | ||
addSideloadedProgram: { | ||
privateInputs: [VerificationKey, MerkleTreeWitness], | ||
async method( | ||
publicInput: MainProgramState, | ||
vk: VerificationKey, | ||
merkleWitness: MerkleTreeWitness | ||
) { | ||
const currentRoot = merkleWitness.calculateRoot(Field(0)); | ||
publicInput.treeRoot.assertEquals( | ||
currentRoot, | ||
'Provided merklewitness not correct or leaf not empty' | ||
); | ||
const newRoot = merkleWitness.calculateRoot(vk.hash); | ||
|
||
return new MainProgramState({ | ||
state: publicInput.state, | ||
treeRoot: newRoot, | ||
}); | ||
}, | ||
}, | ||
validateUsingTree: { | ||
privateInputs: [ | ||
SelfProof, | ||
VerificationKey, | ||
MerkleTreeWitness, | ||
SideloadedProgramProof, | ||
], | ||
async method( | ||
publicInput: MainProgramState, | ||
previous: Proof<MainProgramState, MainProgramState>, | ||
vk: VerificationKey, | ||
merkleWitness: MerkleTreeWitness, | ||
proof: SideloadedProgramProof | ||
) { | ||
// Verify previous program state | ||
previous.publicOutput.state.assertEquals(publicInput.state); | ||
previous.publicOutput.treeRoot.assertEquals(publicInput.treeRoot); | ||
|
||
// Verify inclusion of vk inside the tree | ||
const computedRoot = merkleWitness.calculateRoot(vk.hash); | ||
publicInput.treeRoot.assertEquals( | ||
computedRoot, | ||
'Tree witness with provided vk not correct' | ||
); | ||
|
||
proof.verify(vk); | ||
|
||
// Compute new state | ||
proof.publicInput.assertEquals(publicInput.state); | ||
const newState = proof.publicOutput; | ||
return new MainProgramState({ | ||
treeRoot: publicInput.treeRoot, | ||
state: newState, | ||
}); | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
console.log('Compiling circuits...'); | ||
const programVk = (await sideloadedProgram.compile()).verificationKey; | ||
const mainVk = (await mainProgram.compile()).verificationKey; | ||
|
||
console.log('Proving deployment of sideloaded key'); | ||
const rootBefore = tree.getRoot(); | ||
tree.setLeaf(1n, programVk.hash); | ||
const witness = new MerkleTreeWitness(tree.getWitness(1n)); | ||
|
||
const proof1 = await mainProgram.addSideloadedProgram( | ||
new MainProgramState({ | ||
treeRoot: rootBefore, | ||
state: Field(0), | ||
}), | ||
programVk, | ||
witness | ||
); | ||
|
||
console.log('Proving child program execution'); | ||
const childProof = await sideloadedProgram.compute(Field(0), Field(10)); | ||
|
||
console.log('Proving verification inside main program'); | ||
const proof2 = await mainProgram.validateUsingTree( | ||
proof1.publicOutput, | ||
proof1, | ||
programVk, | ||
witness, | ||
SideloadedProgramProof.fromProof(childProof) | ||
); | ||
|
||
const validProof2 = await verify(proof2, mainVk); | ||
console.log('ok?', validProof2); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { | ||
ZkProgram, | ||
Field, | ||
DynamicProof, | ||
Proof, | ||
VerificationKey, | ||
Undefined, | ||
verify, | ||
} from 'o1js'; | ||
|
||
/** | ||
* This example showcases mutual recursion (A -> B -> A) through two circuits that respectively | ||
* add or multiply a given publicInput. | ||
* Every multiplication or addition step consumes a previous proof from the other circuit to verify prior state. | ||
*/ | ||
|
||
class DynamicMultiplyProof extends DynamicProof<Undefined, Field> { | ||
static publicInputType = Undefined; | ||
static publicOutputType = Field; | ||
static maxProofsVerified = 1 as const; | ||
} | ||
|
||
const add = ZkProgram({ | ||
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 example makes me a bit uncomfortable, since I think adding a TODO comment where you highlight this caveat would be enough to prevent this pattern accidentally ending up in a user app and creating a vulnerability |
||
name: 'add', | ||
publicInput: Undefined, | ||
publicOutput: Field, | ||
methods: { | ||
performAddition: { | ||
privateInputs: [Field, DynamicMultiplyProof, VerificationKey], | ||
async method( | ||
field: Field, | ||
proof: DynamicMultiplyProof, | ||
vk: VerificationKey | ||
) { | ||
const multiplyResult = proof.publicOutput; | ||
// Skip verification in case the input is 0, as that is our base-case | ||
proof.verifyIf(vk, multiplyResult.equals(Field(0)).not()); | ||
|
||
const additionResult = multiplyResult.add(field); | ||
return additionResult; | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const AddProof = ZkProgram.Proof(add); | ||
|
||
const multiply = ZkProgram({ | ||
name: 'multiply', | ||
publicInput: Undefined, | ||
publicOutput: Field, | ||
methods: { | ||
performMultiplication: { | ||
privateInputs: [Field, AddProof], | ||
async method(field: Field, addProof: Proof<Undefined, Field>) { | ||
addProof.verify(); | ||
const multiplicationResult = addProof.publicOutput.mul(field); | ||
return multiplicationResult; | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
console.log('Compiling circuits...'); | ||
const addVk = (await add.compile()).verificationKey; | ||
const multiplyVk = (await multiply.compile()).verificationKey; | ||
|
||
console.log('Proving basecase'); | ||
const dummyProof = await DynamicMultiplyProof.dummy(undefined, Field(0), 1); | ||
const baseCase = await add.performAddition(Field(5), dummyProof, multiplyVk); | ||
|
||
const validBaseCase = await verify(baseCase, addVk); | ||
console.log('ok?', validBaseCase); | ||
|
||
console.log('Proving first multiplication'); | ||
const multiply1 = await multiply.performMultiplication(Field(3), baseCase); | ||
|
||
const validMultiplication = await verify(multiply1, multiplyVk); | ||
console.log('ok?', validMultiplication); | ||
|
||
console.log('Proving second (recursive) addition'); | ||
const add2 = await add.performAddition( | ||
Field(4), | ||
DynamicMultiplyProof.fromProof(multiply1), | ||
multiplyVk | ||
); | ||
|
||
const validAddition = await verify(add2, addVk); | ||
console.log('ok?', validAddition); | ||
|
||
console.log('Result (should be 19):', add2.publicOutput.toBigInt()); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
export type { ProvablePure } from './lib/provable/types/provable-intf.js'; | ||
export { Ledger, initializeBindings } from './snarky.js'; | ||
export { Ledger, initializeBindings, Pickles } from './snarky.js'; | ||
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. wait, why are we exporting the 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. Oh, that is a leftover from testing, thanks for catching that! |
||
export { Field, Bool, Group, Scalar } from './lib/provable/wrapped.js'; | ||
export { | ||
createForeignField, | ||
|
@@ -70,6 +70,7 @@ export { state, State, declareState } from './lib/mina/state.js'; | |
export type { JsonProof } from './lib/proof-system/zkprogram.js'; | ||
export { | ||
Proof, | ||
DynamicProof, | ||
SelfProof, | ||
verify, | ||
Empty, | ||
|
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.
love this example!