Merge branch 'main' into lsankar/fix-lerna-build

This commit is contained in:
Lakshman Sankar
2023-01-27 07:52:58 -08:00
committed by GitHub
37 changed files with 650 additions and 365 deletions

4
.gitignore vendored
View File

@@ -32,4 +32,6 @@ build/
packages/prover/test_circuit/test_circuit_js/
#input files
packages/prover/test_circuit/*.json
packages/prover/test_circuit/*.json
packages/lib/src/circuits/

View File

@@ -1,10 +1,10 @@
{
"name": "node",
"version": "1.0.0",
"main": "index.ts",
"main": "node.bench.ts",
"license": "MIT",
"scripts": {
"bench": "ts-node ./src/index.ts"
"bench": "ts-node ./src/node.bench.ts"
},
"dependencies": {
"@ethereumjs/util": "^8.0.3",

View File

@@ -0,0 +1,9 @@
import benchPubKeyMembership from "./node.bench_pubkey_membership";
import benchAddressMembership from "./node.bench_addr_membership";
const bench = async () => {
await benchPubKeyMembership();
await benchAddressMembership();
};
bench();

View File

@@ -0,0 +1,66 @@
import {
hashPersonalMessage,
privateToAddress,
ecsign
} from "@ethereumjs/util";
import {
SpartanWasm,
Tree,
Poseidon,
MembershipProver,
defaultAddressMembershipConfig,
defaultWasmConfig
} from "spartan-ecdsa";
const benchAddrMembership = async () => {
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);
// Init the Poseidon hash
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
const treeDepth = 20;
const tree = new Tree(treeDepth, poseidon);
// Get the prover public key hash
const proverAddress = BigInt(
"0x" + privateToAddress(privKey).toString("hex")
);
// 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(address);
}
// Compute the merkle proof
const index = tree.indexOf(proverAddress);
const merkleProof = tree.createProof(index);
// Init the prover
const prover = new MembershipProver({
...defaultAddressMembershipConfig,
enableProfiler: true
});
await prover.initWasm(wasm);
// Prove membership
await prover.prove(sig, msgHash, merkleProof);
};
export default benchAddrMembership;

View File

@@ -1,4 +1,11 @@
import { MembershipProver, Poseidon, Tree } from "spartan-ecdsa";
import {
MembershipProver,
Poseidon,
Tree,
SpartanWasm,
defaultWasmConfig,
defaultPubkeyMembershipConfig
} from "spartan-ecdsa";
import {
hashPersonalMessage,
ecsign,
@@ -6,7 +13,7 @@ import {
privateToPublic
} from "@ethereumjs/util";
const main = async () => {
const benchPubKeyMembership = async () => {
const privKey = Buffer.from("".padStart(16, "🧙"), "utf16le");
const msg = Buffer.from("harry potter");
const msgHash = hashPersonalMessage(msg);
@@ -15,29 +22,44 @@ const main = 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.init();
const treeDepth = 10;
await poseidon.initWasm(wasm);
const treeDepth = 20;
const tree = new Tree(treeDepth, poseidon);
// Insert prover public key into the tree
tree.hashAndInsert(pubKey);
// Get the prover public key hash
const proverPubkeyHash = poseidon.hashPubKey(pubKey);
// 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.hashAndInsert(pubKey);
tree.insert(poseidon.hashPubKey(pubKey));
}
const index = tree.indexOf(pubKey);
// Compute the merkle proof
const index = tree.indexOf(proverPubkeyHash);
const merkleProof = tree.createProof(index);
const prover = new MembershipProver(treeDepth);
// Init the prover
const prover = new MembershipProver({
...defaultPubkeyMembershipConfig,
enableProfiler: true
});
await prover.initWasm(wasm);
// Prove membership
await prover.prove(sig, msgHash, merkleProof);
// TODO: Verify the proof
};
main();
export default benchPubKeyMembership;

View File

@@ -1,16 +1,26 @@
import { useState } from "react";
import { MembershipProver, Tree, Poseidon } from "spartan-ecdsa";
import {
MembershipProver,
Tree,
Poseidon,
SpartanWasm,
defaultAddressMembershipConfig,
defaultWasmConfig,
defaultPubkeyMembershipConfig
} from "spartan-ecdsa";
import {
ecrecover,
ecsign,
hashPersonalMessage,
privateToPublic
privateToAddress,
privateToPublic,
pubToAddress
} from "@ethereumjs/util";
export default function Home() {
const [proof, setProof] = useState<any | undefined>();
const prove = async () => {
const provePubKeyMembership = async () => {
const privKey = Buffer.from("".padStart(16, "🧙"), "utf16le");
const msg = Buffer.from("harry potter");
const msgHash = hashPersonalMessage(msg);
@@ -19,32 +29,92 @@ 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.init();
const treeDepth = 10;
const tree = new Tree(treeDepth, poseidon);
await poseidon.initWasm(wasm);
// Insert prover public key into the tree
tree.hashAndInsert(pubKey);
const treeDepth = 20;
const pubKeyTree = new Tree(treeDepth, poseidon);
const proverPubKeyHash = poseidon.hashPubKey(pubKey);
pubKeyTree.insert(proverPubKeyHash);
// Insert other members into the tree
for (const member of ["🕵️", "🥷", "👩‍🔬"]) {
const pubKey = privateToPublic(
Buffer.from("".padStart(16, member), "utf16le")
);
tree.hashAndInsert(pubKey);
pubKeyTree.insert(poseidon.hashPubKey(pubKey));
}
const index = tree.indexOf(pubKey);
const merkleProof = tree.createProof(index);
const index = pubKeyTree.indexOf(proverPubKeyHash);
const merkleProof = pubKeyTree.createProof(index);
console.log("Proving...");
console.time("Full proving time");
const prover = new MembershipProver(defaultPubkeyMembershipConfig);
prover.initWasm(wasm);
const { proof, publicInput } = await prover.prove(
sig,
msgHash,
merkleProof
);
console.timeEnd("Full proving time");
console.log(
"Raw proof size (excluding public input)",
proof.length,
"bytes"
);
setProof({ proof, publicInput });
};
const proverAddressMembership = async () => {
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)}`;
const wasm = new SpartanWasm(defaultWasmConfig);
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
const treeDepth = 20;
const addressTree = new Tree(treeDepth, poseidon);
const proverAddress = BigInt(
"0x" + privateToAddress(privKey).toString("hex")
);
addressTree.insert(proverAddress);
// Insert other members into the tree
for (const member of ["🕵️", "🥷", "👩‍🔬"]) {
const pubKey = privateToPublic(
Buffer.from("".padStart(16, member), "utf16le")
);
const address = BigInt("0x" + pubToAddress(pubKey).toString("hex"));
addressTree.insert(address);
}
const index = addressTree.indexOf(proverAddress);
const merkleProof = addressTree.createProof(index);
console.log("Proving...");
console.time("Full proving time");
const prover = new MembershipProver({
...defaultAddressMembershipConfig,
enableProfiler: true
});
prover.initWasm(wasm);
const { proof, publicInput } = await prover.prove(
sig,
msgHash,
@@ -77,7 +147,12 @@ export default function Home() {
return (
<div>
<button onClick={prove}>Prove</button>
<button onClick={provePubKeyMembership}>
Prove Public Key Membership
</button>
<button onClick={proverAddressMembership}>
Prove Address Membership
</button>
</div>
);
}

View File

@@ -0,0 +1,46 @@
pragma circom 2.1.2;
include "./eff_ecdsa.circom";
include "./tree.circom";
include "./to_address/zk-identity/eth.circom";
template AddrMembership(nLevels) {
signal input s;
signal input root;
signal input Tx;
signal input Ty;
signal input Ux;
signal input Uy;
signal input pathIndices[nLevels];
signal input siblings[nLevels];
component effEcdsa = EfficientECDSA();
effEcdsa.Tx <== Tx;
effEcdsa.Ty <== Ty;
effEcdsa.Ux <== Ux;
effEcdsa.Uy <== Uy;
effEcdsa.s <== s;
component pubKeyXBits = Num2Bits(256);
pubKeyXBits.in <== effEcdsa.pubKeyX;
component pubKeyYBits = Num2Bits(256);
pubKeyYBits.in <== effEcdsa.pubKeyY;
component pubToAddr = PubkeyToAddress();
for (var i = 0; i < 256; i++) {
pubToAddr.pubkeyBits[i] <== pubKeyYBits.out[i];
pubToAddr.pubkeyBits[i + 256] <== pubKeyXBits.out[i];
}
component merkleProof = MerkleTreeInclusionProof(nLevels);
merkleProof.leaf <== pubToAddr.address;
for (var i = 0; i < nLevels; i++) {
merkleProof.pathIndices[i] <== pathIndices[i];
merkleProof.siblings[i] <== siblings[i];
}
root === merkleProof.root;
}

View File

@@ -4,7 +4,7 @@ include "./eff_ecdsa.circom";
include "./tree.circom";
include "../poseidon/poseidon.circom";
template Membership(nLevels) {
template PubKeyMembership(nLevels) {
signal input s;
signal input root;
signal input Tx;

View File

@@ -0,0 +1,5 @@
pragma circom 2.1.2;
include "../eff_ecdsa_membership/addr_membership.circom";
component main { public[ root, Tx, Ty, Ux, Uy ]} = AddrMembership(20);

View File

@@ -1,5 +0,0 @@
pragma circom 2.1.2;
include "../eff_ecdsa_membership/eff_ecdsa.circom";
component main { public[ Tx, Ty, Ux, Uy ]} = EfficientECDSA();

View File

@@ -1,5 +0,0 @@
pragma circom 2.1.2;
include "../eff_ecdsa_membership/membership.circom";
component main { public[ root, Tx, Ty, Ux, Uy ]} = Membership(10);

View File

@@ -0,0 +1,5 @@
pragma circom 2.1.2;
include "../eff_ecdsa_membership/pubkey_membership.circom";
component main { public[ root, Tx, Ty, Ux, Uy ]} = PubKeyMembership(20);

View File

@@ -0,0 +1,73 @@
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 "spartan-ecdsa";
import { getEffEcdsaCircuitInput } from "./test_utils";
import { privateToAddress } from "@ethereumjs/util";
describe("membership", () => {
it("should verify correct signature and merkle proof", async () => {
// Compile the circuit
const circuit = await wasm_tester(
path.join(__dirname, "./circuits/addr_membership_test.circom"),
{
prime: "secq256k1" // Specify to use the option --prime secq256k1 when compiling with circom
}
);
const wasm = new SpartanWasm(defaultWasmConfig);
// Construct the tree
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
const nLevels = 10;
const tree = new Tree(nLevels, poseidon);
const privKeys = [
Buffer.from("".padStart(16, "🧙"), "utf16le"),
Buffer.from("".padStart(16, "🪄"), "utf16le"),
Buffer.from("".padStart(16, "🔮"), "utf16le")
];
// Store addresses hashes
const addresses: bigint[] = [];
// Compute public key hashes
for (const privKey of privKeys) {
const address = privateToAddress(privKey);
addresses.push(BigInt("0x" + address.toString("hex")));
}
// Insert the pubkey hashes into the tree
for (const address of addresses) {
tree.insert(address);
}
// Sanity check (check that there are not duplicate members)
expect(new Set(addresses).size === addresses.length).toBeTruthy();
// Sign
const index = 0; // Use privKeys[0] for proving
const privKey = privKeys[index];
const msg = Buffer.from("hello world");
// Prepare signature proof input
const effEcdsaInput = getEffEcdsaCircuitInput(privKey, msg);
const merkleProof = tree.createProof(index);
const input = {
...effEcdsaInput,
siblings: merkleProof.siblings,
pathIndices: merkleProof.pathIndices,
root: tree.root()
};
// Generate witness
const w = await circuit.calculateWitness(input, true);
await circuit.checkConstraints(w);
});
});

View File

@@ -0,0 +1,5 @@
pragma circom 2.1.2;
include "../../eff_ecdsa_membership/addr_membership.circom";
component main = AddrMembership(10);

View File

@@ -1,5 +0,0 @@
pragma circom 2.1.2;
include "../../eff_ecdsa_membership/membership.circom";
component main = Membership(10);

View File

@@ -0,0 +1,5 @@
pragma circom 2.1.2;
include "../../eff_ecdsa_membership/pubkey_membership.circom";
component main = PubKeyMembership(10);

View File

@@ -1,23 +1,26 @@
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 } from "spartan-ecdsa";
import { Poseidon, Tree, SpartanWasm, defaultWasmConfig } from "spartan-ecdsa";
import { privateToPublic } from "@ethereumjs/util";
import { getEffEcdsaCircuitInput } from "./test_utils";
describe("membership", () => {
describe("pubkey_membership", () => {
it("should verify correct signature and merkle proof", async () => {
// Compile the circuit
const circuit = await wasm_tester(
path.join(__dirname, "./circuits/membership_test.circom"),
path.join(__dirname, "./circuits/pubkey_membership_test.circom"),
{
prime: "secq256k1" // Specify to use the option --prime secq256k1 when compiling with circom
}
);
const wasm = new SpartanWasm(defaultWasmConfig);
// Construct the tree
const poseidon = new Poseidon();
await poseidon.init();
await poseidon.initWasm(wasm);
const nLevels = 10;
const tree = new Tree(nLevels, poseidon);
@@ -32,10 +35,8 @@ describe("membership", () => {
// Compute public key hashes
for (const privKey of privKeys) {
const pubKey = ec.keyFromPrivate(privKey).getPublic();
const pubKeyX = BigInt(pubKey.x.toString());
const pubKeyY = BigInt(pubKey.y.toString());
const pubKeyHash = poseidon.hash([pubKeyX, pubKeyY]);
const pubKey = privateToPublic(privKey);
const pubKeyHash = poseidon.hashPubKey(pubKey);
pubKeyHashes.push(pubKeyHash);
}

View File

@@ -2,28 +2,113 @@
## Usage example
```typescript
import { EffECDSAProver, EffECDSAVerifier } from "spartan-ecdsa";
import { ecsign, hashPersonalMessage } from "@ethereumjs/util";
### Proving membership to a group of public keys
// Sign
```typescript
// Setup
const privKey = Buffer.from("".padStart(16, "🧙"), "utf16le");
const msg = Buffer.from("harry potter");
const msgHash = hashPersonalMessage(msg);
const { v, r, s } = ecsign(msg, privKey);
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)}`;
// Prove
const prover = new EffECDSAProver();
let wasm = new SpartanWasm(defaultWasmConfig);
const { proof, publicInput } = await prover.prove(sig, msgHash);
// Init the Poseidon hash
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
// Verify
const verifier = new EffECDSAVerifier();
const treeDepth = 20;
const tree = new Tree(treeDepth, poseidon);
const verified = await verifier.verify(proof.proof, proof.publicInput);
console.log("Verified?", verified);
// Get the prover public key hash
const proverPubkeyHash = poseidon.hashPubKey(pubKey);
// 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(pubKey));
}
// Compute the merkle proof
const index = tree.indexOf(proverPubkeyHash);
const merkleProof = tree.createProof(index);
// Init the prover
const prover = new MembershipProver(defaultPubkeyMembershipConfig);
await prover.initWasm(wasm);
// Prove membership
await prover.prove(sig, msgHash, merkleProof);
```
### Proving membership to a group of addresses
```typescript
import {
hashPersonalMessage,
privateToAddress,
ecsign
} from "@ethereumjs/util";
import {
SpartanWasm,
Tree,
Poseidon,
MembershipProver,
defaultAddressMembershipConfig,
defaultWasmConfig
} from "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);
// Init the Poseidon hash
const poseidon = new Poseidon();
await poseidon.initWasm(wasm);
const treeDepth = 20;
const tree = new Tree(treeDepth, poseidon);
// Get the prover address
const proverAddress = BigInt("0x" + privateToAddress(privKey).toString("hex"));
// Insert prover address 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(address);
}
// Compute the merkle proof
const index = tree.indexOf(proverAddress);
const merkleProof = tree.createProof(index);
// Init the prover
const prover = new MembershipProver(defaultAddressMembershipConfig);
await prover.initWasm(wasm);
// Prove membership
await prover.prove(sig, msgHash, merkleProof);
```
## Development

View File

@@ -0,0 +1,7 @@
# 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

@@ -4,7 +4,7 @@
"main": "./build/lib.js",
"license": "MIT",
"scripts": {
"build": "tsc"
"build": "tsc && sh ./copy_artifacts.sh"
},
"devDependencies": {
"@types/jest": "^29.2.5",

View File

@@ -1,12 +1,45 @@
export const DEFAULT_EFF_ECDSA_WITNESS_GEN_WASM =
"https://storage.googleapis.com/personae-proving_keys/eff_ecdsa/eff_ecdsa.wasm";
export const DEFAULT_EFF_ECDSA_CIRCUIT =
"https://storage.googleapis.com/personae-proving_keys/eff_ecdsa/eff_ecdsa.circuit";
export const DEFAULT_SPARTAN_WASM =
"https://storage.googleapis.com/personae-proving_keys/spartan_wasm_bg.wasm";
import * as path from "path";
const isWeb = typeof window !== "undefined";
import { LeafType, ProverConfig, WasmConfig } from "./types";
export const DEFAULT_MEMBERSHIP_WITNESS_GEN_WASM =
"https://storage.googleapis.com/personae-proving-keys/spartan_wasm_bg.wasm";
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")
};
export const DEFAULT_MEMBERSHIP_CIRCUIT =
"https://storage.googleapis.com/personae-proving_keys/membership/membership.circuit";
// 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"),
circuit: isWeb
? "https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.circuit"
: path.join(__dirname, "circuits/pubkey_membership.circuit"),
leafType: LeafType.PubKeyHash
};
// 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"),
circuit: isWeb
? "https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.circuit"
: path.join(__dirname, "circuits/addr_membership.circuit"),
leafType: LeafType.Address
};

View File

@@ -1,76 +0,0 @@
import {
DEFAULT_EFF_ECDSA_CIRCUIT,
DEFAULT_EFF_ECDSA_WITNESS_GEN_WASM
} from "../config";
import { loadCircuit, snarkJsWitnessGen, fromSig } from "../helpers/utils";
import {
EffEcdsaPubInput,
EffEcdsaCircuitPubInput
} from "../helpers/efficient_ecdsa";
import { SpartanWasm } from "../wasm";
import { hashPersonalMessage } from "@ethereumjs/util";
import { ProverOptions, IProver, NIZK } from "../types";
import { Profiler } from "../helpers/profiler";
export class EffECDSAProver extends Profiler implements IProver {
spartanWasm: SpartanWasm;
circuit: string;
witnessGenWasm: string;
constructor(options?: ProverOptions) {
super({ enabled: options?.enableProfiler });
this.spartanWasm = new SpartanWasm({ spartanWasm: options?.spartanWasm });
this.circuit = options?.circuit || DEFAULT_EFF_ECDSA_CIRCUIT;
this.witnessGenWasm =
options?.witnessGenWasm || DEFAULT_EFF_ECDSA_WITNESS_GEN_WASM;
}
// sig: format of the `eth_sign` RPC method
// https://ethereum.github.io/execution-apis/api-documentation
async prove(sig: string, msg: Buffer): Promise<NIZK> {
const { r, s, v } = fromSig(sig);
const msgHash = hashPersonalMessage(msg);
const circuitPubInput = EffEcdsaCircuitPubInput.computeFromSig(
r,
v,
msgHash
);
const effEcdsaPubInput = new EffEcdsaPubInput(
r,
v,
msgHash,
circuitPubInput
);
const witnessGenInput = {
s,
...circuitPubInput
};
this.time("Generate witness");
const witness = await snarkJsWitnessGen(
witnessGenInput,
this.witnessGenWasm
);
this.timeEnd("Generate witness");
this.time("Load circuit");
const circuitBin = await loadCircuit(this.circuit);
this.timeEnd("Load circuit");
await this.spartanWasm.init();
this.time("Prove");
let proof = await this.spartanWasm.prove(
circuitBin,
witness.data,
effEcdsaPubInput.circuitPubInput.serialize()
);
this.timeEnd("Prove");
return { proof, publicInput: effEcdsaPubInput.serialize() };
}
}

View File

@@ -1,51 +0,0 @@
import { DEFAULT_EFF_ECDSA_CIRCUIT } from "../config";
import { loadCircuit } from "../helpers/utils";
import { SpartanWasm } from "../wasm";
import {
EffEcdsaPubInput,
verifyEffEcdsaPubInput
} from "../helpers/efficient_ecdsa";
import { Profiler } from "../helpers/profiler";
import { VerifyOptions, IVerifier } from "../types";
export class EffECDSAVerifier extends Profiler implements IVerifier {
spartanWasm: SpartanWasm;
circuit: string;
constructor(options?: VerifyOptions) {
super({ enabled: options?.enableProfiler });
this.circuit = options?.circuit || DEFAULT_EFF_ECDSA_CIRCUIT;
this.spartanWasm = new SpartanWasm({ spartanWasm: options?.spartanWasm });
}
async verify(
proof: Uint8Array,
publicInputSer: Uint8Array
): Promise<boolean> {
this.time("Load circuit");
const circuitBin = await loadCircuit(this.circuit);
this.timeEnd("Load circuit");
this.time("Verify public input");
const publicInput = EffEcdsaPubInput.deserialize(publicInputSer);
const isPubInputValid = verifyEffEcdsaPubInput(publicInput);
this.timeEnd("Verify public input");
this.time("Verify NIZK");
await this.spartanWasm.init();
let nizkValid;
try {
nizkValid = await this.spartanWasm.verify(
circuitBin,
proof,
publicInput.circuitPubInput.serialize()
);
} catch (e) {
return false;
}
this.timeEnd("Verify NIZK");
return isPubInputValid && nizkValid;
}
}

View File

@@ -1,5 +1,5 @@
import { Profiler } from "../helpers/profiler";
import { IProver, MerkleProof, NIZK, ProverOptions } from "../types";
import { IProver, MerkleProof, NIZK, ProverConfig, LeafType } from "../types";
import { SpartanWasm } from "../wasm";
import {
bigIntToBytes,
@@ -11,27 +11,27 @@ import {
EffEcdsaPubInput,
EffEcdsaCircuitPubInput
} from "../helpers/efficient_ecdsa";
import {
DEFAULT_MEMBERSHIP_CIRCUIT,
DEFAULT_MEMBERSHIP_WITNESS_GEN_WASM
} from "../config";
/**
* ECDSA Membership Prover
*/
export class MembershipProver extends Profiler implements IProver {
spartanWasm: SpartanWasm;
spartanWasm!: SpartanWasm;
circuit: string;
witnessGenWasm: string;
leafType: LeafType;
constructor(options?: ProverOptions) {
constructor(options: ProverConfig) {
super({ enabled: options?.enableProfiler });
const spartanWasm = new SpartanWasm({ spartanWasm: options?.spartanWasm });
this.spartanWasm = spartanWasm;
this.circuit = options?.circuit || DEFAULT_MEMBERSHIP_CIRCUIT;
this.witnessGenWasm =
options?.witnessGenWasm || DEFAULT_MEMBERSHIP_WITNESS_GEN_WASM;
this.leafType = options.leafType;
this.circuit = options.circuit;
this.witnessGenWasm = options.witnessGenWasm;
}
async initWasm(wasm: SpartanWasm) {
this.spartanWasm = wasm;
this.spartanWasm.init();
}
// @ts-ignore
@@ -81,7 +81,6 @@ export class MembershipProver extends Profiler implements IProver {
const circuitBin = await loadCircuit(this.circuit);
this.timeEnd("Load circuit");
await this.spartanWasm.init();
this.time("Prove");
let proof = await this.spartanWasm.prove(
circuitBin,

View File

@@ -2,16 +2,11 @@ import { SpartanWasm } from "../wasm";
import { bigIntToLeBytes, bytesLeToBigInt } from "./utils";
export class Poseidon {
wasm: SpartanWasm;
constructor(wasm?: SpartanWasm) {
if (typeof wasm === "undefined") {
this.wasm = new SpartanWasm();
} else {
this.wasm = wasm;
}
}
wasm!: SpartanWasm;
constructor() {}
async init() {
async initWasm(wasm: SpartanWasm) {
this.wasm = wasm;
await this.wasm.init();
}
@@ -24,4 +19,12 @@ export class Poseidon {
const result = this.wasm.poseidon(inputsBytes);
return bytesLeToBigInt(result);
}
hashPubKey(pubKey: Buffer): bigint {
const pubKeyX = BigInt("0x" + pubKey.toString("hex").slice(0, 64));
const pubKeyY = BigInt("0x" + pubKey.toString("hex").slice(64, 128));
const pubKeyHash = this.hash([pubKeyX, pubKeyY]);
return pubKeyHash;
}
}

View File

@@ -16,18 +16,6 @@ export class Tree {
this.treeInner = new IncrementalMerkleTree(hash, this.depth, BigInt(0));
}
private hashPubKey(pubKey: Buffer): bigint {
const pubKeyX = BigInt("0x" + pubKey.toString("hex").slice(0, 64));
const pubKeyY = BigInt("0x" + pubKey.toString("hex").slice(64, 128));
const pubKeyHash = this.poseidon.hash([pubKeyX, pubKeyY]);
return pubKeyHash;
}
hashAndInsert(pubKey: Buffer) {
this.insert(this.hashPubKey(pubKey));
}
insert(leaf: bigint) {
this.treeInner.insert(leaf);
}
@@ -36,8 +24,8 @@ export class Tree {
return this.treeInner.root;
}
indexOf(pubKey: Buffer): number {
return this.treeInner.indexOf(this.hashPubKey(pubKey));
indexOf(leaf: bigint): number {
return this.treeInner.indexOf(leaf);
}
createProof(index: number): MerkleProof {

View File

@@ -1,7 +1,7 @@
export * from "./types";
export * from "./helpers/efficient_ecdsa";
export * from "./core/eff_ecdsa_prover";
export * from "./core/membership_prover";
export * from "./core/eff_ecdsa_verifier";
export * from "./helpers/tree";
export * from "./helpers/poseidon";
export * from "./wasm/index";
export * from "./config";

View File

@@ -14,13 +14,17 @@ export interface NIZK {
publicInput: Uint8Array;
}
export interface ProverOptions {
export interface ProverConfig {
proverWasm?: string;
witnessGenWasm?: string;
circuit?: string;
spartanWasm?: string;
witnessGenWasm: string;
circuit: string;
spartanWasm: string;
enableProfiler?: boolean;
leafType: LeafType;
}
export interface WasmConfig {
pathOrUrl: string;
}
export interface VerifyOptions {
@@ -45,5 +49,10 @@ export interface IVerifier {
}
export interface SpartanWasmOptions {
spartanWasm?: string;
spartanWasm: string;
}
export enum LeafType {
PubKeyHash,
Address
}

View File

@@ -2,20 +2,14 @@ import * as wasm from "./wasm";
import _initWeb from "./wasm.js";
import fs from "fs";
import path from "path";
import { SpartanWasmOptions } from "../types";
import { DEFAULT_SPARTAN_WASM } from "../config";
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;
constructor(options?: SpartanWasmOptions) {
const defaultWasmPath =
typeof window === "undefined"
? path.join(__dirname, "./build/spartan_wasm_bg.wasm")
: DEFAULT_SPARTAN_WASM;
this.spartanWasmPathOrUrl = options?.spartanWasm || defaultWasmPath;
constructor(config: WasmConfig) {
this.spartanWasmPathOrUrl = config.pathOrUrl;
}
async init() {

View File

@@ -1,60 +0,0 @@
import { EffECDSAProver, EffECDSAVerifier } from "../src/lib";
import { hashPersonalMessage, ecsign } from "@ethereumjs/util";
import * as path from "path";
describe("eff_ecdsa prove and verify", () => {
// Sign message
const privKey = Buffer.from(
"f5b552f608f5b552f608f5b552f6082ff5b552f608f5b552f608f5b552f6082f",
"hex"
);
let 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)}`;
const CIRCUIT = path.join(
__dirname,
"/../../circuits/build/eff_ecdsa/eff_ecdsa.circuit"
);
const WITNESS_GEN_WASM = path.join(
__dirname,
"/../../circuits/build/eff_ecdsa/eff_ecdsa_js/eff_ecdsa.wasm"
);
// Init prover and verifier
let prover = new EffECDSAProver({
enableProfiler: false,
circuit: CIRCUIT,
witnessGenWasm: WITNESS_GEN_WASM
});
let verifier = new EffECDSAVerifier({
circuit: CIRCUIT,
enableProfiler: false
});
it("should prove and verify valid signature", async () => {
const { proof, publicInput } = await prover.prove(sig, msg);
const result = await verifier.verify(proof, publicInput);
expect(result).toBe(true);
});
it("verifier should return false when the proof is invalid", async () => {
const { proof, publicInput } = await prover.prove(sig, msg);
proof[0] = proof[0] + 1;
const result = await verifier.verify(proof, publicInput);
expect(result).toBe(false);
});
it("verifier should return false when the public input is invalid", async () => {
const { proof, publicInput } = await prover.prove(sig, msg);
publicInput[0] = publicInput[0] + 1;
const result = await verifier.verify(proof, publicInput);
expect(result).toBe(false);
});
});

View File

@@ -1,13 +1,24 @@
import * as path from "path";
import { MembershipProver, Tree, Poseidon } from "../src/lib";
import { hashPersonalMessage, ecsign, privateToPublic } from "@ethereumjs/util";
import {
MembershipProver,
Tree,
Poseidon,
defaultAddressMembershipConfig,
defaultPubkeyMembershipConfig,
SpartanWasm,
defaultWasmConfig
} from "../src/lib";
import {
hashPersonalMessage,
ecsign,
privateToAddress,
privateToPublic
} from "@ethereumjs/util";
var EC = require("elliptic").ec;
const ec = new EC("secp256k1");
//! Still doesn't pass. Need to fix.
describe("membership prove and verify", () => {
// Init prover
const treeDepth = 10;
const treeDepth = 20;
const privKeys = ["1", "a", "bb", "ccc", "dddd", "ffff"].map(val =>
Buffer.from(val.padStart(64, "0"), "hex")
@@ -16,6 +27,7 @@ describe("membership prove and verify", () => {
// Sign (Use privKeys[0] for proving)
const proverIndex = 0;
const proverPrivKey = privKeys[proverIndex];
let proverAddress: bigint;
let msg = Buffer.from("harry potter");
const msgHash = hashPersonalMessage(msg);
@@ -24,47 +36,83 @@ describe("membership prove and verify", () => {
const sig = `0x${r.toString("hex")}${s.toString("hex")}${v.toString(16)}`;
let poseidon: Poseidon;
let tree: Tree;
let prover: MembershipProver;
let wasm: SpartanWasm;
beforeAll(async () => {
// Init Wasm
wasm = new SpartanWasm(defaultWasmConfig);
// Init Poseidon
poseidon = new Poseidon();
await poseidon.init();
tree = new Tree(treeDepth, poseidon);
const CIRCUIT = path.join(
__dirname,
"/../../circuits/build/membership/membership.circuit"
);
const WITNESS_GEN_WASM = path.join(
__dirname,
"/../../circuits/build/membership/membership_js/membership.wasm"
);
prover = new MembershipProver({
circuit: CIRCUIT,
witnessGenWasm: WITNESS_GEN_WASM
});
// Insert the members into the tree
for (const privKey of privKeys) {
const pubKey = privateToPublic(privKey);
tree.hashAndInsert(pubKey);
}
await poseidon.initWasm(wasm);
});
it("should prove and verify valid signature and merkle proof", async () => {
const index = tree.indexOf(privateToPublic(proverPrivKey));
const merkleProof = tree.createProof(proverIndex);
describe("pubkey_membership prover and verify", () => {
it("should prove and verify valid signature and merkle proof", async () => {
const pubKeyTree = new Tree(treeDepth, poseidon);
const { proof, publicInput } = await prover.prove(
sig,
msgHash,
merkleProof
);
let proverPubKeyHash;
// Insert the members into the tree
for (const privKey of privKeys) {
const pubKey = privateToPublic(privKey);
const pubKeyHash = poseidon.hashPubKey(pubKey);
pubKeyTree.insert(pubKeyHash);
// TODO: Verify the proof
// Set prover's public key hash for the reference below
if (proverPrivKey === privKey) proverPubKeyHash = pubKeyHash;
}
const pubKeyMembershipProver = new MembershipProver(
defaultPubkeyMembershipConfig
);
await pubKeyMembershipProver.initWasm(wasm);
const index = pubKeyTree.indexOf(proverPubKeyHash as bigint);
const merkleProof = pubKeyTree.createProof(index);
const { proof, publicInput } = await pubKeyMembershipProver.prove(
sig,
msgHash,
merkleProof
);
// TODO: Verify the proof
});
});
describe("adddr_membership prover and verify", () => {
it("should prove and verify valid signature and merkle proof", async () => {
const addressTree = new Tree(treeDepth, poseidon);
let proverAddress;
// Insert the members into the tree
for (const privKey of privKeys) {
const address = BigInt(
"0x" + privateToAddress(privKey).toString("hex")
);
addressTree.insert(address);
// Set prover's public key hash for the reference below
if (proverPrivKey === privKey) proverAddress = address;
}
const addressMembershipProver = new MembershipProver(
defaultAddressMembershipConfig
);
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
);
// TODO: Verify the proof
});
});
});

View File

@@ -0,0 +1,2 @@
#!/bin/bash
sh ./scripts/compile_circuit.sh addr_membership 5

View File

@@ -0,0 +1,15 @@
CIRCUIT_NAME=$1
NUM_PUB_INPUTS=$2
BUILD_DIR=./packages/circuits/build/$CIRCUIT_NAME
mkdir -p $BUILD_DIR &&
circom ./packages/circuits/instances/$CIRCUIT_NAME.circom --r1cs --wasm --prime secq256k1 -o $BUILD_DIR &&
# Compile circom r1cs into binary
cargo run --release --bin gen_spartan_inst $BUILD_DIR/$CIRCUIT_NAME.r1cs $BUILD_DIR/$CIRCUIT_NAME.circuit $NUM_PUB_INPUTS &&
# Copy the circuit into the lib dir
LIB_CIRCUITS_DIR=./packages/lib/src/circuits
mkdir -p $LIB_CIRCUITS_DIR &&
cp $BUILD_DIR/*_js/*.wasm $LIB_CIRCUITS_DIR &&
cp $BUILD_DIR/*.circuit $LIB_CIRCUITS_DIR

View File

@@ -1,4 +0,0 @@
BUILD_DIR=./packages/circuits/build/eff_ecdsa
mkdir -p $BUILD_DIR &&
circom ./packages/circuits/instances/eff_ecdsa.circom --r1cs --wasm --prime secq256k1 -o $BUILD_DIR &&
cargo run --bin gen_spartan_inst $BUILD_DIR/eff_ecdsa.r1cs $BUILD_DIR/eff_ecdsa.circuit 4

View File

@@ -1,4 +0,0 @@
BUILD_DIR=./packages/circuits/build/eff_ecdsa_to_addr
mkdir -p $BUILD_DIR &&
circom ./packages/circuits/instances/eff_ecdsa_to_addr.circom --r1cs --wasm --prime secq256k1 -o $BUILD_DIR &&
cargo run --release --bin gen_spartan_inst $BUILD_DIR/eff_ecdsa_to_addr.r1cs $BUILD_DIR/eff_ecdsa_to_addr.circuit 4

View File

@@ -1,4 +0,0 @@
BUILD_DIR=./packages/circuits/build/membership
mkdir -p $BUILD_DIR &&
circom ./packages/circuits/instances/membership.circom --r1cs --wasm --prime secq256k1 -o $BUILD_DIR &&
cargo run --release --bin gen_spartan_inst $BUILD_DIR/membership.r1cs $BUILD_DIR/membership.circuit 4

View File

@@ -0,0 +1,2 @@
#!/bin/bash
sh ./scripts/compile_circuit.sh pubkey_membership 5