UNPKG

11.6 kBJavaScriptView Raw
1'use strict';
2
3var owsCommon = require('@owstack/ows-common');
4var Base58Check = owsCommon.encoding.Base58Check;
5var BN = owsCommon.BN;
6var Hash = owsCommon.Hash;
7var JSUtil = owsCommon.util.js;
8var Networks = require('@owstack/network-lib');
9var Point = require('./crypto/point');
10var PublicKey = require('./publickey');
11var Random = owsCommon.Random;
12var lodash = owsCommon.deps.lodash;
13var $ = owsCommon.util.preconditions;
14
15/**
16 * Instantiate a PrivateKey from a BN, Buffer and WIF.
17 *
18 * @example
19 * ```javascript
20 * // generate a new random key
21 * var key = PrivateKey();
22 *
23 * // encode into wallet export format
24 * var exported = key.toWIF();
25 *
26 * // instantiate from the exported (and saved) private key
27 * var imported = PrivateKey.fromWIF(exported);
28 * ```
29 *
30 * @param {string} data - The encoded data in various formats
31 * @param {Network|string=} network - a {@link Network} object, or a string with the network name
32 * @returns {PrivateKey} A new valid instance of an PrivateKey
33 * @constructor
34 */
35function PrivateKey(data, network) {
36 /* jshint maxstatements: 20 */
37 /* jshint maxcomplexity: 8 */
38 if (!(this instanceof PrivateKey)) {
39 return new PrivateKey(data, network);
40 }
41 if (data instanceof PrivateKey) {
42 return data;
43 }
44
45 var info = this._classifyArguments(data, network);
46
47 // validation
48 if (!info.bn || info.bn.cmp(new BN(0)) === 0){
49 throw new TypeError('Number can not be equal to zero, undefined, null or false');
50 }
51 if (!info.bn.lt(Point.getN())) {
52 throw new TypeError('Number must be less than N');
53 }
54 if (typeof(info.network) === 'undefined') {
55 throw new TypeError('Must specify the network ("livenet" or "testnet")');
56 }
57
58 JSUtil.defineImmutable(this, {
59 bn: info.bn,
60 compressed: info.compressed,
61 network: info.network
62 });
63
64 Object.defineProperty(this, 'publicKey', {
65 configurable: false,
66 enumerable: true,
67 get: this.toPublicKey.bind(this)
68 });
69
70 return this;
71
72};
73
74/**
75 * Internal helper to instantiate PrivateKey internal `info` object from
76 * different kinds of arguments passed to the constructor.
77 *
78 * @param {*} data
79 * @param {Network|string=} network - a {@link Network} object, or a string with the network name
80 * @return {Object}
81 */
82PrivateKey.prototype._classifyArguments = function(data, network) {
83 /* jshint maxcomplexity: 10 */
84 var info = {
85 compressed: true,
86 network: network ? Networks.get(network) : Networks.defaultNetwork
87 };
88
89 // detect type of data
90 if (lodash.isUndefined(data) || lodash.isNull(data)){
91 info.bn = PrivateKey._getRandomBN();
92 } else if (data instanceof BN) {
93 info.bn = data;
94 } else if (data instanceof Buffer || data instanceof Uint8Array) {
95 info = PrivateKey._transformBuffer(data, network);
96 } else if (data.bn && data.network){
97 info = PrivateKey._transformObject(data);
98 } else if (!network && Networks.get(data)) {
99 info.bn = PrivateKey._getRandomBN();
100 info.network = Networks.get(data);
101 } else if (typeof(data) === 'string'){
102 if (JSUtil.isHexa(data)) {
103 info.bn = new BN(new Buffer(data, 'hex'));
104 } else {
105 info = PrivateKey._transformWIF(data, network);
106 }
107 } else {
108 throw new TypeError('First argument is an unrecognized data type.');
109 }
110
111 return info;
112};
113
114/**
115 * Internal function to get a random Big Number (BN)
116 *
117 * @returns {BN} A new randomly generated BN
118 * @private
119 */
120PrivateKey._getRandomBN = function(){
121 var condition;
122 var bn;
123 do {
124 var privbuf = Random.getRandomBuffer(32);
125 bn = BN.fromBuffer(privbuf);
126 condition = bn.lt(Point.getN());
127 } while (!condition);
128 return bn;
129};
130
131/**
132 * Internal function to transform a WIF Buffer into a private key
133 *
134 * @param {Buffer} buf - An WIF string
135 * @param {Network|string=} network - a {@link Network} object, or a string with the network name
136 * @returns {Object} An object with keys: bn, network and compressed
137 * @private
138 */
139PrivateKey._transformBuffer = function(buf, network) {
140 var info = {};
141
142 if (buf.length === 32) {
143 return PrivateKey._transformBNBuffer(buf, network);
144 }
145
146 // Buffer defined network must be valid.
147 if (!Networks.get(buf[0], 'prefix.privatekey')) {
148 throw new Error('Invalid network');
149 }
150
151 var n;
152 if (!network) {
153 // Prefer the default network if the buffer matches it. For example, BTC and BCH share version bytes, if
154 // BCH is desired then BCH must be specified in 'network' otherwise the default is selected (BTC).
155 if (buf[0] == Networks.defaultNetwork.prefix.privatekey) {
156 n = Networks.defaultNetwork;
157 } else {
158 n = Networks.get(buf[0], 'prefix.privatekey');
159 }
160 } else {
161 n = Networks.get(network);
162 }
163
164 if (!n) {
165 throw new Error('Invalid network');
166 }
167
168 // Several networks share the same one byte private key prefix so it's not possible to discern the specific
169 // network when this prefix is detected in the buffer. This livenet 'shared' prefix and the testnet 'shared'
170 // prefix is defined by the bitcoin standard WIF one byte prefix.
171 //
172 // Determine whether or not there is a network mismatch between the specified key string and the specified
173 // network (if provided).
174 //
175 // If there is a difference in the one byte private key prefixes then there is network mismatch.
176 if (buf[0] != n.prefix.privatekey) {
177 throw new TypeError('Private key network mismatch');
178 }
179
180 // If the buffer indicates a shared prefix AND a network is specified then set the network as specified
181 // (pass it through).
182 if (Networks.isSharedPrefix(buf[0], 'prefix.privatekey') && network) {
183 n = Networks.get(network);
184 }
185
186 if (buf.length === 1 + 32 + 1 && buf[1 + 32 + 1 - 1] === 1) {
187 info.compressed = true;
188 } else if (buf.length === 1 + 32) {
189 info.compressed = false;
190 } else {
191 throw new Error('Length of buffer must be 33 (uncompressed) or 34 (compressed)');
192 }
193
194 info.bn = BN.fromBuffer(buf.slice(1, 32 + 1));
195 info.network = n;
196
197 return info;
198};
199
200/**
201 * Internal function to transform a BN buffer into a private key
202 *
203 * @param {Buffer} buf
204 * @param {Network|string=} network - a {@link Network} object, or a string with the network name
205 * @returns {object} an Object with keys: bn, network, and compressed
206 * @private
207 */
208PrivateKey._transformBNBuffer = function(buf, network) {
209 var info = {};
210 info.network = Networks.get(network) || Networks.defaultNetwork;
211 info.bn = BN.fromBuffer(buf);
212 info.compressed = false;
213 return info;
214};
215
216/**
217 * Internal function to transform a WIF string into a private key
218 *
219 * @param {string} buf - An WIF string
220 * @returns {Object} An object with keys: bn, network and compressed
221 * @private
222 */
223PrivateKey._transformWIF = function(str, network) {
224 return PrivateKey._transformBuffer(Base58Check.decode(str), network);
225};
226
227/**
228 * Instantiate a PrivateKey from a Buffer with the DER or WIF representation
229 *
230 * @param {Buffer} arg
231 * @param {Network} network
232 * @return {PrivateKey}
233 */
234PrivateKey.fromBuffer = function(arg, network) {
235 return new PrivateKey(arg, network);
236};
237
238/**
239 * Internal function to transform a JSON string on plain object into a private key
240 * return this.
241 *
242 * @param {string} json - A JSON string or plain object
243 * @returns {Object} An object with keys: bn, network and compressed
244 * @private
245 */
246PrivateKey._transformObject = function(json) {
247 var bn = new BN(json.bn, 'hex');
248 var network = Networks.get(json.network);
249 return {
250 bn: bn,
251 network: network,
252 compressed: json.compressed
253 };
254};
255
256/**
257 * Instantiate a PrivateKey from a WIF string
258 *
259 * @param {string} str - The WIF encoded private key string
260 * @returns {PrivateKey} A new valid instance of PrivateKey
261 */
262PrivateKey.fromString = PrivateKey.fromWIF = function(str, network) {
263 $.checkArgument(lodash.isString(str), 'First argument is expected to be a string.');
264 return new PrivateKey(str, network);
265};
266
267/**
268 * Instantiate a PrivateKey from a plain JavaScript object
269 *
270 * @param {Object} obj - The output from privateKey.toObject()
271 */
272PrivateKey.fromObject = function(obj) {
273 $.checkArgument(lodash.isObject(obj), 'First argument is expected to be an object.');
274 return new PrivateKey(obj);
275};
276
277/**
278 * Instantiate a PrivateKey from random bytes
279 *
280 * @param {string=} network - Network name
281 * @returns {PrivateKey} A new valid instance of PrivateKey
282 */
283PrivateKey.fromRandom = function(network) {
284 var bn = PrivateKey._getRandomBN();
285 return new PrivateKey(bn, network);
286};
287
288/**
289 * Check if there would be any errors when initializing a PrivateKey
290 *
291 * @param {string} data - The encoded data in various formats
292 * @param {string=} network - Network name
293 * @returns {null|Error} An error if exists
294 */
295
296PrivateKey.getValidationError = function(data, network) {
297 var error;
298 try {
299 /* jshint nonew: false */
300 new PrivateKey(data, network);
301 } catch (e) {
302 error = e;
303 }
304 return error;
305};
306
307/**
308 * Check if the parameters are valid
309 *
310 * @param {string} data - The encoded data in various formats
311 * @param {string=} network - Network name
312 * @returns {Boolean} If the private key is would be valid
313 */
314PrivateKey.isValid = function(data, network){
315 if (!data) {
316 return false;
317 }
318 return !PrivateKey.getValidationError(data, network);
319};
320
321/**
322 * Will output the PrivateKey encoded as hex string
323 *
324 * @returns {string}
325 */
326PrivateKey.prototype.toString = function() {
327 return this.toBuffer().toString('hex');
328};
329
330/**
331 * Will output the PrivateKey to a WIF string
332 *
333 * @returns {string} A WIP representation of the private key
334 */
335PrivateKey.prototype.toWIF = function() {
336 var network = this.network;
337 var compressed = this.compressed;
338
339 var buf;
340 if (compressed) {
341 buf = Buffer.concat([new Buffer([network.prefix.privatekey]), this.bn.toBuffer({size: 32}), new Buffer([0x01])]);
342 } else {
343 buf = Buffer.concat([new Buffer([network.prefix.privatekey]), this.bn.toBuffer({size: 32})]);
344 }
345
346 return Base58Check.encode(buf);
347};
348
349/**
350 * Will return the private key as a BN instance
351 *
352 * @returns {BN} A BN instance of the private key
353 */
354PrivateKey.prototype.toBigNumber = function(){
355 return this.bn;
356};
357
358/**
359 * Will return the private key as a BN buffer
360 *
361 * @returns {Buffer} A buffer of the private key
362 */
363PrivateKey.prototype.toBuffer = function(){
364 // TODO: use `return this.bn.toBuffer({ size: 32 })` in v1.0.0
365 return this.bn.toBuffer();
366};
367
368/**
369 * Will return the private key as a BN buffer without leading zero padding
370 *
371 * @returns {Buffer} A buffer of the private key
372 */
373PrivateKey.prototype.toBufferNoPadding = function() {
374 return this.bn.toBuffer();
375};
376
377/**
378 * Will return the corresponding public key
379 *
380 * @returns {PublicKey} A public key generated from the private key
381 */
382PrivateKey.prototype.toPublicKey = function() {
383 if (!this._pubkey) {
384 this._pubkey = PublicKey.fromPrivateKey(this);
385 }
386 return this._pubkey;
387};
388
389PrivateKey.prototype.toAESKey = function() {
390 return Hash.sha256(this.toBuffer()).slice(0, 16).toString('base64');
391};
392
393/**
394 * @returns {Object} A plain object representation
395 */
396PrivateKey.prototype.toObject = PrivateKey.prototype.toJSON = function toObject() {
397 return {
398 bn: this.bn.toString('hex'),
399 compressed: this.compressed,
400 network: this.network.toString()
401 };
402};
403
404/**
405 * Will return a string formatted for the console
406 *
407 * @returns {string} Private key
408 */
409PrivateKey.prototype.inspect = function() {
410 var uncompressed = !this.compressed ? ', uncompressed' : '';
411 return '<PrivateKey: ' + this.toString() + ', network: ' + this.network + uncompressed + '>';
412};
413
414module.exports = PrivateKey;