Skip to content

Commit

Permalink
🚀 Deploy Groth16Verifier to Sepolia (#198)
Browse files Browse the repository at this point in the history
* Generate Groth16Verifier.sol

* chore: forge init

* Deploy Groth16Verifier to Sepolia

* Add `generate-sol` script

* Add `generate-calldata` script

* format

* Define `verify.local` and `verify.onchain` npm scripts

* Update README
  • Loading branch information
sripwoud committed Jun 13, 2023
1 parent a205245 commit ca0f581
Show file tree
Hide file tree
Showing 19 changed files with 420 additions and 50 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ overrides:
'@typescript-eslint/no-misused-promises': off
- files:
[
'demo/src/cli/index.ts',
'apis/*/src/index.ts',
'apis/prove/generated/*.js',
'apis/prove/src/mq/*.ts',
'circuits/circom/scripts/*.ts',
'demo/src/index.ts',
'demo/src/cli/index.ts',
]
rules:
no-console: off
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ coverage
dist
build
node_modules
out

*.log
tsconfig.tsbuildinfo
circuits/circom/cache

.env*

Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "circuits/circom/lib/forge-std"]
path = circuits/circom/lib/forge-std
url = https://github.com/foundry-rs/forge-std
25 changes: 24 additions & 1 deletion circuits/circom/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
[circuit stored on IPFS](https://bafybeidvy5mfoxm57sze7juqnzafvkzpbspj4j2tdd3g6n6wss4o4mgnhe.ipfs.w3s.link/)
# Circom Circuits

## `.zkey` file

[Stored on IPFS](https://bafybeidvy5mfoxm57sze7juqnzafvkzpbspj4j2tdd3g6n6wss4o4mgnhe.ipfs.w3s.link/)

## Solidity Verifier

## Deployment

1. Set `SEPOLIA_RPC_URL` and `VERIFIER_DEPLOYER_PRIVATEKEY`, `ETHERSCAN_API_KEY` environment variables.
2. `pnpm deploy.sepolia`

## Deployed contracts

| Network | Address |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Sepolia | [`0x893f293e3918a179bf87fb772206e9927db61b0c`](https://sepolia.etherscan.io/address/0x893f293e3918a179bf87fb772206e9927db61b0c#code) |

## Verification scripts

- `pnpm verify.local`
- `pnpm verify.onchain`
- `pnpm generate-calldata`
7 changes: 7 additions & 0 deletions circuits/circom/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[profile.default]
src = "generated"
out = "out"
libs = ["node_modules"]
remappings = ["play-contracts/=node_modules/play-contracts/"]

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
198 changes: 198 additions & 0 deletions circuits/circom/generated/Groth16Verifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// 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 = 20491192805390485299153009773594534940189261866228447918068658471970481763042;
uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958;
uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132;
uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731;
uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679;
uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856;
uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
uint256 constant deltax1 = 2585261721879365932300297402926320099793018688169530309287082840487789731743;
uint256 constant deltax2 = 7557095872897644921759750309236066110681591963328276958365184487727942977384;
uint256 constant deltay1 = 21219585362573197963046338548656788412468519114640293735397923738407446979124;
uint256 constant deltay2 = 9689161368389106761777611967003662439915950766131105424166713268072538716933;


uint256 constant IC0x = 15378369537707264939263012273103364806213377581448269315521635172522072433169;
uint256 constant IC0y = 8244706524651740812258441209265733043489087637457491139477547416234965412250;

uint256 constant IC1x = 5268129217668546846176047734506919040398913361196690774171619214821302194328;
uint256 constant IC1y = 20498558697224297068438830912114159918250085963563409522029547416501980743564;

uint256 constant IC2x = 18267622522246942977993095729159783481216871499337636542083874298090901078680;
uint256 constant IC2y = 18073186125325070112114279569374127154471967602121799425533714510160303267109;

uint256 constant IC3x = 15097381742252683565102752741266011361607873201723190028580008973246746757824;
uint256 constant IC3y = 3656879294956228684080223317008893793228491657300197015647614146542484614073;

uint256 constant IC4x = 15079904671097387635239451225226304620736246954574123371221895853669801688488;
uint256 constant IC4y = 16446309530230169021713718914880148092213363709571546199466064851457314405846;

uint256 constant IC5x = 20815718287486358263204708897895389953308268671685097372687009787516053051191;
uint256 constant IC5y = 14231404347608211118449829347751581876279288989746622009710573788582567415535;


// 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[5] 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

g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))

g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))

g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64)))

g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96)))

g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128)))


// -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

checkField(calldataload(add(_pubSignals, 0)))

checkField(calldataload(add(_pubSignals, 32)))

checkField(calldataload(add(_pubSignals, 64)))

checkField(calldataload(add(_pubSignals, 96)))

checkField(calldataload(add(_pubSignals, 128)))

checkField(calldataload(add(_pubSignals, 160)))


// Validate all evaluations
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)

mstore(0, isValid)
return(0, 0x20)
}
}
}
1 change: 1 addition & 0 deletions circuits/circom/lib/forge-std
Submodule forge-std added at d78a4e
16 changes: 13 additions & 3 deletions circuits/circom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,27 @@
"devDependencies": {
"@anonset/membership": "workspace:^",
"@noble/secp256k1": "^1.7.0",
"@types/inquirer": "^9.0.3",
"circom-ecdsa": "github:0xparc/circom-ecdsa",
"circom_tester": "^0.0.19",
"circomlib": "^2.0.5",
"circomlibjs": "^0.1.7",
"ethers": "^5.7.2",
"ffjavascript": "^0.2.57",
"inquirer": "^8",
"inquirer-fuzzy-path": "^2.3.0",
"play-contracts": "github:ChihChengLiang/poseidon-tornado",
"snarkjs": "^0.7.0"
"snarkjs": "^0.7.0",
"terminal-link": "2.1.1",
"viem": "0.3.27"
},
"scripts": {
"test": "bash maybe-fix-circom-imports.sh && NODE_OPTIONS=--max_old_space_size=8192 jest -c test/jest.config.ts",
"typecheck": "tsc"
"generate-calldata": "tsnd scripts/generate-calldata",
"deploy.sepolia": "forge create --rpc-url $SEPOLIA_RPC_URL --private-key $VERIFIER_DEPLOYER_PRIVATEKEY --etherscan-api-key $ETHERSCAN_API_KEY --verify generated/Groth16Verifier.sol:Groth16Verifier",
"generatecall": "bash generatecall.sh",
"test": "bash scripts/maybe-fix-circom-imports.sh && NODE_OPTIONS=--max_old_space_size=8192 jest -c test/jest.config.ts",
"typecheck": "tsc",
"verify.local": "tsnd scripts/verify-local",
"verify.onchain": "tsnd scripts/verify-onchain"
}
}
7 changes: 7 additions & 0 deletions circuits/circom/scripts/_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createPublicClient, http } from 'viem'
import { sepolia } from 'viem/chains'

export const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
})
40 changes: 40 additions & 0 deletions circuits/circom/scripts/_prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import inquirer, { DistinctQuestion, QuestionCollection } from 'inquirer'
import inquirerFuzzyPath from 'inquirer-fuzzy-path'
import { join } from 'path'

inquirer.registerPrompt('fuzzypath', inquirerFuzzyPath)

export const prompt =
<T>(questions: QuestionCollection | DistinctQuestion) =>
async (): Promise<T> => {
if (questions instanceof Array)
return (await inquirer.prompt(questions as QuestionCollection)) as T

const { name } = questions as DistinctQuestion
if (name === undefined) throw new Error('Question must have a name')
const { [name]: answer } = await inquirer.prompt(questions)
return answer
}

const excludeRegex =
/(coverage|dist|Library|node_modules|turbo|package|tsconfig|\/\.\w+)/

const askFile = (fileName: string) => async () => {
const path = await prompt<string>({
depthLimit: 6,
excludeFilter: (path: string) => !path.endsWith('.json'),
excludePath: (path: string) => excludeRegex.test(path),
itemType: 'file',
message: `What is the path to your ${fileName} .json file?`,
name: 'file',
rootPath: join(__dirname, '..', '..'),
// @ts-expect-error
type: 'fuzzypath',
})()

return (await import(path)).default
}

export const askProofFile = askFile('proof')
export const askPublicSignalsFile = askFile('public signals')
export const askVerificationKeyFile = askFile('verification key')
10 changes: 10 additions & 0 deletions circuits/circom/scripts/_wrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const wrapExec = (exec: () => Promise<void>) => {
exec()
.then(() => {
process.exit(0)
})
.catch((reason) => {
console.error(reason)
process.exit(1)
})
}
34 changes: 34 additions & 0 deletions circuits/circom/scripts/generate-calldata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { groth16 } from 'snarkjs'
import terminalLink from 'terminal-link'
import { askProofFile, askPublicSignalsFile } from './_prompt'
import { wrapExec } from './_wrap'

const stringify = (input: string[]) =>
`${JSON.stringify(input).replace(/"/g, '')}`

const main = async () => {
const proof = await askProofFile()
const publicSignals = await askPublicSignalsFile()

const callDataStr: string = await groth16.exportSolidityCallData(
proof,
publicSignals,
)

const callData: [string[], string[], string[], string[]] = JSON.parse(
`[${callDataStr}]`,
)
console.log(
`Use the following inputs to perform a 'verifyProof' read call to the ${terminalLink(
'Groth16Verifier contract',
'https://sepolia.etherscan.io/address/0x893f293e3918a179bf87fb772206e9927db61b0c#readContract',
)}`,
)
console.log()
console.log(`_pA: ${stringify(callData[0])}`)
console.log(`_pB: ${stringify(callData[1])}`)
console.log(`_pC: ${stringify(callData[2])}`)
console.log(`publicSignals: ${stringify(callData[3])}`)
}

wrapExec(main)
File renamed without changes.
Loading

0 comments on commit ca0f581

Please sign in to comment.