Completed
Pull Request — master (#127)
by thomas
14:59
created

WalletV2::unlock()   F

Complexity

Conditions 18
Paths 2112

Size

Total Lines 62

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 40.6258

Importance

Changes 0
Metric Value
cc 18
nc 2112
nop 2
dl 0
loc 62
ccs 20
cts 34
cp 0.5881
crap 40.6258
rs 0.7
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace Blocktrail\SDK;
4
5
use BitWasp\Bitcoin\Key\Deterministic\HierarchicalKey;
6
use BitWasp\Bitcoin\Key\Deterministic\HierarchicalKeyFactory;
7
use BitWasp\Bitcoin\Mnemonic\MnemonicFactory;
8
use BitWasp\Buffertools\Buffer;
9
use Blocktrail\CryptoJSAES\CryptoJSAES;
10
use Blocktrail\SDK\Address\AddressReaderBase;
11
use Blocktrail\SDK\Bitcoin\BIP32Key;
12
use Blocktrail\SDK\Exceptions\BlocktrailSDKException;
13
use Blocktrail\SDK\Exceptions\WalletDecryptException;
14
15
class WalletV2 extends Wallet {
16
17
    protected $encryptedPrimarySeed;
18
19
    protected $encryptedSecret;
20
21
    protected $secret = null;
22
23
    protected $primarySeed = null;
24
25
    /**
26
     * @param BlocktrailSDKInterface $sdk        SDK instance used to do requests
27
     * @param string                 $identifier identifier of the wallet
28
     * @param string                 $encryptedPrimarySeed
29
     * @param                        $encryptedSecret
30
     * @param BIP32Key[]             $primaryPublicKeys
31
     * @param BIP32Key               $backupPublicKey
32
     * @param BIP32Key[]             $blocktrailPublicKeys
33
     * @param int                    $keyIndex
34
     * @param string                 $network
35
     * @param bool                   $testnet
36
     * @param bool                   $segwit
37
     * @param string                 $checksum
38
     * @param AddressReaderBase      $addressReader
39
     * @throws                       BlocktrailSDKException
40
     */
41 1
    public function __construct(BlocktrailSDKInterface $sdk, $identifier, $encryptedPrimarySeed, $encryptedSecret, $primaryPublicKeys, $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $network, $testnet, $segwit, AddressReaderBase $addressReader, $checksum) {
42 1
        $this->encryptedPrimarySeed = $encryptedPrimarySeed;
43 1
        $this->encryptedSecret = $encryptedSecret;
44
45 1
        parent::__construct($sdk, $identifier, $primaryPublicKeys, $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $network, $testnet, $segwit, $addressReader, $checksum);
46 1
    }
47
48
    /**
49
     * unlock wallet so it can be used for payments
50
     *
51
     * @param          $options ['primary_private_key' => key] OR ['passphrase' => pass]
52
     * @param callable $fn
53
     * @return bool
54
     * @throws \Exception
55
     */
56 1
    public function unlock($options, callable $fn = null) {
57
        // explode the wallet data
58 1
        $password = isset($options['passphrase']) ? $options['passphrase'] : (isset($options['password']) ? $options['password'] : null);
59 1
        $encryptedPrimarySeed = $this->encryptedPrimarySeed;
60 1
        $encryptedSecret = $this->encryptedSecret;
61 1
        $primaryPrivateKey = isset($options['primary_private_key']) ? $options['primary_private_key'] : null;
62
63 1
        if (isset($options['secret'])) {
64 1
            $this->secret = $options['secret'];
65
        }
66 1
        if (isset($options['primary_seed'])) {
67 1
            $this->primarySeed = $options['primary_seed'];
68
        }
69
70 1
        if (!$primaryPrivateKey) {
71
            if (!$password) {
72
                throw new \InvalidArgumentException("Can't init wallet with Primary Seed without a passphrase");
73
            } elseif (!$encryptedSecret) {
74
                throw new \InvalidArgumentException("Can't init wallet with Primary Seed without a encrypted secret");
75
            }
76
        }
77
78 1
        if ($primaryPrivateKey) {
79 1
            if (!($primaryPrivateKey instanceof HierarchicalKey) && !($primaryPrivateKey instanceof BIP32Key)) {
80 1
                $primaryPrivateKey = HierarchicalKeyFactory::fromExtended($primaryPrivateKey);
81
            }
82
        } else {
83
            if (!($this->secret = CryptoJSAES::decrypt($encryptedSecret, $password))) {
84
                throw new WalletDecryptException("Failed to decrypt secret with password");
85
            }
86
87
            // convert the mnemonic to a seed using BIP39 standard
88
            if (!($this->primarySeed = CryptoJSAES::decrypt($encryptedPrimarySeed, $this->secret))) {
89
                throw new WalletDecryptException("Failed to decrypt primary seed with secret");
90
            }
91
92
            $seedBuffer = new Buffer(base64_decode($this->primarySeed));
93
94
            // create BIP32 private key from the seed
95
            $primaryPrivateKey = HierarchicalKeyFactory::fromEntropy($seedBuffer);
96
        }
97
98 1
        $this->primaryPrivateKey = $primaryPrivateKey instanceof BIP32Key ? $primaryPrivateKey : BIP32Key::create($primaryPrivateKey, "m");
99
100
        // create checksum (address) of the primary privatekey to compare to the stored checksum
101 1
        $checksum = $this->primaryPrivateKey->publicKey()->getAddress()->getAddress();
102 1
        if ($checksum != $this->checksum) {
103
            throw new \Exception("Checksum [{$checksum}] does not match [{$this->checksum}], most likely due to incorrect password");
104
        }
105
106 1
        $this->locked = false;
107
108
        // if the response suggests we should upgrade to a different blocktrail cosigning key then we should
109 1
        if (isset($data['upgrade_key_index'])) {
0 ignored issues
show
Bug introduced by
The variable $data seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
110
            $this->upgradeKeyIndex($data['upgrade_key_index']);
111
        }
112
113 1
        if ($fn) {
114
            $fn($this);
115
            $this->lock();
116
        }
117 1
    }
118
119
    /**
120
     * lock the wallet (unsets primary private key)
121
     *
122
     * @return void
123
     */
124
    public function lock() {
125
        $this->primaryPrivateKey = null;
126
        $this->secret = null;
127
        $this->primarySeed = null;
128
        $this->locked = true;
129
    }
130
131
    /**
132
     * change password that is used to store data encrypted on server
133
     *
134
     * @param $newPassword
135
     * @return array backupInfo
136
     * @throws BlocktrailSDKException
137
     */
138
    public function passwordChange($newPassword) {
139
        if ($this->locked) {
140
            throw new BlocktrailSDKException("Wallet needs to be unlocked to change password");
141
        }
142
143
        if (!$this->secret) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->secret of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
144
            throw new BlocktrailSDKException("No secret");
145
        }
146
147
        $encryptedSecret = CryptoJSAES::encrypt($this->secret, $newPassword);
148
149
        $this->sdk->updateWallet($this->identifier, ['encrypted_secret' => $encryptedSecret]);
150
151
        $this->encryptedSecret = $encryptedSecret;
152
153
        return [
154
            'encrypted_secret' => MnemonicFactory::bip39()->entropyToMnemonic(new Buffer(base64_decode($this->encryptedSecret))),
155
        ];
156
    }
157
}
158