mirror of
https://github.com/orbitdb/orbitdb.git
synced 2025-10-07 22:57:07 +00:00
238 lines
6.3 KiB
JavaScript
238 lines
6.3 KiB
JavaScript
import { Level } from 'level'
|
|
import * as crypto from '@libp2p/crypto'
|
|
import secp256k1 from 'secp256k1'
|
|
import LRU from 'lru'
|
|
import { Buffer } from 'safe-buffer'
|
|
import fs from 'fs'
|
|
import pkg from 'elliptic'
|
|
const { ec: EC } = pkg
|
|
|
|
const ec = new EC('secp256k1')
|
|
const unmarshal = crypto.keys.supportedKeys.secp256k1.unmarshalSecp256k1PrivateKey
|
|
|
|
const verifySignature = async (signature, publicKey, data) => {
|
|
const unmarshalPubKey = crypto.keys.supportedKeys.secp256k1.unmarshalSecp256k1PublicKey
|
|
|
|
if (!signature) {
|
|
throw new Error('No signature given')
|
|
}
|
|
if (!publicKey) {
|
|
throw new Error('Given publicKey was undefined')
|
|
}
|
|
if (!data) {
|
|
throw new Error('Given input data was undefined')
|
|
}
|
|
|
|
if (!Buffer.isBuffer(data)) {
|
|
data = Buffer.from(data)
|
|
}
|
|
|
|
const isValid = (key, msg, sig) => key.verify(msg, sig)
|
|
|
|
let res = false
|
|
try {
|
|
const pubKey = unmarshalPubKey(Buffer.from(publicKey, 'hex'))
|
|
res = await isValid(pubKey, data, Buffer.from(signature, 'hex'))
|
|
} catch (e) {
|
|
// Catch error: sig length wrong
|
|
}
|
|
return Promise.resolve(res)
|
|
}
|
|
|
|
function createStore (path = './keystore') {
|
|
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') {
|
|
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'))
|
|
}
|
|
|
|
async sign (key, data) {
|
|
if (!key) {
|
|
throw new Error('No signing key given')
|
|
}
|
|
|
|
if (!data) {
|
|
throw new Error('Given input data was undefined')
|
|
}
|
|
|
|
if (!Buffer.isBuffer(data)) {
|
|
data = Buffer.from(data)
|
|
}
|
|
|
|
return Buffer.from(await key.sign(data)).toString('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')
|
|
}
|
|
|
|
async verify (signature, publicKey, data, v = 'v1') {
|
|
return Keystore.verify(signature, publicKey, data, v)
|
|
}
|
|
|
|
static async verify (signature, publicKey, data, v = 'v1') {
|
|
// const cached = verifiedCache.get(signature)
|
|
const cached = null
|
|
let res = false
|
|
if (!cached) {
|
|
const verified = await verifySignature(signature, publicKey, data)
|
|
res = verified
|
|
// if (verified) {
|
|
// verifiedCache.set(signature, { publicKey, data })
|
|
// }
|
|
} else {
|
|
const compare = (cached, data, v) => {
|
|
let match
|
|
if (v === 'v0') {
|
|
match = Buffer.compare(Buffer.alloc(30, cached), Buffer.alloc(30, data)) === 0
|
|
} else {
|
|
match = Buffer.isBuffer(data) ? Buffer.compare(cached, data) === 0 : cached === data
|
|
}
|
|
return match
|
|
}
|
|
res = cached.publicKey === publicKey && compare(cached.data, data, v)
|
|
}
|
|
return res
|
|
}
|
|
}
|