Completed
Pull Request — master (#91)
by thomas
01:09
created

Wallet.getAddressAndType   F

Complexity

Conditions 17
Paths 729

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
c 0
b 0
f 0
nc 729
dl 0
loc 65
rs 3.0769
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Wallet.getAddressAndType often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
var _ = require('lodash');
2
var assert = require('assert');
3
var q = require('q');
4
var async = require('async');
5
var bitcoin = require('bitcoinjs-lib');
6
var bitcoinMessage = require('bitcoinjs-message');
7
var blocktrail = require('./blocktrail');
8
var CryptoJS = require('crypto-js');
9
var Encryption = require('./encryption');
10
var EncryptionMnemonic = require('./encryption_mnemonic');
11
var SizeEstimation = require('./size_estimation');
12
var bip39 = require('bip39');
13
14
var SignMode = {
15
    SIGN: "sign",
16
    DONT_SIGN: "dont_sign"
17
};
18
19
/**
20
 *
21
 * @param sdk                   APIClient       SDK instance used to do requests
22
 * @param identifier            string          identifier of the wallet
23
 * @param walletVersion         string
24
 * @param primaryMnemonic       string          primary mnemonic
25
 * @param encryptedPrimarySeed
26
 * @param encryptedSecret
27
 * @param primaryPublicKeys     string          primary mnemonic
28
 * @param backupPublicKey       string          BIP32 master pubKey M/
29
 * @param blocktrailPublicKeys  array           list of blocktrail pubKeys indexed by keyIndex
30
 * @param keyIndex              int             key index to use
31
 * @param segwit                int             segwit toggle from server
32
 * @param testnet               bool            testnet
33
 * @param checksum              string
34
 * @param upgradeToKeyIndex     int
35
 * @param useNewCashAddr        bool            flag to opt in to bitcoin cash cashaddr's
36
 * @param bypassNewAddressCheck bool            flag to indicate if wallet should/shouldn't derive new address locally to verify api
37
 * @constructor
38
 * @internal
39
 */
40
var Wallet = function(
41
    sdk,
42
    identifier,
43
    walletVersion,
44
    primaryMnemonic,
45
    encryptedPrimarySeed,
46
    encryptedSecret,
47
    primaryPublicKeys,
48
    backupPublicKey,
49
    blocktrailPublicKeys,
50
    keyIndex,
51
    segwit,
52
    testnet,
53
    checksum,
54
    upgradeToKeyIndex,
55
    useNewCashAddr,
56
    bypassNewAddressCheck
57
) {
58
    /* jshint -W071 */
59
    var self = this;
60
61
    self.sdk = sdk;
62
    self.identifier = identifier;
63
    self.walletVersion = walletVersion;
64
    self.locked = true;
65
    self.bypassNewAddressCheck = !!bypassNewAddressCheck;
66
    self.bitcoinCash = self.sdk.bitcoinCash;
67
    self.segwit = !!segwit;
68
    self.useNewCashAddr = !!useNewCashAddr;
69
    assert(!self.segwit || !self.bitcoinCash);
70
71
    self.testnet = testnet;
72
    if (self.bitcoinCash) {
73
        if (self.testnet) {
74
            self.network = bitcoin.networks.bitcoincashtestnet;
75
        } else {
76
            self.network = bitcoin.networks.bitcoincash;
77
        }
78
    } else {
79
        if (self.testnet) {
80
            self.network = bitcoin.networks.testnet;
81
        } else {
82
            self.network = bitcoin.networks.bitcoin;
83
        }
84
    }
85
86
    assert(backupPublicKey instanceof bitcoin.HDNode);
87
    assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; }));
88
    assert(_.every(blocktrailPublicKeys, function(blocktrailPublicKey) { return blocktrailPublicKey instanceof bitcoin.HDNode; }));
89
90
    // v1
91
    self.primaryMnemonic = primaryMnemonic;
92
93
    // v2 & v3
94
    self.encryptedPrimarySeed = encryptedPrimarySeed;
95
    self.encryptedSecret = encryptedSecret;
96
97
    self.primaryPrivateKey = null;
98
    self.backupPrivateKey = null;
99
100
    self.backupPublicKey = backupPublicKey;
101
    self.blocktrailPublicKeys = blocktrailPublicKeys;
102
    self.primaryPublicKeys = primaryPublicKeys;
103
    self.keyIndex = keyIndex;
104
105
    if (!self.bitcoinCash) {
106
        if (self.segwit) {
107
            self.chain = Wallet.CHAIN_BTC_DEFAULT;
108
            self.changeChain = Wallet.CHAIN_BTC_SEGWIT;
109
        } else {
110
            self.chain = Wallet.CHAIN_BTC_DEFAULT;
111
            self.changeChain = Wallet.CHAIN_BTC_DEFAULT;
112
        }
113
    } else {
114
        self.chain = Wallet.CHAIN_BCC_DEFAULT;
115
        self.changeChain = Wallet.CHAIN_BCC_DEFAULT;
116
    }
117
118
    self.checksum = checksum;
119
    self.upgradeToKeyIndex = upgradeToKeyIndex;
120
121
    self.secret = null;
122
    self.seedHex = null;
123
};
124
125
Wallet.WALLET_VERSION_V1 = 'v1';
126
Wallet.WALLET_VERSION_V2 = 'v2';
127
Wallet.WALLET_VERSION_V3 = 'v3';
128
129
Wallet.WALLET_ENTROPY_BITS = 256;
130
131
Wallet.OP_RETURN = 'opreturn';
132
Wallet.DATA = Wallet.OP_RETURN; // alias
133
134
Wallet.PAY_PROGRESS_START = 0;
135
Wallet.PAY_PROGRESS_COIN_SELECTION = 10;
136
Wallet.PAY_PROGRESS_CHANGE_ADDRESS = 20;
137
Wallet.PAY_PROGRESS_SIGN = 30;
138
Wallet.PAY_PROGRESS_SEND = 40;
139
Wallet.PAY_PROGRESS_DONE = 100;
140
141
Wallet.CHAIN_BTC_DEFAULT = 0;
142
Wallet.CHAIN_BTC_SEGWIT = 2;
143
Wallet.CHAIN_BCC_DEFAULT = 1;
144
145
Wallet.FEE_STRATEGY_FORCE_FEE = blocktrail.FEE_STRATEGY_FORCE_FEE;
146
Wallet.FEE_STRATEGY_BASE_FEE = blocktrail.FEE_STRATEGY_BASE_FEE;
147
Wallet.FEE_STRATEGY_OPTIMAL = blocktrail.FEE_STRATEGY_OPTIMAL;
148
Wallet.FEE_STRATEGY_LOW_PRIORITY = blocktrail.FEE_STRATEGY_LOW_PRIORITY;
149
Wallet.FEE_STRATEGY_MIN_RELAY_FEE = blocktrail.FEE_STRATEGY_MIN_RELAY_FEE;
150
151
Wallet.prototype.isSegwit = function() {
152
    return !!this.segwit;
153
};
154
155
Wallet.prototype.unlock = function(options, cb) {
156
    var self = this;
157
158
    var deferred = q.defer();
159
    deferred.promise.nodeify(cb);
160
161
    // avoid modifying passed options
162
    options = _.merge({}, options);
163
164
    q.fcall(function() {
165
        switch (self.walletVersion) {
166
            case Wallet.WALLET_VERSION_V1:
0 ignored issues
show
Bug introduced by
The variable Wallet seems to be never declared. If this is a global, consider adding a /** global: Wallet */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
167
                return self.unlockV1(options);
168
169
            case Wallet.WALLET_VERSION_V2:
170
                return self.unlockV2(options);
171
172
            case Wallet.WALLET_VERSION_V3:
173
                return self.unlockV3(options);
174
175
            default:
176
                return q.reject(new blocktrail.WalletInitError("Invalid wallet version"));
177
        }
178
    }).then(
179
        function(primaryPrivateKey) {
180
            self.primaryPrivateKey = primaryPrivateKey;
181
182
            // create a checksum of our private key which we'll later use to verify we used the right password
183
            var checksum = self.primaryPrivateKey.getAddress();
184
185
            // check if we've used the right passphrase
186
            if (checksum !== self.checksum) {
187
                throw new blocktrail.WalletChecksumError("Generated checksum [" + checksum + "] does not match " +
188
                    "[" + self.checksum + "], most likely due to incorrect password");
189
            }
190
191
            self.locked = false;
192
193
            // if the response suggests we should upgrade to a different blocktrail cosigning key then we should
194
            if (typeof self.upgradeToKeyIndex !== "undefined" && self.upgradeToKeyIndex !== null) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if typeof self.upgradeToKey...radeToKeyIndex !== null is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
195
                return self.upgradeKeyIndex(self.upgradeToKeyIndex);
196
            }
197
        }
198
    ).then(
199
        function(r) {
200
            deferred.resolve(r);
201
        },
202
        function(e) {
203
            deferred.reject(e);
204
        }
205
    );
206
207
    return deferred.promise;
208
};
209
210
Wallet.prototype.unlockV1 = function(options) {
211
    var self = this;
212
213
    options.primaryMnemonic = typeof options.primaryMnemonic !== "undefined" ? options.primaryMnemonic : self.primaryMnemonic;
214
    options.secretMnemonic = typeof options.secretMnemonic !== "undefined" ? options.secretMnemonic : self.secretMnemonic;
215
216
    return self.sdk.resolvePrimaryPrivateKeyFromOptions(options)
217
        .then(function(options) {
218
            self.primarySeed = options.primarySeed;
219
220
            return options.primaryPrivateKey;
221
        });
222
};
223
224
Wallet.prototype.unlockV2 = function(options, cb) {
225
    var self = this;
226
227
    var deferred = q.defer();
228
    deferred.promise.nodeify(cb);
229
230
    deferred.resolve(q.fcall(function() {
231
        /* jshint -W071, -W074 */
232
        options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed;
233
        options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret;
234
235
        if (options.secret) {
236
            self.secret = options.secret;
237
        }
238
239
        if (options.primaryPrivateKey) {
240
            throw new blocktrail.WalletDecryptError("specifying primaryPrivateKey has been deprecated");
241
        }
242
243
        if (options.primarySeed) {
244
            self.primarySeed = options.primarySeed;
245
        } else if (options.secret) {
246
            try {
247
                self.primarySeed = new Buffer(
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
248
                    CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedPrimarySeed), self.secret).toString(CryptoJS.enc.Utf8), 'base64');
249
                if (!self.primarySeed.length) {
250
                    throw new Error();
251
                }
252
            } catch (e) {
253
                throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
254
            }
255
256
        } else {
257
            // avoid conflicting options
258
            if (options.passphrase && options.password) {
259
                throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
260
            }
261
            // normalize passphrase/password
262
            options.passphrase = options.passphrase || options.password;
263
264
            try {
265
                self.secret = CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedSecret), options.passphrase).toString(CryptoJS.enc.Utf8);
266
                if (!self.secret.length) {
267
                    throw new Error();
268
                }
269
            } catch (e) {
270
                throw new blocktrail.WalletDecryptError("Failed to decrypt secret");
271
            }
272
            try {
273
                self.primarySeed = new Buffer(
274
                    CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedPrimarySeed), self.secret).toString(CryptoJS.enc.Utf8), 'base64');
275
                if (!self.primarySeed.length) {
276
                    throw new Error();
277
                }
278
            } catch (e) {
279
                throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
280
            }
281
        }
282
283
        return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
284
    }));
285
286
    return deferred.promise;
287
};
288
289
Wallet.prototype.unlockV3 = function(options, cb) {
290
    var self = this;
291
292
    var deferred = q.defer();
293
    deferred.promise.nodeify(cb);
294
295
    deferred.resolve(q.fcall(function() {
296
        return q.when()
297
            .then(function() {
298
                /* jshint -W071, -W074 */
299
                options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed;
300
                options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret;
301
302
                if (options.secret) {
303
                    self.secret = options.secret;
304
                }
305
306
                if (options.primaryPrivateKey) {
307
                    throw new blocktrail.WalletInitError("specifying primaryPrivateKey has been deprecated");
308
                }
309
310
                if (options.primarySeed) {
311
                    self.primarySeed = options.primarySeed;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
312
                } else if (options.secret) {
313
                    return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
314
                        .then(function(primarySeed) {
315
                            self.primarySeed = primarySeed;
316
                        }, function() {
317
                            throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
318
                        });
319
                } else {
320
                    // avoid conflicting options
321
                    if (options.passphrase && options.password) {
322
                        throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
323
                    }
324
                    // normalize passphrase/password
325
                    options.passphrase = options.passphrase || options.password;
326
                    delete options.password;
327
328
                    return self.sdk.promisedDecrypt(new Buffer(options.encryptedSecret, 'base64'), new Buffer(options.passphrase))
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
329
                        .then(function(secret) {
330
                            self.secret = secret;
331
                        }, function() {
332
                            throw new blocktrail.WalletDecryptError("Failed to decrypt secret");
333
                        })
334
                        .then(function() {
335
                            return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
336
                                .then(function(primarySeed) {
337
                                    self.primarySeed = primarySeed;
338
                                }, function() {
339
                                    throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
340
                                });
341
                        });
342
                }
343
            })
344
            .then(function() {
345
                return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
346
            })
347
        ;
348
    }));
349
350
    return deferred.promise;
351
};
352
353
Wallet.prototype.lock = function() {
354
    var self = this;
355
356
    self.secret = null;
357
    self.primarySeed = null;
358
    self.primaryPrivateKey = null;
359
    self.backupPrivateKey = null;
360
361
    self.locked = true;
362
};
363
364
/**
365
 * upgrade wallet to V3 encryption scheme
366
 *
367
 * @param passphrase is required again to reencrypt the data, important that it's the correct password!!!
368
 * @param cb
369
 * @returns {promise}
370
 */
371
Wallet.prototype.upgradeToV3 = function(passphrase, cb) {
372
    var self = this;
373
374
    var deferred = q.defer();
375
    deferred.promise.nodeify(cb);
376
377
    q.when(true)
378
        .then(function() {
379
            if (self.locked) {
380
                throw new blocktrail.WalletLockedError("Wallet needs to be unlocked to upgrade");
381
            }
382
383
            if (self.walletVersion === Wallet.WALLET_VERSION_V3) {
384
                throw new blocktrail.WalletUpgradeError("Wallet is already V3");
385
            } else if (self.walletVersion === Wallet.WALLET_VERSION_V2) {
386
                return self._upgradeV2ToV3(passphrase, deferred.notify.bind(deferred));
387
            } else if (self.walletVersion === Wallet.WALLET_VERSION_V1) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if self.walletVersion === Wallet.WALLET_VERSION_V1 is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
388
                return self._upgradeV1ToV3(passphrase, deferred.notify.bind(deferred));
389
            }
390
        })
391
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
392
393
    return deferred.promise;
394
};
395
396
Wallet.prototype._upgradeV2ToV3 = function(passphrase, notify) {
397
    var self = this;
398
399
    return q.when(true)
400
        .then(function() {
401
            var options = {
402
                storeDataOnServer: true,
403
                passphrase: passphrase,
404
                primarySeed: self.primarySeed,
405
                recoverySecret: false // don't create new recovery secret, V2 already has ones
406
            };
407
408
            return self.sdk.produceEncryptedDataV3(options, notify || function noop() {})
409
                .then(function(options) {
410
                    return self.sdk.updateWallet(self.identifier, {
411
                        encrypted_primary_seed: options.encryptedPrimarySeed.toString('base64'),
412
                        encrypted_secret: options.encryptedSecret.toString('base64'),
413
                        wallet_version: Wallet.WALLET_VERSION_V3
414
                    }).then(function() {
415
                        self.secret = options.secret;
416 View Code Duplication
                        self.encryptedPrimarySeed = options.encryptedPrimarySeed;
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
417
                        self.encryptedSecret = options.encryptedSecret;
418
                        self.walletVersion = Wallet.WALLET_VERSION_V3;
419
420
                        return self;
421
                    });
422
                });
423
        });
424
425
};
426
427
Wallet.prototype._upgradeV1ToV3 = function(passphrase, notify) {
428
    var self = this;
429
430
    return q.when(true)
431
        .then(function() {
432
            var options = {
433
                storeDataOnServer: true,
434
                passphrase: passphrase,
435
                primarySeed: self.primarySeed
436
            };
437
438
            return self.sdk.produceEncryptedDataV3(options, notify || function noop() {})
439
                .then(function(options) {
440
                    // store recoveryEncryptedSecret for printing on backup sheet
441
                    self.recoveryEncryptedSecret = options.recoveryEncryptedSecret;
442
443
                    return self.sdk.updateWallet(self.identifier, {
444
                        primary_mnemonic: '',
445
                        encrypted_primary_seed: options.encryptedPrimarySeed.toString('base64'),
446
                        encrypted_secret: options.encryptedSecret.toString('base64'),
447
                        recovery_secret: options.recoverySecret.toString('hex'),
448
                        wallet_version: Wallet.WALLET_VERSION_V3
449
                    }).then(function() {
450
                        self.secret = options.secret;
451
                        self.encryptedPrimarySeed = options.encryptedPrimarySeed;
452
                        self.encryptedSecret = options.encryptedSecret;
453
                        self.walletVersion = Wallet.WALLET_VERSION_V3;
454
455
                        return self;
456
                    });
457
                });
458
        });
459
};
460
461
Wallet.prototype.doPasswordChange = function(newPassword) {
462
    var self = this;
463
464
    return q.when(null)
465
        .then(function() {
466
467
            if (self.walletVersion === Wallet.WALLET_VERSION_V1) {
468
                throw new blocktrail.WalletLockedError("Wallet version does not support password change!");
469
            }
470
471
            if (self.locked) {
472
                throw new blocktrail.WalletLockedError("Wallet needs to be unlocked to change password");
473
            }
474
475
            if (!self.secret) {
476
                throw new blocktrail.WalletLockedError("No secret");
477
            }
478
479
            var newEncryptedSecret;
480
            var newEncrypedWalletSecretMnemonic;
481
            if (self.walletVersion === Wallet.WALLET_VERSION_V2) {
482
                newEncryptedSecret = CryptoJS.AES.encrypt(self.secret, newPassword).toString(CryptoJS.format.OpenSSL);
483
                newEncrypedWalletSecretMnemonic = bip39.entropyToMnemonic(blocktrail.convert(newEncryptedSecret, 'base64', 'hex'));
484
485
            } else {
486
                if (typeof newPassword === "string") {
487
                    newPassword = new Buffer(newPassword);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
488
                } else {
489
                    if (!(newPassword instanceof Buffer)) {
490
                        throw new Error('New password must be provided as a string or a Buffer');
491
                    }
492
                }
493
494
                newEncryptedSecret = Encryption.encrypt(self.secret, newPassword);
495
                newEncrypedWalletSecretMnemonic = EncryptionMnemonic.encode(newEncryptedSecret);
496
497
                // It's a buffer, so convert it back to base64
498
                newEncryptedSecret = newEncryptedSecret.toString('base64');
499
            }
500
501
            return [newEncryptedSecret, newEncrypedWalletSecretMnemonic];
502
        });
503
};
504
505
Wallet.prototype.passwordChange = function(newPassword, cb) {
506
    var self = this;
507
508
    var deferred = q.defer();
509
    deferred.promise.nodeify(cb);
510
511
    q.fcall(function() {
512
        return self.doPasswordChange(newPassword)
513
            .then(function(r) {
514
                var newEncryptedSecret = r[0];
515
                var newEncrypedWalletSecretMnemonic = r[1];
516
517
                return self.sdk.updateWallet(self.identifier, {encrypted_secret: newEncryptedSecret}).then(function() {
518
                    self.encryptedSecret = newEncryptedSecret;
519
520
                    // backupInfo
521
                    return {
522
                        encryptedSecret: newEncrypedWalletSecretMnemonic
523
                    };
524
                });
525
            })
526
            .then(
527
                function(r) {
528
                    deferred.resolve(r);
529
                },
530
                function(e) {
531
                    deferred.reject(e);
532
                }
533
            );
534
    });
535
536
    return deferred.promise;
537
};
538
539
/**
540
 * get address for specified path
541
 *
542
 * @param path
543
 * @returns string
544
 */
545
Wallet.prototype.getAddressByPath = function(path) {
546
    return this.getWalletScriptByPath(path).address;
547
};
548
549
/**
550
 * get redeemscript for specified path
551
 *
552
 * @param path
553
 * @returns {Buffer}
554
 */
555
Wallet.prototype.getRedeemScriptByPath = function(path) {
556
    return this.getWalletScriptByPath(path).redeemScript;
557
};
558
559
/**
560
 * Generate scripts, and address.
561
 * @param path
562
 * @returns {{witnessScript: *, redeemScript: *, scriptPubKey, address: *}}
563
 */
564
Wallet.prototype.getWalletScriptByPath = function(path) {
565
    var self = this;
566
567
    // get derived primary key
568
    var derivedPrimaryPublicKey = self.getPrimaryPublicKey(path);
569
    // get derived blocktrail key
570
    var derivedBlocktrailPublicKey = self.getBlocktrailPublicKey(path);
571
    // derive the backup key
572
    var derivedBackupPublicKey = Wallet.deriveByPath(self.backupPublicKey, path.replace("'", ""), "M");
573
574
    // sort the pubkeys
575
    var pubKeys = Wallet.sortMultiSigKeys([
576
        derivedPrimaryPublicKey.keyPair.getPublicKeyBuffer(),
577
        derivedBackupPublicKey.keyPair.getPublicKeyBuffer(),
578
        derivedBlocktrailPublicKey.keyPair.getPublicKeyBuffer()
579
    ]);
580
581
    var multisig = bitcoin.script.multisig.output.encode(2, pubKeys);
582
    var scriptType = parseInt(path.split("/")[2]);
583
584
    var ws, rs;
585
    if (this.network !== "bitcoincash" && scriptType === Wallet.CHAIN_BTC_SEGWIT) {
586
        ws = multisig;
587
        rs = bitcoin.script.witnessScriptHash.output.encode(bitcoin.crypto.sha256(ws));
588
    } else {
589
        ws = null;
590
        rs = multisig;
591
    }
592
593
    var spk = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(rs));
594
    var addr = bitcoin.address.fromOutputScript(spk, this.network, self.useNewCashAddr);
595
596
    return {
597
        witnessScript: ws,
598
        redeemScript: rs,
599
        scriptPubKey: spk,
600
        address: addr
601
    };
602
};
603
604
/**
605
 * get primary public key by path
606
 *  first level of the path is used as keyIndex to find the correct key in the dict
607
 *
608
 * @param path  string
609
 * @returns {bitcoin.HDNode}
610
 */
611
Wallet.prototype.getPrimaryPublicKey = function(path) {
612
    var self = this;
613
614
    path = path.replace("m", "M");
615
616
    var keyIndex = path.split("/")[1].replace("'", "");
617
618
    if (!self.primaryPublicKeys[keyIndex]) {
619
        if (self.primaryPrivateKey) {
620
            self.primaryPublicKeys[keyIndex] = Wallet.deriveByPath(self.primaryPrivateKey, "M/" + keyIndex + "'", "m");
621
        } else {
622
            throw new blocktrail.KeyPathError("Wallet.getPrimaryPublicKey keyIndex (" + keyIndex + ") is unknown to us");
623
        }
624
    }
625
626
    var primaryPublicKey = self.primaryPublicKeys[keyIndex];
627
    return Wallet.deriveByPath(primaryPublicKey, path, "M/" + keyIndex + "'");
628
};
629
630
/**
631
 * get blocktrail public key by path
632
 *  first level of the path is used as keyIndex to find the correct key in the dict
633
 *
634
 * @param path  string
635
 * @returns {bitcoin.HDNode}
636
 */
637
Wallet.prototype.getBlocktrailPublicKey = function(path) {
638
    var self = this;
639
640
    path = path.replace("m", "M");
641
642
    var keyIndex = path.split("/")[1].replace("'", "");
643
644
    if (!self.blocktrailPublicKeys[keyIndex]) {
645
        throw new blocktrail.KeyPathError("Wallet.getBlocktrailPublicKey keyIndex (" + keyIndex + ") is unknown to us");
646
    }
647
648
    var blocktrailPublicKey = self.blocktrailPublicKeys[keyIndex];
649
650
    return Wallet.deriveByPath(blocktrailPublicKey, path, "M/" + keyIndex + "'");
651
};
652
653
/**
654
 * upgrade wallet to different blocktrail cosign key
655
 *
656
 * @param keyIndex  int
657
 * @param [cb]      function
658
 * @returns {q.Promise}
659
 */
660
Wallet.prototype.upgradeKeyIndex = function(keyIndex, cb) {
661
    var self = this;
662
663
    var deferred = q.defer();
664
    deferred.promise.nodeify(cb);
665
666
    if (self.locked) {
667
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to upgrade key index"));
668
        return deferred.promise;
669
    }
670
671
    var primaryPublicKey = self.primaryPrivateKey.deriveHardened(keyIndex).neutered();
672
673
    deferred.resolve(
674
        self.sdk.upgradeKeyIndex(self.identifier, keyIndex, [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"])
675
            .then(function(result) {
676
                self.keyIndex = keyIndex;
677
                _.forEach(result.blocktrail_public_keys, function(publicKey, keyIndex) {
678
                    self.blocktrailPublicKeys[keyIndex] = bitcoin.HDNode.fromBase58(publicKey[0], self.network);
679
                });
680
681
                self.primaryPublicKeys[keyIndex] = primaryPublicKey;
682
683
                return true;
684
            })
685
    );
686
687
    return deferred.promise;
688
};
689
690
/**
691
 * generate a new derived private key and return the new address for it
692
 *
693
 * @param [chainIdx] int
694
 * @param [cb]  function        callback(err, address)
695
 * @returns {q.Promise}
696
 */
697
Wallet.prototype.getNewAddress = function(chainIdx, cb) {
698
    var self = this;
699
700
    // chainIdx is optional
701
    if (typeof chainIdx === "function") {
702
        cb = chainIdx;
703
        chainIdx = null;
704
    }
705
706
    var deferred = q.defer();
707
    deferred.promise.spreadNodeify(cb);
708
709
    // Only enter if it's not an integer
710
    if (chainIdx !== parseInt(chainIdx, 10)) {
711
        // deal with undefined or null, assume defaults
712
        if (typeof chainIdx === "undefined" || chainIdx === null) {
713
            chainIdx = self.chain;
714
        } else {
715
            // was a variable but not integer
716
            deferred.reject(new Error("Invalid chain index"));
717
            return deferred.promise;
718
        }
719
    }
720
721
    deferred.resolve(
722
        self.sdk.getNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx)
723
            .then(function(newDerivation) {
724
                var path = newDerivation.path;
725
                var addressFromServer = newDerivation.address;
726
                if (!self.bypassNewAddressCheck) {
727
                    var decodedFromServer;
728
729
                    try {
730
                        // Decode the address the serer gave us
731
                        decodedFromServer = self.decodeAddress(addressFromServer)
732
                    } catch (e) {
733
                        throw new blocktrail.WalletAddressError("Failed to decode address [" + newDerivation.address + "]")
734
                    }
735
736
                    // We need to reproduce this address with the same path,
737
                    // but the server (for BCH cashaddrs) uses base58?
738
                    var verifyAddress = self.getAddressByPath(newDerivation.path);
739
740
                    // If this occasion arises:
741
                    if ("cashAddrPrefix" in self.network && self.useNewCashAddr && decodedFromServer.type === "base58") {
742
                        // Decode our the address we produced for the path
743
                        var decodeOurs;
744
                        try {
745
                            decodeOurs = self.decodeAddress(verifyAddress);
746
                        } catch (e) {
747
                            throw new blocktrail.WalletAddressError("Error while verifying address from server [" + e.message + "]")
748
                        }
749
750
                        // Peek beyond the encoding - the hashes must match at least
751
                        if (decodeOurs.decoded.hash.toString('hex') !== decodedFromServer.decoded.hash.toString('hex')) {
752
                            throw new blocktrail.WalletAddressError("Failed to verify legacy address [hash mismatch]")
753
                        }
754
755
                        var matchedP2PKH = decodeOurs.decoded.version === bitcoin.script.types.P2PKH && decodedFromServer.decoded.version === self.network.pubKeyHash;
756
                        var matchedP2SH = decodeOurs.decoded.version === bitcoin.script.types.P2SH && decodedFromServer.decoded.version === self.network.scriptHash;
757
                        if (!(matchedP2PKH || matchedP2SH)) {
758
                            throw new blocktrail.WalletAddressError("Failed to verify legacy address [prefix mismatch]");
759
                        }
760
761
                        // We are satisfied that the address is for the same
762
                        // destination, so substitute addressFromServer with our
763
                        // 'reencoded' form.
764
                        addressFromServer = decodeOurs.address
765
                    }
766
767
                    // debug check
768
                    if (verifyAddress !== addressFromServer) {
769
                        throw new blocktrail.WalletAddressError("Failed to verify address [" + newDerivation.address + "] !== [" + addressFromServer + "]");
770
                    }
771
                }
772
773
                return [addressFromServer, path];
774
            })
775
    );
776
777
    return deferred.promise;
778
};
779
780
/**
781
 * get the balance for the wallet
782
 *
783
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
784
 * @returns {q.Promise}
785
 */
786
Wallet.prototype.getBalance = function(cb) {
787
    var self = this;
788
789 View Code Duplication
    var deferred = q.defer();
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
790
    deferred.promise.spreadNodeify(cb);
791
792
    deferred.resolve(
793
        self.sdk.getWalletBalance(self.identifier)
794
            .then(function(result) {
795
                return [result.confirmed, result.unconfirmed];
796
            })
797
    );
798
799
    return deferred.promise;
800
};
801
802
/**
803
 * get the balance for the wallet
804
 *
805
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
806
 * @returns {q.Promise}
807
 */
808
Wallet.prototype.getInfo = function(cb) {
809
    var self = this;
810
811
    var deferred = q.defer();
812
    deferred.promise.spreadNodeify(cb);
813
814
    deferred.resolve(
815
        self.sdk.getWalletBalance(self.identifier)
816
    );
817
818
    return deferred.promise;
819
};
820
821
/**
822
 * do wallet discovery (slow)
823
 *
824
 * @param [gap] int             gap limit
825
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
826
 * @returns {q.Promise}
827
 */
828
Wallet.prototype.doDiscovery = function(gap, cb) {
829
    var self = this;
830
831
    if (typeof gap === "function") {
832
        cb = gap;
833
        gap = null;
834
    }
835
836
    var deferred = q.defer();
837
    deferred.promise.spreadNodeify(cb);
838
839
    deferred.resolve(
840
        self.sdk.doWalletDiscovery(self.identifier, gap)
841
            .then(function(result) {
842
                return [result.confirmed, result.unconfirmed];
843
            })
844
    );
845
846
    return deferred.promise;
847
};
848
849
/**
850
 *
851
 * @param [force]   bool            ignore warnings (such as non-zero balance)
852
 * @param [cb]      function        callback(err, success)
853
 * @returns {q.Promise}
854
 */
855
Wallet.prototype.deleteWallet = function(force, cb) {
856
    var self = this;
857
858
    if (typeof force === "function") {
859
        cb = force;
860
        force = false;
861
    }
862
863
    var deferred = q.defer();
864
    deferred.promise.nodeify(cb);
865
866
    if (self.locked) {
867 View Code Duplication
        deferred.reject(new blocktrail.WalletDeleteError("Wallet needs to be unlocked to delete wallet"));
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
868
        return deferred.promise;
869
    }
870
871
    var checksum = self.primaryPrivateKey.getAddress();
872
    var privBuf = self.primaryPrivateKey.keyPair.d.toBuffer(32);
873
    var signature = bitcoinMessage.sign(checksum, self.network.messagePrefix, privBuf, true).toString('base64');
874
875
    deferred.resolve(
876
        self.sdk.deleteWallet(self.identifier, checksum, signature, force)
877
            .then(function(result) {
878
                return result.deleted;
879
            })
880
    );
881
882
    return deferred.promise;
883
};
884
885
/**
886
 * create, sign and send a transaction
887
 *
888
 * @param pay                   array       {'address': (int)value}     coins to send
889
 * @param [changeAddress]       bool        change address to use (auto generated if NULL)
890
 * @param [allowZeroConf]       bool        allow zero confirmation unspent outputs to be used in coin selection
891
 * @param [randomizeChangeIdx]  bool        randomize the index of the change output (default TRUE, only disable if you have a good reason to)
892
 * @param [feeStrategy]         string      defaults to Wallet.FEE_STRATEGY_OPTIMAL
893
 * @param [twoFactorToken]      string      2FA token
894
 * @param options
895
 * @param [cb]                  function    callback(err, txHash)
896
 * @returns {q.Promise}
897
 */
898
Wallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) {
899
900
    /* jshint -W071 */
901
    var self = this;
902
903
    if (typeof changeAddress === "function") {
904
        cb = changeAddress;
905
        changeAddress = null;
906
    } else if (typeof allowZeroConf === "function") {
907
        cb = allowZeroConf;
908
        allowZeroConf = false;
909
    } else if (typeof randomizeChangeIdx === "function") {
910
        cb = randomizeChangeIdx;
911
        randomizeChangeIdx = true;
912
    } else if (typeof feeStrategy === "function") {
913
        cb = feeStrategy;
914
        feeStrategy = null;
915
    } else if (typeof twoFactorToken === "function") {
916
        cb = twoFactorToken;
917
        twoFactorToken = null;
918
    } else if (typeof options === "function") {
919
        cb = options;
920
        options = {};
921
    }
922
923
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
924
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
925
    options = options || {};
926
    var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true;
927
928
    var deferred = q.defer();
929
    deferred.promise.nodeify(cb);
930
931
    if (self.locked) {
932
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to send coins"));
933
        return deferred.promise;
934
    }
935
936
    q.nextTick(function() {
937
        deferred.notify(Wallet.PAY_PROGRESS_START);
938
        self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options)
939
            .then(
940
            function(r) { return r; },
941
            function(e) { deferred.reject(e); },
942
            function(progress) {
943
                deferred.notify(progress);
944
            }
945
        )
946
            .spread(
947
            function(tx, utxos) {
948
949
                deferred.notify(Wallet.PAY_PROGRESS_SEND);
950
951
                var data = {
952
                    signed_transaction: tx.toHex(),
953
                    base_transaction: tx.__toBuffer(null, null, false).toString('hex')
954
                };
955
956
                return self.sendTransaction(data, utxos.map(function(utxo) { return utxo['path']; }), checkFee, twoFactorToken, options.prioboost)
957
                    .then(function(result) {
958
                        deferred.notify(Wallet.PAY_PROGRESS_DONE);
959
960
                        if (!result || !result['complete'] || result['complete'] === 'false') {
961
                            deferred.reject(new blocktrail.TransactionSignError("Failed to completely sign transaction"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
962
                        } else {
963
                            return result['txid'];
964
                        }
965
                    });
966
            },
967
            function(e) {
968
                throw e;
969
            }
970
        )
971
            .then(
972
            function(r) { deferred.resolve(r); },
973
            function(e) { deferred.reject(e); }
974
        )
975
        ;
976
    });
977
978
    return deferred.promise;
979
};
980
981
Wallet.prototype.decodeAddress = function(address) {
982
    return Wallet.getAddressAndType(address, this.network, this.useNewCashAddr);
983
};
984
985
Wallet.getAddressAndType = function(address, network, allowCashAddress) {
986
    var addr;
987
    var type;
988
    var err;
989
990
    if (network === bitcoin.networks.bitcoin || network === bitcoin.networks.testnet) {
991
        try {
992
            addr = bitcoin.address.fromBech32(address, network);
993
            err = null;
994
            type = "bech32";
995
996
        } catch (_err) {
997
            err = _err;
998
        }
999
1000
        if (!err) {
1001
            // Valid bech32 but invalid network immediately alerts
1002
            if (addr.prefix !== network.bech32) {
1003
                throw new blocktrail.InvalidAddressError("Address invalid on this network");
1004
            }
1005
        }
1006
    }
1007
1008
    if ('cashAddrPrefix' in network && allowCashAddress) {
1009
        try {
1010
            addr = bitcoin.address.fromBase32(address);
1011
            err = null;
1012
            type = "base32";
1013
        } catch (_err) {
1014
            err = _err;
1015
        }
1016
1017
        if (!err) {
1018
            // Valid base58 but invalid network immediately alerts
1019
            if (addr.prefix !== network.cashAddrPrefix) throw new Error(address + ' has an invalid prefix');
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1020
        }
1021
    }
1022
1023
    if (!addr) {
1024
        try {
1025
            addr = bitcoin.address.fromBase58Check(address);
1026
            err = null;
1027
            type = "base58";
1028
        } catch (_err) {
1029
            err = _err;
1030
        }
1031
1032
        if (!err) {
1033
            // Valid base58 but invalid network immediately alerts
1034
            if (addr.version !== network.pubKeyHash && addr.version !== network.scriptHash) {
1035
                throw new blocktrail.InvalidAddressError("Address invalid on this network");
1036
            }
1037
        }
1038
    }
1039
1040
    if (err) {
1041
        throw new blocktrail.InvalidAddressError(err.message);
1042
    }
1043
1044
    return {
1045
        address: address,
1046
        decoded: addr,
1047
        type: type
0 ignored issues
show
Bug introduced by
The variable type seems to not be initialized for all possible execution paths.
Loading history...
1048
    };
1049
};
1050
1051
Wallet.convertPayToOutputs = function(pay, network, allowCashAddr) {
1052
    var send = [];
1053
1054
    var readFunc;
1055
1056
    // Deal with two different forms
1057
    if (Array.isArray(pay)) {
1058
        // output[]
1059
        readFunc = function(i, output, obj) {
1060
            if (typeof output !== "object") {
1061
                throw new Error("Invalid transaction output for numerically indexed list [1]");
1062
            }
1063
1064
            var keys = Object.keys(output);
1065
            if (keys.indexOf("scriptPubKey") !== -1 && keys.indexOf("value") !== -1) {
1066
                obj.scriptPubKey = output["scriptPubKey"];
1067
                obj.value = output["value"];
1068
            } else if (keys.indexOf("address") !== -1 && keys.indexOf("value") !== -1) {
1069
                obj.address = output["address"];
1070
                obj.value = output["value"];
1071
            } else if (keys.length !== 2 && output.length !== 2 && keys[0] !== 0 && keys[1] !== 1) {
1072
                obj.address = output[0];
1073
                obj.value = output[1];
1074
            } else {
1075
                throw new Error("Invalid transaction output for numerically indexed list [2]");
1076
            }
1077
        };
1078
    } else if (typeof pay === "object") {
1079
        // map[addr]amount
1080
        readFunc = function(address, value, obj) {
1081
            obj.address = address.trim();
1082
            obj.value = value;
1083
            if (obj.address === Wallet.OP_RETURN) {
1084
                var datachunk = Buffer.isBuffer(value) ? value : new Buffer(value, 'utf-8');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1085
                obj.scriptPubKey = bitcoin.script.nullData.output.encode(datachunk).toString('hex');
1086
                obj.value = 0;
1087
                obj.address = null;
1088
            }
1089
        };
1090
    } else {
1091
        throw new Error("Invalid input");
1092
    }
1093
1094
    Object.keys(pay).forEach(function(key) {
1095
        var obj = {};
1096
        readFunc(key, pay[key], obj);
1097
1098
        if (parseInt(obj.value, 10).toString() !== obj.value.toString()) {
1099
            throw new blocktrail.WalletSendError("Values should be in Satoshis");
1100
        }
1101
1102
        // Remove address, replace with scriptPubKey
1103
        if (typeof obj.address === "string") {
1104
            try {
1105
                var addrAndType = Wallet.getAddressAndType(obj.address, network, allowCashAddr);
1106
                obj.scriptPubKey = bitcoin.address.toOutputScript(addrAndType.address, network, allowCashAddr).toString('hex');
1107
                delete obj.address;
1108
            } catch (e) {
1109
                throw new blocktrail.InvalidAddressError("Invalid address [" + obj.address + "] (" + e.message + ")");
1110
            }
1111
        }
1112
1113
        // Extra checks when the output isn't OP_RETURN
1114
        if (obj.scriptPubKey.slice(0, 2) !== "6a") {
1115
            if (!(obj.value = parseInt(obj.value, 10))) {
1116
                throw new blocktrail.WalletSendError("Values should be non zero");
1117
            } else if (obj.value <= blocktrail.DUST) {
1118
                throw new blocktrail.WalletSendError("Values should be more than dust (" + blocktrail.DUST + ")");
1119
            }
1120
        }
1121
1122
        // Value fully checked now
1123
        obj.value = parseInt(obj.value, 10);
1124
1125
        send.push(obj);
1126
    });
1127
1128
    return send;
1129
};
1130
1131
Wallet.prototype.buildTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) {
1132
    /* jshint -W071 */
1133
    var self = this;
1134
1135
    if (typeof changeAddress === "function") {
1136
        cb = changeAddress;
1137
        changeAddress = null;
1138
    } else if (typeof allowZeroConf === "function") {
1139
        cb = allowZeroConf;
1140
        allowZeroConf = false;
1141
    } else if (typeof randomizeChangeIdx === "function") {
1142
        cb = randomizeChangeIdx;
1143
        randomizeChangeIdx = true;
1144
    } else if (typeof feeStrategy === "function") {
1145
        cb = feeStrategy;
1146
        feeStrategy = null;
1147
    } else if (typeof options === "function") {
1148
        cb = options;
1149
        options = {};
1150
    }
1151
1152
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
1153
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1154
    options = options || {};
1155
1156
    var deferred = q.defer();
1157
    deferred.promise.spreadNodeify(cb);
1158
1159
    q.nextTick(function() {
1160
        var send;
1161
        try {
1162
            send = Wallet.convertPayToOutputs(pay, self.network, self.useNewCashAddr);
1163
        } catch (e) {
1164
            deferred.reject(e);
1165
            return deferred.promise;
1166
        }
1167
1168
        if (!send.length) {
1169
            deferred.reject(new blocktrail.WalletSendError("Need at least one recipient"));
1170
            return deferred.promise;
1171
        }
1172
1173
        deferred.notify(Wallet.PAY_PROGRESS_COIN_SELECTION);
1174
1175
        deferred.resolve(
1176
            self.coinSelection(send, true, allowZeroConf, feeStrategy, options)
1177
            /**
1178
             *
1179
             * @param {Object[]} utxos
1180
             * @param fee
1181
             * @param change
1182
             * @param randomizeChangeIdx
0 ignored issues
show
Documentation introduced by
The parameter randomizeChangeIdx does not exist. Did you maybe forget to remove this comment?
Loading history...
1183
             * @returns {*}
1184
             */
1185
                .spread(function(utxos, fee, change) {
1186
                    var tx, txb, outputs = [];
1187
1188
                    var deferred = q.defer();
1189
1190
                    async.waterfall([
1191
                        /**
1192
                         * prepare
1193
                         *
1194
                         * @param cb
1195
                         */
1196
                        function(cb) {
1197
                            var inputsTotal = utxos.map(function(utxo) {
1198
                                return utxo['value'];
1199
                            }).reduce(function(a, b) {
1200
                                return a + b;
1201
                            });
1202
                            var outputsTotal = send.map(function(output) {
1203
                                return output.value;
1204
                            }).reduce(function(a, b) {
1205
                                return a + b;
1206
                            });
1207
                            var estimatedChange = inputsTotal - outputsTotal - fee;
1208
1209
                            if (estimatedChange > blocktrail.DUST * 2 && estimatedChange !== change) {
1210
                                return cb(new blocktrail.WalletFeeError("the amount of change (" + change + ") " +
1211
                                    "suggested by the coin selection seems incorrect (" + estimatedChange + ")"));
1212
                            }
1213
1214
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1215
                        },
1216
                        /**
1217
                         * init transaction builder
1218
                         *
1219
                         * @param cb
1220
                         */
1221
                        function(cb) {
1222
                            txb = new bitcoin.TransactionBuilder(self.network);
1223
                            if (self.bitcoinCash) {
1224
                                txb.enableBitcoinCash();
1225
                            }
1226
1227
                            cb();
1228
                        },
1229
                        /**
1230
                         * add UTXOs as inputs
1231
                         *
1232
                         * @param cb
1233
                         */
1234
                        function(cb) {
1235
                            var i;
1236
1237
                            for (i = 0; i < utxos.length; i++) {
1238
                                txb.addInput(utxos[i]['hash'], utxos[i]['idx']);
1239
                            }
1240
1241
                            cb();
1242
                        },
1243
                        /**
1244
                         * build desired outputs
1245
                         *
1246
                         * @param cb
1247
                         */
1248
                        function(cb) {
1249
                            send.forEach(function(_send) {
1250
                                if (_send.address) {
1251
                                    outputs.push({address: _send.address, value: _send.value});
1252
                                } else if (_send.scriptPubKey) {
1253
                                    outputs.push({scriptPubKey: new Buffer(_send.scriptPubKey, 'hex'), value: _send.value});
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1254
                                } else {
1255
                                    throw new Error("Invalid send");
1256
                                }
1257
                            });
1258
                            cb();
1259
                        },
1260
                        /**
1261
                         * get change address if required
1262
                         *
1263
                         * @param cb
1264
                         */
1265
                        function(cb) {
1266
                            if (change > 0) {
1267
                                if (change <= blocktrail.DUST) {
1268
                                    change = 0; // don't do a change output if it would be a dust output
1269
1270
                                } else {
1271
                                    if (!changeAddress) {
1272
                                        deferred.notify(Wallet.PAY_PROGRESS_CHANGE_ADDRESS);
1273
1274
                                        return self.getNewAddress(self.changeChain, function(err, address) {
1275
                                            if (err) {
1276
                                                return cb(err);
1277
                                            }
1278
                                            changeAddress = address;
1279
                                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1280
                                        });
1281
                                    }
1282
                                }
1283
                            }
1284
1285
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1286
                        },
1287
                        /**
1288
                         * add change to outputs
1289
                         *
1290
                         * @param cb
1291
                         */
1292
                        function(cb) {
1293
                            if (change > 0) {
1294
                                if (randomizeChangeIdx) {
1295
                                    outputs.splice(_.random(0, outputs.length), 0, {
1296
                                        address: changeAddress,
1297
                                        value: change
1298
                                    });
1299
                                } else {
1300
                                    outputs.push({address: changeAddress, value: change});
1301
                                }
1302
                            }
1303
1304
                            cb();
1305
                        },
1306
                        /**
1307
                         * add outputs to txb
1308
                         *
1309
                         * @param cb
1310
                         */
1311
                        function(cb) {
1312
                            outputs.forEach(function(outputInfo) {
1313
                                txb.addOutput(outputInfo.scriptPubKey || outputInfo.address, outputInfo.value);
1314
                            });
1315
1316
                            cb();
1317
                        },
1318
                        /**
1319
                         * sign
1320
                         *
1321
                         * @param cb
1322
                         */
1323
                        function(cb) {
1324
                            var i, privKey, path, redeemScript, witnessScript;
1325
1326
                            deferred.notify(Wallet.PAY_PROGRESS_SIGN);
1327
1328
                            for (i = 0; i < utxos.length; i++) {
1329
                                var mode = SignMode.SIGN;
1330
                                if (utxos[i].sign_mode) {
1331
                                    mode = utxos[i].sign_mode;
1332
                                }
1333
1334
                                redeemScript = null;
0 ignored issues
show
Unused Code introduced by
The assignment to redeemScript seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
1335
                                witnessScript = null;
1336
                                if (mode === SignMode.SIGN) {
1337
                                    path = utxos[i]['path'].replace("M", "m");
1338
1339
                                    // todo: regenerate scripts for path and compare for utxo (paranoid mode)
1340
                                    if (self.primaryPrivateKey) {
1341
                                        privKey = Wallet.deriveByPath(self.primaryPrivateKey, path, "m").keyPair;
1342
                                    } else if (self.backupPrivateKey) {
1343
                                        privKey = Wallet.deriveByPath(self.backupPrivateKey, path.replace(/^m\/(\d+)\'/, 'm/$1'), "m").keyPair;
1344
                                    } else {
1345
                                        throw new Error("No master privateKey present");
1346
                                    }
1347
1348
                                    redeemScript = new Buffer(utxos[i]['redeem_script'], 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1349
                                    if (typeof utxos[i]['witness_script'] === 'string') {
1350
                                        witnessScript = new Buffer(utxos[i]['witness_script'], 'hex');
1351
                                    }
1352
1353
                                    var sigHash = bitcoin.Transaction.SIGHASH_ALL;
1354
                                    if (self.bitcoinCash) {
1355
                                        sigHash |= bitcoin.Transaction.SIGHASH_BITCOINCASHBIP143;
1356
                                    }
1357
1358
                                    txb.sign(i, privKey, redeemScript, sigHash, utxos[i].value, witnessScript);
1359
                                }
1360
                            }
1361
1362
                            tx = txb.buildIncomplete();
1363
1364
                            cb();
1365
                        },
1366
                        /**
1367
                         * estimate fee to verify that the API is not providing us wrong data
1368
                         *
1369
                         * @param cb
1370
                         */
1371
                        function(cb) {
1372
                            var estimatedFee = Wallet.estimateVsizeFee(tx, utxos);
1373
1374
                            if (self.sdk.feeSanityCheck) {
1375
                                switch (feeStrategy) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1376
                                    case Wallet.FEE_STRATEGY_BASE_FEE:
0 ignored issues
show
Bug introduced by
The variable Wallet seems to be never declared. If this is a global, consider adding a /** global: Wallet */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1377
                                        if (Math.abs(estimatedFee - fee) > blocktrail.BASE_FEE) {
1378
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1379
                                                "seems incorrect (" + estimatedFee + ") for FEE_STRATEGY_BASE_FEE"));
1380
                                        }
1381
                                    break;
1382
1383
                                    case Wallet.FEE_STRATEGY_OPTIMAL:
1384
                                        if (fee > estimatedFee * self.feeSanityCheckBaseFeeMultiplier) {
1385
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1386
                                                "seems awefully high (" + estimatedFee + ") for FEE_STRATEGY_OPTIMAL"));
1387
                                        }
1388
                                    break;
1389
                                }
1390
                            }
1391
1392
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1393
                        }
1394
                    ], function(err) {
1395
                        if (err) {
1396
                            deferred.reject(new blocktrail.WalletSendError(err));
1397
                            return;
1398
                        }
1399
1400
                        deferred.resolve([tx, utxos]);
1401
                    });
1402
1403
                    return deferred.promise;
1404
                }
1405
            )
1406
        );
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1407
    });
1408
1409
    return deferred.promise;
1410
};
1411
1412
1413
/**
1414
 * use the API to get the best inputs to use based on the outputs
1415
 *
1416
 * @param pay               array       {'address': (int)value}     coins to send
1417
 * @param [lockUTXO]        bool        lock UTXOs for a few seconds to allow for transaction to be created
1418
 * @param [allowZeroConf]   bool        allow zero confirmation unspent outputs to be used in coin selection
1419
 * @param [feeStrategy]     string      defaults to FEE_STRATEGY_OPTIMAL
1420
 * @param [options]         object
1421
 * @param [cb]              function    callback(err, utxos, fee, change)
1422
 * @returns {q.Promise}
1423
 */
1424
Wallet.prototype.coinSelection = function(pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1425
    var self = this;
1426
1427
    if (typeof lockUTXO === "function") {
1428
        cb = lockUTXO;
1429
        lockUTXO = true;
1430
    } else if (typeof allowZeroConf === "function") {
1431
        cb = allowZeroConf;
1432
        allowZeroConf = false;
1433
    } else if (typeof feeStrategy === "function") {
1434
        cb = feeStrategy;
1435
        feeStrategy = null;
1436
    } else if (typeof options === "function") {
1437
        cb = options;
1438
        options = {};
1439
    }
1440
1441
    lockUTXO = typeof lockUTXO !== "undefined" ? lockUTXO : true;
1442
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1443
    options = options || {};
1444
1445
    var send;
1446
    try {
1447
        send = Wallet.convertPayToOutputs(pay, self.network, self.useNewCashAddr);
1448
    } catch (e) {
1449
        var deferred = q.defer();
1450
        deferred.promise.nodeify(cb);
1451
        deferred.reject(e);
1452
        return deferred.promise;
1453
    }
1454
1455
    return self.sdk.coinSelection(self.identifier, send, lockUTXO, allowZeroConf, feeStrategy, options, cb);
1456
};
1457
1458
/**
1459
 * send the transaction using the API
1460
 *
1461
 * @param txHex             string      partially signed transaction as hex string
1462
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1463
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1464
 * @param [twoFactorToken]  string      2FA token
1465
 * @param prioboost         bool
1466
 * @param [cb]              function    callback(err, txHash)
1467
 * @returns {q.Promise}
1468
 */
1469
Wallet.prototype.sendTransaction = function(txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1470
    var self = this;
1471
1472
    if (typeof twoFactorToken === "function") {
1473
        cb = twoFactorToken;
1474
        twoFactorToken = null;
1475
        prioboost = false;
1476
    } else if (typeof prioboost === "function") {
1477
        cb = twoFactorToken;
1478
        prioboost = false;
1479
    }
1480
1481
    var deferred = q.defer();
1482
    deferred.promise.nodeify(cb);
1483
1484
    self.sdk.sendTransaction(self.identifier, txHex, paths, checkFee, twoFactorToken, prioboost)
1485
        .then(
1486
            function(result) {
1487
                deferred.resolve(result);
1488
            },
1489
            function(e) {
1490
                if (e.requires_2fa) {
1491
                    deferred.reject(new blocktrail.WalletMissing2FAError());
1492
                } else if (e.message.match(/Invalid two_factor_token/)) {
1493
                    deferred.reject(new blocktrail.WalletInvalid2FAError());
1494
                } else {
1495
                    deferred.reject(e);
1496
                }
1497
            }
1498
        )
1499
    ;
1500
1501
    return deferred.promise;
1502
};
1503
1504
/**
1505
 * setup a webhook for this wallet
1506
 *
1507
 * @param url           string      URL to receive webhook events
1508
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1509
 * @param [cb]          function    callback(err, webhook)
1510
 * @returns {q.Promise}
1511
 */
1512
Wallet.prototype.setupWebhook = function(url, identifier, cb) {
1513
    var self = this;
1514
1515
    if (typeof identifier === "function") {
1516
        cb = identifier;
1517
        identifier = null;
1518
    }
1519
1520
    identifier = identifier || ('WALLET-' + self.identifier);
1521
1522
    return self.sdk.setupWalletWebhook(self.identifier, identifier, url, cb);
1523
};
1524
1525
/**
1526
 * delete a webhook that was created for this wallet
1527
 *
1528
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1529
 * @param [cb]          function    callback(err, success)
1530
 * @returns {q.Promise}
1531
 */
1532
Wallet.prototype.deleteWebhook = function(identifier, cb) {
1533
    var self = this;
1534
1535
    if (typeof identifier === "function") {
1536
        cb = identifier;
1537
        identifier = null;
1538
    }
1539
1540
    identifier = identifier || ('WALLET-' + self.identifier);
1541
1542
    return self.sdk.deleteWalletWebhook(self.identifier, identifier, cb);
1543
};
1544
1545
/**
1546
 * get all transactions for the wallet (paginated)
1547
 *
1548
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1549
 * @param [cb]      function    callback(err, transactions)
1550
 * @returns {q.Promise}
1551
 */
1552
Wallet.prototype.transactions = function(params, cb) {
1553
    var self = this;
1554
1555
    return self.sdk.walletTransactions(self.identifier, params, cb);
1556
};
1557
1558
Wallet.prototype.maxSpendable = function(allowZeroConf, feeStrategy, options, cb) {
1559
    var self = this;
1560
1561
    if (typeof allowZeroConf === "function") {
1562
        cb = allowZeroConf;
1563
        allowZeroConf = false;
1564
    } else if (typeof feeStrategy === "function") {
1565
        cb = feeStrategy;
1566
        feeStrategy = null;
1567
    } else if (typeof options === "function") {
1568
        cb = options;
1569
        options = {};
1570
    }
1571
1572
    if (typeof allowZeroConf === "object") {
1573
        options = allowZeroConf;
1574
        allowZeroConf = false;
1575
    } else if (typeof feeStrategy === "object") {
1576
        options = feeStrategy;
1577
        feeStrategy = null;
1578
    }
1579
1580
    options = options || {};
1581
1582
    if (typeof options.allowZeroConf !== "undefined") {
1583
        allowZeroConf = options.allowZeroConf;
1584
    }
1585
    if (typeof options.feeStrategy !== "undefined") {
1586
        feeStrategy = options.feeStrategy;
1587
    }
1588
1589
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1590
1591
    return self.sdk.walletMaxSpendable(self.identifier, allowZeroConf, feeStrategy, options, cb);
1592
};
1593
1594
/**
1595
 * get all addresses for the wallet (paginated)
1596
 *
1597
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1598
 * @param [cb]      function    callback(err, addresses)
1599
 * @returns {q.Promise}
1600
 */
1601
Wallet.prototype.addresses = function(params, cb) {
1602
    var self = this;
1603
1604
    return self.sdk.walletAddresses(self.identifier, params, cb);
1605
};
1606
1607
/**
1608
 * @param address   string      the address to label
1609
 * @param label     string      the label
1610
 * @param [cb]      function    callback(err, res)
1611
 * @returns {q.Promise}
1612
 */
1613
Wallet.prototype.labelAddress = function(address, label, cb) {
1614
    var self = this;
1615
1616
    return self.sdk.labelWalletAddress(self.identifier, address, label, cb);
1617
};
1618
1619
/**
1620
 * get all UTXOs for the wallet (paginated)
1621
 *
1622
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1623
 * @param [cb]      function    callback(err, addresses)
1624
 * @returns {q.Promise}
1625
 */
1626
Wallet.prototype.utxos = function(params, cb) {
1627
    var self = this;
1628
1629
    return self.sdk.walletUTXOs(self.identifier, params, cb);
1630
};
1631
1632
Wallet.prototype.unspentOutputs = Wallet.prototype.utxos;
1633
1634
/**
1635
 * sort list of pubkeys to be used in a multisig redeemscript
1636
 *  sorted in lexicographical order on the hex of the pubkey
1637
 *
1638
 * @param pubKeys   {bitcoin.HDNode[]}
1639
 * @returns string[]
1640
 */
1641
Wallet.sortMultiSigKeys = function(pubKeys) {
1642
    pubKeys.sort(function(key1, key2) {
1643
        return key1.toString('hex').localeCompare(key2.toString('hex'));
1644
    });
1645
1646
    return pubKeys;
1647
};
1648
1649
/**
1650
 * determine how much fee is required based on the inputs and outputs
1651
 *  this is an estimation, not a proper 100% correct calculation
1652
 *
1653
 * @todo: mark deprecated in favor of estimations where UTXOS are known
1654
 * @param {bitcoin.Transaction} tx
1655
 * @param {int} feePerKb when not null use this feePerKb, otherwise use BASE_FEE legacy calculation
1656
 * @returns {number}
1657
 */
1658
Wallet.estimateIncompleteTxFee = function(tx, feePerKb) {
1659
    var size = Wallet.estimateIncompleteTxSize(tx);
1660
    var sizeKB = size / 1000;
1661
    var sizeKBCeil = Math.ceil(size / 1000);
1662
1663
    if (feePerKb) {
1664
        return parseInt(sizeKB * feePerKb, 10);
1665
    } else {
1666
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1667
    }
1668
};
1669
1670
/**
1671
 * Takes tx and utxos, computing their estimated vsize,
1672
 * and uses feePerKb (or BASEFEE as default) to estimate
1673
 * the number of satoshis in fee.
1674
 *
1675
 * @param {bitcoin.Transaction} tx
1676
 * @param {Array} utxos
1677
 * @param feePerKb
1678
 * @returns {Number}
1679
 */
1680
Wallet.estimateVsizeFee = function(tx, utxos, feePerKb) {
1681
    var vsize = SizeEstimation.estimateTxVsize(tx, utxos);
1682
    var sizeKB = vsize / 1000;
1683
    var sizeKBCeil = Math.ceil(vsize / 1000);
1684
1685
    if (feePerKb) {
1686
        return parseInt(sizeKB * feePerKb, 10);
1687
    } else {
1688
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1689
    }
1690
};
1691
1692
/**
1693
 * determine how much fee is required based on the inputs and outputs
1694
 *  this is an estimation, not a proper 100% correct calculation
1695
 *
1696
 * @param {bitcoin.Transaction} tx
1697
 * @returns {number}
1698
 */
1699
Wallet.estimateIncompleteTxSize = function(tx) {
1700
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1701
1702
    size += tx.outs.length * 34;
1703
1704
    tx.ins.forEach(function(txin) {
1705
        var scriptSig = txin.script,
1706
            scriptType = bitcoin.script.classifyInput(scriptSig);
1707
1708
        var multiSig = [2, 3]; // tmp hardcoded
1709
1710
        // Re-classify if P2SH
1711
        if (!multiSig && scriptType === 'scripthash') {
1712
            var sigChunks = bitcoin.script.decompile(scriptSig);
1713
            var redeemScript = sigChunks.slice(-1)[0];
1714
            scriptSig = bitcoin.script.compile(sigChunks.slice(0, -1));
1715
            scriptType = bitcoin.script.classifyInput(scriptSig);
1716
1717
            if (bitcoin.script.classifyOutput(redeemScript) !== scriptType) {
1718
                throw new blocktrail.TransactionInputError('Non-matching scriptSig and scriptPubKey in input');
1719
            }
1720
1721
            // figure out M of N for multisig (code from internal usage of bitcoinjs)
1722
            if (scriptType === 'multisig') {
1723
                var rsChunks = bitcoin.script.decompile(redeemScript);
1724
                var mOp = rsChunks[0];
1725
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1726
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1727
                }
1728
1729
                var nOp = rsChunks[redeemScript.chunks.length - 2];
1730
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1731
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1732
                }
1733
1734
                var m = mOp - (bitcoin.opcodes.OP_1 - 1);
1735
                var n = nOp - (bitcoin.opcodes.OP_1 - 1);
1736
                if (n < m) {
1737
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1738
                }
1739
1740
                multiSig = [m, n];
1741
            }
1742
        }
1743
1744
        if (multiSig) {
1745
            size += (
1746
                32 + // txhash
1747
                4 + // idx
1748
                3 + // scriptVarInt[>=253]
1749
                1 + // OP_0
1750
                ((1 + 72) * multiSig[0]) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1751
                (2 + 105) + // OP_PUSHDATA[>=75] + script
1752
                4 // sequence
1753
            );
1754
1755
        } else {
1756
            size += 32 + // txhash
1757
                4 + // idx
1758
                73 + // sig
1759
                34 + // script
1760
                4; // sequence
1761
        }
1762
    });
1763
1764
    return size;
1765
};
1766
1767
/**
1768
 * determine how much fee is required based on the amount of inputs and outputs
1769
 *  this is an estimation, not a proper 100% correct calculation
1770
 *  this asumes all inputs are 2of3 multisig
1771
 *
1772
 * @todo: mark deprecated in favor of situations where UTXOS are known
1773
 * @param txinCnt       {number}
1774
 * @param txoutCnt      {number}
1775
 * @returns {number}
1776
 */
1777
Wallet.estimateFee = function(txinCnt, txoutCnt) {
1778
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1779
1780
    size += txoutCnt * 34;
1781
1782
    size += (
1783
            32 + // txhash
1784
            4 + // idx
1785
            3 + // scriptVarInt[>=253]
1786
            1 + // OP_0
1787
            ((1 + 72) * 2) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1788
            (2 + 105) + // OP_PUSHDATA[>=75] + script
1789
            4 // sequence
1790
        ) * txinCnt;
1791
1792
    var sizeKB = Math.ceil(size / 1000);
1793
1794
    return sizeKB * blocktrail.BASE_FEE;
1795
};
1796
1797
/**
1798
 * create derived key from parent key by path
1799
 *
1800
 * @param hdKey     {bitcoin.HDNode}
1801
 * @param path      string
1802
 * @param keyPath   string
1803
 * @returns {bitcoin.HDNode}
1804
 */
1805
Wallet.deriveByPath = function(hdKey, path, keyPath) {
1806
    keyPath = keyPath || (!!hdKey.keyPair.d ? "m" : "M");
1807
1808
    if (path[0].toLowerCase() !== "m" || keyPath[0].toLowerCase() !== "m") {
1809
        throw new blocktrail.KeyPathError("Wallet.deriveByPath only works with absolute paths. (" + path + ", " + keyPath + ")");
1810
    }
1811
1812
    if (path[0] === "m" && keyPath[0] === "M") {
1813
        throw new blocktrail.KeyPathError("Wallet.deriveByPath can't derive private path from public parent. (" + path + ", " + keyPath + ")");
1814
    }
1815
1816
    // if the desired path is public while the input is private
1817
    var toPublic = path[0] === "M" && keyPath[0] === "m";
1818
    if (toPublic) {
1819
        // derive the private path, convert to public when returning
1820
        path[0] = "m";
1821
    }
1822
1823
    // keyPath should be the parent parent of path
1824
    if (path.toLowerCase().indexOf(keyPath.toLowerCase()) !== 0) {
1825
        throw new blocktrail.KeyPathError("Wallet.derivePath requires path (" + path + ") to be a child of keyPath (" + keyPath + ")");
1826
    }
1827
1828
    // remove the part of the path we already have
1829
    path = path.substr(keyPath.length);
1830
1831
    // iterate over the chunks and derive
1832
    var newKey = hdKey;
1833
    path.replace(/^\//, "").split("/").forEach(function(chunk) {
1834
        if (!chunk) {
1835
            return;
1836
        }
1837
1838
        if (chunk.indexOf("'") !== -1) {
1839
            chunk = parseInt(chunk.replace("'", ""), 10) + bitcoin.HDNode.HIGHEST_BIT;
1840
        }
1841
1842
        newKey = newKey.derive(parseInt(chunk, 10));
1843
    });
1844
1845
    if (toPublic) {
1846
        return newKey.neutered();
1847
    } else {
1848
        return newKey;
1849
    }
1850
};
1851
1852
module.exports = Wallet;
1853