export-ton-verifier
Version:
Tool for generating Groth16 and PLONK verifier code for the TON blockchain from SnarkJS .zkey or verification key JSON files.
249 lines (218 loc) • 7.14 kB
text/typescript
import {
Address,
beginCell,
Cell,
Contract,
contractAddress,
ContractProvider,
Sender,
SendMode,
TupleItem,
} from "@ton/core";
export type VerifierConfig = {};
const VERIFY_PROOF_OP = 0x76524659;
const PUBLIC_INPUTS_ARG_INDEX = 15;
const UINT256_ARRAY_CHUNK_SIZE = 3;
const MAX_PLONK_PUBLIC_INPUTS = 244;
export function verifierConfigToCell(config: VerifierConfig): Cell {
return beginCell().endCell();
}
function requireTupleCell(
args: TupleItem[],
index: number,
type: "slice" | "cell",
): Cell {
const item = args[index];
if (item?.type !== type) {
throw new Error(`Expected calldata[${index}] to be ${type}`);
}
return item.cell;
}
function requireTupleInt(args: TupleItem[], index: number): bigint {
const item = args[index];
if (item?.type !== "int") {
throw new Error(`Expected calldata[${index}] to be int`);
}
return item.value;
}
function requirePublicInputTuple(args: TupleItem[]): bigint[] {
const item = args[PUBLIC_INPUTS_ARG_INDEX];
if (item?.type !== "tuple") {
throw new Error(`Expected calldata[${PUBLIC_INPUTS_ARG_INDEX}] to be tuple`);
}
return item.items.map((publicInput, index) => {
if (publicInput.type !== "int") {
throw new Error(`Expected calldata[${PUBLIC_INPUTS_ARG_INDEX}][${index}] to be int`);
}
return publicInput.value;
});
}
function uint256ArrayToCell(values: bigint[]): Cell {
if (values.length > MAX_PLONK_PUBLIC_INPUTS) {
throw new Error(
`Expected at most ${MAX_PLONK_PUBLIC_INPUTS} public inputs, got ${values.length}`,
);
}
const chunks: bigint[][] = [];
for (let i = 0; i < values.length; i += UINT256_ARRAY_CHUNK_SIZE) {
chunks.push(values.slice(i, i + UINT256_ARRAY_CHUNK_SIZE));
}
const buildChunk = (index: number): Cell => {
const hasNext = index + 1 < chunks.length;
const builder = beginCell().storeBit(hasNext);
for (const value of chunks[index]) {
builder.storeUint(value, 256);
}
if (hasNext) {
builder.storeRef(buildChunk(index + 1));
}
return builder.endCell();
};
const builder = beginCell().storeUint(values.length, 8);
if (chunks.length === 0) {
builder.storeBit(false);
} else {
builder.storeBit(true).storeRef(buildChunk(0));
}
return builder.endCell();
}
function storePair(first: Cell, second: Cell): Cell {
return beginCell()
.storeSlice(first.beginParse())
.storeSlice(second.beginParse())
.endCell();
}
async function runVerifyGet(provider: ContractProvider, args: TupleItem[]) {
const res = await provider.get("verify", args);
return {
ok: res.stack.readBoolean(),
gasUsed: res.gasUsed ?? undefined,
};
}
export function proofMessageToCell(args: TupleItem[]): Cell {
const a = requireTupleCell(args, 0, "slice");
const b = requireTupleCell(args, 1, "slice");
const c = requireTupleCell(args, 2, "slice");
const z = requireTupleCell(args, 3, "slice");
const t1 = requireTupleCell(args, 4, "slice");
const t2 = requireTupleCell(args, 5, "slice");
const t3 = requireTupleCell(args, 6, "slice");
const wxi = requireTupleCell(args, 13, "slice");
const wxiw = requireTupleCell(args, 14, "slice");
const publicInputs = uint256ArrayToCell(requirePublicInputTuple(args));
const aUc = requireTupleCell(args, 16, "slice");
const bUc = requireTupleCell(args, 17, "slice");
const cUc = requireTupleCell(args, 18, "slice");
const zUc = requireTupleCell(args, 19, "slice");
const t1Uc = requireTupleCell(args, 20, "slice");
const t2Uc = requireTupleCell(args, 21, "slice");
const t3Uc = requireTupleCell(args, 22, "slice");
const wxiUc = requireTupleCell(args, 23, "slice");
const wxiwUc = requireTupleCell(args, 24, "slice");
const compressedTail = beginCell()
.storeRef(storePair(t3, wxi))
.storeRef(wxiw)
.endCell();
const compressed = beginCell()
.storeRef(storePair(a, b))
.storeRef(storePair(c, z))
.storeRef(storePair(t1, t2))
.storeRef(compressedTail)
.endCell();
const evaluations = beginCell()
.storeRef(
beginCell()
.storeUint(requireTupleInt(args, 7), 256)
.storeUint(requireTupleInt(args, 8), 256)
.storeUint(requireTupleInt(args, 9), 256),
)
.storeRef(
beginCell()
.storeUint(requireTupleInt(args, 10), 256)
.storeUint(requireTupleInt(args, 11), 256)
.storeUint(requireTupleInt(args, 12), 256),
)
.endCell();
const uncompressedTail2 = beginCell()
.storeRef(t3Uc)
.storeRef(wxiUc)
.storeRef(wxiwUc)
.endCell();
const uncompressedTail = beginCell()
.storeRef(zUc)
.storeRef(t1Uc)
.storeRef(t2Uc)
.storeRef(uncompressedTail2)
.endCell();
const uncompressed = beginCell()
.storeRef(aUc)
.storeRef(bUc)
.storeRef(cUc)
.storeRef(uncompressedTail)
.endCell();
const payload = beginCell()
.storeRef(compressed)
.storeRef(evaluations)
.storeSlice(publicInputs.beginParse())
.storeRef(uncompressed)
.endCell();
return beginCell().storeUint(VERIFY_PROOF_OP, 32).storeRef(payload).endCell();
}
export function proofMessageToCellNew(args: TupleItem[]): Cell {
return proofMessageToCell(args);
}
export class Verifier implements Contract {
constructor(
readonly address: Address,
readonly init?: { code: Cell; data: Cell },
) {}
static createFromAddress(address: Address) {
return new Verifier(address);
}
static createFromConfig(config: VerifierConfig, code: Cell, workchain = 0) {
const data = verifierConfigToCell(config);
const init = { code, data };
return new Verifier(contractAddress(workchain, init), init);
}
async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().endCell(),
});
}
async sendVerify(
provider: ContractProvider,
via: Sender,
value: bigint,
args: TupleItem[],
) {
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: proofMessageToCell(args),
});
}
async sendVerifyNew(
provider: ContractProvider,
via: Sender,
value: bigint,
args: TupleItem[],
) {
await this.sendVerify(provider, via, value, args);
}
async getVerifyResult(provider: ContractProvider, args: TupleItem[]) {
return runVerifyGet(provider, args);
}
async getVerifyResultNew(provider: ContractProvider, args: TupleItem[]) {
return this.getVerifyResult(provider, args);
}
async getVerify(provider: ContractProvider, args: TupleItem[]) {
const res = await this.getVerifyResult(provider, args);
return res.ok;
}
async getVerifyNew(provider: ContractProvider, args: TupleItem[]) {
const res = await this.getVerifyResultNew(provider, args);
return res.ok;
}
}