UNPKG

@orbitdb/core

Version:

Distributed p2p database on IPFS

186 lines (168 loc) 5.84 kB
import Clock from './clock.js' import * as Block from 'multiformats/block' import * as dagCbor from '@ipld/dag-cbor' import { sha256 } from 'multiformats/hashes/sha2' import { base58btc } from 'multiformats/bases/base58' const codec = dagCbor const hasher = sha256 const hashStringEncoding = base58btc /** * @typedef {Object} module:Log~Entry * @property {string} id A string linking multiple entries together. * @property {*} payload An arbitrary chunk of data. * @property {Array<string>} next One or more hashes pointing to the next entries in a chain of * entries. * @property {Array<string>} refs One or more hashes which reference other entries in the chain. * @property {Clock} clock A logical clock. See {@link module:Log~Clock}. * @property {integer} v The version of the entry. * @property {string} key The public key of the identity. * @property {string} identity The identity of the entry's owner. * @property {string} sig The signature of the entry signed by the owner. */ /** * Creates an Entry. * @param {module:Identities~Identity} identity The identity instance * @param {string} logId The unique identifier for this log * @param {*} data Data of the entry to be added. Can be any JSON.stringifyable * data. * @param {module:Log~Clock} [clock] The clock * @param {Array<string|Entry>} [next=[]] An array of CIDs as base58btc encoded * strings which point to the next entries in a chain of entries. * @param {Array<string|module:Log~Entry>} [refs=[]] An array of CIDs as * base58btc encoded strings pointing to various entries which come before * this entry. * @return {Promise<module:Log~Entry>} A promise which contains an instance of * Entry. * Entry consists of the following properties: * * - id: A string linking multiple entries together, * - payload: An arbitrary chunk of data, * - next: One or more hashes pointing to the next entries in a chain of * entries, * - refs: One or more hashes which reference other entries in the chain, * - clock: A logical clock. See {@link module:Log~Clock}, * - v: The version of the entry, * - key: The public key of the identity, * - identity: The identity of the entry's owner, * - sig: The signature of the entry signed by the owner. * @memberof module:Log~Entry * @example * const entry = await Entry.create(identity, 'log1', 'hello') * console.log(entry) * // { payload: "hello", next: [], ... } * @private */ const create = async (identity, id, payload, clock = null, next = [], refs = []) => { if (identity == null) throw new Error('Identity is required, cannot create entry') if (id == null) throw new Error('Entry requires an id') if (payload == null) throw new Error('Entry requires a payload') if (next == null || !Array.isArray(next)) throw new Error("'next' argument is not an array") clock = clock || Clock(identity.publicKey) const entry = { id, // For determining a unique chain payload, // Can be any dag-cbor encodeable data next, // Array of strings of CIDs refs, // Array of strings of CIDs clock, // Clock v: 2 // To tag the version of this data structure } const { bytes } = await Block.encode({ value: entry, codec, hasher }) const signature = await identity.sign(identity, bytes) entry.key = identity.publicKey entry.identity = identity.hash entry.sig = signature return encode(entry) } /** * Verifies an entry signature. * @param {Identities} identities Identities system to use * @param {module:Log~Entry} entry The entry being verified * @return {Promise<boolean>} A promise that resolves to a boolean value indicating if * the signature is valid. * @memberof module:Log~Entry * @private */ const verify = async (identities, entry) => { if (!identities) throw new Error('Identities is required, cannot verify entry') if (!isEntry(entry)) throw new Error('Invalid Log entry') if (!entry.key) throw new Error("Entry doesn't have a key") if (!entry.sig) throw new Error("Entry doesn't have a signature") const value = { id: entry.id, payload: entry.payload, next: entry.next, refs: entry.refs, clock: entry.clock, v: entry.v } const { bytes } = await Block.encode({ value, codec, hasher }) return identities.verify(entry.sig, entry.key, bytes) } /** * Checks if an object is an Entry. * @param {module:Log~Entry} obj * @return {boolean} * @memberof module:Log~Entry * @private */ const isEntry = (obj) => { return obj && obj.id !== undefined && obj.next !== undefined && obj.payload !== undefined && obj.v !== undefined && obj.clock !== undefined && obj.refs !== undefined } /** * Determines whether two entries are equal. * @param {module:Log~Entry} a An entry to compare. * @param {module:Log~Entry} b An entry to compare. * @return {boolean} True if a and b are equal, false otherwise. * @memberof module:Log~Entry * @private */ const isEqual = (a, b) => { return a && b && a.hash === b.hash } /** * Decodes a serialized Entry from bytes * @param {Uint8Array} bytes * @return {module:Log~Entry} * @memberof module:Log~Entry * @private */ const decode = async (bytes) => { const { cid, value } = await Block.decode({ bytes, codec, hasher }) const hash = cid.toString(hashStringEncoding) return { ...value, hash, bytes } } /** * Encodes an Entry and adds bytes field to it * @param {Entry} entry * @return {module:Log~Entry} * @memberof module:Log~Entry * @private */ const encode = async (entry) => { const { cid, bytes } = await Block.encode({ value: entry, codec, hasher }) const hash = cid.toString(hashStringEncoding) const clock = Clock(entry.clock.id, entry.clock.time) return { ...entry, clock, hash, bytes } } export default { create, verify, decode, encode, isEntry, isEqual }