├── web ├── zkeys │ ├── NftMint__prod.wasm │ ├── NftMint__prod.0.zkey │ └── verification_key.json ├── tsconfig.json ├── package.json └── src │ ├── index.html │ ├── app.ts │ └── witness_calculator.js ├── lerna.json ├── circuits ├── circom │ ├── test │ │ └── NftMint_test.circom │ └── NftMint.circom ├── tsconfig.json ├── jest.config.js ├── ts │ ├── __tests__ │ │ └── NftMint.test.ts │ ├── index.ts │ └── exportVerifier.ts ├── package.json ├── zkeys.config.yml ├── verifierTemplate.sol └── verifier.sol ├── contracts ├── tsconfig.json ├── contracts │ ├── SnarkConstants.sol │ ├── Hasher.sol │ └── NftMint.sol ├── scripts │ └── compileSol.sh ├── ts │ ├── deploy.ts │ ├── buildPoseidon.ts │ ├── index.ts │ └── __tests__ │ │ └── NftMint.test.ts ├── jest.config.js ├── hardhat.config.js ├── package.json └── cache │ └── solidity-files-cache.json ├── .gitignore ├── package.json ├── prepare_web.sh ├── tsconfig.json └── README.md /web/zkeys/NftMint__prod.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/zknftmint/HEAD/web/zkeys/NftMint__prod.wasm -------------------------------------------------------------------------------- /web/zkeys/NftMint__prod.0.zkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/zknftmint/HEAD/web/zkeys/NftMint__prod.0.zkey -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages":[ 3 | "contracts", 4 | "web", 5 | "circuits" 6 | ], 7 | "version":"1.0.0" 8 | } 9 | -------------------------------------------------------------------------------- /circuits/circom/test/NftMint_test.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.0; 2 | 3 | include "../NftMint.circom"; 4 | 5 | /*component main = NftMint();*/ 6 | component main {public [hash, address]} = NftMint(); 7 | -------------------------------------------------------------------------------- /circuits/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./build" 5 | }, 6 | "include": [ 7 | "./ts" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./build" 5 | }, 6 | "include": [ 7 | "./ts" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "checkJs": false, 5 | "allowJs": true, 6 | "esModuleInterop": true 7 | }, 8 | "include": ["src/**/*"], 9 | "exclude": ["node_modules/**", "tests/**"] 10 | } 11 | 12 | -------------------------------------------------------------------------------- /contracts/contracts/SnarkConstants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract SnarkConstants { 5 | // The scalar field 6 | uint256 internal constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 7 | } 8 | -------------------------------------------------------------------------------- /contracts/scripts/compileSol.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o pipefail 3 | 4 | cd "$(dirname "$0")" 5 | cd .. 6 | 7 | # Delete old files 8 | rm -rf ./artifacts/* 9 | rm -rf ./cache/* 10 | rm -rf ./dist/* 11 | rm -rf ./typechain/* 12 | 13 | echo 'Building contracts with Hardhat' 14 | npx hardhat compile 15 | 16 | # Build the Poseidon contract from bytecode 17 | #npm run build 18 | #node build/buildPoseidon.js 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | circuits/zkeys/* 2 | contracts/artifacts 3 | contracts/cache 4 | *.wtns 5 | node_modules 6 | circomHelperConfig.json 7 | circuits/circomHelperConfig.json 8 | circuits/zkeys 9 | **/build/ 10 | contracts/dist 11 | contracts/contracts/verifier.sol 12 | circuits/vk.json 13 | 14 | **/MyLogFile.log 15 | input.json 16 | proof.json 17 | public.json 18 | 19 | lerna-debug.log 20 | 21 | **/.parcel-cache/* 22 | **/dist/ 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zknftmint", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "lerna run build", 8 | "bootstrap": "lerna bootstrap", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "weijiekoh", 12 | "devDependencies": { 13 | "lerna": "^4.0.0" 14 | }, 15 | "dependencies": { 16 | "typescript": "^4.4.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/ts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getDefaultSigner, 3 | deployVerifier, 4 | deployNftMint, 5 | } from './' 6 | 7 | const main = async () => { 8 | const signer = await getDefaultSigner() 9 | 10 | const verifierContract = await deployVerifier() 11 | await verifierContract.deployTransaction.wait() 12 | 13 | const nftMintContract = await deployNftMint( 14 | verifierContract.address 15 | ) 16 | await nftMintContract.deployTransaction.wait() 17 | } 18 | 19 | main() 20 | -------------------------------------------------------------------------------- /prepare_web.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o pipefail 4 | 5 | cd "$(dirname "$0")" 6 | 7 | npm i 8 | npm run bootstrap 9 | npm run build 10 | 11 | cd circuits 12 | 13 | npx zkey-manager compile -c ./zkeys.config.yml 14 | npx zkey-manager downloadPtau -c ./zkeys.config.yml 15 | npx zkey-manager genZkeys -c ./zkeys.config.yml 16 | npx snarkjs zkev ./zkeys/NftMint__prod.0.zkey ./zkeys/verification_key.json 17 | 18 | cd ../web 19 | mkdir -p zkeys 20 | 21 | cp ../circuits/zkeys/NftMint__prod_js/NftMint__prod.wasm ./zkeys/ 22 | cp ../circuits/zkeys/NftMint__prod.0.zkey ./zkeys/ 23 | cp ../circuits/zkeys/verification_key.json ./zkeys/ 24 | -------------------------------------------------------------------------------- /contracts/ts/buildPoseidon.ts: -------------------------------------------------------------------------------- 1 | const { ethers, overwriteArtifact } = require('hardhat') 2 | const circomlib = require('circomlibjs') 3 | 4 | const buildPoseidon = async (numInputs: number) => { 5 | await overwriteArtifact( 6 | `PoseidonT${numInputs + 1}`, 7 | circomlib.poseidon_gencontract.createCode(numInputs) 8 | ) 9 | } 10 | 11 | const buildPoseidonT2 = () => buildPoseidon(1) 12 | const buildPoseidonT3 = () => buildPoseidon(2) 13 | 14 | if (require.main === module) { 15 | buildPoseidonT2() 16 | buildPoseidonT3() 17 | } 18 | 19 | export { 20 | buildPoseidonT2, 21 | buildPoseidonT3, 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "alwaysStrict": true, 5 | "allowJs": true, 6 | "noImplicitAny": false, 7 | "forceConsistentCasingInFileNames": true, 8 | "noUnusedLocals": false, 9 | "noUnusedParameters": false, 10 | "esModuleInterop": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "outDir": "./build" 16 | }, 17 | "exclude": [ 18 | "node_modules/**" 19 | ], 20 | "include": [ 21 | "./ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zknftmint-web", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "serve": "parcel serve src/index.html", 7 | "serve-static": "cd zkeys && http-server --cors -p 8000" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/zknftmint/project.git" 12 | }, 13 | "author": "weijiekoh", 14 | "bugs": { 15 | "url": "https://github.com/zknftmint/project/issues" 16 | }, 17 | "homepage": "https://github.com/zknftmint/project#readme", 18 | "dependencies": { 19 | "circomlibjs": "^0.1.1", 20 | "parcel": "2.0.1", 21 | "snarkjs": "0.4.10" 22 | }, 23 | "devDependencies": { 24 | "http-server": "14.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /circuits/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | transform: { 4 | "^.+\\.tsx?$": 'ts-jest' 5 | }, 6 | testPathIgnorePatterns: [ 7 | "/build/", 8 | "/node_modules/", 9 | ], 10 | testRegex: '/__tests__/.*\\.test\\.ts$', 11 | moduleFileExtensions: [ 12 | 'ts', 13 | 'tsx', 14 | 'js', 15 | 'jsx', 16 | 'json', 17 | 'node' 18 | ], 19 | globals: { 20 | 'ts-jest': { 21 | diagnostics: { 22 | // Do not fail on TS compilation errors 23 | // https://kulshekhar.github.io/ts-jest/user/config/diagnostics#do-not-fail-on-first-error 24 | warnOnly: true 25 | } 26 | } 27 | }, 28 | testEnvironment: 'node' 29 | } -------------------------------------------------------------------------------- /contracts/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | transform: { 4 | "^.+\\.tsx?$": 'ts-jest' 5 | }, 6 | testPathIgnorePatterns: [ 7 | "/build/", 8 | "/node_modules/", 9 | ], 10 | testRegex: '/__tests__/.*\\.test\\.ts$', 11 | moduleFileExtensions: [ 12 | 'ts', 13 | 'tsx', 14 | 'js', 15 | 'jsx', 16 | 'json', 17 | 'node' 18 | ], 19 | globals: { 20 | 'ts-jest': { 21 | diagnostics: { 22 | // Do not fail on TS compilation errors 23 | // https://kulshekhar.github.io/ts-jest/user/config/diagnostics#do-not-fail-on-first-error 24 | warnOnly: true 25 | } 26 | } 27 | }, 28 | testEnvironment: 'node' 29 | } -------------------------------------------------------------------------------- /circuits/circom/NftMint.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.0; 2 | 3 | include "../node_modules/circomlib/circuits/poseidon.circom"; 4 | 5 | template NftMint() { 6 | // The public inputs 7 | signal output nullifier; 8 | signal input hash; 9 | signal input address; 10 | 11 | // The private inputs 12 | signal input preimage; 13 | 14 | // Hash the preimage and check if the result matches the hash. 15 | component hasher = Poseidon(1); 16 | hasher.inputs[0] <== preimage; 17 | 18 | hasher.out === hash; 19 | 20 | // The contract should keep track of seen nullifiers so as to prevent 21 | // double-spends. 22 | component nullifierHasher = Poseidon(2); 23 | nullifierHasher.inputs[0] <== address; 24 | nullifierHasher.inputs[1] <== preimage; 25 | 26 | nullifier <== nullifierHasher.out; 27 | } 28 | -------------------------------------------------------------------------------- /web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | zknftmint 6 | 7 | 8 | 9 |

Mint an NFT if you can prove knowledge of a preimage

10 |

See zknftmint.

11 | 12 |

Your address:

13 |

The secret:

14 | 15 | 16 |
Time taken: 
17 |
Valid?: 
18 |
Nullifier: 
19 |
Proof:
20 | 
21 |     
22 | 23 | 24 | -------------------------------------------------------------------------------- /contracts/contracts/Hasher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | import { SnarkConstants } from "./SnarkConstants.sol"; 4 | 5 | library PoseidonT2 { 6 | function poseidon(uint256[1] memory input) public pure returns (uint256) {} 7 | } 8 | 9 | library PoseidonT3 { 10 | function poseidon(uint256[2] memory input) public pure returns (uint256) {} 11 | } 12 | 13 | /* 14 | * A SHA256 hash function for any number of input elements, and Poseidon hash 15 | * functions for 2, 3, 4, 5, and 12 input elements. 16 | */ 17 | contract Hasher is SnarkConstants { 18 | function hash1(uint256[1] memory array) public pure returns (uint256) { 19 | return PoseidonT2.poseidon(array); 20 | } 21 | 22 | function hash2(uint256[2] memory array) public pure returns (uint256) { 23 | return PoseidonT3.poseidon(array); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/hardhat.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type import('hardhat/config').HardhatUserConfig 3 | */ 4 | require('@nomiclabs/hardhat-ethers') 5 | require('hardhat-artifactor') 6 | require("@nomiclabs/hardhat-etherscan") 7 | 8 | module.exports = { 9 | solidity: "0.8.10", 10 | settings: { 11 | optimizer: { 12 | enabled: true, 13 | runs: 200 14 | } 15 | 16 | }, 17 | networks: { 18 | hardhat: { 19 | accounts: { 20 | mnemonic: "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" 21 | }, 22 | loggingEnabled: false, 23 | allowUnlimitedContractSize: false 24 | }, 25 | goerli: { 26 | url: "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", 27 | accounts: { 28 | mnemonic: "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" 29 | }, 30 | } 31 | }, 32 | etherscan: { 33 | apiKey: "ISWN73946TYJT6P79Z64X826RWVES7PFS3" 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /contracts/contracts/NftMint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { IVerifier } from "./verifier.sol"; 5 | import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | 7 | contract NftMint is ERC721 { 8 | uint256 public hash = uint256(1121645852825515626345503741442177404306361956507933536148868635850297893661); 9 | uint256 public nextTokenId; 10 | IVerifier public verifier; 11 | 12 | mapping (uint256 => bool) public nullifiers; 13 | 14 | constructor 15 | ( 16 | IVerifier _verifier 17 | ) 18 | ERC721("ZkNftMint", "ZK") 19 | { 20 | verifier = _verifier; 21 | } 22 | 23 | function mintWithProof( 24 | uint256 _nullifier, 25 | uint256[8] memory _proof 26 | ) 27 | public 28 | { 29 | require( 30 | nullifiers[_nullifier] == false, 31 | "NftMint: nullifier seen" 32 | ); 33 | 34 | uint256[3] memory publicInputs = [ 35 | _nullifier, 36 | hash, 37 | uint256(uint160(address(msg.sender))) 38 | ]; 39 | 40 | require( 41 | verifier.verify(_proof, publicInputs), 42 | "NftMint: invalid proof" 43 | ); 44 | 45 | nullifiers[_nullifier] = true; 46 | 47 | _safeMint(msg.sender, nextTokenId); 48 | nextTokenId ++; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/ts/index.ts: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | 3 | const getDefaultSigner = async () => { 4 | const signers = await ethers.getSigners() 5 | const signer = signers[0] 6 | 7 | return signer 8 | } 9 | 10 | const deployVerifier = async () => { 11 | const signer = await getDefaultSigner() 12 | const cf = await ethers.getContractFactory('Verifier', signer) 13 | return await cf.deploy() 14 | } 15 | 16 | const deployNftMint = async ( 17 | verifierAddress: string 18 | ) => { 19 | const signer = await getDefaultSigner() 20 | 21 | //const PoseidonT2 = await ethers.getContractFactory( 22 | //"PoseidonT2", 23 | //signer, 24 | //) 25 | //const poseidonT2Contract = await PoseidonT2.deploy() 26 | //await poseidonT2Contract.deployed() 27 | 28 | //const PoseidonT3 = await ethers.getContractFactory( 29 | //"PoseidonT3", 30 | //signer, 31 | //) 32 | //const poseidonT3Contract = await PoseidonT3.deploy() 33 | //await poseidonT3Contract.deployed() 34 | 35 | const cf = await ethers.getContractFactory('NftMint', signer) 36 | return await cf.deploy( 37 | verifierAddress, 38 | //{ 39 | //libraries: { 40 | //PoseidonT2: poseidonT2Contract.address, 41 | //PoseidonT3: poseidonT3Contract.address, 42 | //} 43 | //} 44 | ) 45 | } 46 | 47 | export { 48 | deployNftMint, 49 | deployVerifier, 50 | getDefaultSigner, 51 | } 52 | -------------------------------------------------------------------------------- /contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zknftmint-contracts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "watch": "tsc --watch", 9 | "test": "hardhat test", 10 | "compileSol": "./scripts/compileSol.sh", 11 | "test-nftMint": "jest NftMint.test.ts", 12 | "test-nftMint-debug": "node --inspect-brk ./node_modules/.bin/jest NftMint.test.ts" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/zknftmint/project.git" 17 | }, 18 | "author": "weijiekoh", 19 | "bugs": { 20 | "url": "https://github.com/zknftmint/project/issues" 21 | }, 22 | "homepage": "https://github.com/zknftmint/project#readme", 23 | "dependencies": { 24 | "zknftmint-circuits": "1.0.0" 25 | }, 26 | "devDependencies": { 27 | "@nomiclabs/hardhat-ethers": "^2.0.2", 28 | "@typechain/ethers-v5": "^7.2.0", 29 | "@typechain/hardhat": "^2.3.1", 30 | "@types/node": "^16.11.4", 31 | "@nomiclabs/hardhat-etherscan": "^2.1.8", 32 | "@openzeppelin/contracts": "^4.3.2", 33 | "ethers": "^5.5.1", 34 | "@types/jest": "^27.0.3", 35 | "hardhat": "^2.6.7", 36 | "hardhat-gas-reporter": "^1.0.4", 37 | "lerna": "^4.0.0", 38 | "ts-jest": "^27.0.7", 39 | "ts-node": "^10.4.0", 40 | "typescript": "^4.4.4", 41 | "circomlibjs": "^0.1.1", 42 | "hardhat-artifactor": "^0.2.0", 43 | "zknftmint-circuits": "1.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /circuits/ts/__tests__/NftMint.test.ts: -------------------------------------------------------------------------------- 1 | jest.setTimeout(90000) 2 | import * as fs from 'fs' 3 | const circomlib = require('circomlibjs') 4 | 5 | const ff = require('ffjavascript') 6 | const stringifyBigInts: (obj: object) => any = ff.utils.stringifyBigInts 7 | 8 | import { 9 | callGenWitness as genWitness, 10 | callGetSignalByName as getSignalByName, 11 | } from 'circom-helper' 12 | 13 | describe('NFT mint test', () => { 14 | const circuit = 'NftMint_test' 15 | let poseidon 16 | 17 | beforeAll(async () => { 18 | poseidon = await circomlib.buildPoseidon() 19 | }) 20 | 21 | it('should produce a witness and correct nullifier', async () => { 22 | const preimage = BigInt(1234) 23 | 24 | // Hash the preimage 25 | const hash = poseidon([preimage]) 26 | const address = BigInt('0x1111111111111111111111111111111111111111') 27 | 28 | // Construct the circut inputs 29 | const circuitInputs = stringifyBigInts({ 30 | // Converts the buffer to a BigInt 31 | hash: poseidon.F.toObject(hash), 32 | address, 33 | preimage, 34 | }) 35 | 36 | // Generate the witness 37 | const witness = await genWitness(circuit, circuitInputs) 38 | 39 | // Get the nullifier from the witness 40 | const nullifier = await getSignalByName(circuit, witness, 'main.nullifier') 41 | 42 | // Check that the nullifier is correct 43 | const expectedNullifier = poseidon.F.toObject( 44 | poseidon([address, preimage]), 45 | ) 46 | expect(nullifier.toString()).toEqual(expectedNullifier.toString()) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /circuits/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zknftmint-circuits", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "circom-helper": "npx circom-helper -c ./circomHelperConfig.json -b ./build/test/ -p 9001", 8 | "zkey-manager-compile": "zkey-manager compile -c ./zkeys.config.yml", 9 | "zkey-manager-downloadPtau": "zkey-manager downloadPtau -c ./zkeys.config.yml", 10 | "zkey-manager-genZkeys": "zkey-manager genZkeys -c ./zkeys.config.yml", 11 | "export-verifier-sol": "node build/exportVerifier.js ./zkeys/NftMint__prod.0.zkey ../contracts/contracts/verifier.sol", 12 | "build": "tsc", 13 | "watch": "tsc --watch", 14 | "test": "jest --runInBand", 15 | "test-nftMint": "jest NftMint.test.ts", 16 | "test-nftMint-debug": "node --inspect-brk ./node_modules/.bin/jest NftMint.test.ts" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/zknftmint/project.git" 21 | }, 22 | "author": "weijiekoh", 23 | "bugs": { 24 | "url": "https://github.com/zknftmint/project/issues" 25 | }, 26 | "homepage": "https://github.com/zknftmint/project#readme", 27 | "dependencies": { 28 | "axios": "^0.23.0", 29 | "circomlib": "^2.0.2", 30 | "circomlibjs": "^0.1.1", 31 | "crypto": "^1.0.1", 32 | "ffjavascript": "^0.2.46", 33 | "snarkjs": "0.4.10", 34 | "shelljs": "0.8.4", 35 | "zkey-manager": "^0.1.2", 36 | "ts-jest": "^27.0.7" 37 | }, 38 | "devDependencies": { 39 | "@types/jest": "^27.0.2", 40 | "circom-helper": "^0.3.3", 41 | "jest": "^27.3.1", 42 | "lerna": "^4.0.0", 43 | "typescript": "^4.4.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/ts/__tests__/NftMint.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | const { ethers } = require('hardhat') 4 | jest.setTimeout(90000) 5 | 6 | import { 7 | hash, 8 | hash2, 9 | genNftMintCircuitInputs, 10 | genProof, 11 | } from 'zknftmint-circuits' 12 | 13 | import { 14 | deployNftMint, 15 | deployVerifier, 16 | getDefaultSigner, 17 | } from '../' 18 | 19 | let nftMintContract 20 | let verifierContract 21 | let preimage = BigInt(1234) 22 | let hashedVal 23 | let signer 24 | 25 | describe('NftMint', () => { 26 | beforeAll(async () => { 27 | signer = await getDefaultSigner() 28 | hashedVal = await hash([preimage]) 29 | 30 | verifierContract = await deployVerifier() 31 | await verifierContract.deployTransaction.wait() 32 | 33 | nftMintContract = await deployNftMint( 34 | verifierContract.address 35 | ) 36 | await nftMintContract.deployTransaction.wait() 37 | }) 38 | 39 | it('should mint an NFT', async () => { 40 | // Generate inputs 41 | const nullifier = await hash([BigInt(signer.address), preimage]) 42 | const circuitInputs = genNftMintCircuitInputs( 43 | hashedVal, 44 | BigInt(signer.address), 45 | preimage, 46 | ) 47 | 48 | const rapidsnarkExePath = path.resolve(process.argv[3]) 49 | const witnessExePath = path.resolve(process.argv[4]) 50 | const zkeyPath = path.resolve(process.argv[5]) 51 | 52 | // Generate the proof 53 | const r = genProof( 54 | circuitInputs, 55 | rapidsnarkExePath, 56 | witnessExePath, 57 | zkeyPath, 58 | ) 59 | 60 | // Arrange the proof elements for the contract 61 | const proofForTx: any = [ 62 | r.proof.pi_a[0], 63 | r.proof.pi_a[1], 64 | r.proof.pi_b[0][1], 65 | r.proof.pi_b[0][0], 66 | r.proof.pi_b[1][1], 67 | r.proof.pi_b[1][0], 68 | r.proof.pi_c[0], 69 | r.proof.pi_c[1], 70 | ] 71 | 72 | const balanceBefore = await nftMintContract.balanceOf(signer.address) 73 | 74 | // Mint the token 75 | await nftMintContract.mintWithProof( 76 | nullifier, 77 | proofForTx, 78 | ) 79 | 80 | const balanceAfter = await nftMintContract.balanceOf(signer.address) 81 | 82 | // The NFT should have been minted 83 | expect(balanceAfter - balanceBefore).toEqual(1) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /web/src/app.ts: -------------------------------------------------------------------------------- 1 | import { builder } from './witness_calculator' 2 | import { groth16 } from 'snarkjs' 3 | 4 | const zkeyPath = 'http://127.0.0.1:8000/NftMint__prod.0.zkey' 5 | const wasmPath = 'http://127.0.0.1:8000/NftMint__prod.wasm' 6 | const vkPath = 'http://127.0.0.1:8000/verification_key.json' 7 | 8 | const calculateProof = async ( 9 | address: string, 10 | secret: string 11 | ) => { 12 | // Fetch the zkey and wasm files, and convert them into array buffers 13 | let resp = await fetch(wasmPath) 14 | const wasmBuff = await resp.arrayBuffer() 15 | resp = await fetch(zkeyPath) 16 | const zkeyBuff = await resp.arrayBuffer() 17 | 18 | const circuitInputs = { 19 | hash: BigInt('1121645852825515626345503741442177404306361956507933536148868635850297893661'), 20 | address: BigInt(address), 21 | preimage: BigInt(secret), 22 | } 23 | 24 | const witnessCalculator = await builder(wasmBuff) 25 | 26 | const wtnsBuff = await witnessCalculator.calculateWTNSBin(circuitInputs, 0) 27 | 28 | const start = Date.now() 29 | const { proof, publicSignals } = 30 | await groth16.prove(new Uint8Array(zkeyBuff), wtnsBuff, null) 31 | const end = Date.now() 32 | const timeTaken = ((end - start) / 1000).toString() + ' seconds' 33 | 34 | const timeComponent = document.getElementById('time') 35 | timeComponent.innerHTML = timeTaken 36 | 37 | const proofForTx = [ 38 | proof.pi_a[0], 39 | proof.pi_a[1], 40 | proof.pi_b[0][1], 41 | proof.pi_b[0][0], 42 | proof.pi_b[1][1], 43 | proof.pi_b[1][0], 44 | proof.pi_c[0], 45 | proof.pi_c[1], 46 | ]; 47 | 48 | const proofAsStr = JSON.stringify( 49 | proofForTx.map((x) => BigInt(x).toString(10)), 50 | ).split('\n').join().replaceAll('"', '') 51 | 52 | const proofCompnent = document.getElementById('proof') 53 | proofCompnent.innerHTML = proofAsStr 54 | 55 | const nullifier = document.getElementById("nullifier") 56 | nullifier.innerHTML = BigInt(publicSignals[0]).toString() 57 | 58 | // Verify the proof 59 | resp = await fetch(vkPath) 60 | const vkey = await resp.json() 61 | 62 | const res = await groth16.verify(vkey, publicSignals, proof); 63 | 64 | const resultComponent = document.getElementById('result') 65 | resultComponent.innerHTML = res; 66 | } 67 | 68 | const main = async () => { 69 | const bGenProof = document.getElementById("bGenProof") 70 | 71 | bGenProof.addEventListener("click", () => { 72 | const secret = document.getElementById("secret") 73 | const address = document.getElementById("address") 74 | calculateProof( 75 | address.value, 76 | secret.value 77 | ) 78 | }) 79 | } 80 | 81 | 82 | main() 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mint an NFT if you know a secret 2 | 3 | Developed for 0xPARC. 4 | 5 | Contract on Goerli: [`0xc4490d6407f81378c8d3620eA11092B2FC429Df2`](https://goerli.etherscan.io/address/0xc4490d6407f81378c8d3620eA11092B2FC429Df2) 6 | 7 | ## Getting started 8 | 9 | Clone this repository and install dependencies: 10 | 11 | ```bash 12 | git clone git@github.com:weijiekoh/zknftmint.git 13 | cd zknftmint 14 | npm i 15 | npm run bootstrap 16 | ``` 17 | 18 | You may need to manually install dependencies for `zkey-manager` by following instructions [here](https://github.com/privacy-scaling-explorations/zkey-manager#requirements). 19 | 20 | In a separate terminal, run a HTTP server in `web/zkeys`: 21 | 22 | ```bash 23 | cd web/zkeys 24 | npx http-server --cors -p 8000 25 | ``` 26 | 27 | In another terminal, run the web application: 28 | 29 | ```bash 30 | cd web 31 | npm run serve 32 | ``` 33 | 34 | Get Goerli ETH: https://faucet.paradigm.xyz/ 35 | 36 | To generate a proof and nullifier, first navigate to: 37 | 38 | http://127.0.0.1:1234 39 | 40 | Next, paste your ETH address from Metamask and enter the secret (currently 41 | hardcoded to `1234`). Click "Create proof". 42 | 43 | Navigate to the Write Contract page for the NftMint contract on Etherscan, 44 | click on "Connect to Web3", and select `mintWithProof`. Copy and paste the 45 | nullifier and the proof, and click "Write". 46 | 47 | https://goerli.etherscan.io/address/0xc4490d6407f81378c8d3620eA11092B2FC429Df2#writeContract 48 | 49 | If the proof is valid and you have not previously used this address to mint an 50 | NFT on this contract, the transaction will execute and mint an NFT to your 51 | address. 52 | 53 | ## Development 54 | 55 | To install NPM dependencies, run this in the project's root directory: 56 | 57 | ``` 58 | npm i && npm run bootstrap 59 | ``` 60 | 61 | To compile the Typescript code for tests, run: 62 | 63 | ``` 64 | npm run build 65 | ``` 66 | 67 | To compile the circuits, generate its zkey file, and export its verification 68 | key for off-chain proof verification: 69 | 70 | ``` 71 | cd circuits 72 | npx zkey-manager compile -c ./zkeys.config.yml 73 | npx zkey-manager downloadPtau -c ./zkeys.config.yml 74 | npx zkey-manager genZkeys -c ./zkeys.config.yml 75 | npx snarkjs zkev ./NftMint__prod.0.zkey ./zkeys/verification_key.json 76 | node build/exportVerifier.js ./zkeys/NftMint__prod.0.zkey ../contracts/contracts/verifier.sol 77 | ``` 78 | 79 | Note that no phase 2 trusted setup is performed, so do not use this in 80 | production unless you perform one. 81 | 82 | Next, compile the contracts: 83 | 84 | ``` 85 | cd ../contracts 86 | npm run compileSol 87 | ``` 88 | 89 | Deploy the contracts to a testnet: 90 | 91 | ```bash 92 | npx hardhat run build/deploy.js --network goerli 93 | ``` 94 | 95 | Verify the contracts on Etherscan: 96 | 97 | 1. Update `contracts/hardhat.config.js` with your Etherscan API key. 98 | 2. Run: 99 | 100 | ```bash 101 | npx hardhat verify --network goerli "" 102 | ``` 103 | -------------------------------------------------------------------------------- /circuits/ts/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import * as shelljs from 'shelljs' 4 | import * as tmp from 'tmp' 5 | const circomlib = require('circomlibjs') 6 | 7 | const ff = require('ffjavascript') 8 | const stringifyBigInts: (obj: object) => any = ff.utils.stringifyBigInts 9 | 10 | const p = circomlib.buildPoseidon() 11 | 12 | const hash = async (preimage: bigint[]) => { 13 | const poseidon = await p 14 | 15 | return poseidon.F.toObject( 16 | poseidon(preimage) 17 | ) 18 | } 19 | 20 | const genNftMintCircuitInputs = ( 21 | hashedVal: bigint, 22 | address: bigint, 23 | preimage: bigint, 24 | ) => { 25 | const circuitInputs = stringifyBigInts({ 26 | hash: hashedVal, 27 | address, 28 | preimage, 29 | }) 30 | 31 | return circuitInputs 32 | } 33 | 34 | const genProof = ( 35 | inputs: any, 36 | rapidsnarkExePath: string, 37 | witnessExePath: string, 38 | zkeyPath: string, 39 | silent = true, 40 | ): any => { 41 | // Create tmp directory 42 | const tmpObj = tmp.dirSync() 43 | const tmpDirPath = tmpObj.name 44 | 45 | const inputJsonPath = path.join(tmpDirPath, 'input.json') 46 | const outputWtnsPath = path.join(tmpDirPath, 'output.wtns') 47 | const proofJsonPath = path.join(tmpDirPath, 'proof.json') 48 | const publicJsonPath = path.join(tmpDirPath, 'public.json') 49 | 50 | // Write input.json 51 | const jsonData = JSON.stringify(stringifyBigInts(inputs)) 52 | fs.writeFileSync(inputJsonPath, jsonData) 53 | 54 | // Generate the witness 55 | const witnessGenCmd = `${witnessExePath} ${inputJsonPath} ${outputWtnsPath}` 56 | 57 | const witnessGenOutput = shelljs.exec(witnessGenCmd, { silent }) 58 | if (witnessGenOutput.stderr) { 59 | console.log(witnessGenOutput.stderr) 60 | } 61 | 62 | if (!fs.existsSync(outputWtnsPath)) { 63 | console.error(witnessGenOutput.stderr) 64 | throw new Error('Error executing ' + witnessGenCmd) 65 | } 66 | 67 | // Generate the proof 68 | const proofGenCmd = `${rapidsnarkExePath} ${zkeyPath} ${outputWtnsPath} ${proofJsonPath} ${publicJsonPath}` 69 | shelljs.exec(proofGenCmd, { silent }) 70 | 71 | if (!fs.existsSync(proofJsonPath)) { 72 | throw new Error('Error executing ' + proofGenCmd) 73 | } 74 | 75 | // Read the proof and public inputs 76 | const proof = JSON.parse(fs.readFileSync(proofJsonPath).toString()) 77 | const publicInputs = JSON.parse(fs.readFileSync(publicJsonPath).toString()) 78 | 79 | // Delete the temp files and the temp directory 80 | for (const f of [ 81 | proofJsonPath, 82 | publicJsonPath, 83 | inputJsonPath, 84 | outputWtnsPath, 85 | ]) { 86 | if (fs.existsSync(f)) { 87 | fs.unlinkSync(f) 88 | } 89 | } 90 | tmpObj.removeCallback() 91 | 92 | return { proof, publicInputs } 93 | } 94 | 95 | export { 96 | hash, 97 | genProof, 98 | genNftMintCircuitInputs, 99 | } 100 | -------------------------------------------------------------------------------- /web/zkeys/verification_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "groth16", 3 | "curve": "bn128", 4 | "nPublic": 3, 5 | "vk_alpha_1": [ 6 | "20491192805390485299153009773594534940189261866228447918068658471970481763042", 7 | "9383485363053290200918347156157836566562967994039712273449902621266178545958", 8 | "1" 9 | ], 10 | "vk_beta_2": [ 11 | [ 12 | "6375614351688725206403948262868962793625744043794305715222011528459656738731", 13 | "4252822878758300859123897981450591353533073413197771768651442665752259397132" 14 | ], 15 | [ 16 | "10505242626370262277552901082094356697409835680220590971873171140371331206856", 17 | "21847035105528745403288232691147584728191162732299865338377159692350059136679" 18 | ], 19 | [ 20 | "1", 21 | "0" 22 | ] 23 | ], 24 | "vk_gamma_2": [ 25 | [ 26 | "10857046999023057135944570762232829481370756359578518086990519993285655852781", 27 | "11559732032986387107991004021392285783925812861821192530917403151452391805634" 28 | ], 29 | [ 30 | "8495653923123431417604973247489272438418190587263600148770280649306958101930", 31 | "4082367875863433681332203403145435568316851327593401208105741076214120093531" 32 | ], 33 | [ 34 | "1", 35 | "0" 36 | ] 37 | ], 38 | "vk_delta_2": [ 39 | [ 40 | "10857046999023057135944570762232829481370756359578518086990519993285655852781", 41 | "11559732032986387107991004021392285783925812861821192530917403151452391805634" 42 | ], 43 | [ 44 | "8495653923123431417604973247489272438418190587263600148770280649306958101930", 45 | "4082367875863433681332203403145435568316851327593401208105741076214120093531" 46 | ], 47 | [ 48 | "1", 49 | "0" 50 | ] 51 | ], 52 | "vk_alphabeta_12": [ 53 | [ 54 | [ 55 | "2029413683389138792403550203267699914886160938906632433982220835551125967885", 56 | "21072700047562757817161031222997517981543347628379360635925549008442030252106" 57 | ], 58 | [ 59 | "5940354580057074848093997050200682056184807770593307860589430076672439820312", 60 | "12156638873931618554171829126792193045421052652279363021382169897324752428276" 61 | ], 62 | [ 63 | "7898200236362823042373859371574133993780991612861777490112507062703164551277", 64 | "7074218545237549455313236346927434013100842096812539264420499035217050630853" 65 | ] 66 | ], 67 | [ 68 | [ 69 | "7077479683546002997211712695946002074877511277312570035766170199895071832130", 70 | "10093483419865920389913245021038182291233451549023025229112148274109565435465" 71 | ], 72 | [ 73 | "4595479056700221319381530156280926371456704509942304414423590385166031118820", 74 | "19831328484489333784475432780421641293929726139240675179672856274388269393268" 75 | ], 76 | [ 77 | "11934129596455521040620786944827826205713621633706285934057045369193958244500", 78 | "8037395052364110730298837004334506829870972346962140206007064471173334027475" 79 | ] 80 | ] 81 | ], 82 | "IC": [ 83 | [ 84 | "3857659530219906943152785552899198663852979207590420620802743549295296919679", 85 | "8367960888682158022427105742558475486088669782591421317171600999122506510796", 86 | "1" 87 | ], 88 | [ 89 | "8361626798107864008264733825256613042734968937398128936109720230259510075488", 90 | "9271507670617320818166611831037484320104273577166835176215748372308714790630", 91 | "1" 92 | ], 93 | [ 94 | "21480786565845101634948265554385650370680540421570179632822539769216309491270", 95 | "20517002848256449318692808547386513700186931404514317826671867969707721490772", 96 | "1" 97 | ], 98 | [ 99 | "6823739927950557742246913693052072220213742766782370219666937126411380340633", 100 | "19878777316727584245579797737044924949420447706770682437221414501064053790692", 101 | "1" 102 | ] 103 | ] 104 | } -------------------------------------------------------------------------------- /circuits/ts/exportVerifier.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import * as shelljs from 'shelljs' 3 | import * as fs from 'fs' 4 | 5 | const main = async ( 6 | zkeyPath: string, 7 | verifierSolPath: string, 8 | verifierTemplatePath: string, 9 | ) => { 10 | // Export verifiying key 11 | const snarkjsPath = './node_modules/snarkjs/build/cli.cjs' 12 | const vkJsonPath = 'vk.json' 13 | const cmd = `node ${snarkjsPath} zkey export verificationkey ${zkeyPath} ${vkJsonPath}` 14 | const output = shelljs.exec(cmd) 15 | 16 | if (output.stderr) { 17 | console.error(output.stderr) 18 | return 19 | } 20 | 21 | const vkJson = fs.readFileSync(vkJsonPath).toString() 22 | const verificationKey = JSON.parse(vkJson) 23 | 24 | let template = fs.readFileSync(verifierTemplatePath).toString() 25 | const vkalpha1_str = 26 | `\n ` + 27 | `uint256(${verificationKey.vk_alpha_1[0].toString()}),\n`+ 28 | ` uint256(${verificationKey.vk_alpha_1[1].toString()})\n ` 29 | template = template.replace("<%vk_alpha1%>", vkalpha1_str) 30 | 31 | const vkbeta2_str = 32 | `\n ` + 33 | `[uint256(${verificationKey.vk_beta_2[0][1].toString()}),\n`+ 34 | ` uint256(${verificationKey.vk_beta_2[0][0].toString()})],\n`+ 35 | ` [uint256(${verificationKey.vk_beta_2[1][1].toString()}),\n` + 36 | ` uint256(${verificationKey.vk_beta_2[1][0].toString()})]\n ` 37 | template = template.replace("<%vk_beta2%>", vkbeta2_str) 38 | 39 | const vkgamma2_str = 40 | `\n ` + 41 | `[uint256(${verificationKey.vk_gamma_2[0][1].toString()}),\n`+ 42 | ` uint256(${verificationKey.vk_gamma_2[0][0].toString()})],\n`+ 43 | ` [uint256(${verificationKey.vk_gamma_2[1][1].toString()}),\n` + 44 | ` uint256(${verificationKey.vk_gamma_2[1][0].toString()})]\n ` 45 | template = template.replace("<%vk_gamma2%>", vkgamma2_str) 46 | 47 | const vkdelta2_str = 48 | `\n ` + 49 | `[uint256(${verificationKey.vk_delta_2[0][1].toString()}),\n`+ 50 | ` uint256(${verificationKey.vk_delta_2[0][0].toString()})],\n`+ 51 | ` [uint256(${verificationKey.vk_delta_2[1][1].toString()}),\n` + 52 | ` uint256(${verificationKey.vk_delta_2[1][0].toString()})]\n ` 53 | template = template.replace("<%vk_delta2%>", vkdelta2_str) 54 | const vil = '<%vk_input_length%>' 55 | while (template.includes(vil)) { 56 | template = template.replace(vil, (verificationKey.IC.length-1).toString()) 57 | } 58 | const vic = '<%vk_ic_length%>' 59 | while (template.includes(vic)) { 60 | template = template.replace(vic, verificationKey.IC.length.toString()) 61 | } 62 | 63 | let vi = "" 64 | for (let i=0; i", vi) 71 | 72 | fs.writeFileSync(verifierSolPath, template) 73 | } 74 | 75 | if (require.main === module) { 76 | if (process.argv.length !== 4) { 77 | console.error('Usage: node ') 78 | process.exit(1) 79 | } 80 | 81 | const zkeyPath = path.resolve(process.argv[2]) 82 | const verifierSolPath = path.resolve(process.argv[3]) 83 | const verifierTemplatePath = path.resolve( 84 | path.join(__dirname, '..', 'verifierTemplate.sol'), 85 | ) 86 | 87 | if (! fs.existsSync(zkeyPath)) { 88 | console.error('The zkey file does not exist') 89 | process.exit(1) 90 | } 91 | 92 | if (! fs.existsSync(verifierTemplatePath)) { 93 | console.log(verifierTemplatePath) 94 | console.error('The verifier template does not exist') 95 | process.exit(1) 96 | } 97 | 98 | main( 99 | zkeyPath, 100 | verifierSolPath, 101 | verifierTemplatePath, 102 | ) 103 | } 104 | -------------------------------------------------------------------------------- /circuits/zkeys.config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | out: "./zkeys" 3 | 4 | # Make sure that these paths are correct 5 | circomPath: "../../.cargo/bin/circom" 6 | #circomPath: "./node_modules/circom/cli.js" 7 | snarkjsPath: "./node_modules/snarkjs/build/cli.cjs" 8 | circomRuntimePath: "./node_modules/circom_runtime" 9 | ffiasmPath: "./node_modules/ffiasm" 10 | 11 | circomFlags: "--c --r1cs --sym --wasm --wat" 12 | 13 | circuits: 14 | - template: "./circom/NftMint.circom" 15 | component: "NftMint" 16 | params: [] 17 | pubInputs: ["hash", "address"] 18 | type: "prod" 19 | 20 | ptauFiles: 21 | 1: 22 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_01.ptau" 23 | name: "powersOfTau28_hez_final_01.ptau" 24 | 25 | 2: 26 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_02.ptau" 27 | name: "powersOfTau28_hez_final_02.ptau" 28 | 29 | 3: 30 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_03.ptau" 31 | name: "powersOfTau28_hez_final_03.ptau" 32 | 33 | 4: 34 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_04.ptau" 35 | name: "powersOfTau28_hez_final_04.ptau" 36 | 37 | 5: 38 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_05.ptau" 39 | name: "powersOfTau28_hez_final_05.ptau" 40 | 41 | 6: 42 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_06.ptau" 43 | name: "powersOfTau28_hez_final_06.ptau" 44 | 45 | 7: 46 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_07.ptau" 47 | name: "powersOfTau28_hez_final_7.ptau" 48 | 49 | 8: 50 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_08.ptau" 51 | name: "powersOfTau28_hez_final_8.ptau" 52 | 53 | 9: 54 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_09.ptau" 55 | name: "powersOfTau28_hez_final_9.ptau" 56 | 57 | 10: 58 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_10.ptau" 59 | name: "powersOfTau28_hez_final_10.ptau" 60 | 61 | 11: 62 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_11.ptau" 63 | name: "powersOfTau28_hez_final_11.ptau" 64 | 65 | 12: 66 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_12.ptau" 67 | name: "powersOfTau28_hez_final_12.ptau" 68 | 69 | 13: 70 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_13.ptau" 71 | name: "powersOfTau28_hez_final_13.ptau" 72 | 73 | 14: 74 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_14.ptau" 75 | name: "powersOfTau28_hez_final_14.ptau" 76 | 77 | 15: 78 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_15.ptau" 79 | name: "powersOfTau28_hez_final_15.ptau" 80 | 81 | 16: 82 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_16.ptau" 83 | name: "powersOfTau28_hez_final_16.ptau" 84 | 85 | 17: 86 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_17.ptau" 87 | name: "powersOfTau28_hez_final_17.ptau" 88 | 89 | 18: 90 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_18.ptau" 91 | name: "powersOfTau28_hez_final_18.ptau" 92 | 93 | 19: 94 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_19.ptau" 95 | name: "powersOfTau28_hez_final_19.ptau" 96 | 97 | 20: 98 | url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_20.ptau" 99 | name: "powersOfTau28_hez_final_20.ptau" 100 | 101 | 21: 102 | url: "https://www.dropbox.com/sh/mn47gnepqu88mzl/AABiMm_4UMnvU0VIkCDVX9XZa/powersOfTau28_hez_final_21.ptau?dl=1" 103 | name: "powersOfTau28_hez_final_21.ptau" 104 | 105 | 22: 106 | url: "https://www.dropbox.com/sh/mn47gnepqu88mzl/AAAzGtg94saShf044uZdwnuGa/powersOfTau28_hez_final_22.ptau?dl=1" 107 | name: "powersOfTau28_hez_final_22.ptau" 108 | 109 | 23: 110 | url: "https://www.dropbox.com/sh/mn47gnepqu88mzl/AACXdEyzF6V5G5SLwlcV24pYa/powersOfTau28_hez_final_23.ptau?dl=1" 111 | name: "powersOfTau28_hez_final_23.ptau" 112 | 113 | 24: 114 | url: "https://www.dropbox.com/sh/mn47gnepqu88mzl/AAAi2DHbiB5LhGGFRJ_M2DwVa/powersOfTau28_hez_final_24.ptau?dl=1" 115 | name: "powersOfTau28_hez_final_24.ptau" 116 | 117 | 25: 118 | url: "https://www.dropbox.com/sh/mn47gnepqu88mzl/AABtJqpgsk93Xe-gtLauqycTa/powersOfTau28_hez_final_25.ptau?dl=1" 119 | name: "powersOfTau28_hez_final_25.ptau" 120 | 121 | 26: 122 | url: "https://www.dropbox.com/sh/mn47gnepqu88mzl/AABcvvD2qA2wGwJCU2AhbwX0a/powersOfTau28_hez_final_26.ptau?dl=1" 123 | name: "powersOfTau28_hez_final_26.ptau" 124 | 125 | 27: 126 | url: "https://www.dropbox.com/sh/mn47gnepqu88mzl/AACxvAidsajKkHS5eMgpnHyZa/powersOfTau28_hez_final_27.ptau?dl=1" 127 | name: "powersOfTau28_hez_final_27.ptau" 128 | 129 | 28: 130 | url: "https://www.dropbox.com/sh/mn47gnepqu88mzl/AADgFSy_UnsSoDwOPy64tpCWa/powersOfTau28_hez_final.ptau?dl=1" 131 | name: "powersOfTau28_hez_final_28.ptau" 132 | -------------------------------------------------------------------------------- /circuits/verifierTemplate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | 6 | abstract contract IVerifier { 7 | function verify( 8 | uint256[8] memory, 9 | uint256[<%vk_input_length%>] memory 10 | ) virtual public view returns (bool); 11 | } 12 | 13 | library Pairing { 14 | 15 | uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 16 | 17 | struct G1Point { 18 | uint256 x; 19 | uint256 y; 20 | } 21 | 22 | // Encoding of field elements is: X[0] * z + X[1] 23 | struct G2Point { 24 | uint256[2] x; 25 | uint256[2] y; 26 | } 27 | 28 | /* 29 | * @return The negation of p, i.e. p.plus(p.negate()) should be zero. 30 | */ 31 | function negate(G1Point memory p) internal pure returns (G1Point memory) { 32 | 33 | // The prime q in the base field F_q for G1 34 | if (p.x == 0 && p.y == 0) { 35 | return G1Point(0, 0); 36 | } else { 37 | return G1Point(p.x, PRIME_Q - (p.y % PRIME_Q)); 38 | } 39 | } 40 | 41 | /* 42 | * @return The sum of two points of G1 43 | */ 44 | function plus( 45 | G1Point memory p1, 46 | G1Point memory p2 47 | ) internal view returns (G1Point memory r) { 48 | 49 | uint256[4] memory input; 50 | input[0] = p1.x; 51 | input[1] = p1.y; 52 | input[2] = p2.x; 53 | input[3] = p2.y; 54 | bool success; 55 | 56 | // solium-disable-next-line security/no-inline-assembly 57 | assembly { 58 | success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) 59 | // Use "invalid" to make gas estimation work 60 | switch success case 0 { invalid() } 61 | } 62 | 63 | require(success,"pairing-add-failed"); 64 | } 65 | 66 | /* 67 | * @return The product of a point on G1 and a scalar, i.e. 68 | * p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all 69 | * points p. 70 | */ 71 | function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { 72 | 73 | uint256[3] memory input; 74 | input[0] = p.x; 75 | input[1] = p.y; 76 | input[2] = s; 77 | bool success; 78 | // solium-disable-next-line security/no-inline-assembly 79 | assembly { 80 | success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) 81 | // Use "invalid" to make gas estimation work 82 | switch success case 0 { invalid() } 83 | } 84 | require (success,"pairing-mul-failed"); 85 | } 86 | 87 | /* @return The result of computing the pairing check 88 | * e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 89 | * For example, 90 | * pairing([P1(), P1().negate()], [P2(), P2()]) should return true. 91 | */ 92 | function pairing( 93 | G1Point memory a1, 94 | G2Point memory a2, 95 | G1Point memory b1, 96 | G2Point memory b2, 97 | G1Point memory c1, 98 | G2Point memory c2, 99 | G1Point memory d1, 100 | G2Point memory d2 101 | ) internal view returns (bool) { 102 | 103 | G1Point[4] memory p1 = [a1, b1, c1, d1]; 104 | G2Point[4] memory p2 = [a2, b2, c2, d2]; 105 | 106 | uint256 inputSize = 24; 107 | uint256[] memory input = new uint256[](inputSize); 108 | 109 | for (uint256 i = 0; i < 4; i++) { 110 | uint256 j = i * 6; 111 | input[j + 0] = p1[i].x; 112 | input[j + 1] = p1[i].y; 113 | input[j + 2] = p2[i].x[0]; 114 | input[j + 3] = p2[i].x[1]; 115 | input[j + 4] = p2[i].y[0]; 116 | input[j + 5] = p2[i].y[1]; 117 | } 118 | 119 | uint256[1] memory out; 120 | bool success; 121 | 122 | // solium-disable-next-line security/no-inline-assembly 123 | assembly { 124 | success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) 125 | // Use "invalid" to make gas estimation work 126 | switch success case 0 { invalid() } 127 | } 128 | 129 | require(success, "pairing-opcode-failed"); 130 | 131 | return out[0] != 0; 132 | } 133 | } 134 | 135 | contract Verifier is IVerifier { 136 | struct Proof { 137 | Pairing.G1Point a; 138 | Pairing.G2Point b; 139 | Pairing.G1Point c; 140 | } 141 | 142 | struct VerifyingKey { 143 | Pairing.G1Point alpha1; 144 | Pairing.G2Point beta2; 145 | Pairing.G2Point gamma2; 146 | Pairing.G2Point delta2; 147 | Pairing.G1Point[<%vk_ic_length%>] IC; 148 | } 149 | 150 | using Pairing for *; 151 | 152 | uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 153 | string constant ERROR_PROOF_Q = "VE1"; 154 | string constant ERROR_INPUT_VAL = "VE2"; 155 | 156 | function verifyingKey() internal pure returns (VerifyingKey memory vk) { 157 | vk.alpha1 = Pairing.G1Point(<%vk_alpha1%>); 158 | vk.beta2 = Pairing.G2Point(<%vk_beta2%>); 159 | vk.gamma2 = Pairing.G2Point(<%vk_gamma2%>); 160 | vk.delta2 = Pairing.G2Point(<%vk_delta2%>); 161 | <%vk_ic_pts%> 162 | } 163 | 164 | /* 165 | * @returns Whether the proof is valid given the verifying key and public 166 | * input. Note that this function only supports one public input. 167 | * Refer to the Semaphore source code for a verifier that supports 168 | * multiple public inputs. 169 | */ 170 | function verify( 171 | uint256[8] memory _proof, 172 | uint256[<%vk_input_length%>] memory input 173 | ) override public view returns (bool) { 174 | VerifyingKey memory vk = verifyingKey(); 175 | Proof memory proof; 176 | proof.a = Pairing.G1Point(_proof[0], _proof[1]); 177 | proof.b = Pairing.G2Point( 178 | [_proof[2], _proof[3]], 179 | [_proof[4], _proof[5]] 180 | ); 181 | proof.c = Pairing.G1Point(_proof[6], _proof[7]); 182 | 183 | // Make sure that proof.A, B, and C are each less than the prime q 184 | require(proof.a.x < PRIME_Q, ERROR_PROOF_Q); 185 | require(proof.a.y < PRIME_Q, ERROR_PROOF_Q); 186 | 187 | require(proof.b.x[0] < PRIME_Q, ERROR_PROOF_Q); 188 | require(proof.b.y[0] < PRIME_Q, ERROR_PROOF_Q); 189 | 190 | require(proof.b.x[1] < PRIME_Q, ERROR_PROOF_Q); 191 | require(proof.b.y[1] < PRIME_Q, ERROR_PROOF_Q); 192 | 193 | require(proof.c.x < PRIME_Q, ERROR_PROOF_Q); 194 | require(proof.c.y < PRIME_Q, ERROR_PROOF_Q); 195 | 196 | uint256 SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 197 | 198 | // Compute the linear combination vk_x 199 | Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); 200 | 201 | for (uint256 i = 0; i < <%vk_input_length%>; i++) { 202 | // Make sure that every input is less than the snark scalar field 203 | require(input[i] < SNARK_SCALAR_FIELD,"verifier-gte-snark-scalar-field"); 204 | 205 | vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); 206 | } 207 | 208 | vk_x = Pairing.plus(vk_x, vk.IC[0]); 209 | 210 | return Pairing.pairing( 211 | Pairing.negate(proof.a), 212 | proof.b, 213 | vk.alpha1, 214 | vk.beta2, 215 | vk_x, 216 | vk.gamma2, 217 | proof.c, 218 | vk.delta2 219 | ); 220 | } 221 | } 222 | 223 | 224 | -------------------------------------------------------------------------------- /web/src/witness_calculator.js: -------------------------------------------------------------------------------- 1 | //module.exports = async function builder(code, options) { 2 | export const builder = async(code, options) => { 3 | 4 | options = options || {}; 5 | 6 | const wasmModule = await WebAssembly.compile(code); 7 | 8 | let wc; 9 | 10 | 11 | const instance = await WebAssembly.instantiate(wasmModule, { 12 | runtime: { 13 | exceptionHandler : function(code) { 14 | let errStr; 15 | if (code == 1) { 16 | errStr= "Signal not found. "; 17 | } else if (code == 2) { 18 | errStr= "Too many signals set. "; 19 | } else if (code == 3) { 20 | errStr= "Signal already set. "; 21 | } else if (code == 4) { 22 | errStr= "Assert Failed. "; 23 | } else if (code == 5) { 24 | errStr= "Not enough memory. "; 25 | } else { 26 | errStr= "Unknown error\n"; 27 | } 28 | // get error message from wasm 29 | errStr += getMessage(); 30 | throw new Error(errStr); 31 | }, 32 | showSharedRWMemory: function() { 33 | printSharedRWMemory (); 34 | } 35 | 36 | } 37 | }); 38 | 39 | const sanityCheck = 40 | options 41 | // options && 42 | // ( 43 | // options.sanityCheck || 44 | // options.logGetSignal || 45 | // options.logSetSignal || 46 | // options.logStartComponent || 47 | // options.logFinishComponent 48 | // ); 49 | 50 | 51 | wc = new WitnessCalculator(instance, sanityCheck); 52 | return wc; 53 | 54 | function getMessage() { 55 | var message = ""; 56 | var c = instance.exports.getMessageChar(); 57 | while ( c != 0 ) { 58 | message += String.fromCharCode(c); 59 | c = instance.exports.getMessageChar(); 60 | } 61 | return message; 62 | } 63 | 64 | function printSharedRWMemory () { 65 | const shared_rw_memory_size = instance.exports.getFieldNumLen32(); 66 | const arr = new Uint32Array(shared_rw_memory_size); 67 | for (let j=0; j { 103 | const h = fnvHash(k); 104 | const hMSB = parseInt(h.slice(0,8), 16); 105 | const hLSB = parseInt(h.slice(8,16), 16); 106 | const fArr = flatArray(input[k]); 107 | for (let i=0; i0) { 240 | res.unshift(0); 241 | i--; 242 | } 243 | } 244 | return res; 245 | } 246 | 247 | function fromArray32(arr) { //returns a BigInt 248 | var res = BigInt(0); 249 | const radix = BigInt(0x100000000); 250 | for (let i = 0; i