diff --git a/dependency-graph.json b/dependency-graph.json index df420260e3f..7047587b9bc 100644 --- a/dependency-graph.json +++ b/dependency-graph.json @@ -202,6 +202,13 @@ "@celo/wallet-local" ] }, + "@celo/keystores": { + "location": "packages/sdk/keystores", + "dependencies": [ + "@celo/utils", + "@celo/wallet-local" + ] + }, "@celo/network-utils": { "location": "packages/sdk/network-utils", "dependencies": [ diff --git a/packages/sdk/keystores/.gitignore b/packages/sdk/keystores/.gitignore new file mode 100644 index 00000000000..cd91bda02d9 --- /dev/null +++ b/packages/sdk/keystores/.gitignore @@ -0,0 +1,2 @@ +lib/ +test-keystore-dir/ diff --git a/packages/sdk/keystores/.npmignore b/packages/sdk/keystores/.npmignore new file mode 100644 index 00000000000..45e506bacd1 --- /dev/null +++ b/packages/sdk/keystores/.npmignore @@ -0,0 +1,17 @@ +/.devchain/ +/.devchain.tar.gz +/coverage/ +/node_modules/ +/src/ +/tmp/ +/.tmp/ + +/tslint.json +/tsconfig.* +/jest.config.* +*.tgz + +/src + +/lib/**/*.test.* +/lib/test-utils \ No newline at end of file diff --git a/packages/sdk/keystores/jest.config.js b/packages/sdk/keystores/jest.config.js new file mode 100644 index 00000000000..2681a75d5a6 --- /dev/null +++ b/packages/sdk/keystores/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['/src/**/?(*.)+(spec|test).ts?(x)'], +} diff --git a/packages/sdk/keystores/package.json b/packages/sdk/keystores/package.json new file mode 100644 index 00000000000..7e512efef07 --- /dev/null +++ b/packages/sdk/keystores/package.json @@ -0,0 +1,31 @@ +{ + "name": "@celo/keystores", + "version": "1.2.2-dev", + "description": "keystore implementation", + "author": "Celo", + "license": "Apache-2.0", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "keywords": [ + "celo", + "blockchain", + "sdk" + ], + "scripts": { + "build": "tsc -b .", + "clean": "tsc -b . --clean", + "docs": "typedoc && ts-node ../utils/scripts/linkdocs.ts keystores", + "test": "jest --runInBand", + "lint": "tslint -c tslint.json --project .", + "prepublishOnly": "yarn build" + }, + "dependencies": { + "@celo/utils": "1.2.2-dev", + "@celo/wallet-local": "1.2.2-dev", + "@types/ethereumjs-util": "^5.2.0", + "ethereumjs-wallet": "^1.0.1" + }, + "engines": { + "node": ">=10" + } +} diff --git a/packages/sdk/keystores/src/file-keystore.test.ts b/packages/sdk/keystores/src/file-keystore.test.ts new file mode 100644 index 00000000000..b423888c441 --- /dev/null +++ b/packages/sdk/keystores/src/file-keystore.test.ts @@ -0,0 +1,50 @@ +import { trimLeading0x } from '@celo/utils/lib/address' +import { mkdirSync, readdirSync, readFileSync, rmdirSync, writeFileSync } from 'fs' +import path from 'path' +import { FileKeystore } from './file-keystore' +import { ADDRESS1, GETH_GEN_KEYSTORE1, KEYSTORE_NAME1, PASSPHRASE1, PK1 } from './test-constants' + +jest.setTimeout(20000) + +describe('FileKeystore tests', () => { + const parentWorkdir = path.join(__dirname, 'wallet-keystore-workdir') + let testWorkdir: string + + beforeAll(() => { + mkdirSync(parentWorkdir, { recursive: true }) + }) + + beforeEach(() => { + testWorkdir = path.join(parentWorkdir, `test-${Math.random().toString(36).substring(2, 7)}`) + mkdirSync(testWorkdir) + }) + + afterAll(() => { + rmdirSync(parentWorkdir, { recursive: true }) + }) + it('initializes keystore, imports key into keystore file, and deletes', async () => { + const keystore = new FileKeystore(testWorkdir) + expect(readdirSync(testWorkdir)).toEqual(['keystore']) + const keystorePath = path.join(testWorkdir, 'keystore') + expect(readdirSync(keystorePath).length).toBe(0) + await keystore.importPrivateKey(PK1, PASSPHRASE1) + const keystoreFiles = readdirSync(keystorePath) + expect(keystoreFiles.length).toBe(1) + const keystoreName = await keystore.getKeystoreName(ADDRESS1) + expect(readFileSync(path.join(keystorePath, keystoreFiles[0])).toString()).toEqual( + keystore.getRawKeystore(keystoreName) + ) + keystore.removeKeystore(keystoreName) + expect(readdirSync(keystorePath).length).toBe(0) + }) + + it('reads key from file in existing keystore', async () => { + const keystorePath = path.join(testWorkdir, 'keystore') + mkdirSync(keystorePath) + writeFileSync(path.join(keystorePath, KEYSTORE_NAME1), GETH_GEN_KEYSTORE1) + const keystore = new FileKeystore(testWorkdir) + expect(await keystore.getAllKeystoreNames()).toEqual([KEYSTORE_NAME1]) + expect(await keystore.listKeystoreAddresses()).toEqual([ADDRESS1]) + expect(trimLeading0x(await keystore.getPrivateKey(ADDRESS1, PASSPHRASE1))).toEqual(PK1) + }) +}) diff --git a/packages/sdk/keystores/src/file-keystore.ts b/packages/sdk/keystores/src/file-keystore.ts new file mode 100644 index 00000000000..08eb4dbac35 --- /dev/null +++ b/packages/sdk/keystores/src/file-keystore.ts @@ -0,0 +1,56 @@ +import { mkdirSync, promises as fsPromises, readFileSync, unlinkSync, writeFileSync } from 'fs' +import path from 'path' +import { KeystoreBase } from './keystore-base' + +export class FileKeystore extends KeystoreBase { + /** + * Implements keystore as a directory on disk + * with files for keystore entries + */ + private _keystoreDir: string + + /** + * Creates (but does not overwrite existing) directory + * for containing keystore entries. + * @param keystoreDir Path to directory where keystore will be saved + */ + constructor(keystoreDir: string) { + super() + this._keystoreDir = path.join(keystoreDir, 'keystore') + // Does not overwrite existing directories + mkdirSync(this._keystoreDir, { recursive: true }) + } + + /** + * @returns List of file names (keystore entries) in the keystore + */ + async getAllKeystoreNames(): Promise { + return fsPromises.readdir(this._keystoreDir) + } + + /** + * Saves keystore entries as a file in the keystore directory + * @param keystoreName File name of keystore entry + * @param keystore V3Keystore string entry + */ + persistKeystore(keystoreName: string, keystore: string) { + writeFileSync(path.join(this._keystoreDir, keystoreName), keystore) + } + + /** + * Gets contents of keystore entry file + * @param keystoreName File name of keystore entry + * @returns V3Keystore string entry + */ + getRawKeystore(keystoreName: string): string { + return readFileSync(path.join(this._keystoreDir, keystoreName)).toString() + } + + /** + * Deletes file keystore entry from directory + * @param keystoreName File name of keystore entry to be removed + */ + removeKeystore(keystoreName: string) { + return unlinkSync(path.join(this._keystoreDir, keystoreName)) + } +} diff --git a/packages/sdk/keystores/src/index.ts b/packages/sdk/keystores/src/index.ts new file mode 100644 index 00000000000..9c1467886b6 --- /dev/null +++ b/packages/sdk/keystores/src/index.ts @@ -0,0 +1,4 @@ +export * from './file-keystore' +export * from './inmemory-keystore' +export * from './keystore-base' +export * from './keystore-wallet-wrapper' diff --git a/packages/sdk/keystores/src/inmemory-keystore.ts b/packages/sdk/keystores/src/inmemory-keystore.ts new file mode 100644 index 00000000000..5fa6a41b271 --- /dev/null +++ b/packages/sdk/keystores/src/inmemory-keystore.ts @@ -0,0 +1,25 @@ +import { KeystoreBase } from './keystore-base' + +export class InMemoryKeystore extends KeystoreBase { + /** + * Used for mocking keystore operations in unit tests + */ + private _storage: Record = {} + + // Implements required abstract class methods + persistKeystore(keystoreName: string, keystore: string) { + this._storage[keystoreName] = keystore + } + + getRawKeystore(keystoreName: string): string { + return this._storage[keystoreName] + } + + async getAllKeystoreNames(): Promise { + return Object.keys(this._storage) + } + + removeKeystore(keystoreName: string) { + delete this._storage[keystoreName] + } +} diff --git a/packages/sdk/keystores/src/keystore-base.test.ts b/packages/sdk/keystores/src/keystore-base.test.ts new file mode 100644 index 00000000000..938e3da331c --- /dev/null +++ b/packages/sdk/keystores/src/keystore-base.test.ts @@ -0,0 +1,97 @@ +import { privateKeyToAddress, trimLeading0x } from '@celo/utils/lib/address' +import { InMemoryKeystore } from './inmemory-keystore' +import { ErrorMessages } from './keystore-base' +import { + ADDRESS1, + ADDRESS2, + GETH_GEN_KEYSTORE1, + GETH_GEN_KEYSTORE2, + KEYSTORE_NAME1, + KEYSTORE_NAME2, + PASSPHRASE1, + PASSPHRASE2, + PK1, +} from './test-constants' + +jest.setTimeout(20000) + +describe('KeystoreBase functionality via InMemoryKeystore (mock)', () => { + let keystore: InMemoryKeystore + beforeEach(() => { + keystore = new InMemoryKeystore() + }) + + describe('checks with an empty keystore', () => { + it('lists no addresses', async () => { + expect(await keystore.listKeystoreAddresses()).toEqual([]) + }) + it('imports keystore (can decrypt, can list addresses)', async () => { + await keystore.importPrivateKey(PK1, PASSPHRASE1) + expect(await keystore.listKeystoreAddresses()).toEqual([ADDRESS1]) + expect(trimLeading0x(await keystore.getPrivateKey(ADDRESS1, PASSPHRASE1))).toEqual(PK1) + }) + it('gets empty address map', async () => { + expect(await keystore.getAddressMap()).toEqual({}) + }) + it('fails when address is not in the keystore', async () => { + await expect(keystore.getPrivateKey(ADDRESS1, PK1)).rejects.toThrow( + ErrorMessages.NO_MATCHING_ENTRY + ) + }) + }) + + describe('checks with a populated keystore', () => { + beforeEach(() => { + keystore.persistKeystore(KEYSTORE_NAME1, GETH_GEN_KEYSTORE1) + }) + + it('decrypts and returns raw private key from keystore blob', async () => { + expect(trimLeading0x(await keystore.getPrivateKey(ADDRESS1, PASSPHRASE1))).toBe(PK1) + }) + + it('decrypts when non-normalized address is passed in', async () => { + expect( + trimLeading0x(await keystore.getPrivateKey(privateKeyToAddress(PK1), PASSPHRASE1)) + ).toBe(PK1) + }) + + it('does not decrypt keystore with incorrect passphrase', async () => { + await expect(keystore.getPrivateKey(ADDRESS1, PASSPHRASE1 + '!')).rejects.toThrow( + 'Key derivation failed - possibly wrong passphrase' + ) + }) + + it('gets keystore name from address', async () => { + expect(await keystore.getKeystoreName(ADDRESS1)).toBe(KEYSTORE_NAME1) + }) + + it('changes keystore passphrase successfully', async () => { + await keystore.changeKeystorePassphrase(ADDRESS1, PASSPHRASE1, PASSPHRASE2) + await expect(keystore.getPrivateKey(ADDRESS1, PASSPHRASE1)).rejects.toThrow() + expect(trimLeading0x(await keystore.getPrivateKey(ADDRESS1, PASSPHRASE2))).toBe(PK1) + }) + + it('does not import same private key twice', async () => { + await expect(keystore.importPrivateKey(PK1, PASSPHRASE2)).rejects.toThrow( + ErrorMessages.KEYSTORE_ENTRY_EXISTS + ) + }) + + it('lists addresses', async () => { + expect(await keystore.listKeystoreAddresses()).toEqual([ADDRESS1]) + }) + + it('deletes keystore', async () => { + await keystore.deleteKeystore(ADDRESS1) + expect(await keystore.listKeystoreAddresses()).toEqual([]) + }) + + it('maps address to keystore name', async () => { + keystore.persistKeystore(KEYSTORE_NAME2, GETH_GEN_KEYSTORE2) + const expectedMap: Record = {} + expectedMap[ADDRESS1] = KEYSTORE_NAME1 + expectedMap[ADDRESS2] = KEYSTORE_NAME2 + expect(await keystore.getAddressMap()).toEqual(expectedMap) + }) + }) +}) diff --git a/packages/sdk/keystores/src/keystore-base.ts b/packages/sdk/keystores/src/keystore-base.ts new file mode 100644 index 00000000000..358cf6056a2 --- /dev/null +++ b/packages/sdk/keystores/src/keystore-base.ts @@ -0,0 +1,148 @@ +import { + ensureLeading0x, + normalizeAddressWith0x, + privateKeyToAddress, +} from '@celo/utils/lib/address' +import Wallet from 'ethereumjs-wallet' + +export enum ErrorMessages { + KEYSTORE_ENTRY_EXISTS = 'Existing encrypted keystore for address', + NO_MATCHING_ENTRY = 'Keystore entry not found for address', + UNKNOWN_STRUCTURE = 'Unexpected keystore entry structure', +} + +// TODO refine language around keystore entry / keystore / keystore container ...? +export abstract class KeystoreBase { + /** + * Handles encrytion and transformation between private keys <-> V3Keystore strings + * See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition + * for more details on conventions around Web3 Secret Storage. + */ + + /** + * Saves encrypted keystore entry (i.e. to disk, database, ...). Must be implemented by subclass. + * @param keystoreName Name of keystore entry to be saved + * @param keystore encrypted V3Keystore string entry + */ + abstract persistKeystore(keystoreName: string, keystore: string): void + + /** + * Returns raw encrypted keystore entry string by name + * @param keystoreName Name of keystore entry to retrieve + */ + abstract getRawKeystore(keystoreName: string): string + + /** + * Gets a list of the names of each entry in the keystore + */ + abstract getAllKeystoreNames(): Promise + + /** + * Removes keystore entry from keystore permanently + * @param keystoreName Name of keystore entry to remove + */ + abstract removeKeystore(keystoreName: string): void + + /** + * Gets the address corresponding to a particular keystore entry + * @param keystoreName Name of keystore entry belonging to the address + * @returns Account address + */ + getAddress(keystoreName: string): string { + const rawKeystore = this.getRawKeystore(keystoreName) + try { + const address = JSON.parse(rawKeystore).address + return ensureLeading0x(address) + } catch (e) { + throw new Error(ErrorMessages.UNKNOWN_STRUCTURE) + } + } + /** + * Gets a list of all account addresses in the keystore + * @returns List of account address strings + */ + async listKeystoreAddresses(): Promise { + return Object.keys(await this.getAddressMap()) + } + + /** + * Maps account addresses to their respective keystore entries (names) + * @returns Record with account addresses as keys, keystore entry names as values + */ + async getAddressMap(): Promise> { + // Don't store this to minimize race conditions (file is deleted/added manually) + const addressToFile: Record = {} + ;(await this.getAllKeystoreNames()).forEach( + (file) => (addressToFile[this.getAddress(file)] = file) + ) + return addressToFile + } + + /** + * Encrypts and stores a private key as a new keystore entry + * @param privateKey Private key to encrypted + * @param passphrase Secret string to encrypt private key + */ + async importPrivateKey(privateKey: string, passphrase: string) { + // Duplicate entries for the same private key are not permitted + const address = normalizeAddressWith0x(privateKeyToAddress(privateKey)) + if ((await this.listKeystoreAddresses()).includes(address)) { + throw new Error(ErrorMessages.KEYSTORE_ENTRY_EXISTS) + } + + const key = Buffer.from(privateKey, 'hex') + const wallet = Wallet.fromPrivateKey(key) + const keystore = await wallet.toV3String(passphrase) + const keystoreName = wallet.getV3Filename(Date.now()) + + this.persistKeystore(keystoreName, keystore) + } + + /** + * Gets name of keystore entry corresponding to an address + * @param address Account address + * @returns Name of corresponding keystore entry + */ + async getKeystoreName(address: string): Promise { + const keystoreName = (await this.getAddressMap())[normalizeAddressWith0x(address)] + if (keystoreName === undefined) { + throw new Error(ErrorMessages.NO_MATCHING_ENTRY) + } + return keystoreName + } + + /** + * Gets decrypted (plaintext) private key for an account address + * @param address Account address + * @param passphrase Secret phrase used to encrypt the private key + * @returns + */ + async getPrivateKey(address: string, passphrase: string): Promise { + const rawKeystore = this.getRawKeystore(await this.getKeystoreName(address)) + // TODO do we want to trim leading 0x here? what is the best practice here? + return (await Wallet.fromV3(rawKeystore, passphrase)).getPrivateKeyString() + } + + /** + * Change secret phrase used to encrypt the private key of an address + * @param address Account address + * @param oldPassphrase Secret phrase used to encrypt the private key + * @param newPassphrase New secret phrase to re-encrypt the private key + */ + async changeKeystorePassphrase(address: string, oldPassphrase: string, newPassphrase: string) { + const keystoreName = await this.getKeystoreName(address) + const rawKeystore = this.getRawKeystore(keystoreName) + const newKeystore = await (await Wallet.fromV3(rawKeystore, oldPassphrase)).toV3String( + newPassphrase + ) + this.persistKeystore(keystoreName, newKeystore) + } + + /** + * Permanently removes keystore entry from keystore + * @param address Account address of keystore to be deleted + */ + async deleteKeystore(address: string) { + this.removeKeystore(await this.getKeystoreName(address)) + } +} diff --git a/packages/sdk/keystores/src/keystore-wallet-wrapper.test.ts b/packages/sdk/keystores/src/keystore-wallet-wrapper.test.ts new file mode 100644 index 00000000000..d4f22573508 --- /dev/null +++ b/packages/sdk/keystores/src/keystore-wallet-wrapper.test.ts @@ -0,0 +1,39 @@ +import { normalizeAddressWith0x, privateKeyToAddress } from '@celo/utils/lib/address' +import { InMemoryKeystore } from './inmemory-keystore' +import { KeystoreWalletWrapper } from './keystore-wallet-wrapper' + +const PASSPHRASE1 = 'test- passwøörd1!' +const PK1 = 'd72f6c0b0d7348a72eaa7d3c997bd49293bdc7d4bf79eba03e9f7ca9c5ac6b7f' +const GETH_GEN_KEYSTORE1 = `{"address":"8233d802bdc645d0d1b9b2e6face6e5825905081","blspublickey":"ed2ed9b2670458d01df329a4c750e7a6f89ec0e86676d4e093b2f32b4f3b603b6927b8dfe12e9fdf5c9f4bbbc504770052d816dbcaae90f4ef0af19333965b29f29b069c1f28eaa4bcaa62b27459855e4ad201aac245de05c3cb51dcab118080","crypto":{"cipher":"aes-128-ctr","ciphertext":"7b2ccdede461b9f7cc33fbbd7a9bfe23fdf455f3d4a8558cb10e86c5a4c5cc39","cipherparams":{"iv":"a78b8382da088a544edef093e922947b"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"2007752e0c72eed75a793cddba6a9e3c698b95a259002b32443d8c0430038505"},"mac":"e1599623f8957e538e17512e39693bf1a85fc4eab10fdb243c7d33fd18f9c766"},"id":"3b9465ac-eca1-4923-84e6-4624bd41ab0b","version":3}` +const KEYSTORE_NAME1 = 'PK1 keystore name' +const ADDRESS1 = normalizeAddressWith0x(privateKeyToAddress(PK1)) + +describe('KeystoreWalletWrapper using InMemoryKeystore', () => { + let keystoreWalletWrapper: KeystoreWalletWrapper + + beforeEach(() => { + keystoreWalletWrapper = new KeystoreWalletWrapper(new InMemoryKeystore()) + }) + + describe('checks with an empty keystore', () => { + it('imports private key into keystore wallet', async () => { + await keystoreWalletWrapper.importPrivateKey(PK1, PASSPHRASE1) + expect(keystoreWalletWrapper.getLocalWallet().getAccounts()).toEqual([ADDRESS1]) + }) + }) + + describe('checks with a populated keystore', () => { + beforeEach(() => { + keystoreWalletWrapper.getKeystore().persistKeystore(KEYSTORE_NAME1, GETH_GEN_KEYSTORE1) + }) + + it('lists no accounts pre-unlock', async () => { + expect(keystoreWalletWrapper.getLocalWallet().getAccounts()).toEqual([]) + }) + + it('lists account post-unlock', async () => { + await keystoreWalletWrapper.unlockAccount(ADDRESS1, PASSPHRASE1) + expect(keystoreWalletWrapper.getLocalWallet().getAccounts()).toEqual([ADDRESS1]) + }) + }) +}) diff --git a/packages/sdk/keystores/src/keystore-wallet-wrapper.ts b/packages/sdk/keystores/src/keystore-wallet-wrapper.ts new file mode 100644 index 00000000000..f93b887465d --- /dev/null +++ b/packages/sdk/keystores/src/keystore-wallet-wrapper.ts @@ -0,0 +1,39 @@ +import { LocalWallet } from '@celo/wallet-local' +import { KeystoreBase } from './keystore-base' + +/** + * Convenience wrapper of the LocalWallet to connect to a keystore + */ +export class KeystoreWalletWrapper { + private _keystore: KeystoreBase + private _localWallet: LocalWallet + + constructor(keystore: KeystoreBase) { + this._keystore = keystore + this._localWallet = new LocalWallet() + } + + async importPrivateKey(privateKey: string, passphrase: string) { + await this._keystore.importPrivateKey(privateKey, passphrase) + this._localWallet.addAccount(privateKey) + } + + getLocalWallet(): LocalWallet { + return this._localWallet + } + + getKeystore(): KeystoreBase { + return this._keystore + } + + async unlockAccount(address: string, passphrase: string) { + // Unlock and add account to internal LocalWallet + this._localWallet.addAccount(await this._keystore.getPrivateKey(address, passphrase)) + } + + async lockAccount(address: string) { + this._localWallet.removeAccount(address) + } +} + +// const testKeystoreWalletWrapper = new KeystoreWalletWrapper(new FileKeystore()) diff --git a/packages/sdk/keystores/src/test-constants.ts b/packages/sdk/keystores/src/test-constants.ts new file mode 100644 index 00000000000..677769d5758 --- /dev/null +++ b/packages/sdk/keystores/src/test-constants.ts @@ -0,0 +1,13 @@ +import { normalizeAddressWith0x, privateKeyToAddress } from '@celo/utils/lib/address' + +export const PASSPHRASE1 = 'test- passwøörd1!' +export const PK1 = 'd72f6c0b0d7348a72eaa7d3c997bd49293bdc7d4bf79eba03e9f7ca9c5ac6b7f' +// Use a geth-generated keystore to ensure compatibility +export const GETH_GEN_KEYSTORE1 = `{"address":"8233d802bdc645d0d1b9b2e6face6e5825905081","blspublickey":"ed2ed9b2670458d01df329a4c750e7a6f89ec0e86676d4e093b2f32b4f3b603b6927b8dfe12e9fdf5c9f4bbbc504770052d816dbcaae90f4ef0af19333965b29f29b069c1f28eaa4bcaa62b27459855e4ad201aac245de05c3cb51dcab118080","crypto":{"cipher":"aes-128-ctr","ciphertext":"7b2ccdede461b9f7cc33fbbd7a9bfe23fdf455f3d4a8558cb10e86c5a4c5cc39","cipherparams":{"iv":"a78b8382da088a544edef093e922947b"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"2007752e0c72eed75a793cddba6a9e3c698b95a259002b32443d8c0430038505"},"mac":"e1599623f8957e538e17512e39693bf1a85fc4eab10fdb243c7d33fd18f9c766"},"id":"3b9465ac-eca1-4923-84e6-4624bd41ab0b","version":3}` +export const KEYSTORE_NAME1 = 'PK1 keystore name' +export const ADDRESS1 = normalizeAddressWith0x(privateKeyToAddress(PK1)) +export const PASSPHRASE2 = 'test-password2 !!' +export const PK2 = 'bb6f3fa4a83b7b06e72e580a3b09df5dd6fb4fa745ee2b0d865413ad6299e64e' +export const KEYSTORE_NAME2 = 'PK2 keystore name' +export const GETH_GEN_KEYSTORE2 = `{"address":"b81a82696018fd9d8b43431966b60c31bdcdc2e8","blspublickey":"b9f862e2ced58bb2eef8ffde7020189ab2bb050603630eceec9b80c1636d98f8c3b9bd517d673937a0551c3a0698a00086bda4db1f0d859912a91988775ae388886013e7eb254d195871f9ced6643e288755da0b483ebe6dda448fea2eb75481","crypto":{"cipher":"aes-128-ctr","ciphertext":"6f3cd02b2d3d81b2bbf76743396c9c3c1685ddc6cfafbba34195ab03476831d3","cipherparams":{"iv":"af1f9853e0ff20ee5d495cf7d9461e1c"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"cf0446914e5d214f2a312c08ef24e7e3dd15e948d2ca67d59b3bb97903a96147"},"mac":"58348f6d843d28b3ac8cf40542d10da198016f28f132c32389ab56a945c858e1"},"id":"b224dac6-c089-4b47-8557-e04ae60b3506","version":3}` +export const ADDRESS2 = normalizeAddressWith0x(privateKeyToAddress(PK2)) diff --git a/packages/sdk/keystores/tsconfig.json b/packages/sdk/keystores/tsconfig.json new file mode 100644 index 00000000000..25d848de425 --- /dev/null +++ b/packages/sdk/keystores/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@celo/typescript/tsconfig.library.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib" + }, + "include": ["src/**/*", "types/**/*"] +} diff --git a/packages/sdk/keystores/tslint.json b/packages/sdk/keystores/tslint.json new file mode 100644 index 00000000000..2c11b3bf4e1 --- /dev/null +++ b/packages/sdk/keystores/tslint.json @@ -0,0 +1,8 @@ +{ + "extends": ["@celo/typescript/tslint.json"], + "rules": { + "no-global-arrow-functions": false, + "member-ordering": false, + "max-classes-per-file": false + } +} diff --git a/packages/sdk/keystores/typedoc.json b/packages/sdk/keystores/typedoc.json new file mode 100644 index 00000000000..3ef0cc1240d --- /dev/null +++ b/packages/sdk/keystores/typedoc.json @@ -0,0 +1,13 @@ +{ + "mode": "modules", + "exclude": ["**/*+(index|.test).ts"], + "excludeNotExported": true, + "excludePrivate": true, + "excludeProtected": true, + "includeDeclarations": false, + "ignoreCompilerErrors": true, + "hideGenerator": "true", + "theme": "gitbook", + "out": "../../../docs/developer-resources/keystores/reference", + "gitRevision": "master" +} diff --git a/yarn.lock b/yarn.lock index aa44acccd57..c023b94f101 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11524,6 +11524,20 @@ ethereumjs-wallet@0.6.4, ethereumjs-wallet@^0.6.0, ethereumjs-wallet@^0.6.3: utf8 "^3.0.0" uuid "^3.3.2" +ethereumjs-wallet@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-1.0.1.tgz#664a4bcacfc1291ca2703de066df1178938dba1c" + integrity sha512-3Z5g1hG1das0JWU6cQ9HWWTY2nt9nXCcwj7eXVNAHKbo00XAZO8+NHlwdgXDWrL0SXVQMvTWN8Q/82DRH/JhPw== + dependencies: + aes-js "^3.1.1" + bs58check "^2.1.2" + ethereum-cryptography "^0.1.3" + ethereumjs-util "^7.0.2" + randombytes "^2.0.6" + scrypt-js "^3.0.1" + utf8 "^3.0.0" + uuid "^3.3.2" + ethers@4.0.0-beta.3: version "4.0.0-beta.3" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.0-beta.3.tgz#15bef14e57e94ecbeb7f9b39dd0a4bd435bc9066"