o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
132 lines (109 loc) • 3.5 kB
text/typescript
import {
Field,
state,
State,
method,
SmartContract,
Mina,
AccountUpdate,
ZkappPublicInput,
SelfProof,
verify,
Empty,
} from 'o1js';
class TrivialZkapp extends SmartContract {
async proveSomething(hasToBe1: Field) {
hasToBe1.assertEquals(1);
}
}
class TrivialProof extends TrivialZkapp.Proof() {}
class NotSoSimpleZkapp extends SmartContract {
(Field) x = State<Field>();
async initialize(proof: TrivialProof) {
proof.verify();
this.x.set(Field(1));
}
async update(
y: Field,
oldProof: SelfProof<ZkappPublicInput, Empty>,
trivialProof: TrivialProof
) {
oldProof.verify();
trivialProof.verify();
let x = this.x.get();
this.x.requireEquals(x);
this.x.set(x.add(y));
}
}
let Local = await Mina.LocalBlockchain();
Mina.setActiveInstance(Local);
let [feePayer] = Local.testAccounts;
const [trivialContractAccount, notSoSimpleContractAccount] = Mina.TestPublicKey.random(2);
// compile and prove trivial zkapp
console.log('compile (trivial zkapp)');
let { verificationKey: trivialVerificationKey } = await TrivialZkapp.compile();
// TODO: should we have a simpler API for zkapp proving without
// submitting transactions? or is this an irrelevant use case?
// would also improve the return type -- `Proof` instead of `(Proof | undefined)[]`
console.log('prove (trivial zkapp)');
let [trivialProof] = await Mina.transaction(feePayer, async () => {
await new TrivialZkapp(notSoSimpleContractAccount).proveSomething(Field(1));
})
.prove()
.proofs();
trivialProof = await testJsonRoundtripAndVerify(TrivialProof, trivialProof, trivialVerificationKey);
console.log('compile');
let { verificationKey } = await NotSoSimpleZkapp.compile();
let zkapp = new NotSoSimpleZkapp(trivialContractAccount);
console.log('deploy');
await Mina.transaction(feePayer, async () => {
AccountUpdate.fundNewAccount(feePayer);
await zkapp.deploy();
})
.prove()
.sign([feePayer.key, trivialContractAccount.key])
.send();
console.log('initialize');
let tx = await Mina.transaction(feePayer, async () => {
await zkapp.initialize(trivialProof!);
})
.prove()
.sign([feePayer.key]);
let [proof] = tx.proofs;
await tx.send();
proof = await testJsonRoundtripAndVerify(NotSoSimpleZkapp.Proof(), proof, verificationKey);
console.log('initial state: ' + zkapp.x.get());
console.log('update');
tx = await Mina.transaction(feePayer, async () => {
await zkapp.update(Field(3), proof!, trivialProof!);
})
.prove()
.sign([feePayer.key]);
[proof] = tx.proofs;
await tx.send();
proof = await testJsonRoundtripAndVerify(NotSoSimpleZkapp.Proof(), proof, verificationKey);
console.log('state 2: ' + zkapp.x.get());
console.log('update');
tx = await Mina.transaction(feePayer, async () => {
await zkapp.update(Field(3), proof!, trivialProof!);
})
.prove()
.sign([feePayer.key]);
[proof] = tx.proofs;
await tx.send();
proof = await testJsonRoundtripAndVerify(NotSoSimpleZkapp.Proof(), proof, verificationKey);
console.log('final state: ' + zkapp.x.get());
async function testJsonRoundtripAndVerify(
Proof: any,
proof: any,
verificationKey: { data: string }
): Promise<any> {
let jsonProof = proof.toJSON();
console.log(
'json proof:',
JSON.stringify({ ...jsonProof, proof: jsonProof.proof.slice(0, 10) + '..' })
);
let ok = await verify(jsonProof, verificationKey.data);
if (!ok) throw Error('proof cannot be verified');
return Proof.fromJSON(jsonProof);
}