refactor: wip.

This commit is contained in:
Hayden Young 2023-02-24 19:57:16 +00:00
parent 4b8379cbbb
commit be2d5900de
2 changed files with 104 additions and 88 deletions

View File

@ -1,9 +1,6 @@
import { Level } from 'level'
import * as crypto from '@libp2p/crypto' import * as crypto from '@libp2p/crypto'
import secp256k1 from 'secp256k1' import secp256k1 from 'secp256k1'
import LRU from 'lru'
import { Buffer } from 'safe-buffer' import { Buffer } from 'safe-buffer'
import fs from 'fs'
import pkg from 'elliptic' import pkg from 'elliptic'
const { ec: EC } = pkg const { ec: EC } = pkg
@ -39,159 +36,7 @@ const verifySignature = async (signature, publicKey, data) => {
return Promise.resolve(res) return Promise.resolve(res)
} }
function createStore (path = './keystore') { const sign = async (key, data) => {
if (fs && fs.mkdirSync) {
fs.mkdirSync(path, { recursive: true })
}
return new Level(path, {})
}
// const verifiedCache = new LRU(1000)
export default class KeyStore {
constructor (input = {}) {
if (typeof input === 'string') {
this._store = createStore(input)
} else if (typeof input.open === 'function') {
this._store = input
} else if (typeof input.store === 'string') {
this._store = createStore(input.store)
} else {
this._store = input.store || createStore()
}
this._cache = input.cache || new LRU(100)
}
async open () {
if (!this._store) {
throw new Error('KeyStore: No store found to open')
}
await this._store.open()
}
async close () {
if (!this._store) return
await this._store.close()
}
async hasKey (id) {
if (!id) {
throw new Error('id needed to check a key')
}
if (this._store.status && this._store.status !== 'open') {
return null
}
let hasKey = false
try {
const storedKey = this._cache.get(id) || await this._store.get(id)
hasKey = storedKey !== undefined && storedKey !== null
} catch (e) {
// Catches 'Error: ENOENT: no such file or directory, open <path>'
console.error('Error: ENOENT: no such file or directory')
}
return hasKey
}
async addKey (id, key) {
try {
await this._store.put(id, JSON.stringify(key))
} catch (e) {
console.log(e)
}
this._cache.set(id, key)
}
async createKey (id, { entropy } = {}) {
if (!id) {
throw new Error('id needed to create a key')
}
// if (this._store.status && this._store.status !== 'open') {
// console.log("22::", id)
// return null
// }
// Generate a private key
const privKey = ec.genKeyPair({ entropy }).getPrivate().toArrayLike(Buffer)
// Left pad the key to 32 bytes. The module used in libp2p crypto (noble-secp256k1)
// verifies the length and will throw an error if key is not 32 bytes.
// For more details on why the generated key is not always 32 bytes, see:
// https://stackoverflow.com/questions/62938091/why-are-secp256k1-privatekeys-not-always-32-bytes-in-nodejs
const buf = Buffer.alloc(32)
// Copy the private key buffer to the padded buffer
privKey.copy(buf, buf.length - privKey.length)
const keys = await unmarshal(buf)
const pubKey = keys.public.marshal()
const decompressedKey = secp256k1.publicKeyConvert(Buffer.from(pubKey), false)
const key = {
publicKey: Buffer.from(decompressedKey).toString('hex'),
privateKey: Buffer.from(keys.marshal()).toString('hex')
}
try {
await this._store.put(id, JSON.stringify(key))
} catch (e) {
console.log(e)
}
this._cache.set(id, key)
return keys
}
async getKey (id) {
if (!id) {
throw new Error('id needed to get a key')
}
if (!this._store) {
await this.open()
}
if (this._store.status && this._store.status !== 'open') {
return null
}
const cachedKey = this._cache.get(id)
let storedKey
try {
storedKey = cachedKey || await this._store.get(id)
} catch (e) {
// ignore ENOENT error
}
if (!storedKey) {
return
}
const deserializedKey = cachedKey || JSON.parse(storedKey)
if (!deserializedKey) {
return
}
if (!cachedKey) {
this._cache.set(id, deserializedKey)
}
return unmarshal(Buffer.from(deserializedKey.privateKey, 'hex'))
}
getPublic (keys, options = {}) {
const formats = ['hex', 'buffer']
const decompress = typeof options.decompress === 'undefined' ? true : options.decompress
const format = options.format || 'hex'
if (formats.indexOf(format) === -1) {
throw new Error('Supported formats are `hex` and `buffer`')
}
let pubKey = keys.public.marshal()
if (decompress) {
pubKey = secp256k1.publicKeyConvert(Buffer.from(pubKey), false)
}
pubKey = Buffer.from(pubKey)
return format === 'buffer' ? pubKey : pubKey.toString('hex')
}
static async sign (key, data) {
if (!key) { if (!key) {
throw new Error('No signing key given') throw new Error('No signing key given')
} }
@ -207,7 +52,7 @@ export default class KeyStore {
return Buffer.from(await key.sign(data)).toString('hex') return Buffer.from(await key.sign(data)).toString('hex')
} }
static async verify (signature, publicKey, data) { const verify = async (signature, publicKey, data) => {
// const cached = verifiedCache.get(signature) // const cached = verifiedCache.get(signature)
const cached = null const cached = null
let res = false let res = false
@ -231,4 +76,143 @@ export default class KeyStore {
} }
return res return res
} }
// const verifiedCache = new LRU(1000)
const KeyStore = async ({ storage, cache }) => {
const close = async () => {
if (!storage) return
await storage.close()
}
const hasKey = async (id) => {
if (!id) {
throw new Error('id needed to check a key')
}
if (storage.status && storage.status !== 'open') {
return null
}
let hasKey = false
try {
const storedKey = await cache.get(id) || await storage.get(id)
hasKey = storedKey !== undefined && storedKey !== null
} catch (e) {
// Catches 'Error: ENOENT: no such file or directory, open <path>'
console.error('Error: ENOENT: no such file or directory')
}
return hasKey
}
const addKey = async (id, key) => {
try {
await storage.put(id, JSON.stringify(key))
} catch (e) {
console.log(e)
}
cache.put(id, key)
}
const createKey = async (id, { entropy } = {}) => {
if (!id) {
throw new Error('id needed to create a key')
}
// if (storage.status && storage.status !== 'open') {
// console.log("22::", id)
// return null
// }
// Generate a private key
const privKey = ec.genKeyPair({ entropy }).getPrivate().toArrayLike(Buffer)
// Left pad the key to 32 bytes. The module used in libp2p crypto (noble-secp256k1)
// verifies the length and will throw an error if key is not 32 bytes.
// For more details on why the generated key is not always 32 bytes, see:
// https://stackoverflow.com/questions/62938091/why-are-secp256k1-privatekeys-not-always-32-bytes-in-nodejs
const buf = Buffer.alloc(32)
// Copy the private key buffer to the padded buffer
privKey.copy(buf, buf.length - privKey.length)
const keys = await unmarshal(buf)
const pubKey = keys.public.marshal()
const decompressedKey = secp256k1.publicKeyConvert(Buffer.from(pubKey), false)
const key = {
publicKey: Buffer.from(decompressedKey).toString('hex'),
privateKey: Buffer.from(keys.marshal()).toString('hex')
}
try {
await storage.put(id, JSON.stringify(key))
} catch (e) {
console.log(e)
}
cache.put(id, key)
return keys
}
const getKey = async (id) => {
if (!id) {
throw new Error('id needed to get a key')
}
if (!storage) {
await open()
}
if (storage.status && storage.status !== 'open') {
return null
}
const cachedKey = await cache.get(id)
let storedKey
try {
storedKey = cachedKey || await storage.get(id)
} catch (e) {
// ignore ENOENT error
}
if (!storedKey) {
return
}
const deserializedKey = cachedKey || JSON.parse(storedKey)
if (!deserializedKey) {
return
}
if (!cachedKey) {
cache.put(id, deserializedKey)
}
return unmarshal(Buffer.from(deserializedKey.privateKey, 'hex'))
}
const getPublic = (keys, options = {}) => {
const formats = ['hex', 'buffer']
const decompress = typeof options.decompress === 'undefined' ? true : options.decompress
const format = options.format || 'hex'
if (formats.indexOf(format) === -1) {
throw new Error('Supported formats are `hex` and `buffer`')
}
let pubKey = keys.public.marshal()
if (decompress) {
pubKey = secp256k1.publicKeyConvert(Buffer.from(pubKey), false)
}
pubKey = Buffer.from(pubKey)
return format === 'buffer' ? pubKey : pubKey.toString('hex')
}
return {
close,
hasKey,
addKey,
createKey,
getKey,
getPublic
}
}
export {
KeyStore as default,
verify,
sign
} }

32
test/key-store.test.js Normal file
View File

@ -0,0 +1,32 @@
import { deepStrictEqual } from 'assert'
import LevelStorage from '../src/storage/level.js'
import LRUStorage from '../src/storage/lru.js'
import KeyStore from '../src/key-store.js'
import { config, testAPIs } from 'orbit-db-test-utils'
// import { userA } from '../fixtures/orbit-db-identity-keys.js'
Object.keys(testAPIs).forEach((IPFS) => {
describe('KeyStore (' + IPFS + ')', () => {
let keystore
beforeEach(async () => {
const storage = await LevelStorage('./keys_1')
const cache = await LRUStorage(100)
keystore = await KeyStore({ storage, cache })
})
it('creates a key', async () => {
const id = 'key1'
console.log(await keystore.createKey(id))
})
it('gets a key', async () => {
const id = 'key1'
const keys = await keystore.createKey(id)
deepStrictEqual(await keystore.getKey(id), keys)
})
afterEach(async () => {
keystore.close()
})
})
})