Merge pull request #20 from personaelabs/dan/embed-spartan-wasm

Embed spartan_wasm.wasm & Remove circuit artifacts from lib
This commit is contained in:
Dan Tehrani
2023-02-02 10:01:28 +09:00
committed by GitHub
24 changed files with 344 additions and 328 deletions

1
.eslintignore Normal file
View File

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

5
.gitignore vendored
View File

@@ -35,5 +35,8 @@ packages/prover/test_circuit/test_circuit_js/
packages/prover/test_circuit/*.json
wasm_bytes.ts
**/sage/*.sage.py
packages/lib/src/circuits/
packages/lib/src/circuits/
packages/lib/example/

1
.prettierignore Normal file
View File

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

View File

@@ -4,15 +4,12 @@ import {
ecsign
} from "@ethereumjs/util";
import {
SpartanWasm,
Tree,
Poseidon,
MembershipProver,
defaultAddressMembershipPConfig,
defaultWasmConfig,
MembershipVerifier,
defaultAddressMembershipVConfig
MembershipVerifier
} from "@personaelabs/spartan-ecdsa";
import * as path from "path";
const benchAddrMembership = async () => {
const privKey = Buffer.from("".padStart(16, "🧙"), "utf16le");
@@ -22,11 +19,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);
@@ -52,24 +47,35 @@ const benchAddrMembership = async () => {
// Compute the merkle proof
const index = tree.indexOf(proverAddress);
const proverConfig = {
circuit: path.join(
__dirname,
"../../../circuits/build/addr_membership/addr_membership.circuit"
),
witnessGenWasm: path.join(
__dirname,
"../../../circuits/build/addr_membership/addr_membership_js/addr_membership.wasm"
),
enableProfiler: true
};
const merkleProof = tree.createProof(index);
// Init the prover
const prover = new MembershipProver({
...defaultAddressMembershipPConfig,
enableProfiler: true
});
await prover.initWasm(wasm);
const prover = new MembershipProver(proverConfig);
await prover.initWasm();
// Prove membership
const { proof, publicInput } = await prover.prove(sig, msgHash, merkleProof);
// Init verifier
const verifier = new MembershipVerifier({
...defaultAddressMembershipVConfig,
const verifierConfig = {
circuit: proverConfig.circuit,
enableProfiler: true
});
await verifier.initWasm(wasm);
};
// Init verifier
const verifier = new MembershipVerifier(verifierConfig);
await verifier.initWasm();
// Verify proof
await verifier.verify(proof, publicInput);

View File

@@ -2,10 +2,6 @@ import {
MembershipProver,
Poseidon,
Tree,
SpartanWasm,
defaultWasmConfig,
defaultPubkeyMembershipPConfig,
defaultPubkeyMembershipVConfig,
MembershipVerifier
} from "@personaelabs/spartan-ecdsa";
import {
@@ -14,6 +10,7 @@ import {
ecrecover,
privateToPublic
} from "@ethereumjs/util";
import * as path from "path";
const benchPubKeyMembership = async () => {
const privKey = Buffer.from("".padStart(16, "🧙"), "utf16le");
@@ -24,11 +21,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);
@@ -51,22 +46,33 @@ const benchPubKeyMembership = async () => {
const index = tree.indexOf(proverPubkeyHash);
const merkleProof = tree.createProof(index);
// Init the prover
const prover = new MembershipProver({
...defaultPubkeyMembershipPConfig,
const proverConfig = {
circuit: path.join(
__dirname,
"../../../circuits/build/pubkey_membership/pubkey_membership.circuit"
),
witnessGenWasm: path.join(
__dirname,
"../../../circuits/build/pubkey_membership/pubkey_membership_js/pubkey_membership.wasm"
),
enableProfiler: true
});
await prover.initWasm(wasm);
};
// Init the prover
const prover = new MembershipProver(proverConfig);
await prover.initWasm();
// Prove membership
const { proof, publicInput } = await prover.prove(sig, msgHash, merkleProof);
// Init verifier
const verifier = new MembershipVerifier({
...defaultPubkeyMembershipVConfig,
const verifierConfig = {
circuit: proverConfig.circuit,
enableProfiler: true
});
await verifier.initWasm(wasm);
};
// Init verifier
const verifier = new MembershipVerifier(verifierConfig);
await verifier.initWasm();
// Verify proof
await verifier.verify(proof, publicInput);

View File

@@ -4,9 +4,7 @@ import {
MembershipVerifier,
Tree,
Poseidon,
SpartanWasm,
defaultAddressMembershipPConfig,
defaultWasmConfig,
defaultPubkeyMembershipPConfig,
defaultPubkeyMembershipVConfig,
defaultAddressMembershipVConfig
@@ -30,9 +28,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);
@@ -60,7 +57,7 @@ export default function Home() {
enableProfiler: true
});
prover.initWasm(wasm);
await prover.initWasm();
const { proof, publicInput } = await prover.prove(
sig,
@@ -80,7 +77,7 @@ export default function Home() {
...defaultPubkeyMembershipVConfig,
enableProfiler: true
});
await verifier.initWasm(wasm);
await verifier.initWasm();
console.time("Verification time");
const result = await verifier.verify(proof, publicInput);
@@ -101,9 +98,8 @@ 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);
await poseidon.initWasm();
const treeDepth = 20;
const addressTree = new Tree(treeDepth, poseidon);
@@ -133,7 +129,7 @@ export default function Home() {
enableProfiler: true
});
prover.initWasm(wasm);
await prover.initWasm();
const { proof, publicInput } = await prover.prove(
sig,
@@ -153,7 +149,7 @@ export default function Home() {
...defaultAddressMembershipVConfig,
enableProfiler: true
});
await verifier.initWasm(wasm);
await verifier.initWasm();
console.time("Verification time");
const result = await verifier.verify(proof, publicInput);

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

View File

@@ -5,36 +5,35 @@
### Proving membership to a group of public keys
```typescript
// Setup
const privKey = Buffer.from("".padStart(16, "🧙"), "utf16le");
const msg = Buffer.from("harry potter");
const msgHash = hashPersonalMessage(msg);
const { v, r, s } = ecsign(msgHash, privKey);
const pubKey = ecrecover(msgHash, v, r, s);
const sig = `0x${r.toString("hex")}${s.toString("hex")}${v.toString(16)}`;
let wasm = new SpartanWasm(defaultWasmConfig);
import {
MembershipProver,
MembershipVerifier,
Poseidon,
Tree,
defaultPubkeyMembershipPConfig,
defaultPubkeyMembershipVConfig
} from "@personaelabs/spartan-ecdsa";
import { hashPersonalMessage } from "@ethereumjs/util";
// Init the Poseidon hash
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
await poseidon.initWasm();
const treeDepth = 20;
const treeDepth = 20; // Provided circuits have tree depth = 20
const tree = new Tree(treeDepth, poseidon);
const proverPubKey = Buffer.from("...");
// Get the prover public key hash
const proverPubkeyHash = poseidon.hashPubKey(pubKey);
const proverPubkeyHash = poseidon.hashPubKey(proverPubKey);
// Insert prover public key hash into the tree
tree.insert(proverPubkeyHash);
// Insert other members into the tree
for (const member of ["🕵️", "🥷", "👩‍🔬"]) {
const pubKey = privateToPublic(
Buffer.from("".padStart(16, member), "utf16le")
tree.insert(
poseidon.hashPubKey(Buffer.from("".padStart(16, member), "utf16le"))
);
tree.insert(poseidon.hashPubKey(pubKey));
}
// Compute the merkle proof
@@ -42,61 +41,55 @@ const index = tree.indexOf(proverPubkeyHash);
const merkleProof = tree.createProof(index);
// Init the prover
const prover = new MembershipProver(defaultPubkeyMembershipConfig);
await prover.initWasm(wasm);
const prover = new MembershipProver(defaultPubkeyMembershipPConfig);
await prover.initWasm();
const sig = "0x...";
const msgHash = hashPersonalMessage(Buffer.from("harry potter"));
// Prove membership
await prover.prove(sig, msgHash, merkleProof);
const { proof, publicInput } = await prover.prove(sig, msgHash, merkleProof);
// Init verifier
const verifier = new MembershipVerifier(defaultPubkeyMembershipVConfig);
await verifier.initWasm();
// Verify proof
await verifier.verify(proof, publicInput);
```
### Proving membership to a group of addresses
```typescript
import {
hashPersonalMessage,
privateToAddress,
ecsign
} from "@ethereumjs/util";
import {
SpartanWasm,
Tree,
Poseidon,
MembershipProver,
defaultAddressMembershipConfig,
defaultWasmConfig
MembershipVerifier,
Poseidon,
Tree,
defaultAddressMembershipPConfig,
defaultAddressMembershipVConfig
} from "@personaelabs/spartan-ecdsa";
const privKey = Buffer.from("".padStart(16, "🧙"), "utf16le");
const msg = Buffer.from("harry potter");
const msgHash = hashPersonalMessage(msg);
const { v, r, s } = ecsign(msgHash, privKey);
const sig = `0x${r.toString("hex")}${s.toString("hex")}${v.toString(16)}`;
let wasm = new SpartanWasm(defaultWasmConfig);
import { hashPersonalMessage } from "@ethereumjs/util";
// Init the Poseidon hash
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
await poseidon.initWasm();
const treeDepth = 20;
const treeDepth = 20; // Provided circuits have tree depth = 20
const tree = new Tree(treeDepth, poseidon);
// Get the prover address
const proverAddress = BigInt("0x" + privateToAddress(privKey).toString("hex"));
// Get the prover public key hash
const proverAddress = BigInt("0x...");
// Insert prover address into the tree
// Insert prover public key hash into the tree
tree.insert(proverAddress);
// Insert other members into the tree
for (const member of ["🕵️", "🥷", "👩‍🔬"]) {
const address = BigInt(
"0x" +
privateToAddress(
Buffer.from("".padStart(16, member), "utf16le")
).toString("hex")
tree.insert(
BigInt(
"0x" + Buffer.from("".padStart(16, member), "utf16le").toString("hex")
)
);
tree.insert(address);
}
// Compute the merkle proof
@@ -104,29 +97,61 @@ const index = tree.indexOf(proverAddress);
const merkleProof = tree.createProof(index);
// Init the prover
const prover = new MembershipProver(defaultAddressMembershipConfig);
await prover.initWasm(wasm);
const prover = new MembershipProver(defaultAddressMembershipPConfig);
await prover.initWasm();
const sig = "0x...";
const msgHash = hashPersonalMessage(Buffer.from("harry potter"));
// Prove membership
await prover.prove(sig, msgHash, merkleProof);
const { proof, publicInput } = await prover.prove(sig, msgHash, merkleProof);
// Init verifier
const verifier = new MembershipVerifier(defaultAddressMembershipVConfig);
await verifier.initWasm();
// Verify proof
await verifier.verify(proof, publicInput);
```
## Circuit downloads
_Provided circuits have Merkle tree depth = 20.
Change in the tree depth doesn't significantly affect the proving time, hence we only provide a single tree depth that is adequate (2^20 ~= 1 million leaves) for most situations._
**Public key membership**
| | |
| --- | --- |
| circuit | https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.circuit |
| witnessGenWasm | https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.wasm |
**Ethereum address membership**
|||
| --- | --- |
| circuit | https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.circuit |
| witnessGenWasm | https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.wasm |
## Development
### Install dependencies
```
yarn
```
### Run tests
```
yarn jest
```
### Build
```
yarn build
```

View File

@@ -1,7 +0,0 @@
# Copy circuit artifacts into the lib build dir
mkdir -p ./build/circuits/ &&
cp ./src/circuits/* ./build/circuits/ &&
# Copy wasm into the lib build dir
cp ./src/wasm/build/*.wasm ./build/wasm/build/

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 embedWasmBytes = async () => {
let wasm = fs.readFileSync("../spartan_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);
};
embedWasmBytes();

View File

@@ -4,5 +4,6 @@ module.exports = {
testEnvironment: 'node',
transform: {
"^.+\\.js?$": "ts-jest"
}
},
testTimeout: 600000,
};

View File

@@ -8,9 +8,11 @@
"build/**/*"
],
"scripts": {
"build": "tsc && sh ./copy_artifacts.sh",
"test": "jest",
"prepublishOnly": "yarn build"
"build": "rm -rf ./build && yarn embedWasmBytes && tsc",
"prepublishOnly": "yarn build",
"prepare": "yarn embedWasmBytes",
"embedWasmBytes": "ts-node ./embed_wasm_bytes.ts",
"test": "jest"
},
"devDependencies": {
"@types/jest": "^29.2.5",

View File

@@ -1,53 +1,25 @@
import * as path from "path";
const isWeb = typeof window !== "undefined";
import { LeafType, ProverConfig, VerifyConfig, 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 { ProverConfig, VerifyConfig } from "./types";
// Default configs for pubkey membership proving/verifying
export const defaultPubkeyMembershipPConfig: 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"),
circuit: isWeb
? "https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.circuit"
: path.join(__dirname, "circuits/pubkey_membership.circuit"),
leafType: LeafType.PubKeyHash
witnessGenWasm:
"https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.wasm",
circuit:
"https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.circuit"
};
export const defaultPubkeyMembershipVConfig: VerifyConfig = {
spartanWasm: defaultPubkeyMembershipPConfig.spartanWasm,
circuit: defaultPubkeyMembershipPConfig.circuit
};
// Default configs for address membership proving/verifyign
export const defaultAddressMembershipPConfig: 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"),
circuit: isWeb
? "https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.circuit"
: path.join(__dirname, "circuits/addr_membership.circuit"),
leafType: LeafType.Address
witnessGenWasm:
"https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.wasm",
circuit:
"https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.circuit"
};
export const defaultAddressMembershipVConfig: VerifyConfig = {
spartanWasm: defaultAddressMembershipPConfig.spartanWasm,
circuit: defaultAddressMembershipPConfig.circuit
};

View File

@@ -1,6 +1,5 @@
import { Profiler } from "../helpers/profiler";
import { IProver, MerkleProof, NIZK, ProverConfig, LeafType } from "../types";
import { SpartanWasm } from "../wasm";
import { IProver, MerkleProof, NIZK, ProverConfig } from "../types";
import {
bigIntToBytes,
loadCircuit,
@@ -11,27 +10,43 @@ import {
EffEcdsaPubInput,
EffEcdsaCircuitPubInput
} from "../helpers/efficient_ecdsa";
import wasm, { init } from "../wasm";
import {
defaultPubkeyMembershipPConfig,
defaultAddressMembershipPConfig
} from "../config";
/**
* ECDSA Membership Prover
*/
export class MembershipProver extends Profiler implements IProver {
spartanWasm!: SpartanWasm;
circuit: string;
witnessGenWasm: string;
leafType: LeafType;
constructor(options: ProverConfig) {
super({ enabled: options?.enableProfiler });
this.leafType = options.leafType;
if (
options.circuit === defaultPubkeyMembershipPConfig.circuit ||
options.witnessGenWasm ===
defaultPubkeyMembershipPConfig.witnessGenWasm ||
options.circuit === defaultAddressMembershipPConfig.circuit ||
options.witnessGenWasm === defaultAddressMembershipPConfig.witnessGenWasm
) {
console.warn(`
Spartan-ecdsa default config warning:
We recommend using defaultPubkeyMembershipPConfig/defaultPubkeyMembershipVConfig only for testing purposes.
Please host and specify the circuit and witnessGenWasm files on your own server for sovereign control.
Download files: https://github.com/personaelabs/spartan-ecdsa/blob/main/packages/lib/README.md#circuit-downloads
`);
}
this.circuit = options.circuit;
this.witnessGenWasm = options.witnessGenWasm;
}
async initWasm(wasm: SpartanWasm) {
this.spartanWasm = wasm;
this.spartanWasm.init();
async initWasm() {
await init();
}
// @ts-ignore
@@ -40,10 +55,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 +97,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 {

View File

@@ -1,24 +1,38 @@
import {
defaultAddressMembershipVConfig,
defaultPubkeyMembershipVConfig
} from "../config";
import { Profiler } from "../helpers/profiler";
import { loadCircuit } from "../helpers/utils";
import { IVerifier, VerifyConfig } from "../types";
import { SpartanWasm } from "../wasm";
import wasm, { init } from "../wasm";
/**
* ECDSA Membership Verifier
*/
export class MembershipVerifier extends Profiler implements IVerifier {
spartanWasm!: SpartanWasm;
circuit: string;
constructor(options: VerifyConfig) {
super({ enabled: options?.enableProfiler });
if (
options.circuit === defaultAddressMembershipVConfig.circuit ||
options.circuit === defaultPubkeyMembershipVConfig.circuit
) {
console.warn(`
Spartan-ecdsa default config warning:
We recommend using defaultPubkeyMembershipPConfig/defaultPubkeyMembershipVConfig only for testing purposes.
Please host and specify the circuit and witnessGenWasm files on your own server for sovereign control.
Download files: https://github.com/personaelabs/spartan-ecdsa/blob/main/packages/lib/README.md#circuit-downloads
`);
}
this.circuit = options.circuit;
}
async initWasm(wasm: SpartanWasm) {
this.spartanWasm = wasm;
this.spartanWasm.init();
async initWasm() {
await init();
}
async verify(proof: Uint8Array, publicInput: Uint8Array): Promise<boolean> {
@@ -27,11 +41,12 @@ export class MembershipVerifier extends Profiler implements IVerifier {
this.timeEnd("Load circuit");
this.time("Verify proof");
const result = await this.spartanWasm.verify(
circuitBin,
proof,
publicInput
);
let result;
try {
result = await wasm.verify(circuitBin, proof, publicInput);
} catch (_e) {
result = false;
}
this.timeEnd("Verify proof");
return result;
}

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

@@ -19,7 +19,8 @@ export const snarkJsWitnessGen = async (input: any, wasmFile: string) => {
* Load a circuit from a file or URL
*/
export const loadCircuit = async (pathOrUrl: string): Promise<Uint8Array> => {
if (pathOrUrl.startsWith("http")) {
const isWeb = typeof window !== "undefined";
if (isWeb) {
return await fetchCircuit(pathOrUrl);
} else {
return await readCircuitFromFs(pathOrUrl);

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[];
@@ -15,26 +13,17 @@ export interface NIZK {
}
export interface ProverConfig {
proverWasm?: string;
witnessGenWasm: string;
circuit: string;
spartanWasm: string;
enableProfiler?: boolean;
leafType: LeafType;
}
export interface WasmConfig {
pathOrUrl: string;
}
export interface VerifyConfig {
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,17 +31,7 @@ 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,10 @@
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, proof: Uint8Array, public_inputs: Uint8Array) {
return wasm.verify(circuit, proof, 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,18 +400,23 @@ function finalizeInit(instance, module) {
return wasm;
}
function initSync(module, maybe_memory) {
const imports = getImports();
async function initSync(module, maybe_memory) {
if (!wasm) {
const imports = getImports();
initMemory(imports, maybe_memory);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
initMemory(imports, maybe_memory);
/*
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
*/
const compiled = WebAssembly.compile(module);
const instance = await WebAssembly.instantiate(await compiled, imports);
return finalizeInit(instance, module);
}
const instance = new WebAssembly.Instance(module, imports);
return finalizeInit(instance, module);
}
async function init(input, maybe_memory) {

View File

@@ -1,15 +1,9 @@
import * as path from "path";
import {
MembershipProver,
MembershipVerifier,
Tree,
Poseidon,
defaultAddressMembershipPConfig,
defaultPubkeyMembershipPConfig,
SpartanWasm,
defaultWasmConfig,
defaultPubkeyMembershipVConfig,
defaultAddressMembershipVConfig
NIZK
} from "../src/lib";
import {
hashPersonalMessage,
@@ -18,6 +12,7 @@ import {
privateToPublic
} from "@ethereumjs/util";
var EC = require("elliptic").ec;
import * as path from "path";
describe("membership prove and verify", () => {
// Init prover
@@ -39,18 +34,35 @@ 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", () => {
const config = {
witnessGenWasm: path.join(
__dirname,
"../../circuits/build/pubkey_membership/pubkey_membership_js/pubkey_membership.wasm"
),
circuit: path.join(
__dirname,
"../../circuits/build/pubkey_membership/pubkey_membership.circuit"
)
};
let pubKeyMembershipVerifier: MembershipVerifier, nizk: NIZK;
beforeAll(async () => {
pubKeyMembershipVerifier = new MembershipVerifier({
circuit: config.circuit
});
await pubKeyMembershipVerifier.initWasm();
});
it("should prove and verify valid signature and merkle proof", async () => {
const pubKeyTree = new Tree(treeDepth, poseidon);
@@ -65,34 +77,61 @@ describe("membership prove and verify", () => {
if (proverPrivKey === privKey) proverPubKeyHash = pubKeyHash;
}
const pubKeyMembershipProver = new MembershipProver(
defaultPubkeyMembershipPConfig
);
const pubKeyMembershipProver = new MembershipProver(config);
await pubKeyMembershipProver.initWasm(wasm);
await pubKeyMembershipProver.initWasm();
const index = pubKeyTree.indexOf(proverPubKeyHash as bigint);
const merkleProof = pubKeyTree.createProof(index);
const { proof, publicInput } = await pubKeyMembershipProver.prove(
sig,
msgHash,
merkleProof
);
const pubKeyMembershipVerifier = new MembershipVerifier(
defaultPubkeyMembershipVConfig
);
await pubKeyMembershipVerifier.initWasm(wasm);
nizk = await pubKeyMembershipProver.prove(sig, msgHash, merkleProof);
const { proof, publicInput } = nizk;
expect(await pubKeyMembershipVerifier.verify(proof, publicInput)).toBe(
true
);
});
it("should assert invalid proof", async () => {
const { publicInput } = nizk;
let proof = nizk.proof;
proof[0] = proof[0] += 1;
expect(await pubKeyMembershipVerifier.verify(proof, publicInput)).toBe(
false
);
});
it("should assert invalid public input", async () => {
const { proof } = nizk;
let publicInput = nizk.publicInput;
publicInput[0] = publicInput[0] += 1;
expect(await pubKeyMembershipVerifier.verify(proof, publicInput)).toBe(
false
);
});
});
describe("addr_membership prover and verify", () => {
const config = {
witnessGenWasm: path.join(
__dirname,
"../../circuits/build/addr_membership/addr_membership_js/addr_membership.wasm"
),
circuit: path.join(
__dirname,
"../../circuits/build/addr_membership/addr_membership.circuit"
)
};
let addressMembershipVerifier: MembershipVerifier, nizk: NIZK;
beforeAll(async () => {
addressMembershipVerifier = new MembershipVerifier({
circuit: config.circuit
});
await addressMembershipVerifier.initWasm();
});
it("should prove and verify valid signature and merkle proof", async () => {
const addressTree = new Tree(treeDepth, poseidon);
@@ -108,29 +147,36 @@ describe("membership prove and verify", () => {
if (proverPrivKey === privKey) proverAddress = address;
}
const addressMembershipProver = new MembershipProver(
defaultAddressMembershipPConfig
);
await addressMembershipProver.initWasm(wasm);
const index = addressTree.indexOf(proverAddress as bigint);
const merkleProof = addressTree.createProof(index);
const { proof, publicInput } = await addressMembershipProver.prove(
sig,
msgHash,
merkleProof
);
const addressMembershipProver = new MembershipProver(config);
const addressMembershipVerifier = new MembershipVerifier(
defaultAddressMembershipVConfig
);
await addressMembershipProver.initWasm();
await addressMembershipVerifier.initWasm(wasm);
nizk = await addressMembershipProver.prove(sig, msgHash, merkleProof);
await addressMembershipVerifier.initWasm();
expect(
await addressMembershipVerifier.verify(nizk.proof, nizk.publicInput)
).toBe(true);
});
it("should assert invalid proof", async () => {
const { publicInput } = nizk;
let proof = nizk.proof;
proof[0] = proof[0] += 1;
expect(await addressMembershipVerifier.verify(proof, publicInput)).toBe(
true
false
);
});
it("should assert invalid public input", async () => {
const { proof } = nizk;
let publicInput = nizk.publicInput;
publicInput[0] = publicInput[0] += 1;
expect(await addressMembershipVerifier.verify(proof, publicInput)).toBe(
false
);
});
});

View File

@@ -1,3 +1,3 @@
rm -rf ./packages/lib/src/wasm/build &&
rm -rf ./packages/spartan_wasm/build &&
cd ./packages/spartan_wasm &&
wasm-pack build --target web --out-dir ../lib/src/wasm/build
wasm-pack build --target web --out-dir ../spartan_wasm/build