Last active
May 2, 2023 20:37
-
-
Save lucasmenendez/d4a3d5c9e107a04449a04caee34e2f8b to your computer and use it in GitHub Desktop.
[Vocdoni] Script to generate the zkAddress and circuit inputs with the correct encoding on TypeScript.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ZkAddress } from "./zkAddress.js"; | |
import { CensusGenProofZk } from "./zkCensus.js"; | |
(async function() { | |
// Create a ZkAddress instance from a provided string | |
const accountPrivateKey = "6430ab787ad5130942369901498a118fade013ebab5450efbfb6acac66d8fb88"; | |
const electionId = "c5d2460186f760d51371516148fd334b4199052f01538553aa9a020200000000"; | |
const censusRoot = "21f20a61be6bb9415b777367989313a2640109990d187e397fa74256361f0e11"; | |
const zkAddr = await ZkAddress.FromString(accountPrivateKey); | |
const zkProof = await CensusGenProofZk(zkAddr, censusRoot, electionId); | |
console.log(zkProof); | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"scripts": { | |
"build": "tsc", | |
"start": "node main.js" | |
}, | |
"type": "module", | |
"dependencies": { | |
"blake-hash": "^2.0.0", | |
"circomlibjs": "^0.1.7", | |
"ffjavascript": "^0.2.57", | |
"snarkjs": "^0.5.0" | |
}, | |
"devDependencies": { | |
"@types/node": "^18.13.0" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
{ | |
"compilerOptions": { | |
"target": "es2020", | |
/* Modules */ | |
"module": "ESNext", | |
"moduleResolution": "node", | |
"esModuleInterop": true, | |
"forceConsistentCasingInFileNames": true, | |
/* Type Checking */ | |
"strict": false, | |
"skipLibCheck": true | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as crypto from "crypto"; | |
export function littleEndianToNBytes(num : bigint, nBytes : number): bigint { | |
const strNum = BigInt(num).toString(2); | |
const nBits = nBytes * 8; | |
// little endian | |
const m = strNum.length - nBits; | |
return BigInt("0b"+strNum.slice(m)); | |
} | |
export function arboHash(input : string): string[] { | |
const inputBuff = Buffer.from(input, "hex"); | |
const inputHash = crypto.createHash("sha256").update(inputBuff).digest(); | |
return [ | |
BigInt("0x" + inputHash.subarray(0, 16).reverse().toString("hex")).toString(), | |
BigInt("0x" + inputHash.subarray(16, 32).reverse().toString("hex")).toString() | |
]; | |
} | |
export function bigIntToStr(bi : bigint): string { | |
const hex = bi.toString(16); | |
return (hex.length % 2 != 0) ? "0" + hex : hex; | |
} | |
export function strToBigInt(str : string): bigint { | |
const strBuff = Buffer.from(str, "hex"); | |
return BigInt("0x"+ strBuff.reverse().toString("hex")); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { buildPoseidon, buildEddsa, buildBabyjub } from "circomlibjs"; | |
import { Scalar, utils } from "ffjavascript"; | |
import createBlakeHash from "blake-hash"; | |
import { arboHash, littleEndianToNBytes } from "./utils.js"; | |
export const ZkAddressDefaultLenght : number = 20; | |
export class ZkAddress { | |
private _privateKey : bigint; | |
private _publicKey : bigint; | |
private _address : bigint; | |
private _bbjbjb : any; | |
private _poseidon : any; | |
private _eddsa : any; | |
public get privateKey(): string { | |
return this._privateKey.toString(); | |
} | |
public get publicKey(): string { | |
return this._publicKey.toString(); | |
} | |
public static async FromString(accountPrivateKey : string) : Promise<ZkAddress> { | |
const self = new ZkAddress() | |
self._bbjbjb = await buildBabyjub(); | |
self._poseidon = await buildPoseidon(); | |
self._eddsa = await buildEddsa(); | |
self.calcPrivateKey(accountPrivateKey); | |
self.calcPublicKey(); | |
self.calcAddress(); | |
return self; | |
} | |
public getNullifier(electionId : string) : string { | |
const electionHash = arboHash(electionId); | |
const nullifierHash = this._poseidon([ | |
this._privateKey, | |
electionHash[0], | |
electionHash[1] | |
]); | |
return this._poseidon.F.toObject(nullifierHash).toString(); | |
} | |
public toString(): string { | |
const hexAddr = this._address.toString(16); | |
const buffAddr = Buffer.from(hexAddr, "hex").reverse(); | |
return buffAddr.toString("hex"); | |
} | |
private calcPrivateKey(seed : string) { | |
// Get buffSeed from hexadecimal string | |
const buffSeed = Buffer.from(seed, "hex"); | |
// Instance the EDDSA curve with the blake512 hash of the accPrivKey (trim it at 32 bytes) | |
const privateKeyHash = this._eddsa.pruneBuffer(createBlakeHash("blake512").update(buffSeed).digest().slice(0, 32)); | |
// Return the Little Endian BigInt representation of the hash, shifted 3 bytes to the right | |
this._privateKey = Scalar.shr(utils.leBuff2int(privateKeyHash), 3); | |
} | |
private calcPublicKey() { | |
const A = this._bbjbjb.mulPointEscalar(this._bbjbjb.Base8, this._privateKey.toString()); | |
const rawPubKey = this._poseidon([A[0], A[1]]); | |
this._publicKey = BigInt(utils.stringifyFElements(this._poseidon.F, rawPubKey)); | |
} | |
private calcAddress() { | |
const rawAddress = littleEndianToNBytes(this._publicKey, ZkAddressDefaultLenght); | |
this._address = BigInt(utils.stringifyFElements(this._poseidon.F, rawAddress)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { verify } from "crypto"; | |
import { arboHash, bigIntToStr, strToBigInt } from "./utils.js"; | |
import { ZkAddress } from "./zkAddress.js"; | |
import * as snarkjs from "snarkjs"; | |
export async function CensusGenProofZk(zkAddr : ZkAddress, censusRoot : string, electionId : string) : Promise<any> { | |
// TODO: make a http request to /censuses/{censusRoot}/type to check if the | |
// current census is a zkweighted one. | |
// TODO: make a http request to /censuses/{censusRoot}/proof/{zkAddr} to get | |
// the correct formated siblings and vote weight. | |
const mockCensusSiblings = ["580248495380568564123114759404848148595751514101110571080218449954471889552","633773650715998492339991508741183573324294111862264796224228057688802848801","4989687640539066742397958643613126020089632025064585620245735039080884277747","3863931244154987782138626341091989752721559524607603934673141845608721144566","4688361268219000735189072482478199217683515606859266889918935760406393255791","13548759886643907861771470850568634627625204202812868538547476494298706407763","2714600331130374821301177119574534122901226309274343707259485383900427657102","11263980701448890785172384726839875656945350242204041401041770089765910047533","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"]; | |
const mockWeight = 10n | |
const strCensusRoot = strToBigInt(censusRoot).toString(); | |
const circuitInputs = { | |
censusRoot: strCensusRoot, | |
censusSiblings: mockCensusSiblings, | |
weight: mockWeight.toString(), | |
privateKey: zkAddr.privateKey, | |
voteHash: arboHash(bigIntToStr(mockWeight)), | |
processId: arboHash(electionId), | |
nullifier: zkAddr.getNullifier(electionId) | |
} | |
// This line assumes that the circuit wasm and proving key artifacts are | |
// downloaded and stored into the same folder that this file. | |
const { proof, publicSignals } = await snarkjs.groth16.fullProve(circuitInputs, "./circuit.wasm", "./proving_key.zkey"); | |
return { | |
proof, | |
publicSignals, | |
weight: mockWeight, | |
nullifier: zkAddr.getNullifier(electionId) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment