Hardcode wasm into ts file & add script for embedded wasm ops

This commit is contained in:
Daniel Tehrani
2023-01-29 21:10:50 +09:00
parent f6ffbb4fc8
commit d1f54a6a8c
18 changed files with 65 additions and 161 deletions

1
.eslintignore Normal file
View File

@@ -0,0 +1 @@
wasm_bytes.ts

4
.gitignore vendored
View File

@@ -36,4 +36,6 @@ packages/prover/test_circuit/*.json
packages/lib/src/circuits/
packages/poseidon/sage/*.py
packages/poseidon/sage/*.py
wasm_bytes.ts

1
.prettierignore Normal file
View File

@@ -0,0 +1 @@
wasm_bytes.ts

View File

@@ -4,12 +4,10 @@ import {
ecsign
} from "@ethereumjs/util";
import {
SpartanWasm,
Tree,
Poseidon,
MembershipProver,
defaultAddressMembershipConfig,
defaultWasmConfig
defaultAddressMembershipConfig
} from "@personaelabs/spartan-ecdsa";
const benchAddrMembership = async () => {
@@ -20,11 +18,9 @@ const benchAddrMembership = async () => {
const { v, r, s } = ecsign(msgHash, privKey);
const sig = `0x${r.toString("hex")}${s.toString("hex")}${v.toString(16)}`;
let wasm = new SpartanWasm(defaultWasmConfig);
// Init the Poseidon hash
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
await poseidon.initWasm();
const treeDepth = 20;
const tree = new Tree(treeDepth, poseidon);
@@ -57,7 +53,7 @@ const benchAddrMembership = async () => {
...defaultAddressMembershipConfig,
enableProfiler: true
});
await prover.initWasm(wasm);
await prover.initWasm();
// Prove membership
await prover.prove(sig, msgHash, merkleProof);

View File

@@ -2,8 +2,6 @@ import {
MembershipProver,
Poseidon,
Tree,
SpartanWasm,
defaultWasmConfig,
defaultPubkeyMembershipConfig
} from "@personaelabs/spartan-ecdsa";
import {
@@ -22,11 +20,9 @@ const benchPubKeyMembership = async () => {
const pubKey = ecrecover(msgHash, v, r, s);
const sig = `0x${r.toString("hex")}${s.toString("hex")}${v.toString(16)}`;
let wasm = new SpartanWasm(defaultWasmConfig);
// Init the Poseidon hash
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
await poseidon.initWasm();
const treeDepth = 20;
const tree = new Tree(treeDepth, poseidon);
@@ -54,7 +50,7 @@ const benchPubKeyMembership = async () => {
...defaultPubkeyMembershipConfig,
enableProfiler: true
});
await prover.initWasm(wasm);
await prover.initWasm();
// Prove membership
await prover.prove(sig, msgHash, merkleProof);

View File

@@ -3,9 +3,7 @@ import {
MembershipProver,
Tree,
Poseidon,
SpartanWasm,
defaultAddressMembershipConfig,
defaultWasmConfig,
defaultPubkeyMembershipConfig
} from "@personaelabs/spartan-ecdsa";
import {
@@ -29,9 +27,8 @@ export default function Home() {
const pubKey = ecrecover(msgHash, v, r, s);
const sig = `0x${r.toString("hex")}${s.toString("hex")}${v.toString(16)}`;
const wasm = new SpartanWasm(defaultWasmConfig);
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
await poseidon.initWasm();
const treeDepth = 20;
const pubKeyTree = new Tree(treeDepth, poseidon);
@@ -55,8 +52,7 @@ export default function Home() {
console.time("Full proving time");
const prover = new MembershipProver(defaultPubkeyMembershipConfig);
prover.initWasm(wasm);
await prover.initWasm();
const { proof, publicInput } = await prover.prove(
sig,
@@ -81,9 +77,7 @@ export default function Home() {
const { v, r, s } = ecsign(msgHash, privKey);
const sig = `0x${r.toString("hex")}${s.toString("hex")}${v.toString(16)}`;
const wasm = new SpartanWasm(defaultWasmConfig);
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
const treeDepth = 20;
const addressTree = new Tree(treeDepth, poseidon);
@@ -113,7 +107,7 @@ export default function Home() {
enableProfiler: true
});
prover.initWasm(wasm);
await prover.initWasm();
const { proof, publicInput } = await prover.prove(
sig,

View File

@@ -2,12 +2,7 @@ const wasm_tester = require("circom_tester").wasm;
var EC = require("elliptic").ec;
import * as path from "path";
const ec = new EC("secp256k1");
import {
Poseidon,
Tree,
SpartanWasm,
defaultWasmConfig
} from "@personaelabs/spartan-ecdsa";
import { Poseidon, Tree } from "@personaelabs/spartan-ecdsa";
import { getEffEcdsaCircuitInput } from "./test_utils";
import { privateToAddress } from "@ethereumjs/util";
@@ -21,11 +16,9 @@ describe("membership", () => {
}
);
const wasm = new SpartanWasm(defaultWasmConfig);
// Construct the tree
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
await poseidon.initWasm();
const nLevels = 10;
const tree = new Tree(nLevels, poseidon);

View File

@@ -1,12 +1,7 @@
const wasm_tester = require("circom_tester").wasm;
var EC = require("elliptic").ec;
import * as path from "path";
import {
Poseidon,
Tree,
SpartanWasm,
defaultWasmConfig
} from "@personaelabs/spartan-ecdsa";
import { Poseidon, Tree } from "@personaelabs/spartan-ecdsa";
import { privateToPublic } from "@ethereumjs/util";
import { getEffEcdsaCircuitInput } from "./test_utils";
@@ -20,11 +15,9 @@ describe("pubkey_membership", () => {
}
);
const wasm = new SpartanWasm(defaultWasmConfig);
// Construct the tree
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
await poseidon.initWasm();
const nLevels = 10;
const tree = new Tree(nLevels, poseidon);

View File

@@ -4,3 +4,4 @@
tsconfig.json
jest.config.js
copy_artifacts.sh
load_wasm.ts

18
packages/lib/load_wasm.ts Normal file
View File

@@ -0,0 +1,18 @@
import * as fs from "fs";
/**
* Load the wasm file and output a typescript file with the wasm bytes embedded
*/
const loadWasm = async () => {
let wasm = fs.readFileSync("src/wasm/build/spartan_wasm_bg.wasm");
let bytes = new Uint8Array(wasm.buffer);
const file = `
export const wasmBytes = new Uint8Array([${bytes.toString()}]);
`;
fs.writeFileSync("./src/wasm/wasm_bytes.ts", file);
};
loadWasm();

View File

@@ -8,8 +8,10 @@
"build/**/*"
],
"scripts": {
"build": "tsc && sh ./copy_artifacts.sh",
"prepublishOnly": "yarn build"
"build": "tsc && yarn loadWasm && sh ./copy_artifacts.sh",
"prepublishOnly": "yarn build",
"prepare": "yarn loadWasm",
"loadWasm": "ts-node ./load_wasm.ts"
},
"devDependencies": {
"@types/jest": "^29.2.5",

View File

@@ -1,21 +1,11 @@
import * as path from "path";
const isWeb = typeof window !== "undefined";
import { LeafType, ProverConfig, WasmConfig } from "./types";
export const defaultWasmConfig: WasmConfig = {
pathOrUrl: isWeb
? "https://storage.googleapis.com/personae-proving-keys/spartan_wasm_bg.wasm"
: path.join(__dirname, "wasm/build/spartan_wasm_bg.wasm")
};
import { LeafType, ProverConfig } from "./types";
// Default configs for MembershipProver
// Default configs for pubkey membership proving
export const defaultPubkeyMembershipConfig: ProverConfig = {
spartanWasm: isWeb
? "https://storage.googleapis.com/personae-proving-keys/spartan_wasm_bg.wasm"
: path.join(__dirname, "wasm/build/spartan_wasm_bg.wasm"),
witnessGenWasm: isWeb
? "https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.wasm"
: path.join(__dirname, "circuits/pubkey_membership.wasm"),
@@ -29,10 +19,6 @@ export const defaultPubkeyMembershipConfig: ProverConfig = {
// Default configs for address membership proving
export const defaultAddressMembershipConfig: ProverConfig = {
spartanWasm: isWeb
? "https://storage.googleapis.com/personae-proving-keys/spartan_wasm_bg.wasm"
: path.join(__dirname, "wasm/build/spartan_wasm_bg.wasm"),
witnessGenWasm: isWeb
? "https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.wasm"
: path.join(__dirname, "circuits/addr_membership.wasm"),

View File

@@ -1,6 +1,5 @@
import { Profiler } from "../helpers/profiler";
import { IProver, MerkleProof, NIZK, ProverConfig, LeafType } from "../types";
import { SpartanWasm } from "../wasm";
import {
bigIntToBytes,
loadCircuit,
@@ -11,12 +10,12 @@ import {
EffEcdsaPubInput,
EffEcdsaCircuitPubInput
} from "../helpers/efficient_ecdsa";
import wasm, { init } from "../wasm";
/**
* ECDSA Membership Prover
*/
export class MembershipProver extends Profiler implements IProver {
spartanWasm!: SpartanWasm;
circuit: string;
witnessGenWasm: string;
leafType: LeafType;
@@ -29,9 +28,8 @@ export class MembershipProver extends Profiler implements IProver {
this.witnessGenWasm = options.witnessGenWasm;
}
async initWasm(wasm: SpartanWasm) {
this.spartanWasm = wasm;
this.spartanWasm.init();
async initWasm() {
await init();
}
// @ts-ignore
@@ -40,10 +38,6 @@ export class MembershipProver extends Profiler implements IProver {
msgHash: Buffer,
merkleProof: MerkleProof
): Promise<NIZK> {
if (typeof this.spartanWasm === "undefined") {
throw new Error("wasm not initialized. Please call initWasm().");
}
const { r, s, v } = fromSig(sig);
const circuitPubInput = EffEcdsaCircuitPubInput.computeFromSig(
@@ -86,11 +80,7 @@ export class MembershipProver extends Profiler implements IProver {
this.timeEnd("Load circuit");
this.time("Prove");
let proof = await this.spartanWasm.prove(
circuitBin,
witness.data,
pubInput
);
let proof = wasm.prove(circuitBin, witness.data, pubInput);
this.timeEnd("Prove");
return { proof, publicInput: effEcdsaPubInput.serialize() };

View File

@@ -1,34 +1,22 @@
import { SpartanWasm } from "../wasm";
import { bigIntToLeBytes, bytesLeToBigInt } from "./utils";
import wasm, { init } from "../wasm";
export class Poseidon {
wasm!: SpartanWasm;
constructor() {}
async initWasm(wasm: SpartanWasm) {
this.wasm = wasm;
await this.wasm.init();
}
assertInitWasm() {
if (typeof this.wasm === "undefined") {
throw new Error("wasm not initialized. Please call initWasm().");
}
}
hash(inputs: bigint[]): bigint {
this.assertInitWasm();
const inputsBytes = new Uint8Array(32 * inputs.length);
for (let i = 0; i < inputs.length; i++) {
inputsBytes.set(bigIntToLeBytes(inputs[i], 32), i * 32);
}
const result = this.wasm.poseidon(inputsBytes);
const result = wasm.poseidon(inputsBytes);
return bytesLeToBigInt(result);
}
async initWasm() {
await init();
}
hashPubKey(pubKey: Buffer): bigint {
this.assertInitWasm();
const pubKeyX = BigInt("0x" + pubKey.toString("hex").slice(0, 64));
const pubKeyY = BigInt("0x" + pubKey.toString("hex").slice(64, 128));

View File

@@ -1,8 +1,6 @@
// The same structure as MerkleProof in @zk-kit/incremental-merkle-tree.
// Not directly using MerkleProof defined in @zk-kit/incremental-merkle-tree so
// library users can choose whatever merkle tree management method they want.
import { SpartanWasm } from "./wasm";
export interface MerkleProof {
root: bigint;
siblings: bigint[];
@@ -18,23 +16,16 @@ export interface ProverConfig {
proverWasm?: string;
witnessGenWasm: string;
circuit: string;
spartanWasm: string;
enableProfiler?: boolean;
leafType: LeafType;
}
export interface WasmConfig {
pathOrUrl: string;
}
export interface VerifyOptions {
circuit?: string; // Path to circuit file compiled by Nova-Scotia
spartanWasm?: string; // Path to spartan wasm file
enableProfiler?: boolean;
}
export interface IProver {
spartanWasm: SpartanWasm;
circuit: string; // Path to circuit file compiled by Nova-Scotia
witnessGenWasm: string; // Path to witness generator wasm file generated by Circom
@@ -42,16 +33,11 @@ export interface IProver {
}
export interface IVerifier {
spartanWasm: SpartanWasm; // Path to spartan wasm file
circuit: string; // Path to circuit file compiled by Nova-Scotia
verify(proof: Uint8Array, publicInput: Uint8Array): Promise<boolean>;
}
export interface SpartanWasmOptions {
spartanWasm: string;
}
export enum LeafType {
PubKeyHash,
Address

View File

@@ -1,46 +1,11 @@
import * as wasm from "./wasm";
import _initWeb from "./wasm.js";
import fs from "fs";
import path from "path";
import { WasmConfig } from "../types";
// TODO: Rename this to just Wasm since it includes not only Spartan but also Poseidon
export class SpartanWasm {
private spartanWasmPathOrUrl: any;
import { wasmBytes } from "./wasm_bytes";
constructor(config: WasmConfig) {
this.spartanWasmPathOrUrl = config.pathOrUrl;
}
export const init = async () => {
await wasm.initSync(wasmBytes.buffer);
wasm.init_panic_hook();
};
async init() {
if (typeof window === "undefined") {
await this.initNode();
} else {
await this.initWeb();
}
}
prove(circuit: Uint8Array, vars: Uint8Array, public_inputs: Uint8Array) {
return wasm.prove(circuit, vars, public_inputs);
}
verify(circuit: Uint8Array, vars: Uint8Array, public_inputs: Uint8Array) {
return wasm.verify(circuit, vars, public_inputs);
}
poseidon(inputs: Uint8Array) {
return wasm.poseidon(inputs);
}
private async initNode() {
const bytes = fs.readFileSync(this.spartanWasmPathOrUrl);
await wasm.initSync(bytes);
await wasm.init_panic_hook();
}
private async initWeb() {
await _initWeb(this.spartanWasmPathOrUrl);
await wasm.init_panic_hook();
}
}
export default wasm;

View File

@@ -400,16 +400,19 @@ function finalizeInit(instance, module) {
return wasm;
}
function initSync(module, maybe_memory) {
async function initSync(module, maybe_memory) {
const imports = getImports();
initMemory(imports, maybe_memory);
/*
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
*/
const compiled = WebAssembly.compile(module);
const instance = new WebAssembly.Instance(module, imports);
const instance = await WebAssembly.instantiate(await compiled, imports);
return finalizeInit(instance, module);
}

View File

@@ -1,12 +1,9 @@
import * as path from "path";
import {
MembershipProver,
Tree,
Poseidon,
defaultAddressMembershipConfig,
defaultPubkeyMembershipConfig,
SpartanWasm,
defaultWasmConfig
defaultPubkeyMembershipConfig
} from "../src/lib";
import {
hashPersonalMessage,
@@ -36,18 +33,14 @@ describe("membership prove and verify", () => {
const sig = `0x${r.toString("hex")}${s.toString("hex")}${v.toString(16)}`;
let poseidon: Poseidon;
let wasm: SpartanWasm;
beforeAll(async () => {
// Init Wasm
wasm = new SpartanWasm(defaultWasmConfig);
// Init Poseidon
poseidon = new Poseidon();
await poseidon.initWasm(wasm);
await poseidon.initWasm();
});
describe("pubkey_membership prover and verify", () => {
describe.only("pubkey_membership prover and verify", () => {
it("should prove and verify valid signature and merkle proof", async () => {
const pubKeyTree = new Tree(treeDepth, poseidon);
@@ -66,8 +59,6 @@ describe("membership prove and verify", () => {
defaultPubkeyMembershipConfig
);
await pubKeyMembershipProver.initWasm(wasm);
const index = pubKeyTree.indexOf(proverPubKeyHash as bigint);
const merkleProof = pubKeyTree.createProof(index);
@@ -101,8 +92,6 @@ describe("membership prove and verify", () => {
defaultAddressMembershipConfig
);
await addressMembershipProver.initWasm(wasm);
const index = addressTree.indexOf(proverAddress as bigint);
const merkleProof = addressTree.createProof(index);