route payload requests and handle signer methods

This commit is contained in:
Pedro Gomes 2020-05-19 18:26:52 +02:00
parent 5fca284d21
commit 0e32a1f136
13 changed files with 395 additions and 38 deletions

View File

@ -60,6 +60,7 @@
"trailingComma": "es5"
},
"dependencies": {
"basic-provider": "^1.1.0"
"basic-provider": "^1.1.0",
"eccrypto-js": "^5.2.0"
}
}

View File

@ -1,11 +1,19 @@
import { IWakuProvider, IWakuSigner, IWakuStore } from "./typings";
import {
IWakuProvider,
IWakuSigner,
IWakuStore,
IWakuClient,
JsonRpcRequest,
} from "./typings";
import {
HttpConnection,
WakuProvider,
WakuSigner,
WakuStore,
} from "./controllers";
class Waku {
import { isSignerMethod, isNetworkMethod } from "./helpers/validators";
import { WAKU_PREFIX } from "./constants";
class Waku implements IWakuClient {
public provider: IWakuProvider;
public store: IWakuStore;
public signer: IWakuSigner;
@ -18,6 +26,16 @@ class Waku {
this.store = store || new WakuStore();
this.signer = new WakuSigner(this.store);
}
public async request(payload: JsonRpcRequest): Promise<any> {
if (isSignerMethod(payload.method)) {
return this.signer.request(payload);
} else if (isNetworkMethod(payload.method)) {
return this.provider.request(payload);
}
const method = payload.method.replace(WAKU_PREFIX + "_", "");
return this[method](...payload.params);
}
}
export default Waku;

View File

@ -1 +1,2 @@
export * from "./rpc";
export * from "./store";

View File

@ -1,3 +1,5 @@
export const WAKU_PREFIX = "waku";
export const NETWORK_METHODS = {
waku_info: "waku_info",
waku_setMaxMessageSize: "waku_setMaxMessageSize",

1
src/constants/store.ts Normal file
View File

@ -0,0 +1 @@
export const STORE_KEYS_ID = "WAKU_KEYS";

View File

@ -1,7 +1,7 @@
import BasicProvider from "basic-provider";
import { RPC_METHODS } from "../constants";
import { IWakuProvider } from "../typings";
import { IWakuProvider, JsonRpcRequest } from "../typings";
export class WakuProvider extends BasicProvider implements IWakuProvider {
get isWakuProvider(): boolean {
@ -21,4 +21,8 @@ export class WakuProvider extends BasicProvider implements IWakuProvider {
throw err;
}
}
public async request(payload: JsonRpcRequest): Promise<any> {
return this.send(payload.method, payload.params);
}
}

View File

@ -1,18 +1,180 @@
import { IWakuSigner, IWakuStore } from "../typings";
import {
generateKeyPair,
bufferToHex,
getPublic,
hexToBuffer,
generatePrivate,
} from "eccrypto-js";
import {
IWakuSigner,
IWakuStore,
KeyMap,
KeyPair,
Key,
SymKey,
JsonRpcRequest,
} from "../typings";
import { STORE_KEYS_ID, WAKU_PREFIX } from "../constants";
import { uuid, getFirstMatch } from "../helpers";
import { isKeyPair, isSymKey } from "../helpers/validators";
export class WakuSigner implements IWakuSigner {
constructor(private store: IWakuStore) {}
private keyMap: KeyMap = {};
public newKeyPair() {}
public addPrivateKey() {}
public deleteKeyPair() {}
public hasKeyPair() {}
public getPublicKey() {}
public getPrivateKey() {}
public newSymKey() {}
public addSymKey() {}
public generateSymKeyFromPassword() {}
public hasSymKey() {}
public getSymKey() {}
public deleteSymKey() {}
constructor(private store: IWakuStore) {
this.loadKeys();
}
// -- public ----------------------------------------------- //
public async newKeyPair(): Promise<string> {
await this.loadKeys();
const key = this.genKeyPair();
await this.addKey(key);
return key.id;
}
public async addPrivateKey(prvKey: string): Promise<string> {
await this.loadKeys();
let key = this.getMatchingKey("prvKey", prvKey);
if (!key) {
key = this.genKeyPair(prvKey);
}
await this.addKey(key);
return key.id;
}
public async deleteKeyPair(id: string): Promise<boolean> {
await this.loadKeys();
await this.removeKey(id);
return true;
}
public async hasKeyPair(id: string): Promise<boolean> {
await this.loadKeys();
let key = this.getMatchingKey("id", id);
return isKeyPair(key);
}
public async getPublicKey(id: string): Promise<string> {
await this.loadKeys();
let key = this.getMatchingKey("id", id);
if (!key) {
throw new Error(`No matching pubKey for id: ${id}`);
}
return (key as KeyPair).pubKey;
}
public async getPrivateKey(id: string): Promise<string> {
await this.loadKeys();
let key = this.getMatchingKey("id", id);
if (!key) {
throw new Error(`No matching privKey for id: ${id}`);
}
return (key as KeyPair).prvKey;
}
public async newSymKey(): Promise<string> {
await this.loadKeys();
const key = this.genSymKey();
await this.addKey(key);
return key.id;
}
public async addSymKey(symKey: string): Promise<string> {
await this.loadKeys();
let key = this.getMatchingKey("symKey", symKey);
if (!key) {
key = {
id: uuid(),
symKey,
};
}
await this.addKey(key);
return key.id;
}
public async generateSymKeyFromPassword(): Promise<string> {
await this.loadKeys();
// TODO: needs to accept optional "password" argument
const key = this.genSymKey();
await this.addKey(key);
return key.id;
}
public async hasSymKey(id: string): Promise<boolean> {
await this.loadKeys();
let key = this.getMatchingKey("id", id);
return isSymKey(key);
}
public async getSymKey(id: string): Promise<string> {
await this.loadKeys();
let key = this.getMatchingKey("id", id);
if (!key) {
throw new Error(`No matching symKey for id: ${id}`);
}
return (key as SymKey).symKey;
}
public async deleteSymKey(id: string): Promise<boolean> {
await this.loadKeys();
await this.removeKey(id);
return true;
}
public async request(payload: JsonRpcRequest): Promise<any> {
const method = payload.method.replace(WAKU_PREFIX + "_", "");
return this[method](...payload.params);
}
// -- private ----------------------------------------------- //
private getMatchingKey(param: string, value: string): Key | undefined {
return getFirstMatch<Key>(Object.values(this.keyMap), param, value);
}
private genKeyPair(prvKey?: string): KeyPair {
if (prvKey) {
return {
id: uuid(),
pubKey: bufferToHex(getPublic(hexToBuffer(prvKey)), true),
prvKey,
};
} else {
const key = generateKeyPair();
return {
id: uuid(),
pubKey: bufferToHex(key.publicKey, true),
prvKey: bufferToHex(key.privateKey, true),
};
}
}
private genSymKey(): SymKey {
const symKey = generatePrivate();
return {
id: uuid(),
symKey: bufferToHex(symKey, true),
};
}
private async loadKeys() {
this.keyMap = await this.store.get(STORE_KEYS_ID);
}
private async addKey(key: Key) {
this.keyMap[key.id] = key;
await this.persistKeys();
}
private async removeKey(id: string) {
delete this.keyMap[id];
await this.persistKeys();
}
private async persistKeys() {
await this.store.set(STORE_KEYS_ID, this.keyMap);
}
}

View File

@ -1,3 +1,4 @@
export * from "./json";
export * from "./local";
export * from "./misc";
export * from "./uuid";

View File

@ -13,3 +13,20 @@ export function getOrError<T>(name: string, target: any): T {
}
return res;
}
export function getFirstMatch<T>(
array: T[],
key: string,
value: any
): T | undefined {
let result: T | undefined = undefined;
const matches = array.filter(
x => typeof x[key] !== "undefined" && x[key] === value
);
if (!!matches && matches.length) {
result = matches[0];
}
return result;
}

16
src/helpers/uuid.ts Normal file
View File

@ -0,0 +1,16 @@
export function uuid(): string {
const result: string = ((a?: any, b?: any) => {
for (
b = a = "";
a++ < 36;
b +=
(a * 51) & 52
? (a ^ 15 ? 8 ^ (Math.random() * (a ^ 20 ? 16 : 4)) : 4).toString(16)
: "-"
) {
// empty
}
return b;
})();
return result;
}

29
src/helpers/validators.ts Normal file
View File

@ -0,0 +1,29 @@
import { KeyPair, SymKey } from "../typings";
import {
NETWORK_METHODS,
SIGNER_METHODS,
MESSAGING_METHODS,
} from "../constants";
export function isKeyPair(value?: any): value is KeyPair {
return typeof value.prvKey !== "undefined";
}
export function isSymKey(value?: any): value is SymKey {
return typeof value.symKey !== "undefined";
}
export function isNetworkMethod(value?: string): boolean {
if (!value) return false;
return Object.keys(NETWORK_METHODS).includes(value);
}
export function isSignerMethod(value?: string): boolean {
if (!value) return false;
return Object.keys(SIGNER_METHODS).includes(value);
}
export function isMessagingMethod(value?: string): boolean {
if (!value) return false;
return Object.keys(MESSAGING_METHODS).includes(value);
}

View File

@ -1,23 +1,28 @@
import BasicProvider from "basic-provider";
export interface IWakuProvider extends BasicProvider {
// -- interfaces ----------------------------------------------- //
export interface IWakuController {
request(payload: JsonRpcRequest): Promise<any>;
}
export interface IWakuProvider extends BasicProvider, IWakuController {
isWakuProvider: boolean;
enable(): Promise<any>;
}
export interface IWakuSigner {
newKeyPair(): any;
addPrivateKey(): any;
deleteKeyPair(): any;
hasKeyPair(): any;
getPublicKey(): any;
getPrivateKey(): any;
newSymKey(): any;
addSymKey(): any;
generateSymKeyFromPassword(): any;
hasSymKey(): any;
getSymKey(): any;
deleteSymKey(): any;
export interface IWakuSigner extends IWakuController {
newKeyPair(): Promise<string>;
addPrivateKey(prvKey: string): Promise<string>;
deleteKeyPair(id: string): Promise<boolean>;
hasKeyPair(id: string): Promise<boolean>;
getPublicKey(id: string): Promise<string>;
getPrivateKey(id: string): Promise<string>;
newSymKey(): Promise<string>;
addSymKey(symKey: string): Promise<string>;
generateSymKeyFromPassword(): Promise<string>;
hasSymKey(id: string): Promise<boolean>;
getSymKey(id: string): Promise<string>;
deleteSymKey(id: string): Promise<boolean>;
}
export interface IWakuStore {
@ -25,3 +30,35 @@ export interface IWakuStore {
get(key: string): Promise<any>;
remove(key: string): Promise<void>;
}
export interface IWakuClient extends IWakuController {
provider: IWakuProvider;
store: IWakuStore;
signer: IWakuSigner;
}
// -- types ----------------------------------------------- //
export type SymKey = {
id: string;
symKey: string;
};
export type KeyPair = {
id: string;
pubKey: string;
prvKey: string;
};
export type Key = SymKey | KeyPair;
export type KeyMap = {
[id: string]: Key;
};
export type JsonRpcRequest = {
id: number;
jsonrpc: "2.0";
method: string;
params: any;
};

View File

@ -1407,6 +1407,11 @@ acorn@^7.1.0, acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe"
integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==
aes-js@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
ajv-errors@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
@ -1832,6 +1837,13 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"
bip66@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22"
integrity sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=
dependencies:
safe-buffer "^5.0.1"
bluebird@3.5.5:
version "3.5.5"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
@ -1842,7 +1854,7 @@ bluebird@^3.5.5:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
bn.js@4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8, bn.js@^4.4.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
@ -1893,7 +1905,7 @@ browser-resolve@^1.11.3:
dependencies:
resolve "1.1.7"
browserify-aes@^1.0.0, browserify-aes@^1.0.4:
browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6:
version "1.2.0"
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==
@ -2652,6 +2664,15 @@ domexception@^1.0.1:
dependencies:
webidl-conversions "^4.0.2"
drbg.js@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b"
integrity sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=
dependencies:
browserify-aes "^1.0.6"
create-hash "^1.1.2"
create-hmac "^1.1.4"
duplexify@^3.4.2, duplexify@^3.6.0:
version "3.7.1"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
@ -2670,6 +2691,18 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
eccrypto-js@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/eccrypto-js/-/eccrypto-js-5.2.0.tgz#eb3b36e9978d316fedf50be46492bb0d3e240cf5"
integrity sha512-pPb6CMapJ1LIzjLWxMqlrnfaEFap7qkk9wcO/b4AVSdxBQYlpOqvlPpq5SpUI4FdmfdhVD34AjN47fM8fryC4A==
dependencies:
aes-js "3.1.2"
enc-utils "2.1.0"
hash.js "1.1.7"
js-sha3 "0.8.0"
randombytes "2.1.0"
secp256k1 "3.8.0"
electron-to-chromium@^1.3.413:
version "1.3.435"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.435.tgz#22a7008e8f5a317a6d2d80802bddacebb19ae025"
@ -2708,6 +2741,15 @@ emojis-list@^3.0.0:
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
enc-utils@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/enc-utils/-/enc-utils-2.1.0.tgz#f6c28c3d4bb38fb409a93185848cf361f4fde142"
integrity sha512-VD0eunGDyzhojePzkORWDnW88gi6tIeGb5Z6QVHugux6mMAPiXyw94fb/7WdDQEWhKMSoYRyzFFUebCqeH20PA==
dependencies:
bn.js "4.11.8"
is-typedarray "1.0.0"
typedarray-to-buffer "3.1.5"
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@ -3608,7 +3650,7 @@ hash-base@^3.0.0:
readable-stream "^3.6.0"
safe-buffer "^5.2.0"
hash.js@^1.0.0, hash.js@^1.0.3:
hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
@ -3991,7 +4033,7 @@ is-symbol@^1.0.2:
dependencies:
has-symbols "^1.0.1"
is-typedarray@~1.0.0:
is-typedarray@1.0.0, is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
@ -4465,6 +4507,11 @@ jpjs@^1.2.1:
resolved "https://registry.yarnpkg.com/jpjs/-/jpjs-1.2.1.tgz#f343833de8838a5beba1f42d5a219be0114c44b7"
integrity sha512-GxJWybWU4NV0RNKi6EIqk6IRPOTqd/h+U7sbtyuD7yUISUzV78LdHnq2xkevJsTlz/EImux4sWj+wfMiwKLkiw==
js-sha3@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -5012,7 +5059,7 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
nan@^2.12.1:
nan@^2.12.1, nan@^2.14.0:
version "2.14.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
@ -5783,7 +5830,7 @@ querystring@0.2.0:
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
randombytes@2.1.0, randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
@ -6271,6 +6318,20 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
secp256k1@3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.8.0.tgz#28f59f4b01dbee9575f56a47034b7d2e3b3b352d"
integrity sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==
dependencies:
bindings "^1.5.0"
bip66 "^1.1.5"
bn.js "^4.11.8"
create-hash "^1.2.0"
drbg.js "^1.0.1"
elliptic "^6.5.2"
nan "^2.14.0"
safe-buffer "^5.1.2"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
@ -7067,6 +7128,13 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
typedarray-to-buffer@3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
dependencies:
is-typedarray "^1.0.0"
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"