/**
* @module Identities
* @description
* Identities provides a framework for generating and managing identity
* details and providers.
*/
import Identity, { isIdentity, isEqual, decodeIdentity } from './identity.js'
import { getIdentityProvider } from './providers/index.js'
// import DIDIdentityProvider from './identity-providers/did.js'
// import EthIdentityProvider from './identity-providers/ethereum.js'
import KeyStore, { signMessage, verifyMessage } from '../key-store.js'
import { LRUStorage, IPFSBlockStorage, MemoryStorage, ComposedStorage } from '../storage/index.js'
import pathJoin from '../utils/path-join.js'
const DefaultIdentityKeysPath = pathJoin('./orbitdb', 'identities')
/**
* Creates an instance of Identities.
* @function
* @param {Object} params One or more parameters for configuring Identities.
* @param {module:KeyStore} [params.keystore] A preconfigured KeyStore.
* A KeyStore will be created in the path defined by the path param. If neither
* Keystore nor path are defined, a new KeyStore is stored in ./orbitdb
* identities.
* @param {string} [params.path] The path to a KeyStore. If no path is
* provided, the default is ./orbitdb/identities.
* @param {module:Storage} [params.storage] An instance of a compatible storage
* module.
* @param {IPFS} [params.ipfs] An instance of IPFS. This param is not required
* if storage is provided.
* @return {module:Identities~Identities} An instance of Identities.
* @instance
*/
const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
/**
* @namespace module:Identities~Identities
* @description The instance returned by {@link module:Identities}.
*/
keystore = keystore || await KeyStore({ path: path || DefaultIdentityKeysPath })
if (!storage) {
storage = ipfs
? await ComposedStorage(await LRUStorage({ size: 1000 }), await IPFSBlockStorage({ ipfs, pin: true }))
: await MemoryStorage()
}
const verifiedIdentitiesCache = await LRUStorage({ size: 1000 })
/**
* Gets an identity by hash.
* @param {string} hash An identity hash.
* @return {module:Identities~Identity} An instance of identity.
* @memberof module:Identities~Identities
* @instance
*/
const getIdentity = async (hash) => {
const bytes = await storage.get(hash)
if (bytes) {
return decodeIdentity(bytes)
}
}
/**
* Creates an identity, adding it to storage.
* @param {Object} options Various options for configuring a new identity.
* @param {Function} [options.provider=PublicKeyIdentityProvider()] An instance of the Provider to use for generating an identity, e.g. PublicKeyIdentityProvider({ keystore })
* @return {module:Identities~Identity} An instance of identity.
* @memberof module:Identities~Identities
* @instance
*/
const createIdentity = async (options = {}) => {
options.keystore = keystore
const DefaultIdentityProvider = getIdentityProvider('publickey')
const identityProviderInit = options.provider || DefaultIdentityProvider({ keystore })
const identityProvider = await identityProviderInit()
if (!getIdentityProvider(identityProvider.type)) {
throw new Error('Identity provider is unknown. Use useIdentityProvider(provider) to register the identity provider')
}
const id = await identityProvider.getId(options)
const privateKey = await keystore.getKey(id) || await keystore.createKey(id)
const publicKey = keystore.getPublic(privateKey)
const idSignature = await signMessage(privateKey, id)
const publicKeyAndIdSignature = await identityProvider.signIdentity(publicKey + idSignature, options)
const signatures = {
id: idSignature,
publicKey: publicKeyAndIdSignature
}
const identity = await Identity({ id, publicKey, signatures, type: identityProvider.type, sign, verify })
await storage.put(identity.hash, identity.bytes)
return identity
}
/**
* Verifies an identity using the identity's provider.
* @param {module:Identities~Identity} identity The identity to verify.
* @return {boolean} True the identity is valid, false otherwise.
* @memberof module:Identities~Identities
*/
const verifyIdentity = async (identity) => {
if (!isIdentity(identity)) {
return false
}
const { id, publicKey, signatures } = identity
const idSignatureVerified = await verify(signatures.id, publicKey, id)
if (!idSignatureVerified) {
return false
}
const verifiedIdentity = await verifiedIdentitiesCache.get(signatures.id)
if (verifiedIdentity) {
return isEqual(identity, verifiedIdentity)
}
const Provider = getIdentityProvider(identity.type)
const identityVerified = await Provider.verifyIdentity(identity)
if (identityVerified) {
await verifiedIdentitiesCache.put(signatures.id, identity)
}
return identityVerified
}
/**
* Signs data using an identity.
* @param {module:Identities~Identity} identity The identity to use for
* signing.
* @param {string} data The data to sign.
* @return {string} The signed data.
* @throws Private signing key not found from KeyStore when no signing key can
* be retrieved.
* @memberof module:Identities~Identities
* @instance
* @private
*/
const sign = async (identity, data) => {
const signingKey = await keystore.getKey(identity.id)
if (!signingKey) {
throw new Error('Private signing key not found from KeyStore')
}
return await signMessage(signingKey, data)
}
/**
* Verifies data using a valid signature and publicKey.
* @param {string} signature A signature.
* @param {string} publicKey A public key.
* @param {string} data The data to be verified.
* @return {boolean} True if the the data is signed by the publicKey, false
* otherwise.
* @memberof module:Identities~Identities
* @instance
* @private
*/
const verify = async (signature, publicKey, data) => {
return await verifyMessage(signature, publicKey, data)
}
return {
createIdentity,
verifyIdentity,
getIdentity,
sign,
verify,
keystore
}
}
export {
Identities as default
}