Skip to content

Instantly share code, notes, and snippets.

@lucasmenendez
Last active May 2, 2023 20:37
Show Gist options
  • Save lucasmenendez/d4a3d5c9e107a04449a04caee34e2f8b to your computer and use it in GitHub Desktop.
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.
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);
})()
{
"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"
}
}
{
"compilerOptions": {
"target": "es2020",
/* Modules */
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
/* Type Checking */
"strict": false,
"skipLibCheck": true
}
}
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"));
}
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));
}
}
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