/*! * ec.js - ecdsa wrapper for secp256k1 and elliptic * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var elliptic = require('elliptic'); var bn = require('bn.js'); var utils = require('../utils/utils'); var crypto = require('./crypto'); var assert = require('assert'); var secp256k1; try { if (+process.env.BCOIN_USE_ELLIPTIC !== 1) secp256k1 = require('secp256k1'); } catch (e) { ; } /* * Constants */ var ZERO_S = new Buffer( '0000000000000000000000000000000000000000000000000000000000000000', 'hex' ); var HALF_ORDER = new Buffer( '7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 'hex'); /** * @exports ec */ var ec = exports; /** * elliptic.js secp256k1 curve. * @type {Object} */ ec.elliptic = elliptic.ec('secp256k1'); /** * elliptic.js signature constructor. * @static * @type {Function} */ ec.signature = require('elliptic/lib/elliptic/ec/signature'); /** * elliptic.js keypair constructor. * @static * @type {Function} */ ec.keypair = require('elliptic/lib/elliptic/ec/key'); /** * A reference to the secp256k1 curve. * @const {Object} */ ec.curve = ec.elliptic.curve; /** * Generate a private key. * @returns {Buffer} Private key. */ ec.generatePrivateKey = function generatePrivateKey() { var key, priv; if (secp256k1) { do { priv = crypto.randomBytes(32); } while (!secp256k1.privateKeyVerify(priv)); } else { key = ec.elliptic.genKeyPair(); priv = key.getPrivate().toArrayLike(Buffer, 'be', 32); } return priv; }; /** * Create a public key from a private key. * @param {Buffer} priv * @param {Boolean?} compressed * @returns {Buffer} */ ec.publicKeyCreate = function publicKeyCreate(priv, compressed) { assert(Buffer.isBuffer(priv)); if (secp256k1) return secp256k1.publicKeyCreate(priv, compressed); priv = ec.elliptic.keyPair({ priv: priv }); priv = priv.getPublic(compressed !== false, 'array'); return new Buffer(priv); }; /** * Compress or decompress public key. * @param {Buffer} pub * @returns {Buffer} */ ec.publicKeyConvert = function publicKeyConvert(key, compressed) { var point; if (secp256k1) return secp256k1.publicKeyConvert(key, compressed); point = ec.curve.decodePoint(key); return new Buffer(point.encode('array', compressed !== false)); }; /** * ((tweak + key) % n) * @param {Buffer} privateKey * @param {Buffer} tweak * @returns {Buffer} privateKey */ ec.privateKeyTweakAdd = function privateKeyTweakAdd(privateKey, tweak) { var key; if (secp256k1) return secp256k1.privateKeyTweakAdd(privateKey, tweak); key = new bn(tweak) .add(new bn(privateKey)) .mod(ec.curve.n) .toArrayLike(Buffer, 'be', 32); // Only a 1 in 2^127 chance of happening. if (!ec.privateKeyVerify(key)) throw new Error('Private key is invalid.'); return key; }; /** * ((g * tweak) + key) * @param {Buffer} publicKey * @param {Buffer} tweak * @returns {Buffer} publicKey */ ec.publicKeyTweakAdd = function publicKeyTweakAdd(publicKey, tweak, compressed) { var point, key; if (secp256k1) return secp256k1.publicKeyTweakAdd(publicKey, tweak, compressed); point = ec.curve.decodePoint(publicKey); point = ec.curve.g.mul(new bn(tweak)).add(point); key = new Buffer(point.encode('array', compressed !== false)); if (!ec.publicKeyVerify(key)) throw new Error('Public key is invalid.'); return key; }; /** * Create an ecdh. * @param {Buffer} pub * @param {Buffer} priv * @returns {Buffer} */ ec.ecdh = function ecdh(pub, priv) { var point; if (secp256k1) { point = secp256k1.ecdhUnsafe(pub, priv, true); return point.slice(1, 33); } priv = ec.elliptic.keyPair({ priv: priv }); pub = ec.elliptic.keyPair({ pub: pub }); return priv.derive(pub.getPublic()).toArrayLike(Buffer, 'be', 32); }; /** * Recover a public key. * @param {Buffer} msg * @param {Buffer} sig * @param {Number?} j * @param {Boolean?} compressed * @returns {Buffer[]|Buffer|null} */ ec.recover = function recover(msg, sig, j, compressed) { var point, key; if (!j) j = 0; if (secp256k1) { try { sig = secp256k1.signatureImport(sig); } catch (e) { return; } try { key = secp256k1.recover(msg, sig, j, compressed); } catch (e) { return; } return key; } try { point = ec.elliptic.recoverPubKey(msg, sig, j); } catch (e) { return; } key = point.encode('array', compressed !== false); return new Buffer(key); }; /** * Verify a signature. * @param {Buffer} msg * @param {Buffer} sig - DER formatted. * @param {Buffer} key * @param {Boolean?} - Whether this should be treated as a * "historical" signature. This allows signatures to be of * odd lengths. * @param {Boolean?} high - Allow high S value. * @returns {Boolean} */ ec.verify = function verify(msg, sig, key, historical, high) { var result; assert(Buffer.isBuffer(msg)); assert(Buffer.isBuffer(sig)); assert(Buffer.isBuffer(key)); if (sig.length === 0) return false; if (key.length === 0) return false; if (secp256k1) { try { if (historical) sig = secp256k1.signatureImportLax(sig); else sig = secp256k1.signatureImport(sig); if (high) sig = secp256k1.signatureNormalize(sig); result = secp256k1.verify(msg, sig, key); } catch (e) { result = false; } return result; } // 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 = ec.normalizeLength(sig); // Make elliptic mimic secp256k1's // failure with high S values. if (!high && !ec.isLowS(sig)) return false; try { result = ec.elliptic.verify(msg, sig, key); } catch (e) { result = false; } return result; }; /** * Validate a public key. * @param {Buffer} key * @returns {Boolean} True if buffer is a valid public key. */ ec.publicKeyVerify = function publicKeyVerify(key) { var result; if (secp256k1) return secp256k1.publicKeyVerify(key); try { result = ec.elliptic.keyPair({ pub: key }).validate(); } catch (e) { result = false; } return result; }; /** * Validate a private key. * @param {Buffer} key * @returns {Boolean} True if buffer is a valid private key. */ ec.privateKeyVerify = function privateKeyVerify(key) { if (secp256k1) return secp256k1.privateKeyVerify(key); if (key.length !== 32) return false; key = new bn(key); return key.cmpn(0) !== 0 && key.cmp(ec.curve.n) < 0; }; /** * Sign a message. * @param {Buffer} msg * @param {Buffer} key - Private key. * @returns {Buffer} DER-formatted signature. */ ec.sign = function sign(msg, key) { var sig; assert(Buffer.isBuffer(msg)); assert(Buffer.isBuffer(key)); if (secp256k1) { // Sign message sig = secp256k1.sign(msg, key); // Ensure low S value sig = secp256k1.signatureNormalize(sig.signature); // Convert to DER array sig = secp256k1.signatureExport(sig); } else { // Sign message and ensure low S value sig = ec.elliptic.sign(msg, key, { canonical: true }); // Convert to DER array sig = new Buffer(sig.toDER()); } return sig; }; /** * Convert DER signature to R/S. * @param {Buffer} sig * @returns {Buffer} R/S-formatted signature. */ ec.fromDER = function fromDER(sig) { var out; assert(Buffer.isBuffer(sig)); if (secp256k1) return secp256k1.signatureImport(sig); sig = new ec.signature(sig); out = new Buffer(64); sig.r.toArrayLike(Buffer, 'be', 32).copy(out, 0); sig.s.toArrayLike(Buffer, 'be', 32).copy(out, 32); return out; }; /** * Convert R/S signature to DER. * @param {Buffer} sig * @returns {Buffer} DER-formatted signature. */ ec.toDER = function toDER(sig) { var out; assert(Buffer.isBuffer(sig)); if (secp256k1) return secp256k1.signatureExport(sig); out = new ec.signature({ r: new bn(sig.slice(0, 32), 'be'), s: new bn(sig.slice(32, 64), 'be') }); return new Buffer(out.toDER()); }; /** * Normalize the length of a signature * (only done for historical data). * @param {Buffer} sig - DER formatted signature. * @returns {Buffer} Signature. */ ec.normalizeLength = function normalizeLength(sig) { var data = sig; var p = { place: 0 }; var len, rlen, slen; 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; }; /** * Test whether a signature has a low S value. * @param {Buffer} sig * @returns {Boolean} */ ec.isLowS = function isLowS(sig) { var rs, s; if (secp256k1) { try { rs = secp256k1.signatureImport(sig); s = rs.slice(32, 64); } catch (e) { return false; } if (utils.equal(s, ZERO_S)) return false; // If S is greater than half the order, // it's too high. if (utils.cmp(s, HALF_ORDER) > 0) return false; return true; } try { sig = new ec.signature(sig); } catch (e) { return false; } if (sig.s.cmpn(0) === 0) return false; // If S is greater than half the order, // it's too high. if (sig.s.cmp(ec.elliptic.nh) > 0) return false; return true; }; /* * Helpers */ function getLength(buf, p) { var initial = buf[p.place++]; var octetLen, val, i, off; if (!(initial & 0x80)) return initial; octetLen = initial & 0xf; val = 0; for (i = 0, off = p.place; i < octetLen; i++, off++) { val <<= 8; val |= buf[off]; } p.place = off; return val; }