UNPKG

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