-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1606 from rpanic/feature/side-loaded-keys3
Expose sideloaded verification keys
- Loading branch information
Showing
8 changed files
with
740 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule bindings
updated
15 files
+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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
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); | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
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 | ||
) { | ||
// In practice, this method would be guarded via some access control mechanism | ||
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
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({ | ||
name: 'add', | ||
publicInput: Undefined, | ||
publicOutput: Field, | ||
methods: { | ||
performAddition: { | ||
privateInputs: [Field, DynamicMultiplyProof, VerificationKey], | ||
async method( | ||
field: Field, | ||
proof: DynamicMultiplyProof, | ||
vk: VerificationKey | ||
) { | ||
// TODO The incoming verification key isn't constrained in any way, therefore a malicious prover | ||
// can inject any vk they like which could lead to security issues. In practice, there would always | ||
// be some sort of access control to limit the set of possible vks used. | ||
|
||
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()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.