schnorr: add hmac-drbg. refactor.

This commit is contained in:
Christopher Jeffrey 2017-06-22 05:22:06 -07:00
parent 33ffb780b3
commit 68a3209a96
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 184 additions and 123 deletions

123
lib/crypto/hmac-drbg.js Normal file
View File

@ -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;

View File

@ -8,13 +8,14 @@
var elliptic = require('elliptic'); var elliptic = require('elliptic');
var Signature = require('elliptic/lib/elliptic/ec/signature'); var Signature = require('elliptic/lib/elliptic/ec/signature');
var hmacDRBG = require('hmac-drbg');
var BN = require('./bn'); var BN = require('./bn');
var HmacDRBG = require('./hmac-drbg');
var sha256 = require('./backend').sha256; var sha256 = require('./backend').sha256;
var secp256k1 = elliptic.ec('secp256k1'); var secp256k1 = elliptic.ec('secp256k1');
var curve = secp256k1.curve; var curve = secp256k1.curve;
var curves = elliptic.curves; var curves = elliptic.curves;
var hash = curves.secp256k1.hash; var hash = curves.secp256k1.hash;
var POOL64 = Buffer.allocUnsafe(64);
/** /**
* @exports crypto/schnorr * @exports crypto/schnorr
@ -26,23 +27,17 @@ var schnorr = exports;
* Hash (r | M). * Hash (r | M).
* @param {Buffer} msg * @param {Buffer} msg
* @param {BN} r * @param {BN} r
* @param {Function?} hash
* @returns {Buffer} * @returns {Buffer}
*/ */
schnorr.hash = function _hash(msg, r, hash) { schnorr.hash = function _hash(msg, r) {
var R = r.toArrayLike(Buffer, 'be', 32); var R = r.toArrayLike(Buffer, 'be', 32);
var B = Buffer.allocUnsafe(64); var B = POOL64;
var H;
if (!hash)
hash = sha256;
R.copy(B, 0); R.copy(B, 0);
msg.copy(B, 32); 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 {Buffer} msg
* @param {BN} priv * @param {BN} priv
* @param {BN} k * @param {BN} k
* @param {Function|null} hash * @param {Buffer} pn
* @param {Buffer} pubnonce
* @returns {Signature|null} * @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; 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) if (k.cmpn(0) === 0)
return; return null;
if (k.cmp(curve.n) >= 0) if (k.cmp(curve.n) >= 0)
return; return null;
r = curve.g.mul(k); r = curve.g.mul(k);
if (pubnonce) { if (pn)
pn = curve.decodePoint(pubnonce);
r = r.add(pn); r = r.add(pn);
}
if (r.y.isOdd()) { if (r.y.isOdd()) {
k = k.umod(curve.n); k = k.umod(curve.n);
k = curve.n.sub(k); k = curve.n.sub(k);
} }
h = schnorr.hash(msg, r.getX(), hash); h = schnorr.hash(msg, r.getX());
if (h.cmpn(0) === 0) if (h.cmpn(0) === 0)
return; return null;
if (h.cmp(curve.n) >= 0) if (h.cmp(curve.n) >= 0)
return; return null;
s = h.imul(prv); s = h.imul(prv);
s = k.isub(s); s = k.isub(s);
s = s.umod(curve.n); s = s.umod(curve.n);
if (s.cmpn(0) === 0) if (s.cmpn(0) === 0)
return; return null;
return new Signature({ r: r.getX(), s: s }); return new Signature({ r: r.getX(), s: s });
}; };
@ -99,26 +97,22 @@ schnorr._sign = function _sign(msg, prv, k, hash, pubnonce) {
* Sign message. * Sign message.
* @param {Buffer} msg * @param {Buffer} msg
* @param {Buffer} key * @param {Buffer} key
* @param {Function?} hash * @param {Buffer} pubNonce
* @param {Buffer} pubnonce
* @returns {Signature} * @returns {Signature}
*/ */
schnorr.sign = function sign(msg, key, hash, pubnonce) { schnorr.sign = function sign(msg, key, pubNonce) {
var prv = new BN(key); 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) if (pubNonce)
throw new Error('Bad private key.'); pn = curve.decodePoint(pubNonce);
if (prv.cmp(curve.n) >= 0)
throw new Error('Bad private key.');
drbg = schnorr.drbg(msg, key, pubnonce);
while (!sig) { while (!sig) {
k = new BN(drbg.generate(curve.n.byteLength())); k = new BN(drbg.generate(len));
sig = schnorr._sign(msg, prv, k, hash, pubnonce); sig = schnorr.trySign(msg, prv, k, pn);
} }
return sig; return sig;
@ -129,13 +123,12 @@ schnorr.sign = function sign(msg, key, hash, pubnonce) {
* @param {Buffer} msg * @param {Buffer} msg
* @param {Buffer} signature * @param {Buffer} signature
* @param {Buffer} key * @param {Buffer} key
* @param {Function?} hash
* @returns {Buffer} * @returns {Buffer}
*/ */
schnorr.verify = function verify(msg, signature, key, hash) { schnorr.verify = function verify(msg, signature, key) {
var sig = new Signature(signature); 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; var k, l, r, rl;
if (h.cmp(curve.n) >= 0) if (h.cmp(curve.n) >= 0)
@ -165,13 +158,12 @@ schnorr.verify = function verify(msg, signature, key, hash) {
* Recover public key. * Recover public key.
* @param {Buffer} msg * @param {Buffer} msg
* @param {Buffer} signature * @param {Buffer} signature
* @param {Function?} hash
* @returns {Buffer} * @returns {Buffer}
*/ */
schnorr.recover = function recover(signature, msg, hash) { schnorr.recover = function recover(signature, msg) {
var sig = new Signature(signature); 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; var hinv, s, R, l, r, k, rl;
if (h.cmp(curve.n) >= 0) if (h.cmp(curve.n) >= 0)
@ -280,23 +272,16 @@ schnorr.combineKeys = function combineKeys(keys) {
* Partially sign. * Partially sign.
* @param {Buffer} msg * @param {Buffer} msg
* @param {Buffer} priv * @param {Buffer} priv
* @param {Buffer} privnonce * @param {Buffer} privNonce
* @param {Buffer} pubs * @param {Buffer} pubNonce
* @param {Function?} hash
* @returns {Buffer} * @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 prv = new BN(priv);
var sig; var k = new BN(privNonce);
var pn = curve.decodePoint(pubNonce);
if (prv.cmpn(0) === 0) var sig = schnorr.trySign(msg, prv, k, pn);
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);
if (!sig) if (!sig)
throw new Error('Bad K value.'); throw new Error('Bad K value.');
@ -320,63 +305,18 @@ schnorr.alg = Buffer.from('Schnorr+SHA256 ', 'ascii');
*/ */
schnorr.drbg = function drbg(msg, priv, data) { schnorr.drbg = function drbg(msg, priv, data) {
var kdata = Buffer.allocUnsafe(112); var pers = Buffer.allocUnsafe(48);
var prv, pers;
kdata.fill(0); pers.fill(0);
priv.copy(kdata, 0); if (data) {
msg.copy(kdata, 32); assert(data.length === 32);
data.copy(pers, 0);
}
if (data) schnorr.alg.copy(pers, 32);
data.copy(kdata, 64);
schnorr.alg.copy(kdata, 96); return new HmacDRBG(priv, msg, pers);
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);
}; };
/** /**
@ -384,26 +324,25 @@ schnorr.nonce = function nonce(msg, priv, data, ncb) {
* @param {Buffer} msg * @param {Buffer} msg
* @param {Buffer} priv * @param {Buffer} priv
* @param {Buffer} data * @param {Buffer} data
* @param {Function?} ncb
* @returns {Buffer} * @returns {Buffer}
*/ */
schnorr.generateNoncePair = function generateNoncePair(msg, priv, data, ncb) { schnorr.generateNoncePair = function generateNoncePair(msg, priv, data) {
var k = schnorr.nonce(priv, msg, data, ncb); var drbg = schnorr.drbg(msg, priv, data);
var len = curve.n.byteLength();
var k;
if (k.cmpn(0) === 0) for (;;) {
throw new Error('Bad nonce.'); k = new BN(drbg.generate(len));
if (k.cmp(curve.n) >= 0) if (k.cmpn(0) === 0)
throw new Error('Bad nonce.'); continue;
if (k.cmp(curve.n) >= 0)
continue;
break;
}
return Buffer.from(curve.g.mul(k).encode('array', true)); return Buffer.from(curve.g.mul(k).encode('array', true));
}; };
/*
* Helpers
*/
function toArray(obj) {
return Array.prototype.slice.call(obj);
}

View File

@ -25,7 +25,6 @@
"dependencies": { "dependencies": {
"bn.js": "4.11.6", "bn.js": "4.11.6",
"elliptic": "6.4.0", "elliptic": "6.4.0",
"hmac-drbg": "^1.0.0",
"n64": "0.0.11" "n64": "0.0.11"
}, },
"optionalDependencies": { "optionalDependencies": {