mirror of
https://github.com/personaelabs/spartan-ecdsa.git
synced 2026-01-09 14:07:54 -05:00
Merge pull request #20 from personaelabs/dan/embed-spartan-wasm
Embed spartan_wasm.wasm & Remove circuit artifacts from lib
This commit is contained in:
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
wasm_bytes.ts
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -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
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
wasm_bytes.ts
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
tsconfig.json
|
||||
jest.config.js
|
||||
copy_artifacts.sh
|
||||
load_wasm.ts
|
||||
@@ -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
|
||||
|
||||
```
|
||||
|
||||
@@ -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/
|
||||
|
||||
18
packages/lib/embed_wasm_bytes.ts
Normal file
18
packages/lib/embed_wasm_bytes.ts
Normal 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();
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
"^.+\\.js?$": "ts-jest"
|
||||
}
|
||||
},
|
||||
testTimeout: 600000,
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user