Completed
Pull Request — master (#88)
by thomas
05:05
created

WalletV3::lock()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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