Submit a Snarkjs FFlonk Proof
In this tutorial we will submit a fflonk proof generated by snarkjs.
Requirements
- A ZkVerify address with ACME funds.
- A snarkjs fflonk proof and verification key, otherwise you can follow the snarkjs tutorial to generate them.
- Rust toolchain installed.
Node.js
andyarn
Limitations
For now ZkVerify accepts only fflonk proofs generated with Bn128 Elliptic Curve and exactly one public input.
Submit proof
Assume you have the following files:
proof.json
: the proof in snarkjs format.verification_key.json
: the verification key in snarkjs formatpublic.json
: a json that consist of an array with just one number (we accept just one public input)
We should convert the proof in a ZkVerify valid format: a 780 bytes array where the first 768 ones are
the proof itself and the following 32 bytes are the public input value. To get the proof bytes from the snarkjs
json file we use fflonk_verifier
project that provide a
simple executable to convert the json proof in the needed array.
Convert the proof
fflonk_verifier
project provide some useful utilities to
convert proofs and verify them:
git clone https://github.com/HorizenLabs/fflonk_verifier
cd fflonk_verifier/
cargo install --features bins --path .
Now you can come back in the proof folder and convert it in a format that we can use to send it to ZkVerify:
proof-converter proof.json
This command will dump the proof bytes in the hexadecimal format that we can use to submit it.
Send the proof
Here we'll adapt a bit the polkadot.js simple transfer events example to send the proof and check if it's valid. First we initialize the project and add the needed dependencies:
mkdir send_proof
cd send_proof
yarn init
yarn add @polkadot/api
yarn add @polkadot/keyring
yarn add dotenv
yarn add bignumber.js
Now define a .env
file to write destination chain and our private key:
echo "WEBSOCKET=wss://testnet-rpc.zkverify.io
PRIVATE_KEY=your_private_key" > .env
and add the script section to project.json
"scripts": {
"start": "node index.js"
},
The index.js
file is not so different from the polkadot.js example:
const { ApiPromise, WsProvider } = require('@polkadot/api');
const { Keyring } = require('@polkadot/keyring');
require('dotenv').config();
const fs = require('node:fs');
const { BigNumber } = require('bignumber.js');
// Verification key json file path
const vkFile = process.argv[2];
// Hex proof bytes
const proof = process.argv[3];
// Public input value
const pub = process.argv[4];
async function main() {
// Use address from provided environment
const provider = new WsProvider(process.env.WEBSOCKET);
const api = await ApiPromise.create({ provider });
const keyring = new Keyring({ type: 'sr25519' });
// Here we load the snarkjs verification key and rename two field
// that have a different name in ZkVerify
const vk = {
...JSON.parse(fs.readFileSync(vkFile, 'utf8')),
get x2() { return this.X_2 },
get c0() { return this.C0 },
};
// Use the given private key
const account = keyring.addFromUri(process.env.PRIVATE_KEY);
// Concatenate the proof bytes to the public inputs ones
const submit = api.tx.settlementFFlonkPallet.submitProof(
{ 'Vk': vk },
proof,
"0x" + BigNumber(pub).toString(16).padStart(64, '0')
);
await submit.signAndSend(account, ({ txHash, status, dispatchError }) => {
if (status.isReady) {
console.log('Proof submitted with hash : ', txHash.toHex());
} else if (status.isInBlock) {
console.log(`Proof included in block`);
let exitCode = 0;
if (!dispatchError) {
console.log("Proof verified!!!");
} else {
console.log("Proof verification failed");
exitCode = 1;
}
process.exit(exitCode);
}
});
}
main().catch(console.error);
Now run it
yarn start \
snarkjs/proof/path/verification_key.json \
0x13650a0d5de7a7fdc871c494493929b40a7926afc0912292abaf2f2d5c6cc481\
094dcd8c9579795b9062039f67aa7084505b2a91bd5733b8e0ab356da13c4921\
0f0036bac3346e4701737cbab3d3bc98a3655e22cf15ea397bb0d1d0987f17a6\
17693abb6a68dc5aaaffefdf91bffaadedc5237e67d8c3d879f2e0974890e1be\
0cb3dc14a9786928657042d91de94da19d5a35cac333ddf78b2d2034601334a4\
24dcd0904f3dbc4334e8ba39fc611b282dea13a33b0e51495ddc29a4e1ca0418\
20192d266e98c3a9a6922136539a4ccf92f78f08bb5dbb4e3b8387dcdeacd38a\
1ffb0c26d7474cc4677e85ecc9b7384d8865c8d72363d5ba2c26ab42d0dc5a5a\
29d0695cde42cc4e1e47fe296502c6460e4abd26d27944ed6453d749a1dbc86b\
0969d93f17de7c46fa8d16d48305531eb65229d57c305510d921913987571088\
2a86fe0abd7f2b10ef733e2bdfbe4d8681289e318e617294cd556d500d363747\
23d175851438772e36a73fd89a8a00ce78b20b6a5a110e58bde7985a08349b13\
0000000000000000000000000000000000000000000000000000000000000000\
15351d6ddffe12be3d14373fda2b73ec3a4a42bf24e8127a5a1acddf09adb8b6\
23472d76e75fd536b78927b50977e4436afee3ad52112abb47f6ec802a9a231d\
23fa8d4b61305859389e7bdc5daa16d4e58e54303d6e15f16bc3188dd32eecfe\
1ea9e431a07f57ccc8468d85b66fac6471d582e5a10963ba5164772c06052c76\
0e43f716b264c0a128d12d7c0530b20b7c199db68ff8dbdd5b13da046017b2b2\
12e9e8ccd5408f2a4b815a057469633cf9d724550b17aa531cf9e294d3363786\
1445b0aa0c9b69fd28d28de56dc5c8d513fc0e4e0e8b51060208525bc969183c\
14ea898cc07f20b9ae5d8f86d1cc2f5cfcf9b6105c302763818144783f3a21b1\
27232541d9ada744f797f3d27423d6923d191592da4c77f3362f3d45dac1c1a1\
085d01ef359cb02cbb12c7e24f80e90a4a4ed4f8802b5930f80fc5ff50333989\
125f70f842b24812398693692de0eea8368ca1345564b2b5e8438e60a9328192 \
7713112592372404476342535432037683616424591277138491596200192981572885523208
Will give the follow output
[...]
Proof submitted with hash : 0x2180e6fcd07252f92f8d47a7f4f2aa502aabdb64945fd3e36e0da6945e853025
Proof included in block
Proof verified!!!
Done in 4.82s.