export-ton-verifier
Version:
Tool for generating Groth16 and PLONK verifier code for the TON blockchain from SnarkJS .zkey or verification key JSON files.
202 lines (199 loc) • 7.32 kB
JavaScript
import {
exportPlonkFuncCalldata,
exportPlonkTolkCalldata
} from "./chunk-SFKUOXLN.js";
import {
groth16CompressProof
} from "./chunk-IELVU6ER.js";
// src/proofMessages.js
import { beginCell, Cell, Dictionary } from "@ton/core";
var GROTH16_VERIFY_OP = 993839639;
var PLONK_VERIFY_PROOF_OP = 1985103449;
var UINT256_ARRAY_CHUNK_SIZE = 3;
function normalizeProtocol(proof, protocol) {
const detected = protocol ?? (proof == null ? void 0 : proof.protocol);
if (detected !== "groth16" && detected !== "plonk") {
throw new Error("proof protocol must be 'groth16' or 'plonk'.");
}
return detected;
}
function normalizeLang(lang = "tolk") {
if (lang !== "tolk" && lang !== "func") {
throw new Error("proof-to-message supports --tolk and --func.");
}
return lang;
}
function serializeTolkIntArray(list) {
if (list.length > 255) {
throw new Error("Tolk array<int> wrapper supports at most 255 items.");
}
let tail = null;
for (let i = list.length; i > 0; i -= 3) {
const chunk = list.slice(Math.max(0, i - 3), i);
const cell = beginCell();
for (const value of chunk) {
cell.storeInt(value, 257);
}
if (tail) {
cell.storeRef(tail);
}
tail = cell.endCell();
}
const root = beginCell().storeUint(list.length, 8);
if (tail) {
root.storeSlice(tail.beginParse());
}
return root.endCell();
}
function groth16FuncPublicInputs(list) {
const dict = Dictionary.empty(
Dictionary.Keys.BigUint(32),
Dictionary.Values.BigInt(256)
);
for (let i = 0; i < list.length; i += 1) {
dict.set(BigInt(i), list[i]);
}
return dict;
}
async function groth16MessageCell(proof, publicSignals, lang) {
const compressed = await groth16CompressProof(proof, publicSignals);
const piA = beginCell().storeBuffer(compressed.pi_a).endCell();
const piB = beginCell().storeBuffer(compressed.pi_b).endCell();
const piC = beginCell().storeBuffer(compressed.pi_c).endCell();
const body = beginCell().storeUint(GROTH16_VERIFY_OP, 32).storeRef(piA).storeRef(piB).storeRef(piC);
if (lang === "tolk") {
body.storeSlice(serializeTolkIntArray(compressed.pubInputs).beginParse());
} else {
body.storeDict(groth16FuncPublicInputs(compressed.pubInputs));
}
return body.endCell();
}
function requireTupleCell(args, index, type = "slice") {
const item = args[index];
if ((item == null ? void 0 : item.type) !== type) {
throw new Error(`Expected calldata[${index}] to be ${type}.`);
}
return item.cell;
}
function requireTupleInt(args, index) {
const item = args[index];
if ((item == null ? void 0 : item.type) !== "int") {
throw new Error(`Expected calldata[${index}] to be int.`);
}
return item.value;
}
function requirePublicInputTuple(args) {
const item = args[15];
if ((item == null ? void 0 : item.type) !== "tuple") {
throw new Error("Expected PLONK Tolk calldata[15] to be tuple.");
}
return item.items.map((publicInput, index) => {
if (publicInput.type !== "int") {
throw new Error(`Expected PLONK public input ${index} to be int.`);
}
return publicInput.value;
});
}
function uint256ArrayToCell(values) {
const chunks = [];
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) => {
const hasNext = index + 1 < chunks.length;
const builder2 = beginCell().storeBit(hasNext);
for (const value of chunks[index]) {
builder2.storeUint(value, 256);
}
if (hasNext) {
builder2.storeRef(buildChunk(index + 1));
}
return builder2.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, second) {
return beginCell().storeSlice(first.beginParse()).storeSlice(second.beginParse()).endCell();
}
function plonkTolkMessageFromCalldata(args) {
const a = requireTupleCell(args, 0);
const b = requireTupleCell(args, 1);
const c = requireTupleCell(args, 2);
const z = requireTupleCell(args, 3);
const t1 = requireTupleCell(args, 4);
const t2 = requireTupleCell(args, 5);
const t3 = requireTupleCell(args, 6);
const wxi = requireTupleCell(args, 13);
const wxiw = requireTupleCell(args, 14);
const publicInputs = uint256ArrayToCell(requirePublicInputTuple(args));
const aUc = requireTupleCell(args, 16);
const bUc = requireTupleCell(args, 17);
const cUc = requireTupleCell(args, 18);
const zUc = requireTupleCell(args, 19);
const t1Uc = requireTupleCell(args, 20);
const t2Uc = requireTupleCell(args, 21);
const t3Uc = requireTupleCell(args, 22);
const wxiUc = requireTupleCell(args, 23);
const wxiwUc = requireTupleCell(args, 24);
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(PLONK_VERIFY_PROOF_OP, 32).storeRef(payload).endCell();
}
async function plonkMessageCell(proof, publicSignals, lang) {
if (lang === "func") {
await exportPlonkFuncCalldata(proof, publicSignals);
throw new Error(
"PLONK FunC verifier template does not define an internal message receiver."
);
}
const calldata = await exportPlonkTolkCalldata(proof, publicSignals);
return plonkTolkMessageFromCalldata(calldata);
}
async function proofToMessageCell({
proof,
publicSignals,
protocol,
lang = "tolk"
}) {
const normalizedProtocol = normalizeProtocol(proof, protocol);
const normalizedLang = normalizeLang(lang);
if (!Array.isArray(publicSignals)) {
throw new Error("publicSignals must be an array.");
}
if (normalizedProtocol === "groth16") {
return groth16MessageCell(proof, publicSignals, normalizedLang);
}
return plonkMessageCell(proof, publicSignals, normalizedLang);
}
async function proofToMessageBoc({
proof,
publicSignals,
protocol,
lang = "tolk",
format = "base64"
}) {
if (format !== "base64" && format !== "hex") {
throw new Error("proof-to-message format must be 'base64' or 'hex'.");
}
const cell = await proofToMessageCell({ proof, publicSignals, protocol, lang });
return Buffer.from(cell.toBoc()).toString(format);
}
export {
proofToMessageCell,
proofToMessageBoc
};