Completed
Pull Request — master (#89)
by thomas
20:32
created

WalletV2   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 83.64%

Importance

Changes 0
Metric Value
dl 0
loc 152
ccs 46
cts 55
cp 0.8364
rs 10
c 0
b 0
f 0
wmc 23
lcom 1
cbo 12

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
F unlock() 0 63 18
A lock() 0 6 1
A passwordChange() 0 19 3
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\Bitcoin\BIP32Key;
11
use Blocktrail\SDK\Exceptions\BlocktrailSDKException;
12
use Blocktrail\SDK\Exceptions\WalletDecryptException;
13
14
class WalletV2 extends Wallet {
15
16
    /**
17
     * @var string
18
     */
19
    protected $encryptedPrimarySeed;
20
21
    /**
22
     * @var string
23
     */
24
    protected $encryptedSecret;
25
26
    /**
27
     * @var string|null
28
     */
29
    protected $secret = null;
30
31
    /**
32
     * @var string|null
33
     */
34
    protected $primarySeed = null;
35
36
    /**
37
     * @param BlocktrailSDKInterface $sdk                   SDK instance used to do requests
38
     * @param string                 $identifier            identifier of the wallet
39
     * @param string                 $encryptedPrimarySeed
40
     * @param string                 $encryptedSecret
41
     * @param BIP32Key[]             $primaryPublicKeys
42
     * @param BIP32Key               $backupPublicKey
43
     * @param BIP32Key[]             $blocktrailPublicKeys
44
     * @param int                    $keyIndex
45
     * @param bool                   $segwit
46
     * @param string                 $checksum
47
     */
48 5
    public function __construct(BlocktrailSDKInterface $sdk, $identifier, $encryptedPrimarySeed, $encryptedSecret, $primaryPublicKeys, $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $segwit, $checksum) {
49 5
        $this->encryptedPrimarySeed = $encryptedPrimarySeed;
50 5
        $this->encryptedSecret = $encryptedSecret;
51
52 5
        parent::__construct($sdk, $identifier, $primaryPublicKeys, $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $segwit, $checksum);
53 5
    }
54
55
    /**
56
     * unlock wallet so it can be used for payments
57
     *
58
     * @param          $options ['primary_private_key' => key] OR ['passphrase' => pass]
59
     * @param callable $fn
60
     * @return bool
61
     * @throws \Exception
62
     */
63 5
    public function unlock($options, callable $fn = null) {
64
        // explode the wallet data
65 5
        $password = isset($options['passphrase']) ? $options['passphrase'] : (isset($options['password']) ? $options['password'] : null);
66 5
        $encryptedPrimarySeed = $this->encryptedPrimarySeed;
67 5
        $encryptedSecret = $this->encryptedSecret;
68 5
        $primaryPrivateKey = isset($options['primary_private_key']) ? $options['primary_private_key'] : null;
69
70 5
        if (isset($options['secret'])) {
71 1
            $this->secret = $options['secret'];
72
        }
73 5
        if (isset($options['primary_seed'])) {
74 1
            $this->primarySeed = $options['primary_seed'];
75
        }
76
77 5
        if (!$primaryPrivateKey) {
78 4
            if (!$password) {
79
                throw new \InvalidArgumentException("Can't init wallet with Primary Seed without a passphrase");
80 4
            } elseif (!$encryptedSecret) {
81
                throw new \InvalidArgumentException("Can't init wallet with Primary Seed without a encrypted secret");
82
            }
83
        }
84
85 5
        if ($primaryPrivateKey) {
86 2
            if (!($primaryPrivateKey instanceof HierarchicalKey) && !($primaryPrivateKey instanceof BIP32Key)) {
87 2
                $primaryPrivateKey = HierarchicalKeyFactory::fromExtended($primaryPrivateKey);
88
            }
89
        } else {
90 4
            if (!($this->secret = CryptoJSAES::decrypt($encryptedSecret, $password))) {
91 1
                throw new WalletDecryptException("Failed to decrypt secret with password");
92
            }
93
94
            // convert the mnemonic to a seed using BIP39 standard
95 4
            if (!($this->primarySeed = CryptoJSAES::decrypt($encryptedPrimarySeed, $this->secret))) {
96
                throw new WalletDecryptException("Failed to decrypt primary seed with secret");
97
            }
98
99 4
            $seedBuffer = new Buffer(base64_decode($this->primarySeed));
100
101
            // create BIP32 private key from the seed
102 4
            $primaryPrivateKey = HierarchicalKeyFactory::fromEntropy($seedBuffer);
103
        }
104
105 5
        $network = $this->networkParams->getNetwork();
0 ignored issues
show
Bug introduced by
The method getNetwork() does not seem to exist on object<BitWasp\Bitcoin\Network\NetworkInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
106 5
        $this->primaryPrivateKey = $primaryPrivateKey instanceof BIP32Key ? $primaryPrivateKey : BIP32Key::create($network, $primaryPrivateKey, "m");
107
108
        // create checksum (address) of the primary privatekey to compare to the stored checksum
109 5
        $checksum = $this->primaryPrivateKey->publicKey()->getAddress()->getAddress($network);
110 5
        if ($checksum != $this->checksum) {
111
            throw new \Exception("Checksum [{$checksum}] does not match [{$this->checksum}], most likely due to incorrect password");
112
        }
113
114 5
        $this->locked = false;
115
116
        // if the response suggests we should upgrade to a different blocktrail cosigning key then we should
117 5
        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...
118
            $this->upgradeKeyIndex($data['upgrade_key_index']);
119
        }
120
121 5
        if ($fn) {
122
            $fn($this);
123
            $this->lock();
124
        }
125 5
    }
126
127
    /**
128
     * lock the wallet (unsets primary private key)
129
     *
130
     * @return void
131
     */
132 1
    public function lock() {
133 1
        $this->primaryPrivateKey = null;
134 1
        $this->secret = null;
135 1
        $this->primarySeed = null;
136 1
        $this->locked = true;
137 1
    }
138
139
    /**
140
     * change password that is used to store data encrypted on server
141
     *
142
     * @param $newPassword
143
     * @return array backupInfo
144
     * @throws BlocktrailSDKException
145
     */
146 1
    public function passwordChange($newPassword) {
147 1
        if ($this->locked) {
148
            throw new BlocktrailSDKException("Wallet needs to be unlocked to change password");
149
        }
150
151 1
        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...
152
            throw new BlocktrailSDKException("No secret");
153
        }
154
155 1
        $encryptedSecret = CryptoJSAES::encrypt($this->secret, $newPassword);
156
157 1
        $this->sdk->updateWallet($this->identifier, ['encrypted_secret' => $encryptedSecret]);
158
159 1
        $this->encryptedSecret = $encryptedSecret;
160
161
        return [
162 1
            'encrypted_secret' => MnemonicFactory::bip39()->entropyToMnemonic(new Buffer(base64_decode($this->encryptedSecret))),
163
        ];
164
    }
165
}
166