Created
November 8, 2023 20:11
-
-
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=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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