Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7bc6642
feat: add Keccak256CompressedTranscript for Cardano/Plutus compatibility
perturbing Jun 6, 2026
54cf7a6
feat: add transcript option to PLONK prover and verifier
perturbing Jun 6, 2026
9c10964
feat: add transcript option to FFLONK prover and verifier
perturbing Jun 6, 2026
0075266
feat: wire --transcript option to CLI and export transcript classes
perturbing Jun 6, 2026
dbf365c
test: add Cardano transcript tests for PLONK and FFLONK
perturbing Jun 6, 2026
c71157c
feat: add Cardano/Plutus proof and verification key export commands
perturbing Jun 7, 2026
14c7c6c
fix: compute FFLONK cubic root of unity curve-agnostically
perturbing Jun 7, 2026
ef06a6e
build: rebuild bundles with Keccak256CompressedTranscript
perturbing Jun 7, 2026
ac9b4a6
fix: restore bn128 FFLONK w3 computation, add bls12381 branch
perturbing Jul 21, 2026
b02dce2
refactor: drop shadowing Fr parameter from getOmegaCubicRoot
perturbing Jul 21, 2026
0bc098a
refactor: extract shared ZCash point compression helpers
perturbing Jul 21, 2026
d76a31f
fix: reject non-bls12381 curves in Cardano export commands
perturbing Jul 21, 2026
3db7366
fix: reject unknown transcript option values
perturbing Jul 21, 2026
a8ee709
refactor: collapse duplicate export-cardano-proof CLI handlers
perturbing Jul 21, 2026
e36bd30
build: rebuild bundles
perturbing Jul 21, 2026
e0425ea
fix: accept absent --transcript option from the CLI
perturbing Aug 5, 2026
cbf7e7a
test: pin ZCash point compression to IETF generator vectors
perturbing Aug 5, 2026
b066503
fix: reject non-bls12381 curves in the compressed transcript
perturbing Aug 5, 2026
85a52d8
refactor: derive compression sign flag from a half-field comparison
perturbing Aug 5, 2026
9243c16
refactor: share transcript implementation between variants
perturbing Aug 5, 2026
0aae369
fix: reject unknown curves in FFLONK w3 computation
perturbing Aug 5, 2026
d0d4197
fix: assert getOmegaCubicRoot cubes to the domain root
perturbing Aug 5, 2026
75df902
feat: include protocol and curve in Cardano proof export
perturbing Aug 5, 2026
8045204
chore: tidy Cardano export helpers and new-file headers
perturbing Aug 5, 2026
901c320
docs: document --transcript option and Cardano export commands
perturbing Aug 5, 2026
3d5e2fe
build: rebuild bundles
perturbing Aug 5, 2026
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
679 changes: 553 additions & 126 deletions build/browser.esm.js

Large diffs are not rendered by default.

605 changes: 526 additions & 79 deletions build/cli.cjs

Large diffs are not rendered by default.

524 changes: 453 additions & 71 deletions build/main.cjs

Large diffs are not rendered by default.

698 changes: 564 additions & 134 deletions build/snarkjs.js

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions build/snarkjs.min.js

Large diffs are not rendered by default.

94 changes: 82 additions & 12 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import * as plonk from "./src/plonk.js";
import * as fflonk from "./src/fflonk.js";
import * as wtns from "./src/wtns.js";
import * as curves from "./src/curves.js";
import exportCardanoProof from "./src/export_cardano_proof.js";
import path from "path";
import bfj from "bfj";

Expand Down Expand Up @@ -253,6 +254,30 @@ const commands = [
alias: ["zkesc", "generatecall -pub|public -p|proof"],
action: zkeyExportSolidityCalldata
},
{
cmd: "zkey export cardano-verificationkey [circuit_final.zkey] [cardano_vk.json]",
description: "Exports a Cardano/Plutus verification key with ZCash-compressed BLS12-381 points",
alias: ["zkecv"],
action: zkeyExportCardanoVKey
},
{
cmd: "groth16 export-cardano-proof [proof.json] [cardano_proof.json]",
description: "Converts a Groth16 proof to Cardano/Plutus format (compressed BLS12-381 points)",
alias: ["g16ecp"],
action: groth16ExportCardanoProof
},
{
cmd: "plonk export-cardano-proof [proof.json] [cardano_proof.json]",
description: "Converts a PLONK proof to Cardano/Plutus format (compressed BLS12-381 points)",
alias: ["pkecp"],
action: plonkExportCardanoProof
},
{
cmd: "fflonk export-cardano-proof [proof.json] [cardano_proof.json]",
description: "Converts a FFLONK proof to Cardano/Plutus format (compressed BLS12-381 points)",
alias: ["ffecp"],
action: fflonkExportCardanoProof
},
{
cmd: "groth16 setup [circuit.r1cs] [powersoftau.ptau] [circuit_0000.zkey]",
description: "Creates an initial groth16 pkey file with zero contributions",
Expand Down Expand Up @@ -291,21 +316,21 @@ const commands = [
cmd: "plonk prove [circuit.zkey] [witness.wtns] [proof.json] [public.json]",
description: "Generates a PLONK Proof from witness",
alias: ["pkp"],
options: "-verbose|v -protocol",
options: "-verbose|v -protocol -transcript",
Comment thread
perturbing marked this conversation as resolved.
action: plonkProve
},
{
cmd: "plonk fullprove [input.json] [circuit.wasm] [circuit.zkey] [proof.json] [public.json]",
description: "Generates a PLONK Proof from input",
alias: ["pkf"],
options: "-verbose|v -protocol",
options: "-verbose|v -protocol -transcript",
action: plonkFullProve
},
{
cmd: "plonk verify [verification_key.json] [public.json] [proof.json]",
description: "Verify a PLONK Proof",
alias: ["pkv"],
options: "-verbose|v",
options: "-verbose|v -transcript",
action: plonkVerify
},
{
Expand All @@ -319,21 +344,21 @@ const commands = [
cmd: "fflonk prove [circuit.zkey] [witness.wtns] [proof.json] [public.json]",
description: "BETA version. Generates a FFLONK Proof from witness",
alias: ["ffp"],
options: "-verbose|v -protocol",
options: "-verbose|v -protocol -transcript",
action: fflonkProve
},
{
cmd: "fflonk fullprove [witness.json] [circuit.wasm] [circuit.zkey] [proof.json] [public.json]",
description: "BETA version. Generates a witness and the FFLONK Proof in the same command",
alias: ["fff"],
options: "-verbose|v -protocol",
options: "-verbose|v -protocol -transcript",
action: fflonkFullProve
},
{
cmd: "fflonk verify [verification_key.json] [public.json] [proof.json]",
description: "BETA version. Verify a FFLONK Proof",
alias: ["ffv"],
options: "-verbose|v",
options: "-verbose|v -transcript",
action: fflonkVerify
},
{
Expand Down Expand Up @@ -1141,7 +1166,7 @@ async function plonkProve(params, options) {

if (options.verbose) Logger.setLogLevel("DEBUG");

const {proof, publicSignals} = await plonk.prove(zkeyName, witnessName, logger);
const {proof, publicSignals} = await plonk.prove(zkeyName, witnessName, logger, { transcript: options.transcript });

await bfj.write(proofName, stringifyBigInts(proof), {space: 1});
await bfj.write(publicName, stringifyBigInts(publicSignals), {space: 1});
Expand All @@ -1163,7 +1188,7 @@ async function plonkFullProve(params, options) {

const input = JSON.parse(await fs.promises.readFile(inputName, "utf8"));

const {proof, publicSignals} = await plonk.fullProve(input, wasmName, zkeyName, logger);
const {proof, publicSignals} = await plonk.fullProve(input, wasmName, zkeyName, logger, undefined, { transcript: options.transcript });

await bfj.write(proofName, stringifyBigInts(proof), {space: 1});
await bfj.write(publicName, stringifyBigInts(publicSignals), {space: 1});
Expand All @@ -1185,7 +1210,7 @@ async function plonkVerify(params, options) {

if (options.verbose) Logger.setLogLevel("DEBUG");

const isValid = await plonk.verify(verificationKey, pub, proof, logger);
const isValid = await plonk.verify(verificationKey, pub, proof, logger, { transcript: options.transcript });

if (isValid) {
return 0;
Expand Down Expand Up @@ -1214,7 +1239,7 @@ async function fflonkProve(params, options) {

if (options.verbose) Logger.setLogLevel("DEBUG");

const {proof, publicSignals} = await fflonk.prove(zkeyFilename, witnessFilename, logger);
const {proof, publicSignals} = await fflonk.prove(zkeyFilename, witnessFilename, logger, { transcript: options.transcript });

if(undefined !== proofFilename && undefined !== publicInputsFilename) {
// Write the proof and the publig signals in each file
Expand All @@ -1237,7 +1262,7 @@ async function fflonkFullProve(params, options) {

const input = JSON.parse(await fs.promises.readFile(witnessInputsFilename, "utf8"));

const {proof, publicSignals} = await fflonk.fullProve(input, wasmFilename, zkeyFilename, logger);
const {proof, publicSignals} = await fflonk.fullProve(input, wasmFilename, zkeyFilename, logger, undefined, { transcript: options.transcript });

// Write the proof and the publig signals in each file
await bfj.write(proofFilename, stringifyBigInts(proof), {space: 1});
Expand All @@ -1257,11 +1282,56 @@ async function fflonkVerify(params, options) {
const publicInputs = JSON.parse(fs.readFileSync(publicInputsFilename, "utf8"));
const proof = JSON.parse(fs.readFileSync(proofFilename, "utf8"));

const isValid = await fflonk.verify(vkey, publicInputs, proof, logger);
const isValid = await fflonk.verify(vkey, publicInputs, proof, logger, { transcript: options.transcript });

return isValid ? 0 : 1;
}

async function zkeyExportCardanoVKey(params, options) {
const zKeyFileName = params[0] || "circuit_final.zkey";
const vKeyFilename = params[1] || "cardano_vk.json";

if (options.verbose) Logger.setLogLevel("DEBUG");

const vKey = await zkey.exportCardanoVerificationKey(zKeyFileName, logger);
await bfj.write(vKeyFilename, vKey, {space: 1});

return 0;
}

async function groth16ExportCardanoProof(params, options) {
Comment thread
perturbing marked this conversation as resolved.
Outdated
const proofFilename = params[0] || "proof.json";
const outFilename = params[1] || "cardano_proof.json";

const proof = JSON.parse(fs.readFileSync(proofFilename, "utf8"));
const cardanoProof = await exportCardanoProof(proof);
await bfj.write(outFilename, cardanoProof, {space: 1});

return 0;
}

async function plonkExportCardanoProof(params, options) {
const proofFilename = params[0] || "proof.json";
const outFilename = params[1] || "cardano_proof.json";

const proof = JSON.parse(fs.readFileSync(proofFilename, "utf8"));
const cardanoProof = await exportCardanoProof(proof);
await bfj.write(outFilename, cardanoProof, {space: 1});

return 0;
}

async function fflonkExportCardanoProof(params, options) {
const proofFilename = params[0] || "proof.json";
const outFilename = params[1] || "cardano_proof.json";

const proof = JSON.parse(fs.readFileSync(proofFilename, "utf8"));
const cardanoProof = await exportCardanoProof(proof);
await bfj.write(outFilename, cardanoProof, {space: 1});

return 0;
}

async function fileInfo(params) {
const filename = params[0];
const extension = filename.split(".").pop();
Expand Down
3 changes: 3 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ export * as zKey from "./src/zkey.js";
export * as plonk from "./src/plonk.js";
export * as fflonk from "./src/fflonk.js";
export * as curves from "./src/curves.js";
export { Keccak256Transcript } from "./src/Keccak256Transcript.js";
export { Keccak256CompressedTranscript } from "./src/Keccak256CompressedTranscript.js";
export { default as exportCardanoProof } from "./src/export_cardano_proof.js";
98 changes: 98 additions & 0 deletions src/Keccak256CompressedTranscript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
Copyright 2022 iden3 association.

This file is part of snarkjs.

snarkjs is a free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your option)
any later version.

snarkjs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public License along with
snarkjs. If not, see <https://www.gnu.org/licenses/>.
*/

// Fiat-Shamir transcript using Keccak-256 with compressed G1 points (ZCash/IETF flag format).
// Use this instead of Keccak256Transcript when the on-chain verifier operates on compressed
// BLS12-381 points, e.g. Cardano/Plutus via CIP-0381 + CIP-0101.

import {Scalar} from "ffjavascript";
import {keccak_256} from "@noble/hashes/sha3";

const POLYNOMIAL = 0;
const SCALAR = 1;

export class Keccak256CompressedTranscript {
constructor(curve) {
this.G1 = curve.G1;
this.Fr = curve.Fr;

this.reset();
}

reset() {
this.data = [];
}

addPolCommitment(polynomialCommitment) {
this.data.push({type: POLYNOMIAL, data: polynomialCommitment});
}

addScalar(scalar) {
this.data.push({type: SCALAR, data: scalar});
}

getChallenge() {
if (0 === this.data.length) {
throw new Error("Keccak256CompressedTranscript: No data to generate a transcript");
}

let nPolynomials = 0;
let nScalars = 0;

this.data.forEach(element => POLYNOMIAL === element.type ? nPolynomials++ : nScalars++);

// Compressed G1 points are G1.F.n8 bytes (vs 2*n8 for uncompressed).
let buffer = new Uint8Array(nScalars * this.Fr.n8 + nPolynomials * this.G1.F.n8);
let offset = 0;

for (let i = 0; i < this.data.length; i++) {
if (POLYNOMIAL === this.data[i].type) {
this.G1.toRprCompressed(buffer, offset, this.data[i].data);

// Apply ZCash/IETF compressed-point flags in the three high bits:
// bit 7 (0x80): compression flag — always set for compressed encoding
// bit 6 (0x40): infinity flag
// bit 5 (0x20): sign flag — set when y is the lexicographically larger root
// The top three bits of a valid BLS12-381 field element are always 0,
Comment thread
perturbing marked this conversation as resolved.
Outdated
// so OR-ing the flags into buffer[offset] is safe.
const point = this.G1.toAffine(this.data[i].data);
const pointNeg = this.G1.toAffine(this.G1.neg(this.data[i].data));
const y = this.G1.toObject(point)[1];
const yNeg = this.G1.toObject(pointNeg)[1];

let mask;
if (this.G1.isZero(this.data[i].data)) {
mask = 0b11000000; // compression + infinity flags
} else if (y >= yNeg) {
mask = 0b10100000; // compression + sign flags (y is the larger root)
} else {
mask = 0b10000000; // compression flag only (y is the smaller root)
}
buffer[offset] = buffer[offset] | mask;
offset += this.G1.F.n8;
} else {
this.Fr.toRprBE(buffer, offset, this.data[i].data);
offset += this.Fr.n8;
}
}

const value = Scalar.fromRprBE(keccak_256(buffer));
return this.Fr.e(value);
}
}
Loading