feat: Basic asymmetric encryption using an OrbitDB identity.

This commit is contained in:
Hayden Young 2024-05-01 15:44:57 +01:00
parent bd1eb71e44
commit ed2c5e7d8c
5 changed files with 1309 additions and 13 deletions

1278
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
"dependencies": { "dependencies": {
"@ipld/dag-cbor": "^9.0.6", "@ipld/dag-cbor": "^9.0.6",
"@libp2p/crypto": "^3.0.2", "@libp2p/crypto": "^3.0.2",
"eth-crypto": "^2.6.0",
"it-pipe": "^3.0.1", "it-pipe": "^3.0.1",
"level": "^8.0.0", "level": "^8.0.0",
"lru": "^3.1.0", "lru": "^3.1.0",

View File

@ -56,7 +56,7 @@ const DefaultAccessController = async () => {
* @memberof module:Log * @memberof module:Log
* @instance * @instance
*/ */
const Log = async (identity, { logId, logHeads, access, entryStorage, headsStorage, indexStorage, sortFn } = {}) => { const Log = async (identity, { logId, logHeads, access, entryStorage, headsStorage, indexStorage, sortFn, encryptFn, decryptFn } = {}) => {
/** /**
* @namespace Log * @namespace Log
* @description The instance returned by {@link module:Log} * @description The instance returned by {@link module:Log}
@ -82,6 +82,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
const _heads = await Heads({ storage: headsStorage, heads: logHeads }) const _heads = await Heads({ storage: headsStorage, heads: logHeads })
// Conflict-resolution sorting function // Conflict-resolution sorting function
sortFn = NoZeroes(sortFn || LastWriteWins) sortFn = NoZeroes(sortFn || LastWriteWins)
// Internal queues for processing appends and joins in their call-order // Internal queues for processing appends and joins in their call-order
const appendQueue = new PQueue({ concurrency: 1 }) const appendQueue = new PQueue({ concurrency: 1 })
const joinQueue = new PQueue({ concurrency: 1 }) const joinQueue = new PQueue({ concurrency: 1 })
@ -137,6 +138,11 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
const bytes = await _entries.get(hash) const bytes = await _entries.get(hash)
if (bytes) { if (bytes) {
const entry = await Entry.decode(bytes) const entry = await Entry.decode(bytes)
if (decryptFn) {
entry.payload = await decryptFn(entry.payload)
}
return entry return entry
} }
} }
@ -169,6 +175,11 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
// Get references (pointers) to multiple entries in the past // Get references (pointers) to multiple entries in the past
// (skips the heads which are covered by the next field) // (skips the heads which are covered by the next field)
const refs = await getReferences(heads_, options.referencesCount + heads_.length) const refs = await getReferences(heads_, options.referencesCount + heads_.length)
if (encryptFn) {
data = await encryptFn(data)
}
// Create the entry // Create the entry
const entry = await Entry.create( const entry = await Entry.create(
identity, identity,

View File

@ -3,6 +3,7 @@ import { rimraf } from 'rimraf'
import { copy } from 'fs-extra' import { copy } from 'fs-extra'
import { Log, Entry, Identities, KeyStore, MemoryStorage } from '../../src/index.js' import { Log, Entry, Identities, KeyStore, MemoryStorage } from '../../src/index.js'
import testKeysPath from '../fixtures/test-keys-path.js' import testKeysPath from '../fixtures/test-keys-path.js'
import { encrypt, decrypt } from '../utils/encrypt.js'
const { create } = Entry const { create } = Entry
@ -142,5 +143,14 @@ describe('Log', function () {
strictEqual(values[1].payload, 'hello2') strictEqual(values[1].payload, 'hello2')
strictEqual(values[2].payload, 'hello3') strictEqual(values[2].payload, 'hello3')
}) })
it('encrypts the value of an entry in the log', async () => {
const encryptFn = encrypt({ identity: testIdentity })
const decryptFn = decrypt({ identities, identity: testIdentity })
const log = await Log(testIdentity, { encryptFn, decryptFn })
const entry = await log.append('hello1')
const value = await log.get(entry.hash)
strictEqual(value.payload, 'hello1')
})
}) })
}) })

20
test/utils/encrypt.js Normal file
View File

@ -0,0 +1,20 @@
import EthCrypto from 'eth-crypto'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
const encrypt = ({ identity }) => async (value) => {
const encryptedObj = await EthCrypto.encryptWithPublicKey(identity.publicKey, value)
return EthCrypto.cipher.stringify(encryptedObj)
}
const decrypt = ({ identities, identity }) => async (value) => {
const privateKey = await identities.keystore.getKey(identity.id)
const privateKeyStr = uint8ArrayToString(privateKey.marshal(), 'base16')
const encryptedObj = EthCrypto.cipher.parse(value)
return await EthCrypto.decryptWithPrivateKey(privateKeyStr, encryptedObj)
}
export {
encrypt,
decrypt
}