Skip to content

Commit

Permalink
Merge pull request #812 from o1-labs/fix/update-self-vk
Browse files Browse the repository at this point in the history
Fix self-updating verification key
  • Loading branch information
mitschabaude authored Mar 28, 2023
2 parents 926e2a2 + 89a4a1f commit a16d77d
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 12 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/21de489...HEAD)

> No unreleased changes yet
### Breaking changes

- Change type of verification key returned by `SmartContract.compile()` to match `VerificationKey` https://github.com/o1-labs/snarkyjs/pull/812

### Fixed

- Update the zkApp verification key from within one of its own methods, via proof https://github.com/o1-labs/snarkyjs/pull/812

## [0.9.4](https://github.com/o1-labs/snarkyjs/compare/9acec55...21de489)

Expand Down
9 changes: 6 additions & 3 deletions src/examples/vk_regression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ await isReady;
let dump = process.argv[4] === '--dump';
let jsonPath = process.argv[dump ? 5 : 4];

const Contracts: typeof SmartContract[] = [
const Contracts: (typeof SmartContract)[] = [
Voting_,
Membership_,
HelloWorld,
Expand Down Expand Up @@ -54,7 +54,7 @@ async function checkVk(contracts: typeof Contracts) {
verificationKey: { data, hash },
} = await c.compile();

if (data !== vk.data || hash !== vk.hash) {
if (data !== vk.data || hash.toString() !== vk.hash) {
errorStack += `\n\nRegression test for contract ${
c.name
} failed, because of a verification key mismatch.
Expand Down Expand Up @@ -83,7 +83,10 @@ async function dumpVk(contracts: typeof Contracts) {
for await (const c of contracts) {
let { verificationKey } = await c.compile();
newEntries[c.name] = {
verificationKey,
verificationKey: {
data: verificationKey.data,
hash: verificationKey.hash.toString(),
},
};
}

Expand Down
78 changes: 78 additions & 0 deletions src/examples/zkapps/zkapp-self-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* This example deploys a zkApp and then updates its verification key via proof, self-replacing the zkApp
*/
import {
SmartContract,
VerificationKey,
method,
Permissions,
isReady,
PrivateKey,
Mina,
AccountUpdate,
Circuit,
} from 'snarkyjs';

class Foo extends SmartContract {
init() {
super.init();
this.account.permissions.set({
...Permissions.default(),
setVerificationKey: Permissions.proof(),
});
}

@method replaceVerificationKey(verificationKey: VerificationKey) {
this.account.verificationKey.set(verificationKey);
}
}

class Bar extends SmartContract {
@method call() {
Circuit.log('Bar');
}
}

// setup

await isReady;

const Local = Mina.LocalBlockchain({ proofsEnabled: true });
Mina.setActiveInstance(Local);

const zkAppPrivateKey = PrivateKey.random();
const zkAppAddress = zkAppPrivateKey.toPublicKey();
const zkApp = new Foo(zkAppAddress);

const { privateKey: deployerKey, publicKey: deployerAccount } =
Local.testAccounts[0];

// deploy first verification key

await Foo.compile();

const tx = await Mina.transaction(deployerAccount, () => {
AccountUpdate.fundNewAccount(deployerAccount);
zkApp.deploy();
});
await tx.prove();
await tx.sign([deployerKey, zkAppPrivateKey]).send();

const fooVerificationKey = Mina.getAccount(zkAppAddress).zkapp?.verificationKey;
Circuit.log('original verification key', fooVerificationKey);

// update verification key

const { verificationKey: barVerificationKey } = await Bar.compile();

const tx2 = await Mina.transaction(deployerAccount, () => {
zkApp.replaceVerificationKey(barVerificationKey);
});
await tx2.prove();
await tx2.sign([deployerKey]).send();

const updatedVerificationKey =
Mina.getAccount(zkAppAddress).zkapp?.verificationKey;

// should be different from Foo
Circuit.log('updated verification key', updatedVerificationKey);
15 changes: 13 additions & 2 deletions src/lib/account_update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as Encoding from './encoding.js';
import { hashWithPrefix, packToFields } from './hash.js';
import { prefixes } from '../js_crypto/constants.js';
import { Context } from './global-context.js';
import { assert } from './errors.js';

// external API
export { AccountUpdate, Permissions, ZkappPublicInput };
Expand Down Expand Up @@ -1732,13 +1733,23 @@ const Authorization = {
accountUpdate.lazyAuthorization = { ...signature, kind: 'lazy-signature' };
},
setProofAuthorizationKind(
{ body }: AccountUpdate,
{ body, id }: AccountUpdate,
priorAccountUpdates?: AccountUpdate[]
) {
body.authorizationKind.isSigned = Bool(false);
body.authorizationKind.isProved = Bool(true);
let hash = Circuit.witness(Field, () => {
priorAccountUpdates ??= zkAppProver.getData().transaction.accountUpdates;
let proverData = zkAppProver.getData();
let isProver = proverData !== undefined;
assert(
isProver || priorAccountUpdates !== undefined,
'Called `setProofAuthorizationKind()` outside the prover without passing in `priorAccountUpdates`.'
);
let myAccountUpdateId = isProver ? proverData.accountUpdate.id : id;
priorAccountUpdates ??= proverData.transaction.accountUpdates;
priorAccountUpdates = priorAccountUpdates.filter(
(a) => a.id !== myAccountUpdateId
);
let priorAccountUpdatesFlat = CallForest.toFlatList(
priorAccountUpdates,
false
Expand Down
17 changes: 16 additions & 1 deletion src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Types } from '../provable/types.js';
import { TokenId } from './account_update.js';
import { Int64 } from './int.js';

export { invalidTransactionError };
export { invalidTransactionError, Bug, assert };

const ErrorHandlers = {
Invalid_fee_excess({
Expand Down Expand Up @@ -101,3 +101,18 @@ function invalidTransactionError(
// fallback if we don't have a good error message yet
return rawErrors;
}

/**
* An error that was assumed cannot happen, and communicates to users that it's not their fault but an internal bug.
*/
function Bug(message: string) {
return Error(
`${message}\nThis shouldn't have happened and indicates an internal bug.`
);
}
/**
* Make an assertion. When failing, this will communicate to users it's not their fault but indicates an internal bug.
*/
function assert(condition: boolean, message = 'Failed assertion.') {
if (!condition) throw Bug(message);
}
11 changes: 6 additions & 5 deletions src/lib/zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,12 +694,13 @@ class SmartContract {
this
);

let verificationKey = getVerificationKeyArtifact();
let verificationKey_ = getVerificationKeyArtifact();
let verificationKey = {
data: verificationKey_.data,
hash: Field(verificationKey_.hash),
} satisfies VerificationKey;
this._provers = provers;
this._verificationKey = {
data: verificationKey.data,
hash: Field(verificationKey.hash),
};
this._verificationKey = verificationKey;
// TODO: instead of returning provers, return an artifact from which provers can be recovered
return { verificationKey, provers, verify };
}
Expand Down

0 comments on commit a16d77d

Please sign in to comment.