Skip to content

Instantly share code, notes, and snippets.

@yann300
Created November 8, 2023 20:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yann300/78f346e334d897a91fe2e557185111f5 to your computer and use it in GitHub Desktop.
Save yann300/78f346e334d897a91fe2e557185111f5 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.21+commit.d9974bed.js&optimize=false&runs=200&gist=
CIRCOM ZKP SEMAPHORE WORKSPACE
Welcome to the Remix Circom ZKP Hash Checker Workspace.
The workspace comprises two main directories:
- circuits: Contains sample hash checker circuit. These can be compiled to generate a witness using 'Circom ZKP Compiler' plugin.
- scripts: Provides a sample script designed for a trusted setup using snarkjs. This script also aids
in generating Solidity code, which is essential for on-chain deployment.
pragma circom 2.0.0;
include "circomlib/poseidon.circom";
template CalculateHash() {
signal input value1;
signal input value2;
signal input value3;
signal input value4;
signal output out;
component poseidon = Poseidon(4);
poseidon.inputs[0] <== value1;
poseidon.inputs[1] <== value2;
poseidon.inputs[2] <== value3;
poseidon.inputs[3] <== value4;
out <== poseidon.out;
}
template HashChecker() {
signal input value1;
signal input value2;
signal input value3;
signal input value4;
signal input hash;
component calculateSecret = CalculateHash();
calculateSecret.value1 <== value1;
calculateSecret.value2 <== value2;
calculateSecret.value3 <== value3;
calculateSecret.value4 <== value4;
signal calculatedHash;
calculatedHash <== calculateSecret.out;
assert(hash == calculatedHash);
}
component main {public [value1, value2, value3, value4]} = HashChecker();
import { ethers, BigNumber } from 'ethers'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const snarkjs = require('snarkjs');
const logger = {
info: (...args) => console.log(...args),
debug: (...args) => console.log(...args)
};
/**
* Creates a keccak256 hash of a message compatible with the SNARK scalar modulus.
* @param message The message to be hashed.
* @returns The message digest.
*/
function hash(message: any): bigint {
message = BigNumber.from(message).toTwos(256).toHexString()
message = ethers.utils.zeroPad(message, 32)
return BigInt(ethers.utils.keccak256(message)) >> BigInt(8)
}
(async () => {
try {
// @ts-ignore
await remix.call('circuit-compiler', 'generateR1cs', 'circuits/calculate_hash.circom');
const ptau_final = "https://ipfs-cluster.ethdevops.io/ipfs/QmTiT4eiYz5KF7gQrDsgfCSTRv3wBPYJ4bRN1MmTRshpnW";
// @ts-ignore
const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash.r1cs', true);
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer);
const zkey_0 = { type: "mem" };
const zkey_1 = { type: "mem" };
const zkey_final = { type: "mem" };
console.log('newZkey')
await snarkjs.zKey.newZKey(r1cs, ptau_final, zkey_0);
console.log('contribute')
await snarkjs.zKey.contribute(zkey_0, zkey_1, "p2_C1", "pa_Entropy1");
console.log('beacon')
await snarkjs.zKey.beacon(zkey_1, zkey_final, "B3", "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", 10);
console.log('verifyFromR1cs')
const verifyFromR1csResult = await snarkjs.zKey.verifyFromR1cs(r1cs, ptau_final, zkey_final);
console.assert(verifyFromR1csResult);
console.log('verifyFromInit')
const verifyFromInit = await snarkjs.zKey.verifyFromInit(zkey_0, ptau_final, zkey_final);
console.assert(verifyFromInit);
console.log('exportVerificationKey')
const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final)
await remix.call('fileManager', 'writeFile', './zk/build/verification_key.json', JSON.stringify(vKey))
const templates = {
groth16: await remix.call('fileManager', 'readFile', 'templates/groth16_verifier.sol.ejs')
}
const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates)
await remix.call('fileManager', 'writeFile', './zk/build/zk_verifier.sol', solidityContract)
console.log('buffer', (zkey_final as any).data.length)
await remix.call('fileManager', 'writeFile', './zk/build/zk_setup.txt', JSON.stringify(Array.from(((zkey_final as any).data))))
console.log('setup done.')
} catch (e) {
console.error(e.message)
}
})()
import { poseidon } from "circomlibjs" // v0.0.8
// eslint-disable-next-line @typescript-eslint/no-var-requires
const snarkjs = require('snarkjs');
const logger = {
info: (...args) => console.log(...args),
debug: (...args) => console.log(...args),
error: (...args) => console.error(...args),
}
/**
* Creates a keccak256 hash of a message compatible with the SNARK scalar modulus.
* @param message The message to be hashed.
* @returns The message digest.
*/
function hash(message: any): bigint {
message = BigNumber.from(message).toTwos(256).toHexString()
message = ethers.utils.zeroPad(message, 32)
return BigInt(ethers.utils.keccak256(message)) >> BigInt(8)
}
(async () => {
try {
// @ts-ignore
const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash.r1cs', true);
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer);
// @ts-ignore
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash.wasm', true);
// @ts-ignore
const wasm = new Uint8Array(wasmBuffer);
const zkey_final = {
type: "mem",
data: new Uint8Array(JSON.parse(await remix.call('fileManager', 'readFile', './zk/build/zk_setup.txt')))
}
const wtns = { type: "mem" };
const vKey = JSON.parse(await remix.call('fileManager', 'readFile', './zk/build/verification_key.json'))
const value1 = '1234'
const value2 = '2'
const value3 = '3'
const value4 = '4'
const wrongValue = '5' // put this in the poseidon hash calculation to simulate a non matching hash.
const signals = {
value1,
value2,
value3,
value4,
hash: poseidon([value1, value2, value3, value4])
}
console.log('calculate')
await snarkjs.wtns.calculate(signals, wasm, wtns);
console.log('check')
await snarkjs.wtns.check(r1cs, wtns, logger);
console.log('prove')
const { proof, publicSignals } = await snarkjs.groth16.prove(zkey_final, wtns);
const verified = await snarkjs.groth16.verify(vKey, publicSignals, proof, logger);
console.log('zk proof validity', verified);
} catch (e) {
console.error(e.message)
}
})()
// SPDX-License-Identifier: GPL-3.0
/*
Copyright 2021 0KIMS association.
This file is generated with [snarkJS](https://github.com/iden3/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/>.
*/
pragma solidity >=0.7.0 <0.9.0;
contract Groth16Verifier {
// Scalar field size
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// Base field size
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
// Verification Key data
uint256 constant alphax = <%= vk_alpha_1[0] %>;
uint256 constant alphay = <%= vk_alpha_1[1] %>;
uint256 constant betax1 = <%= vk_beta_2[0][1] %>;
uint256 constant betax2 = <%= vk_beta_2[0][0] %>;
uint256 constant betay1 = <%= vk_beta_2[1][1] %>;
uint256 constant betay2 = <%= vk_beta_2[1][0] %>;
uint256 constant gammax1 = <%= vk_gamma_2[0][1] %>;
uint256 constant gammax2 = <%= vk_gamma_2[0][0] %>;
uint256 constant gammay1 = <%= vk_gamma_2[1][1] %>;
uint256 constant gammay2 = <%= vk_gamma_2[1][0] %>;
uint256 constant deltax1 = <%= vk_delta_2[0][1] %>;
uint256 constant deltax2 = <%= vk_delta_2[0][0] %>;
uint256 constant deltay1 = <%= vk_delta_2[1][1] %>;
uint256 constant deltay2 = <%= vk_delta_2[1][0] %>;
<% for (let i=0; i<IC.length; i++) { %>
uint256 constant IC<%=i%>x = <%=IC[i][0]%>;
uint256 constant IC<%=i%>y = <%=IC[i][1]%>;
<% } %>
// Memory data
uint16 constant pVk = 0;
uint16 constant pPairing = 128;
uint16 constant pLastMem = 896;
function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[<%=IC.length-1%>] calldata _pubSignals) public view returns (bool) {
assembly {
function checkField(v) {
if iszero(lt(v, q)) {
mstore(0, 0)
return(0, 0x20)
}
}
// G1 function to multiply a G1 value(x,y) to value in an address
function g1_mulAccC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn, 32), y)
mstore(add(mIn, 64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
mstore(add(mIn, 64), mload(pR))
mstore(add(mIn, 96), mload(add(pR, 32)))
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
}
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
let _pPairing := add(pMem, pPairing)
let _pVk := add(pMem, pVk)
mstore(_pVk, IC0x)
mstore(add(_pVk, 32), IC0y)
// Compute the linear combination vk_x
<% for (let i = 1; i <= nPublic; i++) { %>
g1_mulAccC(_pVk, IC<%=i%>x, IC<%=i%>y, calldataload(add(pubSignals, <%=(i-1)*32%>)))
<% } %>
// -A
mstore(_pPairing, calldataload(pA))
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
// B
mstore(add(_pPairing, 64), calldataload(pB))
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
// alpha1
mstore(add(_pPairing, 192), alphax)
mstore(add(_pPairing, 224), alphay)
// beta2
mstore(add(_pPairing, 256), betax1)
mstore(add(_pPairing, 288), betax2)
mstore(add(_pPairing, 320), betay1)
mstore(add(_pPairing, 352), betay2)
// vk_x
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
// gamma2
mstore(add(_pPairing, 448), gammax1)
mstore(add(_pPairing, 480), gammax2)
mstore(add(_pPairing, 512), gammay1)
mstore(add(_pPairing, 544), gammay2)
// C
mstore(add(_pPairing, 576), calldataload(pC))
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
// delta2
mstore(add(_pPairing, 640), deltax1)
mstore(add(_pPairing, 672), deltax2)
mstore(add(_pPairing, 704), deltay1)
mstore(add(_pPairing, 736), deltay2)
let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
isOk := and(success, mload(_pPairing))
}
let pMem := mload(0x40)
mstore(0x40, add(pMem, pLastMem))
// Validate that all evaluations ∈ F
<% for (let i=0; i<IC.length; i++) { %>
checkField(calldataload(add(_pubSignals, <%=i*32%>)))
<% } %>
// Validate all evaluations
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)
mstore(0, isValid)
return(0, 0x20)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment