Using Keystores Library for Local Key Management
#
OverviewThis is a JavaScript library that provides functions for creating and interacting with encrypted keystores for private key management. To do this, this library wraps the existing ethereumjs-wallet
library, which is a standard library for managing keystores according to the Web3 Secret Storage Definition. As specified, secrets are encrypted using the Scrypt KDF (Key Derivation Function); in this case, the private key is encrypted with a passphrase (that should be kept secret) and can be decrypted later by the same phrase. Note that a keystore generated for the same (private key, passphrase) multiple times will not yield the same output due to how the KDF works. Keystore files generated by a geth node can be decrypted and accessed with this library, and vice versa.
Note that keystore files generated by this library do not contain BLS public keys, meaning that these should not be used for validator signer keys used in consensus.
The components of the library are roughly as follows:
KeystoreBase
which wraps the functionality ofethereumjs-wallet
and exposes functions to:- import PKs (into encrypted keystores)
- decrypt and get a PK from an encrypted keystore
- change the passphrase on a keystore
FileKeystore
,InMemoryKeystore
which specifiy the IO in addition to the above base classKeystoreWalletWrapper
: (not stable; likely to structurally change) a very simple wrapper for aKeystore
andLocalWallet
, which allows a user to decrypt a keystore and pass the key to theLocalWallet
in order to sign transactions.
#
Usagewarning
For accounts containing significant funds or otherwise requiring a high degree of security, we do not recommend this keystore library! This is only for managing keys for low-risk hot wallets and signers.
For more stringent security requirements, check out the guide to Choosing a Wallet.
Depending on your use case, you can either interact directly with the FileKeystore
(purely for creating and interacting with keystore files, importing or accessing private keys) or else use the KeystoreWalletWrapper
(combines the keystore functionality with convenient access to the LocalWallet
for signing tranactions).
#
Using the FileKeystore#
Create new keystore and import private keyThis snippet will create a keystore
directory in the parentDirectory
and create an encrypted file in the keystore
directory containing the private key. Note that you can only create a new encrypted file for a private key if there is not already an existing file for that private key. If it already exists, you can change the passphrase (see below), but you may not have multiple files for the same private key in the same keystore
directory.
import * as readline from "readline";import { FileKeystore } from "@celo/keystores";
// This is the directory that will contain a "keystore" directoryconst parentDirectory = "<INSERT_PATH_HERE>";// This creates a "keystore" directory if one does not already exist in the parentDirectoryconst keystore = new FileKeystore(parentDirectory);
// Prompt to enter private key and passphrase on the command linelet rl = readline.createInterface({ input: process.stdin, output: process.stdout,});const privateKey: string = await new Promise((resolve) => rl.question("Enter private key:", (answer) => { resolve(answer); }));const passphrase: string = await new Promise((resolve) => rl.question("Enter secret passphrase:", (answer) => { rl.close(); resolve(answer); }));// Import private key into the keystore, which is then stored as an encrypted file// Should create a file with a name like `UTC-<DATETIME>-<ACCOUNT_ADDRESS>`await keystore.importPrivateKey(privateKey, passphrase);// Retrieve all addresses contained in the keystoreconsole.log("Addresses in keystore: ", await keystore.listKeystoreAddresses());
#
Accessing an existing keystore file// Keystore already existsconst parentDirectory = '<INSERT_PATH_HERE>'const keystore = new FileKeystore(parentDirectory)const address = '<YOUR_ADDRESS_HERE>'const oldPassphrase = '<OLD_PASSPHRASE>'
// Decrypt file and retrieve private keyawait keystore.getPrivateKey(address, oldPassphrase)
// Change the passphrase encrypting the fileconst newPassphrase = '<NEW_PASSPHRASE>'await.keystore.changeKeystorePassphrase(address, oldPassphrase, newPassphrase)
// Decrypt file and retrieve private key using new passphraseconsole.log(await keystore.getPrivateKey(address, newPassphrase))
#
Remove (delete) a keystore file for a particular addressconst parentDirectory = "<INSERT_PATH_HERE>";const keystore = new FileKeystore(parentDirectory);const address = "<YOUR_ADDRESS_HERE>";
// When you know the address// Get the filename (keystore name)const keystoreName = await keystore.getKeystoreName(address);await keystore.removeKeystore(keystoreName);
// Alternatively, you can do this by passing in the filename directlykeystore.removeKeystore("<KEYSTORE_FILENAME_TO_DELETE>");
#
Using the KeystoreWalletWrapperThis example will instantiate a KeystoreWalletWrapper
, import a private key, and use the inner LocalWallet
within the wrapper to sign and send a transaction with ContractKit
.
import { newKit } from "@celo/contractkit";import { FileKeystore, KeystoreWalletWrapper } from "@celo/keystores";
// This is the directory that will contain a "keystore" directoryconst parentDirectory = "/celo/celo-monorepo/packages/sdk/wallets/wallet-keystore/test-keystore-dir";// Instantiate a KeystoreWalletWrapper using a FileKeystoreconst keystoreWalletWrapper = new KeystoreWalletWrapper( new FileKeystore(parentDirectory));// Make sure to not commit this if using real funds!// You can also get this as input on the command-line using `readline`// as in the example above for FileKeystoreconst privateKey = "YOUR_TEST_PRIVATE_KEY";const passphrase = "test-passphrase1! ";
// Import private key or unlock accountawait keystoreWalletWrapper.importPrivateKey(privateKey, passphrase);// If the keystore file already exists for an address, simply unlock:// const address = 'YOUR_TEST_ADDRESS'// await keystoreWalletWrapper.unlockAccount(address, passphrase)
// Get the wrapper's `LocalWallet` instance and pass this into ContractKitconst wallet = keystoreWalletWrapper.getLocalWallet();const kit = newKit("https://alfajores-forno.celo-testnet.org", wallet);const [from] = wallet.getAccounts();
// Send a test transactionconst gold = await kit.contracts.getGoldToken();await gold .transfer("0x22579ca45ee22e2e16ddf72d955d6cf4c767b0ef", "1") .sendAndWaitForReceipt({ from });console.log("Transaction sent!");