diff --git a/libs/remix-ws-templates/src/templates/rln/index.ts b/libs/remix-ws-templates/src/templates/rln/index.ts index f3fc2a2eca..aeb3b9be59 100644 --- a/libs/remix-ws-templates/src/templates/rln/index.ts +++ b/libs/remix-ws-templates/src/templates/rln/index.ts @@ -17,6 +17,8 @@ export default async () => { // @ts-ignore 'templates/groth16_verifier.sol.ejs': (await import('!!raw-loader!./templates/groth16_verifier.sol.ejs')).default, // @ts-ignore + 'templates/plonk_verifier.sol.ejs': (await import('!!raw-loader!./templates/groth16_verifier.sol.ejs')).default, + // @ts-ignore 'LICENSE-APACHE': (await import('!!raw-loader!./LICENSE-APACHE')).default, // @ts-ignore 'LICENSE-MIT': (await import('!!raw-loader!./LICENSE-MIT')).default, diff --git a/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_trusted_setup.ts b/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_trusted_setup.ts index 0452df371f..19b9736314 100644 --- a/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_trusted_setup.ts +++ b/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_trusted_setup.ts @@ -42,7 +42,7 @@ const logger = { await remix.call('fileManager', 'writeFile', './zk/keys/groth16/verification_key.json', JSON.stringify(vKey, null, 2)) console.log('save zkey_final') - await remix.call('fileManager', 'writeFile', './zk/keys/groth16/zkey_final.txt', JSON.stringify(Array.from(((zkey_final as any).data)))) + await remix.call('fileManager', 'writeFile', './zk/keys/groth16/zkey_final.txt', (zkey_final as any).data, { encoding: null }) console.log('setup done.') diff --git a/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts b/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts index 109718273b..3c3fe6b20e 100644 --- a/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts +++ b/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts @@ -76,7 +76,7 @@ async function prove (signals, wasm, wtns, r1cs, zkey_final, vKey) { const zkey_final = { type: "mem", - data: new Uint8Array(JSON.parse(await remix.call('fileManager', 'readFile', './zk/keys/groth16/zkey_final.txt'))) + data: new Uint8Array(await remix.call('fileManager', 'readFile', './zk/keys/groth16/zkey_final.txt', { encoding: null })) } const wtns = { type: "mem" }; diff --git a/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_trusted_setup.ts b/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_trusted_setup.ts index 866cb1dac6..2cff9dabae 100644 --- a/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_trusted_setup.ts +++ b/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_trusted_setup.ts @@ -20,7 +20,8 @@ const snarkjs = require('snarkjs'); const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final) console.log('save zkey_final') - await remix.call('fileManager', 'writeFile', './zk/keys/plonk/zkey_final.txt', JSON.stringify(Array.from(((zkey_final as any).data)))) + // @ts-ignore + await remix.call('fileManager', 'writeFile', './zk/keys/plonk/zkey_final.txt', (zkey_final as any).data, { encoding: null }) console.log('save verification key') await remix.call('fileManager', 'writeFile', './zk/keys/plonk/verification_key.json', JSON.stringify(vKey, null, 2)) diff --git a/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts b/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts index 730c98135a..e6010af4dc 100644 --- a/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts +++ b/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts @@ -1,6 +1,13 @@ import { ethers, BigNumber } from 'ethers' +import { IncrementalMerkleTree } from "@zk-kit/incremental-merkle-tree" import { poseidon } from "circomlibjs" // v0.0.8 +import { ZqField } from 'ffjavascript' +const SNARK_FIELD_SIZE = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617') + +// Creates the finite field +const Fq = new ZqField(SNARK_FIELD_SIZE) + // eslint-disable-next-line @typescript-eslint/no-var-requires const snarkjs = require('snarkjs'); @@ -8,86 +15,168 @@ const logger = { info: (...args) => console.log(...args), debug: (...args) => console.log(...args), error: (...args) => console.error(...args), -}; +} + +/** + * Recovers secret from two shares + * @param x1 signal hash of first message + * @param x2 signal hash of second message + * @param y1 yshare of first message + * @param y2 yshare of second message + * @returns identity secret + */ +function shamirRecovery(x1: bigint, x2: bigint, y1: bigint, y2: bigint): bigint { + const slope = Fq.div(Fq.sub(y2, y1), Fq.sub(x2, x1)) + const privateKey = Fq.sub(y1, Fq.mul(slope, x1)) + + return Fq.normalize(privateKey) +} + +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) +} + +function hashNullifier(message: any): bigint { + return BigInt(ethers.utils.keccak256(message)) >> BigInt(8) +} + +async function prove (signals, wasm, wtns, r1cs, zkey_final, vKey) { + 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.plonk.prove(zkey_final, wtns); + + const verified = await snarkjs.plonk.verify(vKey, publicSignals, proof, logger); + console.log('zk proof validity', verified); + + await remix.call('fileManager', 'writeFile', `zk/build/plonk/input-${Date.now()}.json`, JSON.stringify({ + _pubSignals: publicSignals, + _proof: [ + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.A[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.A[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.B[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.B[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.C[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.C[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Z[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Z[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T1[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T1[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T2[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T2[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T3[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T3[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxi[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxi[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxiw[0]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxiw[1]).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_a).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_b).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_c).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_s1).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_s2).toHexString(), 32), + ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_zw).toHexString(), 32), + ] + }, null, 2)) + + console.log('proof done.') + return { + proof, + x: publicSignals[3], + y: publicSignals[0] + } +} (async () => { try { // @ts-ignore - await remix.call('circuit-compiler', 'compile', 'circuits/calculate_hash.circom'); + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln.r1cs', { encoding: null }); + // @ts-ignore + const r1cs = new Uint8Array(r1csBuffer); + // @ts-ignore + await remix.call('circuit-compiler', 'compile', 'circuits/rln.circom'); // @ts-ignore - const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash.wasm', { encoding: null }); + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln.wasm', { encoding: null }); // @ts-ignore - const wasm = new Uint8Array(wasmBuffer); + const wasm = new Uint8Array(wasmBuffer); + const zkey_final = { type: "mem", - data: new Uint8Array(JSON.parse(await remix.call('fileManager', 'readFile', './zk/keys/plonk/zkey_final.txt'))) + data: new Uint8Array(await remix.call('fileManager', 'readFile', './zk/keys/plonk/zkey_final.txt', { encoding: null })) } + const wtns = { type: "mem" }; - const wtns = { type: "mem" }; - const value1 = '1234' - const value2 = '2' - const value3 = '3' - const value4 = '4' + const vKey = JSON.parse(await remix.call('fileManager', 'readFile', './zk/keys/plonk/verification_key.json')) + + // build list of identity commitments + const secrets = [] + const identityCommitments = [] + const rateCommitments = [] + const userMessageLimit = 0x2 + for (let k = 0; k < 2; k++) { + const identitySecret = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) + secrets.push(identitySecret) + + const identityCommitment = poseidon([identitySecret]) + const rateCommitment = poseidon([identityCommitment, userMessageLimit]) + identityCommitments.push(identityCommitment) + rateCommitments.push(rateCommitment) + } - 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]) + let tree + + try { + tree = new IncrementalMerkleTree(poseidon, 20, BigInt(0), 2, rateCommitments) // Binary tree. + } catch (e) { + console.error(e.message) + return } - console.log('calculate') - await snarkjs.wtns.calculate(signals, wasm, wtns, logger); + const merkleproof1 = tree.createProof(0) + + const appId = 0xa + const epoch = 0x1 + + const signals1 = { + identitySecret: secrets[0], + userMessageLimit, + messageId: 0x0, + pathElements: merkleproof1.siblings, + identityPathIndex: merkleproof1.pathIndices, + x: 0xabcd, // hash(message) + externalNullifier: 0xa // hash(epoch, appId) + } + const proof1 = await prove(signals1, wasm, wtns, r1cs, zkey_final, vKey) - const { proof, publicSignals } = await snarkjs.plonk.prove(zkey_final, wtns); + const signals2 = { + identitySecret: secrets[0], + userMessageLimit, + messageId: 0x0, + pathElements: merkleproof1.siblings, + identityPathIndex: merkleproof1.pathIndices, + x: 0xabce, // hash(message) + externalNullifier: 0xa // hash(epoch, appId) + } + const proof2 = await prove(signals2, wasm, wtns, r1cs, zkey_final, vKey) - const vKey = JSON.parse(await remix.call('fileManager', 'readFile', './zk/keys/plonk/verification_key.json')) - - const verified = await snarkjs.plonk.verify(vKey, publicSignals, proof); + const secret = shamirRecovery(BigInt(proof1.x), BigInt(proof2.x), BigInt(proof1.y), BigInt(proof2.y)) - console.log('zk proof validity', verified); + console.log(secret.toString(10)) + console.log(Fq.normalize(secrets[0])) + const templates = { plonk: await remix.call('fileManager', 'readFile', 'templates/plonk_verifier.sol.ejs') } const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates) - await remix.call('fileManager', 'writeFile', 'zk/build/plonk/zk_verifier.sol', solidityContract) - await remix.call('fileManager', 'writeFile', 'zk/build/plonk/input.json', JSON.stringify({ - _pubSignals: publicSignals, - _proof: [ - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.A[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.A[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.B[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.B[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.C[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.C[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Z[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Z[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T1[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T1[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T2[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T2[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T3[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.T3[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxi[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxi[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxiw[0]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.Wxiw[1]).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_a).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_b).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_c).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_s1).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_s2).toHexString(), 32), - ethers.utils.hexZeroPad(ethers.BigNumber.from(proof.eval_zw).toHexString(), 32), - ] - }, null, 2)) - - console.log('proof done.') - + await remix.call('fileManager', 'writeFile', './zk/build/plonk/zk_verifier.sol', solidityContract) } catch (e) { console.error(e.message) } -})() +})() \ No newline at end of file