-
Notifications
You must be signed in to change notification settings - Fork 104
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
zkApp composability #297
zkApp composability #297
Changes from 17 commits
a20146b
c7a0e56
9fc8d3a
ec7ac47
89a55d2
c56d0c3
f669f6c
4ea5c05
504f663
f8536e6
48b9a7a
bcfab0f
8664529
e974cb7
4806e3a
4ba97c1
2b30db8
7b999e3
4a93ee8
e9964a8
56a1283
58331ea
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,118 @@ | ||
/** | ||
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 a great intuitive example to demonstrate composability. |
||
* zkApps composability | ||
*/ | ||
import { | ||
Field, | ||
isReady, | ||
method, | ||
Mina, | ||
Party, | ||
Permissions, | ||
PrivateKey, | ||
SmartContract, | ||
state, | ||
State, | ||
} from 'snarkyjs'; | ||
|
||
const doProofs = true; | ||
|
||
await isReady; | ||
|
||
// contract which can add 1 to a number | ||
class Incrementer extends SmartContract { | ||
@method increment(x: Field): Field { | ||
return x.add(1); | ||
} | ||
} | ||
|
||
// contract which can add two numbers, plus 1, and return the result | ||
// incrementing by one is outsourced to another contract (it's cleaner that way, we want to stick to the single responsibility principle) | ||
class Adder extends SmartContract { | ||
@method addPlus1(x: Field, y: Field): Field { | ||
// compute result | ||
let sum = x.add(y); | ||
// call the other contract to increment | ||
let incrementer = new Incrementer(incrementerAddress); | ||
return incrementer.increment(sum); | ||
} | ||
} | ||
|
||
// contract which calls the Adder, stores the result on chain & emits an event | ||
class Caller extends SmartContract { | ||
@state(Field) sum = State<Field>(); | ||
events = { sum: Field }; | ||
|
||
@method callAddAndEmit(x: Field, y: Field) { | ||
let adder = new Adder(adderAddress); | ||
let sum = adder.addPlus1(x, y); | ||
this.emitEvent('sum', sum); | ||
this.sum.set(sum); | ||
} | ||
} | ||
|
||
// script to deploy zkapps and do interactions | ||
|
||
let Local = Mina.LocalBlockchain(); | ||
Mina.setActiveInstance(Local); | ||
|
||
// a test account that pays all the fees, and puts additional funds into the zkapp | ||
let feePayer = Local.testAccounts[0].privateKey; | ||
|
||
// the first contract's address | ||
let incrementerKey = PrivateKey.random(); | ||
let incrementerAddress = incrementerKey.toPublicKey(); | ||
// the second contract's address | ||
let adderKey = PrivateKey.random(); | ||
let adderAddress = adderKey.toPublicKey(); | ||
// the third contract's address | ||
let zkappKey = PrivateKey.random(); | ||
let zkappAddress = zkappKey.toPublicKey(); | ||
|
||
let zkapp = new Caller(zkappAddress); | ||
let adderZkapp = new Adder(adderAddress); | ||
let incrementerZkapp = new Incrementer(incrementerAddress); | ||
|
||
if (doProofs) { | ||
console.log('compile (incrementer)'); | ||
await Incrementer.compile(incrementerAddress); | ||
console.log('compile (adder)'); | ||
await Adder.compile(adderAddress); | ||
console.log('compile (caller)'); | ||
await Caller.compile(zkappAddress); | ||
} | ||
|
||
console.log('deploy'); | ||
let tx = await Mina.transaction(feePayer, () => { | ||
// TODO: enable funding multiple accounts properly | ||
Party.fundNewAccount(feePayer, { | ||
initialBalance: Mina.accountCreationFee().add(Mina.accountCreationFee()), | ||
}); | ||
zkapp.deploy({ zkappKey }); | ||
if (!doProofs) { | ||
zkapp.setPermissions({ | ||
...Permissions.default(), | ||
editState: Permissions.proofOrSignature(), | ||
}); | ||
} | ||
adderZkapp.deploy({ zkappKey: adderKey }); | ||
incrementerZkapp.deploy({ zkappKey: incrementerKey }); | ||
}); | ||
tx.send(); | ||
|
||
console.log('call interaction'); | ||
tx = await Mina.transaction(feePayer, () => { | ||
// we just call one contract here, nothing special to do | ||
zkapp.callAddAndEmit(Field(5), Field(6)); | ||
if (!doProofs) zkapp.sign(zkappKey); | ||
}); | ||
if (doProofs) { | ||
console.log('proving (3 proofs.. can take a bit!)'); | ||
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. How long are the 3 proofs taking on your machine @mitschabaude? 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. takes 42 sec for me |
||
await tx.prove(); | ||
} | ||
|
||
console.dir(JSON.parse(tx.toJSON()), { depth: 5 }); | ||
|
||
tx.send(); | ||
|
||
// should hopefully be 12 since we added 5 + 6 + 1 | ||
console.log('state: ' + zkapp.sum.get()); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import 'reflect-metadata'; | ||
import { Circuit, Field, Bool, JSONValue, AsFieldElements } from '../snarky'; | ||
import { Context } from './global-context'; | ||
import { snarkContext } from './proof_system'; | ||
|
||
// external API | ||
|
@@ -15,7 +16,15 @@ export { | |
}; | ||
|
||
// internal API | ||
export { cloneCircuitValue, circuitValueEquals, circuitArray }; | ||
export { | ||
cloneCircuitValue, | ||
circuitValueEquals, | ||
circuitArray, | ||
memoizationContext, | ||
memoizeWitness, | ||
getBlindingValue, | ||
toConstant, | ||
}; | ||
|
||
type AnyConstructor = new (...args: any) => any; | ||
|
||
|
@@ -485,6 +494,10 @@ function circuitValueEquals<T>(a: T, b: T): boolean { | |
); | ||
} | ||
|
||
function toConstant<T>(type: AsFieldElements<T>, value: T): T { | ||
return type.ofFields(type.toFields(value).map((x) => x.toConstant())); | ||
} | ||
|
||
// TODO: move `Circuit` to JS entirely, this patching harms code discoverability | ||
Circuit.switch = function <T, A extends AsFieldElements<T>>( | ||
mask: Bool[], | ||
|
@@ -533,3 +546,38 @@ Circuit.constraintSystem = function <T>(f: () => T) { | |
); | ||
return result; | ||
}; | ||
|
||
let memoizationContext = Context.create<{ | ||
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. new global context that is used to remember prover values which are created during |
||
memoized: Field[][]; | ||
currentIndex: number; | ||
blindingValue: Field; | ||
}>(); | ||
|
||
/** | ||
* Like Circuit.witness, but memoizes the witness during transaction construction | ||
* for reuse by the prover. This is needed to witness non-deterministic values. | ||
*/ | ||
function memoizeWitness<T>(type: AsFieldElements<T>, compute: () => T) { | ||
return Circuit.witness(type, () => { | ||
if (!memoizationContext.has()) return compute(); | ||
let context = memoizationContext.get(); | ||
let { memoized, currentIndex } = context; | ||
let currentValue = memoized[currentIndex]; | ||
if (currentValue === undefined) { | ||
let value = compute(); | ||
currentValue = type.toFields(value).map((x) => x.toConstant()); | ||
memoized[currentIndex] = currentValue; | ||
} | ||
context.currentIndex += 1; | ||
return type.ofFields(currentValue); | ||
}); | ||
} | ||
|
||
function getBlindingValue() { | ||
if (!memoizationContext.has()) return Field.random(); | ||
let context = memoizationContext.get(); | ||
if (context.blindingValue === undefined) { | ||
context.blindingValue = Field.random(); | ||
} | ||
return context.blindingValue; | ||
} |
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 is the example that demonstrates composability