Skip to content
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

Prepare 0.5, mitigate CircuitValue disruption #341

Merged
merged 3 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Implement recursion
- RFC: https://github.com/o1-labs/snarkyjs/issues/89
- Enable smart contract methods to take previous proofs as argument
- Add new primitive `ZkProgram` which represents a collection of circuits that produce instances of the same proof.
Like smart contracts, ZkPrograms can produce execution proofs and merge in previous proofs, but they are more general and suitable for roll-up-type systems.
- Supported numbers of merged proofs are 0, 1 and 2
- PRs: https://github.com/o1-labs/snarkyjs/pull/245 https://github.com/o1-labs/snarkyjs/pull/250 https://github.com/o1-labs/snarkyjs/pull/261
- `SmartContract.digest()` to quickly compute a hash of the contract's circuit. This will be used by the zkApp CLI to figure out whether `compile` should be re-run or a cached verification key can be used.
- **Recursive proofs**. RFC: https://github.com/o1-labs/snarkyjs/issues/89, PRs: https://github.com/o1-labs/snarkyjs/pull/245 https://github.com/o1-labs/snarkyjs/pull/250 https://github.com/o1-labs/snarkyjs/pull/261
- Enable smart contract methods to take previous proofs as arguments, and verify them in the circuit
- Add `ZkProgram`, a new primitive which represents a collection of circuits that produce instances of the same proof. So, it's a more general version of `SmartContract`, without any of the Mina-related API.
`ZkProgram` is suitable for rollup-type systems and offchain usage of Pickles + Kimchi.
- **zkApp composability** -- calling other zkApps from inside zkApps. RFC: https://github.com/o1-labs/snarkyjs/issues/303, PRs: https://github.com/o1-labs/snarkyjs/pull/285, https://github.com/o1-labs/snarkyjs/pull/296, https://github.com/o1-labs/snarkyjs/pull/294, https://github.com/o1-labs/snarkyjs/pull/297
- **Events** support via `SmartContract.events`, `this.emitEvent`. RFC: https://github.com/o1-labs/snarkyjs/issues/248, PR: https://github.com/o1-labs/snarkyjs/pull/272
- `fetchEvents` partially implemented for local testing: https://github.com/o1-labs/snarkyjs/pull/323
- **Payments**: `this.send({ to, amount })` as an easier API for sending Mina from smart contracts https://github.com/o1-labs/snarkyjs/pull/325
- `Party.send()` to transfer Mina between any accounts, for example, from users to smart contracts
- `SmartContract.digest()` to quickly compute a hash of the contract's circuit. This is [used by the zkApp CLI](https://github.com/o1-labs/zkapp-cli/pull/233) to figure out whether `compile` should be re-run or a cached verification key can be used. https://github.com/o1-labs/snarkyjs/pull/268
- `Circuit.constraintSystem()` for creating a circuit from a function, counting the number of constraints and computing a digest of the circuit https://github.com/o1-labs/snarkyjs/pull/279
- `this.account.isNew` to assert that an account did not (or did) exist before the transaction https://github.com/MinaProtocol/mina/pull/11524
- `LocalBlockchain.setTimestamp` and other setters for network state, to test network preconditions locally https://github.com/o1-labs/snarkyjs/pull/329
- **Experimental APIs** are now collected under the `Experimental` import, or on `this.experimental` in a smart contract.
- Custom tokens (_experimental_), via `this.experimental.token`. RFC: https://github.com/o1-labs/snarkyjs/issues/233, PR: https://github.com/o1-labs/snarkyjs/pull/273,
- Actions / sequence events support (_experimental_), via `Experimental.Reducer`. RFC: https://github.com/o1-labs/snarkyjs/issues/265, PR: https://github.com/o1-labs/snarkyjs/pull/274
- Merkle tree implementation (_experimental_) via `Experimental.MerkleTree` https://github.com/o1-labs/snarkyjs/pull/343

### Changed

- BREAKING CHANGE: Make on-chain state consistent with other preconditions - throw an error when state is not explicitly constrained https://github.com/o1-labs/snarkyjs/pull/267
- `CircuitValue` improvements https://github.com/o1-labs/snarkyjs/pull/269, https://github.com/o1-labs/snarkyjs/pull/306, https://github.com/o1-labs/snarkyjs/pull/341
- Added a base constructor, so overriding the constructor on classes that extend `CircuitValue` is now _optional_. When overriding, the base constructor can be called without arguments, as previously: `super()`. When not overriding, the expected arguments are all the `@prop`s on the class, in the order they were defined in: `new MyCircuitValue(prop1, prop2)`.
- `CircuitValue.fromObject({ prop1, prop2 })` is a new, better-typed alternative for using the base constructor.
- Fixed: the overridden constructor is now free to have any argument structure -- previously, arguments had to be the props in their declared order. I.e., the behaviour that's now used by the base constructor used to be forced on all constructors, which is no longer the case.
- `Mina.transaction` improvements
- Support zkApp proofs when there are other parties in the same transaction block https://github.com/o1-labs/snarkyjs/pull/280
- Support multiple independent zkApp proofs in one transaction block https://github.com/o1-labs/snarkyjs/pull/296
- Add previously unimplemented preconditions, like `this.network.timestamp` https://github.com/o1-labs/snarkyjs/pull/324 https://github.com/MinaProtocol/mina/pull/11577
- Improve error messages thrown from Wasm, by making Rust's `panic` log to the JS console https://github.com/MinaProtocol/mina/pull/11644
- Not user-facing, but essential: Smart contracts fully constrain the account updates they create, inside the circuit https://github.com/o1-labs/snarkyjs/pull/278

### Fixed

- Fix comparisons on `UInt32` and `UInt64` (`UInt32.lt`, `UInt32.gt`, etc) https://github.com/o1-labs/snarkyjs/issues/174, https://github.com/o1-labs/snarkyjs/issues/101. PR: https://github.com/o1-labs/snarkyjs/pull/307

## [0.4.3](https://github.com/o1-labs/snarkyjs/compare/e66f08d...2375f08)

Expand Down
50 changes: 43 additions & 7 deletions src/examples/sudoku/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,65 @@
import { deploy, submitSolution, getZkappState } from './sudoku-zkapp.js';
import { Sudoku, SudokuZkapp } from './sudoku-zkapp.js';
import { cloneSudoku, generateSudoku, solveSudoku } from './sudoku-lib.js';
import { shutdown } from 'snarkyjs';
import {
Bool,
isReady,
Mina,
Party,
Permissions,
PrivateKey,
shutdown,
} from 'snarkyjs';

// setup
await isReady;
const Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);
const account1 = Local.testAccounts[0].privateKey;
const zkappKey = PrivateKey.random();
let zkappAddress = zkappKey.toPublicKey();
let zkapp = new SudokuZkapp(zkappAddress);

// generate sudoku, compile & deploy
let sudoku = generateSudoku(0.5);

console.log('Deploying Sudoku...');
await deploy(sudoku);
console.log('Is the sudoku solved?', (await getZkappState()).isSolved);
await SudokuZkapp.compile(zkappAddress);
let tx = await Mina.transaction(account1, () => {
Party.fundNewAccount(account1);
let zkapp = new SudokuZkapp(zkappAddress);
let sudokuInstance = new Sudoku(sudoku);
zkapp.deploy({ zkappKey });
zkapp.sudokuHash.set(sudokuInstance.hash());
zkapp.isSolved.set(Bool(false));
});
await tx.send().wait();
console.log('Is the sudoku solved?', zkapp.isSolved.get().toBoolean());

let solution = solveSudoku(sudoku);
if (solution === undefined) throw Error('cannot happen');

// submit a wrong solution
// submit a wrong solution (this runs quickly, because proof creation fails)
let noSolution = cloneSudoku(solution);
noSolution[0][0] = (noSolution[0][0] % 9) + 1;

console.log('Submitting wrong solution...');
try {
await submitSolution(sudoku, noSolution);
} catch {}
console.log('Is the sudoku solved?', (await getZkappState()).isSolved);
console.log('Is the sudoku solved?', zkapp.isSolved.get().toBoolean());

// submit the actual solution
console.log('Submitting solution...');
await submitSolution(sudoku, solution);
console.log('Is the sudoku solved?', (await getZkappState()).isSolved);
console.log('Is the sudoku solved?', zkapp.isSolved.get().toBoolean());

shutdown();

async function submitSolution(sudoku: number[][], solution: number[][]) {
let tx = await Mina.transaction(account1, () => {
let zkapp = new SudokuZkapp(zkappAddress);
zkapp.submitSolution(new Sudoku(sudoku), new Sudoku(solution));
});
await tx.prove();
await tx.send().wait();
}
59 changes: 5 additions & 54 deletions src/examples/sudoku/sudoku-zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,20 @@ import {
Field,
SmartContract,
method,
PrivateKey,
Mina,
Bool,
state,
State,
Poseidon,
Party,
Permissions,
isReady,
} from 'snarkyjs';

export { deploy, submitSolution, getZkappState };
export { SudokuZkapp, Sudoku };

class Sudoku extends CircuitValue {
@matrixProp(Field, 9, 9) value: Field[][];

static from(value: number[][]) {
return new Sudoku(value.map((row) => row.map(Field)));
constructor(sudoku: number[][]) {
super();
this.value = sudoku.map((row) => row.map(Field));
}

hash() {
Expand Down Expand Up @@ -91,54 +87,9 @@ class SudokuZkapp extends SmartContract {
}
}

// setup
await isReady;
// helper

const Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);
const account1 = Local.testAccounts[0].privateKey;

const zkappKey = PrivateKey.random();
let zkappAddress = zkappKey.toPublicKey();

async function deploy(sudoku: number[][]) {
let tx = await Mina.transaction(account1, () => {
Party.fundNewAccount(account1);
let zkapp = new SudokuZkapp(zkappAddress);
let sudokuInstance = Sudoku.from(sudoku);
zkapp.deploy({ zkappKey });
zkapp.setPermissions({
...Permissions.default(),
editState: Permissions.proofOrSignature(),
});
zkapp.sudokuHash.set(sudokuInstance.hash());
zkapp.isSolved.set(Bool(false));
});
await tx.send().wait();
}

async function submitSolution(sudoku: number[][], solution: number[][]) {
let tx = await Mina.transaction(account1, () => {
let zkapp = new SudokuZkapp(zkappAddress);
zkapp.submitSolution(Sudoku.from(sudoku), Sudoku.from(solution));
zkapp.sign(zkappKey);
});
await tx.send().wait();
}

function getZkappState() {
let zkapp = new SudokuZkapp(zkappAddress);
let sudokuHash = fieldToHex(zkapp.sudokuHash.get());
let isSolved = zkapp.isSolved.get().toBoolean();
return { sudokuHash, isSolved };
}

// helpers
function divmod(k: number, n: number) {
let q = Math.floor(k / n);
return [q, k - q * n];
}

function fieldToHex(field: Field) {
return field.toBigInt().toString(16);
}
11 changes: 5 additions & 6 deletions src/lib/circuit_value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,18 @@ type NonMethods<T> = Pick<T, NonMethodKeys<T>>;

abstract class CircuitValue {
constructor(...props: any[]) {
const fields = (this.constructor as any).prototype._fields;
if (fields === undefined || fields === null) {
return;
}
// if this is called with no arguments, do nothing, to support simple super() calls
if (props.length === 0) return;

let fields = this.constructor.prototype._fields;
if (fields === undefined) return;
if (props.length !== fields.length) {
throw Error(
`${this.constructor.name} constructor called with ${props.length} arguments, but expected ${fields.length}`
);
}

for (let i = 0; i < fields.length; ++i) {
const [key, propType] = fields[i];
let [key] = fields[i];
(this as any)[key] = props[i];
}
}
Expand Down