From 1ae18d227f914d4bb064fe02fccc583d3c262f93 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 28 Oct 2014 16:19:02 -0400 Subject: [PATCH] replace bitcore with elliptic, hashjs and bs58 to produce a smaller browser build --- lib/bitauth.js | 197 ++++++++++++++++++++++++++++++++----------- package.json | 8 +- scripts/make-dist.sh | 11 +-- test/test.bitauth.js | 44 ++++++++-- 4 files changed, 191 insertions(+), 69 deletions(-) diff --git a/lib/bitauth.js b/lib/bitauth.js index 3a6a886..fb06aa2 100644 --- a/lib/bitauth.js +++ b/lib/bitauth.js @@ -1,76 +1,171 @@ -var bitcore = require('bitcore'); -var Key = bitcore.Key; -var SIN = bitcore.SIN; -var SINKey = bitcore.SINKey -var util = bitcore.util; +var elliptic = require('elliptic'); +var ecdsa = new elliptic.ec(elliptic.curves.secp256k1); +var hashjs = require('hash.js'); +var bs58 = require('bs58'); -var BitAuth = {}; +var BitAuth = {}; +/** + * Will return a key pair and identity + * + * @example + * var keys = BitAuth.generateSin(); + * + * @returns {Object} An object with keys: created, priv, pub and sin + */ BitAuth.generateSin = function() { - var sk = new SINKey(); - sk.generate(); - return sk.storeObj(); -}; -BitAuth.getPublicKeyFromPrivateKey = function(privkey) { - try { - var key = new Key(); + var keys = ecdsa.genKeyPair(); - key.private = new Buffer(privkey, 'hex'); - key.regenerateSync(); + var publicKey = this.getPublicKeyFromPrivateKey(keys.getPrivate('hex')); + var sin = this.getSinFromPublicKey(publicKey); - return key.public.toString('hex'); - } catch (err) { - console.log(err); - return null; + var sinObj = { + created: new Date().getTime(), + priv: keys.getPrivate('hex'), + pub: publicKey, + sin: sin } + + return sinObj; }; + +/** + * Will return an 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 keys = ecdsa.keyPair(privkey, 'hex'); + + // compressed public key + var pubKey = keys.getPublic(); + var xbuf = new Buffer(pubKey.x.toString('hex'), 'hex'); + var ybuf = new Buffer(pubKey.y.toString('hex'), 'hex'); + if (ybuf[ybuf.length-1] % 2) { //odd + var pub = Buffer.concat([new Buffer([3]), xbuf]); + } + else { //even + var pub = Buffer.concat([new Buffer([2]), xbuf]); + } + + var hexPubKey = pub.toString('hex'); + + return hexPubKey; + +}; + +/** + * Will return a SIN from a compressed private key + * + * @param {String} A public key in hex + * @returns {String} A SIN identity + */ BitAuth.getSinFromPublicKey = function(pubkey) { - var pubkeyHash = util.sha256ripe160(new Buffer(pubkey, 'hex')); - var sin = new SIN(SIN.SIN_EPHEM, pubkeyHash); - return sin.toString(); + + // sha256 hash the pubkey + var pubHash = (new hashjs.sha256()).update(new Buffer(pubkey, 'hex')).digest('hex'); + + // get the ripemd160 hash of the pubkey + var pubRipe = (new hashjs.ripemd160()).update(new Buffer(pubHash, 'hex')).digest('hex'); + + // add the version + var pubPrefixed = '0f02'+pubRipe; + + // two rounds of hashing to generate the checksum + var hash1 = (new hashjs.sha256()).update(new Buffer(pubPrefixed, 'hex')).digest('hex'); + var checksumTotal = (new hashjs.sha256()).update(new Buffer(hash1, 'hex')).digest('hex'); + + // slice the hash to arrive at the checksum + var checksum = checksumTotal.slice(0,8); + + // add the checksum to the ripemd160 pubkey + var pubWithChecksum = pubPrefixed + checksum; + + // encode into base58 + var sin = bs58.encode(new Buffer(pubWithChecksum, 'hex')); + + return sin; + } +/** + * Will return a signature from 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 hash = util.sha256(data); + var hash = (new hashjs.sha256()).update(new Buffer(data, 'ascii')).digest('hex'); + var signature = ecdsa.sign(hash, privkey); + var hexsignature = signature.toDER('hex'); + return hexsignature; +}; - try { - var key = new Key(); - key.private = new Buffer(privkey, 'hex'); - return key.signSync(hash).toString('hex'); - } catch (err) { - console.log(err.stack); - console.log(err); - return null; +/** + * Will verify a signature + * + * @param {String} data - A string of data that has been signed + * @param {String} pubkey - The public identity 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 hash = (new hashjs.sha256()).update(new Buffer(data, 'ascii')).digest('hex'); + var signature = new Buffer(hexsignature, 'hex'); + var valid = ecdsa.verify(hash, signature, pubkey); + if ( callback ){ + return callback(null, valid); + } else { + return valid; } }; -BitAuth.verifySignature = function(data, pubkey, signature, callback) { - var hash = util.sha256(data); - - try { - var key = new Key(); - key.public = new Buffer(pubkey, 'hex'); - key.verifySignature(hash, new Buffer(signature, 'hex'), callback); - } catch (err) { - callback(err, false); - } -}; +/** + * 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 s = new SIN(sin); - try { - s.validate() - } catch(err) { - if ( callback ) - callback(err); + var decoded = bs58.decode(sin); + var pubWithChecksum = new Buffer(decoded, 'hex').toString('hex'); + + // check the version + if ( pubWithChecksum.slice(0, 4) != '0f02' ) { + if ( callback ) { + return callback(new Error('Invalid prefix or SIN version')); + } return false; } - if ( callback ) - callback(null); - return true; + + // 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 = (new hashjs.sha256()).update(new Buffer(pubPrefixed, 'hex')).digest('hex'); + var checksumTotal = (new hashjs.sha256()).update(new Buffer(hash1, 'hex')).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/package.json b/package.json index c91e004..0ce9c22 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,3 @@ - { "name": "bitauth", "description": "Passwordless authentication using Bitcoin cryptography", @@ -22,13 +21,14 @@ ], "scripts": { "make-dist": "sh scripts/make-dist.sh", - "test": "mocha test/*.js --reporter spec", - "postinstall": "npm run make-dist" + "test": "mocha test/*.js --reporter spec" }, "main": "index.js", "version": "0.1.1", "dependencies": { - "bitcore": "0.1.32", + "elliptic": "=0.15.11", + "hash.js": "=0.3.2", + "bs58": "=2.0.0", "request": "^2.36.0", "express": "^4.3.1", "base58-native": "^0.1.4", diff --git a/scripts/make-dist.sh b/scripts/make-dist.sh index 6b01c4b..200c97b 100644 --- a/scripts/make-dist.sh +++ b/scripts/make-dist.sh @@ -1,10 +1,5 @@ -cd node_modules/bitcore -echo "Building browser bundle for bitcore..." -node browser/build -s lib/Key,lib/SINKey,lib/SIN,util/util -cd ../../ -cp node_modules/bitcore/browser/bundle.js dist/bitcore.bundle.js echo "Building browser bundle for bitauth..." -node_modules/.bin/browserify lib/bitauth.js -s bitauth -x buffertools -x bitcore -o dist/bitauth.bundle.js -echo "Minifying bitcore and bitauth..." -node_modules/.bin/uglifyjs dist/bitcore.bundle.js dist/bitauth.bundle.js -o dist/bitauth.browser.min.js +node_modules/.bin/browserify lib/bitauth.js -s bitauth -o dist/bitauth.bundle.js +echo "Minifying bitauth..." +node_modules/.bin/uglifyjs dist/bitauth.bundle.js -o dist/bitauth.browser.min.js echo "Done!" diff --git a/test/test.bitauth.js b/test/test.bitauth.js index 67bd572..f008fbc 100644 --- a/test/test.bitauth.js +++ b/test/test.bitauth.js @@ -12,12 +12,28 @@ describe('bitauth', function() { var should = chai.should(); + // previously known keys for comparison + var keysKnown = { + priv: '97811b691dd7ebaeb67977d158e1da2c4d3eaa4ee4e2555150628acade6b344c', + pub: '02326209e52f6f17e987ec27c56a1321acf3d68088b8fb634f232f12ccbc9a4575', + sin: 'Tf3yr5tYvccKNVrE26BrPs6LWZRh8woHwjR' + } + + // keys generated var keys = null; - var sin = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMHX'; - var sinb = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX'; + + // invalid checksum + var sinbad = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX'; + + // valid sin + var singood = 'TfG4ScDgysrSpodWD4Re5UtXmcLbY5CiUHA'; + + // data to sign var contract = 'keyboard cat'; var secret = 'o hai, nsa. how i do teh cryptos?'; var password = 's4705hiru13z!'; + + // signature from generate keys var signature = null; var enc = null; @@ -41,6 +57,11 @@ describe('bitauth', 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() { @@ -50,6 +71,11 @@ describe('bitauth', 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() { @@ -65,7 +91,12 @@ describe('bitauth', function() { describe('#verifySignature', function() { it('should verify the signature', function(done) { - bitauth.verifySignature(contract, keys.pub, signature, done); + bitauth.verifySignature(contract, keys.pub, signature, function(err, valid){ + should.not.exist(err); + should.exist(valid); + valid.should.equal(true); + done(); + }); }); }); @@ -73,7 +104,7 @@ describe('bitauth', function() { describe('#validateSinTrue', function() { it('should validate the sin as true', function(done) { - var valid = bitauth.validateSin(sin); + var valid = bitauth.validateSin(singood); should.equal(true, valid); done(); }); @@ -83,7 +114,7 @@ describe('bitauth', function() { describe('#validateSinFalse', function() { it('should validate the sin as false', function(done) { - var valid = bitauth.validateSin(sinb); + var valid = bitauth.validateSin(sinbad); should.equal(false, valid); done(); }); @@ -93,8 +124,9 @@ describe('bitauth', function() { describe('#validateSinCallback', function() { it('should receive error callback', function(done) { - var valid = bitauth.validateSin(sinb, function(err){ + var valid = bitauth.validateSin(sinbad, function(err){ should.exist(err); + err.message.should.equal('Checksum does not match'); done(); }); });