Compare commits

...

43 Commits

Author SHA1 Message Date
Patrick Nagurny 787a0115d6 Merge pull request #59 from braydonf/utf8
Fix typo with utf-8 -> utf8
2015-11-18 10:17:01 -05:00
Braydon Fuller 9144166ecc Fix typo with utf-8 -> utf8
The default encoding is utf8, and hence specifying utf-8 would achieve the same result.
https://nodejs.org/api/buffer.html#buffer_buffer
https://nodejs.org/docs/latest-v0.12.x/api/buffer.html#buffer_buffer
https://nodejs.org/docs/latest-v0.10.x/api/buffer.html#buffer_buffer
2015-11-13 17:05:18 -05:00
Patrick Nagurny de6e429d37 Bump package version to 0.3.2 2015-11-10 15:29:00 -05:00
Patrick Nagurny 6638fa64f2 Merge pull request #58 from braydonf/secp256k1-1.1.5
Updated secp256k1 to 1.1.5
2015-11-10 15:01:57 -05:00
Braydon Fuller d39b01afa2 Fix examples 2015-11-10 14:59:07 -05:00
Braydon Fuller f6f0e92e43 Update secp256k1 library to 1.1.5 2015-11-09 18:52:38 -05:00
Braydon Fuller 2cb0c9aa8e Move CXX export to before_install 2015-10-09 19:10:24 -04:00
Braydon Fuller 6422d7221e Add C++11 compiler in travis configuration. 2015-10-09 19:01:07 -04:00
Braydon Fuller ff3efed8ca Run tests against Node.js v4 2015-10-09 18:48:54 -04:00
Braydon Fuller 10fa46403b Upgrade browserify. 2015-10-07 20:35:05 -04:00
Patrick Nagurny 1f962f4c76 Bump package version to 0.3.1 2015-10-01 15:00:47 -04:00
Patrick Nagurny 12da8f22eb Merge pull request #49 from braydonf/master
Fix gitignore issue with lib/middleware/bitauth.js being ignored.
2015-10-01 14:42:31 -04:00
Braydon Fuller 3b0a8233a2 Fix gitignore issue with lib/middleware/bitauth.js being ignored. 2015-10-01 13:22:25 -04:00
Patrick Nagurny b6fda984dd Bump package version to 0.3.0 2015-10-01 12:43:02 -04:00
Patrick Nagurny 58be079968 Merge pull request #48 from xcthulhu/optimized-secp256k1
Optimized secp256k1
2015-10-01 12:21:37 -04:00
Matthew Wampler-Doty 203c4585ff Introducing reference tests for verifying signatures 2015-10-01 10:08:35 -04:00
Braydon Fuller c8afc47fcf Collapse absolute paths in browserify build. 2015-09-21 18:06:03 -04:00
Braydon Fuller 66a3dbb61d Added gulp tasks for release process.
- Includes commands to build and release browser builds
- Commands to run node and browser tests with karma
2015-09-18 19:03:10 -04:00
Braydon Fuller 452dc5386c Performance Optimization
- Uses optimized c secp256k1 library Node.js addon
- Browserified version continues to use elliptic.js
2015-09-18 09:49:17 -04:00
Eric Martindale b1b2693a5c Merge pull request #36 from braydonf/bug/dependency-post-install
Removed post install action that requires devDependencies
2015-02-03 12:25:02 -05:00
Braydon Fuller 5ee1c32688 Add server example text 2015-02-03 11:50:12 -05:00
Braydon Fuller dbac5e8261 Include a basic getting started in the readme for making a browser bundle. 2015-02-03 11:45:52 -05:00
Braydon Fuller adc694ee25 Removed post install action that requires devDependencies. Closes #35 2015-02-03 11:19:58 -05:00
Eric Martindale 4e76ffa5d4 Bump version to 0.2.1. 2015-02-02 20:56:36 -05:00
Eric Martindale 8a039f22d0 Merge pull request #34 from gordonwritescode/master
Replace base58-native with bs58 in encrypt/decrypt
2015-02-02 20:55:40 -05:00
Gordon Hall 4d2404ccde Merge pull request #1 from braydonf/master
Added a test to verify that a previosly encrypted message can be decrypted.
2015-02-02 20:34:56 -05:00
Braydon Fuller f0dedd7ef9 Added a test to verify that a previosly encrypted message can be decrypted. 2015-02-02 18:54:47 -05:00
Gordon Hall afcd0e1662 Merge branch 'master' of github.com:bitpay/bitauth 2015-02-02 15:16:36 -05:00
Gordon Hall 820dc3749c remove base58 native, replace with bs58 2015-02-02 15:16:29 -05:00
Eric Martindale 1e89b20eae Merge branch 'v0.2' 2015-01-15 20:21:46 -05:00
Eric Martindale c2afd9f50d Bump version, 0.2.0. 2015-01-15 20:21:02 -05:00
Eric Martindale 86616beab3 Merge pull request #32 from braydonf/bug/elliptic-public-key-generation
Upgrade elliptic.js to fix a public key calculation bug. Fixes #31
2015-01-15 20:09:46 -05:00
Braydon Fuller ae38af135c Upgrade elliptic.js to fix public key calculation bug. 2015-01-06 20:11:59 -05:00
Braydon Fuller 6f82ffe79e bump version to 0.1.2 2014-11-06 18:16:24 -05:00
Eric Martindale 8a52d04f71 Merge pull request #29 from braydonf/feature/minify
Reduce Filesize of Browser Bundle
2014-11-05 16:05:04 -05:00
Braydon Fuller 7f0d542a6a cleanup docs and formatting 2014-11-04 10:53:37 -05:00
Braydon Fuller f39f5a9b61 include compress param in make-dist 2014-11-04 09:45:20 -05:00
Braydon Fuller 7f4a10f72b fixed formatting and docs 2014-11-03 13:44:03 -05:00
Braydon Fuller 81bc5c9779 remove unnecessary buffers, handle non-base58 chars in validateSin, and add postinstall 2014-11-03 13:30:11 -05:00
Braydon Fuller e6bcf12934 fixed an issue with verifying a signature with a public key with leading zeros 2014-11-03 12:05:33 -05:00
Braydon Fuller a4309b8f3b updated to use the new version of elliptic that includes hex padding 2014-11-02 16:14:23 -05:00
Braydon Fuller 74cecb7fdf add padding of leading zeros 2014-10-30 13:09:55 -04:00
Braydon Fuller 1ae18d227f replace bitcore with elliptic, hashjs and bs58 to produce a smaller browser build 2014-10-28 21:12:31 -04:00
24 changed files with 864 additions and 181 deletions

4
.gitignore vendored
View File

@ -1,2 +1,6 @@
node_modules
./bitauth.js
./bitauth.min.js
./tests.js

45
.jshintrc Normal file
View File

@ -0,0 +1,45 @@
{
"bitwise": false,
"browser": true,
"camelcase": false,
"curly": true,
"devel": false,
"eqeqeq": true,
"esnext": true,
"freeze": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": false,
"noarg": true,
"node": true,
"noempty": true,
"nonew": true,
"quotmark": "single",
"regexp": true,
"smarttabs": false,
"strict": true,
"trailing": true,
"undef": true,
"unused": true,
"maxparams": 4,
"maxstatements": 15,
"maxcomplexity": 10,
"maxdepth": 4,
"maxlen": 120,
"multistr": true,
"predef": [
"after",
"afterEach",
"before",
"beforeEach",
"define",
"describe",
"exports",
"it",
"module",
"require"
]
}

View File

@ -1,4 +1,23 @@
language: node_js
sudo: false
compiler:
- gcc
- clang
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-4.8
- g++-4.8
- clang
node_js:
- '0.10'
- '0.10'
- '0.12'
- '4'
before_install:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- export CXX="g++-4.8" CC="gcc-4.8"
install:
- npm install

View File

@ -3,13 +3,27 @@ BitAuth
Passwordless authentication using Bitcoin cryptography
# Overview
## Overview
BitAuth is a way to do secure, passwordless authentication using the cryptography
in Bitcoin. Instead of using a shared secret, the client signs each request using
a private key and the server checks to make sure the signature is valid and matches
the public key.
## Getting started
Install with Node.js:
```bash
npm install bitauth
```
To generate a browser bundle, you can then run:
```bash
gulp browser
```
## Advantages over other authentication mechanisms
* By signing each request, man in the middle attacks are impossible.
@ -23,18 +37,18 @@ in HMAC.
## Technical Overview
BitAuth uses the same technology in Bitcoin. A public private key pair is created
using elliptic curve secp256k1. The public SIN (System identification number),
like a bitcoin address, is the RIPEMD 160, SHA256 hash of the public key.
using elliptic curve secp256k1. The public SIN (System identification number),
like a bitcoin address, is the RIPEMD 160, SHA256 hash of the public key.
See https://en.bitcoin.it/wiki/Identity_protocol_v1 for complete details.
In each request, the client includes a nonce to prevent replay attacks. The client
signs the full url with the request body concatenated if there is one. The signature
is included in the `x-signature` header and the public key is included in the
signs the full url with the request body concatenated if there is one. The signature
is included in the `x-signature` header and the public key is included in the
`x-identity` header.
The server verifies that the signature is valid and that it matches the identity (the public key).
It then computes the SIN from the public key, and sees whether that SIN has access
to the requested resource. The nonce is checked to make sure it is higher than
to the requested resource. The nonce is checked to make sure it is higher than
the previously used nonce.
## Technology is readily available
@ -47,7 +61,7 @@ to start using BitAuth.
## Problems with password authentication
* Have to keep track of a separate password for every web service. People forget
passwords, encouraging them to reuse passwords and opening themselves up to
passwords, encouraging them to reuse passwords and opening themselves up to
having multiple services compromised.
* Brute force attacks on weak passwords.
* Passwords may travel over plaintext
@ -61,23 +75,23 @@ not worry about one service gaining access to another.
In the future, an identity system could be built around BitAuth keys where a user
could create one key to represent an identity which could authenticate against
multiple services.
multiple services.
In order for this to work, there would have to be a browser
integration or plugin which would manage these identities and a Javascript API
where websites could sign requests going to their website with the private key,
In order for this to work, there would have to be a browser
integration or plugin which would manage these identities and a Javascript API
where websites could sign requests going to their website with the private key,
but without exposing the private key to the third party sites.
There also needs to be a public place to store SIN's, preferably in
a decentralized blockchain or datastore like namecoin. Key revocations could
be stored here as well as reviews/feedback to build a reputation around an
a decentralized blockchain or datastore like namecoin. Key revocations could
be stored here as well as reviews/feedback to build a reputation around an
identity.
# Getting Started
## Examples
Example server
```
```javascript
var express = require('express');
var bodyParser = require('body-parser');
var rawBody = require('../lib/middleware/rawbody');
@ -117,7 +131,7 @@ app.listen(3000);
Example client
```
```javascript
var request = require('request');
var bitauth = require('../lib/bitauth');
@ -173,7 +187,7 @@ for(k in keys) {
}
if(body) {
console.log(body);
}
}
});
}
@ -181,7 +195,7 @@ for(k in keys) {
## Middleware
BitAuth exposes a connect middleware for use in connect or ExpressJS applications. Use:
```
```javascript
var bitauth = require('bitauth');
app.use( bitauth.middleware );
```
@ -190,22 +204,20 @@ app.use( bitauth.middleware );
To build a browser compatible version of BitAuth, run the following command from the project's root directory:
```
npm run make-dist
```bash
gulp browser
```
This will output `bitauth.browser.min.js` to the `dist` directory. The script introduces a global variable at `window.bitauth`.
This will output `bitauth.min.js` to project directory. The script can be loaded using `require('bitauth')`.
To then run tests for a web browser open `test/index.html` in a browser, such as:
To then run tests for a web browser:
```bash
firefox test/index.html
chromium-browser test/index.html
gulp test:browser
```
To run tests for Node.js:
```bash
npm run test
gulp test:node
```

63
benchmarks/index.js Normal file
View File

@ -0,0 +1,63 @@
'use strict';
var assert = require('assert');
var benchmark = require('benchmark');
var bitauth = require('../lib/bitauth-node');
var async = require('async');
var maxTime = 10;
async.series([
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.';
var contractBuffer = new Buffer(contract);
var signature = '3045022100db71942a5a6dd1443cbf7519b2bc16a041aff8d4830bd42599f03ce503b8bf700220281989345617548d2512391a4b04450761df9add920d83043f9e21cb5baeb703';
var signatureBuffer = new Buffer(signature, 'hex');
function nodebitauthVerify() {
bitauth.verifySignature(contractBuffer, pubkeyBuffer, signatureBuffer);
}
// #verifySignature
var suite = new benchmark.Suite();
suite.add('bitauth#verifySignature', nodebitauthVerify, { maxTime: maxTime });
suite
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('---------------------------------------');
next();
})
.run();
},
function(next) {
// invalid checksum
var sinbad = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX';
function nodebitauthValidateSin() {
bitauth.validateSin(sinbad);
}
// #validateSin
var suite = new benchmark.Suite();
suite.add('bitauth#validateSin', nodebitauthValidateSin, { maxTime: maxTime });
suite
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('---------------------------------------');
next();
})
.run();
}
], function(err) {
console.log('Finished');
});

29
bower.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "bitauth",
"main": "./bitauth.min.js",
"version": "0.3.2",
"homepage": "https://github.com/bitpay/bitauth",
"authors": [
"BitPay, Inc."
],
"description": "Passwordless authentication using Bitcoin cryptography",
"moduleType": [
"globals"
],
"keywords": [
"bitcoin",
"bitcore",
"btc",
"satoshi"
],
"license": "MIT",
"ignore": [
"**/.*",
"CONTRIBUTING.md",
"gulpfile.js",
"lib",
"index.js",
"karma.conf.js",
"test"
]
}

3
dist/.gitignore vendored
View File

@ -1,3 +0,0 @@
# Ignore everything in this directory
*
!.gitignore

View File

@ -1,5 +1,5 @@
var request = require('request');
var bitauth = require('../lib/bitauth');
var bitauth = require('..'); // or require('bitauth');
// These can be generated with bitauth.generateSin()
var keys = {

View File

@ -1,7 +1,7 @@
var express = require('express');
var bodyParser = require('body-parser');
var rawBody = require('../lib/middleware/rawbody');
var bitauth = require('../lib/middleware/bitauth');
var bitauthMiddleware = require('../lib/middleware/bitauth');
var users = {
'Tf7UNQnxB8SccfoyZScQmb34V2GdEtQkzDz': {name: 'Alice'},
@ -15,12 +15,12 @@ app.use(rawBody);
app.use(bodyParser());
app.get('/user', bitauth, function(req, res) {
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', bitauth, function(req, res) {
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;

177
gulpfile.js Normal file
View File

@ -0,0 +1,177 @@
'use strict';
// Run these commands to make a release:
//
// gulp release:checkout-releases
// gulp release:install
// gulp test
// gulp release:bump:<major|minor|patch>
// gulp browser
// gulp release:build-commit
// gulp release:push-tag
// npm publish
// gulp release:checkout-master
// gulp release:bump:<major|minor|patch>
// gulp release:version-commit
// gulp release:push
var path = require('path');
var gulp = require('gulp');
var shell = require('gulp-shell');
var mocha = require('gulp-mocha');
var runsequence = require('run-sequence');
runsequence.use(gulp);
var bump = require('gulp-bump');
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 bundlePath = namePath + '.js';
var minPath = namePath + '.min.js';
var browserifyCommand = browserifyPath + ' -p bundle-collapser/plugin --require ' +
indexPath + ':bitauth -o ' + bundlePath;
var uglifyCommand = uglifyPath + ' ' + bundlePath + ' --compress --mangle -o ' + minPath;
gulp.task('browser:uncompressed', shell.task([
browserifyCommand
]));
gulp.task('browser:compressed', ['browser:uncompressed'], shell.task([
uglifyCommand
]));
gulp.task('browser:maketests', shell.task([
'find test/ -type f -name "*.js" | xargs ' + browserifyPath + ' -o tests.js'
]));
gulp.task('browser', function(callback) {
runsequence(['browser:compressed'], callback);
});
gulp.task('release:install', function() {
return shell.task([
'npm install',
]);
});
var releaseFiles = ['./package.json', './bower.json'];
var bumpVersion = function(importance) {
return gulp.src(releaseFiles)
.pipe(bump({
type: importance
}))
.pipe(gulp.dest('./'));
};
['patch', 'minor', 'major'].forEach(function(importance) {
gulp.task('release:bump:' + importance, function() {
bumpVersion(importance);
});
});
gulp.task('release:checkout-releases', function(cb) {
var tempBranch = 'releases/' + new Date().getTime() + '-build';
git.branch(tempBranch, {
args: ''
}, function() {
git.checkout(tempBranch, {
args: ''
}, cb);
});
});
gulp.task('release:checkout-master', function(cb) {
git.checkout('master', {
args: ''
}, cb);
});
gulp.task('release:sign-built-files', shell.task([
'gpg --yes --out ' + namePath + '.js.sig --detach-sig ' + namePath + '.js',
'gpg --yes --out ' + namePath + '.min.js.sig --detach-sig ' + namePath + '.min.js'
]));
var buildFiles = ['./package.json'];
var signatureFiles = [];
buildFiles.push(namePath + '.js');
buildFiles.push(namePath + '.js.sig');
buildFiles.push(namePath + '.min.js');
buildFiles.push(namePath + '.min.js.sig');
buildFiles.push('./bower.json');
signatureFiles.push(namePath + '.js.sig');
signatureFiles.push(namePath + '.min.js.sig');
var addFiles = function() {
return gulp.src(buildFiles)
.pipe(git.add({
args: '-f'
}));
};
var buildCommit = function() {
var pjson = require('./package.json');
return gulp.src(buildFiles)
.pipe(git.commit('Build: ' + pjson.version, {
args: ''
}));
};
gulp.task('release:add-signed-files', ['release:sign-built-files'], addFiles);
gulp.task('release:add-built-files', addFiles);
gulp.task('release:build-commit', [
'release:add-signed-files'
], buildCommit);
gulp.task('release:version-commit', function() {
var pjson = require('./package.json');
return gulp.src(releaseFiles)
.pipe(git.commit('Bump package version to ' + pjson.version, {
args: ''
}));
});
gulp.task('release:push', function(cb) {
git.push('upstream', 'master', {
args: ''
}, 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.push('upstream', name, cb);
});
});
gulp.task('release:publish', shell.task([
'npm publish'
]));
var tests = ['test/**/*.js'];
var testmocha = function() {
return gulp.src(tests).pipe(new mocha({
recursive: true
}));
};
var testkarma = shell.task([
path.resolve(__dirname, './node_modules/karma/bin/karma') +
' start ' + path.resolve(__dirname, './karma.conf.js')
]);
gulp.task('test:node', testmocha);
gulp.task('test:browser', ['browser:uncompressed', 'browser:maketests'], testkarma);
gulp.task('test', function(callback) {
runsequence(['test:node'], ['test:browser'], callback);
});
gulp.task('benchmark', shell.task([
'node benchmarks/index.js'
]));

View File

@ -1,9 +1,15 @@
// get base functionality
var bitauth = require('./lib/bitauth');
'use strict';
// add node-specific encrypt/decrypt
bitauth.encrypt = require('./lib/encrypt');
bitauth.decrypt = require('./lib/decrypt');
bitauth.middleware = require('./lib/middleware/bitauth');
var bitauth;
if (process.browser) {
bitauth = require('./lib/bitauth-browserify');
} else {
bitauth = require('./lib/bitauth-node');
// add node-specific encrypt/decrypt
bitauth.encrypt = require('./lib/encrypt');
bitauth.decrypt = require('./lib/decrypt');
bitauth.middleware = require('./lib/middleware/bitauth');
}
module.exports = bitauth;

18
karma.conf.js Normal file
View File

@ -0,0 +1,18 @@
'use strict';
module.exports = function(config) {
config.set({
browsers: ['Firefox'],
frameworks: ['mocha'],
singleRun: true,
files: [
'./tests.js'
],
plugins: [
'karma-mocha',
'karma-firefox-launcher'
]
});
};

48
lib/bitauth-browserify.js Normal file
View File

@ -0,0 +1,48 @@
'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;

181
lib/bitauth-common.js Normal file
View File

@ -0,0 +1,181 @@
'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;

44
lib/bitauth-node.js Normal file
View File

@ -0,0 +1,44 @@
'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;

View File

@ -1,76 +0,0 @@
var bitcore = require('bitcore');
var Key = bitcore.Key;
var SIN = bitcore.SIN;
var SINKey = bitcore.SINKey
var util = bitcore.util;
var BitAuth = {};
BitAuth.generateSin = function() {
var sk = new SINKey();
sk.generate();
return sk.storeObj();
};
BitAuth.getPublicKeyFromPrivateKey = function(privkey) {
try {
var key = new Key();
key.private = new Buffer(privkey, 'hex');
key.regenerateSync();
return key.public.toString('hex');
} catch (err) {
console.log(err);
return null;
}
};
BitAuth.getSinFromPublicKey = function(pubkey) {
var pubkeyHash = util.sha256ripe160(new Buffer(pubkey, 'hex'));
var sin = new SIN(SIN.SIN_EPHEM, pubkeyHash);
return sin.toString();
}
BitAuth.sign = function(data, privkey) {
var hash = util.sha256(data);
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;
}
};
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);
}
};
BitAuth.validateSin = function(sin, callback) {
var s = new SIN(sin);
try {
s.validate()
} catch(err) {
if ( callback )
callback(err);
return false;
}
if ( callback )
callback(null);
return true;
};
module.exports = BitAuth;

View File

@ -1,14 +1,14 @@
var base58 = require('base58-native');
var base58 = require('bs58');
var crypto = require('crypto');
module.exports = function decrypt(password, str) {
var aes256 = crypto.createDecipher('aes-256-cbc', password);
var a = aes256.update(base58.decode(str));
var b = aes256.final();
var buf = new Buffer(a.length + b.length);
var a = aes256.update(new Buffer(base58.decode(str)));
var b = aes256.final();
var buf = new Buffer(a.length + b.length);
a.copy(buf, 0);
b.copy(buf, a.length);
return buf.toString('utf8');
};
};

View File

@ -1,14 +1,14 @@
var base58 = require('base58-native');
var base58 = require('bs58');
var crypto = require('crypto');
module.exports = function encrypt(password, str) {
var aes256 = crypto.createCipher('aes-256-cbc', password);
var a = aes256.update(str, 'utf8');
var b = aes256.final();
var buf = new Buffer(a.length + b.length);
var a = aes256.update(str, 'utf8');
var b = aes256.final();
var buf = new Buffer(a.length + b.length);
a.copy(buf, 0);
b.copy(buf, a.length);
return base58.encode(buf);
};
};

View File

@ -1,20 +1,24 @@
var bitauth = require('../bitauth');
var bitauth = require('../bitauth-node');
module.exports = function(req, res, next) {
if(req.headers && req.headers['x-identity'] && req.headers['x-signature']) {
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) {
if(err || !result) {
return res.send(400, {error: 'Invalid signature'});
if (err || !result) {
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();
});

View File

@ -4,4 +4,4 @@ module.exports = function(req, res, next) {
req.rawBody += chunk;
});
next();
};
};

View File

@ -1,4 +1,3 @@
{
"name": "bitauth",
"description": "Passwordless authentication using Bitcoin cryptography",
@ -18,26 +17,42 @@
{
"name": "Gordon Hall",
"email": "gordon@bitpay.com"
},
{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
}
],
"scripts": {
"make-dist": "sh scripts/make-dist.sh",
"test": "mocha test/*.js --reporter spec",
"postinstall": "npm run make-dist"
"make-dist": "gulp browser",
"test": "gulp test"
},
"main": "index.js",
"version": "0.1.1",
"version": "0.3.2",
"dependencies": {
"bitcore": "0.1.32",
"request": "^2.36.0",
"express": "^4.3.1",
"base58-native": "^0.1.4",
"body-parser": "^1.2.0"
"bs58": "^2.0.0",
"elliptic": "=1.0.0",
"secp256k1": "=1.1.5"
},
"devDependencies": {
"uglify-js": "~2.4.14",
"browserify": "=6.1.0",
"benchmark": "^1.0.0",
"body-parser": "^1.14.1",
"browserify": "~11.2.0",
"bundle-collapser": "^1.2.1",
"chai": "=1.9.1",
"mocha": "~1.20.1"
}
"express": "^4.13.3",
"gulp": "^3.8.10",
"gulp-bump": "^0.1.11",
"gulp-git": "^0.5.5",
"gulp-mocha": "^2.0.0",
"gulp-shell": "^0.2.10",
"karma": "^0.13.9",
"karma-firefox-launcher": "^0.1.4",
"karma-mocha": "^0.1.9",
"mocha": "~1.20.1",
"request": "^2.65.0",
"run-sequence": "^1.0.2",
"uglify-js": "~2.4.14"
},
"license": "MIT"
}

View File

@ -1,10 +0,0 @@
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
echo "Done!"

View File

@ -9,11 +9,10 @@
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/chai/chai.js"></script>
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
<script type="text/javascript" src="../dist/bitauth.browser.min.js"></script>
<script>
mocha.setup('bdd');
</script>
<script type="text/javascript" src="test.bitauth.js"></script>
<script type="text/javascript" src="../tests.js"></script>
<script>
mocha.run();
</script>

View File

@ -1,25 +1,40 @@
'use strict';
if ( typeof(window) === 'undefined' ) {
var bitauth = require('../index');
} else {
var bitauth = window.bitauth;
}
var chai = chai || require('chai');
var bitauth = require('../');
var chai = require('chai');
describe('bitauth', function() {
var should = chai.should();
var keys = null;
var sin = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMHX';
var sinb = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX';
var contract = 'keyboard cat';
var secret = 'o hai, nsa. how i do teh cryptos?';
var password = 's4705hiru13z!';
// previously known keys for comparison
var keysKnown = {
priv: '97811b691dd7ebaeb67977d158e1da2c4d3eaa4ee4e2555150628acade6b344c',
pub: '02326209e52f6f17e987ec27c56a1321acf3d68088b8fb634f232f12ccbc9a4575',
sin: 'Tf3yr5tYvccKNVrE26BrPs6LWZRh8woHwjR'
};
// a private key that will produce a public key with a leading zero
var privateKeyToZero = 'c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c';
// keys generated
var keys = null;
// invalid checksum
var sinbad = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX';
// valid sin
var singood = 'TfG4ScDgysrSpodWD4Re5UtXmcLbY5CiUHA';
// data to sign
var contract = 'kéyboard cät';
var secret = 'o hai, nsa. how i do teh cryptos?';
var password = 's4705hiru13z!';
var encryptedSecret = '291Dm9unZMwfxBA7BEHiQsraRxCrMRqwJ2TjCWwEH3Sp5QGMehNFNgZLo62sgF5Khe';
// signature from generate keys
var signature = null;
var enc = null;
var enc = null;
describe('#generateSin', function() {
@ -41,6 +56,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 +70,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,15 +90,83 @@ 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();
});
});
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) {
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();
});
});
});
});
});
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();
});
@ -82,8 +175,14 @@ describe('bitauth', function() {
describe('#validateSinFalse', function() {
it('should validate the sin as false', function(done) {
var valid = bitauth.validateSin(sinb);
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) {
var valid = bitauth.validateSin('not#base!58');
should.equal(false, valid);
done();
});
@ -93,8 +192,9 @@ describe('bitauth', function() {
describe('#validateSinCallback', function() {
it('should receive error callback', function(done) {
var valid = bitauth.validateSin(sinb, function(err){
bitauth.validateSin(sinbad, function(err) {
should.exist(err);
err.message.should.equal('Checksum does not match');
done();
});
});
@ -102,7 +202,7 @@ describe('bitauth', function() {
});
// node specific tests
if ( typeof(window) === 'undefined' ) {
if (typeof(window) === 'undefined') {
describe('#encrypt', function() {
@ -118,6 +218,14 @@ describe('bitauth', 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) {
var dec = bitauth.decrypt(password, encryptedSecret);
should.exist(dec);
dec.should.equal(secret);
done();
});
@ -126,7 +234,7 @@ describe('bitauth', function() {
describe('#middleware', function() {
it('should expose an express middleware', function(done) {
bitauth.middleware( {} , {} , function() {
bitauth.middleware({}, {}, function() {
done();
});
});