Skip to content

Commit

Permalink
Merge pull request #245 from o1-labs/feature/recursion
Browse files Browse the repository at this point in the history
Recursion Pt 1
  • Loading branch information
mitschabaude committed Jun 27, 2022
2 parents 1b59faa + a220a8b commit 0cae7ba
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 122 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: run typedoc
run: |
npm ci
npx typedoc src/index.ts
npx typedoc --tsconfig tsconfig.server.json src/index.ts
- name: deploy
uses: peaceiris/actions-gh-pages@v3
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"scripts": {
"type-check": "tsc --noEmit",
"dev": "node --stack-trace-limit=100 src/build/buildNode.mjs --bindings=./dist/server/node_bindings/",
"make": "make -C ../../../.. snarkyjs",
"build": "node --stack-trace-limit=100 src/build/buildNode.mjs",
"build:node": "node --stack-trace-limit=100 src/build/buildNode.mjs",
"build:web": "node src/build/buildWeb.mjs",
Expand Down
128 changes: 128 additions & 0 deletions src/examples/simple_zkapp_with_proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import {
Field,
state,
State,
method,
PrivateKey,
SmartContract,
Mina,
Party,
isReady,
ZkappPublicInput,
Proof,
Ledger,
} from 'snarkyjs';

await isReady;

class SimpleZkappProof extends Proof<ZkappPublicInput> {
static publicInputType = ZkappPublicInput;
static tag = () => NotSoSimpleZkapp;
}
class TrivialProof extends Proof<ZkappPublicInput> {
static publicInputType = ZkappPublicInput;
static tag = () => TrivialZkapp;
}

class NotSoSimpleZkapp extends SmartContract {
@state(Field) x = State<Field>();

@method init(proof: TrivialProof) {
proof.verify();
this.x.set(Field.one);
}

@method update(
y: Field,
oldProof: SimpleZkappProof,
trivialProof: TrivialProof
) {
oldProof.verify();
trivialProof.verify();
let x = this.x.get();
this.x.set(x.add(y));
}
}

class TrivialZkapp extends SmartContract {
@method proveSomething(hasToBe1: Field) {
hasToBe1.assertEquals(1);
}
}

let Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);

// a test account that pays all the fees, and puts additional funds into the zkapp
let feePayerKey = Local.testAccounts[0].privateKey;

// the zkapp account
let zkappKey = PrivateKey.random();
let zkappAddress = zkappKey.toPublicKey();

// trivial zkapp account
let zkappKey2 = PrivateKey.random();
let zkappAddress2 = zkappKey2.toPublicKey();

// compile and prove trivial zkapp
console.log('compile (trivial zkapp)');
await TrivialZkapp.compile(zkappAddress2);
// 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 [trivialZkappProof] = await (
await Mina.transaction(feePayerKey, () => {
new TrivialZkapp(zkappAddress2).proveSomething(Field(1));
})
).prove();

console.log('compile');
let { verificationKey } = await NotSoSimpleZkapp.compile(zkappAddress);

let zkapp = new NotSoSimpleZkapp(zkappAddress);

console.log('deploy');
let tx = await Mina.transaction(feePayerKey, () => {
Party.fundNewAccount(feePayerKey);
zkapp.deploy({ zkappKey });
});
tx.send();

console.log('init');
tx = await Mina.transaction(feePayerKey, () => {
zkapp.init(trivialZkappProof!);
});
let [proof] = await tx.prove();
tx.send();

console.log('initial state: ' + zkapp.x.get());

console.log('update');
tx = await Mina.transaction(feePayerKey, () => {
zkapp.update(Field(3), proof!, trivialZkappProof!);
});
[proof] = await tx.prove();
tx.send();

// check that proof can be converted to string
let proofString = proof!.toString();

// check that proof verifies
let ok = Ledger.verifyPartyProof(
proof!.publicInput,
proofString,
verificationKey.data
);
if (!ok) throw Error('proof cannot be verified');

console.log('state 2: ' + zkapp.x.get());

console.log('update');
tx = await Mina.transaction(feePayerKey, () => {
zkapp.update(Field(3), proof!, trivialZkappProof!);
});
[proof] = await tx.prove();
tx.send();

console.log('final state: ' + zkapp.x.get());
17 changes: 15 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
export * from './snarky';
export {
Field,
Bool,
Group,
Scalar,
AsFieldElements,
Circuit,
Poseidon,
Ledger,
isReady,
shutdown,
Types,
} from './snarky';
export type { VerificationKey, Keypair } from './snarky';
export * from './snarky/addons';
export * from './lib/signature';
export * from './lib/circuit_value';
Expand All @@ -7,7 +20,7 @@ export * from './lib/int';
export * as Mina from './lib/mina';
export * from './lib/zkapp';
export { state, State, declareState } from './lib/state';
// export * from './lib/proof_system';
export * from './lib/proof_system';
export * from './lib/party';
export {
fetchAccount,
Expand Down
67 changes: 66 additions & 1 deletion src/lib/circuit_value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export {
cloneCircuitValue,
circuitValueEquals,
circuitArray,
circuitValue,
};

type Constructor<T> = { new (...args: any[]): T };
Expand Down Expand Up @@ -74,6 +75,8 @@ abstract class CircuitValue {
return new this(...props);
}

static check: (x: any) => void;

static toConstant<T>(this: Constructor<T>, t: T): T {
const xs: Field[] = (this as any).toFields(t);
return (this as any).ofFields(xs.map((x) => x.toConstant()));
Expand Down Expand Up @@ -149,7 +152,10 @@ function prop(this: any, target: any, key: string) {
}
}

function circuitArray<T>(elementType: AsFieldElements<T>, length: number) {
function circuitArray<T>(
elementType: AsFieldElements<T>,
length: number
): AsFieldElements<T[]> {
return {
sizeInFields() {
let elementLength = elementType.sizeInFields();
Expand Down Expand Up @@ -282,6 +288,65 @@ function circuitMain(
}

let primitives = new Set(['Field', 'Bool', 'Scalar', 'Group']);
let complexTypes = new Set(['object', 'function']);

// TODO properly type this at the interface
// create recursive type that describes JSON-like structures of circuit types
// TODO unit-test this
function circuitValue<T>(typeObj: any): AsFieldElements<T> {
function sizeInFields(typeObj: any): number {
if (!complexTypes.has(typeof typeObj) || typeObj === null) return 0;
if (Array.isArray(typeObj))
return typeObj.map(sizeInFields).reduce((a, b) => a + b);
if ('sizeInFields' in typeObj) return typeObj.sizeInFields();
return Object.values(typeObj)
.map(sizeInFields)
.reduce((a, b) => a + b);
}
function toFields(typeObj: any, obj: any): Field[] {
if (!complexTypes.has(typeof typeObj) || typeObj === null) return [];
if (Array.isArray(typeObj))
return typeObj.map((t, i) => toFields(t, obj[i])).flat();
if ('toFields' in typeObj) return typeObj.toFields(obj);
return Object.keys(typeObj)
.sort()
.map((k) => toFields(typeObj[k], obj[k]))
.flat();
}
function ofFields(typeObj: any, fields: Field[]): any {
if (!complexTypes.has(typeof typeObj) || typeObj === null) return null;
if (Array.isArray(typeObj)) {
let array = [];
let offset = 0;
for (let subObj of typeObj) {
let size = sizeInFields(subObj);
array.push(subObj.ofFields(fields.slice(offset, offset + size)));
offset += size;
}
return array;
}
if ('ofFields' in typeObj) return typeObj.ofFields(fields);
let typeObjArray = Object.keys(typeObj)
.sort()
.map((k) => typeObj[k]);
return ofFields(typeObjArray, fields);
}
function check(typeObj: any, obj: any): void {
if (typeof typeObj !== 'object' || typeObj === null) return;
if (Array.isArray(typeObj))
return typeObj.forEach((t, i) => check(t, obj[i]));
if ('check' in typeObj) return typeObj.check(obj);
return Object.keys(typeObj)
.sort()
.forEach((k) => check(typeObj[k], obj[k]));
}
return {
sizeInFields: () => sizeInFields(typeObj),
toFields: (obj: T) => toFields(typeObj, obj),
ofFields: (fields: Field[]) => ofFields(typeObj, fields) as T,
check: (obj: T) => check(typeObj, obj),
};
}

function cloneCircuitValue<T>(obj: T): T {
// primitive JS types and functions aren't cloned
Expand Down
4 changes: 2 additions & 2 deletions src/lib/int.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class UInt64 extends CircuitValue implements Types.UInt64 {
return this.value.toString();
}

static check(x: Types.UInt64) {
static check(x: UInt64) {
let actual = x.value.rangeCheckHelper(64);
actual.assertEquals(x.value);
}
Expand Down Expand Up @@ -204,7 +204,7 @@ class UInt32 extends CircuitValue implements Types.UInt32 {
return new UInt64(this.value);
}

static check(x: Types.UInt32) {
static check(x: UInt32) {
let actual = x.value.rangeCheckHelper(32);
actual.assertEquals(x.value);
}
Expand Down
13 changes: 8 additions & 5 deletions src/lib/mina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import {
partiesToJson,
Party,
ZkappStateLength,
ZkappPublicInput,
} from './party';
import * as Fetch from './fetch';
import { assertPreconditionInvariants, NetworkValue } from './precondition';
import { cloneCircuitValue } from './circuit_value';
import { Proof } from './proof_system';

export {
createUnsignedTransaction,
Expand Down Expand Up @@ -43,7 +45,7 @@ interface Transaction {
toJSON(): string;
toGraphqlQuery(): string;
sign(additionialKeys?: PrivateKey[]): Transaction;
prove(): Promise<Transaction>;
prove(): Promise<(Proof<ZkappPublicInput> | undefined)[]>;
send(): TransactionId;
}

Expand Down Expand Up @@ -140,7 +142,7 @@ function createTransaction(

nextTransactionId.value += 1;
currentTransaction = undefined;
let self = {
let self: Transaction = {
transaction,

sign(additionalKeys?: PrivateKey[]) {
Expand All @@ -149,8 +151,9 @@ function createTransaction(
},

async prove() {
self.transaction = await addMissingProofs(self.transaction);
return self;
let { parties, proofs } = await addMissingProofs(self.transaction);
self.transaction = parties;
return proofs;
},

toJSON() {
Expand Down Expand Up @@ -183,7 +186,7 @@ interface MockMina extends Mina {
* An array of 10 test accounts that have been pre-filled with
* 30000000000 units of currency.
*/
testAccounts: Array<{ publicKey: Types.PublicKey; privateKey: PrivateKey }>;
testAccounts: Array<{ publicKey: PublicKey; privateKey: PrivateKey }>;
applyJsonTransaction: (tx: string) => void;
}

Expand Down
Loading

0 comments on commit 0cae7ba

Please sign in to comment.