ð§ ZKåè·¯ã®æ§ç¯
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããããžã§ã¯ãã®å¿èéšã§ãã ãŒãç¥è蚌æã®ãåè·¯ïŒCircuitïŒã ãå®è£ ããŸãïŒ
Circom
ãšããç¹å¥ãªèšèªã䜿ã£ãŠããããç§å¯ã®ãã¹ã¯ãŒããç¥ã£ãŠããããšããäºå®ã蚌æããããã®ããžãã¯ãèšèšå³ã®ããã«å®çŸ©ãã蚌æãšæ€èšŒã«å¿
èŠãªãã¡ã€ã«ãçæããŠãããŸãã
ð Circomãšã¯ïŒâ
Circomã¯ããŒãç¥è蚌æã®åè·¯ãèšè¿°ããããã«äœããããããã° ã蚌æã®èšèšå³ããäœæããããã®èšèªãã§ãã
ç®è¡åè·¯ïŒè¶³ãç®ãæãç®ãªã©ã®çµã¿åããïŒãå®çŸ©ããããã R1CSïŒRank-1 Constraint SystemïŒ ãšãããå€ãã® ZK-SNARKs ãããã³ã«ãçè§£ã§ããæšæºåœ¢åŒã«å€æïŒã³ã³ãã€ã«ïŒããããšãã§ããŸãã
ãã®ãããžã§ã¯ãã§ã¯ãPasswordHash.circom
ãšãããã¡ã€ã«ã§ããç§å¯ã®ãã¹ã¯ãŒãããããã·ã¥åããèšç®ãæ£ããè¡ãããããš ãæ€èšŒããåè·¯ãå®çŸ©ããŸãã
âïž åè·¯ã®èšèšãšå®è£ â
ããã§ã¯ãpkgs/circuit/src/PasswordHash.circom
ãã¡ã€ã«ãäœæãã以äžã®ã³ãŒããèšè¿°ããŸãããã
ãã®åè·¯ã®ç®çã¯ããç§ã¯ãããç§å¯ã®ãã¹ã¯ãŒããç¥ã£ãŠããŸãããããŠããã®ãã¹ã¯ãŒããããã·ã¥åãããšãå ¬éãããŠãããã®ããã·ã¥å€ã«ãªããŸãã ãšããããšãããã¹ã¯ãŒããã®ãã®ãèŠããããšãªã蚌æããããšã§ãã
// pkgs/circuit/src/PasswordHash.circom
pragma circom 2.0.0;
include "../node_modules/circomlib/circuits/poseidon.circom";
/**
* ããã·ã¥å€ãçæããã¡ãœãã
*/
template PasswordCheck() {
// å
¥åå€ãååŸ
signal input password;
signal input hash;
component poseidon = Poseidon(1);
poseidon.inputs[0] <== password;
hash === poseidon.out; // ããã·ã¥å€ã®äžèŽãæ€èšŒ(true or falseãè¿ã)
}
// ãããªãã¯å
¥åïŒ ãã¹ã¯ãŒãã®ããã·ã¥å€
// ãã©ã€ããŒãå
¥åïŒ ãã¹ã¯ãŒã
component main {public [hash]} = PasswordCheck();
ð ã³ãŒã解説â
-
signal input password
:
蚌æè ïŒProverïŒãã€ãŸããã¹ã¯ãŒããç¥ã£ãŠãã人ã ããæã€ç§å¯ã®å ¥åã§ãã
ãã®å€ã¯èšŒæãçæããããã«äœ¿ãããŸãããå€éšã«ã¯äžåå ¬éãããŸããã -
signal output hash
:
蚌æè ïŒProverïŒ ãš æ€èšŒè ïŒVerifierïŒ ã®äž¡æ¹ãç¥ã£ãŠããå ¬éæ å ±ã§ãã
ãã®åè·¯ã¯ã ãpassword
ãããã·ã¥åããçµæãããã®hash
ã®å€ãšããã¿ãªäžèŽããã ããšãä¿èšŒããŸãã -
component hasher = Poseidon(1)
:
circomlib
ãšãã䟿å©ãªã©ã€ãã©ãªãããPoseidon
ããã·ã¥é¢æ°ã®æ©èœãæã€éšåïŒã³ã³ããŒãã³ãïŒãåŒã³åºããŠããŸãã
Poseidonã¯ããŒãç¥è蚌æãšéåžžã«çžæ§ãè¯ãïŒZKãã¬ã³ããªãŒãªïŒããã·ã¥é¢æ°ãšããŠåºã䜿ãããŠããŸãã -
hasher.inputs[0] <== password
:
ç§å¯ã®å ¥åpassword
ããPoseidonããã·ã¥é¢æ°ã®å ¥åãšããŠã»ããããŠããŸãã -
hash <== hasher.out
:
ãããåè·¯ã®æãéèŠãªéšåããå¶çŽïŒConstraintïŒã ã§ããããã·ã¥é¢æ°ã®èšç®çµæïŒ
hasher.out
ïŒããå ¬éãããŠããhash
ãšçãããªããã°ãªããªãããšããã«ãŒã«ã課ããŠããŸãããã®å¶çŽãæºããpassword
ãç¥ã£ãŠããããšãããããã®åè·¯ã蚌æãããå 容ãã®ãã®ãªã®ã§ãã
ð¢ åè·¯ã®ã³ã³ãã€ã«â
èšèšå³ã宿ããã®ã§ã次ã¯ãã®PasswordHash.circom
ãã¡ã€ã«ãã³ã³ãã€ã«ããŸãã
ã³ã³ãã€ã«ããããšã§ã蚌æãçæããããã«å¿
èŠãªwasm
ãã¡ã€ã« ãšãåè·¯ã®å¶çŽãæ°åŠçã«èšè¿°ããr1cs
ãã¡ã€ã« ãçæãããŸãã
ã¿ãŒããã«ã§ä»¥äžã®ã³ãã³ããå®è¡ããŠãã ããã
pnpm circuit run compile
ããã§ã¯ä»¥äžã®æ§ãªã¹ã¯ãªãããå®è¡ããããšã«ãªããŸãã
#!/bin/bash
# Variable to store the name of the circuit
CIRCUIT=PasswordHash
# In case there is a circuit name as input
if [ "$1" ]; then
CIRCUIT=$1
fi
# Compile the circuit
circom ./src/${CIRCUIT}.circom --r1cs --wasm --sym --c
ãã®ã¹ã¯ãªããã¯ãpkgs/circuit/package.json
ã«å®çŸ©ãããã¹ã¯ãªãããå®è¡ããpkgs/circuit/src/PasswordHash.circom
ãèªã¿èŸŒã¿ãŸãã
ãããŠãpkgs/circuit/build
ãã£ã¬ã¯ããªå
ã«PasswordHash.r1cs
ãšPasswordHash_js/PasswordHash.wasm
ãšãã2ã€ã®éèŠãªãã¡ã€ã«ãåºåããŸãã
æçµçã«ä»¥äžã®ãããªåºåçµæã«ãªã£ãŠããã°OKã§ãïŒ ïŒ
template instances: 71
non-linear constraints: 216
linear constraints: 199
public inputs: 1
private inputs: 1
public outputs: 0
wires: 417
labels: 583
Written successfully: ./PasswordHash.r1cs
Written successfully: ./PasswordHash.sym
Written successfully: ./PasswordHash_cpp/PasswordHash.cpp and ./PasswordHash_cpp/PasswordHash.dat
Written successfully: ./PasswordHash_cpp/main.cpp, circom.hpp, calcwit.hpp, calcwit.cpp, fr.hpp, fr.cpp, fr.asm and Makefile
Written successfully: ./PasswordHash_js/PasswordHash.wasm
Everything went okay
ð ãã¹ã¯ãŒãã®èšå®ãšå ¥åããŒã¿ã®çæâ
次ã«ã蚌æããããç§å¯ã®ãã¹ã¯ãŒãããå ·äœçã«èšå®ãããããåè·¯ãžã®å ¥åããŒã¿ãšããŠJSON圢åŒã§çæããŸãã
ã¹ã¯ãªããã®åœ¹å²â
pkgs/circuit/scripts/generateInput.js
ã¯ãç§ãã¡ãèšå®ãããã¹ã¯ãŒããPoseidonããã·ã¥é¢æ°ã§èšç®ããåè·¯ãžã®å
¥åãšãªãinput.json
ãã¡ã€ã«ãèªåã§äœæããŠããã䟿å©ãªãã«ããŒã¹ã¯ãªããã§ãã
// pkgs/circuit/scripts/generateInput.js
const { buildPoseidon } = require("circomlibjs");
const fs = require("fs");
const path = require("path");
async function main() {
// Poseidonããã·ã¥é¢æ°ã䜿ããããã«æºå
const poseidon = await buildPoseidon();
// ãããä»åã®ãç§å¯ã®åèšèã
const input = "serverless";
// ãã¹ã¯ãŒããåè·¯ãæ±ããæ°å€ïŒBigIntïŒã«å€æ
// 泚æ: å®éã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãããå®å
šãªæ¹æ³ã§æååããã£ãŒã«ãèŠçŽ ã«å€æããããšãæšå¥šãããŸã
const passwordNumber = BigInt(
Buffer.from(input).toString("hex"),
16
).toString();
// ãã¹ã¯ãŒãã®ããã·ã¥å€ãèšç®
const hash = poseidon.F.toString(poseidon([passwordNumber]));
console.log(`Input (passwordNumber): ${passwordNumber}`);
console.log(`Hash: ${hash}`);
// input.jsonãã¡ã€ã«ãäœæããããã®ããŒã¿æ§é
const inputs = {
password: passwordNumber,
hash: hash,
};
fs.writeFileSync(
path.join(__dirname, "../data/input.json"),
JSON.stringify(inputs, null, 2)
);
}
main().catch(console.error);
å ¥åããŒã¿ã®çææé â
-
ãã¹ã¯ãŒãã®ç·šéïŒä»»æïŒ:
pkgs/circuit/scripts/generateInput.js
ãã¡ã€ã«ãéããinput
倿°ã®å€ã奜ããªãã¹ã¯ãŒãïŒè±æ°åïŒã«å€æŽã§ããŸããconst input = "serverless"; // ð ããã奜ããªãã¹ã¯ãŒãã«å€æŽ
-
å ¥åããŒã¿ã®çæ: ã¹ã¯ãªãããå®è¡ããŠã
input.json
ãçæããŸããpnpm circuit run generateInput
以äžã®ãããªåºåçµæãåŸãããã°OKã§ãïŒ
InputData: {
input: 'serverless',
inputNumber: '544943514572763559195507',
hash: '15164376112847237158131942621891884356916177189690069125192984001689200900025'
}ãã®ã³ãã³ãã¯
generateInput.js
ãå®è¡ããpkgs/circuit/data/input.json
ã«ä»¥äžã®ãããªå å®¹ãæžã蟌ã¿ãŸãã{
"password": "YOUR_GENERATED_PASSWORD_NUMBER",
"hash": "YOUR_GENERATED_HASH_VALUE"
}ã¿ãŒããã«ã«åºåããã
hash
ã®å€ã¯ãåŸã§ããã³ããšã³ãã®ç°å¢å€æ°PASSWORD_HASH
ã«èšå®ããŸãã®ã§ãæ§ããŠãããŠãã ããã
ð 蚌æããŒãšæ€èšŒããŒã®ç æïŒGroth16ïŒâ
次ã«ãGroth16
ãšããZK-SNARKsãããã³ã«ã䜿ã£ãŠã蚌æããŒïŒProving KeyïŒãšæ€èšŒããŒïŒVerification KeyïŒãçæããŸãã
ãã®ããã»ã¹ã¯ ãTrusted Setupã ãšãåŒã°ããŸãã
以äžã®ã³ãã³ããå®è¡ããŠãã ããã
pnpm circuit run executeGroth16
å®è¡ãããã¹ã¯ãªããã¯æ¬¡ã®ãããªå 容ã§ãã
#!/bin/bash
# Variable to store the name of the circuit
CIRCUIT=PasswordHash
# Variable to store the number of the ptau file
PTAU=14
# In case there is a circuit name as an input
if [ "$1" ]; then
CIRCUIT=$1
fi
# In case there is a ptau file number as an input
if [ "$2" ]; then
PTAU=$2
fi
# Check if the necessary ptau file already exists. If it does not exist, it will be downloaded from the data center
if [ -f ./ptau/powersOfTau28_hez_final_${PTAU}.ptau ]; then
echo "----- powersOfTau28_hez_final_${PTAU}.ptau already exists -----"
else
echo "----- Download powersOfTau28_hez_final_${PTAU}.ptau -----"
wget -P ./ptau https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_${PTAU}.ptau
fi
# Compile the circuit
circom ./src/${CIRCUIT}.circom --r1cs --wasm --sym --c
# Generate the witness.wtns
node ${CIRCUIT}_js/generate_witness.js ${CIRCUIT}_js/${CIRCUIT}.wasm ./data/input.json ${CIRCUIT}_js/witness.wtns
echo "----- Generate .zkey file -----"
# Generate a .zkey file that will contain the proving and verification keys together with all phase 2 contributions
snarkjs groth16 setup ${CIRCUIT}.r1cs ptau/powersOfTau28_hez_final_${PTAU}.ptau ./zkey/${CIRCUIT}_0000.zkey
echo "----- Contribute to the phase 2 of the ceremony -----"
# Contribute to the phase 2 of the ceremony
snarkjs zkey contribute ./zkey/${CIRCUIT}_0000.zkey ./zkey/${CIRCUIT}_final.zkey --name="1st Contributor Name" -v -e="some random text"
echo "----- Export the verification key -----"
# Export the verification key
snarkjs zkey export verificationkey ./zkey/${CIRCUIT}_final.zkey ./zkey/verification_key.json
echo "----- Generate zk-proof -----"
# Generate a zk-proof associated to the circuit and the witness. This generates proof.json and public.json
snarkjs groth16 prove ./zkey/${CIRCUIT}_final.zkey ${CIRCUIT}_js/witness.wtns ./data/proof.json ./data/public.json
echo "----- Verify the proof -----"
# Verify the proof
snarkjs groth16 verify ./zkey/verification_key.json ./data/public.json ./data/proof.json
echo "----- Generate Solidity verifier -----"
# Generate a Solidity verifier that allows verifying proofs on Ethereum blockchain
snarkjs zkey export solidityverifier ./zkey/${CIRCUIT}_final.zkey ${CIRCUIT}Verifier.sol
# Update the solidity version in the Solidity verifier
sed 's/0.6.11;/0.8.20;/g' ${CIRCUIT}Verifier.sol > ${CIRCUIT}Verifier2.sol
# Update the contract name in the Solidity verifier
sed "s/contract Verifier/contract ${CIRCUIT}Verifier/g" ${CIRCUIT}Verifier2.sol > ${CIRCUIT}Verifier.sol
rm ${CIRCUIT}Verifier2.sol
echo "----- Generate and print parameters of call -----"
# Generate and print parameters of call
snarkjs generatecall data/public.json data/proof.json | tee ./data/calldata.json
以äžã®ãããªåºåçµæãåºãŠããã°OKã§ãïŒ ïŒ
----- Export the verification key -----
[INFO] snarkJS: EXPORT VERIFICATION KEY STARTED
[INFO] snarkJS: > Detected protocol: groth16
[INFO] snarkJS: EXPORT VERIFICATION KEY FINISHED
----- Generate zk-proof -----
----- Verify the proof -----
[INFO] snarkJS: OK!
----- Generate Solidity verifier -----
[INFO] snarkJS: EXPORT VERIFICATION KEY STARTED
[INFO] snarkJS: > Detected protocol: groth16
[INFO] snarkJS: EXPORT VERIFICATION KEY FINISHED
----- Generate and print parameters of call -----
["0x15ae6792cbe82731dfdcef2012a32cf9e2d1d81d6a71086ad14afd229bbab166", "0x1e5fade2062037da2c5309da71a6d5fd7da656274e358e71603579720fde74ee"],[["0x110f6f6bc5846e31b20b1e1cacb8a431759640644e56a342d90fee20b51d961f", "0x204970c9f05d739f5ae4b20d0b086c779cbfa69a3fc23acfb3df558d2fb6abb9"],["0x10b3a650597a08686299d817a1f7868a26d0eb1699117deb01cbb052d7262755", "0x02ca1580581ed8b05f10db03aaaa3479b1b50024574bc1cb894d58ec535b634d"]],["0x0dc789b08391b6a5150a4435b9fa0193baffb7a63ef01780b0a4c89da576e587", "0x19c5d7b33a7b1a14eb61d8147b3bab95d25abaee0b830ef8c34ad110093ee85b"],["0x2186bb937db8faf41e671589c116c1f8491df908baaf0da11aedd7fedb5437b9"]
ãã®ã¹ã¯ãªããã¯ãå éšã§ããã€ãã®ã¹ããããå®è¡ããŸãã
-
Powers of Tau:
äžè¬çãªã»ããã¢ãããã¡ã€ã«ãããŠã³ããŒãããŸãã -
Proving Keyã®çæ:
PasswordHash_final.zkey
ãšãã蚌æããŒãçæããŸãã
ããã¯ã蚌æãçæããéã«å¿ èŠãšãªããŸãã -
Verification Keyã®çæ:
verification_key.json
ãšããæ€èšŒããŒãçæããŸãã
ããã¯ã蚌æãæ€èšŒããéã«å¿ èŠãšãªããŸãã -
蚌æããŒã¿ã®çæ:
proof.json
ïŒèšŒæããŒã¿ïŒãšpublic.json
ïŒå ¬éã·ã°ãã«ïŒãçæããŸãã
ããã«ãSolidityã³ã³ãã©ã¯ãã§ã®æ€èšŒã«äœ¿çšã§ãã圢åŒã®calldata.json
ãåºåããŸãã -
Solidity Verifierã®çæ:
PasswordHashVerifier.sol
ãšããããªã³ãã§ãŒã³ã§èšŒæãæ€èšŒããããã®ã¹ããŒãã³ã³ãã©ã¯ããçæããŸãã
ð§Ÿ Witnessã®èšç®â
Witness
ã¯ãç¹å®ã®å
¥åïŒæã
ã®å Žåã¯ãã¹ã¯ãŒãïŒã«å¯Ÿããåè·¯ã®äžéç¶æ
ããã¹ãŠå«ãã ããŒã¿ã§ãã
蚌æãçæããããã«å¿ èŠãšãªããŸãã
以äžã®ã³ãã³ãã§witness.wtns
ãã¡ã€ã«ãçæããŸãã
pnpm circuit run generateWitness
ããã§ã¯ä»¥äžã®ãããªã¹ã¯ãªãããå®è¡ãããŸã
#!/bin/bash
# Variable to store the name of the circuit
CIRCUIT=PasswordHash
# In case there is a circuit name as input
if [ "$1" ]; then
CIRCUIT=$1
fi
# Compile the circuit
circom ./src/${CIRCUIT}.circom --r1cs --wasm --sym --c
# Generate the witness.wtns
node ${CIRCUIT}_js/generate_witness.js ${CIRCUIT}_js/${CIRCUIT}.wasm ./data/input.json ${CIRCUIT}_js/witness.wtns
以äžã®ãããªåºåçµæãåºãŠããã°OKã§ãïŒ ïŒ
template instances: 71
non-linear constraints: 216
linear constraints: 199
public inputs: 1
private inputs: 1
public outputs: 0
wires: 417
labels: 583
Written successfully: ./PasswordHash.r1cs
Written successfully: ./PasswordHash.sym
Written successfully: ./PasswordHash_cpp/PasswordHash.cpp and ./PasswordHash_cpp/PasswordHash.dat
Written successfully: ./PasswordHash_cpp/main.cpp, circom.hpp, calcwit.hpp, calcwit.cpp, fr.hpp, fr.cpp, fr.asm and Makefile
Written successfully: ./PasswordHash_js/PasswordHash.wasm
Everything went okay
â åè·¯ã®ãã¹ãâ
ãããŸã§ã®ã¹ããããæ£ããå®äºãããã確èªããããã«ããã¹ããå®è£ ããŸããã
pkgs/circuit/test/verify.test.js
ãäœæããŠä»¥äžã®ã³ãŒããã³ããŒ&ããŒã¹ãããŠãã ããã
const snarkjs = require("snarkjs");
const fs = require("fs");
/**
* æ€èšŒçšã®ãµã³ãã«ã¹ã¯ãªãã
*/
async function run() {
// åãã¡ã€ã«ãŸã§ã®ãã¹
const WASM_PATH = "./PasswordHash_js/PasswordHash.wasm";
const ZKEY_PATH = "./zkey/PasswordHash_final.zkey";
const VKEY_PATH = "./zkey/verification_key.json";
// input data
const inputData = JSON.parse(fs.readFileSync("./data/input.json"));
console.log("Input Data: ");
console.log(inputData);
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
inputData,
WASM_PATH,
ZKEY_PATH,
);
console.log("Proof: ");
console.log(JSON.stringify(proof, null, 1));
const vKey = JSON.parse(fs.readFileSync(VKEY_PATH));
// æ€èšŒ
const res = await snarkjs.groth16.verify(vKey, publicSignals, proof);
if (res === true) {
console.log("Verification OK");
} else {
console.log("Invalid proof");
}
}
run().then(() => {
process.exit(0);
});
ãã®ãã¹ãã¯ãçæããããŒãšWitnessã䜿ã£ãŠãå®éã«èšŒæãæ£ããçæã»æ€èšŒã§ãããã確èªããŸãã
以äžã®ã³ãã³ãã§ãã¹ããå®è¡ããŸãã
pnpm circuit run test
ã¿ãŒããã«ã«ä»¥äžã®ãããªå 容ãåºåãããŠããã°OKã§ãïŒ ïŒ
Verification OK
ðŠ ã¢ãŒãã£ãã¡ã¯ãã®ã³ããŒâ
æåŸã«ãçæãããã¢ãŒãã£ãã¡ã¯ãïŒPasswordHashVerifier.sol
ãPasswordHash.wasm
ãPasswordHash_final.zkey
ïŒããbackend
ãšfrontend
ã®åã³ã³ããŒãã³ãããåç
§ã§ããå Žæã«ã³ããŒããŸãã
# æ€èšŒã³ã³ãã©ã¯ããbackendã«ã³ããŒ
pnpm circuit run cp:verifier
# ZKé¢é£ãã¡ã€ã«ãbackendãšfrontendã«ã³ããŒ
pnpm circuit run cp:zk
ããã§ããŒãç¥è蚌æåè·¯ã®æ§ç¯ã¯å®äºã§ãïŒ
次ã®ã»ã¯ã·ã§ã³ã§ã¯ãããã§çæããPasswordHashVerifier.sol
ã䜿ã£ãŠãã¹ããŒãã³ã³ãã©ã¯ããéçºããŠãããŸãã
ðââïž è³ªåããâ
ãããŸã§ã®äœæ¥ã§äœãããããªãããšãããå Žåã¯ãDiscordã®#zk
ã§è³ªåãããŠãã ããã
ãã«ãããããšãã®ãããŒãåæ»ã«ãªãã®ã§ããšã©ãŒã¬ããŒãã«ã¯äžèšã®3ç¹ãèšèŒããŠãã ãã âš
- 質åãé¢é£ããŠããã»ã¯ã·ã§ã³çªå·ãšã¬ãã¹ã³çªå·
- äœãããããšããŠããã
- ãšã©ãŒæãã³ããŒ&ããŒã¹ã
- ãšã©ãŒç»é¢ã®ã¹ã¯ãªãŒã³ã·ã§ãã