-
Notifications
You must be signed in to change notification settings - Fork 107
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
Recursion Pt 1 #245
Changes from 29 commits
1e80f45
7431588
64a113d
4decb76
229c0c6
5c6bd86
df77c35
797efa0
85576f0
48d908b
6bcf4d1
46a6fa8
6846bb9
6fea744
a61a3bc
bdc106b
7b14792
80401ad
ae5ff37
e8fc3a7
576f094
783e9fb
d227d46
bd9a768
a20cbd9
9f60ff4
e7d69ba
004b509
8c9cce6
a220a8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { | ||
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()); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ export { | |
cloneCircuitValue, | ||
circuitValueEquals, | ||
circuitArray, | ||
circuitValue, | ||
}; | ||
|
||
type Constructor<T> = { new (...args: any[]): T }; | ||
|
@@ -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())); | ||
|
@@ -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(); | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. helper function which provides functionality equivalent to 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;
}
}
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -43,7 +45,7 @@ interface Transaction { | |
toJSON(): string; | ||
toGraphqlQuery(): string; | ||
sign(additionialKeys?: PrivateKey[]): Transaction; | ||
prove(): Promise<Transaction>; | ||
prove(): Promise<(Proof<ZkappStatement> | undefined)[]>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only breaking change: |
||
send(): TransactionId; | ||
} | ||
|
||
|
@@ -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( | ||
|
@@ -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 { | ||
|
@@ -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, | ||
|
@@ -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[]) { | ||
|
@@ -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() { | ||
|
@@ -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; | ||
} | ||
|
||
|
There was a problem hiding this comment.
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!