From 10804959d51a262e45ec4e1b23523912a7433db6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 21 Feb 2016 03:34:15 -0800 Subject: [PATCH] add wrapper for elliptic vs secp256k1. see #52. --- lib/bcoin.js | 15 ++- lib/bcoin/ec.js | 200 ++++++++++++++++++++++++++++++++++ lib/bcoin/hd.js | 8 +- lib/bcoin/keypair.js | 4 +- lib/bcoin/protocol/network.js | 2 +- lib/bcoin/script.js | 112 ++----------------- package.json | 1 + 7 files changed, 225 insertions(+), 117 deletions(-) create mode 100644 lib/bcoin/ec.js diff --git a/lib/bcoin.js b/lib/bcoin.js index 99737476..7a3c0158 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -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'); diff --git a/lib/bcoin/ec.js b/lib/bcoin/ec.js new file mode 100644 index 00000000..605196a6 --- /dev/null +++ b/lib/bcoin/ec.js @@ -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(); +}; diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 1ab8eafc..3ab3d096 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -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; diff --git a/lib/bcoin/keypair.js b/lib/bcoin/keypair.js index 32af7e8b..5efed4ce 100644 --- a/lib/bcoin/keypair.js +++ b/lib/bcoin/keypair.js @@ -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); diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index f2089e07..2a8bf757 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -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 = {}; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 76314562..dc359677 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -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) { diff --git a/package.json b/package.json index cb93dc00..a0b11036 100644 --- a/package.json +++ b/package.json @@ -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",