o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
230 lines (199 loc) • 6.02 kB
text/typescript
import {
DynamicProof,
Proof,
VerificationKey,
Void,
ZkProgram,
} from './zkprogram.js';
import { Field, SmartContract, Struct, method } from '../../index.js';
import { it, describe, before } from 'node:test';
import { expect } from 'expect';
const program1 = ZkProgram({
name: 'program1',
publicInput: Field,
methods: {
foo: {
privateInputs: [Field],
async method(publicInput: Field, field: Field) {
publicInput.assertEquals(field);
},
},
},
});
export class Program2Struct extends Struct({
field1: Field,
field2: Field,
}) {}
const program2 = ZkProgram({
name: 'program2',
publicInput: Program2Struct,
publicOutput: Field,
methods: {
foo: {
privateInputs: [Field],
async method(publicInput: Program2Struct, field: Field) {
return publicInput.field1.add(publicInput.field2).add(field);
},
},
},
});
class SampleSideloadedProof extends DynamicProof<Field, Void> {
static publicInputType = Field;
static publicOutputType = Void;
static maxProofsVerified = 0 as const;
}
class SampleSideloadedProof2 extends DynamicProof<Program2Struct, Field> {
static publicInputType = Program2Struct;
static publicOutputType = Field;
static maxProofsVerified = 0 as const;
}
const sideloadedProgram = ZkProgram({
name: 'sideloadedProgram',
publicInput: Field,
methods: {
recurseOneSideloaded: {
privateInputs: [SampleSideloadedProof, VerificationKey],
async method(
publicInput: Field,
proof: SampleSideloadedProof,
vk: VerificationKey
) {
proof.verify(vk);
proof.publicInput.assertEquals(publicInput, 'PublicInput not matching');
},
},
recurseTwoSideloaded: {
privateInputs: [
SampleSideloadedProof,
VerificationKey,
SampleSideloadedProof2,
VerificationKey,
],
async method(
publicInput: Field,
proof1: SampleSideloadedProof,
vk1: VerificationKey,
proof2: SampleSideloadedProof2,
vk2: VerificationKey
) {
proof1.verify(vk1);
proof2.verify(vk2);
proof1.publicInput
.add(proof2.publicInput.field1.add(proof2.publicInput.field2))
.assertEquals(publicInput, 'PublicInput not matching');
},
},
},
});
const sideloadedProgram2 = ZkProgram({
name: 'sideloadedProgram2',
publicInput: Field,
methods: {
recurseTwoSideloaded: {
privateInputs: [
SampleSideloadedProof,
VerificationKey,
SampleSideloadedProof2,
VerificationKey,
],
async method(
publicInput: Field,
proof1: SampleSideloadedProof,
vk1: VerificationKey,
proof2: SampleSideloadedProof2,
vk2: VerificationKey
) {
proof1.verify(vk1);
proof2.verify(vk2);
proof1.publicInput
.add(proof2.publicInput.field1.add(proof2.publicInput.field2))
.add(1)
.assertEquals(publicInput, 'PublicInput not matching');
},
},
},
});
export class SideloadedSmartContract extends SmartContract {
@method async setValue(
value: Field,
proof: SampleSideloadedProof,
vk: VerificationKey
) {
proof.verify(vk);
proof.publicInput.assertEquals(value);
}
}
describe('sideloaded', async () => {
let program1Vk = (await program1.compile()).verificationKey;
let program2Vk = (await program2.compile()).verificationKey;
// Generate sample proofs
const program1Proof = await program1.foo(Field(1), Field(1));
const program2Proof = await program2.foo(
{ field1: Field(1), field2: Field(2) },
Field(3)
);
await sideloadedProgram.compile();
it('should convert proof to DynamicProof', async () => {
const proof = SampleSideloadedProof.fromProof(program1Proof);
expect(proof instanceof DynamicProof).toBe(true);
expect(proof instanceof SampleSideloadedProof).toBe(true);
expect(proof.constructor.name).toStrictEqual(SampleSideloadedProof.name);
});
it('recurse one proof with zkprogram', async () => {
const proof = SampleSideloadedProof.fromProof(program1Proof);
const finalProof = await sideloadedProgram.recurseOneSideloaded(
Field(1),
proof,
program1Vk
);
expect(finalProof).toBeDefined();
expect(finalProof.maxProofsVerified).toBe(2);
});
it('recurse two different proofs with zkprogram', async () => {
const proof1 = SampleSideloadedProof.fromProof(program1Proof);
const proof2 = SampleSideloadedProof2.fromProof(program2Proof);
const finalProof = await sideloadedProgram.recurseTwoSideloaded(
Field(4),
proof1,
program1Vk,
proof2,
program2Vk
);
expect(finalProof).toBeDefined();
});
it('should fail to prove with faulty vk', async () => {
const proof1 = SampleSideloadedProof.fromProof(program1Proof);
const proof2 = SampleSideloadedProof2.fromProof(program2Proof);
// VK for proof2 wrong
await expect(async () => {
return await sideloadedProgram.recurseTwoSideloaded(
Field(7),
proof1,
program1Vk,
proof2,
program1Vk
);
}).rejects.toThrow();
});
it('should work if SL Proof classes are used in different ZkPrograms', async () => {
const proof1 = SampleSideloadedProof.fromProof(program1Proof);
const proof2 = SampleSideloadedProof2.fromProof(program2Proof);
await sideloadedProgram2.compile();
const finalProof = await sideloadedProgram2.recurseTwoSideloaded(
Field(5),
proof1,
program1Vk,
proof2,
program2Vk
);
expect(finalProof).toBeDefined();
});
it('different proof classes should have different tags', async () => {
const tag1 = SampleSideloadedProof.tag();
const tag2 = SampleSideloadedProof2.tag();
expect(tag1).not.toStrictEqual(tag2);
});
it('should compile with SmartContracts', async () => {
await SideloadedSmartContract.compile();
});
});