Added message verification and signing
This commit is contained in:
parent
ed48ded0c6
commit
60940505f1
|
@ -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.
|
45
README.md
45
README.md
|
@ -1 +1,46 @@
|
|||
=======
|
||||
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 verified = Message('hello, world').verify(address, signature);
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/bitpay/bitcore) 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