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

Recursion Pt 1 #245

Merged
merged 30 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1e80f45
add helper to create circuit value types
mitschabaude Jun 15, 2022
7431588
type tweaks to infer int types from their class
mitschabaude Jun 15, 2022
64a113d
start work on Proof
mitschabaude Jun 15, 2022
4decb76
fix source of errors
mitschabaude Jun 15, 2022
229c0c6
add todo comment
mitschabaude Jun 15, 2022
5c6bd86
return proofs from tx.prove()
mitschabaude Jun 15, 2022
df77c35
add TS example which does proving
mitschabaude Jun 15, 2022
797efa0
remove unused argument to pickles main
mitschabaude Jun 15, 2022
85576f0
fix circuitValue
mitschabaude Jun 15, 2022
48d908b
fix circuitValue pt 2
mitschabaude Jun 15, 2022
6bcf4d1
implement self proving for smart contracts
mitschabaude Jun 15, 2022
46a6fa8
test self proving in example
mitschabaude Jun 15, 2022
6846bb9
add some potential types for multi-circuit
mitschabaude Jun 15, 2022
6fea744
add useful script for Mina + snarkyjs development
mitschabaude Jun 16, 2022
a61a3bc
fix Pickles types and make self proving work
mitschabaude Jun 16, 2022
bdc106b
Merge branch 'main' into feature/recursion
mitschabaude Jun 16, 2022
7b14792
implement merging proofs from other zkapps
mitschabaude Jun 16, 2022
80401ad
errors when trying to merge > 2 proofs
mitschabaude Jun 16, 2022
ae5ff37
make example merge proofs from other zkapps
mitschabaude Jun 16, 2022
e8fc3a7
fix typedoc
mitschabaude Jun 16, 2022
576f094
check proof verification
mitschabaude Jun 16, 2022
783e9fb
fix shutdown
mitschabaude Jun 16, 2022
d227d46
Revert "add some potential types for multi-circuit"
mitschabaude Jun 16, 2022
bd9a768
Merge branch 'main' into feature/recursion
mitschabaude Jun 16, 2022
a20cbd9
consistently use `publicInput`
mitschabaude Jun 16, 2022
9f60ff4
fix stale `SmartContract` instance in prover
mitschabaude Jun 16, 2022
e7d69ba
don't export * from snarky.d.ts
mitschabaude Jun 16, 2022
004b509
add TODO
mitschabaude Jun 27, 2022
8c9cce6
Merge branch 'main' into feature/recursion
mitschabaude Jun 27, 2022
a220a8b
fix: can't use Bool.true yet
mitschabaude Jun 27, 2022
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
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
129 changes: 129 additions & 0 deletions src/examples/simple_zkapp_with_proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file tests the additions of this PR!

Field,
state,
State,
method,
PrivateKey,
SmartContract,
Mina,
Party,
isReady,
ZkappStatement,
Proof,
Pickles,
Ledger,
} from 'snarkyjs';

await isReady;

class SimpleZkappProof extends Proof<ZkappStatement> {
static publicInputType = ZkappStatement;
static tag = () => NotSoSimpleZkapp;
}
class TrivialProof extends Proof<ZkappStatement> {
static publicInputType = ZkappStatement;
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 = Pickles.proofToString(proof?.proof);

// 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());
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export {
export * as Encryption from './lib/encryption';
export * as Encoding from './lib/encoding';
export { Character, CircuitString } from './lib/string';
export * from './lib/proof_system';
66 changes: 65 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,64 @@ function circuitMain(
}

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

// TODO properly type this at the interface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a todo for unit tests here too?

// create recursive type that describes JSON-like structures of circuit types
function circuitValue<T>(typeObj: any): AsFieldElements<T> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

helper function which provides functionality equivalent to CircuitValue, but is less brittle, doesn't require decorators and can be applied in situations where a circuit value should be created from a plain JS object
i.e.

let MyCircuitValue = circuitValue({ a: Group, b: Bool, c: [Field, Field] });

is equivalent to (but is more precisely typed than)

class MyCircuitValue extends CircuitValue {
  @prop a: Group;
  @prop b: Bool;
  @arrayProp(Field, 2) c: Field[];
  
  constructor(a: Group, b: Bool, c: Field[]) {
    this.a = a;
    this.b = b;
    this.c = c;
  }
}

circuitValue is only used in one place right now but I want to explore further usage, for example to simplify non-decorator ways of specifying circuit inputs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think we should move towards the more precise typed versions of things with generics in general.

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
22 changes: 12 additions & 10 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,
ZkappStatement,
} 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<ZkappStatement> | undefined)[]>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only breaking change: tx.prove now returns a list of proofs (one for each party, undefined for parties without proofs) instead of the transaction itself. (Because it was async, returning the transaction wasn't very useful for chaining anyway)

send(): TransactionId;
}

Expand All @@ -68,7 +70,7 @@ function setCurrentTransaction(transaction: CurrentTransaction) {

type SenderSpec =
| PrivateKey
| { feePayerKey: PrivateKey; fee?: number | string | UInt64 }
| { feePayerKey: PrivateKey; fee?: number | string | UInt64; memo?: string }
| undefined;

function createUnsignedTransaction(
Expand All @@ -79,10 +81,7 @@ function createUnsignedTransaction(
}

function createTransaction(
feePayer:
| PrivateKey
| { feePayerKey: PrivateKey; fee?: number | string | UInt64 }
| undefined,
feePayer: SenderSpec,
f: () => unknown,
{ fetchMode = 'cached' as FetchMode } = {}
): Transaction {
Expand All @@ -92,6 +91,7 @@ function createTransaction(
let feePayerKey =
feePayer instanceof PrivateKey ? feePayer : feePayer?.feePayerKey;
let fee = feePayer instanceof PrivateKey ? undefined : feePayer?.fee;
let memo = feePayer instanceof PrivateKey ? '' : feePayer?.memo ?? '';

currentTransaction = {
sender: feePayerKey,
Expand Down Expand Up @@ -137,11 +137,12 @@ function createTransaction(
let transaction: Parties = {
otherParties: currentTransaction.parties,
feePayer: feePayerParty,
memo,
};

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

sign(additionalKeys?: PrivateKey[]) {
Expand All @@ -150,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 @@ -184,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