mirror of
https://github.com/AtHeartEngineer/spartan-ecdsa.git
synced 2026-01-09 21:38:02 -05:00
Merge branch 'main' into lsankar/fix-lerna-build
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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/
|
||||
@@ -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",
|
||||
|
||||
9
packages/benchmark/node/src/node.bench.ts
Normal file
9
packages/benchmark/node/src/node.bench.ts
Normal 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();
|
||||
66
packages/benchmark/node/src/node.bench_addr_membership.ts
Normal file
66
packages/benchmark/node/src/node.bench_addr_membership.ts
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
5
packages/circuits/instances/addr_membership.circom
Normal file
5
packages/circuits/instances/addr_membership.circom
Normal 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);
|
||||
@@ -1,5 +0,0 @@
|
||||
pragma circom 2.1.2;
|
||||
|
||||
include "../eff_ecdsa_membership/eff_ecdsa.circom";
|
||||
|
||||
component main { public[ Tx, Ty, Ux, Uy ]} = EfficientECDSA();
|
||||
@@ -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);
|
||||
5
packages/circuits/instances/pubkey_membership.circom
Normal file
5
packages/circuits/instances/pubkey_membership.circom
Normal 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);
|
||||
73
packages/circuits/tests/addr_membership.test.ts
Normal file
73
packages/circuits/tests/addr_membership.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
pragma circom 2.1.2;
|
||||
|
||||
include "../../eff_ecdsa_membership/addr_membership.circom";
|
||||
|
||||
component main = AddrMembership(10);
|
||||
@@ -1,5 +0,0 @@
|
||||
pragma circom 2.1.2;
|
||||
|
||||
include "../../eff_ecdsa_membership/membership.circom";
|
||||
|
||||
component main = Membership(10);
|
||||
@@ -0,0 +1,5 @@
|
||||
pragma circom 2.1.2;
|
||||
|
||||
include "../../eff_ecdsa_membership/pubkey_membership.circom";
|
||||
|
||||
component main = PubKeyMembership(10);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
7
packages/lib/copy_artifacts.sh
Normal file
7
packages/lib/copy_artifacts.sh
Normal 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/
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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() };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
2
scripts/addr_membership_circuit.sh
Normal file
2
scripts/addr_membership_circuit.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
sh ./scripts/compile_circuit.sh addr_membership 5
|
||||
15
scripts/compile_circuit.sh
Normal file
15
scripts/compile_circuit.sh
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
2
scripts/pubkey_membership_circuit.sh
Normal file
2
scripts/pubkey_membership_circuit.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
sh ./scripts/compile_circuit.sh pubkey_membership 5
|
||||
Reference in New Issue
Block a user