UNPKG

5.95 kBJavaScriptView Raw
1'use strict';
2
3var _ = require('lodash');
4var PrivateKey = require('./privatekey');
5var PublicKey = require('./publickey');
6var Address = require('./address');
7var BufferWriter = require('./encoding/bufferwriter');
8var ECDSA = require('./crypto/ecdsa');
9var Signature = require('./crypto/signature');
10var sha256sha256 = require('./crypto/hash').sha256sha256;
11var JSUtil = require('./util/js');
12var $ = require('./util/preconditions');
13
14function Message(message) {
15 if (!(this instanceof Message)) {
16 return new Message(message);
17 }
18 $.checkArgument(_.isString(message), 'First argument should be a string');
19 this.message = message;
20
21 return this;
22}
23
24Message.MAGIC_BYTES = Buffer.from('Bitcoin Signed Message:\n');
25
26Message.prototype.magicHash = function magicHash() {
27 var prefix1 = BufferWriter.varintBufNum(Message.MAGIC_BYTES.length);
28 var messageBuffer = Buffer.from(this.message);
29 var prefix2 = BufferWriter.varintBufNum(messageBuffer.length);
30 var buf = Buffer.concat([prefix1, Message.MAGIC_BYTES, prefix2, messageBuffer]);
31 var hash = sha256sha256(buf);
32 return hash;
33};
34
35Message.prototype._sign = function _sign(privateKey) {
36 $.checkArgument(privateKey instanceof PrivateKey, 'First argument should be an instance of PrivateKey');
37 var hash = this.magicHash();
38 var ecdsa = new ECDSA();
39 ecdsa.hashbuf = hash;
40 ecdsa.privkey = privateKey;
41 ecdsa.pubkey = privateKey.toPublicKey();
42 ecdsa.signRandomK();
43 ecdsa.calci();
44 return ecdsa.sig;
45};
46
47/**
48 * Will sign a message with a given bitcoin private key.
49 *
50 * @param {PrivateKey} privateKey - An instance of PrivateKey
51 * @returns {String} A base64 encoded compact signature
52 */
53Message.prototype.sign = function sign(privateKey) {
54 var signature = this._sign(privateKey);
55 return signature.toCompact().toString('base64');
56};
57
58Message.prototype._verify = function _verify(publicKey, signature) {
59 $.checkArgument(publicKey instanceof PublicKey, 'First argument should be an instance of PublicKey');
60 $.checkArgument(signature instanceof Signature, 'Second argument should be an instance of Signature');
61 var hash = this.magicHash();
62 var verified = ECDSA.verify(hash, signature, publicKey);
63 if (!verified) {
64 this.error = 'The signature was invalid';
65 }
66 return verified;
67};
68
69/**
70 * Will return a boolean of the signature is valid for a given bitcoin address.
71 * If it isn't the specific reason is accessible via the "error" member.
72 *
73 * @param {Address|String} bitcoinAddress - A bitcoin address
74 * @param {String} signatureString - A base64 encoded compact signature
75 * @returns {Boolean}
76 */
77Message.prototype.verify = function verify(bitcoinAddress, signatureString) {
78 $.checkArgument(bitcoinAddress);
79 $.checkArgument(signatureString && _.isString(signatureString));
80
81 if (_.isString(bitcoinAddress)) {
82 bitcoinAddress = Address.fromString(bitcoinAddress);
83 }
84 var signature = Signature.fromCompact(Buffer.from(signatureString, 'base64'));
85
86 // recover the public key
87 var ecdsa = new ECDSA();
88 ecdsa.hashbuf = this.magicHash();
89 ecdsa.sig = signature;
90 var publicKey = ecdsa.toPublicKey();
91
92 var signatureAddress = Address.fromPublicKey(publicKey, bitcoinAddress.network);
93
94 // check that the recovered address and specified address match
95 if (bitcoinAddress.toString() !== signatureAddress.toString()) {
96 this.error = 'The signature did not match the message digest';
97 return false;
98 }
99
100 return this._verify(publicKey, signature);
101};
102
103/**
104 * Will return a public key string if the provided signature and the message digest is correct
105 * If it isn't the specific reason is accessible via the "error" member.
106 *
107 * @param {Address|String} bitcoinAddress - A bitcoin address
108 * @param {String} signatureString - A base64 encoded compact signature
109 * @returns {String}
110 */
111Message.prototype.recoverPublicKey = function recoverPublicKey(bitcoinAddress, signatureString) {
112 $.checkArgument(bitcoinAddress);
113 $.checkArgument(signatureString && _.isString(signatureString));
114
115 if (_.isString(bitcoinAddress)) {
116 bitcoinAddress = Address.fromString(bitcoinAddress);
117 }
118 var signature = Signature.fromCompact(Buffer.from(signatureString, 'base64'));
119
120 // recover the public key
121 var ecdsa = new ECDSA();
122 ecdsa.hashbuf = this.magicHash();
123 ecdsa.sig = signature;
124 var publicKey = ecdsa.toPublicKey();
125
126 var signatureAddress = Address.fromPublicKey(publicKey, bitcoinAddress.network);
127
128 // check that the recovered address and specified address match
129 if (bitcoinAddress.toString() !== signatureAddress.toString()) {
130 this.error = 'The signature did not match the message digest';
131 }
132
133 return publicKey.toString();
134};
135
136/**
137 * Instantiate a message from a message string
138 *
139 * @param {String} str - A string of the message
140 * @returns {Message} A new instance of a Message
141 */
142Message.fromString = function(str) {
143 return new Message(str);
144};
145
146/**
147 * Instantiate a message from JSON
148 *
149 * @param {String} json - An JSON string or Object with keys: message
150 * @returns {Message} A new instance of a Message
151 */
152Message.fromJSON = function fromJSON(json) {
153 if (JSUtil.isValidJSON(json)) {
154 json = JSON.parse(json);
155 }
156 return new Message(json.message);
157};
158
159/**
160 * @returns {Object} A plain object with the message information
161 */
162Message.prototype.toObject = function toObject() {
163 return {
164 message: this.message
165 };
166};
167
168/**
169 * @returns {String} A JSON representation of the message information
170 */
171Message.prototype.toJSON = function toJSON() {
172 return JSON.stringify(this.toObject());
173};
174
175/**
176 * Will return a the string representation of the message
177 *
178 * @returns {String} Message
179 */
180Message.prototype.toString = function() {
181 return this.message;
182};
183
184/**
185 * Will return a string formatted for the console
186 *
187 * @returns {String} Message
188 */
189Message.prototype.inspect = function() {
190 return '<Message: ' + this.toString() + '>';
191};
192
193module.exports = Message;
194
195var Script = require('./script');