diff --git a/src/key-store.js b/src/key-store.js index cc4ced1..04c6e3a 100644 --- a/src/key-store.js +++ b/src/key-store.js @@ -74,8 +74,17 @@ const verifyMessage = async (signature, publicKey, data) => { return res } +const defaultPath = './keystore' + +/** + * Creates an instance of KeyStore. + * @param {Object} options Various options to use when instantiating KeyStore. + * @param {Object} options.storage An instance of a storage class. Can be one of ComposedStorage, IPFSBlockStorage, LevelStorage, etc. Defaults to ComposedStorage. + * @param {string} options.path The path to a valid storage. Defaults to ./keystore. + * @return {KeyStore} An instance of KeyStore. + */ const KeyStore = async ({ storage, path } = {}) => { - storage = storage || await ComposedStorage(await LevelStorage({ path: path || './keystore' }), await LRUStorage({ size: 1000 })) + storage = storage || await ComposedStorage(await LRUStorage({ size: 1000 }), await LevelStorage({ path: path || defaultPath })) const close = async () => { await storage.close() @@ -103,12 +112,8 @@ const KeyStore = async ({ storage, path } = {}) => { } const addKey = async (id, key) => { - try { - await storage.put('public_' + id, key.publicKey) - await storage.put('private_' + id, key.privateKey) - } catch (e) { - console.log(e) - } + await storage.put('public_' + id, key.publicKey) + await storage.put('private_' + id, key.privateKey) } const createKey = async (id, { entropy } = {}) => { diff --git a/test/counterdb.test.js b/test/counterdb.test.js deleted file mode 100644 index 3591dc6..0000000 --- a/test/counterdb.test.js +++ /dev/null @@ -1,176 +0,0 @@ -// import assert from 'assert' -// import mapSeries from 'p-each-series' -// import rmrf from 'rimraf' -// import OrbitDB from '../src/OrbitDB.js' -// // Include test utilities -// import { -// config, -// startIpfs, -// stopIpfs, -// testAPIs, -// connectPeers, -// waitForPeers -// } from 'orbit-db-test-utils' - -// const orbitdbPath1 = './orbitdb/tests/counters/1' -// const orbitdbPath2 = './orbitdb/tests/counters/2' -// const dbPath1 = './orbitdb/tests/counters/db1' -// const dbPath2 = './orbitdb/tests/counters/db2' - -// Object.keys(testAPIs).forEach(API => { -// describe(`orbit-db - Counters (${API})`, function () { -// this.timeout(config.timeout) - -// let orbitdb1, orbitdb2 -// let ipfsd1, ipfsd2, ipfs1, ipfs2 - -// before(async () => { -// rmrf.sync(dbPath1) -// rmrf.sync(dbPath2) -// rmrf.sync(orbitdbPath1) -// rmrf.sync(orbitdbPath2) -// ipfsd1 = await startIpfs(API, config.daemon1) -// ipfsd2 = await startIpfs(API, config.daemon2) -// ipfs1 = ipfsd1.api -// ipfs2 = ipfsd2.api -// // Connect the peers manually to speed up test times -// const isLocalhostAddress = (addr) => addr.toString().includes('127.0.0.1') -// await connectPeers(ipfs1, ipfs2, { filter: isLocalhostAddress }) -// }) - -// after(async () => { -// if (orbitdb1) -// await orbitdb1.stop() - -// if (orbitdb2) -// await orbitdb2.stop() - -// if (ipfsd1) -// await stopIpfs(ipfsd1) - -// if (ipfsd2) -// await stopIpfs(ipfsd2) -// }) - -// beforeEach(async () => { -// orbitdb1 = await OrbitDB.createInstance(ipfs1, { directory: './orbitdb/1' }) -// orbitdb2 = await OrbitDB.createInstance(ipfs2, { directory: './orbitdb/2' }) -// }) - -// afterEach(async () => { -// if (orbitdb1) -// await orbitdb1.stop() - -// if (orbitdb2) -// await orbitdb2.stop() -// }) - -// describe('counters', function() { -// let address - -// it('creates and opens a database', async () => { -// const db = await orbitdb1.counter('counter database') -// assert.notEqual(db, null) -// assert.equal(db.type, 'counter') -// assert.equal(db.dbname, 'counter database') -// }) - -// it('value is zero when it\'s a fresh database', async () => { -// const db = await orbitdb1.counter('counter database') -// assert.equal(db.value, 0) -// await db.close() -// }) - -// it('increases a counter value', async () => { -// const counter = await orbitdb1.counter('counter test', { path: dbPath1 }) -// address = counter.address.toString() -// await mapSeries([13, 1], (f) => counter.inc(f)) -// assert.equal(counter.value, 14) -// await counter.close() -// }) - -// it('opens a saved counter', async () => { -// const counter = await orbitdb1.counter(address, { path: dbPath1 }) -// await counter.load() -// assert.equal(counter.value, 14) -// await counter.drop() -// }) - -// it('syncs counters', async () => { -// console.log("Sync counters") - -// let options = { -// accessController: { -// // Set write access for both clients -// write: [ -// orbitdb1.identity.id, -// orbitdb2.identity.id -// ] -// } -// } - -// const dbName = new Date().getTime().toString() - -// const numbers = [[13, 10], [2, 5]] -// const increaseCounter = (counterDB, i) => mapSeries(numbers[i], n => counterDB.inc(n)) - -// // Create a new counter database in the first client -// options = Object.assign({}, options, { path: dbPath1 }) -// const counter1 = await orbitdb1.counter(dbName, options) - -// // Open the database in the second client -// options = Object.assign({}, options, { path: dbPath2 }) -// const counter2 = await orbitdb2.counter(dbName, options) - -// // Make sure database addresses match since they're built deterministically -// assert.equal(counter1.address.toString(), counter2.address.toString()) - -// // Wait for peers to connect -// console.log("Waiting for peers to connect") -// await waitForPeers(ipfs1, [orbitdb2.id], counter1.address.toString()) -// await waitForPeers(ipfs2, [orbitdb1.id], counter1.address.toString()) - -// let finished1 = counter1.value === 30 -// let finished2 = counter2.value === 30 - -// counter1.events.on('replicated', () => { -// finished1 = (counter1.value === 30) -// finished2 = (counter2.value === 30) -// }) -// counter2.events.on('replicated', () => { -// finished1 = (counter1.value === 30) -// finished2 = (counter2.value === 30) -// }) -// counter1.events.on('write', () => { -// finished1 = (counter1.value === 30) -// finished2 = (counter2.value === 30) -// }) -// counter2.events.on('write', () => { -// finished1 = (counter1.value === 30) -// finished2 = (counter2.value === 30) -// }) - -// // Increase the counters sequentially -// await mapSeries([counter1, counter2], increaseCounter) -// console.log("Waiting for replication to finish") - -// return new Promise((resolve, reject) => { -// let timer = setInterval(async () => { -// if (finished1 && finished2) { -// try { -// clearInterval(timer) -// assert.equal(counter1.value, 30) -// assert.equal(counter2.value, 30) -// await counter1.close() -// await counter2.close() -// resolve() -// } catch (e) { -// reject(e) -// } -// } -// }, 100) -// }) -// }) -// }) -// }) -// }) diff --git a/test/custom-keystore.test.js b/test/custom-keystore.test.js deleted file mode 100644 index 5df81aa..0000000 --- a/test/custom-keystore.test.js +++ /dev/null @@ -1,78 +0,0 @@ -// import assert from 'assert' -// import rmrf from 'rimraf' -// import path from 'path' -// import OrbitDB from '../src/OrbitDB.js' -// import Identities from 'orbit-db-identity-provider' -// // Include test utilities -// import { -// config, -// startIpfs, -// stopIpfs, -// testAPIs, -// } from 'orbit-db-test-utils' - -// import { -// CustomTestKeystore, -// databases, -// } from './utils/index.js' - -// Identities.addIdentityProvider(CustomTestKeystore().identityProvider) - -// const dbPath = './orbitdb/tests/customKeystore' - -// Object.keys(testAPIs).forEach(API => { -// describe(`orbit-db - Use a Custom Keystore (${API})`, function() { -// this.timeout(20000) - -// let ipfsd, ipfs, orbitdb1 - -// before(async () => { -// rmrf.sync(dbPath) -// ipfsd = await startIpfs(API, config.daemon1) -// ipfs = ipfsd.api -// const identity = await Identities.createIdentity({ type: 'custom', keystore: CustomTestKeystore().create() }) -// orbitdb1 = await OrbitDB.createInstance(ipfs, { -// directory: path.join(dbPath, '1'), -// identity -// }) -// }) - -// after(async () => { -// await orbitdb1.stop() -// await stopIpfs(ipfsd) -// }) - -// describe('allows orbit to use a custom keystore with different store types', function() { -// databases.forEach(async (database) => { -// it(database.type + ' allows custom keystore', async () => { -// const db1 = await database.create(orbitdb1, 'custom-keystore') -// await database.tryInsert(db1) - -// assert.deepEqual(database.getTestValue(db1), database.expectedValue) - -// await db1.close() -// }) -// }) -// }) - -// describe('allows a custom keystore to be used with different store and write permissions', function() { -// databases.forEach(async (database) => { -// it(database.type + ' allows custom keystore', async () => { -// const options = { -// accessController: { -// // Set write access for both clients -// write: [orbitdb1.identity.id] -// } -// } - -// const db1 = await database.create(orbitdb1, 'custom-keystore', options) -// await database.tryInsert(db1) - -// assert.deepEqual(database.getTestValue(db1), database.expectedValue) - -// await db1.close() -// }) -// }) -// }) -// }) -// }) diff --git a/test/db/replication/keyvalue-persisted.test.js b/test/db/replication/keyvalue-persisted.test.js new file mode 100644 index 0000000..59406a6 --- /dev/null +++ b/test/db/replication/keyvalue-persisted.test.js @@ -0,0 +1,217 @@ +import { deepStrictEqual } from 'assert' +import rmrf from 'rimraf' +import { copy } from 'fs-extra' +import * as IPFS from 'ipfs' +import { Log, Entry, Database, KeyStore, Identities } from '../../../src/index.js' +import { KeyValue, KeyValuePersisted } from '../../../src/db/index.js' +import config from '../../config.js' +import testKeysPath from '../../fixtures/test-keys-path.js ' +import connectPeers from '../../utils/connect-nodes.js' +import waitFor from '../../utils/wait-for.js' + +const OpLog = { Log, Entry } +const keysPath = './testkeys' + +describe('KeyValue Database Replication', function () { + this.timeout(30000) + + let ipfs1, ipfs2 + let keystore + let identities + let testIdentity1, testIdentity2 + let kv1, kv2 + + const databaseId = 'kv-AAA' + + const accessController = { + canAppend: async (entry) => { + const identity = await identities.getIdentity(entry.identity) + return identity.id === testIdentity1.id + } + } + + before(async () => { + ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' }) + ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' }) + await connectPeers(ipfs1, ipfs2) + + await copy(testKeysPath, keysPath) + keystore = await KeyStore({ path: keysPath }) + identities = await Identities({ keystore }) + testIdentity1 = await identities.createIdentity({ id: 'userA' }) + testIdentity2 = await identities.createIdentity({ id: 'userB' }) + }) + + after(async () => { + if (ipfs1) { + await ipfs1.stop() + } + + if (ipfs2) { + await ipfs2.stop() + } + + if (keystore) { + await keystore.close() + } + + await rmrf(keysPath) + await rmrf('./orbitdb1') + await rmrf('./orbitdb2') + await rmrf('./ipfs1') + await rmrf('./ipfs2') + }) + + afterEach(async () => { + if (kv1) { + await kv1.drop() + await kv1.close() + } + if (kv2) { + await kv2.drop() + await kv2.close() + } + }) + + it('replicates a database', async () => { + let connected = false + let updateCount = 0 + + const onConnected = async (peerId) => { + connected = true + } + + const onUpdate = (entry) => { + ++updateCount + } + + const onError = (err) => { + console.error(err) + } + + kv1 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) + kv2 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) + + kv2.events.on('join', onConnected) + kv1.events.on('join', onConnected) + kv2.events.on('update', onUpdate) + kv2.events.on('error', onError) + kv1.events.on('error', onError) + + await kv1.set('init', true) + await kv1.set('hello', 'friend') + await kv1.del('hello') + await kv1.set('hello', 'friend2') + await kv1.del('hello') + await kv1.set('empty', '') + await kv1.del('empty') + await kv1.set('hello', 'friend3') + + await waitFor(() => connected, () => true) + await waitFor(() => updateCount > 0, () => true) + + const value0 = await kv2.get('init') + deepStrictEqual(value0, true) + + const value2 = await kv2.get('hello') + deepStrictEqual(value2, 'friend3') + + const value1 = await kv1.get('hello') + deepStrictEqual(value1, 'friend3') + + const value9 = await kv1.get('empty') + deepStrictEqual(value9, undefined) + + const all2 = [] + for await (const keyValue of kv2.iterator()) { + all2.push(keyValue) + } + deepStrictEqual(all2, [ + { key: 'hello', value: 'friend3' }, + { key: 'init', value: true } + ]) + + const all1 = [] + for await (const keyValue of kv1.iterator()) { + all1.push(keyValue) + } + deepStrictEqual(all1, [ + { key: 'hello', value: 'friend3' }, + { key: 'init', value: true } + ]) + }) + + it('loads the database after replication', async () => { + let updateCount = 0 + let connected = false + + const onConnected = async (peerId) => { + connected = true + } + + const onUpdate = (entry) => { + ++updateCount + } + + const onError = (err) => { + console.error(err) + } + + kv1 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) + kv2 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) + + kv2.events.on('join', onConnected) + kv1.events.on('join', onConnected) + kv2.events.on('update', onUpdate) + kv2.events.on('error', onError) + kv1.events.on('error', onError) + + await kv1.set('init', true) + await kv1.set('hello', 'friend') + await kv1.del('hello') + await kv1.set('hello', 'friend2') + await kv1.del('hello') + await kv1.set('empty', '') + await kv1.del('empty') + await kv1.set('hello', 'friend3') + + await waitFor(() => connected, () => true) + await waitFor(() => updateCount > 0, () => true) + + await kv1.close() + await kv2.close() + + kv1 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) + kv2 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) + + const value0 = await kv2.get('init') + deepStrictEqual(value0, true) + + const value2 = await kv2.get('hello') + deepStrictEqual(value2, 'friend3') + + const value1 = await kv1.get('hello') + deepStrictEqual(value1, 'friend3') + + const value9 = await kv1.get('empty') + deepStrictEqual(value9, undefined) + + const all2 = [] + for await (const keyValue of kv2.iterator()) { + all2.push(keyValue) + } + deepStrictEqual(all2, [ + { key: 'hello', value: 'friend3' }, + { key: 'init', value: true } + ]) + + const all1 = [] + for await (const keyValue of kv1.iterator()) { + all1.push(keyValue) + } + deepStrictEqual(all1, [ + { key: 'hello', value: 'friend3' }, + { key: 'init', value: true } + ]) + }) +}) diff --git a/test/db/replication/keyvalue.test.js b/test/db/replication/keyvalue.test.js index 59406a6..0120878 100644 --- a/test/db/replication/keyvalue.test.js +++ b/test/db/replication/keyvalue.test.js @@ -3,7 +3,7 @@ import rmrf from 'rimraf' import { copy } from 'fs-extra' import * as IPFS from 'ipfs' import { Log, Entry, Database, KeyStore, Identities } from '../../../src/index.js' -import { KeyValue, KeyValuePersisted } from '../../../src/db/index.js' +import { KeyValue } from '../../../src/db/index.js' import config from '../../config.js' import testKeysPath from '../../fixtures/test-keys-path.js ' import connectPeers from '../../utils/connect-nodes.js' @@ -89,8 +89,8 @@ describe('KeyValue Database Replication', function () { console.error(err) } - kv1 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) - kv2 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) + kv1 = await KeyValue({ OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) + kv2 = await KeyValue({ OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) kv2.events.on('join', onConnected) kv1.events.on('join', onConnected) @@ -157,8 +157,8 @@ describe('KeyValue Database Replication', function () { console.error(err) } - kv1 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) - kv2 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) + kv1 = await KeyValue({ OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) + kv2 = await KeyValue({ OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) kv2.events.on('join', onConnected) kv1.events.on('join', onConnected) @@ -181,8 +181,8 @@ describe('KeyValue Database Replication', function () { await kv1.close() await kv2.close() - kv1 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) - kv2 = await KeyValuePersisted({ KeyValue, OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) + kv1 = await KeyValue({ OpLog, Database, ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' }) + kv2 = await KeyValue({ OpLog, Database, ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' }) const value0 = await kv2.get('init') deepStrictEqual(value0, true) diff --git a/test/key-store.test.js b/test/key-store.test.js index a2e6bd8..5ff4b0f 100644 --- a/test/key-store.test.js +++ b/test/key-store.test.js @@ -1,30 +1,36 @@ import { strictEqual, deepStrictEqual } from 'assert' +import * as crypto from '@libp2p/crypto' +import { Buffer } from 'safe-buffer' import rmrf from 'rimraf' import { copy } from 'fs-extra' import KeyStore, { signMessage, verifyMessage } from '../src/key-store.js' +import LevelStorage from '../src/storage/level.js' import testKeysPath from './fixtures/test-keys-path.js ' +const defaultPath = './keystore' const keysPath = './testkeys' describe('KeyStore', () => { let keystore describe('Creating and retrieving keys', () => { + let id + beforeEach(async () => { - await copy(testKeysPath, keysPath) - keystore = await KeyStore({ path: keysPath }) + keystore = await KeyStore() + + id = 'key1' + await keystore.createKey(id) }) afterEach(async () => { if (keystore) { await keystore.close() + await rmrf(defaultPath) } - await rmrf(keysPath) }) it('creates a key', async () => { - const id = 'key1' - await keystore.createKey(id) const hasKey = await keystore.hasKey(id) strictEqual(hasKey, true) }) @@ -80,6 +86,7 @@ describe('KeyStore', () => { it('gets a key', async () => { const id = 'key1' const keys = await keystore.createKey(id) + deepStrictEqual(await keystore.getKey(id), keys) }) @@ -121,6 +128,101 @@ describe('KeyStore', () => { strictEqual(actual, expected) }) + + it('doesn\'t create a key when keystore is closed', async () => { + let err + await keystore.close() + try { + await keystore.createKey(id) + } catch (e) { + err = e.toString() + } + + strictEqual(err, 'Error: Database is not open') + }) + }) + + describe('Options', () => { + const unmarshal = crypto.keys.supportedKeys.secp256k1.unmarshalSecp256k1PrivateKey + const privateKey = '198594a8de39fd97017d11996d619b3746211605a9d290964badf58bc79bdb33' + const publicKey = '0260baeaffa1de1e4135e5b395e0380563a622b9599d1b8e012a0f7603f516bdaa' + let privateKeyBuffer, publicKeyBuffer, unmarshalledPrivateKey + + before(async () => { + privateKeyBuffer = Buffer.from(privateKey, 'hex') + publicKeyBuffer = Buffer.from(publicKey, 'hex') + unmarshalledPrivateKey = await unmarshal(privateKeyBuffer) + }) + + describe('Using default options', () => { + beforeEach(async () => { + const storage = await LevelStorage({ path: defaultPath }) + await storage.put('private_key1', privateKeyBuffer) + await storage.put('public_key1', publicKeyBuffer) + await storage.close() + + keystore = await KeyStore() + }) + + afterEach(async () => { + if (keystore) { + await keystore.close() + await rmrf(defaultPath) + } + }) + + it('uses default storage and default path to retrieve a key', async () => { + deepStrictEqual(await keystore.getKey('key1'), unmarshalledPrivateKey) + }) + }) + + describe('Setting options.storage', () => { + const path = './custom-level-key-store' + + beforeEach(async () => { + const storage = await LevelStorage({ path }) + await storage.put('private_key2', privateKeyBuffer) + await storage.put('public_key2', publicKeyBuffer) + + keystore = await KeyStore({ storage }) + }) + + afterEach(async () => { + if (keystore) { + await keystore.close() + await rmrf(path) + } + }) + + it('uses the given storage to retrieve a key', async () => { + deepStrictEqual(await keystore.getKey('key2'), unmarshalledPrivateKey) + }) + }) + + describe('Setting options.path', () => { + beforeEach(async () => { + await copy(testKeysPath, keysPath) + + const storage = await LevelStorage({ path: keysPath }) + await storage.put('private_key3', privateKeyBuffer) + await storage.put('public_key3', publicKeyBuffer) + await storage.close() + + keystore = await KeyStore({ path: keysPath }) + }) + + afterEach(async () => { + if (keystore) { + await keystore.close() + } + + await rmrf(keysPath) + }) + + it('uses default storage using given path to retrieve a key', async () => { + deepStrictEqual(await keystore.getKey('key3'), unmarshalledPrivateKey) + }) + }) }) describe('Using keys for signing and verifying', () => {