From c084ce14bb31dcdfdd96f3878ecd529d8ecea354 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Mon, 25 Jan 2016 11:26:31 +0500 Subject: [PATCH] Use secp256k1-node v3.0.0 --- README.md | 105 +--------------------- benchmarks/index.js | 29 +++--- browser.js | 3 +- examples/client.js | 28 ++---- examples/server.js | 15 ++-- gulpfile.js | 48 +++++----- index.js | 2 +- karma.conf.js | 4 +- lib/bitauth-browserify.js | 48 ---------- lib/bitauth-common.js | 181 -------------------------------------- lib/bitauth-node.js | 44 --------- lib/bitauth.js | 179 +++++++++++++++++++++++++++++++++++++ lib/decrypt.js | 4 +- lib/encrypt.js | 4 +- lib/middleware/bitauth.js | 16 ++-- lib/middleware/rawbody.js | 4 +- package.json | 7 +- test/test.bitauth.js | 173 +++++++++++++++--------------------- 18 files changed, 329 insertions(+), 565 deletions(-) delete mode 100644 lib/bitauth-browserify.js delete mode 100644 lib/bitauth-common.js delete mode 100644 lib/bitauth-node.js create mode 100644 lib/bitauth.js diff --git a/README.md b/README.md index dbe12ce..2da3b87 100644 --- a/README.md +++ b/README.md @@ -89,109 +89,8 @@ identity. ## Examples -Example server - -```javascript -var express = require('express'); -var bodyParser = require('body-parser'); -var rawBody = require('../lib/middleware/rawbody'); -var bitauth = require('../lib/middleware/bitauth'); - -var users = { - 'Tf7UNQnxB8SccfoyZScQmb34V2GdEtQkzDz': {name: 'Alice'}, - 'Tf22EUFxHWh4wmA3sDuw151W5C5g32jgph2': {name: 'Bob'} -}; - -var pizzas = []; - -var app = express(); -app.use(rawBody); -app.use(bodyParser()); - - -app.get('/user', bitauth, function(req, res) { - if(!req.sin || !users[req.sin]) return res.send(401, {error: 'Unauthorized'}); - res.send(200, users[req.sin]); -}); - -app.post('/pizzas', bitauth, function(req, res) { - if(!req.sin || !users[req.sin]) return res.send(401, {error: 'Unauthorized'}); - var pizza = req.body; - pizza.owner = users[req.sin].name; - pizzas.push(pizza); - res.send(200, req.body); -}); - -app.get('/pizzas', function(req, res) { - res.send(200, pizzas); -}); - -app.listen(3000); -``` - -Example client - -```javascript -var request = require('request'); -var bitauth = require('../lib/bitauth'); - -// These can be generated with bitauth.generateSin() -var keys = { - alice: '38f93bdda21a5c4a7bae4eb75bb7811cbc3eb627176805c1009ff2099263c6ad', - bob: '09880c962437080d72f72c8c63a69efd65d086c9e7851a87b76373eb6ce9aab5' -}; - -// GET - -for(k in keys) { - var url = 'http://localhost:3000/user'; - var dataToSign = url; - var options = { - url: url, - headers: { - 'x-identity': bitauth.getPublicKeyFromPrivateKey(keys[k]), - 'x-signature': bitauth.sign(dataToSign, keys[k]) - } - }; - - request.get(options, function(err, response, body) { - if(err) { - console.log(err); - } - if(body) { - console.log(body); - } - }); -} - -var pizzas = ['pepperoni', 'sausage', 'veggie', 'hawaiian']; - -// POST - -for(k in keys) { - var url = 'http://localhost:3000/pizzas'; - var data = {type: pizzas[Math.floor(Math.random() * pizzas.length)]}; - var dataToSign = url + JSON.stringify(data); - var options = { - url: url, - headers: { - 'x-identity': bitauth.getPublicKeyFromPrivateKey(keys[k]), - 'x-signature': bitauth.sign(dataToSign, keys[k]) - }, - json: data - }; - - request.post(options, function(err, response, body) { - if(err) { - console.log(err); - } - if(body) { - console.log(body); - } - }); -} - -``` +* [server](https://github.com/bitpay/bitauth/blob/master/examples/server.js) +* [client](https://github.com/bitpay/bitauth/blob/master/examples/client.js) ## Middleware BitAuth exposes a connect middleware for use in connect or ExpressJS applications. Use: diff --git a/benchmarks/index.js b/benchmarks/index.js index 287a1e2..120012f 100644 --- a/benchmarks/index.js +++ b/benchmarks/index.js @@ -1,17 +1,15 @@ 'use strict'; -var assert = require('assert'); var benchmark = require('benchmark'); -var bitauth = require('../lib/bitauth-node'); +var bitauth = require('../lib/bitauth'); var async = require('async'); var maxTime = 10; async.series([ - function(next) { - - var privkey = '9b3bdba1c7910017dae5d6cbfb2e86aafdccfbcbea518d1b984c45817b6c655b'; - var privkeyBuffer = new Buffer(privkey, 'hex'); + function (next) { + // var privkey = '9b3bdba1c7910017dae5d6cbfb2e86aafdccfbcbea518d1b984c45817b6c655b'; + // var privkeyBuffer = new Buffer(privkey, 'hex'); var pubkey = '03ff368ca67364d1df4c0f131b6a454d4fa14c00538357f03235917feabc1a9cb6'; var pubkeyBuffer = new Buffer(pubkey, 'hex'); var contract = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer vestibulum nibh neque, ac fermentum nunc pharetra in. Aenean orci velit, facilisis a gravida eu, ullamcorper feugiat dui. Sed quis eros sed sem egestas sagittis non sit amet arcu. Nulla feugiat purus et sem tempus convallis. Ut a odio consequat, vulputate nisl a, venenatis lectus. Aenean mi diam, pulvinar sed vehicula pulvinar, commodo quis justo. Pellentesque quis elementum eros. Sed ligula tellus, interdum non interdum eget, ultricies in ipsum. Maecenas vitae lectus sit amet ante volutpat malesuada. Nulla condimentum iaculis sem sit amet rhoncus. Mauris at vestibulum felis, a porttitor elit. Pellentesque rhoncus faucibus condimentum. Praesent auctor auctor magna, nec consectetur mi suscipit eget. Nulla sit amet ligula enim. Ut odio augue, auctor ac quam vel, aliquet mattis nisi. Curabitur orci lectus, viverra at hendrerit at, feugiat at magna. Morbi rhoncus bibendum erat, quis dapibus felis eleifend vitae. Etiam vel sapien consequat, tempor libero non, lobortis purus. Maecenas finibus pretium augue a ullamcorper. Donec consectetur sed nunc sed convallis. Phasellus eu magna a nisl lobortis finibus. Quisque hendrerit at arcu tempus gravida. Donec fringilla pulvinar sapien at porta. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed dui metus, rhoncus at iaculis nec, porta at nunc. Donec in purus pellentesque, lacinia erat eget, congue massa. In a magna molestie tellus convallis dictum. Etiam id magna laoreet, suscipit leo non, egestas turpis. Sed dolor orci, pellentesque eget tempor ut, tincidunt at magna. Duis quis imperdiet sapien.'; @@ -19,7 +17,7 @@ async.series([ var signature = '3045022100db71942a5a6dd1443cbf7519b2bc16a041aff8d4830bd42599f03ce503b8bf700220281989345617548d2512391a4b04450761df9add920d83043f9e21cb5baeb703'; var signatureBuffer = new Buffer(signature, 'hex'); - function nodebitauthVerify() { + function nodebitauthVerify () { bitauth.verifySignature(contractBuffer, pubkeyBuffer, signatureBuffer); } @@ -27,21 +25,20 @@ async.series([ var suite = new benchmark.Suite(); suite.add('bitauth#verifySignature', nodebitauthVerify, { maxTime: maxTime }); suite - .on('cycle', function(event) { + .on('cycle', function (event) { console.log(String(event.target)); }) - .on('complete', function() { + .on('complete', function () { console.log('---------------------------------------'); next(); }) .run(); }, - function(next) { - + function (next) { // invalid checksum var sinbad = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX'; - function nodebitauthValidateSin() { + function nodebitauthValidateSin () { bitauth.validateSin(sinbad); } @@ -49,15 +46,15 @@ async.series([ var suite = new benchmark.Suite(); suite.add('bitauth#validateSin', nodebitauthValidateSin, { maxTime: maxTime }); suite - .on('cycle', function(event) { + .on('cycle', function (event) { console.log(String(event.target)); }) - .on('complete', function() { + .on('complete', function () { console.log('---------------------------------------'); next(); }) .run(); } -], function(err) { - console.log('Finished'); +], function (err) { + console.log('Finished', err); }); diff --git a/browser.js b/browser.js index 4dfce9d..3110cb1 100644 --- a/browser.js +++ b/browser.js @@ -1,3 +1,4 @@ 'use strict'; -module.exports = require('./lib/bitauth-browserify'); +var bitauth = require('./lib/bitauth'); +module.exports = bitauth; diff --git a/examples/client.js b/examples/client.js index cdeb258..97fd54a 100644 --- a/examples/client.js +++ b/examples/client.js @@ -9,7 +9,7 @@ var keys = { // GET -for(k in keys) { +for (var k in keys) { var url = 'http://localhost:3000/user'; var dataToSign = url; var options = { @@ -20,13 +20,8 @@ for(k in keys) { } }; - request.get(options, function(err, response, body) { - if(err) { - console.log(err); - } - if(body) { - console.log(body); - } + request.get(options, function (err, response, body) { + console.log(err !== null ? err : body); }); } @@ -34,11 +29,11 @@ var pizzas = ['pepperoni', 'sausage', 'veggie', 'hawaiian']; // POST -for(k in keys) { - var url = 'http://localhost:3000/pizzas'; +for (k in keys) { + url = 'http://localhost:3000/pizzas'; var data = {type: pizzas[Math.floor(Math.random() * pizzas.length)]}; - var dataToSign = url + JSON.stringify(data); - var options = { + dataToSign = url + JSON.stringify(data); + options = { url: url, headers: { 'x-identity': bitauth.getPublicKeyFromPrivateKey(keys[k]), @@ -47,12 +42,7 @@ for(k in keys) { json: data }; - request.post(options, function(err, response, body) { - if(err) { - console.log(err); - } - if(body) { - console.log(body); - } + request.post(options, function (err, response, body) { + console.log(err !== null ? err : body); }); } diff --git a/examples/server.js b/examples/server.js index 490d5a4..a1068ab 100644 --- a/examples/server.js +++ b/examples/server.js @@ -14,21 +14,26 @@ var app = express(); app.use(rawBody); app.use(bodyParser()); +app.get('/user', bitauthMiddleware, function (req, res) { + if (!req.sin || !users[req.sin]) { + return res.send(401, {error: 'Unauthorized'}); + } -app.get('/user', bitauthMiddleware, function(req, res) { - if(!req.sin || !users[req.sin]) return res.send(401, {error: 'Unauthorized'}); res.send(200, users[req.sin]); }); -app.post('/pizzas', bitauthMiddleware, function(req, res) { - if(!req.sin || !users[req.sin]) return res.send(401, {error: 'Unauthorized'}); +app.post('/pizzas', bitauthMiddleware, function (req, res) { + if (!req.sin || !users[req.sin]) { + return res.send(401, {error: 'Unauthorized'}); + } + var pizza = req.body; pizza.owner = users[req.sin].name; pizzas.push(pizza); res.send(200, req.body); }); -app.get('/pizzas', function(req, res) { +app.get('/pizzas', function (req, res) { res.send(200, pizzas); }); diff --git a/gulpfile.js b/gulpfile.js index d034052..803de8b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,6 +2,7 @@ // Run these commands to make a release: // +// gulp lint // gulp release:checkout-releases // gulp release:install // gulp test @@ -18,7 +19,7 @@ var path = require('path'); var gulp = require('gulp'); var shell = require('gulp-shell'); -var mocha = require('gulp-mocha'); +var Mocha = require('gulp-mocha'); var runsequence = require('run-sequence'); runsequence.use(gulp); var bump = require('gulp-bump'); @@ -27,8 +28,8 @@ var git = require('gulp-git'); var binPath = path.resolve(__dirname, './node_modules/.bin/'); var browserifyPath = path.resolve(binPath, './browserify'); var uglifyPath = path.resolve(binPath, './uglifyjs'); -var indexPath = path.resolve(__dirname, './lib/bitauth-browserify'); -var namePath = path.resolve(__dirname, './bitauth'); +var indexPath = path.resolve(__dirname, './browser'); +var namePath = path.resolve(__dirname, './index'); var bundlePath = namePath + '.js'; var minPath = namePath + '.min.js'; @@ -36,6 +37,10 @@ var browserifyCommand = browserifyPath + ' -p bundle-collapser/plugin --require indexPath + ':bitauth -o ' + bundlePath; var uglifyCommand = uglifyPath + ' ' + bundlePath + ' --compress --mangle -o ' + minPath; +gulp.task('lint', shell.task([ + 'semistandard' +])); + gulp.task('browser:uncompressed', shell.task([ browserifyCommand ])); @@ -48,20 +53,19 @@ gulp.task('browser:maketests', shell.task([ 'find test/ -type f -name "*.js" | xargs ' + browserifyPath + ' -o tests.js' ])); -gulp.task('browser', function(callback) { +gulp.task('browser', function (callback) { runsequence(['browser:compressed'], callback); }); - -gulp.task('release:install', function() { +gulp.task('release:install', function () { return shell.task([ - 'npm install', + 'npm install' ]); }); var releaseFiles = ['./package.json', './bower.json']; -var bumpVersion = function(importance) { +var bumpVersion = function (importance) { return gulp.src(releaseFiles) .pipe(bump({ type: importance @@ -69,24 +73,24 @@ var bumpVersion = function(importance) { .pipe(gulp.dest('./')); }; -['patch', 'minor', 'major'].forEach(function(importance) { - gulp.task('release:bump:' + importance, function() { +['patch', 'minor', 'major'].forEach(function (importance) { + gulp.task('release:bump:' + importance, function () { bumpVersion(importance); }); }); -gulp.task('release:checkout-releases', function(cb) { +gulp.task('release:checkout-releases', function (cb) { var tempBranch = 'releases/' + new Date().getTime() + '-build'; git.branch(tempBranch, { args: '' - }, function() { + }, function () { git.checkout(tempBranch, { args: '' }, cb); }); }); -gulp.task('release:checkout-master', function(cb) { +gulp.task('release:checkout-master', function (cb) { git.checkout('master', { args: '' }, cb); @@ -107,14 +111,14 @@ buildFiles.push('./bower.json'); signatureFiles.push(namePath + '.js.sig'); signatureFiles.push(namePath + '.min.js.sig'); -var addFiles = function() { +var addFiles = function () { return gulp.src(buildFiles) .pipe(git.add({ args: '-f' })); }; -var buildCommit = function() { +var buildCommit = function () { var pjson = require('./package.json'); return gulp.src(buildFiles) .pipe(git.commit('Build: ' + pjson.version, { @@ -128,7 +132,7 @@ gulp.task('release:build-commit', [ 'release:add-signed-files' ], buildCommit); -gulp.task('release:version-commit', function() { +gulp.task('release:version-commit', function () { var pjson = require('./package.json'); return gulp.src(releaseFiles) .pipe(git.commit('Bump package version to ' + pjson.version, { @@ -136,16 +140,16 @@ gulp.task('release:version-commit', function() { })); }); -gulp.task('release:push', function(cb) { +gulp.task('release:push', function (cb) { git.push('upstream', 'master', { args: '' }, cb); }); -gulp.task('release:push-tag', function(cb) { +gulp.task('release:push-tag', function (cb) { var pjson = require('./package.json'); var name = 'v' + pjson.version; - git.tag(name, 'Release ' + name, function() { + git.tag(name, 'Release ' + name, function () { git.push('upstream', name, cb); }); }); @@ -155,8 +159,8 @@ gulp.task('release:publish', shell.task([ ])); var tests = ['test/**/*.js']; -var testmocha = function() { - return gulp.src(tests).pipe(new mocha({ +var testmocha = function () { + return gulp.src(tests).pipe(new Mocha({ recursive: true })); }; @@ -168,7 +172,7 @@ var testkarma = shell.task([ gulp.task('test:node', testmocha); gulp.task('test:browser', ['browser:uncompressed', 'browser:maketests'], testkarma); -gulp.task('test', function(callback) { +gulp.task('test', function (callback) { runsequence(['test:node'], ['test:browser'], callback); }); diff --git a/index.js b/index.js index 8eece6d..8bd7559 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ 'use strict'; -var bitauth = require('./lib/bitauth-node'); +var bitauth = require('./lib/bitauth'); // add node-specific encrypt/decrypt bitauth.encrypt = require('./lib/encrypt'); bitauth.decrypt = require('./lib/decrypt'); diff --git a/karma.conf.js b/karma.conf.js index 9a6ca1a..326affa 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,7 +1,6 @@ 'use strict'; -module.exports = function(config) { - +module.exports = function (config) { config.set({ browsers: ['Firefox'], frameworks: ['mocha'], @@ -14,5 +13,4 @@ module.exports = function(config) { 'karma-firefox-launcher' ] }); - }; diff --git a/lib/bitauth-browserify.js b/lib/bitauth-browserify.js deleted file mode 100644 index e3f8d7a..0000000 --- a/lib/bitauth-browserify.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -var elliptic = require('elliptic'); -var ecdsa = new elliptic.ec(elliptic.curves.secp256k1); - -var BitAuth = require('./bitauth-common'); - -BitAuth._generateRandomPair = function() { - var keys = ecdsa.genKeyPair(); - var privateKey = keys.getPrivate('hex'); - var publicKey = BitAuth.getPublicKeyFromPrivateKey(privateKey); - return [privateKey, publicKey]; -}; - -BitAuth._getPublicKeyFromPrivateKey = function(privkey) { - var privKeyString; - if (Buffer.isBuffer(privkey)) { - privKeyString = privkey.toString('hex'); - } else { - privKeyString = privkey; - } - var keys = ecdsa.keyPair(privkey, 'hex'); - - // compressed public key - var pubKey = keys.getPublic(); - var xbuf = new Buffer(pubKey.x.toString('hex', 64), 'hex'); - var ybuf = new Buffer(pubKey.y.toString('hex', 64), 'hex'); - var pub; - - if (ybuf[ybuf.length - 1] % 2) { //odd - pub = Buffer.concat([new Buffer([3]), xbuf]); - } else { //even - pub = Buffer.concat([new Buffer([2]), xbuf]); - } - return pub; -}; - -BitAuth._sign = function(hashBuffer, privkey) { - var signature = ecdsa.sign(hashBuffer.toString('hex'), privkey); - var hexsignature = signature.toDER('hex'); - return hexsignature; -}; - -BitAuth._verifySignature = function(hashBuffer, signatureBuffer, pubkey) { - return ecdsa.verify(hashBuffer.toString('hex'), signatureBuffer, pubkey); -}; - -module.exports = BitAuth; diff --git a/lib/bitauth-common.js b/lib/bitauth-common.js deleted file mode 100644 index 195ab2e..0000000 --- a/lib/bitauth-common.js +++ /dev/null @@ -1,181 +0,0 @@ -'use strict'; - -var crypto = require('crypto'); -var bs58 = require('bs58'); -var BitAuth = {}; - -BitAuth.PREFIX = new Buffer('0f02', 'hex'); - -/** - * Will return a key pair and identity - * - * @returns {Object} An object with keys: created, priv, pub and sin - */ -BitAuth.generateSin = function() { - var pair = BitAuth._generateRandomPair(); - var sin = BitAuth.getSinFromPublicKey(pair[1]); - var sinObj = { - created: Math.round(Date.now() / 1000), - priv: pair[0], - pub: pair[1], - sin: sin - }; - return sinObj; -}; - -/** - * Will return a public key from a private key - * - * @param {String} A private key in hex - * @returns {String} A compressed public key in hex - */ -BitAuth.getPublicKeyFromPrivateKey = function(privkey) { - var pub = BitAuth._getPublicKeyFromPrivateKey(privkey); - var hexPubKey = pub.toString('hex'); - return hexPubKey; -}; - -/** - * Will return a SIN from a compressed public key - * - * @param {String} A public key in hex - * @returns {String} A SIN identity - */ -BitAuth.getSinFromPublicKey = function(pubkey) { - var pubkeyBuffer; - if (!Buffer.isBuffer(pubkey)) { - pubkeyBuffer = new Buffer(pubkey, 'hex'); - } else { - pubkeyBuffer = pubkey; - } - - // sha256 hash the pubkey - var pubHash = crypto.createHash('sha256').update(pubkeyBuffer).digest(); - - // get the ripemd160 hash of the pubkey - var pubRipe = crypto.createHash('rmd160').update(pubHash).digest(); - - // add the version - var pubPrefixed = Buffer.concat([BitAuth.PREFIX, pubRipe]); - - // two rounds of hashing to generate the checksum - var hash1 = crypto.createHash('sha256').update(pubPrefixed).digest(); - var checksumTotal = crypto.createHash('sha256').update(hash1).digest(); - - // slice the hash to arrive at the checksum - var checksum = checksumTotal.slice(0, 4); - - // add the checksum to the ripemd160 pubkey - var pubWithChecksum = Buffer.concat([pubPrefixed, checksum]); - - // encode into base58 - var sin = bs58.encode(pubWithChecksum); - - return sin; - -}; - -/** - * Will sign a string of data with a private key - * - * @param {String} data - A string of data to be signed - * @param {String} privkey - A private key in hex - * @returns {String} signature - A DER signature in hex - */ -BitAuth.sign = function(data, privkey) { - var dataBuffer; - if (!Buffer.isBuffer(data)) { - dataBuffer = new Buffer(data, 'utf8'); - } else { - dataBuffer = data; - } - var hashBuffer = crypto.createHash('sha256').update(dataBuffer).digest(); - var hexsignature = BitAuth._sign(hashBuffer, privkey); - return hexsignature; -}; - -/** - * Will verify a signature - * - * @param {String} data - A string of data that has been signed - * @param {String} pubkey - The compressed public key in hex that has signed the data - * @param {String} hexsignature - A DER signature in hex - * @returns {Function|Boolean} - If the signature is valid - */ -BitAuth.verifySignature = function(data, pubkey, hexsignature, callback) { - var dataBuffer; - if (!Buffer.isBuffer(data)) { - dataBuffer = new Buffer(data, 'utf8'); - } else { - dataBuffer = data; - } - var hashBuffer = crypto.createHash('sha256').update(dataBuffer).digest(); - var signatureBuffer; - if (!Buffer.isBuffer(hexsignature)) { - signatureBuffer = new Buffer(hexsignature, 'hex'); - } else { - signatureBuffer = hexsignature; - } - var valid = BitAuth._verifySignature(hashBuffer, signatureBuffer, pubkey); - - if (callback) { - return callback(null, valid); - } - return valid; -}; - -/** - * Will verify that a SIN is valid - * - * @param {String} sin - A SIN identity - * @returns {Function|Boolean} - If the SIN identity is valid - */ -BitAuth.validateSin = function(sin, callback) { - - var pubWithChecksum; - - // check for non-base58 characters - try { - pubWithChecksum = new Buffer(bs58.decode(sin), 'hex').toString('hex'); - } catch (err) { - if (callback) { - return callback(err); - } - return false; - } - - // check the version - if (pubWithChecksum.slice(0, 4) !== '0f02') { - if (callback) { - return callback(new Error('Invalid prefix or SIN version')); - } - return false; - } - - // get the checksum - var checksum = pubWithChecksum.slice( - pubWithChecksum.length - 8, - pubWithChecksum.length - ); - var pubPrefixed = pubWithChecksum.slice(0, pubWithChecksum.length - 8); - - // two rounds of hashing to generate the checksum - var hash1 = crypto.createHash('sha256').update(new Buffer(pubPrefixed, 'hex')).digest(); - var checksumTotal = crypto.createHash('sha256').update(hash1).digest('hex'); - - // check the checksum - if (checksumTotal.slice(0, 8) === checksum) { - if (callback) { - return callback(null); - } - return true; - } else { - if (callback) { - return callback(new Error('Checksum does not match')); - } - return false; - } - -}; - -module.exports = BitAuth; diff --git a/lib/bitauth-node.js b/lib/bitauth-node.js deleted file mode 100644 index 442c35e..0000000 --- a/lib/bitauth-node.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var secp256k1 = require('secp256k1'); -var BitAuth = require('./bitauth-common'); -var crypto = require('crypto'); - -BitAuth._generateRandomPair = function() { - var privateKeyBuffer = crypto.randomBytes(32); // may throw error if entropy sources drained - var publicKeyBuffer = secp256k1.createPublicKey(privateKeyBuffer, true); - return [privateKeyBuffer.toString('hex'), publicKeyBuffer.toString('hex')]; -}; - -BitAuth._getPublicKeyFromPrivateKey = function(privkey) { - var privateKeyBuffer; - if (Buffer.isBuffer(privkey)) { - privateKeyBuffer = privkey; - } else { - privateKeyBuffer = new Buffer(privkey, 'hex'); - } - return secp256k1.createPublicKey(privateKeyBuffer, true); -}; - -BitAuth._sign = function(hashBuffer, privkey) { - var privkeyBuffer; - if (Buffer.isBuffer(privkey)) { - privkeyBuffer = privkey; - } else { - privkeyBuffer = new Buffer(privkey, 'hex'); - } - var signatureInfo = secp256k1.sign(hashBuffer, privkeyBuffer, true); - return signatureInfo.toString('hex'); -}; - -BitAuth._verifySignature = function(hashBuffer, signatureBuffer, pubkey) { - var pubkeyBuffer; - if (!Buffer.isBuffer(pubkey)){ - pubkeyBuffer = new Buffer(pubkey, 'hex'); - } else { - pubkeyBuffer = pubkey; - } - return secp256k1.verify(hashBuffer, signatureBuffer, pubkeyBuffer) ? true : false; -}; - -module.exports = BitAuth; diff --git a/lib/bitauth.js b/lib/bitauth.js new file mode 100644 index 0000000..f18fc43 --- /dev/null +++ b/lib/bitauth.js @@ -0,0 +1,179 @@ +'use strict'; + +var crypto = require('crypto'); +var secp256k1 = require('secp256k1'); +var bs58check = require('bs58check'); + +/** + * @param {Buffer} data + * @return {Buffer} + */ +function sha256 (data) { + return crypto.createHash('sha256').update(data).digest(); +} + +/** + * @param {Buffer} data + * @return {Buffer} + */ +function rmd160 (data) { + return crypto.createHash('rmd160').update(data).digest(); +} + +var BitAuth = module.exports = {}; +BitAuth.SIN_PREFIX = 0x0f; +BitAuth.SIN_TYPE = 0x02; +BitAuth.PREFIX = new Buffer('0f02', 'hex'); + +/** + * @typedef {Object} BitAuth~generateSinResult + * @property {Number} created + * @property {String} priv + * @property {String} pub + * @property {String} sin + */ + +/** + * Will return a key pair and identity + * + * @returns {BitAuth~generateSinResult} + */ +BitAuth.generateSin = function () { + var privateKey; + do { + privateKey = crypto.randomBytes(32); // may throw error if entropy sources drained + } while (!secp256k1.privateKeyVerify(privateKey)); + + var publicKey = secp256k1.publicKeyCreate(privateKey, true); + var sin = BitAuth.getSinFromPublicKey(publicKey); + + return { + created: Math.round(Date.now() / 1000), + priv: privateKey.toString('hex'), + pub: publicKey.toString('hex'), + sin: sin + }; +}; + +/** + * Will return a public key from a private key + * + * @param {(Buffer|String)} privateKey A private key in hex or as Buffer + * @returns {String} A compressed public key in hex + */ +BitAuth.getPublicKeyFromPrivateKey = function (privateKey) { + if (!Buffer.isBuffer(privateKey)) { + privateKey = new Buffer(privateKey, 'hex'); + } + + return secp256k1.publicKeyCreate(privateKey, true).toString('hex'); +}; + +/** + * Will return a SIN from a compressed public key + * + * @param {(Buffer|String)} publicKey A public key in hex + * @returns {String} A SIN identity + */ +BitAuth.getSinFromPublicKey = function (publicKey) { + if (!Buffer.isBuffer(publicKey)) { + publicKey = new Buffer(publicKey, 'hex'); + } + + // apply sha256 and ripemd160 to the public key + var pubRipe = rmd160(sha256(publicKey)); + + // add the version + var pubPrefixed = Buffer.concat([new Buffer([BitAuth.SIN_PREFIX, BitAuth.SIN_TYPE]), pubRipe]); + + // encode in base-58 with checksum + return bs58check.encode(pubPrefixed); +}; + +/** + * Will sign a string of data with a private key + * + * @param {(Buffer|String)} data - A string of data to be signed + * @param {(Buffer|String)} privateKey - A private key in hex + * @returns {String} A DER signature in hex + */ +BitAuth.sign = function (data, privateKey) { + if (!Buffer.isBuffer(data)) { + data = new Buffer(data, 'utf8'); + } + + if (!Buffer.isBuffer(privateKey)) { + privateKey = new Buffer(privateKey, 'hex'); + } + + var sigObj = secp256k1.sign(sha256(data), privateKey); + return secp256k1.signatureExport(sigObj.signature).toString('hex'); +}; + +/** + * Will verify a signature + * + * @param {(Buffer|String)} data - A string of data that has been signed + * @param {(Buffer|String)} publicKey - The compressed public key in hex that has signed the data + * @param {(Buffer|String)} signature - A DER signature in hex + * @returns {Function|Boolean} If the signature is valid + */ +BitAuth.verifySignature = function (data, publicKey, signature, callback) { + if (!Buffer.isBuffer(data)) { + data = new Buffer(data, 'utf8'); + } + + if (!Buffer.isBuffer(publicKey)) { + publicKey = new Buffer(publicKey, 'hex'); + } + + if (!Buffer.isBuffer(signature)) { + signature = new Buffer(signature, 'hex'); + } + + var _err = null; + var isValid; + try { + signature = secp256k1.signatureImport(signature); + isValid = secp256k1.verify(sha256(data), signature, publicKey); + } catch (err) { + _err = err; + } + + if (callback) { + return callback(_err, isValid); + } + + return _err === null && isValid; +}; + +/** + * Will verify that a SIN is valid + * + * @param {String} sin - A SIN identity + * @returns {Function|Boolean} - If the SIN identity is valid + */ +BitAuth.validateSin = function (sin, callback) { + try { + var pubPrefixed = bs58check.decode(sin); + } catch (err) { + if (callback) { + return callback(err); + } + + return false; + } + + var err = null; + if (pubPrefixed[0] !== BitAuth.SIN_PREFIX) { + err = new Error('Invalid SIN prefix'); + } else if (pubPrefixed[1] !== BitAuth.SIN_TYPE) { + err = new Error('Invalid SIN type'); + } + + if (callback) { + return callback(err); + } + + return err === null; +}; diff --git a/lib/decrypt.js b/lib/decrypt.js index 9635af9..f5851a5 100644 --- a/lib/decrypt.js +++ b/lib/decrypt.js @@ -1,7 +1,7 @@ var base58 = require('bs58'); var crypto = require('crypto'); -module.exports = function decrypt(password, str) { +module.exports = function decrypt (password, str) { var aes256 = crypto.createDecipher('aes-256-cbc', password); var a = aes256.update(new Buffer(base58.decode(str))); var b = aes256.final(); @@ -11,4 +11,4 @@ module.exports = function decrypt(password, str) { b.copy(buf, a.length); return buf.toString('utf8'); -}; \ No newline at end of file +}; diff --git a/lib/encrypt.js b/lib/encrypt.js index edac55c..f9a3f6a 100644 --- a/lib/encrypt.js +++ b/lib/encrypt.js @@ -1,7 +1,7 @@ var base58 = require('bs58'); var crypto = require('crypto'); -module.exports = function encrypt(password, str) { +module.exports = function encrypt (password, str) { var aes256 = crypto.createCipher('aes-256-cbc', password); var a = aes256.update(str, 'utf8'); var b = aes256.final(); @@ -11,4 +11,4 @@ module.exports = function encrypt(password, str) { b.copy(buf, a.length); return base58.encode(buf); -}; \ No newline at end of file +}; diff --git a/lib/middleware/bitauth.js b/lib/middleware/bitauth.js index b3d6e19..981a5cf 100644 --- a/lib/middleware/bitauth.js +++ b/lib/middleware/bitauth.js @@ -1,24 +1,22 @@ -var bitauth = require('../bitauth-node'); +var bitauth = require('../bitauth'); -module.exports = function(req, res, next) { +module.exports = function (req, res, next) { if (req.headers && req.headers['x-identity'] && req.headers['x-signature']) { // Check signature is valid // First construct data to check signature on var fullUrl = req.protocol + '://' + req.get('host') + req.url; var data = fullUrl + req.rawBody; - bitauth.verifySignature(data, req.headers['x-identity'], req.headers['x-signature'], function(err, result) { + bitauth.verifySignature(data, req.headers['x-identity'], req.headers['x-signature'], function (err, result) { if (err || !result) { - return res.send(400, { - error: 'Invalid signature' - }); + return res.send(400, {error: 'Invalid signature'}); } // Get the SIN from the public key var sin = bitauth.getSinFromPublicKey(req.headers['x-identity']); - if (!sin) return res.send(400, { - error: 'Bad public key from identity' - }); + if (!sin) { + return res.send(400, {error: 'Bad public key from identity'}); + } req.sin = sin; next(); }); diff --git a/lib/middleware/rawbody.js b/lib/middleware/rawbody.js index 960e9a2..4e87d51 100644 --- a/lib/middleware/rawbody.js +++ b/lib/middleware/rawbody.js @@ -1,6 +1,6 @@ -module.exports = function(req, res, next) { +module.exports = function (req, res, next) { req.rawBody = ''; - req.on('data', function(chunk) { + req.on('data', function (chunk) { req.rawBody += chunk; }); next(); diff --git a/package.json b/package.json index 2825f15..548469c 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,9 @@ "test": "gulp test" }, "dependencies": { - "bs58": "^2.0.0", - "elliptic": "=1.0.0", - "secp256k1": "=1.1.5" + "bs58": "=2.0.0", + "bs58check": "=1.0.8", + "secp256k1": "=3.0.0" }, "devDependencies": { "benchmark": "^1.0.0", @@ -61,6 +61,7 @@ "mocha": "~1.20.1", "request": "^2.65.0", "run-sequence": "^1.0.2", + "semistandard": "^7.0.5", "uglify-js": "~2.4.14" }, "browser": { diff --git a/test/test.bitauth.js b/test/test.bitauth.js index 5e6b8d3..73002af 100644 --- a/test/test.bitauth.js +++ b/test/test.bitauth.js @@ -1,10 +1,10 @@ 'use strict'; +/* global describe, it */ var bitauth = require('../'); var chai = require('chai'); -describe('bitauth', function() { - +describe('bitauth', function () { var should = chai.should(); // previously known keys for comparison @@ -18,7 +18,7 @@ describe('bitauth', function() { var privateKeyToZero = 'c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c'; // keys generated - var keys = null; + var keys = bitauth.generateSin(); // invalid checksum var sinbad = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX'; @@ -36,61 +36,51 @@ describe('bitauth', function() { var signature = null; var enc = null; - describe('#generateSin', function() { - - it('should generate a sin object', function(done) { - keys = bitauth.generateSin(); + describe('#generateSin', function () { + it('should generate a sin object', function (done) { should.exist(keys); should.exist(keys.pub); should.exist(keys.priv); should.exist(keys.sin); done(); }); - }); - describe('#getPublicKeyFromPrivateKey', function() { - - it('should properly get the public key', function(done) { + describe('#getPublicKeyFromPrivateKey', function () { + it('should properly get the public key', function (done) { bitauth.getPublicKeyFromPrivateKey(keys.priv).should.equal(keys.pub); done(); }); - it('should properly get compressed public key from a previously known private key', function(done) { + it('should properly get compressed public key from a previously known private key', function (done) { bitauth.getPublicKeyFromPrivateKey(keysKnown.priv).should.equal(keysKnown.pub); done(); }); - }); - describe('#getSinFromPublicKey', function() { - - it('should properly get the sin', function(done) { + describe('#getSinFromPublicKey', function () { + it('should properly get the sin', function (done) { bitauth.getSinFromPublicKey(keys.pub).should.equal(keys.sin); done(); }); - it('should properly get the sin from a previously known compressed public key', function(done) { + it('should properly get the sin from a previously known compressed public key', function (done) { bitauth.getSinFromPublicKey(keysKnown.pub).should.equal(keysKnown.sin); done(); }); - }); - describe('#sign', function() { - - it('should sign the string', function(done) { + describe('#sign', function () { + it('should sign the string', function (done) { signature = bitauth.sign(contract, keys.priv); should.exist(signature); done(); }); - }); - describe('#verifySignature', function() { - - it('should verify the signature', function(done) { - bitauth.verifySignature(contract, keys.pub, signature, function(err, valid) { + describe('#verifySignature', function () { + it('should verify the signature', function (done) { + bitauth.verifySignature(contract, keys.pub, signature, function (err, valid) { should.not.exist(err); should.exist(valid); valid.should.equal(true); @@ -98,149 +88,124 @@ describe('bitauth', function() { }); }); - it('should verify the signature with leading zero public key', function(done) { - + it('should verify the signature with leading zero public key', function (done) { var leadingZeroKeys = { priv: privateKeyToZero, pub: bitauth.getPublicKeyFromPrivateKey(privateKeyToZero) }; signature = bitauth.sign(contract, leadingZeroKeys.priv); - bitauth.verifySignature(contract, leadingZeroKeys.pub, signature, function(err, valid) { + bitauth.verifySignature(contract, leadingZeroKeys.pub, signature, function (err, valid) { should.not.exist(err); should.exist(valid); valid.should.equal(true); }); done(); - }); describe('Reference Signature Tests', function () { - var priv = "8295702b2273896ae085c3caebb02985cab02038251e10b6f67a14340edb51b0"; - var pub = bitauth.getPublicKeyFromPrivateKey(priv); - var refPairs = [ - ["foo", - "3044022045bc5aba353f97316b92996c01eba6e0b0cb63a763d26898a561c748a9545c7502204dc0374c8d4ca489c161b21ff5e25714f1046d759ec9adf9440233069d584567"], - - ["baz", - "304502206ac2ffc240d23fd218a5aa9857065b8bb09ed6c154f1d7da2b56f993bd6e1e3e022100e8dba80dea09122ab87aae82f91e23876aa6628055e24afc895405482ac97aae"], - - ["What a piece of work is a man! how noble in reason! how infinite in faculty! in form and moving how express and admirable! in action how like an angel! in apprehension how like a god!", - "304402204c818a10380ba42b3be0a293d47922469c4ae7ad6277e0e62bf32700c79c32210220102b673477ee13877b4b7f8f9a2e4c2004553948fbe5e7fd95d7e23b4cd9f8e3"], - - ["☕️ ⓝ 🀤 ⎈ ∲", - "304502204d78e57e9bce7fc6d3dd61bcd1baaceff2689f9a8efac5bbb8ce59a47f6652120221008bdce60d43916e35db9c8ee889ba2f85acd2a98fa0193cce0a7f9f9d9867aac1"], - - ["इसकी दो प्रजातियाँ हैं सुपर्ब लायर बर्ड तथा अलबर्ट्स लायर बर्ड", - "304602210087d7aad4dc2789b8f58f97f541f95fc150ffc7fad8e09093932c023b13330e1a022100b434f9403048a983f8dfbd9b92ad8e2dac1ec4b1934dec8c94f4165bf981e01c"], - - ["금조류(琴鳥類, lyrebird)는 오스트레일리아 남부에 사는 참새목의 한 부류로, 주변의 소리를 잘 따라한다. 거문고새라고도 한다.", - "3044022030e9acbd8f0f3328bd059296092824a38216a222d04ac7e1f3de89d4270f3e18022014386f61154177111fe1da0eee9874e612990d3ce663e6f2b4c44828b4c7072f"], - - ["コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。", - "3046022100b286833ddce1537e12f56ae63fbbd6db25ac0dfab659d342a323b764765b60c0022100d83878b0529bf2cab70e98929faf11d1836d8452ef978aad558e35cce4fb14c4"], - - ["ဂျူးလိယက်ဆီဇာ(ဘီစီ၁၀၀-၄၄)", - "304402206ba84011c961db733e28f40f2496e8ff1ba60fcbf942b609fd1a9a6971f22e5b02202987d7d6ad5c330c7fdacefe3351554c00f42b82b7ad513104de8caebae40fc8"], - - ["རོ་མའི་རང་དབང་འབངས་མི་ཞིག་ལ་མིང་གསུམ་ཡོད་དེ།", - "304402200e4b0560c42e4de19ddc2541f5531f7614628e9d01503d730ebe38c182baee8702206b80868e3d67fec2a9d5a594edd6b4f0266044965fe41e7cc3bff65feb922b7c"] - ]; - refPairs.forEach(function (pair) { - var contract = pair[0]; - var signature = pair[1]; - it('should verify reference signature for: "' + contract + '"', function (done) { - bitauth.verifySignature(contract, pub, signature, function (err, valid) { - should.not.exist(err); - should.exist(valid); - valid.should.equal(true); - done(); - }); - }); + var priv = '8295702b2273896ae085c3caebb02985cab02038251e10b6f67a14340edb51b0'; + var pub = bitauth.getPublicKeyFromPrivateKey(priv); + var refPairs = [ + ['foo', + '3044022045bc5aba353f97316b92996c01eba6e0b0cb63a763d26898a561c748a9545c7502204dc0374c8d4ca489c161b21ff5e25714f1046d759ec9adf9440233069d584567'], + ['baz', + '304502206ac2ffc240d23fd218a5aa9857065b8bb09ed6c154f1d7da2b56f993bd6e1e3e022100e8dba80dea09122ab87aae82f91e23876aa6628055e24afc895405482ac97aae'], + ['What a piece of work is a man! how noble in reason! how infinite in faculty! in form and moving how express and admirable! in action how like an angel! in apprehension how like a god!', + '304402204c818a10380ba42b3be0a293d47922469c4ae7ad6277e0e62bf32700c79c32210220102b673477ee13877b4b7f8f9a2e4c2004553948fbe5e7fd95d7e23b4cd9f8e3'], + ['☕️ ⓝ 🀤 ⎈ ∲', + '304502204d78e57e9bce7fc6d3dd61bcd1baaceff2689f9a8efac5bbb8ce59a47f6652120221008bdce60d43916e35db9c8ee889ba2f85acd2a98fa0193cce0a7f9f9d9867aac1'], + ['इसकी दो प्रजातियाँ हैं सुपर्ब लायर बर्ड तथा अलबर्ट्स लायर बर्ड', + '304602210087d7aad4dc2789b8f58f97f541f95fc150ffc7fad8e09093932c023b13330e1a022100b434f9403048a983f8dfbd9b92ad8e2dac1ec4b1934dec8c94f4165bf981e01c'], + ['금조류(琴鳥類, lyrebird)는 오스트레일리아 남부에 사는 참새목의 한 부류로, 주변의 소리를 잘 따라한다. 거문고새라고도 한다.', + '3044022030e9acbd8f0f3328bd059296092824a38216a222d04ac7e1f3de89d4270f3e18022014386f61154177111fe1da0eee9874e612990d3ce663e6f2b4c44828b4c7072f'], + ['コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。', + '3046022100b286833ddce1537e12f56ae63fbbd6db25ac0dfab659d342a323b764765b60c0022100d83878b0529bf2cab70e98929faf11d1836d8452ef978aad558e35cce4fb14c4'], + ['ဂျူးလိယက်ဆီဇာ(ဘီစီ၁၀၀-၄၄)', + '304402206ba84011c961db733e28f40f2496e8ff1ba60fcbf942b609fd1a9a6971f22e5b02202987d7d6ad5c330c7fdacefe3351554c00f42b82b7ad513104de8caebae40fc8'], + ['རོ་མའི་རང་དབང་འབངས་མི་ཞིག་ལ་མིང་གསུམ་ཡོད་དེ།', + '304402200e4b0560c42e4de19ddc2541f5531f7614628e9d01503d730ebe38c182baee8702206b80868e3d67fec2a9d5a594edd6b4f0266044965fe41e7cc3bff65feb922b7c'] + ]; + refPairs.forEach(function (pair) { + var contract = pair[0]; + var signature = pair[1]; + it('should verify reference signature for: "' + contract + '"', function (done) { + bitauth.verifySignature(contract, pub, signature, function (err, valid) { + should.not.exist(err); + should.exist(valid); + valid.should.equal(true); + done(); + }); }); - + }); }); }); - describe('#validateSinTrue', function() { - - it('should validate the sin as true', function(done) { + describe('#validateSinTrue', function () { + it('should validate the sin as true', function (done) { var valid = bitauth.validateSin(singood); should.equal(true, valid); done(); }); - }); - describe('#validateSinFalse', function() { - - it('should validate the sin as false because of bad checksum', function(done) { + describe('#validateSinFalse', function () { + it('should validate the sin as false because of bad checksum', function (done) { var valid = bitauth.validateSin(sinbad); should.equal(false, valid); done(); }); - it('should validate the sin as false because of non-base58', function(done) { + it('should validate the sin as false because of non-base58', function (done) { var valid = bitauth.validateSin('not#base!58'); should.equal(false, valid); done(); }); - }); - describe('#validateSinCallback', function() { - - it('should receive error callback', function(done) { - bitauth.validateSin(sinbad, function(err) { + describe('#validateSinCallback', function () { + it('should receive error callback', function (done) { + bitauth.validateSin(sinbad, function (err) { should.exist(err); - err.message.should.equal('Checksum does not match'); + err.message.should.equal('Invalid checksum'); done(); }); }); - }); // node specific tests - if (typeof(window) === 'undefined') { - - describe('#encrypt', function() { - - it('should encrypt the secret message', function(done) { + if (!process.browser) { + describe('#encrypt', function () { + it('should encrypt the secret message', function (done) { enc = bitauth.encrypt(password, secret); should.exist(enc); done(); }); }); - describe('#decrypt', function() { - - it('should decrypt the secret message', function(done) { + describe('#decrypt', function () { + it('should decrypt the secret message', function (done) { var dec = bitauth.decrypt(password, enc); should.exist(dec); dec.should.equal(secret); done(); }); - it('should decrypt a previously known message', function(done) { + it('should decrypt a previously known message', function (done) { var dec = bitauth.decrypt(password, encryptedSecret); should.exist(dec); dec.should.equal(secret); done(); }); - }); - describe('#middleware', function() { - - it('should expose an express middleware', function(done) { - bitauth.middleware({}, {}, function() { + describe('#middleware', function () { + it('should expose an express middleware', function (done) { + bitauth.middleware({}, {}, function () { done(); }); }); - }); - } - });