add wrapper for elliptic vs secp256k1. see #52.

This commit is contained in:
Christopher Jeffrey 2016-02-21 03:34:15 -08:00
parent e7478372cf
commit 10804959d5
7 changed files with 225 additions and 117 deletions

View File

@ -5,6 +5,7 @@
*/
var bcoin = exports;
var assert = require('assert');
var inherits = require('inherits');
var elliptic = require('elliptic');
var bn = require('bn.js');
@ -27,6 +28,12 @@ if (!bcoin.isBrowser) {
bcoin.crypto = require('cry' + 'pto');
bcoin.net = require('n' + 'et');
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) {
@ -44,16 +51,14 @@ bcoin.hash = hash;
bcoin.ecdsa = elliptic.ec('secp256k1');
if (bcoin.ecdsa.signature)
throw new Error;
if (bcoin.ecdsa.keypair)
throw new Error;
assert(!bcoin.ecdsa.signature);
assert(!bcoin.ecdsa.keypair);
bcoin.ecdsa.signature = require('elliptic/lib/elliptic/ec/signature');
bcoin.ecdsa.keypair = require('elliptic/lib/elliptic/ec/key');
bcoin.utils = require('./bcoin/utils');
bcoin.ec = require('./bcoin/ec');
bcoin.lru = require('./bcoin/lru');
bcoin.protocol = require('./bcoin/protocol');
bcoin.bloom = require('./bcoin/bloom');

200
lib/bcoin/ec.js Normal file
View 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();
};

View File

@ -565,7 +565,7 @@ HDPrivateKey.prototype._normalize = function _normalize(data) {
// private key = 32 bytes
if (data.privateKey) {
if (data.privateKey.getPrivate)
data.privateKey = data.privateKey.getPrivate().toArray();
data.privateKey = data.privateKey.getPrivate().toArray('be', 32);
else if (typeof data.privateKey === 'string')
data.privateKey = utils.toBuffer(data.privateKey);
}
@ -616,12 +616,12 @@ HDPrivateKey.prototype._seed = function _seed(seed) {
HDPrivateKey.prototype._generate = function _generate(privateKey, entropy) {
if (!privateKey)
privateKey = bcoin.ecdsa.genKeyPair().getPrivate().toArray();
privateKey = bcoin.ec.generate().getPrivate().toArray('be', 32);
if (utils.isHex(privateKey))
privateKey = utils.toArray(privateKey, 'hex');
else if (utils.isBase58(privateKey))
privateKey = bcoin.keypair.fromSecret(privateKey).getPrivate().toArray();
privateKey = bcoin.keypair.fromSecret(privateKey).getPrivate().toArray('be', 32);
if (!entropy)
entropy = elliptic.rand(32);
@ -691,7 +691,7 @@ HDPrivateKey.prototype._build = function _build(data) {
xprivkey = utils.toBase58(sequence);
pair = bcoin.ecdsa.keyPair({ priv: data.privateKey });
privateKey = pair.getPrivate().toArray();
privateKey = pair.getPrivate().toArray('be', 32);
publicKey = pair.getPublic(true, 'array');
size = constants.hd.parentFingerPrintSize;

View File

@ -64,7 +64,7 @@ function KeyPair(options) {
pub: options.publicKey
});
} else {
this.pair = bcoin.ecdsa.genKeyPair({
this.pair = bcoin.ec.generate({
pers: options.personalization,
entropy: options.entropy
});
@ -113,7 +113,7 @@ KeyPair.prototype.getPrivateKey = function getPrivateKey(enc) {
if (!privateKey)
return;
privateKey = privateKey.toArray();
privateKey = privateKey.toArray('be', 32);
if (enc === 'base58')
return KeyPair.toSecret(privateKey, this.compressed);

View File

@ -234,7 +234,7 @@ regtest.seeds = [
regtest.port = 18444;
regtest._alertKey = bcoin.ecdsa.genKeyPair();
regtest._alertKey = bcoin.ec.generate();
regtest.alertKey = regtest._alertKey.getPublic(true, 'array');
regtest.checkpoints = {};

View File

@ -293,20 +293,17 @@ script.concat = function concat(scripts) {
};
script.checksig = function checksig(msg, sig, key, flags) {
var historical = false;
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
if (key.getPublic)
key = key.getPublic();
if (!utils.isBuffer(sig))
return false;
if (sig.length === 0)
return false;
sig = sig.slice(0, -1);
// Attempt to normalize the signature
// length before passing to elliptic.
// 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)
|| (flags & constants.flags.VERIFY_LOW_S)
|| (flags & constants.flags.VERIFY_STRICTENC))) {
sig = script.normalizeDER(sig);
historical = true;
}
// Use a try catch in case there are
// 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;
}
return bcoin.ec.verify(msg, sig.slice(0, -1), key, historical);
};
script.sign = function sign(msg, key, type) {
var half = bcoin.ecdsa.n.ushrn(1);
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();
var sig = bcoin.ec.sign(msg, key);
// Add the sighash type as a single byte
// to the signature.
@ -358,56 +327,6 @@ script.sign = function sign(msg, key, type) {
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) {
var depth = 0;
var o;
@ -2314,8 +2233,6 @@ script.isHashType = function isHashType(sig) {
};
script.isLowDER = function isLowDER(sig) {
var half = bcoin.ecdsa.n.ushrn(1);
if (!sig.s) {
if (!utils.isBuffer(sig))
return false;
@ -2323,25 +2240,10 @@ script.isLowDER = function isLowDER(sig) {
if (!script.isSignatureEncoding(sig))
return false;
try {
sig = new bcoin.ecdsa.signature(sig.slice(0, -1));
} catch (e) {
return false;
}
sig = sig.slice(0, -1);
}
// 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(half) > 0)
return false;
return true;
return bcoin.ec.isLowS(sig);
};
script.format = function format(s) {

View File

@ -27,6 +27,7 @@
"bn.js": "4.6.3",
"elliptic": "6.0.2",
"hash.js": "1.0.3",
"secp256k1": "3.0.0",
"inherits": "2.0.1",
"leveldown": "1.4.4",
"level-js": "2.2.3",