Merge pull request #1 from bitpay/feature/message
Added message verification and signing
This commit is contained in:
commit
e5861e7a1f
|
@ -0,0 +1 @@
|
||||||
|
repo_token: OMJRNZCl018Yjy44nlG1hF6maKEyXcwPx
|
|
@ -0,0 +1,11 @@
|
||||||
|
*.sw[a-z]
|
||||||
|
coverage
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
npm-debug.log
|
||||||
|
bitcore-message.js
|
||||||
|
bitcore-message.min.js
|
||||||
|
tests.js
|
||||||
|
|
||||||
|
report
|
||||||
|
.DS_Store
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
|
||||||
|
"browser": true, // Standard browser globals e.g. `window`, `document`.
|
||||||
|
"camelcase": false, // Permit only camelcase for `var` and `object indexes`.
|
||||||
|
"curly": true, // Require {} for every new block or scope.
|
||||||
|
"devel": false, // Allow development statements e.g. `console.log();`.
|
||||||
|
"eqeqeq": true, // Require triple equals i.e. `===`.
|
||||||
|
"esnext": true, // Allow ES.next specific features such as `const` and `let`.
|
||||||
|
"freeze": true, // Forbid overwriting prototypes of native objects such as Array, Date and so on.
|
||||||
|
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||||
|
"indent": 2, // Specify indentation spacing
|
||||||
|
"latedef": true, // Prohibit variable use before definition.
|
||||||
|
"newcap": false, // Require capitalization of all constructor functions e.g. `new F()`.
|
||||||
|
"noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
|
||||||
|
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||||
|
"noempty": true, // Prohibit use of empty blocks.
|
||||||
|
"nonew": true, // Prohibits the use of constructor functions for side-effects
|
||||||
|
"quotmark": "single", // Define quotes to string values.
|
||||||
|
"regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
|
||||||
|
"smarttabs": false, // Supress warnings about mixed tabs and spaces
|
||||||
|
"strict": true, // Require `use strict` pragma in every file.
|
||||||
|
"trailing": true, // Prohibit trailing whitespaces.
|
||||||
|
"undef": true, // Require all non-global variables be declared before they are used.
|
||||||
|
"unused": true, // Warn unused variables.
|
||||||
|
|
||||||
|
"maxparams": 4, // Maximum number of parameters for a function
|
||||||
|
"maxstatements": 15, // Maximum number of statements in a function
|
||||||
|
"maxcomplexity": 6, // Cyclomatic complexity (http://en.wikipedia.org/wiki/Cyclomatic_complexity)
|
||||||
|
"maxdepth": 4, // Maximum depth of nested control structures
|
||||||
|
"maxlen": 120, // Maximum number of cols in a line
|
||||||
|
|
||||||
|
"predef": [ // Extra globals.
|
||||||
|
"after",
|
||||||
|
"afterEach",
|
||||||
|
"before",
|
||||||
|
"beforeEach",
|
||||||
|
"define",
|
||||||
|
"describe",
|
||||||
|
"exports",
|
||||||
|
"it",
|
||||||
|
"module",
|
||||||
|
"require"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- '0.10'
|
||||||
|
before_install:
|
||||||
|
- export DISPLAY=:99.0
|
||||||
|
- sh -e /etc/init.d/xvfb start
|
||||||
|
install:
|
||||||
|
- npm install
|
||||||
|
after_script:
|
||||||
|
- gulp coveralls
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Please see [CONTRIBUTING.md](https://github.com/bitpay/bitcore/blob/master/CONTRIBUTING.md) on the main bitcore repo.
|
46
README.md
46
README.md
|
@ -1 +1,47 @@
|
||||||
|
=======
|
||||||
|
Bitcoin Message Verification and Signing for Bitcore
|
||||||
|
=======
|
||||||
|
|
||||||
|
[![NPM Package](https://img.shields.io/npm/v/bitcore-message.svg?style=flat-square)](https://www.npmjs.org/package/bitcore-message)
|
||||||
|
[![Build Status](https://img.shields.io/travis/bitpay/bitcore-message.svg?branch=master&style=flat-square)](https://travis-ci.org/bitpay/bitcore-message)
|
||||||
|
[![Coverage Status](https://img.shields.io/coveralls/bitpay/bitcore-message.svg?style=flat-square)](https://coveralls.io/r/bitpay/bitcore-message?branch=master)
|
||||||
|
|
||||||
|
bitcore-message adds support for verifying and signing bitcoin messages in [Node.js](http://nodejs.org/) and web browsers.
|
||||||
|
|
||||||
|
See [the main bitcore repo](https://github.com/bitpay/bitcore) for more information.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install bitcore-message
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bower install bitcore-message
|
||||||
|
```
|
||||||
|
|
||||||
|
To sign a message:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var privateKey = PrivateKey.fromWIF('cPBn5A4ikZvBTQ8D7NnvHZYCAxzDZ5Z2TSGW2LkyPiLxqYaJPBW4');
|
||||||
|
var signature = Message('hello, world').sign(privateKey);
|
||||||
|
```
|
||||||
|
|
||||||
|
To verify a message:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var address = 'n1ZCYg9YXtB5XCZazLxSmPDa8iwJRZHhGx';
|
||||||
|
var signature = 'H/DIn8uA1scAuKLlCx+/9LnAcJtwQQ0PmcPrJUq90aboLv3fH5fFvY+vmbfOSFEtGarznYli6ShPr9RXwY9UrIY=';
|
||||||
|
var verified = Message('hello, world').verify(address, signature);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](https://github.com/bitpay/bitcore/blob/master/CONTRIBUTING.md) on the main bitcore repo for information about how to contribute.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Code released under [the MIT license](https://github.com/bitpay/bitcore/blob/master/LICENSE).
|
||||||
|
|
||||||
|
Copyright 2013-2015 BitPay, Inc. Bitcore is a trademark maintained by BitPay, Inc.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var gulp = require('gulp');
|
||||||
|
var bitcoreTasks = require('bitcore-build');
|
||||||
|
|
||||||
|
bitcoreTasks('message');
|
||||||
|
|
||||||
|
gulp.task('default', ['lint', 'coverage']);
|
|
@ -0,0 +1,4 @@
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
bitcore.Message = require('./lib/message');
|
||||||
|
|
||||||
|
module.exports = bitcore.Message;
|
|
@ -0,0 +1,111 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var PrivateKey = bitcore.PrivateKey;
|
||||||
|
var PublicKey = bitcore.PublicKey;
|
||||||
|
var Address = bitcore.Address;
|
||||||
|
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||||
|
var ECDSA = bitcore.crypto.ECDSA;
|
||||||
|
var Signature = bitcore.crypto.Signature;
|
||||||
|
var sha256sha256 = bitcore.crypto.Hash.sha256sha256;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will construct a new message to sign and verify.
|
||||||
|
*
|
||||||
|
* @param {String} message
|
||||||
|
* @returns {Message}
|
||||||
|
*/
|
||||||
|
var Message = function Message(message) {
|
||||||
|
if (!(this instanceof Message)) {
|
||||||
|
return new Message(message);
|
||||||
|
}
|
||||||
|
if (typeof message !== 'string') {
|
||||||
|
throw new TypeError('First argument should be a string');
|
||||||
|
}
|
||||||
|
this.message = message;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Message.MAGIC_BYTES = new Buffer('Bitcoin Signed Message:\n');
|
||||||
|
|
||||||
|
Message.prototype.magicHash = function magicHash() {
|
||||||
|
var prefix1 = BufferWriter.varintBufNum(Message.MAGIC_BYTES.length);
|
||||||
|
var messageBuffer = new Buffer(this.message);
|
||||||
|
var prefix2 = BufferWriter.varintBufNum(messageBuffer.length);
|
||||||
|
var buf = Buffer.concat([prefix1, Message.MAGIC_BYTES, prefix2, messageBuffer]);
|
||||||
|
var hash = sha256sha256(buf);
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
Message.prototype._sign = function _sign(privateKey) {
|
||||||
|
if (!(privateKey instanceof PrivateKey)) {
|
||||||
|
throw new TypeError('First argument should be an instance of PrivateKey');
|
||||||
|
}
|
||||||
|
var hash = this.magicHash();
|
||||||
|
var ecdsa = new ECDSA();
|
||||||
|
ecdsa.hashbuf = hash;
|
||||||
|
ecdsa.privkey = privateKey;
|
||||||
|
ecdsa.pubkey = privateKey.toPublicKey();
|
||||||
|
ecdsa.signRandomK();
|
||||||
|
ecdsa.calci();
|
||||||
|
return ecdsa.sig;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will sign a message with a given bitcoin private key.
|
||||||
|
*
|
||||||
|
* @param {PrivateKey} privateKey - An instance of PrivateKey
|
||||||
|
* @returns {String} A base64 encoded compact signature
|
||||||
|
*/
|
||||||
|
Message.prototype.sign = function sign(privateKey) {
|
||||||
|
var signature = this._sign(privateKey);
|
||||||
|
return signature.toCompact().toString('base64');
|
||||||
|
};
|
||||||
|
|
||||||
|
Message.prototype._verify = function _verify(publicKey, signature) {
|
||||||
|
if (!(publicKey instanceof PublicKey)) {
|
||||||
|
throw new TypeError('First argument should be an instance of PublicKey');
|
||||||
|
}
|
||||||
|
if (!(signature instanceof Signature)) {
|
||||||
|
throw new TypeError('Second argument should be an instance of Signature');
|
||||||
|
}
|
||||||
|
var hash = this.magicHash();
|
||||||
|
var verified = ECDSA.verify(hash, signature, publicKey);
|
||||||
|
if (!verified) {
|
||||||
|
this.error = 'The signature was invalid';
|
||||||
|
}
|
||||||
|
return verified;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return a boolean of the signature is valid for a given bitcoin address.
|
||||||
|
* If it isn't the specific reason is accessible via the "error" member.
|
||||||
|
*
|
||||||
|
* @param {String} bitcoinAddress - A bitcoin address
|
||||||
|
* @param {String} signatureString - A base64 encoded compact signature
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
Message.prototype.verify = function verify(bitcoinAddress, signatureString) {
|
||||||
|
var signature = Signature.fromCompact(new Buffer(signatureString, 'base64'));
|
||||||
|
|
||||||
|
// recover the public key
|
||||||
|
var ecdsa = new ECDSA();
|
||||||
|
ecdsa.hashbuf = this.magicHash();
|
||||||
|
ecdsa.sig = signature;
|
||||||
|
var publicKey = ecdsa.toPublicKey();
|
||||||
|
|
||||||
|
var expectedAddress = Address.fromString(bitcoinAddress);
|
||||||
|
var signatureAddress = Address.fromPublicKey(publicKey, expectedAddress.network);
|
||||||
|
|
||||||
|
// check that the recovered address and specified address match
|
||||||
|
if (expectedAddress.toString() !== signatureAddress.toString()) {
|
||||||
|
this.error = 'The signature did not match the message digest';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._verify(publicKey, signature);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Message;
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "bitcore-message",
|
||||||
|
"version": "0.9.0",
|
||||||
|
"description": "Bitcoin Messages for Bitcore",
|
||||||
|
"author": "BitPay <dev@bitpay.com>",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"lint": "gulp lint",
|
||||||
|
"test": "gulp test:node",
|
||||||
|
"coverage": "gulp coverage",
|
||||||
|
"build": "gulp"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"bitcoin",
|
||||||
|
"bitcore"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/bitpay/bitcore-message.git"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bitcore": "^0.9.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"bitcore-build": "bitpay/bitcore-build",
|
||||||
|
"brfs": "^1.3.0",
|
||||||
|
"chai": "~1.10.0",
|
||||||
|
"gulp": "^3.8.10"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var expect = chai.expect;
|
||||||
|
var should = chai.should();
|
||||||
|
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var Signature = bitcore.crypto.Signature;
|
||||||
|
var Message = require('../');
|
||||||
|
|
||||||
|
describe('Message', function() {
|
||||||
|
|
||||||
|
var address = 'n1ZCYg9YXtB5XCZazLxSmPDa8iwJRZHhGx';
|
||||||
|
var badAddress = 'mmRcrB5fTwgxaFJmVLNtaG8SV454y1E3kC';
|
||||||
|
var privateKey = bitcore.PrivateKey.fromWIF('cPBn5A4ikZvBTQ8D7NnvHZYCAxzDZ5Z2TSGW2LkyPiLxqYaJPBW4');
|
||||||
|
var text = 'hello, world';
|
||||||
|
var signatureString = 'H/DIn8uA1scAuKLlCx+/9LnAcJtwQQ0PmcPrJUq90aboLv3fH5fFvY+vmbfOSFEtGarznYli6ShPr9RXwY9UrIY=';
|
||||||
|
|
||||||
|
var badSignatureString = 'H69qZ4mbZCcvXk7CWjptD5ypnYVLvQ3eMXLM8+1gX21SLH/GaFnAjQrDn37+TDw79i9zHhbiMMwhtvTwnPigZ6k=';
|
||||||
|
|
||||||
|
var signature = Signature.fromCompact(new Buffer(signatureString, 'base64'));
|
||||||
|
var badSignature = Signature.fromCompact(new Buffer(badSignatureString, 'base64'));
|
||||||
|
|
||||||
|
var publicKey = privateKey.toPublicKey();
|
||||||
|
|
||||||
|
it('will error with incorrect message type', function() {
|
||||||
|
expect(function(){
|
||||||
|
return new Message(new Date());
|
||||||
|
}).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will instantiate without "new"', function() {
|
||||||
|
var message = Message(text);
|
||||||
|
should.exist(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
var signature2;
|
||||||
|
var signature3;
|
||||||
|
|
||||||
|
it('can sign a message', function() {
|
||||||
|
var message2 = new Message(text);
|
||||||
|
signature2 = message2._sign(privateKey);
|
||||||
|
signature3 = Message(text).sign(privateKey);
|
||||||
|
should.exist(signature2);
|
||||||
|
should.exist(signature3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sign will error with incorrect private key argument', function() {
|
||||||
|
expect(function(){
|
||||||
|
var message3 = new Message(text);
|
||||||
|
return message3.sign('not a private key');
|
||||||
|
}).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can verify a message with signature', function() {
|
||||||
|
var message4 = new Message(text);
|
||||||
|
var verified = message4._verify(publicKey, signature2);
|
||||||
|
verified.should.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can verify a message with existing signature', function() {
|
||||||
|
var message5 = new Message(text);
|
||||||
|
var verified = message5._verify(publicKey, signature);
|
||||||
|
verified.should.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verify will error with incorrect public key argument', function() {
|
||||||
|
expect(function(){
|
||||||
|
var message6 = new Message(text);
|
||||||
|
return message6._verify('not a public key', signature);
|
||||||
|
}).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verify will error with incorrect signature argument', function() {
|
||||||
|
expect(function(){
|
||||||
|
var message7 = new Message(text);
|
||||||
|
return message7._verify(publicKey, 'not a signature');
|
||||||
|
}).to.throw(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verify will correctly identify a bad signature', function() {
|
||||||
|
var message8 = new Message(text);
|
||||||
|
var verified = message8._verify(publicKey, badSignature);
|
||||||
|
should.exist(message8.error);
|
||||||
|
verified.should.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can verify a message with address and generated signature string', function() {
|
||||||
|
var message9 = new Message(text);
|
||||||
|
var verified = message9.verify(address, signature3);
|
||||||
|
should.not.exist(message9.error);
|
||||||
|
verified.should.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will not verify with address mismatch', function() {
|
||||||
|
var message10 = new Message(text);
|
||||||
|
var verified = message10.verify(badAddress, signatureString);
|
||||||
|
should.exist(message10.error);
|
||||||
|
verified.should.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can chain methods', function() {
|
||||||
|
var verified = Message(text).verify(address, signatureString);
|
||||||
|
verified.should.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue