|
|
|
@ -1,6 +1,7 @@ |
|
|
|
|
import { ethers, BigNumber } from 'ethers' |
|
|
|
|
import { IncrementalMerkleTree } from "@zk-kit/incremental-merkle-tree" |
|
|
|
|
import { poseidon } from "circomlibjs" // v0.0.8
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
import { Poseidon, Tree, computeEffEcdsaPubInput } from "@personaelabs/spartan-ecdsa" |
|
|
|
|
// @ts-ignore
|
|
|
|
|
import { privateToPublic, hashPersonalMessage, ecsign, utf8ToBytes, bytesToHex } from "@ethereumjs/util" |
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
|
|
|
const snarkjs = require('snarkjs'); |
|
|
|
@ -11,88 +12,107 @@ const logger = { |
|
|
|
|
error: (...args) => console.error(...args), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. |
|
|
|
|
* @param message The message to be hashed. |
|
|
|
|
* @returns The message digest. |
|
|
|
|
*/ |
|
|
|
|
function hash(message: any): bigint { |
|
|
|
|
message = BigNumber.from(message).toTwos(256).toHexString() |
|
|
|
|
message = ethers.utils.zeroPad(message, 32) |
|
|
|
|
return BigInt(ethers.utils.keccak256(message)) >> BigInt(8) |
|
|
|
|
function getEffEcdsaCircuitInput (privKey, msg) { |
|
|
|
|
const msgHash = hashPersonalMessage(msg) |
|
|
|
|
const { v, r: _r, s } = ecsign(msgHash, privKey) |
|
|
|
|
const r = BigInt('0x' + _r.map(byte => byte.toString(16).padStart(2, '0')).join('')) |
|
|
|
|
const circuitPubInput = computeEffEcdsaPubInput(r, v, msgHash) |
|
|
|
|
|
|
|
|
|
const input = { |
|
|
|
|
s: BigInt('0x' + s.map(byte => byte.toString(16).padStart(2, '0')).join('')), |
|
|
|
|
Tx: circuitPubInput.Tx, |
|
|
|
|
Ty: circuitPubInput.Ty, |
|
|
|
|
Ux: circuitPubInput.Ux, |
|
|
|
|
Uy: circuitPubInput.Uy |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return input |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
(async () => { |
|
|
|
|
try { |
|
|
|
|
// @ts-ignore
|
|
|
|
|
const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true); |
|
|
|
|
const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/instances/.bin/pubkey_membership.r1cs', true) |
|
|
|
|
// @ts-ignore
|
|
|
|
|
const r1cs = new Uint8Array(r1csBuffer); |
|
|
|
|
const r1cs = new Uint8Array(r1csBuffer) |
|
|
|
|
// @ts-ignore
|
|
|
|
|
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.wasm', true); |
|
|
|
|
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/instances/.bin/pubkey_membership.wasm', true) |
|
|
|
|
// @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/build/zk_setup.txt'))) |
|
|
|
|
} |
|
|
|
|
const wtns = { type: "mem" };
|
|
|
|
|
const wtns = { type: "mem" } |
|
|
|
|
|
|
|
|
|
const vKey = JSON.parse(await remix.call('fileManager', 'readFile', './zk/build/verification_key.json')) |
|
|
|
|
|
|
|
|
|
// build list of identity commitments
|
|
|
|
|
const secrets = [] |
|
|
|
|
const identityCommitments = [] |
|
|
|
|
for (let k = 0; k < 2; k++) {
|
|
|
|
|
const identityTrapdoor = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) |
|
|
|
|
const identityNullifier = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) |
|
|
|
|
secrets.push({identityTrapdoor, identityNullifier}) |
|
|
|
|
|
|
|
|
|
const secret = poseidon([identityNullifier, identityTrapdoor]) |
|
|
|
|
const identityCommitment = poseidon([secret]) |
|
|
|
|
identityCommitments.push(identityCommitment) |
|
|
|
|
} |
|
|
|
|
//console.log('incremental tree', identityCommitments.map((x) => x.toString()))
|
|
|
|
|
// Construct the tree
|
|
|
|
|
const poseidon = new Poseidon() |
|
|
|
|
|
|
|
|
|
await poseidon.initWasm() |
|
|
|
|
|
|
|
|
|
let tree |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
tree = new IncrementalMerkleTree(poseidon, 20, BigInt(0), 2, identityCommitments) // Binary tree.
|
|
|
|
|
} catch (e) { |
|
|
|
|
console.error(e.message) |
|
|
|
|
return |
|
|
|
|
const nLevels = 10 |
|
|
|
|
const tree = new Tree(nLevels, poseidon) |
|
|
|
|
|
|
|
|
|
console.log('done') |
|
|
|
|
|
|
|
|
|
const privKeys = [ |
|
|
|
|
utf8ToBytes("".padStart(16, "🧙")), |
|
|
|
|
utf8ToBytes("".padStart(16, "🪄")), |
|
|
|
|
utf8ToBytes("".padStart(16, "🔮")) |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
// Store public key hashes
|
|
|
|
|
const pubKeyHashes: bigint[] = [] |
|
|
|
|
|
|
|
|
|
// Compute public key hashes
|
|
|
|
|
for (const privKey of privKeys) { |
|
|
|
|
const pubKey = privateToPublic(privKey) |
|
|
|
|
const pubKeyHex = bytesToHex(pubKey) |
|
|
|
|
const hexWithoutPrefix = pubKeyHex.startsWith('0x') ? pubKeyHex.slice(2) : pubKeyHex; |
|
|
|
|
const pubKeyHash = poseidon.hashPubKey(hexWithoutPrefix) |
|
|
|
|
|
|
|
|
|
pubKeyHashes.push(pubKeyHash) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Insert the pubkey hashes into the tree
|
|
|
|
|
for (const pubKeyHash of pubKeyHashes) { |
|
|
|
|
tree.insert(pubKeyHash) |
|
|
|
|
} |
|
|
|
|
const index = tree.indexOf(identityCommitments[0]) |
|
|
|
|
|
|
|
|
|
console.log(index.toString()) |
|
|
|
|
|
|
|
|
|
const proof1 = tree.createProof(0) |
|
|
|
|
// Sign
|
|
|
|
|
const index = 0; // Use privKeys[0] for proving
|
|
|
|
|
const privKey = privKeys[index] |
|
|
|
|
const msg = utf8ToBytes("hello world".padStart(16, " ")) |
|
|
|
|
|
|
|
|
|
// Prepare signature proof input
|
|
|
|
|
const effEcdsaInput = getEffEcdsaCircuitInput(privKey, msg) |
|
|
|
|
|
|
|
|
|
const merkleProof = tree.createProof(index) |
|
|
|
|
|
|
|
|
|
console.log('prepare signals for id ', identityCommitments[0].toString(), tree.indexOf(identityCommitments[0]), proof1.siblings.map((x)=> x.toString())) |
|
|
|
|
|
|
|
|
|
const signals = { |
|
|
|
|
identityTrapdoor: secrets[0].identityTrapdoor, |
|
|
|
|
identityNullifier: secrets[0].identityNullifier, |
|
|
|
|
treePathIndices: proof1.pathIndices, |
|
|
|
|
treeSiblings: proof1.siblings, |
|
|
|
|
externalNullifier: hash(42), |
|
|
|
|
signalHash: hash(ethers.utils.formatBytes32String("Hello World")) |
|
|
|
|
...effEcdsaInput, |
|
|
|
|
siblings: merkleProof.siblings, |
|
|
|
|
pathIndices: merkleProof.pathIndices, |
|
|
|
|
root: tree.root() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
console.log('signals: ', signals) |
|
|
|
|
|
|
|
|
|
console.log('calculate') |
|
|
|
|
await snarkjs.wtns.calculate(signals, wasm, wtns); |
|
|
|
|
await snarkjs.wtns.calculate(signals, wasm, wtns) |
|
|
|
|
|
|
|
|
|
console.log('check') |
|
|
|
|
await snarkjs.wtns.check(r1cs, wtns, logger); |
|
|
|
|
// console.log('check')
|
|
|
|
|
// await snarkjs.wtns.check(r1cs, wtns, logger)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('prove') |
|
|
|
|
const { proof, publicSignals } = await snarkjs.groth16.prove(zkey_final, wtns); |
|
|
|
|
// console.log('prove')
|
|
|
|
|
// const { proof, publicSignals } = await snarkjs.groth16.prove(zkey_final, wtns);
|
|
|
|
|
|
|
|
|
|
const verified = await snarkjs.groth16.verify(vKey, publicSignals, proof, logger); |
|
|
|
|
console.log('zk proof validity', verified); |
|
|
|
|
proof1.root.toString() === publicSignals[0] ? console.log('merkle proof valid') : console.log('merkle proof invalid') |
|
|
|
|
// const verified = await snarkjs.groth16.verify(vKey, publicSignals, proof, logger);
|
|
|
|
|
// console.log('zk proof validity', verified);
|
|
|
|
|
// proof1.root.toString() === publicSignals[0] ? console.log('merkle proof valid') : console.log('merkle proof invalid')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|