From 2afdd649ad534d9a8755afea84b61e3c4b5b11fd Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Thu, 28 Sep 2023 22:05:38 +0800 Subject: [PATCH 01/21] use mina blockchain to storage the password tree root --- src/plugins/passwordTree/plugin/index.ts | 240 +++++++++++++++--- .../plugin/treeRootStorageContract.ts | 15 ++ 2 files changed, 220 insertions(+), 35 deletions(-) create mode 100644 src/plugins/passwordTree/plugin/treeRootStorageContract.ts diff --git a/src/plugins/passwordTree/plugin/index.ts b/src/plugins/passwordTree/plugin/index.ts index 0bab9e7..6a47e16 100644 --- a/src/plugins/passwordTree/plugin/index.ts +++ b/src/plugins/passwordTree/plugin/index.ts @@ -1,9 +1,11 @@ -import { Experimental, Field, JsonProof, MerkleTree, Poseidon } from "o1js"; +import { Experimental, Field, JsonProof, MerkleTree, Poseidon, PrivateKey, AccountUpdate, Mina } from "o1js"; import ProvePasswordInTreeProgram, { PASSWORD_TREE_HEIGHT, PasswordTreePublicInput, PasswordTreeWitness } from "./passwordTreeProgram"; import { IMinAuthPlugin, IMinAuthPluginFactory, IMinAuthProver, IMinAuthProverFactory } from 'plugin/pluginType'; import { RequestHandler } from "express"; import { z } from "zod"; import axios from "axios"; +import { TreeRootStorageContract } from "./treeRootStorageContract"; +import fs from 'fs/promises' const PasswordInTreeProofClass = Experimental.ZkProgram.Proof(ProvePasswordInTreeProgram); @@ -11,21 +13,13 @@ abstract class TreeStorage { abstract getRoot(): Promise; abstract getWitness(uid: bigint): Promise; abstract getRole(uid: bigint): Promise; + abstract updateUser(uid: bigint, passwordHash: Field, role: string): Promise; + abstract hasUser(uid: bigint): Promise; } class InMemoryStorage implements TreeStorage { - roles: Map; - merkleTree: MerkleTree; - - constructor(roleMappings: Array<[bigint, Field, string]> = []) { - this.roles = new Map(); - this.merkleTree = new MerkleTree(PASSWORD_TREE_HEIGHT); - - roleMappings.forEach(([uid, password, role]) => { - this.roles.set(uid, role); - this.merkleTree.setLeaf(uid, Poseidon.hash([password])); - }) - } + roles: Map = new Map();; + merkleTree: MerkleTree = new MerkleTree(PASSWORD_TREE_HEIGHT); async getRoot() { return this.merkleTree.getRoot(); } @@ -35,12 +29,161 @@ class InMemoryStorage implements TreeStorage { } async getRole(uid: bigint) { return this.roles.get(uid); } + + async updateUser(uid: bigint, passwordHash: Field, role: string): Promise { + this.roles.set(uid, role); + this.merkleTree.setLeaf(uid, passwordHash); + } + + async hasUser(uid: bigint): Promise { + return this.roles.has(uid); + } +} + +class PersistentInMemoryStorage extends InMemoryStorage { + readonly file: fs.FileHandle; + + async persist() { + const emptyObj: Record = {} + const storageObj = Array.from(this.roles.entries()) + .reduce((prev, [uid, role]) => { + const passwordHash = + this.merkleTree.getNode(PASSWORD_TREE_HEIGHT, uid).toString(); + prev[uid.toString()] = { passwordHash, role }; + return prev; + }, emptyObj); + await this.file.write(JSON.stringify(storageObj), 0, 'utf-8'); + } + + private constructor( + file: fs.FileHandle, + roles: Map, + merkleTree: MerkleTree) { + super(); + + this.file = file; + this.roles = roles; + this.merkleTree = merkleTree; + } + + static async initialize(path: string): Promise { + const handle = await fs.open(path, 'r+'); + const content = await handle.readFile('utf-8'); + const storageObj: Record = + JSON.parse(content); + + const roles: Map = new Map(); + const merkleTree: MerkleTree = new MerkleTree(PASSWORD_TREE_HEIGHT); + + Object + .entries(storageObj) + .forEach(( + [uidStr, { passwordHash: passwordHashStr, role }]) => { + const uid = BigInt(uidStr); + const passwordHash = Field.from(passwordHashStr); + roles.set(uid, role); + merkleTree.setLeaf(uid, passwordHash); + }); + + return new PersistentInMemoryStorage(handle, roles, merkleTree); + } + + async updateUser(uid: bigint, passwordHash: Field, role: string): Promise { + const prevRoot = this.merkleTree.getRoot(); + await super.updateUser(uid, passwordHash, role); + const root = this.merkleTree.getRoot(); + if (prevRoot.equals(root).toBoolean()) return; + await this.persist(); + } } -const storage = new InMemoryStorage([ - [BigInt(0), Field('7555220006856562833147743033256142154591945963958408607501861037584894828141'), 'admin'], - [BigInt(1), Field('21565680844461314807147611702860246336805372493508489110556896454939225549736'), 'member'] -]); +class GenericMinaBlockchainStorage implements TreeStorage { + private underlyingStorage: T; + private contract: TreeRootStorageContract + private mkTx: (txFn: () => void) => Promise + + constructor( + storage: T, + contract: TreeRootStorageContract, + mkTx: (txFn: () => void) => Promise) { + this.underlyingStorage = storage; + this.contract = contract; + this.mkTx = mkTx; + } + + async updateTreeRootOnChainIfNecessary() { + const onChain = await this.contract.treeRoot.fetch(); + const offChain = await this.underlyingStorage.getRoot(); + + if (!onChain) + throw "tree root storage contract not deployed"; + + if (onChain.equals(offChain).toBoolean()) + return; + + await this.mkTx(() => this.contract.treeRoot.set(offChain)); + } + + async getRoot() { return this.underlyingStorage.getRoot(); } + + async getWitness(uid: bigint) { return this.underlyingStorage.getWitness(uid); } + + async getRole(uid: bigint) { return this.underlyingStorage.getRole(uid); } + + async updateUser(uid: bigint, passwordHash: Field, role: string): Promise { + await this.underlyingStorage.updateUser(uid, passwordHash, role); + await this.updateTreeRootOnChainIfNecessary(); + } + + async hasUser(uid: bigint): Promise { + return this.underlyingStorage.hasUser(uid); + } +} + +async function initializeGenericMinaBlockchainStorage( + storage: T, + contractPrivateKey: PrivateKey, + feePayerPrivateKey: PrivateKey +): Promise> { + await TreeRootStorageContract.compile(); + const contract = new TreeRootStorageContract(contractPrivateKey.toPublicKey()); + const feePayerPublicKey = feePayerPrivateKey.toPublicKey(); + + const mkTx = async (txFn: () => void): Promise => { + const txn = await Mina.transaction(feePayerPublicKey, txFn); + await txn.prove(); + await txn.sign([feePayerPrivateKey, contractPrivateKey]).send(); + }; + + const blockchainStorage = new GenericMinaBlockchainStorage(storage, contract, mkTx); + + if (contract.account.isNew.get()) { + const treeRoot = await storage.getRoot(); + await mkTx(() => { + AccountUpdate.fundNewAccount(feePayerPublicKey); + contract.treeRoot.set(treeRoot); + contract.deploy(); + }); + } else { + await blockchainStorage.updateTreeRootOnChainIfNecessary(); + } + + return blockchainStorage; +} + +class MinaBlockchainStorage + extends GenericMinaBlockchainStorage{ + static async initialize( + path: string, + contractPrivateKey: PrivateKey, + feePayerPrivateKey: PrivateKey) { + const storage = await PersistentInMemoryStorage.initialize(path); + return initializeGenericMinaBlockchainStorage( + storage, + contractPrivateKey, + feePayerPrivateKey); + } +} export class SimplePasswordTreePlugin implements IMinAuthPlugin{ readonly verificationKey: string; @@ -54,7 +197,7 @@ export class SimplePasswordTreePlugin implements IMinAuthPlugin{ } const uid = BigInt(req.params['uid']); - const witness = await storage.getWitness(uid); + const witness = await this.storage.getWitness(uid); if (!witness) { resp @@ -71,8 +214,18 @@ export class SimplePasswordTreePlugin implements IMinAuthPlugin{ return; } - const root = await storage.getRoot(); + const root = await this.storage.getRoot(); return resp.status(200).json(root); + }, + "/setPassword/:uid": async (req, resp) => { + const uid = BigInt(req.params['uid']); + const { passwordHashStr }: { passwordHashStr: string } = req.body; + const passwordHash = Field.from(passwordHashStr); + if (!await this.storage.hasUser(uid)) + throw "user doesn't exist"; + const role = await this.storage.getRole(uid); + this.storage.updateUser(uid, passwordHash, role!); + resp.status(200); } }; @@ -81,46 +234,63 @@ export class SimplePasswordTreePlugin implements IMinAuthPlugin{ async verifyAndGetOutput(uid: bigint, jsonProof: JsonProof): Promise { const proof = PasswordInTreeProofClass.fromJSON(jsonProof); - const expectedWitness = await storage.getWitness(uid); - const expectedRoot = await storage.getRoot(); + const expectedWitness = await this.storage.getWitness(uid); + const expectedRoot = await this.storage.getRoot(); if (proof.publicInput.witness != expectedWitness || proof.publicInput.root != expectedRoot) { throw 'public input invalid'; } - const role = await storage.getRole(uid); + const role = await this.storage.getRole(uid); if (!role) { throw 'unknown public input'; } return role; }; - constructor(verificationKey: string, roles: Array<[bigint, Field, string]>) { + constructor(verificationKey: string, storage: MinaBlockchainStorage) { this.verificationKey = verificationKey; - this.storage = new InMemoryStorage(roles); + this.storage = storage; } - static async initialize(configuration: { roles: Array<[bigint, Field, string]> }) - : Promise { + static async initialize(configuration: { + storageFile: string, + contractPrivateKey: string, + feePayerPrivateKey: string + }): Promise { const { verificationKey } = await ProvePasswordInTreeProgram.compile(); - return new SimplePasswordTreePlugin(verificationKey, configuration.roles); + const storage = await MinaBlockchainStorage + .initialize( + configuration.storageFile, + PrivateKey.fromBase58(configuration.contractPrivateKey), + PrivateKey.fromBase58(configuration.feePayerPrivateKey) + ) + return new SimplePasswordTreePlugin(verificationKey, storage); } - static readonly configurationSchema: z.ZodType<{ roles: Array<[bigint, Field, string]> }> = + static readonly configurationSchema: + z.ZodType<{ + storageFile: string, + contractPrivateKey: string, + feePayerPrivateKey: string + }> = z.object({ - roles: z.array(z.tuple([ - z.bigint(), - z.custom((val) => typeof val === "string" ? /^[0-9]+$/.test(val) : false), - z.string()])) + storageFile: z.string(), + contractPrivateKey: z.string(), + feePayerPrivateKey: z.string() }) } SimplePasswordTreePlugin satisfies IMinAuthPluginFactory< IMinAuthPlugin, - { roles: Array<[bigint, Field, string]> }, + { + storageFile: string, + contractPrivateKey: string, + feePayerPrivateKey: string + }, bigint, string>; export type SimplePasswordTreeProverConfiguration = { - apiServer: URL + apiServer: URL, } export class SimplePasswordTreeProver implements @@ -131,7 +301,7 @@ export class SimplePasswordTreeProver implements async prove(publicInput: PasswordTreePublicInput, secretInput: Field) : Promise { const proof = await ProvePasswordInTreeProgram.baseCase( - publicInput, Field(secretInput)); + publicInput, Field.from(secretInput)); return proof.toJSON(); } diff --git a/src/plugins/passwordTree/plugin/treeRootStorageContract.ts b/src/plugins/passwordTree/plugin/treeRootStorageContract.ts new file mode 100644 index 0000000..a1a20ff --- /dev/null +++ b/src/plugins/passwordTree/plugin/treeRootStorageContract.ts @@ -0,0 +1,15 @@ +import { Permissions, DeployArgs, Experimental, Field, JsonProof, MerkleTree, Poseidon, PrivateKey, PublicKey, SmartContract, State, method, state, verify, Mina, AccountUpdate } from "o1js"; + +export class TreeRootStorageContract extends SmartContract { + @state(Field) treeRoot = State(); + + deploy(args?: DeployArgs) { + super.deploy(args); + + this.account.permissions.set({ + ...Permissions.allImpossible(), + editState: Permissions.signature(), + access: Permissions.signature(), + }); + } +} \ No newline at end of file From 33b3edb8af96052209534d6e78308954f97c3c7b Mon Sep 17 00:00:00 2001 From: adamczykm Date: Mon, 2 Oct 2023 18:36:43 +0200 Subject: [PATCH 02/21] Reorganize plugins structure. Fix imports. --- package.json | 2 +- src/library/tools/pluginServer/config.ts | 8 +-- src/library/tools/pluginServer/index.ts | 4 +- src/plugins/passwordTree/client/index.ts | 58 +++++++++++++++++ .../{plugin => common}/passwordTreeProgram.ts | 0 .../treeRootStorageContract.ts | 4 +- .../passwordTree/{plugin => server}/index.ts | 62 ++----------------- src/plugins/simplePreimage/client/index.ts | 24 +++++++ .../{plugin => common}/hashPreimageProof.ts | 0 .../{plugin => server}/index.ts | 26 +------- 10 files changed, 98 insertions(+), 90 deletions(-) create mode 100644 src/plugins/passwordTree/client/index.ts rename src/plugins/passwordTree/{plugin => common}/passwordTreeProgram.ts (100%) rename src/plugins/passwordTree/{plugin => common}/treeRootStorageContract.ts (63%) rename src/plugins/passwordTree/{plugin => server}/index.ts (81%) create mode 100644 src/plugins/simplePreimage/client/index.ts rename src/plugins/simplePreimage/{plugin => common}/hashPreimageProof.ts (100%) rename src/plugins/simplePreimage/{plugin => server}/index.ts (69%) diff --git a/package.json b/package.json index 0cf9a4b..836bb5e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start-api": "nodemon src/someServer/apiServer.ts", - "start-plugin-server": "nodemon src/library/tools/pluginServer.ts", + "start-plugin-server": "nodemon src/library/tools/pluginServer/index.ts", "start-client": "nodemon src/headlessClient/client.ts", "build": "tsc", "serve-api": "node ./dist/someServer/apiServer.js", diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index 5ac2cd4..7c5ad2b 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -1,10 +1,10 @@ -import { IMinAuthPlugin, IMinAuthPluginFactory } from "plugin/pluginType"; +import { IMinAuthPlugin, IMinAuthPluginFactory } from '../../../library/plugin/pluginType'; import z from "zod"; import env from 'env-var'; import fs from 'fs'; import yaml from 'yaml'; -import { SimplePreimagePlugin } from "plugins/simplePreimage/plugin"; -import { SimplePasswordTreePlugin } from "plugins/passwordTree/plugin"; +import { SimplePreimagePlugin } from "./plugins/simplePreimage/server"; +import { SimplePasswordTreePlugin } from "./plugins/passwordTree/server"; // TODO: make use of heterogeneous lists /** @@ -49,4 +49,4 @@ export function readConfigurations(): ServerConfigurations { const configFileContent = fs.readFileSync(configFile, 'utf8'); const untypedConfig: any = yaml.parse(configFileContent); return serverConfigurationsSchema.parse(untypedConfig); -} \ No newline at end of file +} diff --git a/src/library/tools/pluginServer/index.ts b/src/library/tools/pluginServer/index.ts index 0aaf773..afffd65 100644 --- a/src/library/tools/pluginServer/index.ts +++ b/src/library/tools/pluginServer/index.ts @@ -1,7 +1,7 @@ import express, { Request, Response } from 'express'; import bodyParser from 'body-parser'; import { JsonProof, verify } from 'o1js'; -import { IMinAuthPlugin } from 'plugin/pluginType'; +import { IMinAuthPlugin } from '../../../library/plugin/pluginType'; import { readConfigurations, untypedPlugins } from './config'; const configurations = readConfigurations(); @@ -82,4 +82,4 @@ initializePlugins() .catch((error) => { console.error('Error during server initialization:', error); process.exit(1); - }); \ No newline at end of file + }); diff --git a/src/plugins/passwordTree/client/index.ts b/src/plugins/passwordTree/client/index.ts new file mode 100644 index 0000000..de8dc4a --- /dev/null +++ b/src/plugins/passwordTree/client/index.ts @@ -0,0 +1,58 @@ +import { Field, JsonProof } from "o1js"; +import ProvePasswordInTreeProgram, { PasswordTreePublicInput, PasswordTreeWitness } from "../common/passwordTreeProgram"; +import { IMinAuthProver, IMinAuthProverFactory } from '../../../library/plugin/pluginType'; + +import axios from "axios"; + +export type SimplePasswordTreeProverConfiguration = { + apiServer: URL, +} + +export class SimplePasswordTreeProver implements + IMinAuthProver +{ + private readonly cfg: SimplePasswordTreeProverConfiguration; + + async prove(publicInput: PasswordTreePublicInput, secretInput: Field) + : Promise { + const proof = await ProvePasswordInTreeProgram.baseCase( + publicInput, Field.from(secretInput)); + return proof.toJSON(); + } + + async fetchPublicInputs(uid: bigint): Promise { + const mkUrl = (endpoint: string) => `${this.cfg.apiServer}/${endpoint}`; + const getWitness = async (): Promise => { + const resp = await axios.get(mkUrl(`/witness/${uid.toString()}`)); + if (resp.status != 200) { + throw `unable to fetch witness for ${uid.toString()}, error: ${(resp.data as { error: string }).error}`; + } + return PasswordTreeWitness.fromJSON(resp.data); + }; + const getRoot = async (): Promise => { + const resp = await axios.get(mkUrl('/root')); + return Field.fromJSON(resp.data); + } + const witness = await getWitness(); + const root = await getRoot(); + + return new PasswordTreePublicInput({ witness, root }); + } + + constructor(cfg: SimplePasswordTreeProverConfiguration) { + this.cfg = cfg; + } + + static async initialize(cfg: SimplePasswordTreeProverConfiguration): + Promise { + return new SimplePasswordTreeProver(cfg); + } +} + +SimplePasswordTreeProver satisfies IMinAuthProverFactory< + SimplePasswordTreeProver, + SimplePasswordTreeProverConfiguration, + bigint, + PasswordTreePublicInput, + Field +> diff --git a/src/plugins/passwordTree/plugin/passwordTreeProgram.ts b/src/plugins/passwordTree/common/passwordTreeProgram.ts similarity index 100% rename from src/plugins/passwordTree/plugin/passwordTreeProgram.ts rename to src/plugins/passwordTree/common/passwordTreeProgram.ts diff --git a/src/plugins/passwordTree/plugin/treeRootStorageContract.ts b/src/plugins/passwordTree/common/treeRootStorageContract.ts similarity index 63% rename from src/plugins/passwordTree/plugin/treeRootStorageContract.ts rename to src/plugins/passwordTree/common/treeRootStorageContract.ts index a1a20ff..d29699b 100644 --- a/src/plugins/passwordTree/plugin/treeRootStorageContract.ts +++ b/src/plugins/passwordTree/common/treeRootStorageContract.ts @@ -1,4 +1,4 @@ -import { Permissions, DeployArgs, Experimental, Field, JsonProof, MerkleTree, Poseidon, PrivateKey, PublicKey, SmartContract, State, method, state, verify, Mina, AccountUpdate } from "o1js"; +import { Permissions, DeployArgs, Field, SmartContract, State, state} from "o1js"; export class TreeRootStorageContract extends SmartContract { @state(Field) treeRoot = State(); @@ -12,4 +12,4 @@ export class TreeRootStorageContract extends SmartContract { access: Permissions.signature(), }); } -} \ No newline at end of file +} diff --git a/src/plugins/passwordTree/plugin/index.ts b/src/plugins/passwordTree/server/index.ts similarity index 81% rename from src/plugins/passwordTree/plugin/index.ts rename to src/plugins/passwordTree/server/index.ts index 6a47e16..e8415c4 100644 --- a/src/plugins/passwordTree/plugin/index.ts +++ b/src/plugins/passwordTree/server/index.ts @@ -1,10 +1,9 @@ -import { Experimental, Field, JsonProof, MerkleTree, Poseidon, PrivateKey, AccountUpdate, Mina } from "o1js"; -import ProvePasswordInTreeProgram, { PASSWORD_TREE_HEIGHT, PasswordTreePublicInput, PasswordTreeWitness } from "./passwordTreeProgram"; -import { IMinAuthPlugin, IMinAuthPluginFactory, IMinAuthProver, IMinAuthProverFactory } from 'plugin/pluginType'; +import { Experimental, Field, JsonProof, MerkleTree, PrivateKey, AccountUpdate, Mina } from "o1js"; +import ProvePasswordInTreeProgram, { PASSWORD_TREE_HEIGHT, PasswordTreeWitness } from "../common/passwordTreeProgram"; +import { IMinAuthPlugin, IMinAuthPluginFactory } from '../../../library/plugin/pluginType'; import { RequestHandler } from "express"; import { z } from "zod"; -import axios from "axios"; -import { TreeRootStorageContract } from "./treeRootStorageContract"; +import { TreeRootStorageContract } from "../common/treeRootStorageContract"; import fs from 'fs/promises' const PasswordInTreeProofClass = Experimental.ZkProgram.Proof(ProvePasswordInTreeProgram); @@ -288,56 +287,3 @@ SimplePasswordTreePlugin satisfies }, bigint, string>; - -export type SimplePasswordTreeProverConfiguration = { - apiServer: URL, -} - -export class SimplePasswordTreeProver implements - IMinAuthProver -{ - private readonly cfg: SimplePasswordTreeProverConfiguration; - - async prove(publicInput: PasswordTreePublicInput, secretInput: Field) - : Promise { - const proof = await ProvePasswordInTreeProgram.baseCase( - publicInput, Field.from(secretInput)); - return proof.toJSON(); - } - - async fetchPublicInputs(uid: bigint): Promise { - const mkUrl = (endpoint: string) => `${this.cfg.apiServer}/${endpoint}`; - const getWitness = async (): Promise => { - const resp = await axios.get(mkUrl(`/witness/${uid.toString()}`)); - if (resp.status != 200) { - throw `unable to fetch witness for ${uid.toString()}, error: ${(resp.data as { error: string }).error}`; - } - return PasswordTreeWitness.fromJSON(resp.data); - }; - const getRoot = async (): Promise => { - const resp = await axios.get(mkUrl('/root')); - return Field.fromJSON(resp.data); - } - const witness = await getWitness(); - const root = await getRoot(); - - return new PasswordTreePublicInput({ witness, root }); - } - - constructor(cfg: SimplePasswordTreeProverConfiguration) { - this.cfg = cfg; - } - - static async initialize(cfg: SimplePasswordTreeProverConfiguration): - Promise { - return new SimplePasswordTreeProver(cfg); - } -} - -SimplePasswordTreeProver satisfies IMinAuthProverFactory< - SimplePasswordTreeProver, - SimplePasswordTreeProverConfiguration, - bigint, - PasswordTreePublicInput, - Field -> diff --git a/src/plugins/simplePreimage/client/index.ts b/src/plugins/simplePreimage/client/index.ts new file mode 100644 index 0000000..5186a65 --- /dev/null +++ b/src/plugins/simplePreimage/client/index.ts @@ -0,0 +1,24 @@ +import { Field, JsonProof } from 'o1js'; +import { IMinAuthProver } from '../../../library/plugin/pluginType'; +import ProvePreimageProgram from '../common/hashPreimageProof'; + + +export class SimplePreimageProver implements IMinAuthProver{ + async prove(publicInput: Field, secretInput: Field): Promise { + console.log('simplePreimage proving for', publicInput, secretInput); + const proof = await ProvePreimageProgram.baseCase( + Field(publicInput), + Field(secretInput), + ); + return proof.toJSON(); + } + + async fetchPublicInputs(_: any): Promise { + throw "not implemented, please query the `/roles` endpoint"; + } + + static async initialize(_: any): Promise { + return new SimplePreimageProver(); + } +} + diff --git a/src/plugins/simplePreimage/plugin/hashPreimageProof.ts b/src/plugins/simplePreimage/common/hashPreimageProof.ts similarity index 100% rename from src/plugins/simplePreimage/plugin/hashPreimageProof.ts rename to src/plugins/simplePreimage/common/hashPreimageProof.ts diff --git a/src/plugins/simplePreimage/plugin/index.ts b/src/plugins/simplePreimage/server/index.ts similarity index 69% rename from src/plugins/simplePreimage/plugin/index.ts rename to src/plugins/simplePreimage/server/index.ts index b9b3b51..71f90ba 100644 --- a/src/plugins/simplePreimage/plugin/index.ts +++ b/src/plugins/simplePreimage/server/index.ts @@ -1,6 +1,6 @@ -import { verify, Proof, Field, JsonProof, Experimental } from 'o1js'; -import { IMinAuthPlugin, IMinAuthPluginFactory, IMinAuthProver } from '../../../library/plugin/pluginType'; -import ProvePreimageProgram, { ProvePreimageProofClass } from './hashPreimageProof'; +import { JsonProof } from 'o1js'; +import { IMinAuthPlugin, IMinAuthPluginFactory } from '../../../library/plugin/pluginType'; +import ProvePreimageProgram, { ProvePreimageProofClass } from '../common/hashPreimageProof'; import { RequestHandler } from 'express'; import { z } from 'zod'; @@ -57,23 +57,3 @@ export class SimplePreimagePlugin implements IMinAuthPlugin{ // sanity check SimplePreimagePlugin satisfies IMinAuthPluginFactory }, any, string>; - -export class SimplePreimageProver implements IMinAuthProver{ - async prove(publicInput: Field, secretInput: Field): Promise { - console.log('simplePreimage proving for', publicInput, secretInput); - const proof = await ProvePreimageProgram.baseCase( - Field(publicInput), - Field(secretInput), - ); - return proof.toJSON(); - } - - async fetchPublicInputs(_: any): Promise { - throw "not implemented, please query the `/roles` endpoint"; - } - - static async initialize(_: any): Promise { - return new SimplePreimageProver(); - } -} - From 230514e7741cc0d26dd8379fbaaef98b96e6a790 Mon Sep 17 00:00:00 2001 From: adamczykm Date: Mon, 2 Oct 2023 21:35:54 +0200 Subject: [PATCH 03/21] Some renaming + changes to the password tree program --- src/library/plugin/pluginType.ts | 22 ++--- src/library/tools/pluginServer/config.ts | 4 +- src/plugins/passwordTree/client/index.ts | 91 ++++++++++--------- .../common/passwordTreeProgram.ts | 68 ++++++++++---- src/plugins/passwordTree/server/index.ts | 61 ++++++++----- 5 files changed, 144 insertions(+), 102 deletions(-) diff --git a/src/library/plugin/pluginType.ts b/src/library/plugin/pluginType.ts index e5e8c55..46b1c8b 100644 --- a/src/library/plugin/pluginType.ts +++ b/src/library/plugin/pluginType.ts @@ -4,20 +4,20 @@ import z from 'zod'; // Interfaces used on the server side. -export interface IMinAuthPlugin { +export interface IMinAuthPlugin { // Verify a proof give the arguments for fetching public inputs, and return // the output. verifyAndGetOutput( - publicInputArgs: PublicInputsArgs, + publicInputArgs: PublicInputArgs, serializedProof: JsonProof): Promise; // The schema of the arguments for fetching public inputs. - readonly publicInputArgsSchema: z.ZodType; + readonly publicInputArgsSchema: z.ZodType; // TODO: enable plugins to invalidate a proof. // FIXME(Connor): I still have some questions regarding the validation functionality. // In particular, what if a plugin want to invalidate the proof once the public inputs change? - // We have to at least pass PublicInputsArgs. + // We have to at least pass PublicInputArgs. // // checkOutputValidity(output: Output): Promise; @@ -30,8 +30,8 @@ export interface IMinAuthPlugin { // TODO: generic type inference? export interface IMinAuthPluginFactory< - T extends IMinAuthPlugin, - Configuration, PublicInputsArgs, Output> { + T extends IMinAuthPlugin, + Configuration, PublicInputArgs, Output> { // Initialize the plugin given the configuration. The underlying zk program is // typically compiled here. @@ -42,20 +42,20 @@ export interface IMinAuthPluginFactory< // Interfaces used on the client side. -export interface IMinAuthProver { +export interface IMinAuthProver { prove(publicInput: PublicInput, secretInput: PrivateInput): Promise; - fetchPublicInputs(args: PublicInputsArgs): Promise; + fetchPublicInputs(args: PublicInputArgs): Promise; } export interface IMinAuthProverFactory< T extends IMinAuthProver< - PublicInputsArgs, + PublicInputArgs, PublicInput, PrivateInput>, Configuration, - PublicInputsArgs, + PublicInputArgs, PublicInput, PrivateInput> { initialize(cfg: Configuration): Promise; -} \ No newline at end of file +} diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index 7c5ad2b..4b76d72 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -4,7 +4,7 @@ import env from 'env-var'; import fs from 'fs'; import yaml from 'yaml'; import { SimplePreimagePlugin } from "./plugins/simplePreimage/server"; -import { SimplePasswordTreePlugin } from "./plugins/passwordTree/server"; +import { MemberSetPlugin } from "./plugins/passwordTree/server"; // TODO: make use of heterogeneous lists /** @@ -15,7 +15,7 @@ export const untypedPlugins: IMinAuthPluginFactory, any, any, any>> = { "SimplePreimagePlugin": SimplePreimagePlugin, - "SimplePasswordTreePlugin": SimplePasswordTreePlugin + "MemberSetPlugin": MemberSetPlugin }; const serverConfigurationsSchema = z.object({ diff --git a/src/plugins/passwordTree/client/index.ts b/src/plugins/passwordTree/client/index.ts index de8dc4a..d477b20 100644 --- a/src/plugins/passwordTree/client/index.ts +++ b/src/plugins/passwordTree/client/index.ts @@ -1,58 +1,61 @@ +// TODO requires changes import { Field, JsonProof } from "o1js"; -import ProvePasswordInTreeProgram, { PasswordTreePublicInput, PasswordTreeWitness } from "../common/passwordTreeProgram"; +import ProvePasswordInTreeProgram, { PasswordInTreeWitness } from "../common/passwordTreeProgram"; import { IMinAuthProver, IMinAuthProverFactory } from '../../../library/plugin/pluginType'; import axios from "axios"; -export type SimplePasswordTreeProverConfiguration = { - apiServer: URL, +export type MemberSetProverConfiguration = { + apiServer: URL, } -export class SimplePasswordTreeProver implements - IMinAuthProver + +// Prove that you belong to a set of user without revealing which user you are. +export class MemberSetProver implements + IMinAuthProver { - private readonly cfg: SimplePasswordTreeProverConfiguration; - - async prove(publicInput: PasswordTreePublicInput, secretInput: Field) - : Promise { - const proof = await ProvePasswordInTreeProgram.baseCase( - publicInput, Field.from(secretInput)); - return proof.toJSON(); - } - - async fetchPublicInputs(uid: bigint): Promise { - const mkUrl = (endpoint: string) => `${this.cfg.apiServer}/${endpoint}`; - const getWitness = async (): Promise => { - const resp = await axios.get(mkUrl(`/witness/${uid.toString()}`)); - if (resp.status != 200) { - throw `unable to fetch witness for ${uid.toString()}, error: ${(resp.data as { error: string }).error}`; - } - return PasswordTreeWitness.fromJSON(resp.data); - }; - const getRoot = async (): Promise => { - const resp = await axios.get(mkUrl('/root')); - return Field.fromJSON(resp.data); + private readonly cfg: MemberSetProverConfiguration; + + async prove(publicInput: PasswordInTreeWitness, secretInput: Field) + : Promise { + const proof = await ProvePasswordInTreeProgram.baseCase( + publicInput, Field.from(secretInput)); + return proof.toJSON(); } - const witness = await getWitness(); - const root = await getRoot(); - return new PasswordTreePublicInput({ witness, root }); - } + async fetchPublicInputs(uid: bigint): Promise { + const mkUrl = (endpoint: string) => `${this.cfg.apiServer}/${endpoint}`; + const getWitness = async (): Promise => { + const resp = await axios.get(mkUrl(`/witness/${uid.toString()}`)); + if (resp.status != 200) { + throw `unable to fetch witness for ${uid.toString()}, error: ${(resp.data as { error: string }).error}`; + } + return PasswordInTreeWitness.fromJSON(resp.data); + }; + const getRoot = async (): Promise => { + const resp = await axios.get(mkUrl('/root')); + return Field.fromJSON(resp.data); + } + const witness = await getWitness(); + const root = await getRoot(); + + return new PasswordInTreeWitness({ witness, root }); + } - constructor(cfg: SimplePasswordTreeProverConfiguration) { - this.cfg = cfg; - } + constructor(cfg: MemberSetProverConfiguration) { + this.cfg = cfg; + } - static async initialize(cfg: SimplePasswordTreeProverConfiguration): - Promise { - return new SimplePasswordTreeProver(cfg); - } + static async initialize(cfg: MemberSetProverConfiguration): + Promise { + return new MemberSetProver(cfg); + } } -SimplePasswordTreeProver satisfies IMinAuthProverFactory< - SimplePasswordTreeProver, - SimplePasswordTreeProverConfiguration, - bigint, - PasswordTreePublicInput, - Field -> +MemberSetProver satisfies IMinAuthProverFactory< + MemberSetProver, + MemberSetProverConfiguration, + bigint, + PasswordInTreeWitness, + Field + > diff --git a/src/plugins/passwordTree/common/passwordTreeProgram.ts b/src/plugins/passwordTree/common/passwordTreeProgram.ts index d06e4c6..8128994 100644 --- a/src/plugins/passwordTree/common/passwordTreeProgram.ts +++ b/src/plugins/passwordTree/common/passwordTreeProgram.ts @@ -1,29 +1,57 @@ -import { Experimental, Field, MerkleWitness, Poseidon, Struct } from "o1js"; +import { Experimental, Field, MerkleWitness, Poseidon, SelfProof, Struct } from "o1js"; +// TODO how can this be made dynamic export const PASSWORD_TREE_HEIGHT = 10; -export class PasswordTreeWitness extends MerkleWitness(PASSWORD_TREE_HEIGHT) { } +export class PasswordTreeWitness extends MerkleWitness(PASSWORD_TREE_HEIGHT) {} -export class PasswordTreePublicInput extends Struct({ - witness: PasswordTreeWitness, - root: Field -}) { }; +export class PasswordInTreeWitness extends Struct({ + witness: PasswordTreeWitness, + preImage: Field +}) {}; +export class MerkleRoot extends Struct({ + root: Field +}) {}; + + +export class ProvePasswordInTreeOutput extends Struct({ + recursiveMekleRootHash: Field, +}) {}; + +// Prove knowledge of a preimage of a hash in a merkle tree. +// The proof does not reveal the preimage nor the hash. +// The output contains a recursive hash of all the roots for which the preimage is known. +// output = hash(lastRoot + hash(secondLastRoot, ... hash(xLastRoot, lastRoot) ...) +// Therefore the order of the proofs matters. export const ProvePasswordInTreeProgram = Experimental.ZkProgram({ - publicInput: PasswordTreePublicInput, - publicOutput: Field, - - methods: { - baseCase: { - privateInputs: [Field], - method(publicInput: PasswordTreePublicInput, privateInput: Field): Field { - publicInput.witness - .calculateRoot(Poseidon.hash([privateInput])) - .assertEquals(publicInput.root); - return publicInput.witness.calculateIndex(); - } + publicInput: MerkleRoot, + publicOutput: ProvePasswordInTreeOutput, + + methods: { + baseCase: { + privateInputs: [PasswordInTreeWitness], + method(publicInput: MerkleRoot, privateInput: PasswordInTreeWitness): ProvePasswordInTreeOutput { + privateInput.witness + .calculateRoot(Poseidon.hash([publicInput.root])) + .assertEquals(publicInput.root); + return new ProvePasswordInTreeOutput( + { recursiveMekleRootHash: publicInput.root }); + } + }, + + inductiveCase: { + privateInputs: [SelfProof, PasswordInTreeWitness], + method(publicInput: MerkleRoot, earlierProof: SelfProof, privateInput: PasswordInTreeWitness): ProvePasswordInTreeOutput { + earlierProof.verify(); + privateInput.witness + .calculateRoot(Poseidon.hash([publicInput.root])) + .assertEquals(publicInput.root); + return new ProvePasswordInTreeOutput( + { recursiveMekleRootHash: Poseidon.hash([publicInput.root, earlierProof.publicOutput.recursiveMekleRootHash]) }); + } + } } - } }); -export default ProvePasswordInTreeProgram; \ No newline at end of file +export default ProvePasswordInTreeProgram; diff --git a/src/plugins/passwordTree/server/index.ts b/src/plugins/passwordTree/server/index.ts index e8415c4..841dd87 100644 --- a/src/plugins/passwordTree/server/index.ts +++ b/src/plugins/passwordTree/server/index.ts @@ -184,30 +184,39 @@ class MinaBlockchainStorage } } -export class SimplePasswordTreePlugin implements IMinAuthPlugin{ +const PoseidonHashSchema = z.bigint(); + +const publicInputArgsSchema = z.array(PoseidonHashSchema); + +export class MemberSetPlugin implements IMinAuthPlugin, string>{ readonly verificationKey: string; private readonly storage: TreeStorage customRoutes: Record = { - "/witness/:uid": async (req, resp) => { - if (req.method != 'GET') { - resp.status(400); - return; - } - - const uid = BigInt(req.params['uid']); - const witness = await this.storage.getWitness(uid); - - if (!witness) { - resp - .status(400) - .json({ error: "requested user doesn't exist" }); - return; - } - - resp.status(200).json(witness); - }, - "/root": async (req, resp) => { + // NOTE: witnesses are not public inputs now + // "/witness/:uid": async (req, resp) => { + // if (req.method != 'GET') { + // resp.status(400); + // return; + // } + + // const uid = BigInt(req.params['uid']); + // const witness = await this.storage.getWitness(uid); + + // if (!witness) { + // resp + // .status(400) + // .json({ error: "requested user doesn't exist" }); + // return; + // } + + // resp.status(200).json(witness); + // }, + + // TODO: + // input: array of merkle roots (eg. [root1, root2, root3]) + // output: object of the form { root1: tree1, root2: tree2, root3: tree3 } + "/roots": async (req, resp) => { if (req.method != 'GET') { resp.status(400); return; @@ -228,10 +237,12 @@ export class SimplePasswordTreePlugin implements IMinAuthPlugin{ } }; - publicInputArgsSchema: z.ZodType = z.bigint(); + publicInputArgsSchema = publicInputArgsSchema; - async verifyAndGetOutput(uid: bigint, jsonProof: JsonProof): + async verifyAndGetOutput(uid: z.infer, jsonProof: JsonProof): Promise { + + // build an array of merkle trees const proof = PasswordInTreeProofClass.fromJSON(jsonProof); const expectedWitness = await this.storage.getWitness(uid); const expectedRoot = await this.storage.getRoot(); @@ -253,7 +264,7 @@ export class SimplePasswordTreePlugin implements IMinAuthPlugin{ storageFile: string, contractPrivateKey: string, feePayerPrivateKey: string - }): Promise { + }): Promise { const { verificationKey } = await ProvePasswordInTreeProgram.compile(); const storage = await MinaBlockchainStorage .initialize( @@ -261,7 +272,7 @@ export class SimplePasswordTreePlugin implements IMinAuthPlugin{ PrivateKey.fromBase58(configuration.contractPrivateKey), PrivateKey.fromBase58(configuration.feePayerPrivateKey) ) - return new SimplePasswordTreePlugin(verificationKey, storage); + return new MemberSetPlugin(verificationKey, storage); } static readonly configurationSchema: @@ -277,7 +288,7 @@ export class SimplePasswordTreePlugin implements IMinAuthPlugin{ }) } -SimplePasswordTreePlugin satisfies +MemberSetPlugin satisfies IMinAuthPluginFactory< IMinAuthPlugin, { From a9982ca87fdd1c10a6293ef648f1efecf17058ce Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Tue, 3 Oct 2023 22:35:04 +0800 Subject: [PATCH 04/21] clean up and fix everything --- package-lock.json | 6 + package.json | 1 + src/library/tools/pluginServer/config.ts | 6 +- src/plugins/merkleMemberships/client/index.ts | 113 +++++++ .../common/merkleMembershipsProgram.ts | 71 +++++ .../common/treeRootStorageContract.ts | 0 src/plugins/merkleMemberships/server/index.ts | 183 +++++++++++ .../merkleMemberships/server/treeStorage.ts | 230 ++++++++++++++ src/plugins/passwordTree/client/index.ts | 61 ---- .../common/passwordTreeProgram.ts | 57 ---- src/plugins/passwordTree/server/index.ts | 300 ------------------ 11 files changed, 607 insertions(+), 421 deletions(-) create mode 100644 src/plugins/merkleMemberships/client/index.ts create mode 100644 src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts rename src/plugins/{passwordTree => merkleMemberships}/common/treeRootStorageContract.ts (100%) create mode 100644 src/plugins/merkleMemberships/server/index.ts create mode 100644 src/plugins/merkleMemberships/server/treeStorage.ts delete mode 100644 src/plugins/passwordTree/client/index.ts delete mode 100644 src/plugins/passwordTree/common/passwordTreeProgram.ts delete mode 100644 src/plugins/passwordTree/server/index.ts diff --git a/package-lock.json b/package-lock.json index 5ae4f73..8799c89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "crypto": "^1.0.1", "env-var": "^7.4.1", "express": "^4.18.2", + "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.2", "passport": "^0.6.0", "passport-jwt": "^4.0.1", @@ -1855,6 +1856,11 @@ "node": ">= 0.6" } }, + "node_modules/fp-ts": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.1.tgz", + "integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA==" + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", diff --git a/package.json b/package.json index 836bb5e..313c2bf 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "crypto": "^1.0.1", "env-var": "^7.4.1", "express": "^4.18.2", + "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.2", "passport": "^0.6.0", "passport-jwt": "^4.0.1", diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index 4b76d72..0988f99 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -3,8 +3,8 @@ import z from "zod"; import env from 'env-var'; import fs from 'fs'; import yaml from 'yaml'; -import { SimplePreimagePlugin } from "./plugins/simplePreimage/server"; -import { MemberSetPlugin } from "./plugins/passwordTree/server"; +import { SimplePreimagePlugin } from "plugins/simplePreimage/server"; +import { MerkleMembershipsPlugin } from "plugins/merkleMemberships/server"; // TODO: make use of heterogeneous lists /** @@ -15,7 +15,7 @@ export const untypedPlugins: IMinAuthPluginFactory, any, any, any>> = { "SimplePreimagePlugin": SimplePreimagePlugin, - "MemberSetPlugin": MemberSetPlugin + "MerkleMembershipsPlugin": MerkleMembershipsPlugin }; const serverConfigurationsSchema = z.object({ diff --git a/src/plugins/merkleMemberships/client/index.ts b/src/plugins/merkleMemberships/client/index.ts new file mode 100644 index 0000000..5abba0d --- /dev/null +++ b/src/plugins/merkleMemberships/client/index.ts @@ -0,0 +1,113 @@ +import { Field, JsonProof, SelfProof } from "o1js"; +import { + MerkleMembershipTreeWitness, + MerkleMembershipsOutput, + MerkleMembershipsPrivateInputs, + MerkleMembershipsProgram, + MerkleRoot +} from "../common/merkleMembershipsProgram"; +import { IMinAuthProver, IMinAuthProverFactory } from '../../../library/plugin/pluginType'; +import A from 'fp-ts/Array' +import O from 'fp-ts/Option' +import axios from "axios"; + +export type MembershipsProverConfiguration = { + baseUrl: string +} + +export type MembershipsPublicInputArgs = + Array<{ + treeIndex: bigint, + leafIndex: bigint + }> + +// Prove that you belong to a set of user without revealing which user you are. +export class MembershipsProver implements + IMinAuthProver< + MembershipsPublicInputArgs, // TODO how to fetch + Array<[MerkleRoot, MerkleMembershipTreeWitness]>, + Array> +{ + private readonly cfg: MembershipsProverConfiguration; + + async prove( + publicInput: Array<[MerkleRoot, MerkleMembershipTreeWitness]>, + secretInput: Array) + : Promise { + if (publicInput.length != secretInput.length) + throw "unmatched public/secret input list" + + const proof: O.Option> = + await + A.reduce + ( + Promise.resolve>>(O.none), + (acc, [[root, witness], secret]: [[MerkleRoot, MerkleMembershipTreeWitness], Field]) => { + const privInput = new MerkleMembershipsPrivateInputs({ witness, secret }) + return acc.then( + O.match( + () => MerkleMembershipsProgram.baseCase(root, privInput).then(O.some), + (prev) => MerkleMembershipsProgram.inductiveCase(root, prev, privInput).then(O.some) + ) + ) + } + ) + (A.zip(publicInput, secretInput)); + + return O.match( + () => { throw "empty input list" }, // TODO: make it pure + (p: SelfProof) => p.toJSON() + )(proof); + } + + async fetchPublicInputs(args: MembershipsPublicInputArgs): Promise> { + const mkUrl = (treeIndex: bigint, leafIndex: bigint) => + `${this.cfg.baseUrl}/getRootAndWitness/${treeIndex.toString()}/${leafIndex.toString()}`; + const getRootAndWitness = + async (treeIndex: bigint, leafIndex: bigint): + Promise<[MerkleRoot, MerkleMembershipTreeWitness]> => { + const url = + `${this.cfg.baseUrl}/getRootAndWitness/${treeIndex.toString()}/${leafIndex.toString()}`; + const resp = await axios.get(url); + if (resp.status == 200) { + const body: { + root: string, + witness: string + } = resp.data; + const root = Field.fromJSON(body.root); + const witness = MerkleMembershipTreeWitness.fromJSON(body.witness); + return [new MerkleRoot({ root }), witness]; + } else { + const body: { error: string } = resp.data; + throw `error while getting root and witness: ${body.error}`; + } + } + + return Promise.all(A.map( + (args: { + treeIndex: bigint, + leafIndex: bigint + }): Promise<[MerkleRoot, MerkleMembershipTreeWitness]> => + getRootAndWitness(args.treeIndex, args.leafIndex) + )(args)); + } + + constructor(cfg: MembershipsProverConfiguration) { + this.cfg = cfg; + } + + static async initialize(cfg: MembershipsProverConfiguration): + Promise { + return new MembershipsProver(cfg); + } +} + +MembershipsProver satisfies IMinAuthProverFactory< + MembershipsProver, + MembershipsProverConfiguration, + MembershipsPublicInputArgs, + Array<[MerkleRoot, MerkleMembershipTreeWitness]>, + Array +> + +export default MembershipsProver; \ No newline at end of file diff --git a/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts b/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts new file mode 100644 index 0000000..5ef381c --- /dev/null +++ b/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts @@ -0,0 +1,71 @@ +import { Experimental, Field, MerkleWitness, Poseidon, SelfProof, Struct } from "o1js"; + +// TODO how can this be made dynamic +export const MERKLE_MEMBERSHIP_TREE_HEIGHT = 10; + +export class MerkleMembershipTreeWitness extends + MerkleWitness(MERKLE_MEMBERSHIP_TREE_HEIGHT) +{ } + +export class MerkleMembershipsPrivateInputs extends Struct({ + witness: MerkleMembershipTreeWitness, + secret: Field +}) { } + +export class MerkleRoot extends Struct({ + root: Field +}) { } + +export class MerkleMembershipsOutput extends Struct({ + recursiveHash: Field, +}) { }; + +// Prove knowledge of a preimage of a hash in a merkle tree. +// The proof does not reveal the preimage nor the hash. +// The output contains a recursive hash of all the roots for which the preimage is known. +// output = hash(lastRoot + hash(secondLastRoot, ... hash(xLastRoot, lastRoot) ...) +// Therefore the order of the proofs matters. +export const MerkleMembershipsProgram = Experimental.ZkProgram({ + publicInput: MerkleRoot, + publicOutput: MerkleMembershipsOutput, + + methods: { + baseCase: { + privateInputs: [MerkleMembershipsPrivateInputs], + method(publicInput: MerkleRoot, + privateInput: MerkleMembershipsPrivateInputs) + : MerkleMembershipsOutput { + privateInput.witness + .calculateRoot(Poseidon.hash([privateInput.secret])) + .assertEquals(publicInput.root); + return new MerkleMembershipsOutput({ + recursiveHash: publicInput.root + }); + } + }, + + inductiveCase: { + privateInputs: [SelfProof, MerkleMembershipsPrivateInputs], + method( + publicInput: MerkleRoot, + earlierProof: SelfProof, + privateInput: MerkleMembershipsPrivateInputs + ): MerkleMembershipsOutput { + earlierProof.verify(); + privateInput.witness + .calculateRoot(Poseidon.hash([privateInput.secret])) + .assertEquals(publicInput.root); + return new MerkleMembershipsOutput( + { + recursiveHash: + Poseidon.hash([ + publicInput.root, + earlierProof.publicOutput.recursiveHash + ]) + }); + } + } + } +}); + +export default MerkleMembershipsProgram; diff --git a/src/plugins/passwordTree/common/treeRootStorageContract.ts b/src/plugins/merkleMemberships/common/treeRootStorageContract.ts similarity index 100% rename from src/plugins/passwordTree/common/treeRootStorageContract.ts rename to src/plugins/merkleMemberships/common/treeRootStorageContract.ts diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts new file mode 100644 index 0000000..2c11a52 --- /dev/null +++ b/src/plugins/merkleMemberships/server/index.ts @@ -0,0 +1,183 @@ +import { Experimental, Field, JsonProof, Poseidon } from "o1js"; +import O from 'fp-ts/Option'; +import MerkleMembershipsProgram from "../common/merkleMembershipsProgram"; +import z from 'zod'; +import { IMinAuthPlugin, IMinAuthPluginFactory } from "library/plugin/pluginType"; +import { MinaTreesProvider, MinaTreesProviderConfiguration, TreesProvider, minaTreesProviderConfigurationSchema } from "./treeStorage"; +import { RequestHandler } from "express"; +import A from 'fp-ts/Array'; + +const PoseidonHashSchema = z.bigint(); + +const publicInputArgsSchema = z.array(PoseidonHashSchema); + +export type MerkleMembershipsPublicInputArgs = + z.infer; + +export class MerkleMembershipsPlugin + implements IMinAuthPlugin{ + readonly verificationKey: string; + private readonly storageProvider: TreesProvider + + customRoutes: Record = { + "/getRootAndWitness/:treeIndex/:leafIndex": async (req, resp) => { + if (req.method != 'GET') { + resp.status(400); + return; + } + + const treeIndex = Number(req.params['treeIndex']); + const leafIndex = Number(req.params['leafIndex']); + + const trees = await this.storageProvider.getTrees() + + + if (treeIndex >= trees.length) { + resp.status(400).json({ + error: "tree not exists" + }); + return; + } + + const tree = trees[treeIndex]; + + const root = await tree.getRoot(); + const witness = O.toUndefined(await tree.getWitness(BigInt(leafIndex))); + + + if (witness == undefined) { + resp.status(400).json({ + error: "leaf not exists" + }); + return; + } + + resp.status(200).json({ + root: root.toJSON(), + witness: witness.toJSON(), + }); + + } + } + + readonly publicInputArgsSchema = publicInputArgsSchema; + + async verifyAndGetOutput( + publicInputArgs: MerkleMembershipsPublicInputArgs, + serializedProof: JsonProof): Promise { + const proof = + Experimental.ZkProgram + .Proof(MerkleMembershipsProgram) + .fromJSON(serializedProof); + + const trees = await this.storageProvider.getTrees(); + + const expectedHash = O.toUndefined(await + A.reduce( + Promise.resolve>(O.none), + (accP: Promise>, idx: bigint) => + accP.then((acc: O.Option) => + trees[Number(idx)] + .getRoot() + .then( + (root) => + Promise.resolve( + O.some( + O.match( + () => root, + (last: Field) => Poseidon.hash([root, last]) + )(acc))) + ) + ) + )(publicInputArgs)); + + if (expectedHash === undefined || + expectedHash.equals(proof.publicOutput.recursiveHash).not().toBoolean()) + throw "unexpected recursive hash"; + + return expectedHash; + } + + constructor(verificationKey: string, storageProvider: TreesProvider) { + this.verificationKey = verificationKey; + this.storageProvider = storageProvider; + } + + static async initialize(cfg: MinaTreesProviderConfiguration): Promise { + const { verificationKey } = await MerkleMembershipsProgram.compile(); + const storage = await MinaTreesProvider.initialize(cfg); + return new MerkleMembershipsPlugin(verificationKey, storage); + } + + static readonly configurationSchema = minaTreesProviderConfigurationSchema; +} + +// customRoutes: Record = { +// // NOTE: witnesses are not public inputs now +// // "/witness/:uid": async (req, resp) => { +// // if (req.method != 'GET') { +// // resp.status(400); +// // return; +// // } + +// // const uid = BigInt(req.params['uid']); +// // const witness = await this.storage.getWitness(uid); + +// // if (!witness) { +// // resp +// // .status(400) +// // .json({ error: "requested user doesn't exist" }); +// // return; +// // } + +// // resp.status(200).json(witness); +// // }, + +// // TODO: +// // input: array of merkle roots (eg. [root1, root2, root3]) +// // output: object of the form { root1: tree1, root2: tree2, root3: tree3 } +// "/roots": async (req, resp) => { +// if (req.method != 'GET') { +// resp.status(400); +// return; +// } + +// const root = await this.storage.getRoot(); +// return resp.status(200).json(root); +// }, +// "/setPassword/:uid": async (req, resp) => { +// const uid = BigInt(req.params['uid']); +// const { passwordHashStr }: { passwordHashStr: string } = req.body; +// const passwordHash = Field.from(passwordHashStr); +// if (!await this.storage.hasUser(uid)) +// throw "user doesn't exist"; +// const role = await this.storage.getRole(uid); +// this.storage.updateUser(uid, passwordHash, role!); +// resp.status(200); +// } +// }; + +// publicInputArgsSchema = publicInputArgsSchema; + +// async verifyAndGetOutput(uid: z.infer, jsonProof: JsonProof): +// Promise { + +// // build an array of merkle trees +// const proof = PasswordInTreeProofClass.fromJSON(jsonProof); +// const expectedWitness = await this.storage.getWitness(uid); +// const expectedRoot = await this.storage.getRoot(); +// if (proof.publicInput.witness != expectedWitness || +// proof.publicInput.root != expectedRoot) { +// throw 'public input invalid'; +// } +// const role = await this.storage.getRole(uid); +// if (!role) { throw 'unknown public input'; } +// return role; +// }; + +MerkleMembershipsPlugin satisfies + IMinAuthPluginFactory< + IMinAuthPlugin, + MinaTreesProviderConfiguration, + MerkleMembershipsPublicInputArgs, + Field>; diff --git a/src/plugins/merkleMemberships/server/treeStorage.ts b/src/plugins/merkleMemberships/server/treeStorage.ts new file mode 100644 index 0000000..a7c35d6 --- /dev/null +++ b/src/plugins/merkleMemberships/server/treeStorage.ts @@ -0,0 +1,230 @@ +import { AccountUpdate, Field, MerkleTree, Mina, PrivateKey } from "o1js"; +import O from 'fp-ts/Option'; +import { MERKLE_MEMBERSHIP_TREE_HEIGHT, MerkleMembershipTreeWitness } from "../common/merkleMembershipsProgram"; +import z from 'zod'; +import fs from "fs/promises" +import { TreeRootStorageContract } from "../common/treeRootStorageContract"; +import A from 'fp-ts/Array'; + +export interface TreeStorage { + getRoot(): Promise; + getWitness(leafIndex: bigint): Promise>; + hasLeaf(leafIndex: bigint): Promise; + setLeaf(leafIndex: bigint, leaf: Field): Promise; +} + +export class InMemoryStorage implements TreeStorage { + occupied: Set = new Set(); + merkleTree: MerkleTree = new MerkleTree(MERKLE_MEMBERSHIP_TREE_HEIGHT); + + async getRoot() { return this.merkleTree.getRoot(); } + + async getWitness(leafIndex: bigint): Promise> { + return this.occupied.has(leafIndex) ? + O.none : + O.some(new MerkleMembershipTreeWitness(this.merkleTree.getWitness(leafIndex))); + } + + async hasLeaf(leafIndex: bigint): Promise { + return this.occupied.has(leafIndex); + } + + async setLeaf(leafIndex: bigint, leaf: Field): Promise { + this.occupied.add(leafIndex); + this.merkleTree.setLeaf(leafIndex, leaf); + } +} + +export class PersistentInMemoryStorage extends InMemoryStorage { + readonly file: fs.FileHandle; + + async persist() { + const storageObj = + Array + .from(this.occupied.values()) + .reduce((acc: Record, idx: bigint) => { + acc[Number(idx)] = + this.merkleTree + .getNode(MERKLE_MEMBERSHIP_TREE_HEIGHT, idx) + .toJSON(); + return acc; + } + , {}); + await this.file.write(JSON.stringify(storageObj), 0, 'utf-8'); + } + + private constructor( + file: fs.FileHandle, + occupied: Set, + merkleTree: MerkleTree) { + super(); + this.file = file; + this.occupied = occupied; + this.merkleTree = merkleTree; + } + + static async initialize(path: string): Promise { + const handle = await fs.open(path, 'r+'); + const content = await handle.readFile('utf-8'); + const storageObj: Record = + JSON.parse(content); + const occupied: Set = new Set(); + const merkleTree: MerkleTree = new MerkleTree(MERKLE_MEMBERSHIP_TREE_HEIGHT); + + Object + .entries(storageObj) + .forEach(([rawIdx, rawLeaf]) => { + const idx = BigInt(rawIdx); + occupied.add(BigInt(rawIdx)); + merkleTree.setLeaf(idx, Field.fromJSON(rawLeaf)); + }); + + return new PersistentInMemoryStorage(handle, occupied, merkleTree); + } +} + + +export class GenericMinaBlockchainTreeStorage implements TreeStorage { + private underlyingStorage: T; + private contract: TreeRootStorageContract + private mkTx: (txFn: () => void) => Promise + + constructor( + storage: T, + contract: TreeRootStorageContract, + mkTx: (txFn: () => void) => Promise) { + this.underlyingStorage = storage; + this.contract = contract; + this.mkTx = mkTx; + } + + async updateTreeRootOnChainIfNecessary() { + const onChain = await this.contract.treeRoot.fetch(); + const offChain = await this.underlyingStorage.getRoot(); + + if (!onChain) + throw "tree root storage contract not deployed"; + + if (onChain.equals(offChain).toBoolean()) + return; + + await this.mkTx(() => this.contract.treeRoot.set(offChain)); + } + + async getRoot() { return this.underlyingStorage.getRoot(); } + + async getWitness(leafIdx: bigint) { + return this.underlyingStorage.getWitness(leafIdx); + } + + async hasLeaf(leafIdx: bigint): Promise { + return this.underlyingStorage.hasLeaf(leafIdx); + } + + async setLeaf(leafIndex: bigint, leaf: Field): Promise { + this.underlyingStorage.setLeaf(leafIndex, leaf); + await this.updateTreeRootOnChainIfNecessary(); + } +} + +async function initializeGenericMinaBlockchainTreeStorage( + storage: T, + contractPrivateKey: PrivateKey, + feePayerPrivateKey: PrivateKey +): Promise> { + await TreeRootStorageContract.compile(); + const contract = new TreeRootStorageContract(contractPrivateKey.toPublicKey()); + const feePayerPublicKey = feePayerPrivateKey.toPublicKey(); + + const mkTx = async (txFn: () => void): Promise => { + const txn = await Mina.transaction(feePayerPublicKey, txFn); + await txn.prove(); + await txn.sign([feePayerPrivateKey, contractPrivateKey]).send(); + }; + + const blockchainStorage = new GenericMinaBlockchainTreeStorage(storage, contract, mkTx); + + if (contract.account.isNew.get()) { + const treeRoot = await storage.getRoot(); + await mkTx(() => { + AccountUpdate.fundNewAccount(feePayerPublicKey); + contract.treeRoot.set(treeRoot); + contract.deploy(); + }); + } else { + await blockchainStorage.updateTreeRootOnChainIfNecessary(); + } + + return blockchainStorage; +} + +export class MinaBlockchainTreeStorage + extends GenericMinaBlockchainTreeStorage{ + static async initialize( + path: string, + contractPrivateKey: PrivateKey, + feePayerPrivateKey: PrivateKey) { + const storage = await PersistentInMemoryStorage.initialize(path); + return initializeGenericMinaBlockchainTreeStorage( + storage, + contractPrivateKey, + feePayerPrivateKey); + } +} + +export interface TreesProvider { + getTrees(): Promise>; +} + +export const minaTreesProviderConfigurationSchema = + z.object({ + feePayerPrivateKey: z.string().optional(), + trees: z.array(z.object({ + contractPrivateKey: z.string().optional(), + offchainStoragePath: z.string() + })) + }); + + +export type MinaTreesProviderConfiguration = + z.infer; + +export class MinaTreesProvider implements TreesProvider { + treeStorages: Array; + + async getTrees() { return this.treeStorages; } + + constructor(treeStorages: Array) { + this.treeStorages = treeStorages; + } + + static async initialize(cfg: MinaTreesProviderConfiguration): + Promise { + const feePayerPrivateKey = cfg.feePayerPrivateKey ? + PrivateKey.fromBase58(cfg.feePayerPrivateKey) : undefined; + + const trees: TreeStorage[] = await + A.reduce + (Promise.resolve([]), + ( + accP: Promise>, + tCfg: { + offchainStoragePath: string; + contractPrivateKey?: string | undefined; + }) => accP.then( + (acc) => + ( + feePayerPrivateKey && tCfg.contractPrivateKey ? + MinaBlockchainTreeStorage.initialize( + tCfg.offchainStoragePath, + PrivateKey.fromBase58(tCfg.contractPrivateKey), + feePayerPrivateKey) : + PersistentInMemoryStorage.initialize(tCfg.offchainStoragePath) + ).then((storage: TreeStorage) => + Promise.resolve(A.append(storage)(acc))) + )) + (cfg.trees) + + return new MinaTreesProvider(trees); + }; +} diff --git a/src/plugins/passwordTree/client/index.ts b/src/plugins/passwordTree/client/index.ts deleted file mode 100644 index d477b20..0000000 --- a/src/plugins/passwordTree/client/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -// TODO requires changes -import { Field, JsonProof } from "o1js"; -import ProvePasswordInTreeProgram, { PasswordInTreeWitness } from "../common/passwordTreeProgram"; -import { IMinAuthProver, IMinAuthProverFactory } from '../../../library/plugin/pluginType'; - -import axios from "axios"; - -export type MemberSetProverConfiguration = { - apiServer: URL, -} - - -// Prove that you belong to a set of user without revealing which user you are. -export class MemberSetProver implements - IMinAuthProver -{ - private readonly cfg: MemberSetProverConfiguration; - - async prove(publicInput: PasswordInTreeWitness, secretInput: Field) - : Promise { - const proof = await ProvePasswordInTreeProgram.baseCase( - publicInput, Field.from(secretInput)); - return proof.toJSON(); - } - - async fetchPublicInputs(uid: bigint): Promise { - const mkUrl = (endpoint: string) => `${this.cfg.apiServer}/${endpoint}`; - const getWitness = async (): Promise => { - const resp = await axios.get(mkUrl(`/witness/${uid.toString()}`)); - if (resp.status != 200) { - throw `unable to fetch witness for ${uid.toString()}, error: ${(resp.data as { error: string }).error}`; - } - return PasswordInTreeWitness.fromJSON(resp.data); - }; - const getRoot = async (): Promise => { - const resp = await axios.get(mkUrl('/root')); - return Field.fromJSON(resp.data); - } - const witness = await getWitness(); - const root = await getRoot(); - - return new PasswordInTreeWitness({ witness, root }); - } - - constructor(cfg: MemberSetProverConfiguration) { - this.cfg = cfg; - } - - static async initialize(cfg: MemberSetProverConfiguration): - Promise { - return new MemberSetProver(cfg); - } -} - -MemberSetProver satisfies IMinAuthProverFactory< - MemberSetProver, - MemberSetProverConfiguration, - bigint, - PasswordInTreeWitness, - Field - > diff --git a/src/plugins/passwordTree/common/passwordTreeProgram.ts b/src/plugins/passwordTree/common/passwordTreeProgram.ts deleted file mode 100644 index 8128994..0000000 --- a/src/plugins/passwordTree/common/passwordTreeProgram.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Experimental, Field, MerkleWitness, Poseidon, SelfProof, Struct } from "o1js"; - -// TODO how can this be made dynamic -export const PASSWORD_TREE_HEIGHT = 10; - -export class PasswordTreeWitness extends MerkleWitness(PASSWORD_TREE_HEIGHT) {} - -export class PasswordInTreeWitness extends Struct({ - witness: PasswordTreeWitness, - preImage: Field -}) {}; - -export class MerkleRoot extends Struct({ - root: Field -}) {}; - - -export class ProvePasswordInTreeOutput extends Struct({ - recursiveMekleRootHash: Field, -}) {}; - -// Prove knowledge of a preimage of a hash in a merkle tree. -// The proof does not reveal the preimage nor the hash. -// The output contains a recursive hash of all the roots for which the preimage is known. -// output = hash(lastRoot + hash(secondLastRoot, ... hash(xLastRoot, lastRoot) ...) -// Therefore the order of the proofs matters. -export const ProvePasswordInTreeProgram = Experimental.ZkProgram({ - publicInput: MerkleRoot, - publicOutput: ProvePasswordInTreeOutput, - - methods: { - baseCase: { - privateInputs: [PasswordInTreeWitness], - method(publicInput: MerkleRoot, privateInput: PasswordInTreeWitness): ProvePasswordInTreeOutput { - privateInput.witness - .calculateRoot(Poseidon.hash([publicInput.root])) - .assertEquals(publicInput.root); - return new ProvePasswordInTreeOutput( - { recursiveMekleRootHash: publicInput.root }); - } - }, - - inductiveCase: { - privateInputs: [SelfProof, PasswordInTreeWitness], - method(publicInput: MerkleRoot, earlierProof: SelfProof, privateInput: PasswordInTreeWitness): ProvePasswordInTreeOutput { - earlierProof.verify(); - privateInput.witness - .calculateRoot(Poseidon.hash([publicInput.root])) - .assertEquals(publicInput.root); - return new ProvePasswordInTreeOutput( - { recursiveMekleRootHash: Poseidon.hash([publicInput.root, earlierProof.publicOutput.recursiveMekleRootHash]) }); - } - } - } -}); - -export default ProvePasswordInTreeProgram; diff --git a/src/plugins/passwordTree/server/index.ts b/src/plugins/passwordTree/server/index.ts deleted file mode 100644 index 841dd87..0000000 --- a/src/plugins/passwordTree/server/index.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { Experimental, Field, JsonProof, MerkleTree, PrivateKey, AccountUpdate, Mina } from "o1js"; -import ProvePasswordInTreeProgram, { PASSWORD_TREE_HEIGHT, PasswordTreeWitness } from "../common/passwordTreeProgram"; -import { IMinAuthPlugin, IMinAuthPluginFactory } from '../../../library/plugin/pluginType'; -import { RequestHandler } from "express"; -import { z } from "zod"; -import { TreeRootStorageContract } from "../common/treeRootStorageContract"; -import fs from 'fs/promises' - -const PasswordInTreeProofClass = Experimental.ZkProgram.Proof(ProvePasswordInTreeProgram); - -abstract class TreeStorage { - abstract getRoot(): Promise; - abstract getWitness(uid: bigint): Promise; - abstract getRole(uid: bigint): Promise; - abstract updateUser(uid: bigint, passwordHash: Field, role: string): Promise; - abstract hasUser(uid: bigint): Promise; -} - -class InMemoryStorage implements TreeStorage { - roles: Map = new Map();; - merkleTree: MerkleTree = new MerkleTree(PASSWORD_TREE_HEIGHT); - - async getRoot() { return this.merkleTree.getRoot(); } - - async getWitness(uid: bigint) { - if (!this.roles.has(uid)) return undefined; - return new PasswordTreeWitness(this.merkleTree.getWitness(uid)) - } - - async getRole(uid: bigint) { return this.roles.get(uid); } - - async updateUser(uid: bigint, passwordHash: Field, role: string): Promise { - this.roles.set(uid, role); - this.merkleTree.setLeaf(uid, passwordHash); - } - - async hasUser(uid: bigint): Promise { - return this.roles.has(uid); - } -} - -class PersistentInMemoryStorage extends InMemoryStorage { - readonly file: fs.FileHandle; - - async persist() { - const emptyObj: Record = {} - const storageObj = Array.from(this.roles.entries()) - .reduce((prev, [uid, role]) => { - const passwordHash = - this.merkleTree.getNode(PASSWORD_TREE_HEIGHT, uid).toString(); - prev[uid.toString()] = { passwordHash, role }; - return prev; - }, emptyObj); - await this.file.write(JSON.stringify(storageObj), 0, 'utf-8'); - } - - private constructor( - file: fs.FileHandle, - roles: Map, - merkleTree: MerkleTree) { - super(); - - this.file = file; - this.roles = roles; - this.merkleTree = merkleTree; - } - - static async initialize(path: string): Promise { - const handle = await fs.open(path, 'r+'); - const content = await handle.readFile('utf-8'); - const storageObj: Record = - JSON.parse(content); - - const roles: Map = new Map(); - const merkleTree: MerkleTree = new MerkleTree(PASSWORD_TREE_HEIGHT); - - Object - .entries(storageObj) - .forEach(( - [uidStr, { passwordHash: passwordHashStr, role }]) => { - const uid = BigInt(uidStr); - const passwordHash = Field.from(passwordHashStr); - roles.set(uid, role); - merkleTree.setLeaf(uid, passwordHash); - }); - - return new PersistentInMemoryStorage(handle, roles, merkleTree); - } - - async updateUser(uid: bigint, passwordHash: Field, role: string): Promise { - const prevRoot = this.merkleTree.getRoot(); - await super.updateUser(uid, passwordHash, role); - const root = this.merkleTree.getRoot(); - if (prevRoot.equals(root).toBoolean()) return; - await this.persist(); - } -} - -class GenericMinaBlockchainStorage implements TreeStorage { - private underlyingStorage: T; - private contract: TreeRootStorageContract - private mkTx: (txFn: () => void) => Promise - - constructor( - storage: T, - contract: TreeRootStorageContract, - mkTx: (txFn: () => void) => Promise) { - this.underlyingStorage = storage; - this.contract = contract; - this.mkTx = mkTx; - } - - async updateTreeRootOnChainIfNecessary() { - const onChain = await this.contract.treeRoot.fetch(); - const offChain = await this.underlyingStorage.getRoot(); - - if (!onChain) - throw "tree root storage contract not deployed"; - - if (onChain.equals(offChain).toBoolean()) - return; - - await this.mkTx(() => this.contract.treeRoot.set(offChain)); - } - - async getRoot() { return this.underlyingStorage.getRoot(); } - - async getWitness(uid: bigint) { return this.underlyingStorage.getWitness(uid); } - - async getRole(uid: bigint) { return this.underlyingStorage.getRole(uid); } - - async updateUser(uid: bigint, passwordHash: Field, role: string): Promise { - await this.underlyingStorage.updateUser(uid, passwordHash, role); - await this.updateTreeRootOnChainIfNecessary(); - } - - async hasUser(uid: bigint): Promise { - return this.underlyingStorage.hasUser(uid); - } -} - -async function initializeGenericMinaBlockchainStorage( - storage: T, - contractPrivateKey: PrivateKey, - feePayerPrivateKey: PrivateKey -): Promise> { - await TreeRootStorageContract.compile(); - const contract = new TreeRootStorageContract(contractPrivateKey.toPublicKey()); - const feePayerPublicKey = feePayerPrivateKey.toPublicKey(); - - const mkTx = async (txFn: () => void): Promise => { - const txn = await Mina.transaction(feePayerPublicKey, txFn); - await txn.prove(); - await txn.sign([feePayerPrivateKey, contractPrivateKey]).send(); - }; - - const blockchainStorage = new GenericMinaBlockchainStorage(storage, contract, mkTx); - - if (contract.account.isNew.get()) { - const treeRoot = await storage.getRoot(); - await mkTx(() => { - AccountUpdate.fundNewAccount(feePayerPublicKey); - contract.treeRoot.set(treeRoot); - contract.deploy(); - }); - } else { - await blockchainStorage.updateTreeRootOnChainIfNecessary(); - } - - return blockchainStorage; -} - -class MinaBlockchainStorage - extends GenericMinaBlockchainStorage{ - static async initialize( - path: string, - contractPrivateKey: PrivateKey, - feePayerPrivateKey: PrivateKey) { - const storage = await PersistentInMemoryStorage.initialize(path); - return initializeGenericMinaBlockchainStorage( - storage, - contractPrivateKey, - feePayerPrivateKey); - } -} - -const PoseidonHashSchema = z.bigint(); - -const publicInputArgsSchema = z.array(PoseidonHashSchema); - -export class MemberSetPlugin implements IMinAuthPlugin, string>{ - readonly verificationKey: string; - private readonly storage: TreeStorage - - customRoutes: Record = { - // NOTE: witnesses are not public inputs now - // "/witness/:uid": async (req, resp) => { - // if (req.method != 'GET') { - // resp.status(400); - // return; - // } - - // const uid = BigInt(req.params['uid']); - // const witness = await this.storage.getWitness(uid); - - // if (!witness) { - // resp - // .status(400) - // .json({ error: "requested user doesn't exist" }); - // return; - // } - - // resp.status(200).json(witness); - // }, - - // TODO: - // input: array of merkle roots (eg. [root1, root2, root3]) - // output: object of the form { root1: tree1, root2: tree2, root3: tree3 } - "/roots": async (req, resp) => { - if (req.method != 'GET') { - resp.status(400); - return; - } - - const root = await this.storage.getRoot(); - return resp.status(200).json(root); - }, - "/setPassword/:uid": async (req, resp) => { - const uid = BigInt(req.params['uid']); - const { passwordHashStr }: { passwordHashStr: string } = req.body; - const passwordHash = Field.from(passwordHashStr); - if (!await this.storage.hasUser(uid)) - throw "user doesn't exist"; - const role = await this.storage.getRole(uid); - this.storage.updateUser(uid, passwordHash, role!); - resp.status(200); - } - }; - - publicInputArgsSchema = publicInputArgsSchema; - - async verifyAndGetOutput(uid: z.infer, jsonProof: JsonProof): - Promise { - - // build an array of merkle trees - const proof = PasswordInTreeProofClass.fromJSON(jsonProof); - const expectedWitness = await this.storage.getWitness(uid); - const expectedRoot = await this.storage.getRoot(); - if (proof.publicInput.witness != expectedWitness || - proof.publicInput.root != expectedRoot) { - throw 'public input invalid'; - } - const role = await this.storage.getRole(uid); - if (!role) { throw 'unknown public input'; } - return role; - }; - - constructor(verificationKey: string, storage: MinaBlockchainStorage) { - this.verificationKey = verificationKey; - this.storage = storage; - } - - static async initialize(configuration: { - storageFile: string, - contractPrivateKey: string, - feePayerPrivateKey: string - }): Promise { - const { verificationKey } = await ProvePasswordInTreeProgram.compile(); - const storage = await MinaBlockchainStorage - .initialize( - configuration.storageFile, - PrivateKey.fromBase58(configuration.contractPrivateKey), - PrivateKey.fromBase58(configuration.feePayerPrivateKey) - ) - return new MemberSetPlugin(verificationKey, storage); - } - - static readonly configurationSchema: - z.ZodType<{ - storageFile: string, - contractPrivateKey: string, - feePayerPrivateKey: string - }> = - z.object({ - storageFile: z.string(), - contractPrivateKey: z.string(), - feePayerPrivateKey: z.string() - }) -} - -MemberSetPlugin satisfies - IMinAuthPluginFactory< - IMinAuthPlugin, - { - storageFile: string, - contractPrivateKey: string, - feePayerPrivateKey: string - }, - bigint, - string>; From 71d1d1d851553af53c8c6471db084c99ef807976 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Thu, 5 Oct 2023 01:02:48 +0800 Subject: [PATCH 05/21] default configuration for the plugin server fix port number --- src/library/tools/pluginServer/config.ts | 17 ++++++++++++++++- src/library/tools/pluginServer/index.ts | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index 0988f99..55e125d 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -21,7 +21,7 @@ export const untypedPlugins: const serverConfigurationsSchema = z.object({ server: z.object({ address: z.string().ip().default("127.0.0.1"), - port: z.bigint().default(BigInt(3001)), + port: z.number().default(3001), }), plugins: z.object (Object @@ -34,6 +34,17 @@ const serverConfigurationsSchema = z.object({ export type ServerConfigurations = z.infer; +const defaultConfiguration: ServerConfigurations = { + server: { + address: "127.0.0.1", + port: 3001 + }, + plugins: { + SimplePreimagePlugin: {}, + MerkleMembershipsPlugin: {} + } +} + /** * Load configurations from disk. The configuration is encoded in yaml and * should conform to `serverConfigurationsSchema`. The location of the file can @@ -46,6 +57,10 @@ export function readConfigurations(): ServerConfigurations { env.get('MINAUTH_CONFIG') .default("config.yaml") .asString(); + if (!fs.existsSync(configFile)) { + console.warn("configuration file not exists, use the default configuration") + return defaultConfiguration; + } const configFileContent = fs.readFileSync(configFile, 'utf8'); const untypedConfig: any = yaml.parse(configFileContent); return serverConfigurationsSchema.parse(untypedConfig); diff --git a/src/library/tools/pluginServer/index.ts b/src/library/tools/pluginServer/index.ts index afffd65..66d2eec 100644 --- a/src/library/tools/pluginServer/index.ts +++ b/src/library/tools/pluginServer/index.ts @@ -6,6 +6,8 @@ import { readConfigurations, untypedPlugins } from './config'; const configurations = readConfigurations(); +console.log("configuration loaded", configurations) + /** * Construct plugins which are enabled in the configuration. * @returns A record of plugin instances. From fc744da73980f0942e576723c0bd58c309cb4a67 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Thu, 5 Oct 2023 01:16:50 +0800 Subject: [PATCH 06/21] fix module resolution --- src/library/tools/pluginServer/config.ts | 5 +++-- src/plugins/merkleMemberships/server/index.ts | 2 +- tsconfig.json | 4 ---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index 55e125d..68a0cfe 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -3,8 +3,8 @@ import z from "zod"; import env from 'env-var'; import fs from 'fs'; import yaml from 'yaml'; -import { SimplePreimagePlugin } from "plugins/simplePreimage/server"; -import { MerkleMembershipsPlugin } from "plugins/merkleMemberships/server"; +import { SimplePreimagePlugin } from "../../../plugins/simplePreimage/server"; +import { MerkleMembershipsPlugin } from "../../../plugins/merkleMemberships/server"; // TODO: make use of heterogeneous lists /** @@ -57,6 +57,7 @@ export function readConfigurations(): ServerConfigurations { env.get('MINAUTH_CONFIG') .default("config.yaml") .asString(); + if (!fs.existsSync(configFile)) { console.warn("configuration file not exists, use the default configuration") return defaultConfiguration; diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 2c11a52..758dd51 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -2,7 +2,7 @@ import { Experimental, Field, JsonProof, Poseidon } from "o1js"; import O from 'fp-ts/Option'; import MerkleMembershipsProgram from "../common/merkleMembershipsProgram"; import z from 'zod'; -import { IMinAuthPlugin, IMinAuthPluginFactory } from "library/plugin/pluginType"; +import { IMinAuthPlugin, IMinAuthPluginFactory } from "../../../library/plugin/pluginType"; import { MinaTreesProvider, MinaTreesProviderConfiguration, TreesProvider, minaTreesProviderConfigurationSchema } from "./treeStorage"; import { RequestHandler } from "express"; import A from 'fp-ts/Array'; diff --git a/tsconfig.json b/tsconfig.json index ebbd463..137276f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,10 +18,6 @@ "sourceMap": true, "noFallthroughCasesInSwitch": true, "allowSyntheticDefaultImports": true, - "baseUrl": ".", - "paths": { - "*": [ "src/*", "src/library/*" ] - } }, "include": ["./src"], } From fe6c6a4e787acaa7bf78f65c70b97427cb61263a Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Thu, 5 Oct 2023 01:25:05 +0800 Subject: [PATCH 07/21] remove comments --- src/plugins/merkleMemberships/server/index.ts | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 758dd51..9dcb53a 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -112,69 +112,6 @@ export class MerkleMembershipsPlugin static readonly configurationSchema = minaTreesProviderConfigurationSchema; } -// customRoutes: Record = { -// // NOTE: witnesses are not public inputs now -// // "/witness/:uid": async (req, resp) => { -// // if (req.method != 'GET') { -// // resp.status(400); -// // return; -// // } - -// // const uid = BigInt(req.params['uid']); -// // const witness = await this.storage.getWitness(uid); - -// // if (!witness) { -// // resp -// // .status(400) -// // .json({ error: "requested user doesn't exist" }); -// // return; -// // } - -// // resp.status(200).json(witness); -// // }, - -// // TODO: -// // input: array of merkle roots (eg. [root1, root2, root3]) -// // output: object of the form { root1: tree1, root2: tree2, root3: tree3 } -// "/roots": async (req, resp) => { -// if (req.method != 'GET') { -// resp.status(400); -// return; -// } - -// const root = await this.storage.getRoot(); -// return resp.status(200).json(root); -// }, -// "/setPassword/:uid": async (req, resp) => { -// const uid = BigInt(req.params['uid']); -// const { passwordHashStr }: { passwordHashStr: string } = req.body; -// const passwordHash = Field.from(passwordHashStr); -// if (!await this.storage.hasUser(uid)) -// throw "user doesn't exist"; -// const role = await this.storage.getRole(uid); -// this.storage.updateUser(uid, passwordHash, role!); -// resp.status(200); -// } -// }; - -// publicInputArgsSchema = publicInputArgsSchema; - -// async verifyAndGetOutput(uid: z.infer, jsonProof: JsonProof): -// Promise { - -// // build an array of merkle trees -// const proof = PasswordInTreeProofClass.fromJSON(jsonProof); -// const expectedWitness = await this.storage.getWitness(uid); -// const expectedRoot = await this.storage.getRoot(); -// if (proof.publicInput.witness != expectedWitness || -// proof.publicInput.root != expectedRoot) { -// throw 'public input invalid'; -// } -// const role = await this.storage.getRole(uid); -// if (!role) { throw 'unknown public input'; } -// return role; -// }; - MerkleMembershipsPlugin satisfies IMinAuthPluginFactory< IMinAuthPlugin, From 4fdf63bbad586027b4dc23cd8b81bd7f37141e75 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Thu, 5 Oct 2023 01:35:50 +0800 Subject: [PATCH 08/21] validate configuration for plugins --- src/library/tools/pluginServer/config.ts | 6 +++--- src/library/tools/pluginServer/index.ts | 3 ++- src/plugins/merkleMemberships/server/index.ts | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index 68a0cfe..46be7e7 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -40,8 +40,8 @@ const defaultConfiguration: ServerConfigurations = { port: 3001 }, plugins: { - SimplePreimagePlugin: {}, - MerkleMembershipsPlugin: {} + SimplePreimagePlugin: { roles: {} }, + MerkleMembershipsPlugin: { trees: [] } } } @@ -57,7 +57,7 @@ export function readConfigurations(): ServerConfigurations { env.get('MINAUTH_CONFIG') .default("config.yaml") .asString(); - + if (!fs.existsSync(configFile)) { console.warn("configuration file not exists, use the default configuration") return defaultConfiguration; diff --git a/src/library/tools/pluginServer/index.ts b/src/library/tools/pluginServer/index.ts index 66d2eec..a20818e 100644 --- a/src/library/tools/pluginServer/index.ts +++ b/src/library/tools/pluginServer/index.ts @@ -19,7 +19,8 @@ async function initializePlugins(): .entries(configurations.plugins) .reduce(async (o, [name, cfg]) => { const factory = untypedPlugins[name]; - const plugin = await factory.initialize(cfg); + const typedCfg = factory.configurationSchema.parse(cfg) + const plugin = await factory.initialize(typedCfg); return { ...o, [name]: plugin }; }, {}); } diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 9dcb53a..169655f 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -104,6 +104,7 @@ export class MerkleMembershipsPlugin } static async initialize(cfg: MinaTreesProviderConfiguration): Promise { + console.log(cfg) const { verificationKey } = await MerkleMembershipsProgram.compile(); const storage = await MinaTreesProvider.initialize(cfg); return new MerkleMembershipsPlugin(verificationKey, storage); From 89b17c0055685c6852805e685ee969cd2815b053 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Thu, 5 Oct 2023 01:40:44 +0800 Subject: [PATCH 09/21] better logging --- src/library/tools/pluginServer/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/library/tools/pluginServer/index.ts b/src/library/tools/pluginServer/index.ts index a20818e..6bfe79c 100644 --- a/src/library/tools/pluginServer/index.ts +++ b/src/library/tools/pluginServer/index.ts @@ -14,10 +14,11 @@ console.log("configuration loaded", configurations) */ async function initializePlugins(): Promise>> { - console.log('compiling plugins'); + console.log('initializing plugins'); return Object .entries(configurations.plugins) .reduce(async (o, [name, cfg]) => { + console.debug(`initializing ${name}`, cfg); const factory = untypedPlugins[name]; const typedCfg = factory.configurationSchema.parse(cfg) const plugin = await factory.initialize(typedCfg); From 1b171fc0871d016d44804b1f2d8d13ffa38189ad Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Thu, 5 Oct 2023 23:49:49 +0800 Subject: [PATCH 10/21] fix imports from fp-ts --- src/plugins/merkleMemberships/client/index.ts | 4 ++-- src/plugins/merkleMemberships/server/index.ts | 5 ++--- src/plugins/merkleMemberships/server/treeStorage.ts | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/plugins/merkleMemberships/client/index.ts b/src/plugins/merkleMemberships/client/index.ts index 5abba0d..8c15f0f 100644 --- a/src/plugins/merkleMemberships/client/index.ts +++ b/src/plugins/merkleMemberships/client/index.ts @@ -7,8 +7,8 @@ import { MerkleRoot } from "../common/merkleMembershipsProgram"; import { IMinAuthProver, IMinAuthProverFactory } from '../../../library/plugin/pluginType'; -import A from 'fp-ts/Array' -import O from 'fp-ts/Option' +import * as A from 'fp-ts/Array' +import * as O from 'fp-ts/Option' import axios from "axios"; export type MembershipsProverConfiguration = { diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 169655f..5e4593b 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -1,11 +1,11 @@ import { Experimental, Field, JsonProof, Poseidon } from "o1js"; -import O from 'fp-ts/Option'; +import * as O from 'fp-ts/Option'; import MerkleMembershipsProgram from "../common/merkleMembershipsProgram"; import z from 'zod'; import { IMinAuthPlugin, IMinAuthPluginFactory } from "../../../library/plugin/pluginType"; import { MinaTreesProvider, MinaTreesProviderConfiguration, TreesProvider, minaTreesProviderConfigurationSchema } from "./treeStorage"; import { RequestHandler } from "express"; -import A from 'fp-ts/Array'; +import * as A from 'fp-ts/Array'; const PoseidonHashSchema = z.bigint(); @@ -104,7 +104,6 @@ export class MerkleMembershipsPlugin } static async initialize(cfg: MinaTreesProviderConfiguration): Promise { - console.log(cfg) const { verificationKey } = await MerkleMembershipsProgram.compile(); const storage = await MinaTreesProvider.initialize(cfg); return new MerkleMembershipsPlugin(verificationKey, storage); diff --git a/src/plugins/merkleMemberships/server/treeStorage.ts b/src/plugins/merkleMemberships/server/treeStorage.ts index a7c35d6..08e11fc 100644 --- a/src/plugins/merkleMemberships/server/treeStorage.ts +++ b/src/plugins/merkleMemberships/server/treeStorage.ts @@ -1,10 +1,10 @@ import { AccountUpdate, Field, MerkleTree, Mina, PrivateKey } from "o1js"; -import O from 'fp-ts/Option'; +import * as O from 'fp-ts/Option'; import { MERKLE_MEMBERSHIP_TREE_HEIGHT, MerkleMembershipTreeWitness } from "../common/merkleMembershipsProgram"; import z from 'zod'; import fs from "fs/promises" import { TreeRootStorageContract } from "../common/treeRootStorageContract"; -import A from 'fp-ts/Array'; +import * as A from 'fp-ts/Array'; export interface TreeStorage { getRoot(): Promise; From f8f9f914899ed3591dad9eb53d33c19b348b76a9 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Mon, 9 Oct 2023 18:50:14 +0800 Subject: [PATCH 11/21] drop prefixes --- src/plugins/merkleMemberships/client/index.ts | 179 +++++++++--------- .../common/merkleMembershipsProgram.ts | 54 +++--- src/plugins/merkleMemberships/server/index.ts | 30 +-- .../merkleMemberships/server/treeStorage.ts | 14 +- 4 files changed, 140 insertions(+), 137 deletions(-) diff --git a/src/plugins/merkleMemberships/client/index.ts b/src/plugins/merkleMemberships/client/index.ts index 8c15f0f..aa273e2 100644 --- a/src/plugins/merkleMemberships/client/index.ts +++ b/src/plugins/merkleMemberships/client/index.ts @@ -1,113 +1,110 @@ import { Field, JsonProof, SelfProof } from "o1js"; -import { - MerkleMembershipTreeWitness, - MerkleMembershipsOutput, - MerkleMembershipsPrivateInputs, - MerkleMembershipsProgram, - MerkleRoot -} from "../common/merkleMembershipsProgram"; +import * as ZkProgram from "../common/merkleMembershipsProgram"; import { IMinAuthProver, IMinAuthProverFactory } from '../../../library/plugin/pluginType'; import * as A from 'fp-ts/Array' import * as O from 'fp-ts/Option' import axios from "axios"; -export type MembershipsProverConfiguration = { - baseUrl: string +export type ProverConfiguration = { + baseUrl: string } -export type MembershipsPublicInputArgs = - Array<{ - treeIndex: bigint, - leafIndex: bigint - }> +export type PublicInputArgs = + Array<{ + treeIndex: bigint, + leafIndex: bigint + }> + +type ZkProof = SelfProof; // Prove that you belong to a set of user without revealing which user you are. -export class MembershipsProver implements - IMinAuthProver< - MembershipsPublicInputArgs, // TODO how to fetch - Array<[MerkleRoot, MerkleMembershipTreeWitness]>, - Array> +export class MerkleMembershipsProver implements + IMinAuthProver< + PublicInputArgs, + Array<[ZkProgram.PublicInput, ZkProgram.TreeWitness]>, + Array> { - private readonly cfg: MembershipsProverConfiguration; + private readonly cfg: ProverConfiguration; - async prove( - publicInput: Array<[MerkleRoot, MerkleMembershipTreeWitness]>, - secretInput: Array) - : Promise { - if (publicInput.length != secretInput.length) - throw "unmatched public/secret input list" + async prove( + publicInput: Array<[ZkProgram.PublicInput, ZkProgram.TreeWitness]>, + secretInput: Array) + : Promise { + if (publicInput.length != secretInput.length) + throw "unmatched public/secret input list" - const proof: O.Option> = - await - A.reduce - ( - Promise.resolve>>(O.none), - (acc, [[root, witness], secret]: [[MerkleRoot, MerkleMembershipTreeWitness], Field]) => { - const privInput = new MerkleMembershipsPrivateInputs({ witness, secret }) - return acc.then( - O.match( - () => MerkleMembershipsProgram.baseCase(root, privInput).then(O.some), - (prev) => MerkleMembershipsProgram.inductiveCase(root, prev, privInput).then(O.some) - ) - ) - } - ) - (A.zip(publicInput, secretInput)); + const proof: O.Option = + await + A.reduce + ( + Promise.resolve>(O.none), + (acc, [[root, witness], secret]: [[ZkProgram.PublicInput, ZkProgram.TreeWitness], Field]) => { + const privInput = new ZkProgram.PrivateInput({ witness, secret }) + return acc.then( + O.match( + () => ZkProgram.Program.baseCase(root, privInput).then(O.some), + (prev) => ZkProgram.Program.inductiveCase(root, prev, privInput).then(O.some) + ) + ) + } + ) + (A.zip(publicInput, secretInput)); - return O.match( - () => { throw "empty input list" }, // TODO: make it pure - (p: SelfProof) => p.toJSON() - )(proof); - } + return O.match( + () => { throw "empty input list" }, // TODO: make it pure + (p: ZkProof) => p.toJSON() + )(proof); + } - async fetchPublicInputs(args: MembershipsPublicInputArgs): Promise> { - const mkUrl = (treeIndex: bigint, leafIndex: bigint) => - `${this.cfg.baseUrl}/getRootAndWitness/${treeIndex.toString()}/${leafIndex.toString()}`; - const getRootAndWitness = - async (treeIndex: bigint, leafIndex: bigint): - Promise<[MerkleRoot, MerkleMembershipTreeWitness]> => { - const url = - `${this.cfg.baseUrl}/getRootAndWitness/${treeIndex.toString()}/${leafIndex.toString()}`; - const resp = await axios.get(url); - if (resp.status == 200) { - const body: { - root: string, - witness: string - } = resp.data; - const root = Field.fromJSON(body.root); - const witness = MerkleMembershipTreeWitness.fromJSON(body.witness); - return [new MerkleRoot({ root }), witness]; - } else { - const body: { error: string } = resp.data; - throw `error while getting root and witness: ${body.error}`; - } - } + async fetchPublicInputs(args: PublicInputArgs): + Promise> { + const mkUrl = (treeIndex: bigint, leafIndex: bigint) => + `${this.cfg.baseUrl}/getRootAndWitness/${treeIndex.toString()}/${leafIndex.toString()}`; + const getRootAndWitness = + async (treeIndex: bigint, leafIndex: bigint): + Promise<[ZkProgram.PublicInput, ZkProgram.TreeWitness]> => { + const url = + `${this.cfg.baseUrl}/getRootAndWitness/${treeIndex.toString()}/${leafIndex.toString()}`; + const resp = await axios.get(url); + if (resp.status == 200) { + const body: { + merkleRoot: string, + witness: string + } = resp.data; + const merkleRoot = Field.fromJSON(body.merkleRoot); + const witness = ZkProgram.TreeWitness.fromJSON(body.witness); + return [new ZkProgram.PublicInput({ merkleRoot }), witness]; + } else { + const body: { error: string } = resp.data; + throw `error while getting root and witness: ${body.error}`; + } + } - return Promise.all(A.map( - (args: { - treeIndex: bigint, - leafIndex: bigint - }): Promise<[MerkleRoot, MerkleMembershipTreeWitness]> => - getRootAndWitness(args.treeIndex, args.leafIndex) - )(args)); - } + return Promise.all(A.map( + (args: { + treeIndex: bigint, + leafIndex: bigint + }) => + getRootAndWitness(args.treeIndex, args.leafIndex) + )(args)); + } - constructor(cfg: MembershipsProverConfiguration) { - this.cfg = cfg; - } + constructor(cfg: ProverConfiguration) { + this.cfg = cfg; + } - static async initialize(cfg: MembershipsProverConfiguration): - Promise { - return new MembershipsProver(cfg); - } + static async initialize(cfg: ProverConfiguration): + Promise { + return new MerkleMembershipsProver(cfg); + } } -MembershipsProver satisfies IMinAuthProverFactory< - MembershipsProver, - MembershipsProverConfiguration, - MembershipsPublicInputArgs, - Array<[MerkleRoot, MerkleMembershipTreeWitness]>, - Array +MerkleMembershipsProver satisfies IMinAuthProverFactory< + MerkleMembershipsProver, + ProverConfiguration, + PublicInputArgs, + Array<[ZkProgram.PublicInput, ZkProgram.TreeWitness]>, + Array > -export default MembershipsProver; \ No newline at end of file +export default MerkleMembershipsProver; \ No newline at end of file diff --git a/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts b/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts index 5ef381c..14c908b 100644 --- a/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts +++ b/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts @@ -1,22 +1,22 @@ import { Experimental, Field, MerkleWitness, Poseidon, SelfProof, Struct } from "o1js"; // TODO how can this be made dynamic -export const MERKLE_MEMBERSHIP_TREE_HEIGHT = 10; +export const TREE_HEIGHT = 10; -export class MerkleMembershipTreeWitness extends - MerkleWitness(MERKLE_MEMBERSHIP_TREE_HEIGHT) +export class TreeWitness extends + MerkleWitness(TREE_HEIGHT) { } -export class MerkleMembershipsPrivateInputs extends Struct({ - witness: MerkleMembershipTreeWitness, +export class PrivateInput extends Struct({ + witness: TreeWitness, secret: Field }) { } -export class MerkleRoot extends Struct({ - root: Field +export class PublicInput extends Struct({ + merkleRoot: Field }) { } -export class MerkleMembershipsOutput extends Struct({ +export class PublicOutput extends Struct({ recursiveHash: Field, }) { }; @@ -25,41 +25,41 @@ export class MerkleMembershipsOutput extends Struct({ // The output contains a recursive hash of all the roots for which the preimage is known. // output = hash(lastRoot + hash(secondLastRoot, ... hash(xLastRoot, lastRoot) ...) // Therefore the order of the proofs matters. -export const MerkleMembershipsProgram = Experimental.ZkProgram({ - publicInput: MerkleRoot, - publicOutput: MerkleMembershipsOutput, +export const Program = Experimental.ZkProgram({ + publicInput: PublicInput, + publicOutput: PublicOutput, methods: { baseCase: { - privateInputs: [MerkleMembershipsPrivateInputs], - method(publicInput: MerkleRoot, - privateInput: MerkleMembershipsPrivateInputs) - : MerkleMembershipsOutput { + privateInputs: [PrivateInput], + method(publicInput: PublicInput, + privateInput: PrivateInput) + : PublicOutput { privateInput.witness .calculateRoot(Poseidon.hash([privateInput.secret])) - .assertEquals(publicInput.root); - return new MerkleMembershipsOutput({ - recursiveHash: publicInput.root + .assertEquals(publicInput.merkleRoot); + return new PublicOutput({ + recursiveHash: publicInput.merkleRoot }); } }, inductiveCase: { - privateInputs: [SelfProof, MerkleMembershipsPrivateInputs], + privateInputs: [SelfProof, PrivateInput], method( - publicInput: MerkleRoot, - earlierProof: SelfProof, - privateInput: MerkleMembershipsPrivateInputs - ): MerkleMembershipsOutput { + publicInput: PublicInput, + earlierProof: SelfProof, + privateInput: PrivateInput + ): PublicOutput { earlierProof.verify(); privateInput.witness .calculateRoot(Poseidon.hash([privateInput.secret])) - .assertEquals(publicInput.root); - return new MerkleMembershipsOutput( + .assertEquals(publicInput.merkleRoot); + return new PublicOutput( { recursiveHash: Poseidon.hash([ - publicInput.root, + publicInput.merkleRoot, earlierProof.publicOutput.recursiveHash ]) }); @@ -68,4 +68,4 @@ export const MerkleMembershipsProgram = Experimental.ZkProgram({ } }); -export default MerkleMembershipsProgram; +export default Program; diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 5e4593b..385b3b4 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -1,21 +1,27 @@ import { Experimental, Field, JsonProof, Poseidon } from "o1js"; import * as O from 'fp-ts/Option'; -import MerkleMembershipsProgram from "../common/merkleMembershipsProgram"; import z from 'zod'; -import { IMinAuthPlugin, IMinAuthPluginFactory } from "../../../library/plugin/pluginType"; -import { MinaTreesProvider, MinaTreesProviderConfiguration, TreesProvider, minaTreesProviderConfigurationSchema } from "./treeStorage"; -import { RequestHandler } from "express"; +import { IMinAuthPlugin, IMinAuthPluginFactory } + from '../../../library/plugin/pluginType'; +import { + MinaTreesProvider, + MinaTreesProviderConfiguration, + TreesProvider, + minaTreesProviderConfigurationSchema +} from './treeStorage'; +import { RequestHandler } from 'express'; import * as A from 'fp-ts/Array'; +import * as ZkProgram from '../common/merkleMembershipsProgram'; const PoseidonHashSchema = z.bigint(); const publicInputArgsSchema = z.array(PoseidonHashSchema); -export type MerkleMembershipsPublicInputArgs = +export type PublicInputArgs = z.infer; export class MerkleMembershipsPlugin - implements IMinAuthPlugin{ + implements IMinAuthPlugin{ readonly verificationKey: string; private readonly storageProvider: TreesProvider @@ -53,7 +59,7 @@ export class MerkleMembershipsPlugin } resp.status(200).json({ - root: root.toJSON(), + merkleRoot: root.toJSON(), witness: witness.toJSON(), }); @@ -63,11 +69,11 @@ export class MerkleMembershipsPlugin readonly publicInputArgsSchema = publicInputArgsSchema; async verifyAndGetOutput( - publicInputArgs: MerkleMembershipsPublicInputArgs, + publicInputArgs: PublicInputArgs, serializedProof: JsonProof): Promise { const proof = Experimental.ZkProgram - .Proof(MerkleMembershipsProgram) + .Proof(ZkProgram.Program) .fromJSON(serializedProof); const trees = await this.storageProvider.getTrees(); @@ -104,7 +110,7 @@ export class MerkleMembershipsPlugin } static async initialize(cfg: MinaTreesProviderConfiguration): Promise { - const { verificationKey } = await MerkleMembershipsProgram.compile(); + const { verificationKey } = await ZkProgram.Program.compile(); const storage = await MinaTreesProvider.initialize(cfg); return new MerkleMembershipsPlugin(verificationKey, storage); } @@ -114,7 +120,7 @@ export class MerkleMembershipsPlugin MerkleMembershipsPlugin satisfies IMinAuthPluginFactory< - IMinAuthPlugin, + IMinAuthPlugin, MinaTreesProviderConfiguration, - MerkleMembershipsPublicInputArgs, + PublicInputArgs, Field>; diff --git a/src/plugins/merkleMemberships/server/treeStorage.ts b/src/plugins/merkleMemberships/server/treeStorage.ts index 08e11fc..84c8ae0 100644 --- a/src/plugins/merkleMemberships/server/treeStorage.ts +++ b/src/plugins/merkleMemberships/server/treeStorage.ts @@ -1,6 +1,6 @@ import { AccountUpdate, Field, MerkleTree, Mina, PrivateKey } from "o1js"; import * as O from 'fp-ts/Option'; -import { MERKLE_MEMBERSHIP_TREE_HEIGHT, MerkleMembershipTreeWitness } from "../common/merkleMembershipsProgram"; +import * as Program from "../common/merkleMembershipsProgram"; import z from 'zod'; import fs from "fs/promises" import { TreeRootStorageContract } from "../common/treeRootStorageContract"; @@ -8,21 +8,21 @@ import * as A from 'fp-ts/Array'; export interface TreeStorage { getRoot(): Promise; - getWitness(leafIndex: bigint): Promise>; + getWitness(leafIndex: bigint): Promise>; hasLeaf(leafIndex: bigint): Promise; setLeaf(leafIndex: bigint, leaf: Field): Promise; } export class InMemoryStorage implements TreeStorage { occupied: Set = new Set(); - merkleTree: MerkleTree = new MerkleTree(MERKLE_MEMBERSHIP_TREE_HEIGHT); + merkleTree: MerkleTree = new MerkleTree(Program.TREE_HEIGHT); async getRoot() { return this.merkleTree.getRoot(); } - async getWitness(leafIndex: bigint): Promise> { + async getWitness(leafIndex: bigint): Promise> { return this.occupied.has(leafIndex) ? O.none : - O.some(new MerkleMembershipTreeWitness(this.merkleTree.getWitness(leafIndex))); + O.some(new Program.TreeWitness(this.merkleTree.getWitness(leafIndex))); } async hasLeaf(leafIndex: bigint): Promise { @@ -45,7 +45,7 @@ export class PersistentInMemoryStorage extends InMemoryStorage { .reduce((acc: Record, idx: bigint) => { acc[Number(idx)] = this.merkleTree - .getNode(MERKLE_MEMBERSHIP_TREE_HEIGHT, idx) + .getNode(Program.TREE_HEIGHT, idx) .toJSON(); return acc; } @@ -69,7 +69,7 @@ export class PersistentInMemoryStorage extends InMemoryStorage { const storageObj: Record = JSON.parse(content); const occupied: Set = new Set(); - const merkleTree: MerkleTree = new MerkleTree(MERKLE_MEMBERSHIP_TREE_HEIGHT); + const merkleTree: MerkleTree = new MerkleTree(Program.TREE_HEIGHT); Object .entries(storageObj) From a817d6f7a3421f88854d9569bf40028b805a210d Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Mon, 9 Oct 2023 19:39:11 +0800 Subject: [PATCH 12/21] index trees by their merkle roots --- src/plugins/merkleMemberships/client/index.ts | 19 +++--- src/plugins/merkleMemberships/server/index.ts | 62 +++++++++---------- .../merkleMemberships/server/treeStorage.ts | 20 ++++-- 3 files changed, 50 insertions(+), 51 deletions(-) diff --git a/src/plugins/merkleMemberships/client/index.ts b/src/plugins/merkleMemberships/client/index.ts index aa273e2..f0a9b0e 100644 --- a/src/plugins/merkleMemberships/client/index.ts +++ b/src/plugins/merkleMemberships/client/index.ts @@ -11,7 +11,7 @@ export type ProverConfiguration = { export type PublicInputArgs = Array<{ - treeIndex: bigint, + treeRoot: Field, leafIndex: bigint }> @@ -58,22 +58,19 @@ export class MerkleMembershipsProver implements async fetchPublicInputs(args: PublicInputArgs): Promise> { - const mkUrl = (treeIndex: bigint, leafIndex: bigint) => - `${this.cfg.baseUrl}/getRootAndWitness/${treeIndex.toString()}/${leafIndex.toString()}`; + const mkUrl = (treeRoot: Field, leafIndex: bigint) => + `${this.cfg.baseUrl}/getWitness/${treeRoot.toBigInt().toString()}/${leafIndex.toString()}`; const getRootAndWitness = - async (treeIndex: bigint, leafIndex: bigint): + async (treeRoot: Field, leafIndex: bigint): Promise<[ZkProgram.PublicInput, ZkProgram.TreeWitness]> => { - const url = - `${this.cfg.baseUrl}/getRootAndWitness/${treeIndex.toString()}/${leafIndex.toString()}`; + const url = mkUrl(treeRoot, leafIndex); const resp = await axios.get(url); if (resp.status == 200) { const body: { - merkleRoot: string, witness: string } = resp.data; - const merkleRoot = Field.fromJSON(body.merkleRoot); const witness = ZkProgram.TreeWitness.fromJSON(body.witness); - return [new ZkProgram.PublicInput({ merkleRoot }), witness]; + return [new ZkProgram.PublicInput({ merkleRoot: treeRoot }), witness]; } else { const body: { error: string } = resp.data; throw `error while getting root and witness: ${body.error}`; @@ -82,10 +79,10 @@ export class MerkleMembershipsProver implements return Promise.all(A.map( (args: { - treeIndex: bigint, + treeRoot: Field, leafIndex: bigint }) => - getRootAndWitness(args.treeIndex, args.leafIndex) + getRootAndWitness(args.treeRoot, args.leafIndex) )(args)); } diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 385b3b4..1523f48 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -10,8 +10,8 @@ import { minaTreesProviderConfigurationSchema } from './treeStorage'; import { RequestHandler } from 'express'; -import * as A from 'fp-ts/Array'; import * as ZkProgram from '../common/merkleMembershipsProgram'; +import { pipe } from "fp-ts/lib/function"; const PoseidonHashSchema = z.bigint(); @@ -26,30 +26,25 @@ export class MerkleMembershipsPlugin private readonly storageProvider: TreesProvider customRoutes: Record = { - "/getRootAndWitness/:treeIndex/:leafIndex": async (req, resp) => { + "/getWitness/:treeRoot/:leafIndex": async (req, resp) => { if (req.method != 'GET') { resp.status(400); return; } - const treeIndex = Number(req.params['treeIndex']); - const leafIndex = Number(req.params['leafIndex']); + const treeRoot = Field.from(req.params['treeRoot']); + const leafIndex = BigInt(req.params['leafIndex']); - const trees = await this.storageProvider.getTrees() + const tree = O.toUndefined(await this.storageProvider.getTree(treeRoot)); - - if (treeIndex >= trees.length) { + if (tree === undefined) { resp.status(400).json({ error: "tree not exists" }); return; } - const tree = trees[treeIndex]; - - const root = await tree.getRoot(); - const witness = O.toUndefined(await tree.getWitness(BigInt(leafIndex))); - + const witness = O.toUndefined(await tree.getWitness(leafIndex)); if (witness == undefined) { resp.status(400).json({ @@ -59,43 +54,42 @@ export class MerkleMembershipsPlugin } resp.status(200).json({ - merkleRoot: root.toJSON(), witness: witness.toJSON(), }); - } } readonly publicInputArgsSchema = publicInputArgsSchema; async verifyAndGetOutput( - publicInputArgs: PublicInputArgs, + treeRoots: PublicInputArgs, serializedProof: JsonProof): Promise { const proof = Experimental.ZkProgram .Proof(ZkProgram.Program) .fromJSON(serializedProof); - const trees = await this.storageProvider.getTrees(); - - const expectedHash = O.toUndefined(await - A.reduce( - Promise.resolve>(O.none), - (accP: Promise>, idx: bigint) => - accP.then((acc: O.Option) => - trees[Number(idx)] - .getRoot() - .then( - (root) => - Promise.resolve( - O.some( - O.match( - () => root, - (last: Field) => Poseidon.hash([root, last]) - )(acc))) - ) + const computeHash = async () => { + let hash: O.Option = O.none; + + for (const rawRoot of treeRoots) { + const root = Field(rawRoot); + const tree = await this.storageProvider.getTree(root); + if (O.isNone(tree)) throw "tree not found"; + hash = + pipe( + hash, + O.fold + ( + () => root, + (current: Field) => Poseidon.hash([current, root])), + O.some ) - )(publicInputArgs)); + }; + return hash; + } + + const expectedHash = O.toUndefined(await computeHash()); if (expectedHash === undefined || expectedHash.equals(proof.publicOutput.recursiveHash).not().toBoolean()) diff --git a/src/plugins/merkleMemberships/server/treeStorage.ts b/src/plugins/merkleMemberships/server/treeStorage.ts index 84c8ae0..26dabcb 100644 --- a/src/plugins/merkleMemberships/server/treeStorage.ts +++ b/src/plugins/merkleMemberships/server/treeStorage.ts @@ -1,10 +1,11 @@ import { AccountUpdate, Field, MerkleTree, Mina, PrivateKey } from "o1js"; import * as O from 'fp-ts/Option'; -import * as Program from "../common/merkleMembershipsProgram"; +import * as Program from '../common/merkleMembershipsProgram'; import z from 'zod'; import fs from "fs/promises" -import { TreeRootStorageContract } from "../common/treeRootStorageContract"; +import { TreeRootStorageContract } from '../common/treeRootStorageContract'; import * as A from 'fp-ts/Array'; +import { Option } from 'fp-ts/Option'; export interface TreeStorage { getRoot(): Promise; @@ -173,7 +174,7 @@ export class MinaBlockchainTreeStorage } export interface TreesProvider { - getTrees(): Promise>; + getTree(root: Field): Promise>; } export const minaTreesProviderConfigurationSchema = @@ -190,9 +191,16 @@ export type MinaTreesProviderConfiguration = z.infer; export class MinaTreesProvider implements TreesProvider { - treeStorages: Array; - - async getTrees() { return this.treeStorages; } + readonly treeStorages: Array; + + async getTree(root: Field) { + for (const tree of this.treeStorages) { + const thisRoot = await tree.getRoot(); + if (thisRoot.equals(root).toBoolean()) + return O.some(tree); + } + return O.none; + } constructor(treeStorages: Array) { this.treeStorages = treeStorages; From bd0de68e26db99164c1da26b5303f72b5638837c Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Mon, 9 Oct 2023 20:18:53 +0800 Subject: [PATCH 13/21] module aliases --- src/library/tools/pluginServer/config.ts | 6 +++--- src/library/tools/pluginServer/index.ts | 2 +- src/plugins/merkleMemberships/client/index.ts | 2 +- src/plugins/merkleMemberships/server/index.ts | 2 +- src/plugins/simplePreimage/client/index.ts | 2 +- src/plugins/simplePreimage/server/index.ts | 2 +- src/someServer/apiServer.ts | 2 +- tsconfig.json | 20 ++++++++++++++++--- 8 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index 46be7e7..36bb6e9 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -1,10 +1,10 @@ -import { IMinAuthPlugin, IMinAuthPluginFactory } from '../../../library/plugin/pluginType'; +import { IMinAuthPlugin, IMinAuthPluginFactory } from '@lib/plugin/pluginType'; import z from "zod"; import env from 'env-var'; import fs from 'fs'; import yaml from 'yaml'; -import { SimplePreimagePlugin } from "../../../plugins/simplePreimage/server"; -import { MerkleMembershipsPlugin } from "../../../plugins/merkleMemberships/server"; +import { SimplePreimagePlugin } from "@plugins/simplePreimage/server"; +import { MerkleMembershipsPlugin } from "@plugins/merkleMemberships/server"; // TODO: make use of heterogeneous lists /** diff --git a/src/library/tools/pluginServer/index.ts b/src/library/tools/pluginServer/index.ts index 6bfe79c..dd98b05 100644 --- a/src/library/tools/pluginServer/index.ts +++ b/src/library/tools/pluginServer/index.ts @@ -1,7 +1,7 @@ import express, { Request, Response } from 'express'; import bodyParser from 'body-parser'; import { JsonProof, verify } from 'o1js'; -import { IMinAuthPlugin } from '../../../library/plugin/pluginType'; +import { IMinAuthPlugin } from '@lib/plugin/pluginType'; import { readConfigurations, untypedPlugins } from './config'; const configurations = readConfigurations(); diff --git a/src/plugins/merkleMemberships/client/index.ts b/src/plugins/merkleMemberships/client/index.ts index f0a9b0e..28fe3bf 100644 --- a/src/plugins/merkleMemberships/client/index.ts +++ b/src/plugins/merkleMemberships/client/index.ts @@ -1,6 +1,6 @@ import { Field, JsonProof, SelfProof } from "o1js"; import * as ZkProgram from "../common/merkleMembershipsProgram"; -import { IMinAuthProver, IMinAuthProverFactory } from '../../../library/plugin/pluginType'; +import { IMinAuthProver, IMinAuthProverFactory } from '@lib/plugin/pluginType'; import * as A from 'fp-ts/Array' import * as O from 'fp-ts/Option' import axios from "axios"; diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 1523f48..6343fd6 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -2,7 +2,7 @@ import { Experimental, Field, JsonProof, Poseidon } from "o1js"; import * as O from 'fp-ts/Option'; import z from 'zod'; import { IMinAuthPlugin, IMinAuthPluginFactory } - from '../../../library/plugin/pluginType'; + from '@lib/plugin/pluginType'; import { MinaTreesProvider, MinaTreesProviderConfiguration, diff --git a/src/plugins/simplePreimage/client/index.ts b/src/plugins/simplePreimage/client/index.ts index 5186a65..8d2a2a6 100644 --- a/src/plugins/simplePreimage/client/index.ts +++ b/src/plugins/simplePreimage/client/index.ts @@ -1,5 +1,5 @@ import { Field, JsonProof } from 'o1js'; -import { IMinAuthProver } from '../../../library/plugin/pluginType'; +import { IMinAuthProver } from '@lib/plugin/pluginType'; import ProvePreimageProgram from '../common/hashPreimageProof'; diff --git a/src/plugins/simplePreimage/server/index.ts b/src/plugins/simplePreimage/server/index.ts index 71f90ba..16ba227 100644 --- a/src/plugins/simplePreimage/server/index.ts +++ b/src/plugins/simplePreimage/server/index.ts @@ -1,5 +1,5 @@ import { JsonProof } from 'o1js'; -import { IMinAuthPlugin, IMinAuthPluginFactory } from '../../../library/plugin/pluginType'; +import { IMinAuthPlugin, IMinAuthPluginFactory } from '@lib/plugin/pluginType'; import ProvePreimageProgram, { ProvePreimageProofClass } from '../common/hashPreimageProof'; import { RequestHandler } from 'express'; import { z } from 'zod'; diff --git a/src/someServer/apiServer.ts b/src/someServer/apiServer.ts index ae9061a..5bfe477 100644 --- a/src/someServer/apiServer.ts +++ b/src/someServer/apiServer.ts @@ -4,7 +4,7 @@ import bodyParser from 'body-parser'; import passport from 'passport'; import passportJWT from 'passport-jwt'; -import MinAuthStrategy from '../library/server/minauthStrategy' +import MinAuthStrategy from '@lib/server/minauthStrategy' const SECRET_KEY: string = 'YOUR_SECRET_KEY'; diff --git a/tsconfig.json b/tsconfig.json index 137276f..9024a2b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,10 @@ "compilerOptions": { "target": "es2016", "module": "commonjs", - "lib": ["dom", "esnext"], + "lib": [ + "dom", + "esnext" + ], "outDir": "./dist", "rootDir": ".", "strict": true, @@ -18,6 +21,17 @@ "sourceMap": true, "noFallthroughCasesInSwitch": true, "allowSyntheticDefaultImports": true, + "baseUrl": "./", + "paths": { + "@lib/*": [ + "src/library/*" + ], + "@plugins/*": [ + "src/plugins/*" + ] + } }, - "include": ["./src"], -} + "include": [ + "./src" + ], +} \ No newline at end of file From 0c52ab9679ad748b50b2ace9ff66acd3dca12793 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Mon, 9 Oct 2023 20:51:04 +0800 Subject: [PATCH 14/21] setup prettier --- .prettierrc | 6 + package.json | 7 +- src/headlessClient/client.ts | 182 +++++++++--------- src/library/plugin/pluginType.ts | 28 +-- src/library/server/minauthStrategy.ts | 148 +++++++------- src/library/tools/pluginServer/config.ts | 49 ++--- src/library/tools/pluginServer/index.ts | 59 +++--- src/plugins/merkleMemberships/client/index.ts | 137 +++++++------ .../common/merkleMembershipsProgram.ts | 100 +++++----- .../common/treeRootStorageContract.ts | 11 +- src/plugins/merkleMemberships/server/index.ts | 82 ++++---- .../merkleMemberships/server/treeStorage.ts | 176 +++++++++-------- src/plugins/simplePreimage/client/index.ts | 32 ++- .../common/hashPreimageProof.ts | 25 +-- src/plugins/simplePreimage/server/index.ts | 93 +++++---- src/someServer/apiServer.ts | 114 +++++------ 16 files changed, 656 insertions(+), 593 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a1bd96f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "trailingComma": "none", + "singleQuote": true, + "printWidth": 80 +} \ No newline at end of file diff --git a/package.json b/package.json index 313c2bf..bbb8888 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,10 @@ "start-plugin-server": "nodemon src/library/tools/pluginServer/index.ts", "start-client": "nodemon src/headlessClient/client.ts", "build": "tsc", - "serve-api": "node ./dist/someServer/apiServer.js", - "serve-plugin-server": "node ./dist/library/tools/pluginServer.js", - "run-client": "node ./dist/headlessClient/client.js" + "serve-api": "node ./dist/src/someServer/apiServer.js", + "serve-plugin-server": "node ./dist/src/library/tools/pluginServer.js", + "run-client": "node ./dist/src/headlessClient/client.js", + "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write" }, "keywords": [], "author": "", diff --git a/src/headlessClient/client.ts b/src/headlessClient/client.ts index f8e34f2..3f73309 100644 --- a/src/headlessClient/client.ts +++ b/src/headlessClient/client.ts @@ -4,126 +4,126 @@ const SERVER_URL: string = 'http://localhost:3000'; const PROVER_URL: string = 'http://localhost:3001/buildProof'; interface RoleMapping { - [key: string]: [string, string]; + [key: string]: [string, string]; } const generateMockProof = async (role: string) => { - const roleMapping: RoleMapping = { - member: [ - '2', - '21565680844461314807147611702860246336805372493508489110556896454939225549736', - ], - admin: [ - '1', - '7555220006856562833147743033256142154591945963958408607501861037584894828141', - ], - invalid_role: [ - '3', - '27942348051329088894852777850568290047473460593152551617852488829426742444656', - ], - invalid_proof: [ - '1', - '21565680844461314807147611702860246336805372493508489110556896454939225549736', - ], - }; + const roleMapping: RoleMapping = { + member: [ + '2', + '21565680844461314807147611702860246336805372493508489110556896454939225549736' + ], + admin: [ + '1', + '7555220006856562833147743033256142154591945963958408607501861037584894828141' + ], + invalid_role: [ + '3', + '27942348051329088894852777850568290047473460593152551617852488829426742444656' + ], + invalid_proof: [ + '1', + '21565680844461314807147611702860246336805372493508489110556896454939225549736' + ] + }; - const rm = roleMapping[role]; - const [preimage, hash]: [string, string] = rm || [ - 0, - '00000000000000000000000000000000000000000000000000000000000000000000000000000', - ]; + const rm = roleMapping[role]; + const [preimage, hash]: [string, string] = rm || [ + 0, + '00000000000000000000000000000000000000000000000000000000000000000000000000000' + ]; - console.log( - `building proof: For ${role}, public_inp ${hash}, private_inp ${preimage}`, - ); + console.log( + `building proof: For ${role}, public_inp ${hash}, private_inp ${preimage}` + ); - const data: BuildProofData = { - entrypoint: { - name: 'SimplePreimage', - config: {}, - }, - arguments: [hash, preimage], - }; + const data: BuildProofData = { + entrypoint: { + name: 'SimplePreimage', + config: {} + }, + arguments: [hash, preimage] + }; - const response = await axios.post(PROVER_URL, data); - console.log('Received response:', response); - return response.data; + const response = await axios.post(PROVER_URL, data); + console.log('Received response:', response); + return response.data; }; interface BuildProofData { - entrypoint: { - name: string; - config: unknown; - }; - arguments: [string, string]; + entrypoint: { + name: string; + config: unknown; + }; + arguments: [string, string]; } const mockLoginData = async (role: string) => { - return { - entrypoint: { - name: 'SimplePreimage', - config: {}, - }, - proof: await generateMockProof(role), - }; + return { + entrypoint: { + name: 'SimplePreimage', + config: {} + }, + proof: await generateMockProof(role) + }; }; interface LoginResponse { - jwt: string; - refreshToken: string; + jwt: string; + refreshToken: string; } async function login(role: string): Promise { - try { - const loginData = await mockLoginData(role); - console.log('Login with login data:', loginData); - const response = await axios.post(`${SERVER_URL}/login`, loginData); - return { - jwt: response.data.token, - refreshToken: response.data.refreshToken, - }; - } catch (error: unknown) { - console.error('Login failed:', error); - return null; - } + try { + const loginData = await mockLoginData(role); + console.log('Login with login data:', loginData); + const response = await axios.post(`${SERVER_URL}/login`, loginData); + return { + jwt: response.data.token, + refreshToken: response.data.refreshToken + }; + } catch (error: unknown) { + console.error('Login failed:', error); + return null; + } } async function accessProtected(jwt: string): Promise { - try { - const response = await axios.get(`${SERVER_URL}/protected`, { - headers: { - Authorization: `Bearer ${jwt}`, - }, - }); - console.log('Protected response:', response.data); - } catch (error) { - console.error('Accessing protected route failed:', error); - } + try { + const response = await axios.get(`${SERVER_URL}/protected`, { + headers: { + Authorization: `Bearer ${jwt}` + } + }); + console.log('Protected response:', response.data); + } catch (error) { + console.error('Accessing protected route failed:', error); + } } async function refreshToken(refreshToken: string): Promise { - try { - const response = await axios.post(`${SERVER_URL}/token`, { - refreshToken, - }); - return response.data.token; - } catch (error) { - console.error('Token refresh failed:', error); - return null; - } + try { + const response = await axios.post(`${SERVER_URL}/token`, { + refreshToken + }); + return response.data.token; + } catch (error) { + console.error('Token refresh failed:', error); + return null; + } } // Main Execution (async () => { - // provisional tests for the backend - const tokens = await login('admin'); - if (tokens) { - await accessProtected(tokens.jwt); + // provisional tests for the backend + const tokens = await login('admin'); + if (tokens) { + await accessProtected(tokens.jwt); - const newJWT = await refreshToken(tokens.refreshToken); - if (newJWT) { - console.log('Successfully refreshed JWT.'); - await accessProtected(newJWT); - } + const newJWT = await refreshToken(tokens.refreshToken); + if (newJWT) { + console.log('Successfully refreshed JWT.'); + await accessProtected(newJWT); } + } })(); diff --git a/src/library/plugin/pluginType.ts b/src/library/plugin/pluginType.ts index 46b1c8b..442a85b 100644 --- a/src/library/plugin/pluginType.ts +++ b/src/library/plugin/pluginType.ts @@ -1,5 +1,5 @@ -import { RequestHandler } from "express"; -import { JsonProof } from "o1js"; +import { RequestHandler } from 'express'; +import { JsonProof } from 'o1js'; import z from 'zod'; // Interfaces used on the server side. @@ -9,7 +9,8 @@ export interface IMinAuthPlugin { // the output. verifyAndGetOutput( publicInputArgs: PublicInputArgs, - serializedProof: JsonProof): Promise; + serializedProof: JsonProof + ): Promise; // The schema of the arguments for fetching public inputs. readonly publicInputArgsSchema: z.ZodType; @@ -31,9 +32,11 @@ export interface IMinAuthPlugin { // TODO: generic type inference? export interface IMinAuthPluginFactory< T extends IMinAuthPlugin, - Configuration, PublicInputArgs, Output> { - - // Initialize the plugin given the configuration. The underlying zk program is + Configuration, + PublicInputArgs, + Output +> { + // Initialize the plugin given the configuration. The underlying zk program is // typically compiled here. initialize(cfg: Configuration): Promise; @@ -43,19 +46,20 @@ export interface IMinAuthPluginFactory< // Interfaces used on the client side. export interface IMinAuthProver { - prove(publicInput: PublicInput, secretInput: PrivateInput): Promise; + prove( + publicInput: PublicInput, + secretInput: PrivateInput + ): Promise; fetchPublicInputs(args: PublicInputArgs): Promise; } export interface IMinAuthProverFactory< - T extends IMinAuthProver< - PublicInputArgs, - PublicInput, - PrivateInput>, + T extends IMinAuthProver, Configuration, PublicInputArgs, PublicInput, - PrivateInput> { + PrivateInput +> { initialize(cfg: Configuration): Promise; } diff --git a/src/library/server/minauthStrategy.ts b/src/library/server/minauthStrategy.ts index 71c778a..eb22397 100644 --- a/src/library/server/minauthStrategy.ts +++ b/src/library/server/minauthStrategy.ts @@ -9,94 +9,94 @@ const PROVER_URL: string = 'http://localhost:3001/verifyProof'; const SECRET_KEY: string = 'YOUR_SECRET_KEY'; interface MinAuthProof { - entrypoint: { - name: string; - config: never; - }; - proof: JsonProof; + entrypoint: { + name: string; + config: never; + }; + proof: JsonProof; } interface User { - id: number; - name: string; - role: string; - token?: string; - refreshToken?: string; + id: number; + name: string; + role: string; + token?: string; + refreshToken?: string; } type VerificationResult = { - role: string; - message: string; + role: string; + message: string; }; async function verifyProof( - entryName: string, - config: never, - proof: JsonProof, + entryName: string, + config: never, + proof: JsonProof ): Promise { - if (!proof) throw 'Proof cannot be empty'; - - const data: MinAuthProof = { - entrypoint: { - name: entryName, - config: config, - }, - proof: proof, - }; - - console.log('Calling for proof verification with:', data); - const response = await axios.post(PROVER_URL, data); - console.log('Received response:', response); - return response.data; + if (!proof) throw 'Proof cannot be empty'; + + const data: MinAuthProof = { + entrypoint: { + name: entryName, + config: config + }, + proof: proof + }; + + console.log('Calling for proof verification with:', data); + const response = await axios.post(PROVER_URL, data); + console.log('Received response:', response); + return response.data; } class MinAuthStrategy extends Strategy { - name = 'MinAuthStrategy'; - - public constructor() { - super(); - } - - async authenticate(req: Request, _options?: any): Promise { - try { - console.log('authenticating (strategy) with req:', req.body); - const loginData = req.body; - const { entrypoint, proof } = loginData as MinAuthProof; - const { name, config } = entrypoint; - const { role, message } = await verifyProof(name, config, proof); - console.log('proof verification return message is:', message); - console.log('proof verification return role is:', role); - - if (proof && role) { - const user: User = { - id: 1, - name: 'John Doe2', - role: role, - }; - - const jwtPayload = { - sub: user.id, - name: user.name, - role: user.role, - }; - const jwtOptions = { - expiresIn: '1h', - }; - const token = jwt.sign(jwtPayload, SECRET_KEY, jwtOptions); - - user.token = token; - - const refreshToken = crypto.randomBytes(40).toString('hex'); - user.refreshToken = refreshToken; - - return this.success(user); - } else { - return this.fail({ message: 'Invalid serialized proof' }, 401); - } - } catch (error: unknown) { - this.error(error as Error); - } + name = 'MinAuthStrategy'; + + public constructor() { + super(); + } + + async authenticate(req: Request, _options?: any): Promise { + try { + console.log('authenticating (strategy) with req:', req.body); + const loginData = req.body; + const { entrypoint, proof } = loginData as MinAuthProof; + const { name, config } = entrypoint; + const { role, message } = await verifyProof(name, config, proof); + console.log('proof verification return message is:', message); + console.log('proof verification return role is:', role); + + if (proof && role) { + const user: User = { + id: 1, + name: 'John Doe2', + role: role + }; + + const jwtPayload = { + sub: user.id, + name: user.name, + role: user.role + }; + const jwtOptions = { + expiresIn: '1h' + }; + const token = jwt.sign(jwtPayload, SECRET_KEY, jwtOptions); + + user.token = token; + + const refreshToken = crypto.randomBytes(40).toString('hex'); + user.refreshToken = refreshToken; + + return this.success(user); + } else { + return this.fail({ message: 'Invalid serialized proof' }, 401); + } + } catch (error: unknown) { + this.error(error as Error); } + } } export default MinAuthStrategy; diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index 36bb6e9..fed352d 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -1,34 +1,35 @@ import { IMinAuthPlugin, IMinAuthPluginFactory } from '@lib/plugin/pluginType'; -import z from "zod"; +import z from 'zod'; import env from 'env-var'; import fs from 'fs'; import yaml from 'yaml'; -import { SimplePreimagePlugin } from "@plugins/simplePreimage/server"; -import { MerkleMembershipsPlugin } from "@plugins/merkleMemberships/server"; +import { SimplePreimagePlugin } from '@plugins/simplePreimage/server'; +import { MerkleMembershipsPlugin } from '@plugins/merkleMemberships/server'; // TODO: make use of heterogeneous lists /** * All the available plugins and their names go here. */ -export const untypedPlugins: - Record, any, any, any>> - = { - "SimplePreimagePlugin": SimplePreimagePlugin, - "MerkleMembershipsPlugin": MerkleMembershipsPlugin +export const untypedPlugins: Record< + string, + IMinAuthPluginFactory, any, any, any> +> = { + SimplePreimagePlugin: SimplePreimagePlugin, + MerkleMembershipsPlugin: MerkleMembershipsPlugin }; const serverConfigurationsSchema = z.object({ server: z.object({ - address: z.string().ip().default("127.0.0.1"), - port: z.number().default(3001), + address: z.string().ip().default('127.0.0.1'), + port: z.number().default(3001) }), - plugins: z.object - (Object - .entries(untypedPlugins) - .reduce( + plugins: z + .object( + Object.entries(untypedPlugins).reduce( (o, [n, p]) => ({ ...o, [n]: p.configurationSchema }), - {})) + {} + ) + ) .partial() }); @@ -36,14 +37,14 @@ export type ServerConfigurations = z.infer; const defaultConfiguration: ServerConfigurations = { server: { - address: "127.0.0.1", + address: '127.0.0.1', port: 3001 }, plugins: { SimplePreimagePlugin: { roles: {} }, MerkleMembershipsPlugin: { trees: [] } } -} +}; /** * Load configurations from disk. The configuration is encoded in yaml and @@ -53,13 +54,15 @@ const defaultConfiguration: ServerConfigurations = { * @returns The decoded configurations for the server and plugins. */ export function readConfigurations(): ServerConfigurations { - const configFile = - env.get('MINAUTH_CONFIG') - .default("config.yaml") - .asString(); + const configFile = env + .get('MINAUTH_CONFIG') + .default('config.yaml') + .asString(); if (!fs.existsSync(configFile)) { - console.warn("configuration file not exists, use the default configuration") + console.warn( + 'configuration file not exists, use the default configuration' + ); return defaultConfiguration; } const configFileContent = fs.readFileSync(configFile, 'utf8'); diff --git a/src/library/tools/pluginServer/index.ts b/src/library/tools/pluginServer/index.ts index dd98b05..70a5417 100644 --- a/src/library/tools/pluginServer/index.ts +++ b/src/library/tools/pluginServer/index.ts @@ -6,24 +6,26 @@ import { readConfigurations, untypedPlugins } from './config'; const configurations = readConfigurations(); -console.log("configuration loaded", configurations) +console.log('configuration loaded', configurations); /** * Construct plugins which are enabled in the configuration. * @returns A record of plugin instances. */ -async function initializePlugins(): - Promise>> { +async function initializePlugins(): Promise< + Record> +> { console.log('initializing plugins'); - return Object - .entries(configurations.plugins) - .reduce(async (o, [name, cfg]) => { + return Object.entries(configurations.plugins).reduce( + async (o, [name, cfg]) => { console.debug(`initializing ${name}`, cfg); const factory = untypedPlugins[name]; - const typedCfg = factory.configurationSchema.parse(cfg) + const typedCfg = factory.configurationSchema.parse(cfg); const plugin = await factory.initialize(typedCfg); return { ...o, [name]: plugin }; - }, {}); + }, + {} + ); } initializePlugins() @@ -40,18 +42,22 @@ initializePlugins() const pluginName = data.plugin; console.info(`verifying proof using plugin ${pluginName}`); const pluginInstance = activePlugins[pluginName]; - if (!pluginInstance) - throw `plugin ${pluginName} not found`; + if (!pluginInstance) throw `plugin ${pluginName} not found`; // Step 1: check that the proof was generated using a certain verification key. - const proofValid = await verify(data.proof, pluginInstance.verificationKey); - if (!proofValid) - throw `invalid proof`; + const proofValid = await verify( + data.proof, + pluginInstance.verificationKey + ); + if (!proofValid) throw `invalid proof`; // Step 2: use the plugin to extract the output. The plugin is also responsible // for checking the legitimacy of the public inputs. - const typedPublicInputArgs - = pluginInstance.publicInputArgsSchema.parse(data.publicInputArgs); - const output = - await pluginInstance.verifyAndGetOutput(typedPublicInputArgs, data.proof); + const typedPublicInputArgs = pluginInstance.publicInputArgsSchema.parse( + data.publicInputArgs + ); + const output = await pluginInstance.verifyAndGetOutput( + typedPublicInputArgs, + data.proof + ); return output; } @@ -59,11 +65,9 @@ initializePlugins() // Register all custom routes of active plugins under `/plugins/${pluginName}`. Object.entries(activePlugins).map(([name, plugin]) => - Object - .entries(plugin.customRoutes) - .map(([path, handler]) => - app.use(`/plugins/${name}/${path}`, handler) - ) + Object.entries(plugin.customRoutes).map(([path, handler]) => + app.use(`/plugins/${name}/${path}`, handler) + ) ); app @@ -73,14 +77,13 @@ initializePlugins() res.json({ result }); } catch (error) { console.error('Error:', error); - res - .status(500) - .json({ error: 'Internal Server Error' }); + res.status(500).json({ error: 'Internal Server Error' }); } }) - .listen(configurations.server.port, - () => - console.log(`Server is running on http://localhost:${configurations.server.port}`) + .listen(configurations.server.port, () => + console.log( + `Server is running on http://localhost:${configurations.server.port}` + ) ); }) .catch((error) => { diff --git a/src/plugins/merkleMemberships/client/index.ts b/src/plugins/merkleMemberships/client/index.ts index 28fe3bf..eaab531 100644 --- a/src/plugins/merkleMemberships/client/index.ts +++ b/src/plugins/merkleMemberships/client/index.ts @@ -1,97 +1,108 @@ -import { Field, JsonProof, SelfProof } from "o1js"; -import * as ZkProgram from "../common/merkleMembershipsProgram"; +import { Field, JsonProof, SelfProof } from 'o1js'; +import * as ZkProgram from '../common/merkleMembershipsProgram'; import { IMinAuthProver, IMinAuthProverFactory } from '@lib/plugin/pluginType'; -import * as A from 'fp-ts/Array' -import * as O from 'fp-ts/Option' -import axios from "axios"; +import * as A from 'fp-ts/Array'; +import * as O from 'fp-ts/Option'; +import axios from 'axios'; export type ProverConfiguration = { - baseUrl: string -} + baseUrl: string; +}; -export type PublicInputArgs = - Array<{ - treeRoot: Field, - leafIndex: bigint - }> +export type PublicInputArgs = Array<{ + treeRoot: Field; + leafIndex: bigint; +}>; type ZkProof = SelfProof; // Prove that you belong to a set of user without revealing which user you are. -export class MerkleMembershipsProver implements - IMinAuthProver< - PublicInputArgs, - Array<[ZkProgram.PublicInput, ZkProgram.TreeWitness]>, - Array> +export class MerkleMembershipsProver + implements + IMinAuthProver< + PublicInputArgs, + Array<[ZkProgram.PublicInput, ZkProgram.TreeWitness]>, + Array + > { private readonly cfg: ProverConfiguration; async prove( publicInput: Array<[ZkProgram.PublicInput, ZkProgram.TreeWitness]>, - secretInput: Array) - : Promise { + secretInput: Array + ): Promise { if (publicInput.length != secretInput.length) - throw "unmatched public/secret input list" + throw 'unmatched public/secret input list'; - const proof: O.Option = - await - A.reduce - ( - Promise.resolve>(O.none), - (acc, [[root, witness], secret]: [[ZkProgram.PublicInput, ZkProgram.TreeWitness], Field]) => { - const privInput = new ZkProgram.PrivateInput({ witness, secret }) - return acc.then( - O.match( - () => ZkProgram.Program.baseCase(root, privInput).then(O.some), - (prev) => ZkProgram.Program.inductiveCase(root, prev, privInput).then(O.some) - ) + const proof: O.Option = await A.reduce( + Promise.resolve>(O.none), + ( + acc, + [[root, witness], secret]: [ + [ZkProgram.PublicInput, ZkProgram.TreeWitness], + Field + ] + ) => { + const privInput = new ZkProgram.PrivateInput({ witness, secret }); + return acc.then( + O.match( + () => ZkProgram.Program.baseCase(root, privInput).then(O.some), + (prev) => + ZkProgram.Program.inductiveCase(root, prev, privInput).then( + O.some ) - } ) - (A.zip(publicInput, secretInput)); + ); + } + )(A.zip(publicInput, secretInput)); return O.match( - () => { throw "empty input list" }, // TODO: make it pure + () => { + throw 'empty input list'; + }, // TODO: make it pure (p: ZkProof) => p.toJSON() )(proof); } - async fetchPublicInputs(args: PublicInputArgs): - Promise> { + async fetchPublicInputs( + args: PublicInputArgs + ): Promise> { const mkUrl = (treeRoot: Field, leafIndex: bigint) => - `${this.cfg.baseUrl}/getWitness/${treeRoot.toBigInt().toString()}/${leafIndex.toString()}`; - const getRootAndWitness = - async (treeRoot: Field, leafIndex: bigint): - Promise<[ZkProgram.PublicInput, ZkProgram.TreeWitness]> => { - const url = mkUrl(treeRoot, leafIndex); - const resp = await axios.get(url); - if (resp.status == 200) { - const body: { - witness: string - } = resp.data; - const witness = ZkProgram.TreeWitness.fromJSON(body.witness); - return [new ZkProgram.PublicInput({ merkleRoot: treeRoot }), witness]; - } else { - const body: { error: string } = resp.data; - throw `error while getting root and witness: ${body.error}`; - } + `${this.cfg.baseUrl}/getWitness/${treeRoot + .toBigInt() + .toString()}/${leafIndex.toString()}`; + const getRootAndWitness = async ( + treeRoot: Field, + leafIndex: bigint + ): Promise<[ZkProgram.PublicInput, ZkProgram.TreeWitness]> => { + const url = mkUrl(treeRoot, leafIndex); + const resp = await axios.get(url); + if (resp.status == 200) { + const body: { + witness: string; + } = resp.data; + const witness = ZkProgram.TreeWitness.fromJSON(body.witness); + return [new ZkProgram.PublicInput({ merkleRoot: treeRoot }), witness]; + } else { + const body: { error: string } = resp.data; + throw `error while getting root and witness: ${body.error}`; } + }; - return Promise.all(A.map( - (args: { - treeRoot: Field, - leafIndex: bigint - }) => + return Promise.all( + A.map((args: { treeRoot: Field; leafIndex: bigint }) => getRootAndWitness(args.treeRoot, args.leafIndex) - )(args)); + )(args) + ); } constructor(cfg: ProverConfiguration) { this.cfg = cfg; } - static async initialize(cfg: ProverConfiguration): - Promise { + static async initialize( + cfg: ProverConfiguration + ): Promise { return new MerkleMembershipsProver(cfg); } } @@ -102,6 +113,6 @@ MerkleMembershipsProver satisfies IMinAuthProverFactory< PublicInputArgs, Array<[ZkProgram.PublicInput, ZkProgram.TreeWitness]>, Array -> +>; -export default MerkleMembershipsProver; \ No newline at end of file +export default MerkleMembershipsProver; diff --git a/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts b/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts index 14c908b..8cb2fe0 100644 --- a/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts +++ b/src/plugins/merkleMemberships/common/merkleMembershipsProgram.ts @@ -1,24 +1,29 @@ -import { Experimental, Field, MerkleWitness, Poseidon, SelfProof, Struct } from "o1js"; +import { + Experimental, + Field, + MerkleWitness, + Poseidon, + SelfProof, + Struct +} from 'o1js'; // TODO how can this be made dynamic export const TREE_HEIGHT = 10; -export class TreeWitness extends - MerkleWitness(TREE_HEIGHT) -{ } +export class TreeWitness extends MerkleWitness(TREE_HEIGHT) {} export class PrivateInput extends Struct({ - witness: TreeWitness, - secret: Field -}) { } + witness: TreeWitness, + secret: Field +}) {} export class PublicInput extends Struct({ - merkleRoot: Field -}) { } + merkleRoot: Field +}) {} export class PublicOutput extends Struct({ - recursiveHash: Field, -}) { }; + recursiveHash: Field +}) {} // Prove knowledge of a preimage of a hash in a merkle tree. // The proof does not reveal the preimage nor the hash. @@ -26,46 +31,45 @@ export class PublicOutput extends Struct({ // output = hash(lastRoot + hash(secondLastRoot, ... hash(xLastRoot, lastRoot) ...) // Therefore the order of the proofs matters. export const Program = Experimental.ZkProgram({ - publicInput: PublicInput, - publicOutput: PublicOutput, + publicInput: PublicInput, + publicOutput: PublicOutput, - methods: { - baseCase: { - privateInputs: [PrivateInput], - method(publicInput: PublicInput, - privateInput: PrivateInput) - : PublicOutput { - privateInput.witness - .calculateRoot(Poseidon.hash([privateInput.secret])) - .assertEquals(publicInput.merkleRoot); - return new PublicOutput({ - recursiveHash: publicInput.merkleRoot - }); - } - }, + methods: { + baseCase: { + privateInputs: [PrivateInput], + method( + publicInput: PublicInput, + privateInput: PrivateInput + ): PublicOutput { + privateInput.witness + .calculateRoot(Poseidon.hash([privateInput.secret])) + .assertEquals(publicInput.merkleRoot); + return new PublicOutput({ + recursiveHash: publicInput.merkleRoot + }); + } + }, - inductiveCase: { - privateInputs: [SelfProof, PrivateInput], - method( - publicInput: PublicInput, - earlierProof: SelfProof, - privateInput: PrivateInput - ): PublicOutput { - earlierProof.verify(); - privateInput.witness - .calculateRoot(Poseidon.hash([privateInput.secret])) - .assertEquals(publicInput.merkleRoot); - return new PublicOutput( - { - recursiveHash: - Poseidon.hash([ - publicInput.merkleRoot, - earlierProof.publicOutput.recursiveHash - ]) - }); - } - } + inductiveCase: { + privateInputs: [SelfProof, PrivateInput], + method( + publicInput: PublicInput, + earlierProof: SelfProof, + privateInput: PrivateInput + ): PublicOutput { + earlierProof.verify(); + privateInput.witness + .calculateRoot(Poseidon.hash([privateInput.secret])) + .assertEquals(publicInput.merkleRoot); + return new PublicOutput({ + recursiveHash: Poseidon.hash([ + publicInput.merkleRoot, + earlierProof.publicOutput.recursiveHash + ]) + }); + } } + } }); export default Program; diff --git a/src/plugins/merkleMemberships/common/treeRootStorageContract.ts b/src/plugins/merkleMemberships/common/treeRootStorageContract.ts index d29699b..46fc0ad 100644 --- a/src/plugins/merkleMemberships/common/treeRootStorageContract.ts +++ b/src/plugins/merkleMemberships/common/treeRootStorageContract.ts @@ -1,4 +1,11 @@ -import { Permissions, DeployArgs, Field, SmartContract, State, state} from "o1js"; +import { + Permissions, + DeployArgs, + Field, + SmartContract, + State, + state +} from 'o1js'; export class TreeRootStorageContract extends SmartContract { @state(Field) treeRoot = State(); @@ -9,7 +16,7 @@ export class TreeRootStorageContract extends SmartContract { this.account.permissions.set({ ...Permissions.allImpossible(), editState: Permissions.signature(), - access: Permissions.signature(), + access: Permissions.signature() }); } } diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 6343fd6..7b7f846 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -1,8 +1,7 @@ -import { Experimental, Field, JsonProof, Poseidon } from "o1js"; +import { Experimental, Field, JsonProof, Poseidon } from 'o1js'; import * as O from 'fp-ts/Option'; import z from 'zod'; -import { IMinAuthPlugin, IMinAuthPluginFactory } - from '@lib/plugin/pluginType'; +import { IMinAuthPlugin, IMinAuthPluginFactory } from '@lib/plugin/pluginType'; import { MinaTreesProvider, MinaTreesProviderConfiguration, @@ -11,22 +10,22 @@ import { } from './treeStorage'; import { RequestHandler } from 'express'; import * as ZkProgram from '../common/merkleMembershipsProgram'; -import { pipe } from "fp-ts/lib/function"; +import { pipe } from 'fp-ts/lib/function'; const PoseidonHashSchema = z.bigint(); const publicInputArgsSchema = z.array(PoseidonHashSchema); -export type PublicInputArgs = - z.infer; +export type PublicInputArgs = z.infer; export class MerkleMembershipsPlugin - implements IMinAuthPlugin{ + implements IMinAuthPlugin +{ readonly verificationKey: string; - private readonly storageProvider: TreesProvider + private readonly storageProvider: TreesProvider; customRoutes: Record = { - "/getWitness/:treeRoot/:leafIndex": async (req, resp) => { + '/getWitness/:treeRoot/:leafIndex': async (req, resp) => { if (req.method != 'GET') { resp.status(400); return; @@ -39,7 +38,7 @@ export class MerkleMembershipsPlugin if (tree === undefined) { resp.status(400).json({ - error: "tree not exists" + error: 'tree not exists' }); return; } @@ -48,26 +47,26 @@ export class MerkleMembershipsPlugin if (witness == undefined) { resp.status(400).json({ - error: "leaf not exists" + error: 'leaf not exists' }); return; } resp.status(200).json({ - witness: witness.toJSON(), + witness: witness.toJSON() }); } - } + }; readonly publicInputArgsSchema = publicInputArgsSchema; async verifyAndGetOutput( treeRoots: PublicInputArgs, - serializedProof: JsonProof): Promise { - const proof = - Experimental.ZkProgram - .Proof(ZkProgram.Program) - .fromJSON(serializedProof); + serializedProof: JsonProof + ): Promise { + const proof = Experimental.ZkProgram.Proof(ZkProgram.Program).fromJSON( + serializedProof + ); const computeHash = async () => { let hash: O.Option = O.none; @@ -75,25 +74,26 @@ export class MerkleMembershipsPlugin for (const rawRoot of treeRoots) { const root = Field(rawRoot); const tree = await this.storageProvider.getTree(root); - if (O.isNone(tree)) throw "tree not found"; - hash = - pipe( - hash, - O.fold - ( - () => root, - (current: Field) => Poseidon.hash([current, root])), - O.some - ) - }; + if (O.isNone(tree)) throw 'tree not found'; + hash = pipe( + hash, + O.fold( + () => root, + (current: Field) => Poseidon.hash([current, root]) + ), + O.some + ); + } return hash; - } + }; const expectedHash = O.toUndefined(await computeHash()); - if (expectedHash === undefined || - expectedHash.equals(proof.publicOutput.recursiveHash).not().toBoolean()) - throw "unexpected recursive hash"; + if ( + expectedHash === undefined || + expectedHash.equals(proof.publicOutput.recursiveHash).not().toBoolean() + ) + throw 'unexpected recursive hash'; return expectedHash; } @@ -103,7 +103,9 @@ export class MerkleMembershipsPlugin this.storageProvider = storageProvider; } - static async initialize(cfg: MinaTreesProviderConfiguration): Promise { + static async initialize( + cfg: MinaTreesProviderConfiguration + ): Promise { const { verificationKey } = await ZkProgram.Program.compile(); const storage = await MinaTreesProvider.initialize(cfg); return new MerkleMembershipsPlugin(verificationKey, storage); @@ -112,9 +114,9 @@ export class MerkleMembershipsPlugin static readonly configurationSchema = minaTreesProviderConfigurationSchema; } -MerkleMembershipsPlugin satisfies - IMinAuthPluginFactory< - IMinAuthPlugin, - MinaTreesProviderConfiguration, - PublicInputArgs, - Field>; +MerkleMembershipsPlugin satisfies IMinAuthPluginFactory< + IMinAuthPlugin, + MinaTreesProviderConfiguration, + PublicInputArgs, + Field +>; diff --git a/src/plugins/merkleMemberships/server/treeStorage.ts b/src/plugins/merkleMemberships/server/treeStorage.ts index 26dabcb..67cd9c2 100644 --- a/src/plugins/merkleMemberships/server/treeStorage.ts +++ b/src/plugins/merkleMemberships/server/treeStorage.ts @@ -1,8 +1,8 @@ -import { AccountUpdate, Field, MerkleTree, Mina, PrivateKey } from "o1js"; +import { AccountUpdate, Field, MerkleTree, Mina, PrivateKey } from 'o1js'; import * as O from 'fp-ts/Option'; import * as Program from '../common/merkleMembershipsProgram'; import z from 'zod'; -import fs from "fs/promises" +import fs from 'fs/promises'; import { TreeRootStorageContract } from '../common/treeRootStorageContract'; import * as A from 'fp-ts/Array'; import { Option } from 'fp-ts/Option'; @@ -18,12 +18,14 @@ export class InMemoryStorage implements TreeStorage { occupied: Set = new Set(); merkleTree: MerkleTree = new MerkleTree(Program.TREE_HEIGHT); - async getRoot() { return this.merkleTree.getRoot(); } + async getRoot() { + return this.merkleTree.getRoot(); + } async getWitness(leafIndex: bigint): Promise> { - return this.occupied.has(leafIndex) ? - O.none : - O.some(new Program.TreeWitness(this.merkleTree.getWitness(leafIndex))); + return this.occupied.has(leafIndex) + ? O.none + : O.some(new Program.TreeWitness(this.merkleTree.getWitness(leafIndex))); } async hasLeaf(leafIndex: bigint): Promise { @@ -40,24 +42,23 @@ export class PersistentInMemoryStorage extends InMemoryStorage { readonly file: fs.FileHandle; async persist() { - const storageObj = - Array - .from(this.occupied.values()) - .reduce((acc: Record, idx: bigint) => { - acc[Number(idx)] = - this.merkleTree - .getNode(Program.TREE_HEIGHT, idx) - .toJSON(); - return acc; - } - , {}); + const storageObj = Array.from(this.occupied.values()).reduce( + (acc: Record, idx: bigint) => { + acc[Number(idx)] = this.merkleTree + .getNode(Program.TREE_HEIGHT, idx) + .toJSON(); + return acc; + }, + {} + ); await this.file.write(JSON.stringify(storageObj), 0, 'utf-8'); } private constructor( file: fs.FileHandle, occupied: Set, - merkleTree: MerkleTree) { + merkleTree: MerkleTree + ) { super(); this.file = file; this.occupied = occupied; @@ -67,33 +68,32 @@ export class PersistentInMemoryStorage extends InMemoryStorage { static async initialize(path: string): Promise { const handle = await fs.open(path, 'r+'); const content = await handle.readFile('utf-8'); - const storageObj: Record = - JSON.parse(content); + const storageObj: Record = JSON.parse(content); const occupied: Set = new Set(); const merkleTree: MerkleTree = new MerkleTree(Program.TREE_HEIGHT); - Object - .entries(storageObj) - .forEach(([rawIdx, rawLeaf]) => { - const idx = BigInt(rawIdx); - occupied.add(BigInt(rawIdx)); - merkleTree.setLeaf(idx, Field.fromJSON(rawLeaf)); - }); + Object.entries(storageObj).forEach(([rawIdx, rawLeaf]) => { + const idx = BigInt(rawIdx); + occupied.add(BigInt(rawIdx)); + merkleTree.setLeaf(idx, Field.fromJSON(rawLeaf)); + }); return new PersistentInMemoryStorage(handle, occupied, merkleTree); } } - -export class GenericMinaBlockchainTreeStorage implements TreeStorage { +export class GenericMinaBlockchainTreeStorage + implements TreeStorage +{ private underlyingStorage: T; - private contract: TreeRootStorageContract - private mkTx: (txFn: () => void) => Promise + private contract: TreeRootStorageContract; + private mkTx: (txFn: () => void) => Promise; constructor( storage: T, contract: TreeRootStorageContract, - mkTx: (txFn: () => void) => Promise) { + mkTx: (txFn: () => void) => Promise + ) { this.underlyingStorage = storage; this.contract = contract; this.mkTx = mkTx; @@ -103,16 +103,16 @@ export class GenericMinaBlockchainTreeStorage implements const onChain = await this.contract.treeRoot.fetch(); const offChain = await this.underlyingStorage.getRoot(); - if (!onChain) - throw "tree root storage contract not deployed"; + if (!onChain) throw 'tree root storage contract not deployed'; - if (onChain.equals(offChain).toBoolean()) - return; + if (onChain.equals(offChain).toBoolean()) return; await this.mkTx(() => this.contract.treeRoot.set(offChain)); } - async getRoot() { return this.underlyingStorage.getRoot(); } + async getRoot() { + return this.underlyingStorage.getRoot(); + } async getWitness(leafIdx: bigint) { return this.underlyingStorage.getWitness(leafIdx); @@ -128,13 +128,17 @@ export class GenericMinaBlockchainTreeStorage implements } } -async function initializeGenericMinaBlockchainTreeStorage( +async function initializeGenericMinaBlockchainTreeStorage< + T extends TreeStorage +>( storage: T, contractPrivateKey: PrivateKey, feePayerPrivateKey: PrivateKey ): Promise> { await TreeRootStorageContract.compile(); - const contract = new TreeRootStorageContract(contractPrivateKey.toPublicKey()); + const contract = new TreeRootStorageContract( + contractPrivateKey.toPublicKey() + ); const feePayerPublicKey = feePayerPrivateKey.toPublicKey(); const mkTx = async (txFn: () => void): Promise => { @@ -143,7 +147,11 @@ async function initializeGenericMinaBlockchainTreeStorage await txn.sign([feePayerPrivateKey, contractPrivateKey]).send(); }; - const blockchainStorage = new GenericMinaBlockchainTreeStorage(storage, contract, mkTx); + const blockchainStorage = new GenericMinaBlockchainTreeStorage( + storage, + contract, + mkTx + ); if (contract.account.isNew.get()) { const treeRoot = await storage.getRoot(); @@ -159,17 +167,18 @@ async function initializeGenericMinaBlockchainTreeStorage return blockchainStorage; } -export class MinaBlockchainTreeStorage - extends GenericMinaBlockchainTreeStorage{ +export class MinaBlockchainTreeStorage extends GenericMinaBlockchainTreeStorage { static async initialize( path: string, contractPrivateKey: PrivateKey, - feePayerPrivateKey: PrivateKey) { + feePayerPrivateKey: PrivateKey + ) { const storage = await PersistentInMemoryStorage.initialize(path); return initializeGenericMinaBlockchainTreeStorage( storage, contractPrivateKey, - feePayerPrivateKey); + feePayerPrivateKey + ); } } @@ -177,18 +186,19 @@ export interface TreesProvider { getTree(root: Field): Promise>; } -export const minaTreesProviderConfigurationSchema = - z.object({ - feePayerPrivateKey: z.string().optional(), - trees: z.array(z.object({ +export const minaTreesProviderConfigurationSchema = z.object({ + feePayerPrivateKey: z.string().optional(), + trees: z.array( + z.object({ contractPrivateKey: z.string().optional(), offchainStoragePath: z.string() - })) - }); + }) + ) +}); - -export type MinaTreesProviderConfiguration = - z.infer; +export type MinaTreesProviderConfiguration = z.infer< + typeof minaTreesProviderConfigurationSchema +>; export class MinaTreesProvider implements TreesProvider { readonly treeStorages: Array; @@ -196,8 +206,7 @@ export class MinaTreesProvider implements TreesProvider { async getTree(root: Field) { for (const tree of this.treeStorages) { const thisRoot = await tree.getRoot(); - if (thisRoot.equals(root).toBoolean()) - return O.some(tree); + if (thisRoot.equals(root).toBoolean()) return O.some(tree); } return O.none; } @@ -206,33 +215,36 @@ export class MinaTreesProvider implements TreesProvider { this.treeStorages = treeStorages; } - static async initialize(cfg: MinaTreesProviderConfiguration): - Promise { - const feePayerPrivateKey = cfg.feePayerPrivateKey ? - PrivateKey.fromBase58(cfg.feePayerPrivateKey) : undefined; - - const trees: TreeStorage[] = await - A.reduce - (Promise.resolve([]), - ( - accP: Promise>, - tCfg: { - offchainStoragePath: string; - contractPrivateKey?: string | undefined; - }) => accP.then( - (acc) => - ( - feePayerPrivateKey && tCfg.contractPrivateKey ? - MinaBlockchainTreeStorage.initialize( - tCfg.offchainStoragePath, - PrivateKey.fromBase58(tCfg.contractPrivateKey), - feePayerPrivateKey) : - PersistentInMemoryStorage.initialize(tCfg.offchainStoragePath) - ).then((storage: TreeStorage) => - Promise.resolve(A.append(storage)(acc))) - )) - (cfg.trees) + static async initialize( + cfg: MinaTreesProviderConfiguration + ): Promise { + const feePayerPrivateKey = cfg.feePayerPrivateKey + ? PrivateKey.fromBase58(cfg.feePayerPrivateKey) + : undefined; + + const trees: TreeStorage[] = await A.reduce( + Promise.resolve([]), + ( + accP: Promise>, + tCfg: { + offchainStoragePath: string; + contractPrivateKey?: string | undefined; + } + ) => + accP.then((acc) => + (feePayerPrivateKey && tCfg.contractPrivateKey + ? MinaBlockchainTreeStorage.initialize( + tCfg.offchainStoragePath, + PrivateKey.fromBase58(tCfg.contractPrivateKey), + feePayerPrivateKey + ) + : PersistentInMemoryStorage.initialize(tCfg.offchainStoragePath) + ).then((storage: TreeStorage) => + Promise.resolve(A.append(storage)(acc)) + ) + ) + )(cfg.trees); return new MinaTreesProvider(trees); - }; + } } diff --git a/src/plugins/simplePreimage/client/index.ts b/src/plugins/simplePreimage/client/index.ts index 8d2a2a6..d552fb8 100644 --- a/src/plugins/simplePreimage/client/index.ts +++ b/src/plugins/simplePreimage/client/index.ts @@ -2,23 +2,21 @@ import { Field, JsonProof } from 'o1js'; import { IMinAuthProver } from '@lib/plugin/pluginType'; import ProvePreimageProgram from '../common/hashPreimageProof'; +export class SimplePreimageProver implements IMinAuthProver { + async prove(publicInput: Field, secretInput: Field): Promise { + console.log('simplePreimage proving for', publicInput, secretInput); + const proof = await ProvePreimageProgram.baseCase( + Field(publicInput), + Field(secretInput) + ); + return proof.toJSON(); + } -export class SimplePreimageProver implements IMinAuthProver{ - async prove(publicInput: Field, secretInput: Field): Promise { - console.log('simplePreimage proving for', publicInput, secretInput); - const proof = await ProvePreimageProgram.baseCase( - Field(publicInput), - Field(secretInput), - ); - return proof.toJSON(); - } + async fetchPublicInputs(_: any): Promise { + throw 'not implemented, please query the `/roles` endpoint'; + } - async fetchPublicInputs(_: any): Promise { - throw "not implemented, please query the `/roles` endpoint"; - } - - static async initialize(_: any): Promise { - return new SimplePreimageProver(); - } + static async initialize(_: any): Promise { + return new SimplePreimageProver(); + } } - diff --git a/src/plugins/simplePreimage/common/hashPreimageProof.ts b/src/plugins/simplePreimage/common/hashPreimageProof.ts index fc44586..a31198c 100644 --- a/src/plugins/simplePreimage/common/hashPreimageProof.ts +++ b/src/plugins/simplePreimage/common/hashPreimageProof.ts @@ -1,20 +1,21 @@ import { Field, Experimental, Poseidon } from 'o1js'; export const ProvePreimageProgram = Experimental.ZkProgram({ - publicInput: Field, - publicOutput: Field, + publicInput: Field, + publicOutput: Field, - methods: { - baseCase: { - privateInputs: [Field], - method(publicInput: Field, secretInput: Field) { - Poseidon.hash([secretInput]).assertEquals(publicInput); - return publicInput; - }, - }, - }, + methods: { + baseCase: { + privateInputs: [Field], + method(publicInput: Field, secretInput: Field) { + Poseidon.hash([secretInput]).assertEquals(publicInput); + return publicInput; + } + } + } }); -export const ProvePreimageProofClass = Experimental.ZkProgram.Proof(ProvePreimageProgram); +export const ProvePreimageProofClass = + Experimental.ZkProgram.Proof(ProvePreimageProgram); export default ProvePreimageProgram; diff --git a/src/plugins/simplePreimage/server/index.ts b/src/plugins/simplePreimage/server/index.ts index 16ba227..fcdad2d 100644 --- a/src/plugins/simplePreimage/server/index.ts +++ b/src/plugins/simplePreimage/server/index.ts @@ -1,59 +1,70 @@ import { JsonProof } from 'o1js'; import { IMinAuthPlugin, IMinAuthPluginFactory } from '@lib/plugin/pluginType'; -import ProvePreimageProgram, { ProvePreimageProofClass } from '../common/hashPreimageProof'; +import ProvePreimageProgram, { + ProvePreimageProofClass +} from '../common/hashPreimageProof'; import { RequestHandler } from 'express'; import { z } from 'zod'; - const roleMapping: Record = { - '7555220006856562833147743033256142154591945963958408607501861037584894828141': - 'admin', - '21565680844461314807147611702860246336805372493508489110556896454939225549736': - 'member', + '7555220006856562833147743033256142154591945963958408607501861037584894828141': + 'admin', + '21565680844461314807147611702860246336805372493508489110556896454939225549736': + 'member' }; -export class SimplePreimagePlugin implements IMinAuthPlugin{ - readonly verificationKey: string; - private readonly roles: Record; +export class SimplePreimagePlugin implements IMinAuthPlugin { + readonly verificationKey: string; + private readonly roles: Record; - async verifyAndGetOutput(_: any, serializedProof: JsonProof): - Promise { - const proof = ProvePreimageProofClass.fromJSON(serializedProof); - const role = roleMapping[proof.publicOutput.toString()]; - return role; - }; + async verifyAndGetOutput( + _: any, + serializedProof: JsonProof + ): Promise { + const proof = ProvePreimageProofClass.fromJSON(serializedProof); + const role = roleMapping[proof.publicOutput.toString()]; + return role; + } - publicInputArgsSchema: z.ZodType = z.any(); + publicInputArgsSchema: z.ZodType = z.any(); - customRoutes: Record = { - "/roles": (_, res) => { - res.status(200).json(this.roles); - } + customRoutes: Record = { + '/roles': (_, res) => { + res.status(200).json(this.roles); } + }; - // checkOutputValidity(output: string): Promise { - // return Promise.resolve(output in this.roles); - // } + // checkOutputValidity(output: string): Promise { + // return Promise.resolve(output in this.roles); + // } - constructor(verificationKey: string, roles: Record) { - this.verificationKey = verificationKey; - this.roles = roles; - } + constructor(verificationKey: string, roles: Record) { + this.verificationKey = verificationKey; + this.roles = roles; + } + + static async initialize(configuration: { + roles: Record; + }): Promise { + const { verificationKey } = await ProvePreimageProgram.compile(); + return new SimplePreimagePlugin(verificationKey, configuration.roles); + } - static async initialize(configuration: { roles: Record }) - : Promise { - const { verificationKey } = await ProvePreimageProgram.compile(); - return new SimplePreimagePlugin(verificationKey, configuration.roles); - }; - - static readonly configurationSchema: z.ZodType<{ roles: Record }> = - z.object({ - roles: z.record( - // FIXME: the key should be a valid poseidon hash - z.string(), - z.string()) - }); + static readonly configurationSchema: z.ZodType<{ + roles: Record; + }> = z.object({ + roles: z.record( + // FIXME: the key should be a valid poseidon hash + z.string(), + z.string() + ) + }); } // sanity check -SimplePreimagePlugin satisfies IMinAuthPluginFactory }, any, string>; +SimplePreimagePlugin satisfies IMinAuthPluginFactory< + SimplePreimagePlugin, + { roles: Record }, + any, + string +>; diff --git a/src/someServer/apiServer.ts b/src/someServer/apiServer.ts index 5bfe477..d147c5b 100644 --- a/src/someServer/apiServer.ts +++ b/src/someServer/apiServer.ts @@ -4,18 +4,18 @@ import bodyParser from 'body-parser'; import passport from 'passport'; import passportJWT from 'passport-jwt'; -import MinAuthStrategy from '@lib/server/minauthStrategy' +import MinAuthStrategy from '@lib/server/minauthStrategy'; const SECRET_KEY: string = 'YOUR_SECRET_KEY'; const app = express(); interface StoredUser { - id: number; - name: string; - role: string; - token: string; - refreshToken: string; + id: number; + name: string; + role: string; + token: string; + refreshToken: string; } const refreshTokenStore: Record = {}; @@ -25,21 +25,21 @@ const JWTStrategy = passportJWT.Strategy; const ExtractJWT = passportJWT.ExtractJwt; const jwtOptions = { - jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), - secretOrKey: SECRET_KEY, + jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), + secretOrKey: SECRET_KEY }; passport.use( - new JWTStrategy(jwtOptions, (jwtPayload, done) => { - console.log('JWT payload received:', jwtPayload); - - if (jwtPayload) { - return done(null, jwtPayload); - } else { - console.log('JWT verification failed'); - return done(null, false); - } - }), + new JWTStrategy(jwtOptions, (jwtPayload, done) => { + console.log('JWT payload received:', jwtPayload); + + if (jwtPayload) { + return done(null, jwtPayload); + } else { + console.log('JWT verification failed'); + return done(null, false); + } + }) ); // with MinAuthStrategy @@ -52,59 +52,59 @@ app.use(bodyParser.json()); // Debug Middleware to log errors app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { - console.error('Error:', err.stack); - res.status(500).send('Something broke!'); + console.error('Error:', err.stack); + res.status(500).send('Something broke!'); }); app.get('/', (_req, res) => { - res.send('Hello, World!'); + res.send('Hello, World!'); }); const PORT: number = 3000; app.listen(PORT, () => { - console.log(`Server is running on http://localhost:${PORT}`); + console.log(`Server is running on http://localhost:${PORT}`); }); app.post( - '/login', - passport.authenticate(MinAuthStrategy.name, { session: false }), - (req: Request, res: Response) => { - const user = req.user as StoredUser; - - // Store the refresh token - refreshTokenStore[user.refreshToken] = user; - - res.json({ - message: `Hello, ${user.name}. You have the role: ${user.role}`, - token: user.token, - refreshToken: user.refreshToken, - }); - }, + '/login', + passport.authenticate(MinAuthStrategy.name, { session: false }), + (req: Request, res: Response) => { + const user = req.user as StoredUser; + + // Store the refresh token + refreshTokenStore[user.refreshToken] = user; + + res.json({ + message: `Hello, ${user.name}. You have the role: ${user.role}`, + token: user.token, + refreshToken: user.refreshToken + }); + } ); app.post('/token', (req: Request, res: Response) => { - const refreshToken = req.body.refreshToken; - - if (refreshToken && refreshToken in refreshTokenStore) { - const user = refreshTokenStore[refreshToken]; - const jwtPayload = { - sub: user.id, - name: user.name, - role: user.role, - }; - const token = jwt.sign(jwtPayload, SECRET_KEY, { expiresIn: '1h' }); - - res.json({ token }); - } else { - res.status(401).json({ message: 'Invalid refresh token' }); - } + const refreshToken = req.body.refreshToken; + + if (refreshToken && refreshToken in refreshTokenStore) { + const user = refreshTokenStore[refreshToken]; + const jwtPayload = { + sub: user.id, + name: user.name, + role: user.role + }; + const token = jwt.sign(jwtPayload, SECRET_KEY, { expiresIn: '1h' }); + + res.json({ token }); + } else { + res.status(401).json({ message: 'Invalid refresh token' }); + } }); app.get( - '/protected', - passport.authenticate('jwt', { session: false }), - (req: Request, res: Response) => { - const user = req.user as StoredUser; - res.send(`Hello, ${user.name}. You are accessing a protected route.`); - }, + '/protected', + passport.authenticate('jwt', { session: false }), + (req: Request, res: Response) => { + const user = req.user as StoredUser; + res.send(`Hello, ${user.name}. You are accessing a protected route.`); + } ); From cab2edaa47b0bb826a79e041bb885f08f5f5345e Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Mon, 9 Oct 2023 21:02:11 +0800 Subject: [PATCH 15/21] setup eslint and fix errors --- .eslintignore | 2 + .eslintrc | 18 ++++ package-lock.json | 110 ++++++++++----------- package.json | 9 +- src/library/server/minauthStrategy.ts | 1 + src/library/tools/pluginServer/config.ts | 9 +- src/library/tools/pluginServer/index.ts | 6 +- src/plugins/simplePreimage/client/index.ts | 10 +- src/plugins/simplePreimage/server/index.ts | 8 +- src/someServer/apiServer.ts | 4 +- 10 files changed, 104 insertions(+), 73 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..e6d4fd0 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,18 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "prettier" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "rules": { + // "no-console": 1, // Means warning + "prettier/prettier": 2 // Means error + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8799c89..84712d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,9 +32,9 @@ "devDependencies": { "@types/axios": "^0.14.0", "@types/express": "^4.17.17", - "@typescript-eslint/eslint-plugin": "^6.7.0", - "@typescript-eslint/parser": "^6.7.0", - "eslint": "^8.49.0", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@typescript-eslint/parser": "^6.7.4", + "eslint": "^8.51.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "nodemon": "^3.0.1", @@ -138,9 +138,9 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -422,9 +422,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, "node_modules/@types/send": { @@ -447,16 +447,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.0.tgz", - "integrity": "sha512-gUqtknHm0TDs1LhY12K2NA3Rmlmp88jK9Tx8vGZMfHeNMLE3GH2e9TRub+y+SOjuYgtOmok+wt1AyDPZqxbNag==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", + "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.0", - "@typescript-eslint/type-utils": "6.7.0", - "@typescript-eslint/utils": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0", + "@typescript-eslint/scope-manager": "6.7.4", + "@typescript-eslint/type-utils": "6.7.4", + "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/visitor-keys": "6.7.4", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -505,15 +505,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.0.tgz", - "integrity": "sha512-jZKYwqNpNm5kzPVP5z1JXAuxjtl2uG+5NpaMocFPTNC2EdYIgbXIPImObOkhbONxtFTTdoZstLZefbaK+wXZng==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", + "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.0", - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/typescript-estree": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0", + "@typescript-eslint/scope-manager": "6.7.4", + "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/typescript-estree": "6.7.4", + "@typescript-eslint/visitor-keys": "6.7.4", "debug": "^4.3.4" }, "engines": { @@ -556,13 +556,13 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.0.tgz", - "integrity": "sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", + "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0" + "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/visitor-keys": "6.7.4" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -573,13 +573,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.0.tgz", - "integrity": "sha512-f/QabJgDAlpSz3qduCyQT0Fw7hHpmhOzY/Rv6zO3yO+HVIdPfIWhrQoAyG+uZVtWAIS85zAyzgAFfyEr+MgBpg==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", + "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.0", - "@typescript-eslint/utils": "6.7.0", + "@typescript-eslint/typescript-estree": "6.7.4", + "@typescript-eslint/utils": "6.7.4", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -623,9 +623,9 @@ "dev": true }, "node_modules/@typescript-eslint/types": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.0.tgz", - "integrity": "sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", + "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -636,13 +636,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.0.tgz", - "integrity": "sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", + "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0", + "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/visitor-keys": "6.7.4", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -686,17 +686,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.0.tgz", - "integrity": "sha512-MfCq3cM0vh2slSikQYqK2Gq52gvOhe57vD2RM3V4gQRZYX4rDPnKLu5p6cm89+LJiGlwEXU8hkYxhqqEC/V3qA==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", + "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.0", - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/typescript-estree": "6.7.0", + "@typescript-eslint/scope-manager": "6.7.4", + "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/typescript-estree": "6.7.4", "semver": "^7.5.4" }, "engines": { @@ -711,12 +711,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.0.tgz", - "integrity": "sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", + "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/types": "6.7.4", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1365,15 +1365,15 @@ } }, "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", + "@eslint/js": "8.51.0", "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", diff --git a/package.json b/package.json index bbb8888..a78cc7a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "serve-api": "node ./dist/src/someServer/apiServer.js", "serve-plugin-server": "node ./dist/src/library/tools/pluginServer.js", "run-client": "node ./dist/src/headlessClient/client.js", - "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write" + "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write", + "lint": "eslint . --ext .ts" }, "keywords": [], "author": "", @@ -40,9 +41,9 @@ "devDependencies": { "@types/axios": "^0.14.0", "@types/express": "^4.17.17", - "@typescript-eslint/eslint-plugin": "^6.7.0", - "@typescript-eslint/parser": "^6.7.0", - "eslint": "^8.49.0", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@typescript-eslint/parser": "^6.7.4", + "eslint": "^8.51.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "nodemon": "^3.0.1", diff --git a/src/library/server/minauthStrategy.ts b/src/library/server/minauthStrategy.ts index eb22397..4df159c 100644 --- a/src/library/server/minauthStrategy.ts +++ b/src/library/server/minauthStrategy.ts @@ -57,6 +57,7 @@ class MinAuthStrategy extends Strategy { super(); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any async authenticate(req: Request, _options?: any): Promise { try { console.log('authenticating (strategy) with req:', req.body); diff --git a/src/library/tools/pluginServer/config.ts b/src/library/tools/pluginServer/config.ts index fed352d..c141c9e 100644 --- a/src/library/tools/pluginServer/config.ts +++ b/src/library/tools/pluginServer/config.ts @@ -12,7 +12,12 @@ import { MerkleMembershipsPlugin } from '@plugins/merkleMemberships/server'; */ export const untypedPlugins: Record< string, - IMinAuthPluginFactory, any, any, any> + IMinAuthPluginFactory< + IMinAuthPlugin, + unknown, + unknown, + unknown + > > = { SimplePreimagePlugin: SimplePreimagePlugin, MerkleMembershipsPlugin: MerkleMembershipsPlugin @@ -66,6 +71,6 @@ export function readConfigurations(): ServerConfigurations { return defaultConfiguration; } const configFileContent = fs.readFileSync(configFile, 'utf8'); - const untypedConfig: any = yaml.parse(configFileContent); + const untypedConfig: unknown = yaml.parse(configFileContent); return serverConfigurationsSchema.parse(untypedConfig); } diff --git a/src/library/tools/pluginServer/index.ts b/src/library/tools/pluginServer/index.ts index 70a5417..de8d34f 100644 --- a/src/library/tools/pluginServer/index.ts +++ b/src/library/tools/pluginServer/index.ts @@ -13,7 +13,7 @@ console.log('configuration loaded', configurations); * @returns A record of plugin instances. */ async function initializePlugins(): Promise< - Record> + Record> > { console.log('initializing plugins'); return Object.entries(configurations.plugins).reduce( @@ -33,12 +33,12 @@ initializePlugins() // The type of `POST /verifyProof` requests' body. interface VerifyProofData { plugin: string; - publicInputArgs: any; + publicInputArgs: unknown; proof: JsonProof; } // Use the appropriate plugin to verify the proof and return the output. - async function verifyProof(data: VerifyProofData): Promise { + async function verifyProof(data: VerifyProofData): Promise { const pluginName = data.plugin; console.info(`verifying proof using plugin ${pluginName}`); const pluginInstance = activePlugins[pluginName]; diff --git a/src/plugins/simplePreimage/client/index.ts b/src/plugins/simplePreimage/client/index.ts index d552fb8..d0e2651 100644 --- a/src/plugins/simplePreimage/client/index.ts +++ b/src/plugins/simplePreimage/client/index.ts @@ -2,7 +2,9 @@ import { Field, JsonProof } from 'o1js'; import { IMinAuthProver } from '@lib/plugin/pluginType'; import ProvePreimageProgram from '../common/hashPreimageProof'; -export class SimplePreimageProver implements IMinAuthProver { +export class SimplePreimageProver + implements IMinAuthProver +{ async prove(publicInput: Field, secretInput: Field): Promise { console.log('simplePreimage proving for', publicInput, secretInput); const proof = await ProvePreimageProgram.baseCase( @@ -12,11 +14,13 @@ export class SimplePreimageProver implements IMinAuthProver { return proof.toJSON(); } - async fetchPublicInputs(_: any): Promise { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async fetchPublicInputs(_: unknown): Promise { throw 'not implemented, please query the `/roles` endpoint'; } - static async initialize(_: any): Promise { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + static async initialize(_: unknown): Promise { return new SimplePreimageProver(); } } diff --git a/src/plugins/simplePreimage/server/index.ts b/src/plugins/simplePreimage/server/index.ts index fcdad2d..e680af8 100644 --- a/src/plugins/simplePreimage/server/index.ts +++ b/src/plugins/simplePreimage/server/index.ts @@ -13,12 +13,12 @@ const roleMapping: Record = { 'member' }; -export class SimplePreimagePlugin implements IMinAuthPlugin { +export class SimplePreimagePlugin implements IMinAuthPlugin { readonly verificationKey: string; private readonly roles: Record; async verifyAndGetOutput( - _: any, + _: unknown, serializedProof: JsonProof ): Promise { const proof = ProvePreimageProofClass.fromJSON(serializedProof); @@ -26,7 +26,7 @@ export class SimplePreimagePlugin implements IMinAuthPlugin { return role; } - publicInputArgsSchema: z.ZodType = z.any(); + publicInputArgsSchema: z.ZodType = z.any(); customRoutes: Record = { '/roles': (_, res) => { @@ -65,6 +65,6 @@ export class SimplePreimagePlugin implements IMinAuthPlugin { SimplePreimagePlugin satisfies IMinAuthPluginFactory< SimplePreimagePlugin, { roles: Record }, - any, + unknown, string >; diff --git a/src/someServer/apiServer.ts b/src/someServer/apiServer.ts index d147c5b..f54407e 100644 --- a/src/someServer/apiServer.ts +++ b/src/someServer/apiServer.ts @@ -1,4 +1,4 @@ -import express, { Request, Response, NextFunction } from 'express'; +import express, { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; import bodyParser from 'body-parser'; import passport from 'passport'; @@ -51,7 +51,7 @@ app.use(passport.initialize()); app.use(bodyParser.json()); // Debug Middleware to log errors -app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { +app.use((err: Error, _req: Request, res: Response) => { console.error('Error:', err.stack); res.status(500).send('Something broke!'); }); From ce4062122c79e014116a57a9a250018dcda2719d Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Mon, 9 Oct 2023 21:25:04 +0800 Subject: [PATCH 16/21] setup pre-commit hooks --- .gitignore | 3 ++ flake.lock | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++- flake.nix | 28 ++++++++++-- 3 files changed, 150 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index c6bba59..29ec5fc 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,6 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# nix stuff +.pre-commit-config.yaml diff --git a/flake.lock b/flake.lock index 4489ef9..bb98fe4 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,21 @@ { "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" @@ -18,6 +34,42 @@ "type": "github" } }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": ["pre-commit-hooks-nix", "nixpkgs"] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1695360818, @@ -52,10 +104,80 @@ "type": "github" } }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1685801374, + "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1689261696, + "narHash": "sha256-LzfUtFs9MQRvIoQ3MfgSuipBVMXslMPH/vZ+nM40LkA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "df1eee2aa65052a18121ed4971081576b25d6b5c", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks-nix": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": "nixpkgs_2", + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1696846637, + "narHash": "sha256-0hv4kbXxci2+pxhuXlVgftj/Jq79VSmtAyvfabCCtYk=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "42e1b6095ef80a51f79595d9951eb38e91c4e6ca", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { "flake-parts": "flake-parts", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "pre-commit-hooks-nix": "pre-commit-hooks-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 063129c..5cdbeca 100644 --- a/flake.nix +++ b/flake.nix @@ -4,20 +4,40 @@ inputs = { flake-parts.url = "github:hercules-ci/flake-parts"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + pre-commit-hooks-nix.url = "github:cachix/pre-commit-hooks.nix"; }; - outputs = inputs@{ flake-parts, ... }: - flake-parts.lib.mkFlake { inherit inputs; } { + outputs = inputs @ { + flake-parts, + pre-commit-hooks-nix, + ... + }: + flake-parts.lib.mkFlake {inherit inputs;} { imports = [ + inputs.pre-commit-hooks-nix.flakeModule ]; - systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ]; - perSystem = { config, self', inputs', pkgs, system, ... }: { + systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin"]; + perSystem = { + config, + self', + inputs', + pkgs, + system, + ... + }: { + pre-commit.settings.hooks = { + eslint.enable = true; + prettier.enable = true; + alejandra.enable = true; + }; devShells.default = pkgs.mkShell { packages = with pkgs; [ nodejs nodePackages.typescript nodePackages.typescript-language-server + config.pre-commit.settings.package ]; + shellHook = config.pre-commit.installationScript; }; }; flake = { From a47a0da3c3e99891e99b0844c69ae4433d14fb2f Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Mon, 9 Oct 2023 21:50:48 +0800 Subject: [PATCH 17/21] setup ci --- .gitignore | 2 + flake.lock | 21 +- flake.nix | 61 +- nix/default.nix | 22 + nix/node-env.nix | 743 ++++++++++ nix/node-package.nix | 3319 ++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 2685 +++++++++++++++++++++++++++++++++- 7 files changed, 6846 insertions(+), 7 deletions(-) create mode 100644 nix/default.nix create mode 100644 nix/node-env.nix create mode 100644 nix/node-package.nix diff --git a/.gitignore b/.gitignore index 29ec5fc..dabcdff 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,5 @@ dist # nix stuff .pre-commit-config.yaml +result +result-* \ No newline at end of file diff --git a/flake.lock b/flake.lock index bb98fe4..5931a86 100644 --- a/flake.lock +++ b/flake.lock @@ -53,6 +53,24 @@ } }, "gitignore": { + "inputs": { + "nixpkgs": ["nixpkgs"] + }, + "locked": { + "lastModified": 1694102001, + "narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "gitignore_2": { "inputs": { "nixpkgs": ["pre-commit-hooks-nix", "nixpkgs"] }, @@ -140,7 +158,7 @@ "inputs": { "flake-compat": "flake-compat", "flake-utils": "flake-utils", - "gitignore": "gitignore", + "gitignore": "gitignore_2", "nixpkgs": "nixpkgs_2", "nixpkgs-stable": "nixpkgs-stable" }, @@ -161,6 +179,7 @@ "root": { "inputs": { "flake-parts": "flake-parts", + "gitignore": "gitignore", "nixpkgs": "nixpkgs", "pre-commit-hooks-nix": "pre-commit-hooks-nix" } diff --git a/flake.nix b/flake.nix index 5cdbeca..f587ff7 100644 --- a/flake.nix +++ b/flake.nix @@ -5,18 +5,28 @@ flake-parts.url = "github:hercules-ci/flake-parts"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; pre-commit-hooks-nix.url = "github:cachix/pre-commit-hooks.nix"; + gitignore = { + url = "github:hercules-ci/gitignore.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = inputs @ { flake-parts, pre-commit-hooks-nix, + gitignore, ... }: flake-parts.lib.mkFlake {inherit inputs;} { imports = [ inputs.pre-commit-hooks-nix.flakeModule ]; - systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin"]; + systems = [ + "x86_64-linux" + "aarch64-linux" + "aarch64-darwin" + "x86_64-darwin" + ]; perSystem = { config, self', @@ -24,21 +34,62 @@ pkgs, system, ... - }: { + }: let + nodeMajorVersion = 18; + runNode2Nix = pkgs.writeShellScriptBin "runNode2Nix" '' + ${pkgs.node2nix}/bin/node2nix \ + -${builtins.toString nodeMajorVersion} \ + --input package.json \ + --lock package-lock.json \ + --node-env ./nix/node-env.nix \ + --composition ./nix/default.nix \ + --output ./nix/node-package.nix \ + --development \ + --include-peer-dependencies + ''; + nodejs = pkgs."nodejs-${builtins.toString nodeMajorVersion}_x"; + node2nixOutput = import ./nix {inherit pkgs nodejs system;}; + nodeDependencies = node2nixOutput.nodeDependencies; + minAuth = pkgs.stdenv.mkDerivation { + name = "MinAuth"; + version = "0.1.0"; + src = gitignore.lib.gitignoreSource ./.; + buildInputs = [nodejs]; + buildPhase = '' + runHook preBuild + ln -sf ${nodeDependencies}/lib/node_modules ./node_modules + export PATH="${nodeDependencies}/bin:$PATH" + npm run build + runHook postBuild + ''; + installPhase = '' + runHook preInstall + mkdir -p $out + cp package.json $out/package.json + cp -r dist $out/dist + ln -sf ${nodeDependencies}/lib/node_modules $out/node_modules + runHook postInstall + ''; + }; + in { pre-commit.settings.hooks = { eslint.enable = true; prettier.enable = true; alejandra.enable = true; }; devShells.default = pkgs.mkShell { - packages = with pkgs; [ + packages = [ nodejs - nodePackages.typescript - nodePackages.typescript-language-server config.pre-commit.settings.package + runNode2Nix ]; shellHook = config.pre-commit.installationScript; }; + checks.default = self'.checks.pre-commit; + packages = { + inherit minAuth; + default = minAuth; + }; }; flake = { }; diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..7e35ec8 --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,22 @@ +# This file has been generated by node2nix 1.11.1. Do not edit! +{ + pkgs ? + import { + inherit system; + }, + system ? builtins.currentSystem, + nodejs ? pkgs."nodejs_18", +}: let + nodeEnv = import ./node-env.nix { + inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; + inherit pkgs nodejs; + libtool = + if pkgs.stdenv.isDarwin + then pkgs.darwin.cctools + else null; + }; +in + import ./node-package.nix { + inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; + inherit nodeEnv; + } diff --git a/nix/node-env.nix b/nix/node-env.nix new file mode 100644 index 0000000..3b829ff --- /dev/null +++ b/nix/node-env.nix @@ -0,0 +1,743 @@ +# This file originates from node2nix +{ + lib, + stdenv, + nodejs, + python2, + pkgs, + libtool, + runCommand, + writeTextFile, + writeShellScript, +}: let + # Workaround to cope with utillinux in Nixpkgs 20.09 and util-linux in Nixpkgs master + utillinux = + if pkgs ? utillinux + then pkgs.utillinux + else pkgs.util-linux; + + python = + if nodejs ? python + then nodejs.python + else python2; + + # Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise + tarWrapper = runCommand "tarWrapper" {} '' + mkdir -p $out/bin + + cat > $out/bin/tar <> $out/nix-support/hydra-build-products + ''; + }; + + # Common shell logic + installPackage = writeShellScript "install-package" '' + installPackage() { + local packageName=$1 src=$2 + + local strippedName + + local DIR=$PWD + cd $TMPDIR + + unpackFile $src + + # Make the base dir in which the target dependency resides first + mkdir -p "$(dirname "$DIR/$packageName")" + + if [ -f "$src" ] + then + # Figure out what directory has been unpacked + packageDir="$(find . -maxdepth 1 -type d | tail -1)" + + # Restore write permissions to make building work + find "$packageDir" -type d -exec chmod u+x {} \; + chmod -R u+w "$packageDir" + + # Move the extracted tarball into the output folder + mv "$packageDir" "$DIR/$packageName" + elif [ -d "$src" ] + then + # Get a stripped name (without hash) of the source directory. + # On old nixpkgs it's already set internally. + if [ -z "$strippedName" ] + then + strippedName="$(stripHash $src)" + fi + + # Restore write permissions to make building work + chmod -R u+w "$strippedName" + + # Move the extracted directory into the output folder + mv "$strippedName" "$DIR/$packageName" + fi + + # Change to the package directory to install dependencies + cd "$DIR/$packageName" + } + ''; + + # Bundle the dependencies of the package + # + # Only include dependencies if they don't exist. They may also be bundled in the package. + includeDependencies = {dependencies}: + lib.optionalString (dependencies != []) ( + '' + mkdir -p node_modules + cd node_modules + '' + + (lib.concatMapStrings ( + dependency: '' + if [ ! -e "${dependency.packageName}" ]; then + ${composePackage dependency} + fi + '' + ) + dependencies) + + '' + cd .. + '' + ); + + # Recursively composes the dependencies of a package + composePackage = { + name, + packageName, + src, + dependencies ? [], + ... + } @ args: + builtins.addErrorContext "while evaluating node package '${packageName}'" '' + installPackage "${packageName}" "${src}" + ${includeDependencies {inherit dependencies;}} + cd .. + ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} + ''; + + pinpointDependencies = { + dependencies, + production, + }: let + pinpointDependenciesFromPackageJSON = writeTextFile { + name = "pinpointDependencies.js"; + text = '' + var fs = require('fs'); + var path = require('path'); + + function resolveDependencyVersion(location, name) { + if(location == process.env['NIX_STORE']) { + return null; + } else { + var dependencyPackageJSON = path.join(location, "node_modules", name, "package.json"); + + if(fs.existsSync(dependencyPackageJSON)) { + var dependencyPackageObj = JSON.parse(fs.readFileSync(dependencyPackageJSON)); + + if(dependencyPackageObj.name == name) { + return dependencyPackageObj.version; + } + } else { + return resolveDependencyVersion(path.resolve(location, ".."), name); + } + } + } + + function replaceDependencies(dependencies) { + if(typeof dependencies == "object" && dependencies !== null) { + for(var dependency in dependencies) { + var resolvedVersion = resolveDependencyVersion(process.cwd(), dependency); + + if(resolvedVersion === null) { + process.stderr.write("WARNING: cannot pinpoint dependency: "+dependency+", context: "+process.cwd()+"\n"); + } else { + dependencies[dependency] = resolvedVersion; + } + } + } + } + + /* Read the package.json configuration */ + var packageObj = JSON.parse(fs.readFileSync('./package.json')); + + /* Pinpoint all dependencies */ + replaceDependencies(packageObj.dependencies); + if(process.argv[2] == "development") { + replaceDependencies(packageObj.devDependencies); + } + else { + packageObj.devDependencies = {}; + } + replaceDependencies(packageObj.optionalDependencies); + replaceDependencies(packageObj.peerDependencies); + + /* Write the fixed package.json file */ + fs.writeFileSync("package.json", JSON.stringify(packageObj, null, 2)); + ''; + }; + in '' + node ${pinpointDependenciesFromPackageJSON} ${ + if production + then "production" + else "development" + } + + ${lib.optionalString (dependencies != []) + '' + if [ -d node_modules ] + then + cd node_modules + ${lib.concatMapStrings (dependency: pinpointDependenciesOfPackage dependency) dependencies} + cd .. + fi + ''} + ''; + + # Recursively traverses all dependencies of a package and pinpoints all + # dependencies in the package.json file to the versions that are actually + # being used. + + pinpointDependenciesOfPackage = { + packageName, + dependencies ? [], + production ? true, + ... + } @ args: '' + if [ -d "${packageName}" ] + then + cd "${packageName}" + ${pinpointDependencies {inherit dependencies production;}} + cd .. + ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} + fi + ''; + + # Extract the Node.js source code which is used to compile packages with + # native bindings + nodeSources = runCommand "node-sources" {} '' + tar --no-same-owner --no-same-permissions -xf ${nodejs.src} + mv node-* $out + ''; + + # Script that adds _integrity fields to all package.json files to prevent NPM from consulting the cache (that is empty) + addIntegrityFieldsScript = writeTextFile { + name = "addintegrityfields.js"; + text = '' + var fs = require('fs'); + var path = require('path'); + + function augmentDependencies(baseDir, dependencies) { + for(var dependencyName in dependencies) { + var dependency = dependencies[dependencyName]; + + // Open package.json and augment metadata fields + var packageJSONDir = path.join(baseDir, "node_modules", dependencyName); + var packageJSONPath = path.join(packageJSONDir, "package.json"); + + if(fs.existsSync(packageJSONPath)) { // Only augment packages that exist. Sometimes we may have production installs in which development dependencies can be ignored + console.log("Adding metadata fields to: "+packageJSONPath); + var packageObj = JSON.parse(fs.readFileSync(packageJSONPath)); + + if(dependency.integrity) { + packageObj["_integrity"] = dependency.integrity; + } else { + packageObj["_integrity"] = "sha1-000000000000000000000000000="; // When no _integrity string has been provided (e.g. by Git dependencies), add a dummy one. It does not seem to harm and it bypasses downloads. + } + + if(dependency.resolved) { + packageObj["_resolved"] = dependency.resolved; // Adopt the resolved property if one has been provided + } else { + packageObj["_resolved"] = dependency.version; // Set the resolved version to the version identifier. This prevents NPM from cloning Git repositories. + } + + if(dependency.from !== undefined) { // Adopt from property if one has been provided + packageObj["_from"] = dependency.from; + } + + fs.writeFileSync(packageJSONPath, JSON.stringify(packageObj, null, 2)); + } + + // Augment transitive dependencies + if(dependency.dependencies !== undefined) { + augmentDependencies(packageJSONDir, dependency.dependencies); + } + } + } + + if(fs.existsSync("./package-lock.json")) { + var packageLock = JSON.parse(fs.readFileSync("./package-lock.json")); + + if(![1, 2].includes(packageLock.lockfileVersion)) { + process.stderr.write("Sorry, I only understand lock file versions 1 and 2!\n"); + process.exit(1); + } + + if(packageLock.dependencies !== undefined) { + augmentDependencies(".", packageLock.dependencies); + } + } + ''; + }; + + # Reconstructs a package-lock file from the node_modules/ folder structure and package.json files with dummy sha1 hashes + reconstructPackageLock = writeTextFile { + name = "reconstructpackagelock.js"; + text = '' + var fs = require('fs'); + var path = require('path'); + + var packageObj = JSON.parse(fs.readFileSync("package.json")); + + var lockObj = { + name: packageObj.name, + version: packageObj.version, + lockfileVersion: 2, + requires: true, + packages: { + "": { + name: packageObj.name, + version: packageObj.version, + license: packageObj.license, + bin: packageObj.bin, + dependencies: packageObj.dependencies, + engines: packageObj.engines, + optionalDependencies: packageObj.optionalDependencies + } + }, + dependencies: {} + }; + + function augmentPackageJSON(filePath, packages, dependencies) { + var packageJSON = path.join(filePath, "package.json"); + if(fs.existsSync(packageJSON)) { + var packageObj = JSON.parse(fs.readFileSync(packageJSON)); + packages[filePath] = { + version: packageObj.version, + integrity: "sha1-000000000000000000000000000=", + dependencies: packageObj.dependencies, + engines: packageObj.engines, + optionalDependencies: packageObj.optionalDependencies + }; + dependencies[packageObj.name] = { + version: packageObj.version, + integrity: "sha1-000000000000000000000000000=", + dependencies: {} + }; + processDependencies(path.join(filePath, "node_modules"), packages, dependencies[packageObj.name].dependencies); + } + } + + function processDependencies(dir, packages, dependencies) { + if(fs.existsSync(dir)) { + var files = fs.readdirSync(dir); + + files.forEach(function(entry) { + var filePath = path.join(dir, entry); + var stats = fs.statSync(filePath); + + if(stats.isDirectory()) { + if(entry.substr(0, 1) == "@") { + // When we encounter a namespace folder, augment all packages belonging to the scope + var pkgFiles = fs.readdirSync(filePath); + + pkgFiles.forEach(function(entry) { + if(stats.isDirectory()) { + var pkgFilePath = path.join(filePath, entry); + augmentPackageJSON(pkgFilePath, packages, dependencies); + } + }); + } else { + augmentPackageJSON(filePath, packages, dependencies); + } + } + }); + } + } + + processDependencies("node_modules", lockObj.packages, lockObj.dependencies); + + fs.writeFileSync("package-lock.json", JSON.stringify(lockObj, null, 2)); + ''; + }; + + # Script that links bins defined in package.json to the node_modules bin directory + # NPM does not do this for top-level packages itself anymore as of v7 + linkBinsScript = writeTextFile { + name = "linkbins.js"; + text = '' + var fs = require('fs'); + var path = require('path'); + + var packageObj = JSON.parse(fs.readFileSync("package.json")); + + var nodeModules = Array(packageObj.name.split("/").length).fill("..").join(path.sep); + + if(packageObj.bin !== undefined) { + fs.mkdirSync(path.join(nodeModules, ".bin")) + + if(typeof packageObj.bin == "object") { + Object.keys(packageObj.bin).forEach(function(exe) { + if(fs.existsSync(packageObj.bin[exe])) { + console.log("linking bin '" + exe + "'"); + fs.symlinkSync( + path.join("..", packageObj.name, packageObj.bin[exe]), + path.join(nodeModules, ".bin", exe) + ); + } + else { + console.log("skipping non-existent bin '" + exe + "'"); + } + }) + } + else { + if(fs.existsSync(packageObj.bin)) { + console.log("linking bin '" + packageObj.bin + "'"); + fs.symlinkSync( + path.join("..", packageObj.name, packageObj.bin), + path.join(nodeModules, ".bin", packageObj.name.split("/").pop()) + ); + } + else { + console.log("skipping non-existent bin '" + packageObj.bin + "'"); + } + } + } + else if(packageObj.directories !== undefined && packageObj.directories.bin !== undefined) { + fs.mkdirSync(path.join(nodeModules, ".bin")) + + fs.readdirSync(packageObj.directories.bin).forEach(function(exe) { + if(fs.existsSync(path.join(packageObj.directories.bin, exe))) { + console.log("linking bin '" + exe + "'"); + fs.symlinkSync( + path.join("..", packageObj.name, packageObj.directories.bin, exe), + path.join(nodeModules, ".bin", exe) + ); + } + else { + console.log("skipping non-existent bin '" + exe + "'"); + } + }) + } + ''; + }; + + prepareAndInvokeNPM = { + packageName, + bypassCache, + reconstructLock, + npmFlags, + production, + }: let + forceOfflineFlag = + if bypassCache + then "--offline" + else "--registry http://www.example.com"; + in '' + # Pinpoint the versions of all dependencies to the ones that are actually being used + echo "pinpointing versions of dependencies..." + source $pinpointDependenciesScriptPath + + # Patch the shebangs of the bundled modules to prevent them from + # calling executables outside the Nix store as much as possible + patchShebangs . + + # Deploy the Node.js package by running npm install. Since the + # dependencies have been provided already by ourselves, it should not + # attempt to install them again, which is good, because we want to make + # it Nix's responsibility. If it needs to install any dependencies + # anyway (e.g. because the dependency parameters are + # incomplete/incorrect), it fails. + # + # The other responsibilities of NPM are kept -- version checks, build + # steps, postprocessing etc. + + export HOME=$TMPDIR + cd "${packageName}" + runHook preRebuild + + ${lib.optionalString bypassCache '' + ${lib.optionalString reconstructLock '' + if [ -f package-lock.json ] + then + echo "WARNING: Reconstruct lock option enabled, but a lock file already exists!" + echo "This will most likely result in version mismatches! We will remove the lock file and regenerate it!" + rm package-lock.json + else + echo "No package-lock.json file found, reconstructing..." + fi + + node ${reconstructPackageLock} + ''} + + node ${addIntegrityFieldsScript} + ''} + + npm ${forceOfflineFlag} --nodedir=${nodeSources} ${npmFlags} ${lib.optionalString production "--production"} rebuild + + runHook postRebuild + + if [ "''${dontNpmInstall-}" != "1" ] + then + # NPM tries to download packages even when they already exist if npm-shrinkwrap is used. + rm -f npm-shrinkwrap.json + + npm ${forceOfflineFlag} --nodedir=${nodeSources} --no-bin-links --ignore-scripts ${npmFlags} ${lib.optionalString production "--production"} install + fi + + # Link executables defined in package.json + node ${linkBinsScript} + ''; + + # Builds and composes an NPM package including all its dependencies + buildNodePackage = { + name, + packageName, + version ? null, + dependencies ? [], + buildInputs ? [], + production ? true, + npmFlags ? "", + dontNpmInstall ? false, + bypassCache ? false, + reconstructLock ? false, + preRebuild ? "", + dontStrip ? true, + unpackPhase ? "true", + buildPhase ? "true", + meta ? {}, + ... + } @ args: let + extraArgs = removeAttrs args ["name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta"]; + in + stdenv.mkDerivation ({ + name = "${name}${ + if version == null + then "" + else "-${version}" + }"; + buildInputs = + [tarWrapper python nodejs] + ++ lib.optional (stdenv.isLinux) utillinux + ++ lib.optional (stdenv.isDarwin) libtool + ++ buildInputs; + + inherit nodejs; + + inherit dontStrip; # Stripping may fail a build for some package deployments + inherit dontNpmInstall preRebuild unpackPhase buildPhase; + + compositionScript = composePackage args; + pinpointDependenciesScript = pinpointDependenciesOfPackage args; + + passAsFile = ["compositionScript" "pinpointDependenciesScript"]; + + installPhase = '' + source ${installPackage} + + # Create and enter a root node_modules/ folder + mkdir -p $out/lib/node_modules + cd $out/lib/node_modules + + # Compose the package and all its dependencies + source $compositionScriptPath + + ${prepareAndInvokeNPM {inherit packageName bypassCache reconstructLock npmFlags production;}} + + # Create symlink to the deployed executable folder, if applicable + if [ -d "$out/lib/node_modules/.bin" ] + then + ln -s $out/lib/node_modules/.bin $out/bin + + # Fixup all executables + ls $out/bin/* | while read i + do + file="$(readlink -f "$i")" + chmod u+rwx "$file" + if isScript "$file" + then + sed -i 's/\r$//' "$file" # convert crlf to lf + fi + done + fi + + # Create symlinks to the deployed manual page folders, if applicable + if [ -d "$out/lib/node_modules/${packageName}/man" ] + then + mkdir -p $out/share + for dir in "$out/lib/node_modules/${packageName}/man/"* + do + mkdir -p $out/share/man/$(basename "$dir") + for page in "$dir"/* + do + ln -s $page $out/share/man/$(basename "$dir") + done + done + fi + + # Run post install hook, if provided + runHook postInstall + ''; + + meta = + { + # default to Node.js' platforms + platforms = nodejs.meta.platforms; + } + // meta; + } + // extraArgs); + + # Builds a node environment (a node_modules folder and a set of binaries) + buildNodeDependencies = { + name, + packageName, + version ? null, + src, + dependencies ? [], + buildInputs ? [], + production ? true, + npmFlags ? "", + dontNpmInstall ? false, + bypassCache ? false, + reconstructLock ? false, + dontStrip ? true, + unpackPhase ? "true", + buildPhase ? "true", + ... + } @ args: let + extraArgs = removeAttrs args ["name" "dependencies" "buildInputs"]; + in + stdenv.mkDerivation ({ + name = "node-dependencies-${name}${ + if version == null + then "" + else "-${version}" + }"; + + buildInputs = + [tarWrapper python nodejs] + ++ lib.optional (stdenv.isLinux) utillinux + ++ lib.optional (stdenv.isDarwin) libtool + ++ buildInputs; + + inherit dontStrip; # Stripping may fail a build for some package deployments + inherit dontNpmInstall unpackPhase buildPhase; + + includeScript = includeDependencies {inherit dependencies;}; + pinpointDependenciesScript = pinpointDependenciesOfPackage args; + + passAsFile = ["includeScript" "pinpointDependenciesScript"]; + + installPhase = '' + source ${installPackage} + + mkdir -p $out/${packageName} + cd $out/${packageName} + + source $includeScriptPath + + # Create fake package.json to make the npm commands work properly + cp ${src}/package.json . + chmod 644 package.json + ${lib.optionalString bypassCache '' + if [ -f ${src}/package-lock.json ] + then + cp ${src}/package-lock.json . + chmod 644 package-lock.json + fi + ''} + + # Go to the parent folder to make sure that all packages are pinpointed + cd .. + ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} + + ${prepareAndInvokeNPM {inherit packageName bypassCache reconstructLock npmFlags production;}} + + # Expose the executables that were installed + cd .. + ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} + + mv ${packageName} lib + ln -s $out/lib/node_modules/.bin $out/bin + ''; + } + // extraArgs); + + # Builds a development shell + buildNodeShell = { + name, + packageName, + version ? null, + src, + dependencies ? [], + buildInputs ? [], + production ? true, + npmFlags ? "", + dontNpmInstall ? false, + bypassCache ? false, + reconstructLock ? false, + dontStrip ? true, + unpackPhase ? "true", + buildPhase ? "true", + ... + } @ args: let + nodeDependencies = buildNodeDependencies args; + extraArgs = removeAttrs args ["name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "unpackPhase" "buildPhase"]; + in + stdenv.mkDerivation ({ + name = "node-shell-${name}${ + if version == null + then "" + else "-${version}" + }"; + + buildInputs = [python nodejs] ++ lib.optional (stdenv.isLinux) utillinux ++ buildInputs; + buildCommand = '' + mkdir -p $out/bin + cat > $out/bin/shell <= 2.1.2 < 3" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "requires": { + "is-docker": "^3.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + }, + "dependencies": { + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + } + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "peer": true, + "requires": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", + "peer": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "peer": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "nodemon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", + "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + }, + "dependencies": { + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + } + } + }, + "o1js": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/o1js/-/o1js-0.13.0.tgz", + "integrity": "sha512-JwhdgEiGz+ucpvvddykW1pnX25tyPGUEUyurskb9T/k8U5ATPdHrvdV9WWsSGq3fXL4NT40UCY5Y9qQ6IegV8w==", + "peer": true, + "requires": { + "blakejs": "1.2.1", + "detect-gpu": "^5.0.5", + "isomorphic-fetch": "^3.0.0", + "js-sha256": "^0.9.0", + "reflect-metadata": "^0.1.13", + "tslib": "^2.3.0" + } + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "requires": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + } + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + } + }, + "passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "requires": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "peer": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + }, + "dependencies": { + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "requires": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "peer": true + }, + "ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "requires": {} + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==", + "peer": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "peer": true + }, + "whatwg-fetch": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==", + "peer": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "peer": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, + "zod": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", + "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==" + } } } From 41919aeb40124184469805bd90dd2174ae1f4eb4 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Tue, 10 Oct 2023 23:34:33 +0800 Subject: [PATCH 18/21] provide all the leaves --- src/plugins/merkleMemberships/client/index.ts | 52 ++++++++++++--- src/plugins/merkleMemberships/server/index.ts | 64 ++++++++++++------- .../merkleMemberships/server/treeStorage.ts | 22 ++++++- 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/src/plugins/merkleMemberships/client/index.ts b/src/plugins/merkleMemberships/client/index.ts index eaab531..47e8abe 100644 --- a/src/plugins/merkleMemberships/client/index.ts +++ b/src/plugins/merkleMemberships/client/index.ts @@ -1,4 +1,4 @@ -import { Field, JsonProof, SelfProof } from 'o1js'; +import { Field, JsonProof, MerkleTree, SelfProof } from 'o1js'; import * as ZkProgram from '../common/merkleMembershipsProgram'; import { IMinAuthProver, IMinAuthProverFactory } from '@lib/plugin/pluginType'; import * as A from 'fp-ts/Array'; @@ -64,24 +64,56 @@ export class MerkleMembershipsProver )(proof); } + // async fetchPublicInputs( + // args: PublicInputArgs + // ): Promise> { + // const mkUrl = (treeRoot: Field, leafIndex: bigint) => + // `${this.cfg.baseUrl}/getWitness/${treeRoot + // .toBigInt() + // .toString()}/${leafIndex.toString()}`; + // const getRootAndWitness = async ( + // treeRoot: Field, + // leafIndex: bigint + // ): Promise<[ZkProgram.PublicInput, ZkProgram.TreeWitness]> => { + // const url = mkUrl(treeRoot, leafIndex); + // const resp = await axios.get(url); + // if (resp.status == 200) { + // const body: { + // witness: string; + // } = resp.data; + // const witness = ZkProgram.TreeWitness.fromJSON(body.witness); + // return [new ZkProgram.PublicInput({ merkleRoot: treeRoot }), witness]; + // } else { + // const body: { error: string } = resp.data; + // throw `error while getting root and witness: ${body.error}`; + // } + // }; + + // return Promise.all( + // A.map((args: { treeRoot: Field; leafIndex: bigint }) => + // getRootAndWitness(args.treeRoot, args.leafIndex) + // )(args) + // ); + // } + async fetchPublicInputs( args: PublicInputArgs ): Promise> { - const mkUrl = (treeRoot: Field, leafIndex: bigint) => - `${this.cfg.baseUrl}/getWitness/${treeRoot - .toBigInt() - .toString()}/${leafIndex.toString()}`; const getRootAndWitness = async ( treeRoot: Field, leafIndex: bigint ): Promise<[ZkProgram.PublicInput, ZkProgram.TreeWitness]> => { - const url = mkUrl(treeRoot, leafIndex); + const mkUrl = (treeRoot: Field) => + `${this.cfg.baseUrl}/getLeaves/${treeRoot.toBigInt()}`; + const url = mkUrl(treeRoot); const resp = await axios.get(url); if (resp.status == 200) { - const body: { - witness: string; - } = resp.data; - const witness = ZkProgram.TreeWitness.fromJSON(body.witness); + const body: { leaves: Array } = resp.data; + const tree = new MerkleTree(ZkProgram.TREE_HEIGHT); + body.leaves.forEach((leaf, index) => { + if (leaf !== undefined) tree.setLeaf(BigInt(index), Field.from(leaf)); + }); + const witness = new ZkProgram.TreeWitness(tree.getWitness(leafIndex)); return [new ZkProgram.PublicInput({ merkleRoot: treeRoot }), witness]; } else { const body: { error: string } = resp.data; diff --git a/src/plugins/merkleMemberships/server/index.ts b/src/plugins/merkleMemberships/server/index.ts index 7b7f846..db39f15 100644 --- a/src/plugins/merkleMemberships/server/index.ts +++ b/src/plugins/merkleMemberships/server/index.ts @@ -1,10 +1,12 @@ import { Experimental, Field, JsonProof, Poseidon } from 'o1js'; import * as O from 'fp-ts/Option'; +import * as A from 'fp-ts/Array'; import z from 'zod'; import { IMinAuthPlugin, IMinAuthPluginFactory } from '@lib/plugin/pluginType'; import { MinaTreesProvider, MinaTreesProviderConfiguration, + TreeStorage, TreesProvider, minaTreesProviderConfigurationSchema } from './treeStorage'; @@ -25,36 +27,52 @@ export class MerkleMembershipsPlugin private readonly storageProvider: TreesProvider; customRoutes: Record = { - '/getWitness/:treeRoot/:leafIndex': async (req, resp) => { + // '/getWitness/:treeRoot/:leafIndex': async (req, resp) => { + // if (req.method != 'GET') { + // resp.status(400); + // return; + // } + // const treeRoot = Field.from(req.params['treeRoot']); + // const leafIndex = BigInt(req.params['leafIndex']); + // const tree = O.toUndefined(await this.storageProvider.getTree(treeRoot)); + // if (tree === undefined) { + // resp.status(400).json({ + // error: 'tree not exists' + // }); + // return; + // } + // const witness = O.toUndefined(await tree.getWitness(leafIndex)); + // if (witness == undefined) { + // resp.status(400).json({ + // error: 'leaf not exists' + // }); + // return; + // } + // resp.status(200).json({ + // witness: witness.toJSON() + // }); + // } + + '/getLeaves/:treeRoot': async (req, resp) => { if (req.method != 'GET') { resp.status(400); return; } const treeRoot = Field.from(req.params['treeRoot']); - const leafIndex = BigInt(req.params['leafIndex']); - const tree = O.toUndefined(await this.storageProvider.getTree(treeRoot)); - - if (tree === undefined) { - resp.status(400).json({ - error: 'tree not exists' - }); - return; - } - - const witness = O.toUndefined(await tree.getWitness(leafIndex)); - - if (witness == undefined) { - resp.status(400).json({ - error: 'leaf not exists' - }); - return; - } - - resp.status(200).json({ - witness: witness.toJSON() - }); + return O.match( + () => { + resp.status(400).json({ + error: 'tree not exists' + }); + }, + async (tree: TreeStorage) => { + const leaves = A.map(O.toUndefined)(await tree.getLeaves()); + + resp.status(200).json({ leaves }); + } + )(await this.storageProvider.getTree(treeRoot)); } }; diff --git a/src/plugins/merkleMemberships/server/treeStorage.ts b/src/plugins/merkleMemberships/server/treeStorage.ts index 67cd9c2..789d53e 100644 --- a/src/plugins/merkleMemberships/server/treeStorage.ts +++ b/src/plugins/merkleMemberships/server/treeStorage.ts @@ -12,6 +12,7 @@ export interface TreeStorage { getWitness(leafIndex: bigint): Promise>; hasLeaf(leafIndex: bigint): Promise; setLeaf(leafIndex: bigint, leaf: Field): Promise; + getLeaves(): Promise>>; } export class InMemoryStorage implements TreeStorage { @@ -36,6 +37,19 @@ export class InMemoryStorage implements TreeStorage { this.occupied.add(leafIndex); this.merkleTree.setLeaf(leafIndex, leaf); } + + async getLeaves(): Promise>> { + const leaves: Array> = new Array( + Number(this.merkleTree.leafCount) + ); + + for (let i = 0; i < this.merkleTree.leafCount; i++) + leaves[i] = this.occupied.has(BigInt(i)) + ? O.some(this.merkleTree.getNode(0, BigInt(i))) + : O.none; + + return leaves; + } } export class PersistentInMemoryStorage extends InMemoryStorage { @@ -44,9 +58,7 @@ export class PersistentInMemoryStorage extends InMemoryStorage { async persist() { const storageObj = Array.from(this.occupied.values()).reduce( (acc: Record, idx: bigint) => { - acc[Number(idx)] = this.merkleTree - .getNode(Program.TREE_HEIGHT, idx) - .toJSON(); + acc[Number(idx)] = this.merkleTree.getNode(0, idx).toJSON(); return acc; }, {} @@ -126,6 +138,10 @@ export class GenericMinaBlockchainTreeStorage this.underlyingStorage.setLeaf(leafIndex, leaf); await this.updateTreeRootOnChainIfNecessary(); } + + async getLeaves(): Promise[]> { + return this.underlyingStorage.getLeaves(); + } } async function initializeGenericMinaBlockchainTreeStorage< From ea3cda24e93aeef953320bf25c00a336c3bc98a2 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Tue, 10 Oct 2023 23:36:51 +0800 Subject: [PATCH 19/21] comments --- src/library/tools/pluginServer/index.ts | 2 +- src/plugins/merkleMemberships/client/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/library/tools/pluginServer/index.ts b/src/library/tools/pluginServer/index.ts index de8d34f..f24a573 100644 --- a/src/library/tools/pluginServer/index.ts +++ b/src/library/tools/pluginServer/index.ts @@ -43,7 +43,7 @@ initializePlugins() console.info(`verifying proof using plugin ${pluginName}`); const pluginInstance = activePlugins[pluginName]; if (!pluginInstance) throw `plugin ${pluginName} not found`; - // Step 1: check that the proof was generated using a certain verification key. + // Step 1: check that the proof is valid against the given verification key. const proofValid = await verify( data.proof, pluginInstance.verificationKey diff --git a/src/plugins/merkleMemberships/client/index.ts b/src/plugins/merkleMemberships/client/index.ts index 47e8abe..7dcd285 100644 --- a/src/plugins/merkleMemberships/client/index.ts +++ b/src/plugins/merkleMemberships/client/index.ts @@ -34,6 +34,7 @@ export class MerkleMembershipsProver if (publicInput.length != secretInput.length) throw 'unmatched public/secret input list'; + // For each pair of inputs (secret and public) add another layer of the recursive proof const proof: O.Option = await A.reduce( Promise.resolve>(O.none), ( From d6c74060c1722d2bf8641df482751c68e237271a Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Tue, 10 Oct 2023 23:42:55 +0800 Subject: [PATCH 20/21] run prettier --- .eslintrc | 7 ++----- .prettierrc | 2 +- README.md | 3 +-- nodemon.json | 20 +++++++++----------- tsconfig.json | 19 +++++-------------- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/.eslintrc b/.eslintrc index e6d4fd0..996d15e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,7 @@ { "root": true, "parser": "@typescript-eslint/parser", - "plugins": [ - "@typescript-eslint", - "prettier" - ], + "plugins": ["@typescript-eslint", "prettier"], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", @@ -15,4 +12,4 @@ // "no-console": 1, // Means warning "prettier/prettier": 2 // Means error } -} \ No newline at end of file +} diff --git a/.prettierrc b/.prettierrc index a1bd96f..aa458dd 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,4 +3,4 @@ "trailingComma": "none", "singleQuote": true, "printWidth": 80 -} \ No newline at end of file +} diff --git a/README.md b/README.md index 160296d..7b40524 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ # MinAuth -Rapid development of POC of MinAuth - MinAuth is a library and a set of tools for providing JWT-based authentication protocol that uses SnarkyJS zero-knowledge proofs as the basis of the authentication. It uses plugins to prove statements about external data such as on-chain data or 3rd-party data. +Rapid development of POC of MinAuth - MinAuth is a library and a set of tools for providing JWT-based authentication protocol that uses SnarkyJS zero-knowledge proofs as the basis of the authentication. It uses plugins to prove statements about external data such as on-chain data or 3rd-party data. ### Current status The current code implements minimal authentication flow that recreates the usual passport authentication, but using snarkyjs ZKPs. - ### Repository Structure Currently the source code of the repository is structure like this: diff --git a/nodemon.json b/nodemon.json index 7402e3a..c080096 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,13 +1,11 @@ { - "watch": [ - "src" - ], - "ext": ".ts,.js", - "ignore": [], - "execMap": { - "ts": "ts-node" - }, - "env": { - "TS_NODE_PROJECT": "tsconfig.json" - } + "watch": ["src"], + "ext": ".ts,.js", + "ignore": [], + "execMap": { + "ts": "ts-node" + }, + "env": { + "TS_NODE_PROJECT": "tsconfig.json" + } } diff --git a/tsconfig.json b/tsconfig.json index 9024a2b..e724bb7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,7 @@ "compilerOptions": { "target": "es2016", "module": "commonjs", - "lib": [ - "dom", - "esnext" - ], + "lib": ["dom", "esnext"], "outDir": "./dist", "rootDir": ".", "strict": true, @@ -23,15 +20,9 @@ "allowSyntheticDefaultImports": true, "baseUrl": "./", "paths": { - "@lib/*": [ - "src/library/*" - ], - "@plugins/*": [ - "src/plugins/*" - ] + "@lib/*": ["src/library/*"], + "@plugins/*": ["src/plugins/*"] } }, - "include": [ - "./src" - ], -} \ No newline at end of file + "include": ["./src"] +} From 2100e6a23e7c3d67e27adabf02a53bcd0fe230d3 Mon Sep 17 00:00:00 2001 From: Hongrui Fang Date: Tue, 10 Oct 2023 23:46:39 +0800 Subject: [PATCH 21/21] only build for x86_64 linux on ci --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index f587ff7..acc65f4 100644 --- a/flake.nix +++ b/flake.nix @@ -92,6 +92,7 @@ }; }; flake = { + herculesCI.ciSystems = ["x86_64-linux"]; }; }; }