From 68a3209a964c8230a1f16a29a06c1af3355dbaf0 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 22 Jun 2017 05:22:06 -0700 Subject: [PATCH] schnorr: add hmac-drbg. refactor. --- lib/crypto/hmac-drbg.js | 123 +++++++++++++++++++++++++++ lib/crypto/schnorr.js | 183 ++++++++++++++-------------------------- package.json | 1 - 3 files changed, 184 insertions(+), 123 deletions(-) create mode 100644 lib/crypto/hmac-drbg.js diff --git a/lib/crypto/hmac-drbg.js b/lib/crypto/hmac-drbg.js new file mode 100644 index 00000000..e084b7e6 --- /dev/null +++ b/lib/crypto/hmac-drbg.js @@ -0,0 +1,123 @@ +/*! + * hmac-drbg.js - hmac-drbg implementation for bcoin + * Copyright (c) 2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + * Parts of this software based on hmac-drbg. + */ + +'use strict'; + +var assert = require('assert'); +var backend = require('./backend'); + +/* + * Constants + */ + +var HASH_ALG = 'sha256'; +var HASH_SIZE = 32; +var RESEED_INTERVAL = 0x1000000000000; +var POOL33 = Buffer.allocUnsafe(HASH_SIZE + 1); +var POOL112 = Buffer.allocUnsafe(HASH_SIZE * 2 + 48); +var POOL145 = Buffer.allocUnsafe(POOL33.length + POOL112.length); + +/** + * HmacDRBG + * @constructor + */ + +function HmacDRBG(entropy, nonce, pers) { + if (!(this instanceof HmacDRBG)) + return new HmacDRBG(entropy, nonce, pers); + + this.K = Buffer.allocUnsafe(HASH_SIZE); + this.V = Buffer.allocUnsafe(HASH_SIZE); + this.rounds = 0; + + this.init(entropy, nonce, pers); +} + +HmacDRBG.prototype.init = function init(entropy, nonce, pers) { + var i; + + for (i = 0; i < this.V.length; i++) { + this.K[i] = 0x00; + this.V[i] = 0x01; + } + + this.reseed(entropy, nonce, pers); +}; + +HmacDRBG.prototype.reseed = function reseed(entropy, nonce, pers) { + var seed = POOL112; + var i; + + assert(Buffer.isBuffer(entropy)); + assert(Buffer.isBuffer(nonce)); + assert(Buffer.isBuffer(pers)); + + assert(entropy.length === HASH_SIZE); + assert(nonce.length === HASH_SIZE); + assert(pers.length === 48); + + entropy.copy(seed, 0); + nonce.copy(seed, HASH_SIZE); + pers.copy(seed, HASH_SIZE * 2); + + this.update(seed); + this.rounds = 1; +}; + +HmacDRBG.prototype.iterate = function iterate() { + var data = POOL33; + + this.V.copy(data, 0); + data[HASH_SIZE] = 0x00; + + this.K = backend.hmac(HASH_ALG, data, this.K); + this.V = backend.hmac(HASH_ALG, this.V, this.K); +}; + +HmacDRBG.prototype.update = function update(seed) { + var data = POOL145; + + assert(Buffer.isBuffer(seed)); + assert(seed.length === HASH_SIZE * 2 + 48); + + this.V.copy(data, 0); + data[HASH_SIZE] = 0x00; + seed.copy(data, HASH_SIZE + 1); + + this.K = backend.hmac(HASH_ALG, data, this.K); + this.V = backend.hmac(HASH_ALG, this.V, this.K); + + data[HASH_SIZE] = 0x01; + + this.K = backend.hmac(HASH_ALG, data, this.K); + this.V = backend.hmac(HASH_ALG, this.V, this.K); +}; + +HmacDRBG.prototype.generate = function generate(len) { + var data = Buffer.allocUnsafe(len); + var pos = 0; + + if (this.rounds > RESEED_INTERVAL) + throw new Error('Reseed is required.'); + + while (pos < len) { + this.V = backend.hmac(HASH_ALG, this.V, this.K); + this.V.copy(data, pos); + pos += HASH_SIZE; + } + + this.iterate(); + this.rounds++; + + return data; +}; + +/* + * Expose + */ + +module.exports = HmacDRBG; diff --git a/lib/crypto/schnorr.js b/lib/crypto/schnorr.js index 499db16e..ef5dd88b 100644 --- a/lib/crypto/schnorr.js +++ b/lib/crypto/schnorr.js @@ -8,13 +8,14 @@ var elliptic = require('elliptic'); var Signature = require('elliptic/lib/elliptic/ec/signature'); -var hmacDRBG = require('hmac-drbg'); var BN = require('./bn'); +var HmacDRBG = require('./hmac-drbg'); var sha256 = require('./backend').sha256; var secp256k1 = elliptic.ec('secp256k1'); var curve = secp256k1.curve; var curves = elliptic.curves; var hash = curves.secp256k1.hash; +var POOL64 = Buffer.allocUnsafe(64); /** * @exports crypto/schnorr @@ -26,23 +27,17 @@ var schnorr = exports; * Hash (r | M). * @param {Buffer} msg * @param {BN} r - * @param {Function?} hash * @returns {Buffer} */ -schnorr.hash = function _hash(msg, r, hash) { +schnorr.hash = function _hash(msg, r) { var R = r.toArrayLike(Buffer, 'be', 32); - var B = Buffer.allocUnsafe(64); - var H; - - if (!hash) - hash = sha256; + var B = POOL64; R.copy(B, 0); msg.copy(B, 32); - H = hash(B); - return new BN(H); + return new BN(sha256(B)); }; /** @@ -51,46 +46,49 @@ schnorr.hash = function _hash(msg, r, hash) { * @param {Buffer} msg * @param {BN} priv * @param {BN} k - * @param {Function|null} hash - * @param {Buffer} pubnonce + * @param {Buffer} pn * @returns {Signature|null} */ -schnorr._sign = function _sign(msg, prv, k, hash, pubnonce) { +schnorr.trySign = function trySign(msg, prv, k, pn) { var r, pn, h, s; + if (prv.cmpn(0) === 0) + throw new Error('Bad private key.'); + + if (prv.cmp(curve.n) >= 0) + throw new Error('Bad private key.'); + if (k.cmpn(0) === 0) - return; + return null; if (k.cmp(curve.n) >= 0) - return; + return null; r = curve.g.mul(k); - if (pubnonce) { - pn = curve.decodePoint(pubnonce); + if (pn) r = r.add(pn); - } if (r.y.isOdd()) { k = k.umod(curve.n); k = curve.n.sub(k); } - h = schnorr.hash(msg, r.getX(), hash); + h = schnorr.hash(msg, r.getX()); if (h.cmpn(0) === 0) - return; + return null; if (h.cmp(curve.n) >= 0) - return; + return null; s = h.imul(prv); s = k.isub(s); s = s.umod(curve.n); if (s.cmpn(0) === 0) - return; + return null; return new Signature({ r: r.getX(), s: s }); }; @@ -99,26 +97,22 @@ schnorr._sign = function _sign(msg, prv, k, hash, pubnonce) { * Sign message. * @param {Buffer} msg * @param {Buffer} key - * @param {Function?} hash - * @param {Buffer} pubnonce + * @param {Buffer} pubNonce * @returns {Signature} */ -schnorr.sign = function sign(msg, key, hash, pubnonce) { +schnorr.sign = function sign(msg, key, pubNonce) { var prv = new BN(key); - var k, sig, drbg; + var drbg = schnorr.drbg(msg, key, pubNonce); + var len = curve.n.byteLength(); + var k, pn, sig; - if (prv.cmpn(0) === 0) - throw new Error('Bad private key.'); - - if (prv.cmp(curve.n) >= 0) - throw new Error('Bad private key.'); - - drbg = schnorr.drbg(msg, key, pubnonce); + if (pubNonce) + pn = curve.decodePoint(pubNonce); while (!sig) { - k = new BN(drbg.generate(curve.n.byteLength())); - sig = schnorr._sign(msg, prv, k, hash, pubnonce); + k = new BN(drbg.generate(len)); + sig = schnorr.trySign(msg, prv, k, pn); } return sig; @@ -129,13 +123,12 @@ schnorr.sign = function sign(msg, key, hash, pubnonce) { * @param {Buffer} msg * @param {Buffer} signature * @param {Buffer} key - * @param {Function?} hash * @returns {Buffer} */ -schnorr.verify = function verify(msg, signature, key, hash) { +schnorr.verify = function verify(msg, signature, key) { var sig = new Signature(signature); - var h = schnorr.hash(msg, sig.r, hash); + var h = schnorr.hash(msg, sig.r); var k, l, r, rl; if (h.cmp(curve.n) >= 0) @@ -165,13 +158,12 @@ schnorr.verify = function verify(msg, signature, key, hash) { * Recover public key. * @param {Buffer} msg * @param {Buffer} signature - * @param {Function?} hash * @returns {Buffer} */ -schnorr.recover = function recover(signature, msg, hash) { +schnorr.recover = function recover(signature, msg) { var sig = new Signature(signature); - var h = schnorr.hash(msg, sig.r, hash); + var h = schnorr.hash(msg, sig.r); var hinv, s, R, l, r, k, rl; if (h.cmp(curve.n) >= 0) @@ -280,23 +272,16 @@ schnorr.combineKeys = function combineKeys(keys) { * Partially sign. * @param {Buffer} msg * @param {Buffer} priv - * @param {Buffer} privnonce - * @param {Buffer} pubs - * @param {Function?} hash + * @param {Buffer} privNonce + * @param {Buffer} pubNonce * @returns {Buffer} */ -schnorr.partialSign = function partialSign(msg, priv, privnonce, pubs, hash) { +schnorr.partialSign = function partialSign(msg, priv, privNonce, pubNonce) { var prv = new BN(priv); - var sig; - - if (prv.cmpn(0) === 0) - throw new Error('Bad private key.'); - - if (prv.cmp(curve.n) >= 0) - throw new Error('Bad private key.'); - - sig = schnorr._sign(msg, prv, new BN(privnonce), hash, pubs); + var k = new BN(privNonce); + var pn = curve.decodePoint(pubNonce); + var sig = schnorr.trySign(msg, prv, k, pn); if (!sig) throw new Error('Bad K value.'); @@ -320,63 +305,18 @@ schnorr.alg = Buffer.from('Schnorr+SHA256 ', 'ascii'); */ schnorr.drbg = function drbg(msg, priv, data) { - var kdata = Buffer.allocUnsafe(112); - var prv, pers; + var pers = Buffer.allocUnsafe(48); - kdata.fill(0); + pers.fill(0); - priv.copy(kdata, 0); - msg.copy(kdata, 32); + if (data) { + assert(data.length === 32); + data.copy(pers, 0); + } - if (data) - data.copy(kdata, 64); + schnorr.alg.copy(pers, 32); - schnorr.alg.copy(kdata, 96); - - prv = toArray(kdata.slice(0, 32)); - msg = toArray(kdata.slice(32, 64)); - pers = toArray(kdata.slice(64)); - - return new hmacDRBG({ - hash: hash, - entropy: prv, - nonce: msg, - pers: pers - }); -}; - -/** - * Perform hmac drbg according to rfc6979. - * @param {Buffer} msg - * @param {Buffer} priv - * @param {Buffer} data - * @returns {Buffer} - */ - -schnorr.rfc6979 = function rfc6979(msg, priv, data) { - var drbg = schnorr.drbg(msg, priv, data); - var bytes = drbg.generate(curve.n.byteLength()); - return Buffer.from(bytes); -}; - -/** - * Create a schnorr nonce with a nonce callback. - * @param {Buffer} msg - * @param {Buffer} priv - * @param {Buffer} data - * @param {Function?} ncb - * @returns {BN} - */ - -schnorr.nonce = function nonce(msg, priv, data, ncb) { - var pubnonce; - - if (!ncb) - ncb = schnorr.rfc6979; - - pubnonce = ncb(msg, priv, data); - - return new BN(pubnonce); + return new HmacDRBG(priv, msg, pers); }; /** @@ -384,26 +324,25 @@ schnorr.nonce = function nonce(msg, priv, data, ncb) { * @param {Buffer} msg * @param {Buffer} priv * @param {Buffer} data - * @param {Function?} ncb * @returns {Buffer} */ -schnorr.generateNoncePair = function generateNoncePair(msg, priv, data, ncb) { - var k = schnorr.nonce(priv, msg, data, ncb); +schnorr.generateNoncePair = function generateNoncePair(msg, priv, data) { + var drbg = schnorr.drbg(msg, priv, data); + var len = curve.n.byteLength(); + var k; - if (k.cmpn(0) === 0) - throw new Error('Bad nonce.'); + for (;;) { + k = new BN(drbg.generate(len)); - if (k.cmp(curve.n) >= 0) - throw new Error('Bad nonce.'); + if (k.cmpn(0) === 0) + continue; + + if (k.cmp(curve.n) >= 0) + continue; + + break; + } return Buffer.from(curve.g.mul(k).encode('array', true)); }; - -/* - * Helpers - */ - -function toArray(obj) { - return Array.prototype.slice.call(obj); -} diff --git a/package.json b/package.json index 7b31b6d0..c92fed39 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "dependencies": { "bn.js": "4.11.6", "elliptic": "6.4.0", - "hmac-drbg": "^1.0.0", "n64": "0.0.11" }, "optionalDependencies": {