add wrapper for elliptic vs secp256k1. see #52.
This commit is contained in:
parent
e7478372cf
commit
10804959d5
15
lib/bcoin.js
15
lib/bcoin.js
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var bcoin = exports;
|
var bcoin = exports;
|
||||||
|
var assert = require('assert');
|
||||||
var inherits = require('inherits');
|
var inherits = require('inherits');
|
||||||
var elliptic = require('elliptic');
|
var elliptic = require('elliptic');
|
||||||
var bn = require('bn.js');
|
var bn = require('bn.js');
|
||||||
@ -27,6 +28,12 @@ if (!bcoin.isBrowser) {
|
|||||||
bcoin.crypto = require('cry' + 'pto');
|
bcoin.crypto = require('cry' + 'pto');
|
||||||
bcoin.net = require('n' + 'et');
|
bcoin.net = require('n' + 'et');
|
||||||
bcoin.cp = require('child_' + 'process');
|
bcoin.cp = require('child_' + 'process');
|
||||||
|
try {
|
||||||
|
bcoin.secp256k1 = require('secp' + '256k1');
|
||||||
|
} catch (e) {
|
||||||
|
utils.debug('Warning secp256k1 not found.'
|
||||||
|
+ ' Full block validation will be slow.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bcoin.fs) {
|
if (bcoin.fs) {
|
||||||
@ -44,16 +51,14 @@ bcoin.hash = hash;
|
|||||||
|
|
||||||
bcoin.ecdsa = elliptic.ec('secp256k1');
|
bcoin.ecdsa = elliptic.ec('secp256k1');
|
||||||
|
|
||||||
if (bcoin.ecdsa.signature)
|
assert(!bcoin.ecdsa.signature);
|
||||||
throw new Error;
|
assert(!bcoin.ecdsa.keypair);
|
||||||
|
|
||||||
if (bcoin.ecdsa.keypair)
|
|
||||||
throw new Error;
|
|
||||||
|
|
||||||
bcoin.ecdsa.signature = require('elliptic/lib/elliptic/ec/signature');
|
bcoin.ecdsa.signature = require('elliptic/lib/elliptic/ec/signature');
|
||||||
bcoin.ecdsa.keypair = require('elliptic/lib/elliptic/ec/key');
|
bcoin.ecdsa.keypair = require('elliptic/lib/elliptic/ec/key');
|
||||||
|
|
||||||
bcoin.utils = require('./bcoin/utils');
|
bcoin.utils = require('./bcoin/utils');
|
||||||
|
bcoin.ec = require('./bcoin/ec');
|
||||||
bcoin.lru = require('./bcoin/lru');
|
bcoin.lru = require('./bcoin/lru');
|
||||||
bcoin.protocol = require('./bcoin/protocol');
|
bcoin.protocol = require('./bcoin/protocol');
|
||||||
bcoin.bloom = require('./bcoin/bloom');
|
bcoin.bloom = require('./bcoin/bloom');
|
||||||
|
|||||||
200
lib/bcoin/ec.js
Normal file
200
lib/bcoin/ec.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* ec.js - ecdsa wrapper for secp256k1 and elliptic
|
||||||
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
||||||
|
* https://github.com/indutny/bcoin
|
||||||
|
*/
|
||||||
|
|
||||||
|
var bcoin = require('../bcoin');
|
||||||
|
var bn = require('bn.js');
|
||||||
|
var utils = bcoin.utils;
|
||||||
|
var assert = utils.assert;
|
||||||
|
var ec = exports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EC
|
||||||
|
*/
|
||||||
|
|
||||||
|
ec.generate = function generate(options) {
|
||||||
|
var priv, pub;
|
||||||
|
|
||||||
|
if (bcoin.secp256k1 && bcoin.crypto) {
|
||||||
|
do {
|
||||||
|
priv = bcoin.crypto.randomBytes(32);
|
||||||
|
} while (!bcoin.secp256k1.privateKeyVerify(priv));
|
||||||
|
pub = bcoin.secp256k1.publicKeyCreate(priv, true);
|
||||||
|
priv = utils.toArray(priv);
|
||||||
|
pub = utils.toArray(pub);
|
||||||
|
return bcoin.ecdsa.keyPair({ priv: priv, pub: pub });
|
||||||
|
}
|
||||||
|
|
||||||
|
return bcoin.ecdsa.genKeyPair(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
ec.verify = function verify(msg, sig, key, historical) {
|
||||||
|
if (key.getPublic)
|
||||||
|
key = key.getPublic(true, 'array');
|
||||||
|
|
||||||
|
if (!utils.isBuffer(sig))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (sig.length === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Attempt to normalize the signature
|
||||||
|
// length before passing to elliptic.
|
||||||
|
// Note: We only do this for historical data!
|
||||||
|
// https://github.com/indutny/elliptic/issues/78
|
||||||
|
if (historical)
|
||||||
|
sig = bcoin.ec.normalizeLength(sig);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (bcoin.secp256k1) {
|
||||||
|
// secp256k1 fails on low s values. This is
|
||||||
|
// bad for verifying historical data.
|
||||||
|
if (historical)
|
||||||
|
sig = bcoin.ec.toLowS(sig);
|
||||||
|
|
||||||
|
msg = new Buffer(msg);
|
||||||
|
sig = new Buffer(sig);
|
||||||
|
key = new Buffer(key)
|
||||||
|
|
||||||
|
// Import from DER.
|
||||||
|
sig = bcoin.secp256k1.signatureImport(sig);
|
||||||
|
|
||||||
|
// This is supposed to lower the S value
|
||||||
|
// but it doesn't seem to work.
|
||||||
|
// if (historical)
|
||||||
|
// sig = bcoin.secp256k1.signatureNormalize(sig);
|
||||||
|
|
||||||
|
return bcoin.secp256k1.verify(msg, sig, key);
|
||||||
|
}
|
||||||
|
return bcoin.ecdsa.verify(msg, sig, key);
|
||||||
|
} catch (e) {
|
||||||
|
utils.debug('Elliptic threw during verification:');
|
||||||
|
utils.debug(e.stack + '');
|
||||||
|
utils.debug({
|
||||||
|
msg: utils.toHex(msg),
|
||||||
|
sig: utils.toHex(sig),
|
||||||
|
key: utils.toHex(key)
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ec.sign = function sign(msg, key) {
|
||||||
|
var sig;
|
||||||
|
|
||||||
|
if (bcoin.secp256k1) {
|
||||||
|
msg = new Buffer(msg);
|
||||||
|
key = new Buffer(key.getPrivate().toArray('be', 32));
|
||||||
|
|
||||||
|
// Sign message
|
||||||
|
sig = bcoin.secp256k1.sign(msg, key);
|
||||||
|
|
||||||
|
// Ensure low S value
|
||||||
|
sig = bcoin.secp256k1.signatureNormalize(sig.signature);
|
||||||
|
|
||||||
|
// Convert to DER array
|
||||||
|
sig = bcoin.secp256k1.signatureExport(sig);
|
||||||
|
|
||||||
|
sig = utils.toArray(sig);
|
||||||
|
} else {
|
||||||
|
// Sign message and ensure low S value
|
||||||
|
sig = bcoin.ecdsa.sign(msg, key.priv, { canonical: true });
|
||||||
|
|
||||||
|
// Convert to DER array
|
||||||
|
sig = sig.toDER();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig;
|
||||||
|
};
|
||||||
|
|
||||||
|
ec.normalizeLength = function normalizeLength(sig) {
|
||||||
|
var data, p, len, rlen, slen;
|
||||||
|
|
||||||
|
data = sig.slice();
|
||||||
|
p = { place: 0 };
|
||||||
|
|
||||||
|
if (data[p.place++] !== 0x30)
|
||||||
|
return sig;
|
||||||
|
|
||||||
|
len = getLength(data, p);
|
||||||
|
|
||||||
|
if (data.length > len + p.place)
|
||||||
|
data = data.slice(0, len + p.place);
|
||||||
|
|
||||||
|
if (data[p.place++] !== 0x02)
|
||||||
|
return sig;
|
||||||
|
|
||||||
|
rlen = getLength(data, p);
|
||||||
|
p.place += rlen;
|
||||||
|
|
||||||
|
if (data[p.place++] !== 0x02)
|
||||||
|
return sig;
|
||||||
|
|
||||||
|
slen = getLength(data, p);
|
||||||
|
if (data.length > slen + p.place)
|
||||||
|
data = data.slice(0, slen + p.place);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLength(buf, p) {
|
||||||
|
var initial = buf[p.place++];
|
||||||
|
if (!(initial & 0x80)) {
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
var octetLen = initial & 0xf;
|
||||||
|
var val = 0;
|
||||||
|
for (var i = 0, off = p.place; i < octetLen; i++, off++) {
|
||||||
|
val <<= 8;
|
||||||
|
val |= buf[off];
|
||||||
|
}
|
||||||
|
p.place = off;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec.isLowS = function isLowS(sig) {
|
||||||
|
if (!sig.s) {
|
||||||
|
if (!utils.isBuffer(sig))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sig = new bcoin.ecdsa.signature(sig);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Technically a negative S value is low,
|
||||||
|
// but we don't want to ever use negative
|
||||||
|
// S values in bitcoin.
|
||||||
|
if (sig.s.cmpn(0) <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If S is greater than half the order,
|
||||||
|
// it's too high.
|
||||||
|
if (sig.s.cmp(bcoin.ecdsa.nh) > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ec.toLowS = function toLowS(sig) {
|
||||||
|
if (!sig.s) {
|
||||||
|
assert(utils.isBuffer(sig));
|
||||||
|
|
||||||
|
try {
|
||||||
|
sig = new bcoin.ecdsa.signature(sig);
|
||||||
|
} catch (e) {
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If S is greater than half the order,
|
||||||
|
// it's too high.
|
||||||
|
if (sig.s.cmp(bcoin.ecdsa.nh) > 0)
|
||||||
|
sig.s = bcoin.ecdsa.n.sub(sig.s);
|
||||||
|
|
||||||
|
return sig.toDER();
|
||||||
|
};
|
||||||
@ -565,7 +565,7 @@ HDPrivateKey.prototype._normalize = function _normalize(data) {
|
|||||||
// private key = 32 bytes
|
// private key = 32 bytes
|
||||||
if (data.privateKey) {
|
if (data.privateKey) {
|
||||||
if (data.privateKey.getPrivate)
|
if (data.privateKey.getPrivate)
|
||||||
data.privateKey = data.privateKey.getPrivate().toArray();
|
data.privateKey = data.privateKey.getPrivate().toArray('be', 32);
|
||||||
else if (typeof data.privateKey === 'string')
|
else if (typeof data.privateKey === 'string')
|
||||||
data.privateKey = utils.toBuffer(data.privateKey);
|
data.privateKey = utils.toBuffer(data.privateKey);
|
||||||
}
|
}
|
||||||
@ -616,12 +616,12 @@ HDPrivateKey.prototype._seed = function _seed(seed) {
|
|||||||
|
|
||||||
HDPrivateKey.prototype._generate = function _generate(privateKey, entropy) {
|
HDPrivateKey.prototype._generate = function _generate(privateKey, entropy) {
|
||||||
if (!privateKey)
|
if (!privateKey)
|
||||||
privateKey = bcoin.ecdsa.genKeyPair().getPrivate().toArray();
|
privateKey = bcoin.ec.generate().getPrivate().toArray('be', 32);
|
||||||
|
|
||||||
if (utils.isHex(privateKey))
|
if (utils.isHex(privateKey))
|
||||||
privateKey = utils.toArray(privateKey, 'hex');
|
privateKey = utils.toArray(privateKey, 'hex');
|
||||||
else if (utils.isBase58(privateKey))
|
else if (utils.isBase58(privateKey))
|
||||||
privateKey = bcoin.keypair.fromSecret(privateKey).getPrivate().toArray();
|
privateKey = bcoin.keypair.fromSecret(privateKey).getPrivate().toArray('be', 32);
|
||||||
|
|
||||||
if (!entropy)
|
if (!entropy)
|
||||||
entropy = elliptic.rand(32);
|
entropy = elliptic.rand(32);
|
||||||
@ -691,7 +691,7 @@ HDPrivateKey.prototype._build = function _build(data) {
|
|||||||
xprivkey = utils.toBase58(sequence);
|
xprivkey = utils.toBase58(sequence);
|
||||||
|
|
||||||
pair = bcoin.ecdsa.keyPair({ priv: data.privateKey });
|
pair = bcoin.ecdsa.keyPair({ priv: data.privateKey });
|
||||||
privateKey = pair.getPrivate().toArray();
|
privateKey = pair.getPrivate().toArray('be', 32);
|
||||||
publicKey = pair.getPublic(true, 'array');
|
publicKey = pair.getPublic(true, 'array');
|
||||||
|
|
||||||
size = constants.hd.parentFingerPrintSize;
|
size = constants.hd.parentFingerPrintSize;
|
||||||
|
|||||||
@ -64,7 +64,7 @@ function KeyPair(options) {
|
|||||||
pub: options.publicKey
|
pub: options.publicKey
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.pair = bcoin.ecdsa.genKeyPair({
|
this.pair = bcoin.ec.generate({
|
||||||
pers: options.personalization,
|
pers: options.personalization,
|
||||||
entropy: options.entropy
|
entropy: options.entropy
|
||||||
});
|
});
|
||||||
@ -113,7 +113,7 @@ KeyPair.prototype.getPrivateKey = function getPrivateKey(enc) {
|
|||||||
if (!privateKey)
|
if (!privateKey)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
privateKey = privateKey.toArray();
|
privateKey = privateKey.toArray('be', 32);
|
||||||
|
|
||||||
if (enc === 'base58')
|
if (enc === 'base58')
|
||||||
return KeyPair.toSecret(privateKey, this.compressed);
|
return KeyPair.toSecret(privateKey, this.compressed);
|
||||||
|
|||||||
@ -234,7 +234,7 @@ regtest.seeds = [
|
|||||||
|
|
||||||
regtest.port = 18444;
|
regtest.port = 18444;
|
||||||
|
|
||||||
regtest._alertKey = bcoin.ecdsa.genKeyPair();
|
regtest._alertKey = bcoin.ec.generate();
|
||||||
regtest.alertKey = regtest._alertKey.getPublic(true, 'array');
|
regtest.alertKey = regtest._alertKey.getPublic(true, 'array');
|
||||||
|
|
||||||
regtest.checkpoints = {};
|
regtest.checkpoints = {};
|
||||||
|
|||||||
@ -293,20 +293,17 @@ script.concat = function concat(scripts) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
script.checksig = function checksig(msg, sig, key, flags) {
|
script.checksig = function checksig(msg, sig, key, flags) {
|
||||||
|
var historical = false;
|
||||||
|
|
||||||
if (flags == null)
|
if (flags == null)
|
||||||
flags = constants.flags.STANDARD_VERIFY_FLAGS;
|
flags = constants.flags.STANDARD_VERIFY_FLAGS;
|
||||||
|
|
||||||
if (key.getPublic)
|
|
||||||
key = key.getPublic();
|
|
||||||
|
|
||||||
if (!utils.isBuffer(sig))
|
if (!utils.isBuffer(sig))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (sig.length === 0)
|
if (sig.length === 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
sig = sig.slice(0, -1);
|
|
||||||
|
|
||||||
// Attempt to normalize the signature
|
// Attempt to normalize the signature
|
||||||
// length before passing to elliptic.
|
// length before passing to elliptic.
|
||||||
// Note: We only do this for historical data!
|
// Note: We only do this for historical data!
|
||||||
@ -314,42 +311,14 @@ script.checksig = function checksig(msg, sig, key, flags) {
|
|||||||
if (!((flags & constants.flags.VERIFY_DERSIG)
|
if (!((flags & constants.flags.VERIFY_DERSIG)
|
||||||
|| (flags & constants.flags.VERIFY_LOW_S)
|
|| (flags & constants.flags.VERIFY_LOW_S)
|
||||||
|| (flags & constants.flags.VERIFY_STRICTENC))) {
|
|| (flags & constants.flags.VERIFY_STRICTENC))) {
|
||||||
sig = script.normalizeDER(sig);
|
historical = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a try catch in case there are
|
return bcoin.ec.verify(msg, sig.slice(0, -1), key, historical);
|
||||||
// any uncaught errors for bad inputs in verify().
|
|
||||||
try {
|
|
||||||
return bcoin.ecdsa.verify(msg, sig, key);
|
|
||||||
} catch (e) {
|
|
||||||
utils.debug('Elliptic threw during verification:');
|
|
||||||
utils.debug(e.stack + '');
|
|
||||||
utils.debug({
|
|
||||||
msg: utils.toHex(msg),
|
|
||||||
sig: utils.toHex(sig),
|
|
||||||
key: utils.toHex(key)
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
script.sign = function sign(msg, key, type) {
|
script.sign = function sign(msg, key, type) {
|
||||||
var half = bcoin.ecdsa.n.ushrn(1);
|
var sig = bcoin.ec.sign(msg, key);
|
||||||
var sig = bcoin.ecdsa.sign(msg, key.priv);
|
|
||||||
|
|
||||||
// Elliptic shouldn't be generating
|
|
||||||
// negative S values.
|
|
||||||
assert(sig.s.cmpn(0) > 0);
|
|
||||||
|
|
||||||
// If S value is bigger than half of the
|
|
||||||
// order of the curve, it's too damn big.
|
|
||||||
// Subtract from the `n` order to make
|
|
||||||
// it smaller.
|
|
||||||
if (sig.s.cmp(half) > 0)
|
|
||||||
sig.s = bcoin.ecdsa.n.sub(sig.s);
|
|
||||||
|
|
||||||
// Convert to DER array
|
|
||||||
sig = sig.toDER();
|
|
||||||
|
|
||||||
// Add the sighash type as a single byte
|
// Add the sighash type as a single byte
|
||||||
// to the signature.
|
// to the signature.
|
||||||
@ -358,56 +327,6 @@ script.sign = function sign(msg, key, type) {
|
|||||||
return sig;
|
return sig;
|
||||||
};
|
};
|
||||||
|
|
||||||
script.normalizeDER = function normalizeDER(signature) {
|
|
||||||
var data, p, len, rlen, slen;
|
|
||||||
|
|
||||||
if (Buffer.isBuffer(signature))
|
|
||||||
signature = Array.prototype.slice.call(signature);
|
|
||||||
else if (typeof signature === 'string')
|
|
||||||
signature = utils.toArray(signature, 'hex');
|
|
||||||
|
|
||||||
data = signature.slice();
|
|
||||||
p = { place: 0 };
|
|
||||||
|
|
||||||
if (data[p.place++] !== 0x30)
|
|
||||||
return signature;
|
|
||||||
|
|
||||||
len = getLength(data, p);
|
|
||||||
|
|
||||||
if (data.length > len + p.place)
|
|
||||||
data = data.slice(0, len + p.place);
|
|
||||||
|
|
||||||
if (data[p.place++] !== 0x02)
|
|
||||||
return signature;
|
|
||||||
|
|
||||||
rlen = getLength(data, p);
|
|
||||||
p.place += rlen;
|
|
||||||
|
|
||||||
if (data[p.place++] !== 0x02)
|
|
||||||
return signature;
|
|
||||||
|
|
||||||
slen = getLength(data, p);
|
|
||||||
if (data.length > slen + p.place)
|
|
||||||
data = data.slice(0, slen + p.place);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getLength(buf, p) {
|
|
||||||
var initial = buf[p.place++];
|
|
||||||
if (!(initial & 0x80)) {
|
|
||||||
return initial;
|
|
||||||
}
|
|
||||||
var octetLen = initial & 0xf;
|
|
||||||
var val = 0;
|
|
||||||
for (var i = 0, off = p.place; i < octetLen; i++, off++) {
|
|
||||||
val <<= 8;
|
|
||||||
val |= buf[off];
|
|
||||||
}
|
|
||||||
p.place = off;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
script._next = function _next(to, s, pc) {
|
script._next = function _next(to, s, pc) {
|
||||||
var depth = 0;
|
var depth = 0;
|
||||||
var o;
|
var o;
|
||||||
@ -2314,8 +2233,6 @@ script.isHashType = function isHashType(sig) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
script.isLowDER = function isLowDER(sig) {
|
script.isLowDER = function isLowDER(sig) {
|
||||||
var half = bcoin.ecdsa.n.ushrn(1);
|
|
||||||
|
|
||||||
if (!sig.s) {
|
if (!sig.s) {
|
||||||
if (!utils.isBuffer(sig))
|
if (!utils.isBuffer(sig))
|
||||||
return false;
|
return false;
|
||||||
@ -2323,25 +2240,10 @@ script.isLowDER = function isLowDER(sig) {
|
|||||||
if (!script.isSignatureEncoding(sig))
|
if (!script.isSignatureEncoding(sig))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try {
|
sig = sig.slice(0, -1);
|
||||||
sig = new bcoin.ecdsa.signature(sig.slice(0, -1));
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Technically a negative S value is low,
|
return bcoin.ec.isLowS(sig);
|
||||||
// but we don't want to ever use negative
|
|
||||||
// S values in bitcoin.
|
|
||||||
if (sig.s.cmpn(0) <= 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// If S is greater than half the order,
|
|
||||||
// it's too high.
|
|
||||||
if (sig.s.cmp(half) > 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
script.format = function format(s) {
|
script.format = function format(s) {
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
"bn.js": "4.6.3",
|
"bn.js": "4.6.3",
|
||||||
"elliptic": "6.0.2",
|
"elliptic": "6.0.2",
|
||||||
"hash.js": "1.0.3",
|
"hash.js": "1.0.3",
|
||||||
|
"secp256k1": "3.0.0",
|
||||||
"inherits": "2.0.1",
|
"inherits": "2.0.1",
|
||||||
"leveldown": "1.4.4",
|
"leveldown": "1.4.4",
|
||||||
"level-js": "2.2.3",
|
"level-js": "2.2.3",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user