5 Commits
v2.3.1 ... main

Author SHA1 Message Date
Daniel Tehrani
4bf236a1a5 Merge pull request #45 from 0xisk/main
refactor: minor enhancements on the lib package
2023-12-05 14:12:43 -08:00
Daniel Tehrani
ede2b29cac fix: use tsconfig.build.json so vscode can annotate test files as well 2023-12-02 08:45:37 +09:00
0xisk
cb273002c0 refactor: destructure params, update package metadata, enhance file naming and structure. 2023-11-29 20:41:48 +01:00
Daniel Tehrani
a08876d1ff Merge pull request #44 from 0xisk/main
chore: refactor lib package exports to named for clarity
2023-11-25 13:42:03 +09:00
isk
8b0529f868 chore: refactor lib exports to named for clarity 2023-11-24 11:40:18 +01:00
21 changed files with 253 additions and 188 deletions

3
.gitignore vendored
View File

@@ -23,6 +23,7 @@ circom_witness.wtns
*.ptau
build/
dist/
*.r1cs
*.sym
@@ -33,7 +34,7 @@ packages/prover/test_circuit/test_circuit_js/
packages/prover/test_circuit/*.json
wasm_bytes.ts
wasmBytes.ts
**/sage/*.sage.py
packages/lib/src/circuits/

View File

@@ -12,7 +12,7 @@ const embedWasmBytes = async () => {
export const wasmBytes = new Uint8Array([${bytes.toString()}]);
`;
fs.writeFileSync("./src/wasm/wasm_bytes.ts", file);
fs.writeFileSync("./src/wasm/wasmBytes.ts", file);
};
embedWasmBytes();

View File

@@ -3,7 +3,10 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
"^.+\\.js?$": "ts-jest"
"^.+\\.(ts|js)?$": "ts-jest"
},
moduleNameMapper: {
"@src/(.*)$": "<rootDir>/src/$1",
},
testTimeout: 600000,
};

View File

@@ -1,17 +1,32 @@
{
"name": "@personaelabs/spartan-ecdsa",
"version": "2.3.1",
"main": "./build/lib.js",
"types": "./build/lib.d.ts",
"description": "Spartan-ecdsa (which to our knowledge) is the fastest open-source method to verify ECDSA (secp256k1) signatures in zero-knowledge.",
"keywords": [
"spartan",
"spartan-ecdsa",
"zk",
"efficient-ecdsa"
],
"author": "Personae Labs",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "MIT",
"bugs": {
"url": "https://github.com/personaelabs/spartan-ecdsa/issues/new"
},
"homepage": "https://github.com/personaelabs/spartan-ecdsa",
"publishConfig": {
"access": "public"
},
"files": [
"build/**/*"
"dist/**/*"
],
"scripts": {
"build": "rm -rf ./build && yarn embedWasmBytes && tsc",
"build": "rm -rf ./dist && yarn embedWasmBytes && tsc --project tsconfig.build.json",
"prepublishOnly": "yarn build",
"prepare": "yarn embedWasmBytes",
"embedWasmBytes": "ts-node ./embed_wasm_bytes.ts",
"embedWasmBytes": "ts-node ./embedWasmBytes.ts",
"test": "jest"
},
"devDependencies": {
@@ -27,4 +42,4 @@
"elliptic": "^6.5.4",
"snarkjs": "^0.7.1"
}
}
}

View File

@@ -1,25 +0,0 @@
import { ProverConfig, VerifyConfig } from "./types";
// Default configs for pubkey membership proving/verifying
export const defaultPubkeyMembershipPConfig: ProverConfig = {
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 = {
circuit: defaultPubkeyMembershipPConfig.circuit
};
// Default configs for address membership proving/verifyign
export const defaultAddressMembershipPConfig: ProverConfig = {
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 = {
circuit: defaultAddressMembershipPConfig.circuit
};

View File

@@ -0,0 +1,25 @@
import { ProverConfig, VerifyConfig } from "@src/types";
// Default configs for pubkey membership proving/verifying
export const defaultPubkeyProverConfig: ProverConfig = {
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 defaultPubkeyVerifierConfig: VerifyConfig = {
circuit: defaultPubkeyProverConfig.circuit
};
// Default configs for address membership proving/verifyign
export const defaultAddressProverConfig: ProverConfig = {
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 defaultAddressVerifierConfig: VerifyConfig = {
circuit: defaultAddressProverConfig.circuit
};

View File

@@ -1,16 +1,16 @@
import { Profiler } from "../helpers/profiler";
import { IProver, MerkleProof, NIZK, ProverConfig } from "../types";
import { loadCircuit, fromSig, snarkJsWitnessGen } from "../helpers/utils";
import { Profiler } from "@src/helpers/profiler";
import { IProver, MerkleProof, NIZK, ProveArgs, ProverConfig } from "@src/types";
import { loadCircuit, fromSig, snarkJsWitnessGen } from "@src/helpers/utils";
import {
PublicInput,
computeEffEcdsaPubInput,
CircuitPubInput
} from "../helpers/public_input";
import wasm, { init } from "../wasm";
} from "@src/helpers/publicInputs";
import { init, wasm } from "@src/wasm";
import {
defaultPubkeyMembershipPConfig,
defaultAddressMembershipPConfig
} from "../config";
defaultPubkeyProverConfig,
defaultAddressProverConfig
} from "@src/config";
/**
* ECDSA Membership Prover
@@ -20,15 +20,20 @@ export class MembershipProver extends Profiler implements IProver {
witnessGenWasm: string;
useRemoteCircuit: boolean;
constructor(options: ProverConfig) {
super({ enabled: options?.enableProfiler });
constructor({
enableProfiler,
circuit,
witnessGenWasm,
useRemoteCircuit
}: ProverConfig) {
super({ enabled: enableProfiler });
if (
options.circuit === defaultPubkeyMembershipPConfig.circuit ||
options.witnessGenWasm ===
defaultPubkeyMembershipPConfig.witnessGenWasm ||
options.circuit === defaultAddressMembershipPConfig.circuit ||
options.witnessGenWasm === defaultAddressMembershipPConfig.witnessGenWasm
circuit === defaultPubkeyProverConfig.circuit ||
witnessGenWasm ===
defaultPubkeyProverConfig.witnessGenWasm ||
circuit === defaultAddressProverConfig.circuit ||
witnessGenWasm === defaultAddressProverConfig.witnessGenWasm
) {
console.warn(`
Spartan-ecdsa default config warning:
@@ -38,21 +43,16 @@ export class MembershipProver extends Profiler implements IProver {
`);
}
this.circuit = options.circuit;
this.witnessGenWasm = options.witnessGenWasm;
this.useRemoteCircuit = options.useRemoteCircuit ?? false;
this.circuit = circuit;
this.witnessGenWasm = witnessGenWasm;
this.useRemoteCircuit = useRemoteCircuit ?? false;
}
async initWasm() {
await init();
}
// @ts-ignore
async prove(
sig: string,
msgHash: Buffer,
merkleProof: MerkleProof
): Promise<NIZK> {
async prove({ sig, msgHash, merkleProof }: ProveArgs): Promise<NIZK> {
const { r, s, v } = fromSig(sig);
const effEcdsaPubInput = computeEffEcdsaPubInput(r, v, msgHash);

View File

@@ -1,12 +1,12 @@
import {
defaultAddressMembershipVConfig,
defaultPubkeyMembershipVConfig
} from "../config";
import { Profiler } from "../helpers/profiler";
import { loadCircuit } from "../helpers/utils";
import { IVerifier, VerifyConfig } from "../types";
import wasm, { init } from "../wasm";
import { PublicInput, verifyEffEcdsaPubInput } from "../helpers/public_input";
defaultAddressVerifierConfig,
defaultPubkeyVerifierConfig
} from "@src/config";
import { Profiler } from "@src/helpers/profiler";
import { loadCircuit } from "@src/helpers/utils";
import { IVerifier, VerifyArgs, VerifyConfig } from "@src/types";
import { init, wasm } from "@src/wasm";
import { PublicInput, verifyEffEcdsaPubInput } from "@src/helpers/publicInputs";
/**
* ECDSA Membership Verifier
@@ -15,12 +15,16 @@ export class MembershipVerifier extends Profiler implements IVerifier {
circuit: string;
useRemoteCircuit: boolean;
constructor(options: VerifyConfig) {
super({ enabled: options?.enableProfiler });
constructor({
circuit,
enableProfiler,
useRemoteCircuit
}: VerifyConfig) {
super({ enabled: enableProfiler });
if (
options.circuit === defaultAddressMembershipVConfig.circuit ||
options.circuit === defaultPubkeyMembershipVConfig.circuit
circuit === defaultAddressVerifierConfig.circuit ||
circuit === defaultPubkeyVerifierConfig.circuit
) {
console.warn(`
Spartan-ecdsa default config warning:
@@ -30,19 +34,16 @@ export class MembershipVerifier extends Profiler implements IVerifier {
`);
}
this.circuit = options.circuit;
this.circuit = circuit;
this.useRemoteCircuit =
options.useRemoteCircuit || typeof window !== "undefined";
useRemoteCircuit || typeof window !== "undefined";
}
async initWasm() {
await init();
}
async verify(
proof: Uint8Array,
publicInputSer: Uint8Array
): Promise<boolean> {
async verify({ proof, publicInputSer }: VerifyArgs): Promise<boolean> {
this.time("Load circuit");
const circuitBin = await loadCircuit(this.circuit, this.useRemoteCircuit);
this.timeEnd("Load circuit");

View File

@@ -1,5 +1,5 @@
import { init, wasm } from "@src/wasm";
import { bigIntToLeBytes, bytesLeToBigInt } from "./utils";
import wasm, { init } from "../wasm";
export class Poseidon {
hash(inputs: bigint[]): bigint {

View File

@@ -1,8 +1,8 @@
var EC = require("elliptic").ec;
const BN = require("bn.js");
import { EffECDSAPubInput } from "@src/types";
import { bytesToBigInt, bigIntToBytes } from "./utils";
import { EffECDSAPubInput } from "../types";
const ec = new EC("secp256k1");
@@ -144,15 +144,18 @@ export const computeEffEcdsaPubInput = (
/**
* Verify the public values of the efficient ECDSA circuit
*/
export const verifyEffEcdsaPubInput = (pubInput: PublicInput): boolean => {
export const verifyEffEcdsaPubInput = ({
r,
rV,
msgHash,
circuitPubInput
}: PublicInput): boolean => {
const expectedCircuitInput = computeEffEcdsaPubInput(
pubInput.r,
pubInput.rV,
pubInput.msgHash
r,
rV,
msgHash
);
const circuitPubInput = pubInput.circuitPubInput;
const isValid =
expectedCircuitInput.Tx === circuitPubInput.Tx &&
expectedCircuitInput.Ty === circuitPubInput.Ty &&

View File

@@ -18,7 +18,10 @@ export const snarkJsWitnessGen = async (input: any, wasmFile: string) => {
/**
* Load a circuit from a file or URL
*/
export const loadCircuit = async (pathOrUrl: string, useRemoteCircuit: boolean): Promise<Uint8Array> => {
export const loadCircuit = async (
pathOrUrl: string,
useRemoteCircuit: boolean
): Promise<Uint8Array> => {
if (useRemoteCircuit) {
return await fetchCircuit(pathOrUrl);
} else {

View File

@@ -0,0 +1,8 @@
export { MembershipProver } from "@src/core/prover";
export { MembershipVerifier } from "@src/core/verifier";
export { CircuitPubInput, PublicInput, computeEffEcdsaPubInput, verifyEffEcdsaPubInput } from "@src/helpers/publicInputs";
export { Tree } from "@src/helpers/tree";
export { Poseidon } from "@src/helpers/poseidon";
export { init, wasm } from "@src/wasm/index";
export { defaultPubkeyProverConfig as defaultPubkeyMembershipPConfig, defaultPubkeyVerifierConfig as defaultPubkeyMembershipVConfig, defaultAddressProverConfig as defaultAddressMembershipPConfig, defaultAddressVerifierConfig as defaultAddressMembershipVConfig } from "@src/config";
export type { MerkleProof, EffECDSAPubInput, NIZK, ProverConfig, VerifyConfig, IProver, IVerifier } from "@src/types";

View File

@@ -1,8 +0,0 @@
export * from "./types";
export * from "./helpers/public_input";
export * from "./core/membership_prover";
export * from "./core/membership_verifier";
export * from "./helpers/tree";
export * from "./helpers/poseidon";
export * from "./wasm/index";
export * from "./config";

View File

@@ -1,47 +0,0 @@
import { PublicInput } from "./helpers/public_input";
// 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.
export interface MerkleProof {
root: bigint;
siblings: [bigint][];
pathIndices: number[];
}
export interface EffECDSAPubInput {
Tx: bigint;
Ty: bigint;
Ux: bigint;
Uy: bigint;
}
export interface NIZK {
proof: Uint8Array;
publicInput: PublicInput;
}
export interface ProverConfig {
witnessGenWasm: string;
circuit: string;
enableProfiler?: boolean;
useRemoteCircuit?: boolean;
}
export interface VerifyConfig {
circuit: string; // Path to circuit file compiled by Nova-Scotia
enableProfiler?: boolean;
useRemoteCircuit?: boolean;
}
export interface IProver {
circuit: string; // Path to circuit file compiled by Nova-Scotia
witnessGenWasm: string; // Path to witness generator wasm file generated by Circom
prove(...args: any): Promise<NIZK>;
}
export interface IVerifier {
circuit: string; // Path to circuit file compiled by Nova-Scotia
verify(proof: Uint8Array, publicInput: Uint8Array): Promise<boolean>;
}

View File

@@ -0,0 +1,58 @@
import { PublicInput } from "@src/helpers/publicInputs";
// 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.
export interface MerkleProof {
root: bigint;
siblings: [bigint][];
pathIndices: number[];
}
export interface EffECDSAPubInput {
Tx: bigint;
Ty: bigint;
Ux: bigint;
Uy: bigint;
}
export interface NIZK {
proof: Uint8Array;
publicInput: PublicInput;
}
export interface ProverConfig {
witnessGenWasm: string;
circuit: string;
enableProfiler?: boolean;
useRemoteCircuit?: boolean;
}
export interface ProveArgs {
sig: string;
msgHash: Buffer,
merkleProof: MerkleProof;
}
export interface VerifyArgs {
proof: Uint8Array,
publicInputSer: Uint8Array
}
export interface VerifyConfig {
circuit: string; // Path to circuit file compiled by Nova-Scotia
enableProfiler?: boolean;
useRemoteCircuit?: boolean;
}
export interface IProver {
circuit: string; // Path to circuit file compiled by Nova-Scotia
witnessGenWasm: string; // Path to witness generator wasm file generated by Circom
prove({ sig, msgHash, merkleProof }: ProveArgs): Promise<NIZK>;
}
export interface IVerifier {
circuit: string; // Path to circuit file compiled by Nova-Scotia
verify({ proof, publicInputSer }: VerifyArgs): Promise<boolean>;
}

View File

@@ -1,10 +1,10 @@
import * as wasm from "./wasm";
import { wasmBytes } from "./wasm_bytes";
import { wasmBytes } from "./wasmBytes";
export const init = async () => {
await wasm.initSync(wasmBytes.buffer);
wasm.init_panic_hook();
};
export default wasm;
export { wasm };

View File

@@ -1,9 +1,10 @@
import { hashPersonalMessage } from "@ethereumjs/util";
import {
CircuitPubInput,
PublicInput,
verifyEffEcdsaPubInput
} from "../src/helpers/public_input";
import { hashPersonalMessage } from "@ethereumjs/util";
} from "../src/helpers/publicInputs";
describe("public_input", () => {
/**

View File

@@ -1,19 +1,20 @@
import {
MembershipProver,
MembershipVerifier,
Tree,
Poseidon,
NIZK
} from "../src/lib";
import {
hashPersonalMessage,
ecsign,
privateToAddress,
privateToPublic
} from "@ethereumjs/util";
var EC = require("elliptic").ec;
import * as path from "path";
import {
MembershipProver,
MembershipVerifier,
Tree,
Poseidon,
NIZK
} from "../src";
describe("membership prove and verify", () => {
// Init prover
const treeDepth = 20;
@@ -25,7 +26,6 @@ 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);
@@ -84,11 +84,14 @@ describe("membership prove and verify", () => {
const index = pubKeyTree.indexOf(proverPubKeyHash as bigint);
const merkleProof = pubKeyTree.createProof(index);
nizk = await pubKeyMembershipProver.prove(sig, msgHash, merkleProof);
nizk = await pubKeyMembershipProver.prove({ sig, msgHash, merkleProof });
const { proof, publicInput } = nizk;
expect(
await pubKeyMembershipVerifier.verify(proof, publicInput.serialize())
await pubKeyMembershipVerifier.verify({
proof,
publicInputSer: publicInput.serialize()
})
).toBe(true);
});
@@ -97,17 +100,23 @@ describe("membership prove and verify", () => {
let proof = nizk.proof;
proof[0] = proof[0] += 1;
expect(
await pubKeyMembershipVerifier.verify(proof, publicInput.serialize())
await pubKeyMembershipVerifier.verify({
proof,
publicInputSer: publicInput.serialize()
})
).toBe(false);
});
it("should assert invalid public input", async () => {
const { proof } = nizk;
let publicInput = nizk.publicInput.serialize();
publicInput[0] = publicInput[0] += 1;
expect(await pubKeyMembershipVerifier.verify(proof, publicInput)).toBe(
false
);
let publicInputSer = nizk.publicInput.serialize();
publicInputSer[0] = publicInputSer[0] += 1;
expect(
await pubKeyMembershipVerifier.verify({
proof,
publicInputSer
})
).toBe(false);
});
});
@@ -154,14 +163,14 @@ describe("membership prove and verify", () => {
await addressMembershipProver.initWasm();
nizk = await addressMembershipProver.prove(sig, msgHash, merkleProof);
nizk = await addressMembershipProver.prove({ sig, msgHash, merkleProof });
await addressMembershipVerifier.initWasm();
expect(
await addressMembershipVerifier.verify(
nizk.proof,
nizk.publicInput.serialize()
)
await addressMembershipVerifier.verify({
proof: nizk.proof,
publicInputSer: nizk.publicInput.serialize()
})
).toBe(true);
});
@@ -170,17 +179,23 @@ describe("membership prove and verify", () => {
let proof = nizk.proof;
proof[0] = proof[0] += 1;
expect(
await addressMembershipVerifier.verify(proof, publicInput.serialize())
await addressMembershipVerifier.verify({
proof,
publicInputSer: publicInput.serialize()
})
).toBe(false);
});
it("should assert invalid public input", async () => {
const { proof } = nizk;
let publicInput = nizk.publicInput.serialize();
publicInput[0] = publicInput[0] += 1;
expect(await addressMembershipVerifier.verify(proof, publicInput)).toBe(
false
);
let publicInputSer = nizk.publicInput.serialize();
publicInputSer[0] = publicInputSer[0] += 1;
expect(
await addressMembershipVerifier.verify({
proof,
publicInputSer
})
).toBe(false);
});
});
});

View File

@@ -1,4 +1,4 @@
import { Tree, Poseidon } from "../src/lib";
import { Tree, Poseidon } from "../src";
describe("Merkle tree prove and verify", () => {
let poseidon: Poseidon;

View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"exclude": [
"./tests/**/*"
],
}

View File

@@ -1,25 +1,31 @@
{
"compilerOptions": {
"baseUrl": ".",
"rootDir": ".",
"outDir": "./dist",
"declaration": true,
"target": "ES6",
"module": "CommonJS",
"moduleResolution": "node",
"allowJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"paths": {
"@src/*": [
"src/*"
]
},
},
"include": [
"./src/**/*",
"./src/**/*.wasm",
"./tests/**/*"
],
"exclude": [
"./jest.config.js",
"./node_modules",
"./tests",
"./build"
"./dist"
],
"compilerOptions": {
"declaration": true,
"target": "ES6",
"module": "CommonJS",
"rootDir": "./src",
"moduleResolution": "node",
"allowJs": true,
"outDir": "./build",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}