KeyStore = async ({ storage, path } = {}) => {
  /**
   * @namespace module:KeyStore~KeyStore
   * @description The instance returned by {@link module:KeyStore}.
   */

  // Persistent storage for keys
  storage = storage || await ComposedStorage(await LRUStorage({ size: 1000 }), await LevelStorage({ path: path || defaultPath }))

  // Cache for deserialized/unmarshaled keys
  const keyCache = await LRUStorage({ size: 1000 })

  /**
   * Closes the KeyStore's underlying storage.
   * @memberof module:KeyStore~KeyStore
   * @async
   * @instance
   */
  const close = async () => {
    await storage.close()
    await keyCache.close()
  }

  /**
   * Clears the KeyStore's underlying storage.
   * @memberof module:KeyStore~KeyStore
   * @async
   * @instance
   */
  const clear = async () => {
    await storage.clear()
    await keyCache.clear()
  }

  /**
   * Checks if a key exists in the key store .
   * @param {string} id The id of an [Identity]{@link module:Identities~Identity} to check the key for.
   * @return {boolean} True if the key exists, false otherwise.
   * @throws id needed to check a key if no id is specified.
   * @memberof module:KeyStore~KeyStore
   * @async
   * @instance
   */
  const hasKey = async (id) => {
    if (!id) {
      throw new Error('id needed to check a key')
    }

    let hasKey = false
    let key = await keyCache.get(id)
    if (key) {
      hasKey = true
    } else {
      try {
        key = await storage.get('private_' + id)
        hasKey = key !== undefined && key !== null
      } catch (e) {
        // Catches 'Error: ENOENT: no such file or directory, open <path>'
        console.error('Error: ENOENT: no such file or directory')
      }
    }

    return hasKey
  }

  /**
   * Adds a private key to the keystore.
   * @param {string} id An id of the [Identity]{@link module:Identities~Identity} to whom the key belongs to.
   * @param {Uint8Array} key The private key to store.
   * @memberof module:KeyStore~KeyStore
   * @async
   * @instance
   */
  const addKey = async (id, key) => {
    const { privateKey } = key
    await storage.put('private_' + id, privateKey)
    // Unmarshal the key and add it to the cache
    const unmarshaledPrivateKey = privateKeyFromRaw(privateKey)
    await keyCache.put(id, unmarshaledPrivateKey)
  }

  /**
   * Creates a key pair and stores it to the keystore.
   * @param {string} id An id of the [Identity]{@link module:Identities~Identity} to generate the key pair for.
   * @throws id needed to create a key if no id is specified.
   * @memberof module:KeyStore~KeyStore
   * @async
   * @instance
   */
  const createKey = async (id) => {
    if (!id) {
      throw new Error('id needed to create a key')
    }

    // Generate a private key
    const keyPair = await generateKeyPair('secp256k1')

    const key = {
      publicKey: keyPair.publicKey.raw,
      privateKey: keyPair.raw
    }

    await addKey(id, key)

    return keyPair
  }

  /**
   * Gets a key from keystore.
   * @param {string} id An id of the [Identity]{@link module:Identities~Identity} whose key to retrieve.
   * @return {Uint8Array} The key specified by id.
   * @throws id needed to get a key if no id is specified.
   * @memberof module:KeyStore~KeyStore
   * @async
   * @instance
   */
  const getKey = async (id) => {
    if (!id) {
      throw new Error('id needed to get a key')
    }

    let key = await keyCache.get(id)

    if (!key) {
      let storedKey
      try {
        storedKey = await storage.get('private_' + id)
      } catch (e) {
        // ignore ENOENT error
      }

      if (!storedKey) {
        return
      }

      key = privateKeyFromRaw(storedKey)

      await keyCache.put(id, key)
    }

    return key
  }

  /**
   * Gets the serialized public key from a key pair.
   * @param {*} keys A key pair.
   * @param {Object} options One or more options.
   * @param {Object} [options.format=hex] The format the public key should be
   * returned in.
   * @return {Uint8Array|String} The public key.
   * @throws Supported formats are `hex` and `buffer` if an invalid format is
   * passed in options.
   * @memberof module:KeyStore~KeyStore
   * @async
   * @instance
   */
  const getPublic = (keys, options = {}) => {
    const formats = ['hex', 'buffer']
    const format = options.format || 'hex'
    if (formats.indexOf(format) === -1) {
      throw new Error('Supported formats are `hex` and `buffer`')
    }

    const pubKey = keys.publicKey.raw

    return format === 'buffer' ? pubKey : uint8ArrayToString(pubKey, 'base16')
  }

  return {
    clear,
    close,
    hasKey,
    addKey,
    createKey,
    getKey,
    getPublic
  }
}
Creates an instance of KeyStore.
@param {Object} params One or more parameters for configuring KeyStore.
@param {Object} [params.storage] An instance of a storage class. Can be one
of ComposedStorage, IPFSBlockStorage, LevelStorage, etc. Defaults to
ComposedStorage.
@param {string} [params.path=./keystore] The path to a valid storage.
@return {module:KeyStore~KeyStore} An instance of KeyStore.
@instance