fcoin/lib/crypto/schnorr.js
2017-06-24 04:45:46 -07:00

347 lines
6.4 KiB
JavaScript

/*!
* schnorr.js - schnorr signatures for bcoin
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var elliptic = require('elliptic');
var Signature = require('elliptic/lib/elliptic/ec/signature');
var BN = require('./bn');
var HmacDRBG = require('./hmac-drbg');
var sha256 = require('./backend').sha256;
var curve = elliptic.ec('secp256k1').curve;
var POOL64 = Buffer.allocUnsafe(64);
/**
* @exports crypto/schnorr
*/
var schnorr = exports;
/**
* Hash (r | M).
* @param {Buffer} msg
* @param {BN} r
* @returns {Buffer}
*/
schnorr.hash = function _hash(msg, r) {
var R = r.toArrayLike(Buffer, 'be', 32);
var B = POOL64;
R.copy(B, 0);
msg.copy(B, 32);
return new BN(sha256(B));
};
/**
* Sign message.
* @private
* @param {Buffer} msg
* @param {BN} priv
* @param {BN} k
* @param {Buffer} pn
* @returns {Signature|null}
*/
schnorr.trySign = function trySign(msg, prv, k, pn) {
var r, 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 null;
if (k.cmp(curve.n) >= 0)
return null;
r = curve.g.mul(k);
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());
if (h.cmpn(0) === 0)
return null;
if (h.cmp(curve.n) >= 0)
return null;
s = h.imul(prv);
s = k.isub(s);
s = s.umod(curve.n);
if (s.cmpn(0) === 0)
return null;
return new Signature({ r: r.getX(), s: s });
};
/**
* Sign message.
* @param {Buffer} msg
* @param {Buffer} key
* @param {Buffer} pubNonce
* @returns {Signature}
*/
schnorr.sign = function sign(msg, key, pubNonce) {
var prv = new BN(key);
var drbg = schnorr.drbg(msg, key, pubNonce);
var len = curve.n.byteLength();
var k, pn, sig;
if (pubNonce)
pn = curve.decodePoint(pubNonce);
while (!sig) {
k = new BN(drbg.generate(len));
sig = schnorr.trySign(msg, prv, k, pn);
}
return sig;
};
/**
* Verify signature.
* @param {Buffer} msg
* @param {Buffer} signature
* @param {Buffer} key
* @returns {Buffer}
*/
schnorr.verify = function verify(msg, signature, key) {
var sig = new Signature(signature);
var h = schnorr.hash(msg, sig.r);
var k, l, r, rl;
if (h.cmp(curve.n) >= 0)
throw new Error('Invalid hash.');
if (h.cmpn(0) === 0)
throw new Error('Invalid hash.');
if (sig.s.cmp(curve.n) >= 0)
throw new Error('Invalid S value.');
if (sig.r.cmp(curve.p) > 0)
throw new Error('Invalid R value.');
k = curve.decodePoint(key);
l = k.mul(h);
r = curve.g.mul(sig.s);
rl = l.add(r);
if (rl.y.isOdd())
throw new Error('Odd R value.');
return rl.getX().cmp(sig.r) === 0;
};
/**
* Recover public key.
* @param {Buffer} msg
* @param {Buffer} signature
* @returns {Buffer}
*/
schnorr.recover = function recover(signature, msg) {
var sig = new Signature(signature);
var h = schnorr.hash(msg, sig.r);
var hinv, s, R, l, r, k, rl;
if (h.cmp(curve.n) >= 0)
throw new Error('Invalid hash.');
if (h.cmpn(0) === 0)
throw new Error('Invalid hash.');
if (sig.s.cmp(curve.n) >= 0)
throw new Error('Invalid S value.');
if (sig.r.cmp(curve.p) > 0)
throw new Error('Invalid R value.');
hinv = h.invm(curve.n);
hinv = hinv.umod(curve.n);
s = sig.s;
s = curve.n.sub(s);
s = s.umod(curve.n);
s = s.imul(hinv);
s = s.umod(curve.n);
R = curve.pointFromX(sig.r, false);
l = R.mul(hinv);
r = curve.g.mul(s);
k = l.add(r);
l = k.mul(h);
r = curve.g.mul(sig.s);
rl = l.add(r);
if (rl.y.isOdd())
throw new Error('Odd R value.');
if (rl.getX().cmp(sig.r) !== 0)
throw new Error('Could not recover pubkey.');
return Buffer.from(k.encode('array', true));
};
/**
* Combine signatures.
* @param {Buffer[]} sigs
* @returns {Signature}
*/
schnorr.combineSigs = function combineSigs(sigs) {
var s = new BN(0);
var i, r, sig, last;
for (i = 0; i < sigs.length; i++) {
sig = new Signature(sigs[i]);
if (sig.s.cmpn(0) === 0)
throw new Error('Bad S value.');
if (sig.s.cmp(curve.n) >= 0)
throw new Error('Bad S value.');
if (!r)
r = sig.r;
if (last && last.r.cmp(sig.r) !== 0)
throw new Error('Bad signature combination.');
s = s.iadd(sig.s);
s = s.umod(curve.n);
last = sig;
}
if (s.cmpn(0) === 0)
throw new Error('Bad combined signature.');
return new Signature({ r: r, s: s });
};
/**
* Combine public keys.
* @param {Buffer[]} keys
* @returns {Buffer}
*/
schnorr.combineKeys = function combineKeys(keys) {
var i, key, point;
if (keys.length === 0)
throw new Error();
if (keys.length === 1)
return keys[0];
point = curve.decodePoint(keys[0]);
for (i = 1; i < keys.length; i++) {
key = curve.decodePoint(keys[i]);
point = point.add(key);
}
return Buffer.from(point.encode('array', true));
};
/**
* Partially sign.
* @param {Buffer} msg
* @param {Buffer} priv
* @param {Buffer} privNonce
* @param {Buffer} pubNonce
* @returns {Buffer}
*/
schnorr.partialSign = function partialSign(msg, priv, privNonce, pubNonce) {
var prv = new BN(priv);
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.');
return sig;
};
/**
* Schnorr personalization string.
* @const {Buffer}
*/
schnorr.alg = Buffer.from('Schnorr+SHA256 ', 'ascii');
/**
* Instantiate an HMAC-DRBG.
* @param {Buffer} msg
* @param {Buffer} priv
* @param {Buffer} data
* @returns {HmacDRBG}
*/
schnorr.drbg = function drbg(msg, priv, data) {
var pers = Buffer.allocUnsafe(48);
pers.fill(0);
if (data) {
assert(data.length === 32);
data.copy(pers, 0);
}
schnorr.alg.copy(pers, 32);
return new HmacDRBG(priv, msg, pers);
};
/**
* Generate pub+priv nonce pair.
* @param {Buffer} msg
* @param {Buffer} priv
* @param {Buffer} data
* @returns {Buffer}
*/
schnorr.generateNoncePair = function generateNoncePair(msg, priv, data) {
var drbg = schnorr.drbg(msg, priv, data);
var len = curve.n.byteLength();
var k;
for (;;) {
k = new BN(drbg.generate(len));
if (k.cmpn(0) === 0)
continue;
if (k.cmp(curve.n) >= 0)
continue;
break;
}
return Buffer.from(curve.g.mul(k).encode('array', true));
};