Merge pull request #48 from xcthulhu/optimized-secp256k1
Optimized secp256k1
This commit is contained in:
commit
58be079968
|
@ -1,2 +1,6 @@
|
|||
node_modules
|
||||
bitauth.js
|
||||
bitauth.min.js
|
||||
tests.js
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
10
.travis.yml
10
.travis.yml
|
@ -1,4 +1,12 @@
|
|||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '0.10'
|
||||
- '0.10'
|
||||
- '0.12'
|
||||
before_install:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
install:
|
||||
- npm install
|
||||
|
||||
|
||||
|
|
41
README.md
41
README.md
|
@ -21,10 +21,9 @@ npm install bitauth
|
|||
To generate a browser bundle, you can then run:
|
||||
|
||||
```bash
|
||||
npm run make-dist
|
||||
gulp browser
|
||||
```
|
||||
|
||||
|
||||
## Advantages over other authentication mechanisms
|
||||
|
||||
* By signing each request, man in the middle attacks are impossible.
|
||||
|
@ -38,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
|
||||
|
@ -62,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
|
||||
|
@ -76,16 +75,16 @@ 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.
|
||||
|
||||
## Examples
|
||||
|
@ -188,7 +187,7 @@ for(k in keys) {
|
|||
}
|
||||
if(body) {
|
||||
console.log(body);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -206,21 +205,19 @@ app.use( bitauth.middleware );
|
|||
To build a browser compatible version of BitAuth, run the following command from the project's root directory:
|
||||
|
||||
```bash
|
||||
npm run make-dist
|
||||
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
|
||||
```
|
||||
|
|
|
@ -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');
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "bitauth",
|
||||
"main": "./bitauth.min.js",
|
||||
"version": "0.2.1",
|
||||
"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"
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
!.gitignore
|
|
@ -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'
|
||||
]));
|
18
index.js
18
index.js
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
]
|
||||
});
|
||||
|
||||
};
|
|
@ -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;
|
|
@ -1,8 +1,10 @@
|
|||
var elliptic = require('elliptic');
|
||||
var ecdsa = new elliptic.ec(elliptic.curves.secp256k1);
|
||||
var hashjs = require('hash.js');
|
||||
var bs58 = require('bs58');
|
||||
var BitAuth = {};
|
||||
'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
|
||||
|
@ -10,20 +12,14 @@ var BitAuth = {};
|
|||
* @returns {Object} An object with keys: created, priv, pub and sin
|
||||
*/
|
||||
BitAuth.generateSin = function() {
|
||||
|
||||
var keys = ecdsa.genKeyPair();
|
||||
|
||||
var privateKey = keys.getPrivate('hex');
|
||||
var publicKey = this.getPublicKeyFromPrivateKey(privateKey);
|
||||
var sin = this.getSinFromPublicKey(publicKey);
|
||||
|
||||
var pair = BitAuth._generateRandomPair();
|
||||
var sin = BitAuth.getSinFromPublicKey(pair[1]);
|
||||
var sinObj = {
|
||||
created: Math.round(Date.now() / 1000),
|
||||
priv: privateKey,
|
||||
pub: publicKey,
|
||||
priv: pair[0],
|
||||
pub: pair[1],
|
||||
sin: sin
|
||||
};
|
||||
|
||||
return sinObj;
|
||||
};
|
||||
|
||||
|
@ -34,25 +30,9 @@ BitAuth.generateSin = function() {
|
|||
* @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', 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]);
|
||||
}
|
||||
|
||||
var pub = BitAuth._getPublicKeyFromPrivateKey(privkey);
|
||||
var hexPubKey = pub.toString('hex');
|
||||
|
||||
return hexPubKey;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -62,28 +42,34 @@ BitAuth.getPublicKeyFromPrivateKey = function(privkey) {
|
|||
* @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 = (new hashjs.sha256()).update(pubkey, 'hex').digest('hex');
|
||||
var pubHash = crypto.createHash('sha256').update(pubkeyBuffer).digest();
|
||||
|
||||
// get the ripemd160 hash of the pubkey
|
||||
var pubRipe = (new hashjs.ripemd160()).update(pubHash, 'hex').digest('hex');
|
||||
var pubRipe = crypto.createHash('rmd160').update(pubHash).digest();
|
||||
|
||||
// add the version
|
||||
var pubPrefixed = '0f02'+pubRipe;
|
||||
var pubPrefixed = Buffer.concat([BitAuth.PREFIX, pubRipe]);
|
||||
|
||||
// two rounds of hashing to generate the checksum
|
||||
var hash1 = (new hashjs.sha256()).update(pubPrefixed, 'hex').digest('hex');
|
||||
var checksumTotal = (new hashjs.sha256()).update(hash1, 'hex').digest('hex');
|
||||
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,8);
|
||||
var checksum = checksumTotal.slice(0, 4);
|
||||
|
||||
// add the checksum to the ripemd160 pubkey
|
||||
var pubWithChecksum = pubPrefixed + checksum;
|
||||
var pubWithChecksum = Buffer.concat([pubPrefixed, checksum]);
|
||||
|
||||
// encode into base58
|
||||
var sin = bs58.encode(new Buffer(pubWithChecksum, 'hex'));
|
||||
var sin = bs58.encode(pubWithChecksum);
|
||||
|
||||
return sin;
|
||||
|
||||
|
@ -97,9 +83,14 @@ BitAuth.getSinFromPublicKey = function(pubkey) {
|
|||
* @returns {String} signature - A DER signature in hex
|
||||
*/
|
||||
BitAuth.sign = function(data, privkey) {
|
||||
var hash = (new hashjs.sha256()).update(data).digest('hex');
|
||||
var signature = ecdsa.sign(hash, privkey);
|
||||
var hexsignature = signature.toDER('hex');
|
||||
var dataBuffer;
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
dataBuffer = new Buffer(data, 'utf-8');
|
||||
} else {
|
||||
dataBuffer = data;
|
||||
}
|
||||
var hashBuffer = crypto.createHash('sha256').update(dataBuffer).digest();
|
||||
var hexsignature = BitAuth._sign(hashBuffer, privkey);
|
||||
return hexsignature;
|
||||
};
|
||||
|
||||
|
@ -112,15 +103,27 @@ BitAuth.sign = function(data, privkey) {
|
|||
* @returns {Function|Boolean} - If the signature is valid
|
||||
*/
|
||||
BitAuth.verifySignature = function(data, pubkey, hexsignature, callback) {
|
||||
var hash = (new hashjs.sha256()).update(data).digest('hex');
|
||||
var signature = new Buffer(hexsignature, 'hex');
|
||||
var valid = ecdsa.verify(hash, signature, pubkey);
|
||||
if (callback)
|
||||
var dataBuffer;
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
dataBuffer = new Buffer(data, 'utf-8');
|
||||
} 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
|
||||
*
|
||||
|
@ -134,36 +137,42 @@ BitAuth.validateSin = function(sin, callback) {
|
|||
// check for non-base58 characters
|
||||
try {
|
||||
pubWithChecksum = new Buffer(bs58.decode(sin), 'hex').toString('hex');
|
||||
} catch(err) {
|
||||
if (callback)
|
||||
} catch (err) {
|
||||
if (callback) {
|
||||
return callback(err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// check the version
|
||||
if (pubWithChecksum.slice(0, 4) !== '0f02') {
|
||||
if (callback)
|
||||
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);
|
||||
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(pubPrefixed, 'hex').digest('hex');
|
||||
var checksumTotal = (new hashjs.sha256()).update(hash1, 'hex').digest('hex');
|
||||
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)
|
||||
if (checksumTotal.slice(0, 8) === checksum) {
|
||||
if (callback) {
|
||||
return callback(null);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (callback)
|
||||
if (callback) {
|
||||
return callback(new Error('Checksum does not match'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -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;
|
|
@ -3,12 +3,12 @@ var crypto = require('crypto');
|
|||
|
||||
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();
|
||||
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');
|
||||
};
|
||||
};
|
|
@ -3,12 +3,12 @@ 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);
|
||||
};
|
||||
};
|
|
@ -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();
|
||||
});
|
||||
|
|
27
package.json
27
package.json
|
@ -24,23 +24,32 @@
|
|||
}
|
||||
],
|
||||
"scripts": {
|
||||
"make-dist": "sh scripts/make-dist.sh",
|
||||
"test": "mocha test/*.js --reporter spec"
|
||||
"make-dist": "gulp browser",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"main": "index.js",
|
||||
"version": "0.2.1",
|
||||
"dependencies": {
|
||||
"elliptic": "=1.0.0",
|
||||
"hash.js": "^0.3.2",
|
||||
"bs58": "^2.0.0",
|
||||
"request": "^2.36.0",
|
||||
"express": "^4.3.1",
|
||||
"body-parser": "^1.2.0"
|
||||
"elliptic": "=1.0.0",
|
||||
"secp256k1": "=1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"uglify-js": "~2.4.14",
|
||||
"benchmark": "^1.0.0",
|
||||
"bundle-collapser": "^1.2.1",
|
||||
"browserify": "=6.1.0",
|
||||
"chai": "=1.9.1",
|
||||
"gulp": "^3.8.10",
|
||||
"gulp-bump": "^0.1.11",
|
||||
"gulp-mocha": "^2.0.0",
|
||||
"gulp-git": "^0.5.5",
|
||||
"gulp-shell": "^0.2.10",
|
||||
"karma": "^0.13.9",
|
||||
"karma-firefox-launcher": "^0.1.4",
|
||||
"karma-mocha": "^0.1.9",
|
||||
"run-sequence": "^1.0.2",
|
||||
"uglify-js": "~2.4.14",
|
||||
"mocha": "~1.20.1"
|
||||
}
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
echo "Building browser bundle for bitauth..."
|
||||
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 --compress --mangle -o dist/bitauth.browser.min.js
|
||||
echo "Done!"
|
|
@ -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>
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
'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() {
|
||||
|
||||
|
@ -17,29 +12,29 @@ describe('bitauth', function() {
|
|||
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;
|
||||
var keys = null;
|
||||
|
||||
// invalid checksum
|
||||
var sinbad = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX';
|
||||
var sinbad = 'Tf1Jc1xSbqasm5QLwwSQc5umddx2h7mAMhX';
|
||||
|
||||
// valid sin
|
||||
var singood = 'TfG4ScDgysrSpodWD4Re5UtXmcLbY5CiUHA';
|
||||
var singood = 'TfG4ScDgysrSpodWD4Re5UtXmcLbY5CiUHA';
|
||||
|
||||
// data to sign
|
||||
var contract = 'keyboard cat';
|
||||
var secret = 'o hai, nsa. how i do teh cryptos?';
|
||||
var password = 's4705hiru13z!';
|
||||
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() {
|
||||
|
||||
|
@ -95,7 +90,7 @@ describe('bitauth', function() {
|
|||
describe('#verifySignature', function() {
|
||||
|
||||
it('should verify the signature', function(done) {
|
||||
bitauth.verifySignature(contract, keys.pub, signature, function(err, valid){
|
||||
bitauth.verifySignature(contract, keys.pub, signature, function(err, valid) {
|
||||
should.not.exist(err);
|
||||
should.exist(valid);
|
||||
valid.should.equal(true);
|
||||
|
@ -111,7 +106,7 @@ describe('bitauth', function() {
|
|||
};
|
||||
|
||||
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);
|
||||
|
@ -121,6 +116,51 @@ describe('bitauth', function() {
|
|||
|
||||
});
|
||||
|
||||
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() {
|
||||
|
@ -152,7 +192,7 @@ describe('bitauth', function() {
|
|||
describe('#validateSinCallback', function() {
|
||||
|
||||
it('should receive error callback', function(done) {
|
||||
var valid = bitauth.validateSin(sinbad, function(err){
|
||||
bitauth.validateSin(sinbad, function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('Checksum does not match');
|
||||
done();
|
||||
|
@ -162,7 +202,7 @@ describe('bitauth', function() {
|
|||
});
|
||||
|
||||
// node specific tests
|
||||
if ( typeof(window) === 'undefined' ) {
|
||||
if (typeof(window) === 'undefined') {
|
||||
|
||||
describe('#encrypt', function() {
|
||||
|
||||
|
@ -194,7 +234,7 @@ describe('bitauth', function() {
|
|||
describe('#middleware', function() {
|
||||
|
||||
it('should expose an express middleware', function(done) {
|
||||
bitauth.middleware( {} , {} , function() {
|
||||
bitauth.middleware({}, {}, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue