|
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'])) { |
|
|
|
|
|
|
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
|
|
|
|
This check looks for calls to
isset(...)orempty()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.