Completed
Push — master ( 87049c...725322 )
by Tobias
13s
created

lib/wallet.js   F

Complexity

Total Complexity 324
Complexity/F 2.63

Size

Lines of Code 1884
Function Count 123

Duplication

Duplicated Lines 68
Ratio 3.61 %

Importance

Changes 13
Bugs 2 Features 3
Metric Value
cc 0
wmc 324
c 13
b 2
f 3
nc 0
mnd 6
bc 313
fnc 123
dl 68
loc 1884
rs 2.4
bpm 2.5447
cpm 2.6341
noi 22

45 Functions

Rating   Name   Duplication   Size   Complexity  
B Wallet.upgradeToV3 0 24 1
B Wallet.passwordChange 0 33 1
A Wallet.unlock 0 54 1
B Wallet.getWalletScriptByPath 0 39 3
B Wallet.getNewAddress 4 88 5
A Wallet.unlockV3 0 63 1
B Wallet.upgradeKeyIndex 0 29 2
A Wallet.unlockV1 0 13 3
A Wallet.getAddressByPath 0 3 1
A Wallet.getBlocktrailPublicKey 0 15 2
A Wallet.lock 0 10 1
A Wallet.getInfo 0 12 1
A Wallet.getRedeemScriptByPath 0 3 1
A Wallet.getBalance 8 15 1
A Wallet.isSegwit 0 3 1
A Wallet.getPrimaryPublicKey 0 18 3
B Wallet.doPasswordChange 0 43 1
C wallet.js ➔ Wallet 0 90 8
A wallet.js ➔ readBech32Address 0 20 4
B Wallet.deleteWallet 3 29 3
B Wallet._upgradeV1ToV3 14 33 1
A Wallet.decodeAddress 0 3 1
B Wallet._upgradeV2ToV3 18 30 1
A Wallet.unlockV2 0 64 1
D Wallet.pay 0 82 10
A Wallet.labelAddress 0 5 1
A Wallet.deleteWebhook 0 12 2
D Wallet.maxSpendable 0 35 8
C Wallet.deriveByPath 0 46 9
A Wallet.estimateVsizeFee 0 11 2
A Wallet.sortMultiSigKeys 0 7 1
B Wallet.sendTransaction 0 34 3
B wallet.js ➔ readBase58Address 0 19 5
A Wallet.setupWebhook 0 12 2
A Wallet.utxos 0 5 1
A Wallet.addresses 0 5 1
C Wallet.coinSelection 0 33 7
C Wallet.buildTransaction 0 281 7
A Wallet.transactions 0 5 1
B Wallet.convertPayToOutputs 0 79 3
A Wallet.estimateIncompleteTxFee 0 11 2
D Wallet.getAddressAndType 0 40 9
B wallet.js ➔ readCashAddress 0 28 6
A Wallet.estimateIncompleteTxSize 0 67 1
A Wallet.estimateFee 0 19 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like lib/wallet.js 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 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
 *
837
 * @param [force]   bool            ignore warnings (such as non-zero balance)
838
 * @param [cb]      function        callback(err, success)
839
 * @returns {q.Promise}
840
 */
841
Wallet.prototype.deleteWallet = function(force, cb) {
842
    var self = this;
843
844
    if (typeof force === "function") {
845
        cb = force;
846
        force = false;
847
    }
848
849
    var deferred = q.defer();
850
    deferred.promise.nodeify(cb);
851
852
    if (self.locked) {
853
        deferred.reject(new blocktrail.WalletDeleteError("Wallet needs to be unlocked to delete wallet"));
854
        return deferred.promise;
855
    }
856
857
    var checksum = self.primaryPrivateKey.getAddress();
858
    var privBuf = self.primaryPrivateKey.keyPair.d.toBuffer(32);
859
    var signature = bitcoinMessage.sign(checksum, self.network.messagePrefix, privBuf, true).toString('base64');
860
861
    deferred.resolve(
862
        self.sdk.deleteWallet(self.identifier, checksum, signature, force)
863
            .then(function(result) {
864
                return result.deleted;
865
            })
866
    );
867 View Code Duplication
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
/**
872
 * create, sign and send a transaction
873
 *
874
 * @param pay                   array       {'address': (int)value}     coins to send
875
 * @param [changeAddress]       bool        change address to use (auto generated if NULL)
876
 * @param [allowZeroConf]       bool        allow zero confirmation unspent outputs to be used in coin selection
877
 * @param [randomizeChangeIdx]  bool        randomize the index of the change output (default TRUE, only disable if you have a good reason to)
878
 * @param [feeStrategy]         string      defaults to Wallet.FEE_STRATEGY_OPTIMAL
879
 * @param [twoFactorToken]      string      2FA token
880
 * @param options
881
 * @param [cb]                  function    callback(err, txHash)
882
 * @returns {q.Promise}
883
 */
884
Wallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) {
885
886
    /* jshint -W071 */
887
    var self = this;
888
889
    if (typeof changeAddress === "function") {
890
        cb = changeAddress;
891
        changeAddress = null;
892
    } else if (typeof allowZeroConf === "function") {
893
        cb = allowZeroConf;
894
        allowZeroConf = false;
895
    } else if (typeof randomizeChangeIdx === "function") {
896
        cb = randomizeChangeIdx;
897
        randomizeChangeIdx = true;
898
    } else if (typeof feeStrategy === "function") {
899
        cb = feeStrategy;
900
        feeStrategy = null;
901
    } else if (typeof twoFactorToken === "function") {
902
        cb = twoFactorToken;
903
        twoFactorToken = null;
904
    } else if (typeof options === "function") {
905
        cb = options;
906
        options = {};
907
    }
908
909
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
910
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
911
    options = options || {};
912
    var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true;
913
914
    var deferred = q.defer();
915
    deferred.promise.nodeify(cb);
916
917
    if (self.locked) {
918
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to send coins"));
919
        return deferred.promise;
920
    }
921
922
    q.nextTick(function() {
923
        deferred.notify(Wallet.PAY_PROGRESS_START);
924
        self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options)
925
            .then(
926
            function(r) { return r; },
927
            function(e) { deferred.reject(e); },
928
            function(progress) {
929
                deferred.notify(progress);
930
            }
931
        )
932
            .spread(
933
            function(tx, utxos) {
934
935
                deferred.notify(Wallet.PAY_PROGRESS_SEND);
936
937
                var data = {
938
                    signed_transaction: tx.toHex(),
939
                    base_transaction: tx.__toBuffer(null, null, false).toString('hex')
940
                };
941
942
                return self.sendTransaction(data, utxos.map(function(utxo) { return utxo['path']; }), checkFee, twoFactorToken, options.prioboost)
943
                    .then(function(result) {
944
                        deferred.notify(Wallet.PAY_PROGRESS_DONE);
945
946
                        if (!result || !result['complete'] || result['complete'] === 'false') {
947
                            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...
948
                        } else {
949
                            return result['txid'];
950
                        }
951
                    });
952
            },
953
            function(e) {
954
                throw e;
955
            }
956
        )
957
            .then(
958
            function(r) { deferred.resolve(r); },
959
            function(e) { deferred.reject(e); }
960
        )
961
        ;
962
    });
963
964
    return deferred.promise;
965
};
966
967
Wallet.prototype.decodeAddress = function(address) {
968
    return Wallet.getAddressAndType(address, this.network, this.useNewCashAddr);
969
};
970
971
function readBech32Address(address, network) {
972
    var addr;
973
    var err;
974
    try {
975
        addr = bitcoin.address.fromBech32(address, network);
976
        err = null;
977
978
    } catch (_err) {
979
        err = _err;
980
    }
981
982
    if (!err) {
983
        // Valid bech32 but invalid network immediately alerts
984
        if (addr.prefix !== network.bech32) {
985
            throw new blocktrail.InvalidAddressError("Address invalid on this network");
986
        }
987
    }
988
989
    return [err, addr];
990
}
991
992
function readCashAddress(address, network) {
993
    var addr;
994
    var err;
995
    try {
996
        addr = bitcoin.address.fromCashAddress(address);
997
        err = null;
998
    } catch (_err) {
999
        err = _err;
1000
    }
1001
1002
    if (err) {
1003
        try {
1004
            addr = bitcoin.address.fromCashAddress(network.cashAddrPrefix + ':' + address);
1005
            err = null;
1006
        } catch (_err) {
1007
            err = _err;
1008
        }
1009
    }
1010
1011
    if (!err) {
1012
        // Valid base58 but invalid network immediately alerts
1013
        if (addr.prefix !== network.cashAddrPrefix) {
1014
            throw new Error(address + ' has an invalid prefix');
1015
        }
1016
    }
1017
1018
    return [err, addr];
1019
}
1020
1021
function readBase58Address(address, network) {
1022
    var addr;
1023
    var err;
1024
    try {
1025
        addr = bitcoin.address.fromBase58Check(address);
1026
        err = null;
1027
    } catch (_err) {
1028
        err = _err;
1029
    }
1030
1031
    if (!err) {
1032
        // Valid base58 but invalid network immediately alerts
1033
        if (addr.version !== network.pubKeyHash && addr.version !== network.scriptHash) {
1034
            throw new blocktrail.InvalidAddressError("Address invalid on this network");
1035
        }
1036
    }
1037
1038
    return [err, addr];
1039
}
1040
1041
Wallet.getAddressAndType = function(address, network, allowCashAddress) {
1042
    var addr;
1043
    var type;
1044
    var err;
1045
1046
    function readAddress(reader, readType) {
1047
        var decoded = reader(address, network);
1048
        if (decoded[0] === null) {
1049
            addr = decoded[1];
1050
            type = readType;
1051
        } else {
1052
            err = decoded[0];
1053
        }
1054
    }
1055
1056
    if (network === bitcoin.networks.bitcoin ||
1057
        network === bitcoin.networks.testnet ||
1058
        network === bitcoin.networks.regtest
1059
    ) {
1060
        readAddress(readBech32Address, "bech32");
1061
    }
1062
1063
    if (!addr && 'cashAddrPrefix' in network && allowCashAddress) {
1064
        readAddress(readCashAddress, "cashaddr");
1065
    }
1066
1067
    if (!addr) {
1068
        readAddress(readBase58Address, "base58");
1069
    }
1070
1071
    if (addr) {
1072
        return {
1073
            address: address,
1074
            decoded: addr,
1075
            type: type
1076
        };
1077
    } else {
1078
        throw new blocktrail.InvalidAddressError(err.message);
1079
    }
1080
};
1081
1082
Wallet.convertPayToOutputs = function(pay, network, allowCashAddr) {
1083
    var send = [];
1084
1085
    var readFunc;
1086
1087
    // Deal with two different forms
1088
    if (Array.isArray(pay)) {
1089
        // output[]
1090
        readFunc = function(i, output, obj) {
1091
            if (typeof output !== "object") {
1092
                throw new Error("Invalid transaction output for numerically indexed list [1]");
1093
            }
1094
1095
            var keys = Object.keys(output);
1096
            if (keys.indexOf("scriptPubKey") !== -1 && keys.indexOf("value") !== -1) {
1097
                obj.scriptPubKey = output["scriptPubKey"];
1098
                obj.value = output["value"];
1099
            } else if (keys.indexOf("address") !== -1 && keys.indexOf("value") !== -1) {
1100
                obj.address = output["address"];
1101
                obj.value = output["value"];
1102
            } else if (keys.length === 2 && output.length === 2 && keys[0] === '0' && keys[1] === '1') {
1103
                obj.address = output[0];
1104
                obj.value = output[1];
1105
            } else {
1106
                throw new Error("Invalid transaction output for numerically indexed list [2]");
1107
            }
1108
        };
1109
    } else if (typeof pay === "object") {
1110
        // map[addr]amount
1111
        readFunc = function(address, value, obj) {
1112
            obj.address = address.trim();
1113
            obj.value = value;
1114
            if (obj.address === Wallet.OP_RETURN) {
1115
                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...
1116
                obj.scriptPubKey = bitcoin.script.nullData.output.encode(datachunk).toString('hex');
1117
                obj.value = 0;
1118
                obj.address = null;
1119
            }
1120
        };
1121
    } else {
1122
        throw new Error("Invalid input");
1123
    }
1124
1125
    Object.keys(pay).forEach(function(key) {
1126
        var obj = {};
1127
        readFunc(key, pay[key], obj);
1128
1129
        if (parseInt(obj.value, 10).toString() !== obj.value.toString()) {
1130
            throw new blocktrail.WalletSendError("Values should be in Satoshis");
1131
        }
1132
1133
        // Remove address, replace with scriptPubKey
1134
        if (typeof obj.address === "string") {
1135
            try {
1136
                var addrAndType = Wallet.getAddressAndType(obj.address, network, allowCashAddr);
1137
                obj.scriptPubKey = bitcoin.address.toOutputScript(addrAndType.address, network, allowCashAddr).toString('hex');
1138
                delete obj.address;
1139
            } catch (e) {
1140
                throw new blocktrail.InvalidAddressError("Invalid address [" + obj.address + "] (" + e.message + ")");
1141
            }
1142
        }
1143
1144
        // Extra checks when the output isn't OP_RETURN
1145
        if (obj.scriptPubKey.slice(0, 2) !== "6a") {
1146
            if (!(obj.value = parseInt(obj.value, 10))) {
1147
                throw new blocktrail.WalletSendError("Values should be non zero");
1148
            } else if (obj.value <= blocktrail.DUST) {
1149
                throw new blocktrail.WalletSendError("Values should be more than dust (" + blocktrail.DUST + ")");
1150
            }
1151
        }
1152
1153
        // Value fully checked now
1154
        obj.value = parseInt(obj.value, 10);
1155
1156
        send.push(obj);
1157
    });
1158
1159
    return send;
1160
};
1161
1162
Wallet.prototype.buildTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) {
1163
    /* jshint -W071 */
1164
    var self = this;
1165
1166
    if (typeof changeAddress === "function") {
1167
        cb = changeAddress;
1168
        changeAddress = null;
1169
    } else if (typeof allowZeroConf === "function") {
1170
        cb = allowZeroConf;
1171
        allowZeroConf = false;
1172
    } else if (typeof randomizeChangeIdx === "function") {
1173
        cb = randomizeChangeIdx;
1174
        randomizeChangeIdx = true;
1175
    } else if (typeof feeStrategy === "function") {
1176
        cb = feeStrategy;
1177
        feeStrategy = null;
1178
    } else if (typeof options === "function") {
1179
        cb = options;
1180
        options = {};
1181
    }
1182
1183
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
1184
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1185
    options = options || {};
1186
1187
    var deferred = q.defer();
1188
    deferred.promise.spreadNodeify(cb);
1189
1190
    q.nextTick(function() {
1191
        var send;
1192
        try {
1193
            send = Wallet.convertPayToOutputs(pay, self.network, self.useNewCashAddr);
1194
        } catch (e) {
1195
            deferred.reject(e);
1196
            return deferred.promise;
1197
        }
1198
1199
        if (!send.length) {
1200
            deferred.reject(new blocktrail.WalletSendError("Need at least one recipient"));
1201
            return deferred.promise;
1202
        }
1203
1204
        deferred.notify(Wallet.PAY_PROGRESS_COIN_SELECTION);
1205
1206
        deferred.resolve(
1207
            self.coinSelection(send, true, allowZeroConf, feeStrategy, options)
1208
            /**
1209
             *
1210
             * @param {Object[]} utxos
1211
             * @param fee
1212
             * @param change
1213
             * @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...
1214
             * @returns {*}
1215
             */
1216
                .spread(function(utxos, fee, change) {
1217
                    var tx, txb, outputs = [];
1218
1219
                    var deferred = q.defer();
1220
1221
                    async.waterfall([
1222
                        /**
1223
                         * prepare
1224
                         *
1225
                         * @param cb
1226
                         */
1227
                        function(cb) {
1228
                            var inputsTotal = utxos.map(function(utxo) {
1229
                                return utxo['value'];
1230
                            }).reduce(function(a, b) {
1231
                                return a + b;
1232
                            });
1233
                            var outputsTotal = send.map(function(output) {
1234
                                return output.value;
1235
                            }).reduce(function(a, b) {
1236
                                return a + b;
1237
                            });
1238
                            var estimatedChange = inputsTotal - outputsTotal - fee;
1239
1240
                            if (estimatedChange > blocktrail.DUST * 2 && estimatedChange !== change) {
1241
                                return cb(new blocktrail.WalletFeeError("the amount of change (" + change + ") " +
1242
                                    "suggested by the coin selection seems incorrect (" + estimatedChange + ")"));
1243
                            }
1244
1245
                            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...
1246
                        },
1247
                        /**
1248
                         * init transaction builder
1249
                         *
1250
                         * @param cb
1251
                         */
1252
                        function(cb) {
1253
                            txb = new bitcoin.TransactionBuilder(self.network);
1254
                            if (self.bitcoinCash) {
1255
                                txb.enableBitcoinCash();
1256
                            }
1257
1258
                            cb();
1259
                        },
1260
                        /**
1261
                         * add UTXOs as inputs
1262
                         *
1263
                         * @param cb
1264
                         */
1265
                        function(cb) {
1266
                            var i;
1267
1268
                            for (i = 0; i < utxos.length; i++) {
1269
                                txb.addInput(utxos[i]['hash'], utxos[i]['idx']);
1270
                            }
1271
1272
                            cb();
1273
                        },
1274
                        /**
1275
                         * build desired outputs
1276
                         *
1277
                         * @param cb
1278
                         */
1279
                        function(cb) {
1280
                            send.forEach(function(_send) {
1281
                                if (_send.scriptPubKey) {
1282
                                    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...
1283
                                } else {
1284
                                    throw new Error("Invalid send");
1285
                                }
1286
                            });
1287
                            cb();
1288
                        },
1289
                        /**
1290
                         * get change address if required
1291
                         *
1292
                         * @param cb
1293
                         */
1294
                        function(cb) {
1295
                            if (change > 0) {
1296
                                if (change <= blocktrail.DUST) {
1297
                                    change = 0; // don't do a change output if it would be a dust output
1298
1299
                                } else {
1300
                                    if (!changeAddress) {
1301
                                        deferred.notify(Wallet.PAY_PROGRESS_CHANGE_ADDRESS);
1302
1303
                                        return self.getNewAddress(self.changeChain, function(err, address) {
1304
                                            if (err) {
1305
                                                return cb(err);
1306
                                            }
1307
                                            changeAddress = address;
1308
                                            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...
1309
                                        });
1310
                                    }
1311
                                }
1312
                            }
1313
1314
                            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...
1315
                        },
1316
                        /**
1317
                         * add change to outputs
1318
                         *
1319
                         * @param cb
1320
                         */
1321
                        function(cb) {
1322
                            if (change > 0) {
1323
                                var changeOutput = {
1324
                                    scriptPubKey: bitcoin.address.toOutputScript(changeAddress, self.network, self.useNewCashAddr),
1325
                                    value: change
1326
                                };
1327
                                if (randomizeChangeIdx) {
1328
                                    outputs.splice(_.random(0, outputs.length), 0, changeOutput);
1329
                                } else {
1330
                                    outputs.push(changeOutput);
1331
                                }
1332
                            }
1333
1334
                            cb();
1335
                        },
1336
                        /**
1337
                         * add outputs to txb
1338
                         *
1339
                         * @param cb
1340
                         */
1341
                        function(cb) {
1342
                            outputs.forEach(function(outputInfo) {
1343
                                txb.addOutput(outputInfo.scriptPubKey, outputInfo.value);
1344
                            });
1345
1346
                            cb();
1347
                        },
1348
                        /**
1349
                         * sign
1350
                         *
1351
                         * @param cb
1352
                         */
1353
                        function(cb) {
1354
                            var i, privKey, path, redeemScript, witnessScript;
1355
1356
                            deferred.notify(Wallet.PAY_PROGRESS_SIGN);
1357
1358
                            for (i = 0; i < utxos.length; i++) {
1359
                                var mode = SignMode.SIGN;
1360
                                if (utxos[i].sign_mode) {
1361
                                    mode = utxos[i].sign_mode;
1362
                                }
1363
1364
                                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...
1365
                                witnessScript = null;
1366
                                if (mode === SignMode.SIGN) {
1367
                                    path = utxos[i]['path'].replace("M", "m");
1368
1369
                                    // todo: regenerate scripts for path and compare for utxo (paranoid mode)
1370
                                    if (self.primaryPrivateKey) {
1371
                                        privKey = Wallet.deriveByPath(self.primaryPrivateKey, path, "m").keyPair;
1372
                                    } else if (self.backupPrivateKey) {
1373
                                        privKey = Wallet.deriveByPath(self.backupPrivateKey, path.replace(/^m\/(\d+)\'/, 'm/$1'), "m").keyPair;
1374
                                    } else {
1375
                                        throw new Error("No master privateKey present");
1376
                                    }
1377
1378
                                    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...
1379
                                    if (typeof utxos[i]['witness_script'] === 'string') {
1380
                                        witnessScript = new Buffer(utxos[i]['witness_script'], 'hex');
1381
                                    }
1382
1383
                                    var sigHash = bitcoin.Transaction.SIGHASH_ALL;
1384
                                    if (self.bitcoinCash) {
1385
                                        sigHash |= bitcoin.Transaction.SIGHASH_BITCOINCASHBIP143;
1386
                                    }
1387
1388
                                    txb.sign(i, privKey, redeemScript, sigHash, utxos[i].value, witnessScript);
1389
                                }
1390
                            }
1391
1392
                            tx = txb.buildIncomplete();
1393
1394
                            cb();
1395
                        },
1396
                        /**
1397
                         * estimate fee to verify that the API is not providing us wrong data
1398
                         *
1399
                         * @param cb
1400
                         */
1401
                        function(cb) {
1402
                            var estimatedFee = Wallet.estimateVsizeFee(tx, utxos);
1403
1404
                            if (self.sdk.feeSanityCheck) {
1405
                                switch (feeStrategy) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1406
                                    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...
1407
                                        if (Math.abs(estimatedFee - fee) > blocktrail.BASE_FEE) {
1408
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1409
                                                "seems incorrect (" + estimatedFee + ") for FEE_STRATEGY_BASE_FEE"));
1410
                                        }
1411
                                    break;
1412
1413
                                    case Wallet.FEE_STRATEGY_HIGH_PRIORITY:
1414
                                    case Wallet.FEE_STRATEGY_OPTIMAL:
1415
                                    case Wallet.FEE_STRATEGY_LOW_PRIORITY:
1416
                                        if (fee > estimatedFee * self.feeSanityCheckBaseFeeMultiplier) {
1417
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1418
                                                "seems awefully high (" + estimatedFee + ") for FEE_STRATEGY_OPTIMAL"));
1419
                                        }
1420
                                    break;
1421
                                }
1422
                            }
1423
1424
                            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...
1425
                        }
1426
                    ], function(err) {
1427
                        if (err) {
1428
                            deferred.reject(new blocktrail.WalletSendError(err));
1429
                            return;
1430
                        }
1431
1432
                        deferred.resolve([tx, utxos]);
1433
                    });
1434
1435
                    return deferred.promise;
1436
                }
1437
            )
1438
        );
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...
1439
    });
1440
1441
    return deferred.promise;
1442
};
1443
1444
1445
/**
1446
 * use the API to get the best inputs to use based on the outputs
1447
 *
1448
 * @param pay               array       {'address': (int)value}     coins to send
1449
 * @param [lockUTXO]        bool        lock UTXOs for a few seconds to allow for transaction to be created
1450
 * @param [allowZeroConf]   bool        allow zero confirmation unspent outputs to be used in coin selection
1451
 * @param [feeStrategy]     string      defaults to FEE_STRATEGY_OPTIMAL
1452
 * @param [options]         object
1453
 * @param [cb]              function    callback(err, utxos, fee, change)
1454
 * @returns {q.Promise}
1455
 */
1456
Wallet.prototype.coinSelection = function(pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1457
    var self = this;
1458
1459
    if (typeof lockUTXO === "function") {
1460
        cb = lockUTXO;
1461
        lockUTXO = true;
1462
    } else if (typeof allowZeroConf === "function") {
1463
        cb = allowZeroConf;
1464
        allowZeroConf = false;
1465
    } else if (typeof feeStrategy === "function") {
1466
        cb = feeStrategy;
1467
        feeStrategy = null;
1468
    } else if (typeof options === "function") {
1469
        cb = options;
1470
        options = {};
1471
    }
1472
1473
    lockUTXO = typeof lockUTXO !== "undefined" ? lockUTXO : true;
1474
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1475
    options = options || {};
1476
1477
    var send;
1478
    try {
1479
        send = Wallet.convertPayToOutputs(pay, self.network, self.useNewCashAddr);
1480
    } catch (e) {
1481
        var deferred = q.defer();
1482
        deferred.promise.nodeify(cb);
1483
        deferred.reject(e);
1484
        return deferred.promise;
1485
    }
1486
1487
    return self.sdk.coinSelection(self.identifier, send, lockUTXO, allowZeroConf, feeStrategy, options, cb);
1488
};
1489
1490
/**
1491
 * send the transaction using the API
1492
 *
1493
 * @param txHex             string      partially signed transaction as hex string
1494
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1495
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1496
 * @param [twoFactorToken]  string      2FA token
1497
 * @param prioboost         bool
1498
 * @param [cb]              function    callback(err, txHash)
1499
 * @returns {q.Promise}
1500
 */
1501
Wallet.prototype.sendTransaction = function(txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1502
    var self = this;
1503
1504
    if (typeof twoFactorToken === "function") {
1505
        cb = twoFactorToken;
1506
        twoFactorToken = null;
1507
        prioboost = false;
1508
    } else if (typeof prioboost === "function") {
1509
        cb = twoFactorToken;
1510
        prioboost = false;
1511
    }
1512
1513
    var deferred = q.defer();
1514
    deferred.promise.nodeify(cb);
1515
1516
    self.sdk.sendTransaction(self.identifier, txHex, paths, checkFee, twoFactorToken, prioboost)
1517
        .then(
1518
            function(result) {
1519
                deferred.resolve(result);
1520
            },
1521
            function(e) {
1522
                if (e.requires_2fa) {
1523
                    deferred.reject(new blocktrail.WalletMissing2FAError());
1524
                } else if (e.message.match(/Invalid two_factor_token/)) {
1525
                    deferred.reject(new blocktrail.WalletInvalid2FAError());
1526
                } else {
1527
                    deferred.reject(e);
1528
                }
1529
            }
1530
        )
1531
    ;
1532
1533
    return deferred.promise;
1534
};
1535
1536
/**
1537
 * setup a webhook for this wallet
1538
 *
1539
 * @param url           string      URL to receive webhook events
1540
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1541
 * @param [cb]          function    callback(err, webhook)
1542
 * @returns {q.Promise}
1543
 */
1544
Wallet.prototype.setupWebhook = function(url, identifier, cb) {
1545
    var self = this;
1546
1547
    if (typeof identifier === "function") {
1548
        cb = identifier;
1549
        identifier = null;
1550
    }
1551
1552
    identifier = identifier || ('WALLET-' + self.identifier);
1553
1554
    return self.sdk.setupWalletWebhook(self.identifier, identifier, url, cb);
1555
};
1556
1557
/**
1558
 * delete a webhook that was created for this wallet
1559
 *
1560
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1561
 * @param [cb]          function    callback(err, success)
1562
 * @returns {q.Promise}
1563
 */
1564
Wallet.prototype.deleteWebhook = function(identifier, cb) {
1565
    var self = this;
1566
1567
    if (typeof identifier === "function") {
1568
        cb = identifier;
1569
        identifier = null;
1570
    }
1571
1572
    identifier = identifier || ('WALLET-' + self.identifier);
1573
1574
    return self.sdk.deleteWalletWebhook(self.identifier, identifier, cb);
1575
};
1576
1577
/**
1578
 * get all transactions for the wallet (paginated)
1579
 *
1580
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1581
 * @param [cb]      function    callback(err, transactions)
1582
 * @returns {q.Promise}
1583
 */
1584
Wallet.prototype.transactions = function(params, cb) {
1585
    var self = this;
1586
1587
    return self.sdk.walletTransactions(self.identifier, params, cb);
1588
};
1589
1590
Wallet.prototype.maxSpendable = function(allowZeroConf, feeStrategy, options, cb) {
1591
    var self = this;
1592
1593
    if (typeof allowZeroConf === "function") {
1594
        cb = allowZeroConf;
1595
        allowZeroConf = false;
1596
    } else if (typeof feeStrategy === "function") {
1597
        cb = feeStrategy;
1598
        feeStrategy = null;
1599
    } else if (typeof options === "function") {
1600
        cb = options;
1601
        options = {};
1602
    }
1603
1604
    if (typeof allowZeroConf === "object") {
1605
        options = allowZeroConf;
1606
        allowZeroConf = false;
1607
    } else if (typeof feeStrategy === "object") {
1608
        options = feeStrategy;
1609
        feeStrategy = null;
1610
    }
1611
1612
    options = options || {};
1613
1614
    if (typeof options.allowZeroConf !== "undefined") {
1615
        allowZeroConf = options.allowZeroConf;
1616
    }
1617
    if (typeof options.feeStrategy !== "undefined") {
1618
        feeStrategy = options.feeStrategy;
1619
    }
1620
1621
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1622
1623
    return self.sdk.walletMaxSpendable(self.identifier, allowZeroConf, feeStrategy, options, cb);
1624
};
1625
1626
/**
1627
 * get all addresses for the wallet (paginated)
1628
 *
1629
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1630
 * @param [cb]      function    callback(err, addresses)
1631
 * @returns {q.Promise}
1632
 */
1633
Wallet.prototype.addresses = function(params, cb) {
1634
    var self = this;
1635
1636
    return self.sdk.walletAddresses(self.identifier, params, cb);
1637
};
1638
1639
/**
1640
 * @param address   string      the address to label
1641
 * @param label     string      the label
1642
 * @param [cb]      function    callback(err, res)
1643
 * @returns {q.Promise}
1644
 */
1645
Wallet.prototype.labelAddress = function(address, label, cb) {
1646
    var self = this;
1647
1648
    return self.sdk.labelWalletAddress(self.identifier, address, label, cb);
1649
};
1650
1651
/**
1652
 * get all UTXOs for the wallet (paginated)
1653
 *
1654
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1655
 * @param [cb]      function    callback(err, addresses)
1656
 * @returns {q.Promise}
1657
 */
1658
Wallet.prototype.utxos = function(params, cb) {
1659
    var self = this;
1660
1661
    return self.sdk.walletUTXOs(self.identifier, params, cb);
1662
};
1663
1664
Wallet.prototype.unspentOutputs = Wallet.prototype.utxos;
1665
1666
/**
1667
 * sort list of pubkeys to be used in a multisig redeemscript
1668
 *  sorted in lexicographical order on the hex of the pubkey
1669
 *
1670
 * @param pubKeys   {bitcoin.HDNode[]}
1671
 * @returns string[]
1672
 */
1673
Wallet.sortMultiSigKeys = function(pubKeys) {
1674
    pubKeys.sort(function(key1, key2) {
1675
        return key1.toString('hex').localeCompare(key2.toString('hex'));
1676
    });
1677
1678
    return pubKeys;
1679
};
1680
1681
/**
1682
 * determine how much fee is required based on the inputs and outputs
1683
 *  this is an estimation, not a proper 100% correct calculation
1684
 *
1685
 * @todo: mark deprecated in favor of estimations where UTXOS are known
1686
 * @param {bitcoin.Transaction} tx
1687
 * @param {int} feePerKb when not null use this feePerKb, otherwise use BASE_FEE legacy calculation
1688
 * @returns {number}
1689
 */
1690
Wallet.estimateIncompleteTxFee = function(tx, feePerKb) {
1691
    var size = Wallet.estimateIncompleteTxSize(tx);
1692
    var sizeKB = size / 1000;
1693
    var sizeKBCeil = Math.ceil(size / 1000);
1694
1695
    if (feePerKb) {
1696
        return parseInt(sizeKB * feePerKb, 10);
1697
    } else {
1698
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1699
    }
1700
};
1701
1702
/**
1703
 * Takes tx and utxos, computing their estimated vsize,
1704
 * and uses feePerKb (or BASEFEE as default) to estimate
1705
 * the number of satoshis in fee.
1706
 *
1707
 * @param {bitcoin.Transaction} tx
1708
 * @param {Array} utxos
1709
 * @param feePerKb
1710
 * @returns {Number}
1711
 */
1712
Wallet.estimateVsizeFee = function(tx, utxos, feePerKb) {
1713
    var vsize = SizeEstimation.estimateTxVsize(tx, utxos);
1714
    var sizeKB = vsize / 1000;
1715
    var sizeKBCeil = Math.ceil(vsize / 1000);
1716
1717
    if (feePerKb) {
1718
        return parseInt(sizeKB * feePerKb, 10);
1719
    } else {
1720
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1721
    }
1722
};
1723
1724
/**
1725
 * determine how much fee is required based on the inputs and outputs
1726
 *  this is an estimation, not a proper 100% correct calculation
1727
 *
1728
 * @param {bitcoin.Transaction} tx
1729
 * @returns {number}
1730
 */
1731
Wallet.estimateIncompleteTxSize = function(tx) {
1732
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1733
1734
    size += tx.outs.length * 34;
1735
1736
    tx.ins.forEach(function(txin) {
1737
        var scriptSig = txin.script,
1738
            scriptType = bitcoin.script.classifyInput(scriptSig);
1739
1740
        var multiSig = [2, 3]; // tmp hardcoded
1741
1742
        // Re-classify if P2SH
1743
        if (!multiSig && scriptType === 'scripthash') {
1744
            var sigChunks = bitcoin.script.decompile(scriptSig);
1745
            var redeemScript = sigChunks.slice(-1)[0];
1746
            scriptSig = bitcoin.script.compile(sigChunks.slice(0, -1));
1747
            scriptType = bitcoin.script.classifyInput(scriptSig);
1748
1749
            if (bitcoin.script.classifyOutput(redeemScript) !== scriptType) {
1750
                throw new blocktrail.TransactionInputError('Non-matching scriptSig and scriptPubKey in input');
1751
            }
1752
1753
            // figure out M of N for multisig (code from internal usage of bitcoinjs)
1754
            if (scriptType === 'multisig') {
1755
                var rsChunks = bitcoin.script.decompile(redeemScript);
1756
                var mOp = rsChunks[0];
1757
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1758
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1759
                }
1760
1761
                var nOp = rsChunks[redeemScript.chunks.length - 2];
1762
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1763
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1764
                }
1765
1766
                var m = mOp - (bitcoin.opcodes.OP_1 - 1);
1767
                var n = nOp - (bitcoin.opcodes.OP_1 - 1);
1768
                if (n < m) {
1769
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1770
                }
1771
1772
                multiSig = [m, n];
1773
            }
1774
        }
1775
1776
        if (multiSig) {
1777
            size += (
1778
                32 + // txhash
1779
                4 + // idx
1780
                3 + // scriptVarInt[>=253]
1781
                1 + // OP_0
1782
                ((1 + 72) * multiSig[0]) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1783
                (2 + 105) + // OP_PUSHDATA[>=75] + script
1784
                4 // sequence
1785
            );
1786
1787
        } else {
1788
            size += 32 + // txhash
1789
                4 + // idx
1790
                73 + // sig
1791
                34 + // script
1792
                4; // sequence
1793
        }
1794
    });
1795
1796
    return size;
1797
};
1798
1799
/**
1800
 * determine how much fee is required based on the amount of inputs and outputs
1801
 *  this is an estimation, not a proper 100% correct calculation
1802
 *  this asumes all inputs are 2of3 multisig
1803
 *
1804
 * @todo: mark deprecated in favor of situations where UTXOS are known
1805
 * @param txinCnt       {number}
1806
 * @param txoutCnt      {number}
1807
 * @returns {number}
1808
 */
1809
Wallet.estimateFee = function(txinCnt, txoutCnt) {
1810
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1811
1812
    size += txoutCnt * 34;
1813
1814
    size += (
1815
            32 + // txhash
1816
            4 + // idx
1817
            3 + // scriptVarInt[>=253]
1818
            1 + // OP_0
1819
            ((1 + 72) * 2) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1820
            (2 + 105) + // OP_PUSHDATA[>=75] + script
1821
            4 // sequence
1822
        ) * txinCnt;
1823
1824
    var sizeKB = Math.ceil(size / 1000);
1825
1826
    return sizeKB * blocktrail.BASE_FEE;
1827
};
1828
1829
/**
1830
 * create derived key from parent key by path
1831
 *
1832
 * @param hdKey     {bitcoin.HDNode}
1833
 * @param path      string
1834
 * @param keyPath   string
1835
 * @returns {bitcoin.HDNode}
1836
 */
1837
Wallet.deriveByPath = function(hdKey, path, keyPath) {
1838
    keyPath = keyPath || (!!hdKey.keyPair.d ? "m" : "M");
1839
1840
    if (path[0].toLowerCase() !== "m" || keyPath[0].toLowerCase() !== "m") {
1841
        throw new blocktrail.KeyPathError("Wallet.deriveByPath only works with absolute paths. (" + path + ", " + keyPath + ")");
1842
    }
1843
1844
    if (path[0] === "m" && keyPath[0] === "M") {
1845
        throw new blocktrail.KeyPathError("Wallet.deriveByPath can't derive private path from public parent. (" + path + ", " + keyPath + ")");
1846
    }
1847
1848
    // if the desired path is public while the input is private
1849
    var toPublic = path[0] === "M" && keyPath[0] === "m";
1850
    if (toPublic) {
1851
        // derive the private path, convert to public when returning
1852
        path[0] = "m";
1853
    }
1854
1855
    // keyPath should be the parent parent of path
1856
    if (path.toLowerCase().indexOf(keyPath.toLowerCase()) !== 0) {
1857
        throw new blocktrail.KeyPathError("Wallet.derivePath requires path (" + path + ") to be a child of keyPath (" + keyPath + ")");
1858
    }
1859
1860
    // remove the part of the path we already have
1861
    path = path.substr(keyPath.length);
1862
1863
    // iterate over the chunks and derive
1864
    var newKey = hdKey;
1865
    path.replace(/^\//, "").split("/").forEach(function(chunk) {
1866
        if (!chunk) {
1867
            return;
1868
        }
1869
1870
        if (chunk.indexOf("'") !== -1) {
1871
            chunk = parseInt(chunk.replace("'", ""), 10) + bitcoin.HDNode.HIGHEST_BIT;
1872
        }
1873
1874
        newKey = newKey.derive(parseInt(chunk, 10));
1875
    });
1876
1877
    if (toPublic) {
1878
        return newKey.neutered();
1879
    } else {
1880
        return newKey;
1881
    }
1882
};
1883
1884
module.exports = Wallet;
1885