Emilio Almansi
7 years ago
9 changed files with 528 additions and 80 deletions
@ -0,0 +1,166 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var _ = require('lodash'); |
||||
|
var $ = require('./util/preconditions'); |
||||
|
var Address = require('./address'); |
||||
|
var PublicKey = require('./publickey'); |
||||
|
var PrivateKey = require('./privatekey'); |
||||
|
var BufferWriter = require('./encoding/bufferwriter'); |
||||
|
var ECDSA = require('./crypto/ecdsa'); |
||||
|
var Signature = require('./crypto/signature'); |
||||
|
var sha256sha256 = require('./crypto/hash').sha256sha256; |
||||
|
var JSUtil = require('./util/js'); |
||||
|
|
||||
|
/** |
||||
|
* constructs 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); |
||||
|
} |
||||
|
$.checkArgument(_.isString(message), '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) { |
||||
|
$.checkArgument(privateKey instanceof PrivateKey, '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) { |
||||
|
$.checkArgument(publicKey instanceof PublicKey, 'First argument should be an instance of PublicKey'); |
||||
|
$.checkArgument(signature instanceof Signature, '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 {Address|String} bitcoinAddress - A bitcoin address |
||||
|
* @param {String} signatureString - A base64 encoded compact signature |
||||
|
* @returns {Boolean} |
||||
|
*/ |
||||
|
Message.prototype.verify = function verify(bitcoinAddress, signatureString) { |
||||
|
$.checkArgument(bitcoinAddress); |
||||
|
$.checkArgument(signatureString && _.isString(signatureString)); |
||||
|
|
||||
|
if (_.isString(bitcoinAddress)) { |
||||
|
bitcoinAddress = Address.fromString(bitcoinAddress); |
||||
|
} |
||||
|
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 signatureAddress = Address.fromPublicKey(publicKey, bitcoinAddress.network); |
||||
|
|
||||
|
// check that the recovered address and specified address match
|
||||
|
if (bitcoinAddress.toString() !== signatureAddress.toString()) { |
||||
|
this.error = 'The signature did not match the message digest'; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return this._verify(publicKey, signature); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Instantiate a message from a message string |
||||
|
* |
||||
|
* @param {String} str - A string of the message |
||||
|
* @returns {Message} A new instance of a Message |
||||
|
*/ |
||||
|
Message.fromString = function (str) { |
||||
|
return new Message(str); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Instantiate a message from JSON |
||||
|
* |
||||
|
* @param {String} json - An JSON string or Object with keys: message |
||||
|
* @returns {Message} A new instance of a Message |
||||
|
*/ |
||||
|
Message.fromJSON = function fromJSON(json) { |
||||
|
if (JSUtil.isValidJSON(json)) { |
||||
|
json = JSON.parse(json); |
||||
|
} |
||||
|
return new Message(json.message); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* @returns {Object} A plain object with the message information |
||||
|
*/ |
||||
|
Message.prototype.toObject = function toObject() { |
||||
|
return { |
||||
|
message: this.message |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* @returns {String} A JSON representation of the message information |
||||
|
*/ |
||||
|
Message.prototype.toJSON = function toJSON() { |
||||
|
return JSON.stringify(this.toObject()); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Will return a the string representation of the message |
||||
|
* |
||||
|
* @returns {String} Message |
||||
|
*/ |
||||
|
Message.prototype.toString = function () { |
||||
|
return this.message; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Will return a string formatted for the console |
||||
|
* |
||||
|
* @returns {String} Message |
||||
|
*/ |
||||
|
Message.prototype.inspect = function () { |
||||
|
return '<Message: ' + this.toString() + '>'; |
||||
|
}; |
||||
|
|
||||
|
module.exports = Message; |
@ -0,0 +1,168 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var _ = require('lodash'); |
||||
|
var $ = require('./util/preconditions'); |
||||
|
var Address = require('./address'); |
||||
|
var PublicKey = require('./publickey'); |
||||
|
var PrivateKey = require('./privatekey'); |
||||
|
var BufferWriter = require('./encoding/bufferwriter'); |
||||
|
var ECDSA = require('./crypto/ecdsa'); |
||||
|
var Signature = require('./crypto/signature'); |
||||
|
var sha256sha256 = require('./crypto/hash').sha256sha256; |
||||
|
var JSUtil = require('./util/js'); |
||||
|
|
||||
|
/** |
||||
|
* constructs 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); |
||||
|
} |
||||
|
$.checkArgument(_.isString(message), '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) { |
||||
|
console.log(privateKey); |
||||
|
$.checkArgument(privateKey instanceof PrivateKey, |
||||
|
'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) { |
||||
|
$.checkArgument(publicKey instanceof PublicKey, 'First argument should be an instance of PublicKey'); |
||||
|
$.checkArgument(signature instanceof Signature, '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 {Address|String} bitcoinAddress - A bitcoin address |
||||
|
* @param {String} signatureString - A base64 encoded compact signature |
||||
|
* @returns {Boolean} |
||||
|
*/ |
||||
|
Message.prototype.verify = function verify(bitcoinAddress, signatureString) { |
||||
|
$.checkArgument(bitcoinAddress); |
||||
|
$.checkArgument(signatureString && _.isString(signatureString)); |
||||
|
|
||||
|
if (_.isString(bitcoinAddress)) { |
||||
|
bitcoinAddress = Address.fromString(bitcoinAddress); |
||||
|
} |
||||
|
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 signatureAddress = Address.fromPublicKey(publicKey, bitcoinAddress.network); |
||||
|
|
||||
|
// check that the recovered address and specified address match
|
||||
|
if (bitcoinAddress.toString() !== signatureAddress.toString()) { |
||||
|
this.error = 'The signature did not match the message digest'; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return this._verify(publicKey, signature); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Instantiate a message from a message string |
||||
|
* |
||||
|
* @param {String} str - A string of the message |
||||
|
* @returns {Message} A new instance of a Message |
||||
|
*/ |
||||
|
Message.fromString = function(str) { |
||||
|
return new Message(str); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Instantiate a message from JSON |
||||
|
* |
||||
|
* @param {String} json - An JSON string or Object with keys: message |
||||
|
* @returns {Message} A new instance of a Message |
||||
|
*/ |
||||
|
Message.fromJSON = function fromJSON(json) { |
||||
|
if (JSUtil.isValidJSON(json)) { |
||||
|
json = JSON.parse(json); |
||||
|
} |
||||
|
return new Message(json.message); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* @returns {Object} A plain object with the message information |
||||
|
*/ |
||||
|
Message.prototype.toObject = function toObject() { |
||||
|
return { |
||||
|
message: this.message |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* @returns {String} A JSON representation of the message information |
||||
|
*/ |
||||
|
Message.prototype.toJSON = function toJSON() { |
||||
|
return JSON.stringify(this.toObject()); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Will return a the string representation of the message |
||||
|
* |
||||
|
* @returns {String} Message |
||||
|
*/ |
||||
|
Message.prototype.toString = function() { |
||||
|
return this.message; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Will return a string formatted for the console |
||||
|
* |
||||
|
* @returns {String} Message |
||||
|
*/ |
||||
|
Message.prototype.inspect = function() { |
||||
|
return '<Message: ' + this.toString() + '>'; |
||||
|
}; |
||||
|
|
||||
|
module.exports = Message; |
@ -0,0 +1,164 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var chai = require('chai'); |
||||
|
var expect = chai.expect; |
||||
|
var should = chai.should(); |
||||
|
|
||||
|
var bch = require('..'); |
||||
|
var Address = bch.Address; |
||||
|
var Signature = bch.crypto.Signature; |
||||
|
var Message = bch.Message; |
||||
|
|
||||
|
describe('Message', function() { |
||||
|
|
||||
|
var address = 'n1ZCYg9YXtB5XCZazLxSmPDa8iwJRZHhGx'; |
||||
|
var badAddress = 'mmRcrB5fTwgxaFJmVLNtaG8SV454y1E3kC'; |
||||
|
var privateKey = bch.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('First argument should be a string'); |
||||
|
}); |
||||
|
|
||||
|
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('First argument should be an instance of PrivateKey'); |
||||
|
}); |
||||
|
|
||||
|
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('First argument should be an instance of PublicKey'); |
||||
|
}); |
||||
|
|
||||
|
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('Second argument should be an instance of Signature'); |
||||
|
}); |
||||
|
|
||||
|
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('will verify with an uncompressed pubkey', function() { |
||||
|
var privateKey = new bch.PrivateKey('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss'); |
||||
|
var message = new Message('This is an example of a signed message.'); |
||||
|
var signature = message.sign(privateKey); |
||||
|
var verified = message.verify(privateKey.toAddress(), signature); |
||||
|
verified.should.equal(true); |
||||
|
}); |
||||
|
|
||||
|
it('can chain methods', function() { |
||||
|
var verified = Message(text).verify(address, signatureString); |
||||
|
verified.should.equal(true); |
||||
|
}); |
||||
|
|
||||
|
describe('#json', function() { |
||||
|
|
||||
|
it('roundtrip to-from-to', function() { |
||||
|
var json = new Message(text).toJSON(); |
||||
|
var message = Message.fromJSON(json); |
||||
|
message.toString().should.equal(text); |
||||
|
}); |
||||
|
|
||||
|
it('checks that the string parameter is valid JSON', function() { |
||||
|
expect(function() { |
||||
|
return Message.fromJSON('¹'); |
||||
|
}).to.throw(); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe('#toString', function() { |
||||
|
|
||||
|
it('message string', function() { |
||||
|
var message = new Message(text); |
||||
|
message.toString().should.equal(text); |
||||
|
}); |
||||
|
|
||||
|
it('roundtrip to-from-to', function() { |
||||
|
var str = new Message(text).toString(); |
||||
|
var message = Message.fromString(str); |
||||
|
message.toString().should.equal(text); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe('#inspect', function() { |
||||
|
|
||||
|
it('should output formatted output correctly', function() { |
||||
|
var message = new Message(text); |
||||
|
var output = '<Message: '+text+'>'; |
||||
|
message.inspect().should.equal(output); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
it('accepts Address for verification', function() { |
||||
|
var verified = Message(text) |
||||
|
.verify(new Address(address), signatureString); |
||||
|
verified.should.equal(true); |
||||
|
}); |
||||
|
|
||||
|
}); |
Loading…
Reference in new issue