Completed
Pull Request — master (#111)
by thomas
34:55 queued 32:56
created

WalletV3::unlock()   C

Complexity

Conditions 14
Paths 92

Size

Total Lines 54
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 20.5803

Importance

Changes 0
Metric Value
cc 14
eloc 32
nc 92
nop 2
dl 0
loc 54
ccs 21
cts 31
cp 0.6774
crap 20.5803
rs 6.7343
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\HierarchicalKeyFactory;
6
use BitWasp\Buffertools\Buffer;
7
use BitWasp\Buffertools\BufferInterface;
8
use Blocktrail\SDK\Address\AddressReaderBase;
9
use Blocktrail\SDK\Bitcoin\BIP32Key;
10
use Blocktrail\SDK\Exceptions\BlocktrailSDKException;
11
use Blocktrail\SDK\Exceptions\WalletDecryptException;
12
use Btccom\JustEncrypt\Encryption;
13
use Btccom\JustEncrypt\EncryptionMnemonic;
14
15
class WalletV3 extends Wallet
16
{
17
18
    /**
19
     * @var BufferInterface
20
     */
21
    protected $encryptedPrimarySeed;
22
23
    /**
24
     * @var BufferInterface
25
     */
26
    protected $encryptedSecret;
27
28
    /**
29
     * @var BufferInterface
30
     */
31
    protected $secret = null;
32
33
    /**
34
     * @var BufferInterface
35
     */
36
    protected $primarySeed = null;
37
38
    /**
39
     * @param BlocktrailSDKInterface $sdk        SDK instance used to do requests
40
     * @param string                 $identifier identifier of the wallet
41
     * @param BufferInterface        $encryptedPrimarySeed
42
     * @param BufferInterface        $encryptedSecret
43
     * @param BIP32Key[]             $primaryPublicKeys
44
     * @param BIP32Key               $backupPublicKey
45
     * @param BIP32Key[]             $blocktrailPublicKeys
46
     * @param int                    $keyIndex
47
     * @param string                 $network
48
     * @param bool                   $testnet
49
     * @param bool                   $segwit
50
     * @param AddressReaderBase      $addressReader
51
     * @param string                 $checksum
52
     * @throws                       BlocktrailSDKException
53
     */
54 4
    public function __construct(BlocktrailSDKInterface $sdk, $identifier, $encryptedPrimarySeed, $encryptedSecret, $primaryPublicKeys, $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $network, $testnet, $segwit, AddressReaderBase $addressReader, $checksum) {
55 4
        if ($encryptedPrimarySeed !== null && !($encryptedPrimarySeed instanceof Buffer)) {
56
            throw new \InvalidArgumentException('Encrypted Primary Seed must be a Buffer or null');
57
        }
58 4
        if ($encryptedSecret !== null && !($encryptedSecret instanceof Buffer)) {
59
            throw new \InvalidArgumentException('Encrypted Secret must be a Buffer or null');
60
        }
61 4
        $this->encryptedPrimarySeed = $encryptedPrimarySeed;
62 4
        $this->encryptedSecret = $encryptedSecret;
63
64 4
        parent::__construct($sdk, $identifier, $primaryPublicKeys, $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $network, $testnet, $segwit, $addressReader, $checksum);
65 4
    }
66
67
    /**
68
     * unlock wallet so it can be used for payments
69
     *
70
     * @param          $options ['primary_private_key' => key] OR ['passphrase' => pass]
71
     * @param callable $fn
72
     * @return bool
73
     * @throws \Exception
74
     */
75 4
    public function unlock($options, callable $fn = null) {
76
        // explode the wallet data
77 4
        $password = isset($options['passphrase']) ? $options['passphrase'] : (isset($options['password']) ? $options['password'] : null);
78
79 4
        $encryptedPrimarySeed = $this->encryptedPrimarySeed;
80 4
        $encryptedSecret = $this->encryptedSecret;
81
82 4
        if (isset($options['primary_seed'])) {
83 4
            if (!$options['primary_seed'] instanceof BufferInterface) {
84
                throw new \RuntimeException('Primary Seed must be a BufferInterface');
85
            }
86 4
            $this->primarySeed = $options['primary_seed'];
87
        } else {
88 4
            if (!$password) {
89
                throw new \InvalidArgumentException("Can't init wallet with Primary Seed without a passphrase");
90 4
            } elseif (!$encryptedSecret) {
91
                throw new \InvalidArgumentException("Can't init wallet with Primary Seed without a encrypted secret");
92 4
            } elseif (!$encryptedPrimarySeed) {
93
                throw new \InvalidArgumentException("Can't init wallet without an Encrypted Primary Seed");
94
            }
95
96 4
            if (!$password instanceof Buffer) {
97 4
                $password = new Buffer($password);
98
            }
99 4
            if (!($this->secret = Encryption::decrypt($encryptedSecret, $password))) {
100
                throw new WalletDecryptException("Failed to decrypt secret with password");
101
            }
102
103 4
            if (!($this->primarySeed = Encryption::decrypt($encryptedPrimarySeed, $this->secret))) {
104
                throw new WalletDecryptException("Failed to decrypt primary seed with secret");
105
            }
106
        }
107 4
        $this->primaryPrivateKey = BIP32Key::create(HierarchicalKeyFactory::fromEntropy($this->primarySeed), "m");
108
109
        // create checksum (address) of the primary privatekey to compare to the stored checksum
110 4
        $checksum = $this->primaryPrivateKey->publicKey()->getAddress()->getAddress();
111 4
        if ($checksum != $this->checksum) {
112
            throw new \Exception("Checksum [{$checksum}] does not match [{$this->checksum}], most likely due to incorrect password");
113
        }
114
115 4
        $this->locked = false;
116
117
        // if the response suggests we should upgrade to a different blocktrail cosigning key then we should
118 4
        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...
119
            $this->upgradeKeyIndex($data['upgrade_key_index']);
120
        }
121
122 4
        if ($fn) {
123
            $fn($this);
124
            $this->lock();
125
        }
126
127 4
        return true;
128
    }
129
130
    /**
131
     * lock the wallet (unsets primary private key)
132
     *
133
     * @return void
134
     */
135 2
    public function lock() {
136 2
        $this->primaryPrivateKey = null;
137 2
        $this->secret = null;
138 2
        $this->primarySeed = null;
139 2
        $this->locked = true;
140 2
    }
141
142
    /**
143
     * change password that is used to store data encrypted on server
144
     *
145
     * @param string $newPassword
146
     * @return array backupInfo
147
     * @throws BlocktrailSDKException
148
     */
149 2
    public function passwordChange($newPassword) {
150 2
        if ($this->locked) {
151
            throw new BlocktrailSDKException("Wallet needs to be unlocked to change password");
152
        }
153
154 2
        if (!$this->secret) {
155
            throw new BlocktrailSDKException("No secret");
156
        }
157
158 2
        $encryptedSecret = Encryption::encrypt($this->secret, new Buffer($newPassword))
159 2
            ->getBuffer();
160
161 2
        $this->sdk->updateWallet($this->identifier, ['encrypted_secret' => base64_encode($encryptedSecret->getBinary())]);
162
163 2
        $this->encryptedSecret = $encryptedSecret;
164
165
        return [
166 2
            'encrypted_secret' => EncryptionMnemonic::encode($encryptedSecret),
167
        ];
168
    }
169
}
170