crypto: preliminary support for schnorr signatures.
This commit is contained in:
parent
3376ffba56
commit
a0726efe75
290
lib/crypto/schnorr.js
Normal file
290
lib/crypto/schnorr.js
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
/*!
|
||||||
|
* schnorr.js - schnorr signatures for bcoin
|
||||||
|
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
|
||||||
|
* https://github.com/bcoin-org/bcoin
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var bn = require('bn.js');
|
||||||
|
var elliptic = require('elliptic');
|
||||||
|
var Signature = require('elliptic/lib/elliptic/ec/signature');
|
||||||
|
var ec = require('./ec');
|
||||||
|
var curve = elliptic.ec('secp256k1').curve;
|
||||||
|
var sha256 = require('./crypto').sha256;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports schnorr
|
||||||
|
*/
|
||||||
|
|
||||||
|
var schnorr = exports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash (r | M).
|
||||||
|
* @param {Buffer} msg
|
||||||
|
* @param {BN} r
|
||||||
|
* @param {Function?} hash
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
|
schnorr.hash = function _hash(msg, r, hash) {
|
||||||
|
var R = r.toArrayLike(Buffer, 'be', 32);
|
||||||
|
var B = new Buffer(64);
|
||||||
|
var H;
|
||||||
|
|
||||||
|
if (!hash)
|
||||||
|
hash = sha256;
|
||||||
|
|
||||||
|
R.copy(B, 0);
|
||||||
|
msg.copy(B, 32);
|
||||||
|
H = hash(B);
|
||||||
|
|
||||||
|
return new bn(H);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign message.
|
||||||
|
* @param {Buffer} msg
|
||||||
|
* @param {BN} priv
|
||||||
|
* @param {BN} k
|
||||||
|
* @param {Function|null} hash
|
||||||
|
* @param {Buffer} pubnonce
|
||||||
|
* @returns {Signature|null}
|
||||||
|
*/
|
||||||
|
|
||||||
|
schnorr._sign = function _sign(msg, prv, k, hash, pubnonce) {
|
||||||
|
var r, pn, h, s;
|
||||||
|
|
||||||
|
if (k.cmpn(0) === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (k.cmp(curve.n) >= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
r = curve.g.mul(k);
|
||||||
|
|
||||||
|
if (pubnonce) {
|
||||||
|
pn = curve.decodePoint(pubnonce);
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (h.cmp(curve.n) >= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
s = h.imul(prv);
|
||||||
|
s = k.isub(s);
|
||||||
|
s = s.umod(curve.n);
|
||||||
|
|
||||||
|
if (s.cmpn(0) === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return new Signature({ r: r.getX(), s: s });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign message.
|
||||||
|
* @param {Buffer} msg
|
||||||
|
* @param {Buffer} key
|
||||||
|
* @param {Function?} hash
|
||||||
|
* @param {Buffer} pubnonce
|
||||||
|
* @returns {Signature}
|
||||||
|
*/
|
||||||
|
|
||||||
|
schnorr.sign = function sign(msg, key, hash, pubnonce) {
|
||||||
|
var prv = new bn(key);
|
||||||
|
var k, sig;
|
||||||
|
|
||||||
|
if (prv.cmpn(0) === 0)
|
||||||
|
throw new Error('Bad private key.');
|
||||||
|
|
||||||
|
if (prv.cmp(curve.n) >= 0)
|
||||||
|
throw new Error('Bad private key.');
|
||||||
|
|
||||||
|
while (!sig) {
|
||||||
|
k = new bn(ec.random(32));
|
||||||
|
sig = schnorr._sign(msg, prv, k, hash, pubnonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify signature.
|
||||||
|
* @param {Buffer} msg
|
||||||
|
* @param {Buffer} signature
|
||||||
|
* @param {Buffer} key
|
||||||
|
* @param {Function?} hash
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
|
schnorr.verify = function verify(msg, signature, key, hash) {
|
||||||
|
var sig = new Signature(signature);
|
||||||
|
var h = schnorr.hash(msg, sig.r, hash);
|
||||||
|
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
|
||||||
|
* @param {Function?} hash
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
|
schnorr.recover = function recover(signature, msg, hash) {
|
||||||
|
var sig = new Signature(signature);
|
||||||
|
var h = schnorr.hash(msg, sig.r, hash);
|
||||||
|
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 new Buffer(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.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 new Buffer(point.encode('array', true));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partially sign.
|
||||||
|
* @param {Buffer} msg
|
||||||
|
* @param {Buffer} priv
|
||||||
|
* @param {Buffer} privnonce
|
||||||
|
* @param {Buffer} pubs
|
||||||
|
* @param {Function?} hash
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
|
schnorr.partialSign = function partialSign(msg, priv, privnonce, pubs, hash) {
|
||||||
|
var prv = new bn(priv);
|
||||||
|
|
||||||
|
if (prv.cmpn(0) === 0)
|
||||||
|
throw new Error('Bad private key.');
|
||||||
|
|
||||||
|
if (prv.cmp(curve.n) >= 0)
|
||||||
|
throw new Error('Bad private key.');
|
||||||
|
|
||||||
|
return schnorr._sign(msg, prv, new bn(privnonce), hash, pubs);
|
||||||
|
};
|
||||||
@ -4,6 +4,7 @@ var bn = require('bn.js');
|
|||||||
var bcoin = require('../').set('main');
|
var bcoin = require('../').set('main');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var utils = bcoin.utils;
|
var utils = bcoin.utils;
|
||||||
|
var schnorr = require('../lib/crypto/schnorr');
|
||||||
|
|
||||||
describe('Utils', function() {
|
describe('Utils', function() {
|
||||||
var vectors = [
|
var vectors = [
|
||||||
@ -290,4 +291,13 @@ describe('Utils', function() {
|
|||||||
assert.equal(prk.toString('hex'), prkE);
|
assert.equal(prk.toString('hex'), prkE);
|
||||||
assert.equal(okm.toString('hex'), okmE);
|
assert.equal(okm.toString('hex'), okmE);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should do proper schnorr', function() {
|
||||||
|
var key = bcoin.ec.generatePrivateKey();
|
||||||
|
var pub = bcoin.ec.publicKeyCreate(key, true);
|
||||||
|
var msg = utils.hash256(new Buffer('foo', 'ascii'));
|
||||||
|
var sig = schnorr.sign(msg, key);
|
||||||
|
assert(schnorr.verify(msg, sig, pub));
|
||||||
|
assert.deepEqual(schnorr.recover(sig, msg), pub);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user