Completed
Pull Request — master (#100)
by thomas
01:26
created

Wallet.getAddressAndType   D

Complexity

Conditions 9
Paths 8

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
c 0
b 0
f 0
nc 8
dl 0
loc 40
rs 4.909
nop 3

1 Function

Rating   Name   Duplication   Size   Complexity  
A 0 9 2
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 regtest               bool            regtest
34
 * @param checksum              string
35
 * @param upgradeToKeyIndex     int
36
 * @param useNewCashAddr        bool            flag to opt in to bitcoin cash cashaddr's
37
 * @param bypassNewAddressCheck bool            flag to indicate if wallet should/shouldn't derive new address locally to verify api
38
 * @constructor
39
 * @internal
40
 */
41
var Wallet = function(
42
    sdk,
43
    identifier,
44
    walletVersion,
45
    primaryMnemonic,
46
    encryptedPrimarySeed,
47
    encryptedSecret,
48
    primaryPublicKeys,
49
    backupPublicKey,
50
    blocktrailPublicKeys,
51
    keyIndex,
52
    segwit,
53
    testnet,
54
    regtest,
55
    checksum,
56
    upgradeToKeyIndex,
57
    useNewCashAddr,
58
    bypassNewAddressCheck
59
) {
60
    /* jshint -W071 */
61
    var self = this;
62
63
    self.sdk = sdk;
64
    self.identifier = identifier;
65
    self.walletVersion = walletVersion;
66
    self.locked = true;
67
    self.bypassNewAddressCheck = !!bypassNewAddressCheck;
68
    self.bitcoinCash = self.sdk.bitcoinCash;
69
    self.segwit = !!segwit;
70
    self.useNewCashAddr = !!useNewCashAddr;
71
    assert(!self.segwit || !self.bitcoinCash);
72
73
    self.testnet = testnet;
74
    self.regtest = regtest;
75
    if (self.bitcoinCash) {
76
        if (self.regtest) {
77
            self.network = bitcoin.networks.bitcoincashregtest;
78
        } else if (self.testnet) {
79
            self.network = bitcoin.networks.bitcoincashtestnet;
80
        } else {
81
            self.network = bitcoin.networks.bitcoincash;
82
        }
83
    } else {
84
        if (self.regtest) {
85
            self.network = bitcoin.networks.regtest;
86
        } else if (self.testnet) {
87
            self.network = bitcoin.networks.testnet;
88
        } else {
89
            self.network = bitcoin.networks.bitcoin;
90
        }
91
    }
92
93
    assert(backupPublicKey instanceof bitcoin.HDNode);
94
    assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; }));
95
    assert(_.every(blocktrailPublicKeys, function(blocktrailPublicKey) { return blocktrailPublicKey instanceof bitcoin.HDNode; }));
96
97
    // v1
98
    self.primaryMnemonic = primaryMnemonic;
99
100
    // v2 & v3
101
    self.encryptedPrimarySeed = encryptedPrimarySeed;
102
    self.encryptedSecret = encryptedSecret;
103
104
    self.primaryPrivateKey = null;
105
    self.backupPrivateKey = null;
106
107
    self.backupPublicKey = backupPublicKey;
108
    self.blocktrailPublicKeys = blocktrailPublicKeys;
109
    self.primaryPublicKeys = primaryPublicKeys;
110
    self.keyIndex = keyIndex;
111
112
    if (!self.bitcoinCash) {
113
        if (self.segwit) {
114
            self.chain = Wallet.CHAIN_BTC_DEFAULT;
115
            self.changeChain = Wallet.CHAIN_BTC_SEGWIT;
116
        } else {
117
            self.chain = Wallet.CHAIN_BTC_DEFAULT;
118
            self.changeChain = Wallet.CHAIN_BTC_DEFAULT;
119
        }
120
    } else {
121
        self.chain = Wallet.CHAIN_BCC_DEFAULT;
122
        self.changeChain = Wallet.CHAIN_BCC_DEFAULT;
123
    }
124
125
    self.checksum = checksum;
126
    self.upgradeToKeyIndex = upgradeToKeyIndex;
127
128
    self.secret = null;
129
    self.seedHex = null;
130
};
131
132
Wallet.WALLET_VERSION_V1 = 'v1';
133
Wallet.WALLET_VERSION_V2 = 'v2';
134
Wallet.WALLET_VERSION_V3 = 'v3';
135
136
Wallet.WALLET_ENTROPY_BITS = 256;
137
138
Wallet.OP_RETURN = 'opreturn';
139
Wallet.DATA = Wallet.OP_RETURN; // alias
140
141
Wallet.PAY_PROGRESS_START = 0;
142
Wallet.PAY_PROGRESS_COIN_SELECTION = 10;
143
Wallet.PAY_PROGRESS_CHANGE_ADDRESS = 20;
144
Wallet.PAY_PROGRESS_SIGN = 30;
145
Wallet.PAY_PROGRESS_SEND = 40;
146
Wallet.PAY_PROGRESS_DONE = 100;
147
148
Wallet.CHAIN_BTC_DEFAULT = 0;
149
Wallet.CHAIN_BTC_SEGWIT = 2;
150
Wallet.CHAIN_BCC_DEFAULT = 1;
151
152
Wallet.FEE_STRATEGY_FORCE_FEE = blocktrail.FEE_STRATEGY_FORCE_FEE;
153
Wallet.FEE_STRATEGY_BASE_FEE = blocktrail.FEE_STRATEGY_BASE_FEE;
154
Wallet.FEE_STRATEGY_HIGH_PRIORITY = blocktrail.FEE_STRATEGY_HIGH_PRIORITY;
155
Wallet.FEE_STRATEGY_OPTIMAL = blocktrail.FEE_STRATEGY_OPTIMAL;
156
Wallet.FEE_STRATEGY_LOW_PRIORITY = blocktrail.FEE_STRATEGY_LOW_PRIORITY;
157
Wallet.FEE_STRATEGY_MIN_RELAY_FEE = blocktrail.FEE_STRATEGY_MIN_RELAY_FEE;
158
159
Wallet.prototype.isSegwit = function() {
160
    return !!this.segwit;
161
};
162
163
Wallet.prototype.unlock = function(options, cb) {
164
    var self = this;
165
166
    var deferred = q.defer();
167
    deferred.promise.nodeify(cb);
168
169
    // avoid modifying passed options
170
    options = _.merge({}, options);
171
172
    q.fcall(function() {
173
        switch (self.walletVersion) {
174
            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...
175
                return self.unlockV1(options);
176
177
            case Wallet.WALLET_VERSION_V2:
178
                return self.unlockV2(options);
179
180
            case Wallet.WALLET_VERSION_V3:
181
                return self.unlockV3(options);
182
183
            default:
184
                return q.reject(new blocktrail.WalletInitError("Invalid wallet version"));
185
        }
186
    }).then(
187
        function(primaryPrivateKey) {
188
            self.primaryPrivateKey = primaryPrivateKey;
189
190
            // create a checksum of our private key which we'll later use to verify we used the right password
191
            var checksum = self.primaryPrivateKey.getAddress();
192
193
            // check if we've used the right passphrase
194
            if (checksum !== self.checksum) {
195
                throw new blocktrail.WalletChecksumError("Generated checksum [" + checksum + "] does not match " +
196
                    "[" + self.checksum + "], most likely due to incorrect password");
197
            }
198
199
            self.locked = false;
200
201
            // if the response suggests we should upgrade to a different blocktrail cosigning key then we should
202
            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...
203
                return self.upgradeKeyIndex(self.upgradeToKeyIndex);
204
            }
205
        }
206
    ).then(
207
        function(r) {
208
            deferred.resolve(r);
209
        },
210
        function(e) {
211
            deferred.reject(e);
212
        }
213
    );
214
215
    return deferred.promise;
216
};
217
218
Wallet.prototype.unlockV1 = function(options) {
219
    var self = this;
220
221
    options.primaryMnemonic = typeof options.primaryMnemonic !== "undefined" ? options.primaryMnemonic : self.primaryMnemonic;
222
    options.secretMnemonic = typeof options.secretMnemonic !== "undefined" ? options.secretMnemonic : self.secretMnemonic;
223
224
    return self.sdk.resolvePrimaryPrivateKeyFromOptions(options)
225
        .then(function(options) {
226
            self.primarySeed = options.primarySeed;
227
228
            return options.primaryPrivateKey;
229
        });
230
};
231
232
Wallet.prototype.unlockV2 = function(options, cb) {
233
    var self = this;
234
235
    var deferred = q.defer();
236
    deferred.promise.nodeify(cb);
237
238
    deferred.resolve(q.fcall(function() {
239
        /* jshint -W071, -W074 */
240
        options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed;
241
        options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret;
242
243
        if (options.secret) {
244
            self.secret = options.secret;
245
        }
246
247
        if (options.primaryPrivateKey) {
248
            throw new blocktrail.WalletDecryptError("specifying primaryPrivateKey has been deprecated");
249
        }
250
251
        if (options.primarySeed) {
252
            self.primarySeed = options.primarySeed;
253
        } else if (options.secret) {
254
            try {
255
                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...
256
                    CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedPrimarySeed), self.secret).toString(CryptoJS.enc.Utf8), 'base64');
257
                if (!self.primarySeed.length) {
258
                    throw new Error();
259
                }
260
            } catch (e) {
261
                throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
262
            }
263
264
        } else {
265
            // avoid conflicting options
266
            if (options.passphrase && options.password) {
267
                throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
268
            }
269
            // normalize passphrase/password
270
            options.passphrase = options.passphrase || options.password;
271
272
            try {
273
                self.secret = CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedSecret), options.passphrase).toString(CryptoJS.enc.Utf8);
274
                if (!self.secret.length) {
275
                    throw new Error();
276
                }
277
            } catch (e) {
278
                throw new blocktrail.WalletDecryptError("Failed to decrypt secret");
279
            }
280
            try {
281
                self.primarySeed = new Buffer(
282
                    CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedPrimarySeed), self.secret).toString(CryptoJS.enc.Utf8), 'base64');
283
                if (!self.primarySeed.length) {
284
                    throw new Error();
285
                }
286
            } catch (e) {
287
                throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
288
            }
289
        }
290
291
        return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
292
    }));
293
294
    return deferred.promise;
295
};
296
297
Wallet.prototype.unlockV3 = function(options, cb) {
298
    var self = this;
299
300
    var deferred = q.defer();
301
    deferred.promise.nodeify(cb);
302
303
    deferred.resolve(q.fcall(function() {
304
        return q.when()
305
            .then(function() {
306
                /* jshint -W071, -W074 */
307
                options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed;
308
                options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret;
309
310
                if (options.secret) {
311
                    self.secret = options.secret;
312
                }
313
314
                if (options.primaryPrivateKey) {
315
                    throw new blocktrail.WalletInitError("specifying primaryPrivateKey has been deprecated");
316
                }
317
318
                if (options.primarySeed) {
319
                    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...
320
                } else if (options.secret) {
321
                    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...
322
                        .then(function(primarySeed) {
323
                            self.primarySeed = primarySeed;
324
                        }, function() {
325
                            throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
326
                        });
327
                } else {
328
                    // avoid conflicting options
329
                    if (options.passphrase && options.password) {
330
                        throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
331
                    }
332
                    // normalize passphrase/password
333
                    options.passphrase = options.passphrase || options.password;
334
                    delete options.password;
335
336
                    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...
337
                        .then(function(secret) {
338
                            self.secret = secret;
339
                        }, function() {
340
                            throw new blocktrail.WalletDecryptError("Failed to decrypt secret");
341
                        })
342
                        .then(function() {
343
                            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...
344
                                .then(function(primarySeed) {
345
                                    self.primarySeed = primarySeed;
346
                                }, function() {
347
                                    throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
348
                                });
349
                        });
350
                }
351
            })
352
            .then(function() {
353
                return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
354
            })
355
        ;
356
    }));
357
358
    return deferred.promise;
359
};
360
361
Wallet.prototype.lock = function() {
362
    var self = this;
363
364
    self.secret = null;
365
    self.primarySeed = null;
366
    self.primaryPrivateKey = null;
367
    self.backupPrivateKey = null;
368
369
    self.locked = true;
370
};
371
372
/**
373
 * upgrade wallet to V3 encryption scheme
374
 *
375
 * @param passphrase is required again to reencrypt the data, important that it's the correct password!!!
376
 * @param cb
377
 * @returns {promise}
378
 */
379
Wallet.prototype.upgradeToV3 = function(passphrase, cb) {
380
    var self = this;
381
382
    var deferred = q.defer();
383
    deferred.promise.nodeify(cb);
384
385
    q.when(true)
386
        .then(function() {
387
            if (self.locked) {
388
                throw new blocktrail.WalletLockedError("Wallet needs to be unlocked to upgrade");
389
            }
390
391
            if (self.walletVersion === Wallet.WALLET_VERSION_V3) {
392
                throw new blocktrail.WalletUpgradeError("Wallet is already V3");
393
            } else if (self.walletVersion === Wallet.WALLET_VERSION_V2) {
394
                return self._upgradeV2ToV3(passphrase, deferred.notify.bind(deferred));
395
            } 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...
396
                return self._upgradeV1ToV3(passphrase, deferred.notify.bind(deferred));
397
            }
398
        })
399
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
400
401
    return deferred.promise;
402
};
403
404
Wallet.prototype._upgradeV2ToV3 = function(passphrase, notify) {
405
    var self = this;
406
407
    return q.when(true)
408
        .then(function() {
409
            var options = {
410
                storeDataOnServer: true,
411
                passphrase: passphrase,
412
                primarySeed: self.primarySeed,
413
                recoverySecret: false // don't create new recovery secret, V2 already has ones
414
            };
415
416 View Code Duplication
            return self.sdk.produceEncryptedDataV3(options, notify || function noop() {})
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
417
                .then(function(options) {
418
                    return self.sdk.updateWallet(self.identifier, {
419
                        encrypted_primary_seed: options.encryptedPrimarySeed.toString('base64'),
420
                        encrypted_secret: options.encryptedSecret.toString('base64'),
421
                        wallet_version: Wallet.WALLET_VERSION_V3
422
                    }).then(function() {
423
                        self.secret = options.secret;
424
                        self.encryptedPrimarySeed = options.encryptedPrimarySeed;
425
                        self.encryptedSecret = options.encryptedSecret;
426
                        self.walletVersion = Wallet.WALLET_VERSION_V3;
427
428
                        return self;
429
                    });
430
                });
431
        });
432
433
};
434
435
Wallet.prototype._upgradeV1ToV3 = function(passphrase, notify) {
436
    var self = this;
437
438
    return q.when(true)
439
        .then(function() {
440
            var options = {
441
                storeDataOnServer: true,
442
                passphrase: passphrase,
443
                primarySeed: self.primarySeed
444
            };
445
446
            return self.sdk.produceEncryptedDataV3(options, notify || function noop() {})
447
                .then(function(options) {
448
                    // store recoveryEncryptedSecret for printing on backup sheet
449
                    self.recoveryEncryptedSecret = options.recoveryEncryptedSecret;
450
451
                    return self.sdk.updateWallet(self.identifier, {
452
                        primary_mnemonic: '',
453
                        encrypted_primary_seed: options.encryptedPrimarySeed.toString('base64'),
454
                        encrypted_secret: options.encryptedSecret.toString('base64'),
455
                        recovery_secret: options.recoverySecret.toString('hex'),
456
                        wallet_version: Wallet.WALLET_VERSION_V3
457
                    }).then(function() {
458
                        self.secret = options.secret;
459
                        self.encryptedPrimarySeed = options.encryptedPrimarySeed;
460
                        self.encryptedSecret = options.encryptedSecret;
461
                        self.walletVersion = Wallet.WALLET_VERSION_V3;
462
463
                        return self;
464
                    });
465
                });
466
        });
467
};
468
469
Wallet.prototype.doPasswordChange = function(newPassword) {
470
    var self = this;
471
472
    return q.when(null)
473
        .then(function() {
474
475
            if (self.walletVersion === Wallet.WALLET_VERSION_V1) {
476
                throw new blocktrail.WalletLockedError("Wallet version does not support password change!");
477
            }
478
479
            if (self.locked) {
480
                throw new blocktrail.WalletLockedError("Wallet needs to be unlocked to change password");
481
            }
482
483
            if (!self.secret) {
484
                throw new blocktrail.WalletLockedError("No secret");
485
            }
486
487
            var newEncryptedSecret;
488
            var newEncrypedWalletSecretMnemonic;
489
            if (self.walletVersion === Wallet.WALLET_VERSION_V2) {
490
                newEncryptedSecret = CryptoJS.AES.encrypt(self.secret, newPassword).toString(CryptoJS.format.OpenSSL);
491
                newEncrypedWalletSecretMnemonic = bip39.entropyToMnemonic(blocktrail.convert(newEncryptedSecret, 'base64', 'hex'));
492
493
            } else {
494
                if (typeof newPassword === "string") {
495
                    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...
496
                } else {
497
                    if (!(newPassword instanceof Buffer)) {
498
                        throw new Error('New password must be provided as a string or a Buffer');
499
                    }
500
                }
501
502
                newEncryptedSecret = Encryption.encrypt(self.secret, newPassword);
503
                newEncrypedWalletSecretMnemonic = EncryptionMnemonic.encode(newEncryptedSecret);
504
505
                // It's a buffer, so convert it back to base64
506
                newEncryptedSecret = newEncryptedSecret.toString('base64');
507
            }
508
509
            return [newEncryptedSecret, newEncrypedWalletSecretMnemonic];
510
        });
511
};
512
513
Wallet.prototype.passwordChange = function(newPassword, cb) {
514
    var self = this;
515
516
    var deferred = q.defer();
517
    deferred.promise.nodeify(cb);
518
519
    q.fcall(function() {
520
        return self.doPasswordChange(newPassword)
521
            .then(function(r) {
522
                var newEncryptedSecret = r[0];
523
                var newEncrypedWalletSecretMnemonic = r[1];
524
525
                return self.sdk.updateWallet(self.identifier, {encrypted_secret: newEncryptedSecret}).then(function() {
526
                    self.encryptedSecret = newEncryptedSecret;
527
528
                    // backupInfo
529
                    return {
530
                        encryptedSecret: newEncrypedWalletSecretMnemonic
531
                    };
532
                });
533
            })
534
            .then(
535
                function(r) {
536
                    deferred.resolve(r);
537
                },
538
                function(e) {
539
                    deferred.reject(e);
540
                }
541
            );
542
    });
543
544
    return deferred.promise;
545
};
546
547
/**
548
 * get address for specified path
549
 *
550
 * @param path
551
 * @returns string
552
 */
553
Wallet.prototype.getAddressByPath = function(path) {
554
    return this.getWalletScriptByPath(path).address;
555
};
556
557
/**
558
 * get redeemscript for specified path
559
 *
560
 * @param path
561
 * @returns {Buffer}
562
 */
563
Wallet.prototype.getRedeemScriptByPath = function(path) {
564
    return this.getWalletScriptByPath(path).redeemScript;
565
};
566
567
/**
568
 * Generate scripts, and address.
569
 * @param path
570
 * @returns {{witnessScript: *, redeemScript: *, scriptPubKey, address: *}}
571
 */
572
Wallet.prototype.getWalletScriptByPath = function(path) {
573
    var self = this;
574
575
    // get derived primary key
576
    var derivedPrimaryPublicKey = self.getPrimaryPublicKey(path);
577
    // get derived blocktrail key
578
    var derivedBlocktrailPublicKey = self.getBlocktrailPublicKey(path);
579
    // derive the backup key
580
    var derivedBackupPublicKey = Wallet.deriveByPath(self.backupPublicKey, path.replace("'", ""), "M");
581
582
    // sort the pubkeys
583
    var pubKeys = Wallet.sortMultiSigKeys([
584
        derivedPrimaryPublicKey.keyPair.getPublicKeyBuffer(),
585
        derivedBackupPublicKey.keyPair.getPublicKeyBuffer(),
586
        derivedBlocktrailPublicKey.keyPair.getPublicKeyBuffer()
587
    ]);
588
589
    var multisig = bitcoin.script.multisig.output.encode(2, pubKeys);
590
    var scriptType = parseInt(path.split("/")[2]);
591
592
    var ws, rs;
593
    if (this.network !== "bitcoincash" && scriptType === Wallet.CHAIN_BTC_SEGWIT) {
594
        ws = multisig;
595
        rs = bitcoin.script.witnessScriptHash.output.encode(bitcoin.crypto.sha256(ws));
596
    } else {
597
        ws = null;
598
        rs = multisig;
599
    }
600
601
    var spk = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(rs));
602
    var addr = bitcoin.address.fromOutputScript(spk, this.network, self.useNewCashAddr);
603
604
    return {
605
        witnessScript: ws,
606
        redeemScript: rs,
607
        scriptPubKey: spk,
608
        address: addr
609
    };
610
};
611
612
/**
613
 * get primary public key by path
614
 *  first level of the path is used as keyIndex to find the correct key in the dict
615
 *
616
 * @param path  string
617
 * @returns {bitcoin.HDNode}
618
 */
619
Wallet.prototype.getPrimaryPublicKey = function(path) {
620
    var self = this;
621
622
    path = path.replace("m", "M");
623
624
    var keyIndex = path.split("/")[1].replace("'", "");
625
626
    if (!self.primaryPublicKeys[keyIndex]) {
627
        if (self.primaryPrivateKey) {
628
            self.primaryPublicKeys[keyIndex] = Wallet.deriveByPath(self.primaryPrivateKey, "M/" + keyIndex + "'", "m");
629
        } else {
630
            throw new blocktrail.KeyPathError("Wallet.getPrimaryPublicKey keyIndex (" + keyIndex + ") is unknown to us");
631
        }
632
    }
633
634
    var primaryPublicKey = self.primaryPublicKeys[keyIndex];
635
    return Wallet.deriveByPath(primaryPublicKey, path, "M/" + keyIndex + "'");
636
};
637
638
/**
639
 * get blocktrail public key by path
640
 *  first level of the path is used as keyIndex to find the correct key in the dict
641
 *
642
 * @param path  string
643
 * @returns {bitcoin.HDNode}
644
 */
645
Wallet.prototype.getBlocktrailPublicKey = function(path) {
646
    var self = this;
647
648
    path = path.replace("m", "M");
649
650
    var keyIndex = path.split("/")[1].replace("'", "");
651
652
    if (!self.blocktrailPublicKeys[keyIndex]) {
653
        throw new blocktrail.KeyPathError("Wallet.getBlocktrailPublicKey keyIndex (" + keyIndex + ") is unknown to us");
654
    }
655
656
    var blocktrailPublicKey = self.blocktrailPublicKeys[keyIndex];
657
658
    return Wallet.deriveByPath(blocktrailPublicKey, path, "M/" + keyIndex + "'");
659
};
660
661
/**
662
 * upgrade wallet to different blocktrail cosign key
663
 *
664
 * @param keyIndex  int
665
 * @param [cb]      function
666
 * @returns {q.Promise}
667
 */
668
Wallet.prototype.upgradeKeyIndex = function(keyIndex, cb) {
669
    var self = this;
670
671
    var deferred = q.defer();
672
    deferred.promise.nodeify(cb);
673
674
    if (self.locked) {
675
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to upgrade key index"));
676
        return deferred.promise;
677
    }
678
679
    var primaryPublicKey = self.primaryPrivateKey.deriveHardened(keyIndex).neutered();
680
681
    deferred.resolve(
682
        self.sdk.upgradeKeyIndex(self.identifier, keyIndex, [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"])
683
            .then(function(result) {
684
                self.keyIndex = keyIndex;
685
                _.forEach(result.blocktrail_public_keys, function(publicKey, keyIndex) {
686
                    self.blocktrailPublicKeys[keyIndex] = bitcoin.HDNode.fromBase58(publicKey[0], self.network);
687
                });
688
689
                self.primaryPublicKeys[keyIndex] = primaryPublicKey;
690
691
                return true;
692
            })
693
    );
694
695
    return deferred.promise;
696
};
697
698
/**
699
 * generate a new derived private key and return the new address for it
700
 *
701
 * @param [chainIdx] int
702
 * @param [cb]  function        callback(err, address)
703
 * @returns {q.Promise}
704
 */
705
Wallet.prototype.getNewAddress = function(chainIdx, cb) {
706
    var self = this;
707
708
    // chainIdx is optional
709
    if (typeof chainIdx === "function") {
710
        cb = chainIdx;
711
        chainIdx = null;
712
    }
713
714
    var deferred = q.defer();
715
    deferred.promise.spreadNodeify(cb);
716
717
    // Only enter if it's not an integer
718
    if (chainIdx !== parseInt(chainIdx, 10)) {
719
        // deal with undefined or null, assume defaults
720
        if (typeof chainIdx === "undefined" || chainIdx === null) {
721
            chainIdx = self.chain;
722
        } else {
723
            // was a variable but not integer
724
            deferred.reject(new Error("Invalid chain index"));
725
            return deferred.promise;
726
        }
727
    }
728
729
    deferred.resolve(
730
        self.sdk.getNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx)
731
            .then(function(newDerivation) {
732
                var path = newDerivation.path;
733
                var addressFromServer = newDerivation.address;
734
                var decodedFromServer;
735
736
                try {
737
                    // Decode the address the serer gave us
738
                    decodedFromServer = self.decodeAddress(addressFromServer);
739
                    if ("cashAddrPrefix" in self.network && self.useNewCashAddr && decodedFromServer.type === "base58") {
740
                        self.bypassNewAddressCheck = false;
741
                    }
742
                } catch (e) {
743
                    throw new blocktrail.WalletAddressError("Failed to decode address [" + newDerivation.address + "]");
744
                }
745
746
                if (!self.bypassNewAddressCheck) {
747
                    // We need to reproduce this address with the same path,
748
                    // but the server (for BCH cashaddrs) uses base58?
749
                    var verifyAddress = self.getAddressByPath(newDerivation.path);
750
751
                    // If this occasion arises:
752
                    if ("cashAddrPrefix" in self.network && self.useNewCashAddr && decodedFromServer.type === "base58") {
753
                        // Decode our the address we produced for the path
754
                        var decodeOurs;
755
                        try {
756
                            decodeOurs = self.decodeAddress(verifyAddress);
757
                        } catch (e) {
758
                            throw new blocktrail.WalletAddressError("Error while verifying address from server [" + e.message + "]");
759
                        }
760
761
                        // Peek beyond the encoding - the hashes must match at least
762
                        if (decodeOurs.decoded.hash.toString('hex') !== decodedFromServer.decoded.hash.toString('hex')) {
763
                            throw new blocktrail.WalletAddressError("Failed to verify legacy address [hash mismatch]");
764
                        }
765
766
                        var matchedP2PKH = decodeOurs.decoded.version === bitcoin.script.types.P2PKH &&
767
                            decodedFromServer.decoded.version === self.network.pubKeyHash;
768
                        var matchedP2SH = decodeOurs.decoded.version === bitcoin.script.types.P2SH &&
769
                            decodedFromServer.decoded.version === self.network.scriptHash;
770
771
                        if (!(matchedP2PKH || matchedP2SH)) {
772
                            throw new blocktrail.WalletAddressError("Failed to verify legacy address [prefix mismatch]");
773
                        }
774
775
                        // We are satisfied that the address is for the same
776
                        // destination, so substitute addressFromServer with our
777
                        // 'reencoded' form.
778
                        addressFromServer = decodeOurs.address;
779
                    }
780
781
                    // debug check
782
                    if (verifyAddress !== addressFromServer) {
783
                        throw new blocktrail.WalletAddressError("Failed to verify address [" + newDerivation.address + "] !== [" + addressFromServer + "]");
784
                    }
785
                }
786
787
                return [addressFromServer, path];
788
            })
789 View Code Duplication
    );
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
790
791
    return deferred.promise;
792
};
793
794
/**
795
 * get the balance for the wallet
796
 *
797
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
798
 * @returns {q.Promise}
799
 */
800
Wallet.prototype.getBalance = function(cb) {
801
    var self = this;
802
803
    var deferred = q.defer();
804
    deferred.promise.spreadNodeify(cb);
805
806
    deferred.resolve(
807
        self.sdk.getWalletBalance(self.identifier)
808
            .then(function(result) {
809
                return [result.confirmed, result.unconfirmed];
810
            })
811
    );
812
813
    return deferred.promise;
814
};
815
816
/**
817
 * get the balance for the wallet
818
 *
819
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
820
 * @returns {q.Promise}
821
 */
822
Wallet.prototype.getInfo = function(cb) {
823
    var self = this;
824
825
    var deferred = q.defer();
826
    deferred.promise.spreadNodeify(cb);
827
828
    deferred.resolve(
829
        self.sdk.getWalletBalance(self.identifier)
830
    );
831
832
    return deferred.promise;
833
};
834
835
/**
836
 * do wallet discovery (slow)
837
 *
838
 * @param [gap] int             gap limit
839
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
840
 * @returns {q.Promise}
841
 */
842
Wallet.prototype.doDiscovery = function(gap, cb) {
843
    var self = this;
844
845
    if (typeof gap === "function") {
846
        cb = gap;
847
        gap = null;
848
    }
849
850
    var deferred = q.defer();
851
    deferred.promise.spreadNodeify(cb);
852
853
    deferred.resolve(
854
        self.sdk.doWalletDiscovery(self.identifier, gap)
855
            .then(function(result) {
856
                return [result.confirmed, result.unconfirmed];
857
            })
858
    );
859
860
    return deferred.promise;
861
};
862
863
/**
864
 *
865
 * @param [force]   bool            ignore warnings (such as non-zero balance)
866
 * @param [cb]      function        callback(err, success)
867 View Code Duplication
 * @returns {q.Promise}
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
868
 */
869
Wallet.prototype.deleteWallet = function(force, cb) {
870
    var self = this;
871
872
    if (typeof force === "function") {
873
        cb = force;
874
        force = false;
875
    }
876
877
    var deferred = q.defer();
878
    deferred.promise.nodeify(cb);
879
880
    if (self.locked) {
881
        deferred.reject(new blocktrail.WalletDeleteError("Wallet needs to be unlocked to delete wallet"));
882
        return deferred.promise;
883
    }
884
885
    var checksum = self.primaryPrivateKey.getAddress();
886
    var privBuf = self.primaryPrivateKey.keyPair.d.toBuffer(32);
887
    var signature = bitcoinMessage.sign(checksum, self.network.messagePrefix, privBuf, true).toString('base64');
888
889
    deferred.resolve(
890
        self.sdk.deleteWallet(self.identifier, checksum, signature, force)
891
            .then(function(result) {
892
                return result.deleted;
893
            })
894
    );
895
896
    return deferred.promise;
897
};
898
899
/**
900
 * create, sign and send a transaction
901
 *
902
 * @param pay                   array       {'address': (int)value}     coins to send
903
 * @param [changeAddress]       bool        change address to use (auto generated if NULL)
904
 * @param [allowZeroConf]       bool        allow zero confirmation unspent outputs to be used in coin selection
905
 * @param [randomizeChangeIdx]  bool        randomize the index of the change output (default TRUE, only disable if you have a good reason to)
906
 * @param [feeStrategy]         string      defaults to Wallet.FEE_STRATEGY_OPTIMAL
907
 * @param [twoFactorToken]      string      2FA token
908
 * @param options
909
 * @param [cb]                  function    callback(err, txHash)
910
 * @returns {q.Promise}
911
 */
912
Wallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) {
913
914
    /* jshint -W071 */
915
    var self = this;
916
917
    if (typeof changeAddress === "function") {
918
        cb = changeAddress;
919
        changeAddress = null;
920
    } else if (typeof allowZeroConf === "function") {
921
        cb = allowZeroConf;
922
        allowZeroConf = false;
923
    } else if (typeof randomizeChangeIdx === "function") {
924
        cb = randomizeChangeIdx;
925
        randomizeChangeIdx = true;
926
    } else if (typeof feeStrategy === "function") {
927
        cb = feeStrategy;
928
        feeStrategy = null;
929
    } else if (typeof twoFactorToken === "function") {
930
        cb = twoFactorToken;
931
        twoFactorToken = null;
932
    } else if (typeof options === "function") {
933
        cb = options;
934
        options = {};
935
    }
936
937
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
938
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
939
    options = options || {};
940
    var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true;
941
942
    var deferred = q.defer();
943
    deferred.promise.nodeify(cb);
944
945
    if (self.locked) {
946
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to send coins"));
947
        return deferred.promise;
948
    }
949
950
    q.nextTick(function() {
951
        deferred.notify(Wallet.PAY_PROGRESS_START);
952
        self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options)
953
            .then(
954
            function(r) { return r; },
955
            function(e) { deferred.reject(e); },
956
            function(progress) {
957
                deferred.notify(progress);
958
            }
959
        )
960
            .spread(
961
            function(tx, utxos) {
962
963
                deferred.notify(Wallet.PAY_PROGRESS_SEND);
964
965
                var data = {
966
                    signed_transaction: tx.toHex(),
967
                    base_transaction: tx.__toBuffer(null, null, false).toString('hex')
968
                };
969
970
                return self.sendTransaction(data, utxos.map(function(utxo) { return utxo['path']; }), checkFee, twoFactorToken, options.prioboost)
971
                    .then(function(result) {
972
                        deferred.notify(Wallet.PAY_PROGRESS_DONE);
973
974
                        if (!result || !result['complete'] || result['complete'] === 'false') {
975
                            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...
976
                        } else {
977
                            return result['txid'];
978
                        }
979
                    });
980
            },
981
            function(e) {
982
                throw e;
983
            }
984
        )
985
            .then(
986
            function(r) { deferred.resolve(r); },
987
            function(e) { deferred.reject(e); }
988
        )
989
        ;
990
    });
991
992
    return deferred.promise;
993
};
994
995
Wallet.prototype.decodeAddress = function(address) {
996
    return Wallet.getAddressAndType(address, this.network, this.useNewCashAddr);
997
};
998
999
function readBech32Address(address, network) {
1000
    var addr;
1001
    var err;
1002
    try {
1003
        addr = bitcoin.address.fromBech32(address, network);
1004
        err = null;
1005
1006
    } catch (_err) {
1007
        err = _err;
1008
    }
1009
1010
    if (!err) {
1011
        // Valid bech32 but invalid network immediately alerts
1012
        if (addr.prefix !== network.bech32) {
1013
            throw new blocktrail.InvalidAddressError("Address invalid on this network");
1014
        }
1015
    }
1016
1017
    return [err, addr];
1018
}
1019
1020
function readCashAddress(address, network) {
1021
    var addr;
1022
    var err;
1023
    try {
1024
        addr = bitcoin.address.fromCashAddress(address);
1025
        err = null;
1026
    } catch (_err) {
1027
        err = _err;
1028
    }
1029
1030
    if (!err) {
1031
        // Valid base58 but invalid network immediately alerts
1032
        if (addr.prefix !== network.cashAddrPrefix) {
1033
            throw new Error(address + ' has an invalid prefix');
1034
        }
1035
    }
1036
1037
    return [err, addr];
1038
}
1039
1040
function readBase58Address(address, network) {
1041
    var addr;
1042
    var err;
1043
    try {
1044
        addr = bitcoin.address.fromBase58Check(address);
1045
        err = null;
1046
    } catch (_err) {
1047
        err = _err;
1048
    }
1049
1050
    if (!err) {
1051
        // Valid base58 but invalid network immediately alerts
1052
        if (addr.version !== network.pubKeyHash && addr.version !== network.scriptHash) {
1053
            throw new blocktrail.InvalidAddressError("Address invalid on this network");
1054
        }
1055
    }
1056
1057
    return [err, addr];
1058
}
1059
1060
Wallet.getAddressAndType = function(address, network, allowCashAddress) {
1061
    var addr;
1062
    var type;
1063
    var err;
1064
1065
    function readAddress(reader, readType) {
1066
        var decoded = reader(address, network);
1067
        if (decoded[0] === null) {
1068
            addr = decoded[1];
1069
            type = readType;
1070
        } else {
1071
            err = decoded[0];
1072
        }
1073
    }
1074
1075
    if (network === bitcoin.networks.bitcoin ||
1076
        network === bitcoin.networks.testnet ||
1077
        network === bitcoin.networks.regtest
1078
    ) {
1079
        readAddress(readBech32Address, "bech32");
1080
    }
1081
1082
    if (!addr && 'cashAddrPrefix' in network && allowCashAddress) {
1083
        readAddress(readCashAddress, "cashaddr");
1084
    }
1085
1086
    if (!addr) {
1087
        readAddress(readBase58Address, "base58");
1088
    }
1089
1090
    if (addr) {
1091
        return {
1092
            address: address,
1093
            decoded: addr,
1094
            type: type
1095
        };
1096
    } else {
1097
        throw new blocktrail.InvalidAddressError(err.message);
1098
    }
1099
};
1100
1101
Wallet.convertPayToOutputs = function(pay, network, allowCashAddr) {
1102
    var send = [];
1103
1104
    var readFunc;
1105
1106
    // Deal with two different forms
1107
    if (Array.isArray(pay)) {
1108
        // output[]
1109
        readFunc = function(i, output, obj) {
1110
            if (typeof output !== "object") {
1111
                throw new Error("Invalid transaction output for numerically indexed list [1]");
1112
            }
1113
1114
            var keys = Object.keys(output);
1115
            if (keys.indexOf("scriptPubKey") !== -1 && keys.indexOf("value") !== -1) {
1116
                obj.scriptPubKey = output["scriptPubKey"];
1117
                obj.value = output["value"];
1118
            } else if (keys.indexOf("address") !== -1 && keys.indexOf("value") !== -1) {
1119
                obj.address = output["address"];
1120
                obj.value = output["value"];
1121
            } else if (keys.length === 2 && output.length === 2 && keys[0] === '0' && keys[1] === '1') {
1122
                obj.address = output[0];
1123
                obj.value = output[1];
1124
            } else {
1125
                throw new Error("Invalid transaction output for numerically indexed list [2]");
1126
            }
1127
        };
1128
    } else if (typeof pay === "object") {
1129
        // map[addr]amount
1130
        readFunc = function(address, value, obj) {
1131
            obj.address = address.trim();
1132
            obj.value = value;
1133
            if (obj.address === Wallet.OP_RETURN) {
1134
                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...
1135
                obj.scriptPubKey = bitcoin.script.nullData.output.encode(datachunk).toString('hex');
1136
                obj.value = 0;
1137
                obj.address = null;
1138
            }
1139
        };
1140
    } else {
1141
        throw new Error("Invalid input");
1142
    }
1143
1144
    Object.keys(pay).forEach(function(key) {
1145
        var obj = {};
1146
        readFunc(key, pay[key], obj);
1147
1148
        if (parseInt(obj.value, 10).toString() !== obj.value.toString()) {
1149
            throw new blocktrail.WalletSendError("Values should be in Satoshis");
1150
        }
1151
1152
        // Remove address, replace with scriptPubKey
1153
        if (typeof obj.address === "string") {
1154
            try {
1155
                var addrAndType = Wallet.getAddressAndType(obj.address, network, allowCashAddr);
1156
                obj.scriptPubKey = bitcoin.address.toOutputScript(addrAndType.address, network, allowCashAddr).toString('hex');
1157
                delete obj.address;
1158
            } catch (e) {
1159
                throw new blocktrail.InvalidAddressError("Invalid address [" + obj.address + "] (" + e.message + ")");
1160
            }
1161
        }
1162
1163
        // Extra checks when the output isn't OP_RETURN
1164
        if (obj.scriptPubKey.slice(0, 2) !== "6a") {
1165
            if (!(obj.value = parseInt(obj.value, 10))) {
1166
                throw new blocktrail.WalletSendError("Values should be non zero");
1167
            } else if (obj.value <= blocktrail.DUST) {
1168
                throw new blocktrail.WalletSendError("Values should be more than dust (" + blocktrail.DUST + ")");
1169
            }
1170
        }
1171
1172
        // Value fully checked now
1173
        obj.value = parseInt(obj.value, 10);
1174
1175
        send.push(obj);
1176
    });
1177
1178
    return send;
1179
};
1180
1181
Wallet.prototype.buildTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) {
1182
    /* jshint -W071 */
1183
    var self = this;
1184
1185
    if (typeof changeAddress === "function") {
1186
        cb = changeAddress;
1187
        changeAddress = null;
1188
    } else if (typeof allowZeroConf === "function") {
1189
        cb = allowZeroConf;
1190
        allowZeroConf = false;
1191
    } else if (typeof randomizeChangeIdx === "function") {
1192
        cb = randomizeChangeIdx;
1193
        randomizeChangeIdx = true;
1194
    } else if (typeof feeStrategy === "function") {
1195
        cb = feeStrategy;
1196
        feeStrategy = null;
1197
    } else if (typeof options === "function") {
1198
        cb = options;
1199
        options = {};
1200
    }
1201
1202
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
1203
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1204
    options = options || {};
1205
1206
    var deferred = q.defer();
1207
    deferred.promise.spreadNodeify(cb);
1208
1209
    q.nextTick(function() {
1210
        var send;
1211
        try {
1212
            send = Wallet.convertPayToOutputs(pay, self.network, self.useNewCashAddr);
1213
        } catch (e) {
1214
            deferred.reject(e);
1215
            return deferred.promise;
1216
        }
1217
1218
        if (!send.length) {
1219
            deferred.reject(new blocktrail.WalletSendError("Need at least one recipient"));
1220
            return deferred.promise;
1221
        }
1222
1223
        deferred.notify(Wallet.PAY_PROGRESS_COIN_SELECTION);
1224
1225
        deferred.resolve(
1226
            self.coinSelection(send, true, allowZeroConf, feeStrategy, options)
1227
            /**
1228
             *
1229
             * @param {Object[]} utxos
1230
             * @param fee
1231
             * @param change
1232
             * @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...
1233
             * @returns {*}
1234
             */
1235
                .spread(function(utxos, fee, change) {
1236
                    var tx, txb, outputs = [];
1237
1238
                    var deferred = q.defer();
1239
1240
                    async.waterfall([
1241
                        /**
1242
                         * prepare
1243
                         *
1244
                         * @param cb
1245
                         */
1246
                        function(cb) {
1247
                            var inputsTotal = utxos.map(function(utxo) {
1248
                                return utxo['value'];
1249
                            }).reduce(function(a, b) {
1250
                                return a + b;
1251
                            });
1252
                            var outputsTotal = send.map(function(output) {
1253
                                return output.value;
1254
                            }).reduce(function(a, b) {
1255
                                return a + b;
1256
                            });
1257
                            var estimatedChange = inputsTotal - outputsTotal - fee;
1258
1259
                            if (estimatedChange > blocktrail.DUST * 2 && estimatedChange !== change) {
1260
                                return cb(new blocktrail.WalletFeeError("the amount of change (" + change + ") " +
1261
                                    "suggested by the coin selection seems incorrect (" + estimatedChange + ")"));
1262
                            }
1263
1264
                            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...
1265
                        },
1266
                        /**
1267
                         * init transaction builder
1268
                         *
1269
                         * @param cb
1270
                         */
1271
                        function(cb) {
1272
                            txb = new bitcoin.TransactionBuilder(self.network);
1273
                            if (self.bitcoinCash) {
1274
                                txb.enableBitcoinCash();
1275
                            }
1276
1277
                            cb();
1278
                        },
1279
                        /**
1280
                         * add UTXOs as inputs
1281
                         *
1282
                         * @param cb
1283
                         */
1284
                        function(cb) {
1285
                            var i;
1286
1287
                            for (i = 0; i < utxos.length; i++) {
1288
                                txb.addInput(utxos[i]['hash'], utxos[i]['idx']);
1289
                            }
1290
1291
                            cb();
1292
                        },
1293
                        /**
1294
                         * build desired outputs
1295
                         *
1296
                         * @param cb
1297
                         */
1298
                        function(cb) {
1299
                            send.forEach(function(_send) {
1300
                                if (_send.scriptPubKey) {
1301
                                    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...
1302
                                } else {
1303
                                    throw new Error("Invalid send");
1304
                                }
1305
                            });
1306
                            cb();
1307
                        },
1308
                        /**
1309
                         * get change address if required
1310
                         *
1311
                         * @param cb
1312
                         */
1313
                        function(cb) {
1314
                            if (change > 0) {
1315
                                if (change <= blocktrail.DUST) {
1316
                                    change = 0; // don't do a change output if it would be a dust output
1317
1318
                                } else {
1319
                                    if (!changeAddress) {
1320
                                        deferred.notify(Wallet.PAY_PROGRESS_CHANGE_ADDRESS);
1321
1322
                                        return self.getNewAddress(self.changeChain, function(err, address) {
1323
                                            if (err) {
1324
                                                return cb(err);
1325
                                            }
1326
                                            changeAddress = address;
1327
                                            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...
1328
                                        });
1329
                                    }
1330
                                }
1331
                            }
1332
1333
                            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...
1334
                        },
1335
                        /**
1336
                         * add change to outputs
1337
                         *
1338
                         * @param cb
1339
                         */
1340
                        function(cb) {
1341
                            if (change > 0) {
1342
                                var changeOutput = {
1343
                                    scriptPubKey: bitcoin.address.toOutputScript(changeAddress, self.network, self.useNewCashAddr),
1344
                                    value: change
1345
                                };
1346
                                if (randomizeChangeIdx) {
1347
                                    outputs.splice(_.random(0, outputs.length), 0, changeOutput);
1348
                                } else {
1349
                                    outputs.push(changeOutput);
1350
                                }
1351
                            }
1352
1353
                            cb();
1354
                        },
1355
                        /**
1356
                         * add outputs to txb
1357
                         *
1358
                         * @param cb
1359
                         */
1360
                        function(cb) {
1361
                            outputs.forEach(function(outputInfo) {
1362
                                txb.addOutput(outputInfo.scriptPubKey, outputInfo.value);
1363
                            });
1364
1365
                            cb();
1366
                        },
1367
                        /**
1368
                         * sign
1369
                         *
1370
                         * @param cb
1371
                         */
1372
                        function(cb) {
1373
                            var i, privKey, path, redeemScript, witnessScript;
1374
1375
                            deferred.notify(Wallet.PAY_PROGRESS_SIGN);
1376
1377
                            for (i = 0; i < utxos.length; i++) {
1378
                                var mode = SignMode.SIGN;
1379
                                if (utxos[i].sign_mode) {
1380
                                    mode = utxos[i].sign_mode;
1381
                                }
1382
1383
                                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...
1384
                                witnessScript = null;
1385
                                if (mode === SignMode.SIGN) {
1386
                                    path = utxos[i]['path'].replace("M", "m");
1387
1388
                                    // todo: regenerate scripts for path and compare for utxo (paranoid mode)
1389
                                    if (self.primaryPrivateKey) {
1390
                                        privKey = Wallet.deriveByPath(self.primaryPrivateKey, path, "m").keyPair;
1391
                                    } else if (self.backupPrivateKey) {
1392
                                        privKey = Wallet.deriveByPath(self.backupPrivateKey, path.replace(/^m\/(\d+)\'/, 'm/$1'), "m").keyPair;
1393
                                    } else {
1394
                                        throw new Error("No master privateKey present");
1395
                                    }
1396
1397
                                    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...
1398
                                    if (typeof utxos[i]['witness_script'] === 'string') {
1399
                                        witnessScript = new Buffer(utxos[i]['witness_script'], 'hex');
1400
                                    }
1401
1402
                                    var sigHash = bitcoin.Transaction.SIGHASH_ALL;
1403
                                    if (self.bitcoinCash) {
1404
                                        sigHash |= bitcoin.Transaction.SIGHASH_BITCOINCASHBIP143;
1405
                                    }
1406
1407
                                    txb.sign(i, privKey, redeemScript, sigHash, utxos[i].value, witnessScript);
1408
                                }
1409
                            }
1410
1411
                            tx = txb.buildIncomplete();
1412
1413
                            cb();
1414
                        },
1415
                        /**
1416
                         * estimate fee to verify that the API is not providing us wrong data
1417
                         *
1418
                         * @param cb
1419
                         */
1420
                        function(cb) {
1421
                            var estimatedFee = Wallet.estimateVsizeFee(tx, utxos);
1422
1423
                            if (self.sdk.feeSanityCheck) {
1424
                                switch (feeStrategy) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1425
                                    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...
1426
                                        if (Math.abs(estimatedFee - fee) > blocktrail.BASE_FEE) {
1427
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1428
                                                "seems incorrect (" + estimatedFee + ") for FEE_STRATEGY_BASE_FEE"));
1429
                                        }
1430
                                    break;
1431
1432
                                    case Wallet.FEE_STRATEGY_HIGH_PRIORITY:
1433
                                    case Wallet.FEE_STRATEGY_OPTIMAL:
1434
                                    case Wallet.FEE_STRATEGY_LOW_PRIORITY:
1435
                                        if (fee > estimatedFee * self.feeSanityCheckBaseFeeMultiplier) {
1436
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1437
                                                "seems awefully high (" + estimatedFee + ") for FEE_STRATEGY_OPTIMAL"));
1438
                                        }
1439
                                    break;
1440
                                }
1441
                            }
1442
1443
                            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...
1444
                        }
1445
                    ], function(err) {
1446
                        if (err) {
1447
                            deferred.reject(new blocktrail.WalletSendError(err));
1448
                            return;
1449
                        }
1450
1451
                        deferred.resolve([tx, utxos]);
1452
                    });
1453
1454
                    return deferred.promise;
1455
                }
1456
            )
1457
        );
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...
1458
    });
1459
1460
    return deferred.promise;
1461
};
1462
1463
1464
/**
1465
 * use the API to get the best inputs to use based on the outputs
1466
 *
1467
 * @param pay               array       {'address': (int)value}     coins to send
1468
 * @param [lockUTXO]        bool        lock UTXOs for a few seconds to allow for transaction to be created
1469
 * @param [allowZeroConf]   bool        allow zero confirmation unspent outputs to be used in coin selection
1470
 * @param [feeStrategy]     string      defaults to FEE_STRATEGY_OPTIMAL
1471
 * @param [options]         object
1472
 * @param [cb]              function    callback(err, utxos, fee, change)
1473
 * @returns {q.Promise}
1474
 */
1475
Wallet.prototype.coinSelection = function(pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1476
    var self = this;
1477
1478
    if (typeof lockUTXO === "function") {
1479
        cb = lockUTXO;
1480
        lockUTXO = true;
1481
    } else if (typeof allowZeroConf === "function") {
1482
        cb = allowZeroConf;
1483
        allowZeroConf = false;
1484
    } else if (typeof feeStrategy === "function") {
1485
        cb = feeStrategy;
1486
        feeStrategy = null;
1487
    } else if (typeof options === "function") {
1488
        cb = options;
1489
        options = {};
1490
    }
1491
1492
    lockUTXO = typeof lockUTXO !== "undefined" ? lockUTXO : true;
1493
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1494
    options = options || {};
1495
1496
    var send;
1497
    try {
1498
        send = Wallet.convertPayToOutputs(pay, self.network, self.useNewCashAddr);
1499
    } catch (e) {
1500
        var deferred = q.defer();
1501
        deferred.promise.nodeify(cb);
1502
        deferred.reject(e);
1503
        return deferred.promise;
1504
    }
1505
1506
    return self.sdk.coinSelection(self.identifier, send, lockUTXO, allowZeroConf, feeStrategy, options, cb);
1507
};
1508
1509
/**
1510
 * send the transaction using the API
1511
 *
1512
 * @param txHex             string      partially signed transaction as hex string
1513
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1514
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1515
 * @param [twoFactorToken]  string      2FA token
1516
 * @param prioboost         bool
1517
 * @param [cb]              function    callback(err, txHash)
1518
 * @returns {q.Promise}
1519
 */
1520
Wallet.prototype.sendTransaction = function(txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1521
    var self = this;
1522
1523
    if (typeof twoFactorToken === "function") {
1524
        cb = twoFactorToken;
1525
        twoFactorToken = null;
1526
        prioboost = false;
1527
    } else if (typeof prioboost === "function") {
1528
        cb = twoFactorToken;
1529
        prioboost = false;
1530
    }
1531
1532
    var deferred = q.defer();
1533
    deferred.promise.nodeify(cb);
1534
1535
    self.sdk.sendTransaction(self.identifier, txHex, paths, checkFee, twoFactorToken, prioboost)
1536
        .then(
1537
            function(result) {
1538
                deferred.resolve(result);
1539
            },
1540
            function(e) {
1541
                if (e.requires_2fa) {
1542
                    deferred.reject(new blocktrail.WalletMissing2FAError());
1543
                } else if (e.message.match(/Invalid two_factor_token/)) {
1544
                    deferred.reject(new blocktrail.WalletInvalid2FAError());
1545
                } else {
1546
                    deferred.reject(e);
1547
                }
1548
            }
1549
        )
1550
    ;
1551
1552
    return deferred.promise;
1553
};
1554
1555
/**
1556
 * setup a webhook for this wallet
1557
 *
1558
 * @param url           string      URL to receive webhook events
1559
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1560
 * @param [cb]          function    callback(err, webhook)
1561
 * @returns {q.Promise}
1562
 */
1563
Wallet.prototype.setupWebhook = function(url, identifier, cb) {
1564
    var self = this;
1565
1566
    if (typeof identifier === "function") {
1567
        cb = identifier;
1568
        identifier = null;
1569
    }
1570
1571
    identifier = identifier || ('WALLET-' + self.identifier);
1572
1573
    return self.sdk.setupWalletWebhook(self.identifier, identifier, url, cb);
1574
};
1575
1576
/**
1577
 * delete a webhook that was created for this wallet
1578
 *
1579
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1580
 * @param [cb]          function    callback(err, success)
1581
 * @returns {q.Promise}
1582
 */
1583
Wallet.prototype.deleteWebhook = function(identifier, cb) {
1584
    var self = this;
1585
1586
    if (typeof identifier === "function") {
1587
        cb = identifier;
1588
        identifier = null;
1589
    }
1590
1591
    identifier = identifier || ('WALLET-' + self.identifier);
1592
1593
    return self.sdk.deleteWalletWebhook(self.identifier, identifier, cb);
1594
};
1595
1596
/**
1597
 * get all transactions for the wallet (paginated)
1598
 *
1599
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1600
 * @param [cb]      function    callback(err, transactions)
1601
 * @returns {q.Promise}
1602
 */
1603
Wallet.prototype.transactions = function(params, cb) {
1604
    var self = this;
1605
1606
    return self.sdk.walletTransactions(self.identifier, params, cb);
1607
};
1608
1609
Wallet.prototype.maxSpendable = function(allowZeroConf, feeStrategy, options, cb) {
1610
    var self = this;
1611
1612
    if (typeof allowZeroConf === "function") {
1613
        cb = allowZeroConf;
1614
        allowZeroConf = false;
1615
    } else if (typeof feeStrategy === "function") {
1616
        cb = feeStrategy;
1617
        feeStrategy = null;
1618
    } else if (typeof options === "function") {
1619
        cb = options;
1620
        options = {};
1621
    }
1622
1623
    if (typeof allowZeroConf === "object") {
1624
        options = allowZeroConf;
1625
        allowZeroConf = false;
1626
    } else if (typeof feeStrategy === "object") {
1627
        options = feeStrategy;
1628
        feeStrategy = null;
1629
    }
1630
1631
    options = options || {};
1632
1633
    if (typeof options.allowZeroConf !== "undefined") {
1634
        allowZeroConf = options.allowZeroConf;
1635
    }
1636
    if (typeof options.feeStrategy !== "undefined") {
1637
        feeStrategy = options.feeStrategy;
1638
    }
1639
1640
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1641
1642
    return self.sdk.walletMaxSpendable(self.identifier, allowZeroConf, feeStrategy, options, cb);
1643
};
1644
1645
/**
1646
 * get all addresses for the wallet (paginated)
1647
 *
1648
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1649
 * @param [cb]      function    callback(err, addresses)
1650
 * @returns {q.Promise}
1651
 */
1652
Wallet.prototype.addresses = function(params, cb) {
1653
    var self = this;
1654
1655
    return self.sdk.walletAddresses(self.identifier, params, cb);
1656
};
1657
1658
/**
1659
 * @param address   string      the address to label
1660
 * @param label     string      the label
1661
 * @param [cb]      function    callback(err, res)
1662
 * @returns {q.Promise}
1663
 */
1664
Wallet.prototype.labelAddress = function(address, label, cb) {
1665
    var self = this;
1666
1667
    return self.sdk.labelWalletAddress(self.identifier, address, label, cb);
1668
};
1669
1670
/**
1671
 * get all UTXOs for the wallet (paginated)
1672
 *
1673
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1674
 * @param [cb]      function    callback(err, addresses)
1675
 * @returns {q.Promise}
1676
 */
1677
Wallet.prototype.utxos = function(params, cb) {
1678
    var self = this;
1679
1680
    return self.sdk.walletUTXOs(self.identifier, params, cb);
1681
};
1682
1683
Wallet.prototype.unspentOutputs = Wallet.prototype.utxos;
1684
1685
/**
1686
 * sort list of pubkeys to be used in a multisig redeemscript
1687
 *  sorted in lexicographical order on the hex of the pubkey
1688
 *
1689
 * @param pubKeys   {bitcoin.HDNode[]}
1690
 * @returns string[]
1691
 */
1692
Wallet.sortMultiSigKeys = function(pubKeys) {
1693
    pubKeys.sort(function(key1, key2) {
1694
        return key1.toString('hex').localeCompare(key2.toString('hex'));
1695
    });
1696
1697
    return pubKeys;
1698
};
1699
1700
/**
1701
 * determine how much fee is required based on the inputs and outputs
1702
 *  this is an estimation, not a proper 100% correct calculation
1703
 *
1704
 * @todo: mark deprecated in favor of estimations where UTXOS are known
1705
 * @param {bitcoin.Transaction} tx
1706
 * @param {int} feePerKb when not null use this feePerKb, otherwise use BASE_FEE legacy calculation
1707
 * @returns {number}
1708
 */
1709
Wallet.estimateIncompleteTxFee = function(tx, feePerKb) {
1710
    var size = Wallet.estimateIncompleteTxSize(tx);
1711
    var sizeKB = size / 1000;
1712
    var sizeKBCeil = Math.ceil(size / 1000);
1713
1714
    if (feePerKb) {
1715
        return parseInt(sizeKB * feePerKb, 10);
1716
    } else {
1717
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1718
    }
1719
};
1720
1721
/**
1722
 * Takes tx and utxos, computing their estimated vsize,
1723
 * and uses feePerKb (or BASEFEE as default) to estimate
1724
 * the number of satoshis in fee.
1725
 *
1726
 * @param {bitcoin.Transaction} tx
1727
 * @param {Array} utxos
1728
 * @param feePerKb
1729
 * @returns {Number}
1730
 */
1731
Wallet.estimateVsizeFee = function(tx, utxos, feePerKb) {
1732
    var vsize = SizeEstimation.estimateTxVsize(tx, utxos);
1733
    var sizeKB = vsize / 1000;
1734
    var sizeKBCeil = Math.ceil(vsize / 1000);
1735
1736
    if (feePerKb) {
1737
        return parseInt(sizeKB * feePerKb, 10);
1738
    } else {
1739
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1740
    }
1741
};
1742
1743
/**
1744
 * determine how much fee is required based on the inputs and outputs
1745
 *  this is an estimation, not a proper 100% correct calculation
1746
 *
1747
 * @param {bitcoin.Transaction} tx
1748
 * @returns {number}
1749
 */
1750
Wallet.estimateIncompleteTxSize = function(tx) {
1751
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1752
1753
    size += tx.outs.length * 34;
1754
1755
    tx.ins.forEach(function(txin) {
1756
        var scriptSig = txin.script,
1757
            scriptType = bitcoin.script.classifyInput(scriptSig);
1758
1759
        var multiSig = [2, 3]; // tmp hardcoded
1760
1761
        // Re-classify if P2SH
1762
        if (!multiSig && scriptType === 'scripthash') {
1763
            var sigChunks = bitcoin.script.decompile(scriptSig);
1764
            var redeemScript = sigChunks.slice(-1)[0];
1765
            scriptSig = bitcoin.script.compile(sigChunks.slice(0, -1));
1766
            scriptType = bitcoin.script.classifyInput(scriptSig);
1767
1768
            if (bitcoin.script.classifyOutput(redeemScript) !== scriptType) {
1769
                throw new blocktrail.TransactionInputError('Non-matching scriptSig and scriptPubKey in input');
1770
            }
1771
1772
            // figure out M of N for multisig (code from internal usage of bitcoinjs)
1773
            if (scriptType === 'multisig') {
1774
                var rsChunks = bitcoin.script.decompile(redeemScript);
1775
                var mOp = rsChunks[0];
1776
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1777
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1778
                }
1779
1780
                var nOp = rsChunks[redeemScript.chunks.length - 2];
1781
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1782
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1783
                }
1784
1785
                var m = mOp - (bitcoin.opcodes.OP_1 - 1);
1786
                var n = nOp - (bitcoin.opcodes.OP_1 - 1);
1787
                if (n < m) {
1788
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1789
                }
1790
1791
                multiSig = [m, n];
1792
            }
1793
        }
1794
1795
        if (multiSig) {
1796
            size += (
1797
                32 + // txhash
1798
                4 + // idx
1799
                3 + // scriptVarInt[>=253]
1800
                1 + // OP_0
1801
                ((1 + 72) * multiSig[0]) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1802
                (2 + 105) + // OP_PUSHDATA[>=75] + script
1803
                4 // sequence
1804
            );
1805
1806
        } else {
1807
            size += 32 + // txhash
1808
                4 + // idx
1809
                73 + // sig
1810
                34 + // script
1811
                4; // sequence
1812
        }
1813
    });
1814
1815
    return size;
1816
};
1817
1818
/**
1819
 * determine how much fee is required based on the amount of inputs and outputs
1820
 *  this is an estimation, not a proper 100% correct calculation
1821
 *  this asumes all inputs are 2of3 multisig
1822
 *
1823
 * @todo: mark deprecated in favor of situations where UTXOS are known
1824
 * @param txinCnt       {number}
1825
 * @param txoutCnt      {number}
1826
 * @returns {number}
1827
 */
1828
Wallet.estimateFee = function(txinCnt, txoutCnt) {
1829
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1830
1831
    size += txoutCnt * 34;
1832
1833
    size += (
1834
            32 + // txhash
1835
            4 + // idx
1836
            3 + // scriptVarInt[>=253]
1837
            1 + // OP_0
1838
            ((1 + 72) * 2) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1839
            (2 + 105) + // OP_PUSHDATA[>=75] + script
1840
            4 // sequence
1841
        ) * txinCnt;
1842
1843
    var sizeKB = Math.ceil(size / 1000);
1844
1845
    return sizeKB * blocktrail.BASE_FEE;
1846
};
1847
1848
/**
1849
 * create derived key from parent key by path
1850
 *
1851
 * @param hdKey     {bitcoin.HDNode}
1852
 * @param path      string
1853
 * @param keyPath   string
1854
 * @returns {bitcoin.HDNode}
1855
 */
1856
Wallet.deriveByPath = function(hdKey, path, keyPath) {
1857
    keyPath = keyPath || (!!hdKey.keyPair.d ? "m" : "M");
1858
1859
    if (path[0].toLowerCase() !== "m" || keyPath[0].toLowerCase() !== "m") {
1860
        throw new blocktrail.KeyPathError("Wallet.deriveByPath only works with absolute paths. (" + path + ", " + keyPath + ")");
1861
    }
1862
1863
    if (path[0] === "m" && keyPath[0] === "M") {
1864
        throw new blocktrail.KeyPathError("Wallet.deriveByPath can't derive private path from public parent. (" + path + ", " + keyPath + ")");
1865
    }
1866
1867
    // if the desired path is public while the input is private
1868
    var toPublic = path[0] === "M" && keyPath[0] === "m";
1869
    if (toPublic) {
1870
        // derive the private path, convert to public when returning
1871
        path[0] = "m";
1872
    }
1873
1874
    // keyPath should be the parent parent of path
1875
    if (path.toLowerCase().indexOf(keyPath.toLowerCase()) !== 0) {
1876
        throw new blocktrail.KeyPathError("Wallet.derivePath requires path (" + path + ") to be a child of keyPath (" + keyPath + ")");
1877
    }
1878
1879
    // remove the part of the path we already have
1880
    path = path.substr(keyPath.length);
1881
1882
    // iterate over the chunks and derive
1883
    var newKey = hdKey;
1884
    path.replace(/^\//, "").split("/").forEach(function(chunk) {
1885
        if (!chunk) {
1886
            return;
1887
        }
1888
1889
        if (chunk.indexOf("'") !== -1) {
1890
            chunk = parseInt(chunk.replace("'", ""), 10) + bitcoin.HDNode.HIGHEST_BIT;
1891
        }
1892
1893
        newKey = newKey.derive(parseInt(chunk, 10));
1894
    });
1895
1896
    if (toPublic) {
1897
        return newKey.neutered();
1898
    } else {
1899
        return newKey;
1900
    }
1901
};
1902
1903
module.exports = Wallet;
1904