UNPKG

11.1 kBJavaScriptView Raw
1'use strict';
2
3var BN = require('./crypto/bn');
4var Point = require('./crypto/point');
5var Hash = require('./crypto/hash');
6var JSUtil = require('./util/js');
7var Network = require('./networks');
8var _ = require('lodash');
9var $ = require('./util/preconditions');
10
11/**
12 * Instantiate a PublicKey from a {@link PrivateKey}, {@link Point}, `string`, or `Buffer`.
13 *
14 * There are two internal properties, `network` and `compressed`, that deal with importing
15 * a PublicKey from a PrivateKey in WIF format. More details described on {@link PrivateKey}
16 *
17 * @example
18 * ```javascript
19 * // instantiate from a private key
20 * var key = PublicKey(privateKey, true);
21 *
22 * // export to as a DER hex encoded string
23 * var exported = key.toString();
24 *
25 * // import the public key
26 * var imported = PublicKey.fromString(exported);
27 * ```
28 *
29 * @param {string} data - The encoded data in various formats
30 * @param {Object} extra - additional options
31 * @param {Network=} extra.network - Which network should the address for this public key be for
32 * @param {String=} extra.compressed - If the public key is compressed
33 * @returns {PublicKey} A new valid instance of an PublicKey
34 * @constructor
35 */
36function PublicKey(data, extra) {
37
38 if (!(this instanceof PublicKey)) {
39 return new PublicKey(data, extra);
40 }
41
42 $.checkArgument(data, 'First argument is required, please include public key data.');
43
44 if (data instanceof PublicKey) {
45 // Return copy, but as it's an immutable object, return same argument
46 return data;
47 }
48 extra = extra || {};
49
50 var info = this._classifyArgs(data, extra);
51
52 // validation
53 info.point.validate();
54
55 JSUtil.defineImmutable(this, {
56 point: info.point,
57 compressed: info.compressed,
58 network: info.network || Network.defaultNetwork
59 });
60
61 return this;
62};
63
64/**
65 * Internal function to differentiate between arguments passed to the constructor
66 * @param {*} data
67 * @param {Object} extra
68 */
69PublicKey.prototype._classifyArgs = function(data, extra) {
70 /* jshint maxcomplexity: 10 */
71 var info = {
72 compressed: _.isUndefined(extra.compressed) || extra.compressed
73 };
74
75 // detect type of data
76 if (data instanceof Point) {
77 info.point = data;
78 } else if (data.x && data.y) {
79 info = PublicKey._transformObject(data);
80 } else if (typeof(data) === 'string') {
81 info = PublicKey._transformDER(Buffer.from(data, 'hex'));
82 } else if (PublicKey._isBuffer(data)) {
83 info = PublicKey._transformDER(data);
84 } else if (PublicKey._isPrivateKey(data)) {
85 info = PublicKey._transformPrivateKey(data);
86 } else {
87 throw new TypeError('First argument is an unrecognized data format.');
88 }
89 if (!info.network) {
90 info.network = _.isUndefined(extra.network) ? undefined : Network.get(extra.network);
91 }
92 return info;
93};
94
95/**
96 * Internal function to detect if an object is a {@link PrivateKey}
97 *
98 * @param {*} param - object to test
99 * @returns {boolean}
100 * @private
101 */
102PublicKey._isPrivateKey = function(param) {
103 var PrivateKey = require('./privatekey');
104 return param instanceof PrivateKey;
105};
106
107/**
108 * Internal function to detect if an object is a Buffer
109 *
110 * @param {*} param - object to test
111 * @returns {boolean}
112 * @private
113 */
114PublicKey._isBuffer = function(param) {
115 return (param instanceof Buffer) || (param instanceof Uint8Array);
116};
117
118/**
119 * Internal function to transform a private key into a public key point
120 *
121 * @param {PrivateKey} privkey - An instance of PrivateKey
122 * @returns {Object} An object with keys: point and compressed
123 * @private
124 */
125PublicKey._transformPrivateKey = function(privkey) {
126 $.checkArgument(PublicKey._isPrivateKey(privkey), 'Must be an instance of PrivateKey');
127 var info = {};
128 info.point = Point.getG().mul(privkey.bn);
129 info.compressed = privkey.compressed;
130 info.network = privkey.network;
131 return info;
132};
133
134/**
135 * Internal function to transform DER into a public key point
136 *
137 * @param {Buffer} buf - An hex encoded buffer
138 * @param {bool=} strict - if set to false, will loosen some conditions
139 * @returns {Object} An object with keys: point and compressed
140 * @private
141 */
142PublicKey._transformDER = function(buf, strict) {
143 /* jshint maxstatements: 30 */
144 /* jshint maxcomplexity: 12 */
145 $.checkArgument(PublicKey._isBuffer(buf), 'Must be a hex buffer of DER encoded public key');
146 var info = {};
147
148 strict = _.isUndefined(strict) ? true : strict;
149
150 var x;
151 var y;
152 var xbuf;
153 var ybuf;
154
155 if (buf[0] === 0x04 || (!strict && (buf[0] === 0x06 || buf[0] === 0x07))) {
156 xbuf = buf.slice(1, 33);
157 ybuf = buf.slice(33, 65);
158 if (xbuf.length !== 32 || ybuf.length !== 32 || buf.length !== 65) {
159 throw new TypeError('Length of x and y must be 32 bytes');
160 }
161 x = new BN(xbuf);
162 y = new BN(ybuf);
163 info.point = new Point(x, y);
164 info.compressed = false;
165 } else if (buf[0] === 0x03) {
166 xbuf = buf.slice(1);
167 x = new BN(xbuf);
168 info = PublicKey._transformX(true, x);
169 info.compressed = true;
170 } else if (buf[0] === 0x02) {
171 xbuf = buf.slice(1);
172 x = new BN(xbuf);
173 info = PublicKey._transformX(false, x);
174 info.compressed = true;
175 } else {
176 throw new TypeError('Invalid DER format public key');
177 }
178 return info;
179};
180
181/**
182 * Internal function to transform X into a public key point
183 *
184 * @param {Boolean} odd - If the point is above or below the x axis
185 * @param {Point} x - The x point
186 * @returns {Object} An object with keys: point and compressed
187 * @private
188 */
189PublicKey._transformX = function(odd, x) {
190 $.checkArgument(typeof odd === 'boolean', 'Must specify whether y is odd or not (true or false)');
191 var info = {};
192 info.point = Point.fromX(odd, x);
193 return info;
194};
195
196/**
197 * Internal function to transform a JSON into a public key point
198 *
199 * @param {String|Object} json - a JSON string or plain object
200 * @returns {Object} An object with keys: point and compressed
201 * @private
202 */
203PublicKey._transformObject = function(json) {
204 var x = new BN(json.x, 'hex');
205 var y = new BN(json.y, 'hex');
206 var point = new Point(x, y);
207 return new PublicKey(point, {
208 compressed: json.compressed
209 });
210};
211
212/**
213 * Instantiate a PublicKey from a PrivateKey
214 *
215 * @param {PrivateKey} privkey - An instance of PrivateKey
216 * @returns {PublicKey} A new valid instance of PublicKey
217 */
218PublicKey.fromPrivateKey = function(privkey) {
219 $.checkArgument(PublicKey._isPrivateKey(privkey), 'Must be an instance of PrivateKey');
220 var info = PublicKey._transformPrivateKey(privkey);
221 return new PublicKey(info.point, {
222 compressed: info.compressed,
223 network: info.network
224 });
225};
226
227/**
228 * Instantiate a PublicKey from a Buffer
229 * @param {Buffer} buf - A DER hex buffer
230 * @param {bool=} strict - if set to false, will loosen some conditions
231 * @returns {PublicKey} A new valid instance of PublicKey
232 */
233PublicKey.fromDER = PublicKey.fromBuffer = function(buf, strict) {
234 $.checkArgument(PublicKey._isBuffer(buf), 'Must be a hex buffer of DER encoded public key');
235 var info = PublicKey._transformDER(buf, strict);
236 return new PublicKey(info.point, {
237 compressed: info.compressed
238 });
239};
240
241/**
242 * Instantiate a PublicKey from a Point
243 *
244 * @param {Point} point - A Point instance
245 * @param {boolean=} compressed - whether to store this public key as compressed format
246 * @returns {PublicKey} A new valid instance of PublicKey
247 */
248PublicKey.fromPoint = function(point, compressed) {
249 $.checkArgument(point instanceof Point, 'First argument must be an instance of Point.');
250 return new PublicKey(point, {
251 compressed: compressed
252 });
253};
254
255/**
256 * Instantiate a PublicKey from a DER hex encoded string
257 *
258 * @param {string} str - A DER hex string
259 * @param {String=} encoding - The type of string encoding
260 * @returns {PublicKey} A new valid instance of PublicKey
261 */
262PublicKey.fromString = function(str, encoding) {
263 var buf = Buffer.from(str, encoding || 'hex');
264 var info = PublicKey._transformDER(buf);
265 return new PublicKey(info.point, {
266 compressed: info.compressed
267 });
268};
269
270/**
271 * Instantiate a PublicKey from an X Point
272 *
273 * @param {Boolean} odd - If the point is above or below the x axis
274 * @param {Point} x - The x point
275 * @returns {PublicKey} A new valid instance of PublicKey
276 */
277PublicKey.fromX = function(odd, x) {
278 var info = PublicKey._transformX(odd, x);
279 return new PublicKey(info.point, {
280 compressed: info.compressed
281 });
282};
283
284/**
285 * Check if there would be any errors when initializing a PublicKey
286 *
287 * @param {string} data - The encoded data in various formats
288 * @returns {null|Error} An error if exists
289 */
290PublicKey.getValidationError = function(data) {
291 var error;
292 try {
293 /* jshint nonew: false */
294 new PublicKey(data);
295 } catch (e) {
296 error = e;
297 }
298 return error;
299};
300
301/**
302 * Check if the parameters are valid
303 *
304 * @param {string} data - The encoded data in various formats
305 * @returns {Boolean} If the public key would be valid
306 */
307PublicKey.isValid = function(data) {
308 return !PublicKey.getValidationError(data);
309};
310
311/**
312 * @returns {Object} A plain object of the PublicKey
313 */
314PublicKey.prototype.toObject = PublicKey.prototype.toJSON = function toObject() {
315 return {
316 x: this.point.getX().toString('hex', 2),
317 y: this.point.getY().toString('hex', 2),
318 compressed: this.compressed
319 };
320};
321
322/**
323 * Will output the PublicKey to a DER Buffer
324 *
325 * @returns {Buffer} A DER hex encoded buffer
326 */
327PublicKey.prototype.toBuffer = PublicKey.prototype.toDER = function() {
328 var x = this.point.getX();
329 var y = this.point.getY();
330
331 var xbuf = x.toBuffer({
332 size: 32
333 });
334 var ybuf = y.toBuffer({
335 size: 32
336 });
337
338 var prefix;
339 if (!this.compressed) {
340 prefix = Buffer.from([0x04]);
341 return Buffer.concat([prefix, xbuf, ybuf]);
342 } else {
343 var odd = ybuf[ybuf.length - 1] % 2;
344 if (odd) {
345 prefix = Buffer.from([0x03]);
346 } else {
347 prefix = Buffer.from([0x02]);
348 }
349 return Buffer.concat([prefix, xbuf]);
350 }
351};
352
353/**
354 * Will return a sha256 + ripemd160 hash of the serialized public key
355 * @see https://github.com/bitcoin/bitcoin/blob/master/src/pubkey.h#L141
356 * @returns {Buffer}
357 */
358PublicKey.prototype._getID = function _getID() {
359 return Hash.sha256ripemd160(this.toBuffer());
360};
361
362/**
363 * Will return an address for the public key
364 *
365 * @param {String|Network=} network - Which network should the address be for
366 * @param {string} type - Either 'pubkeyhash', 'witnesspubkeyhash', or 'scripthash'
367 * @returns {Address} An address generated from the public key
368 */
369PublicKey.prototype.toAddress = function(network, type) {
370 var Address = require('./address');
371 return Address.fromPublicKey(this, network || this.network, type);
372};
373
374/**
375 * Will output the PublicKey to a DER encoded hex string
376 *
377 * @returns {string} A DER hex encoded string
378 */
379PublicKey.prototype.toString = function() {
380 return this.toDER().toString('hex');
381};
382
383/**
384 * Will return a string formatted for the console
385 *
386 * @returns {string} Public key
387 */
388PublicKey.prototype.inspect = function() {
389 return '<PublicKey: ' + this.toString() +
390 (this.compressed ? '' : ', uncompressed') + '>';
391};
392
393
394module.exports = PublicKey;