1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Btccom\JustEncrypt\Encoding; |
4
|
|
|
|
5
|
|
|
use BitWasp\Buffertools\Buffer; |
6
|
|
|
use BitWasp\Buffertools\BufferInterface; |
7
|
|
|
|
8
|
|
|
class Mnemonic |
9
|
|
|
{ |
10
|
|
|
/** |
11
|
|
|
* @var Wordlist |
12
|
|
|
*/ |
13
|
|
|
private $wordList; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Mnemonic constructor. |
17
|
|
|
* @param Wordlist $wordList |
18
|
|
|
*/ |
19
|
6 |
|
public function __construct(Wordlist $wordList) |
20
|
|
|
{ |
21
|
6 |
|
$this->wordList = $wordList; |
22
|
6 |
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @param BufferInterface $entropy |
26
|
|
|
* @param integer $CSlen |
27
|
|
|
* @return string |
28
|
|
|
*/ |
29
|
53 |
|
private function calculateChecksum(BufferInterface $entropy, $CSlen) |
30
|
|
|
{ |
31
|
53 |
|
$entHash = gmp_init(hash('sha256', $entropy->getBinary(), false), 16); |
32
|
|
|
|
33
|
|
|
// Convert byte string to padded binary string of 0/1's. |
34
|
53 |
|
$hashBits = str_pad(gmp_strval($entHash, 2), 256, '0', STR_PAD_LEFT); |
35
|
|
|
|
36
|
|
|
// Take $CSlen bits for the checksum |
37
|
53 |
|
$checksumBits = substr($hashBits, 0, $CSlen); |
38
|
|
|
|
39
|
53 |
|
return $checksumBits; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @param BufferInterface $entropy |
44
|
|
|
* @return array |
45
|
|
|
*/ |
46
|
30 |
|
public function entropyToWords(BufferInterface $entropy) |
47
|
|
|
{ |
48
|
30 |
|
if ($entropy->getSize() === 0) { |
49
|
|
|
throw new \InvalidArgumentException('Invalid entropy, empty'); |
50
|
|
|
} |
51
|
30 |
|
if ($entropy->getSize() > 1024) { |
52
|
1 |
|
throw new \InvalidArgumentException('Invalid entropy, max 1024 bytes'); |
53
|
|
|
} |
54
|
29 |
|
if ($entropy->getSize() % 4 !== 0) { |
55
|
1 |
|
throw new \InvalidArgumentException('Invalid entropy, must be multitude of 4 bytes'); |
56
|
|
|
} |
57
|
|
|
|
58
|
28 |
|
$ENT = $entropy->getSize() * 8; |
59
|
28 |
|
$CS = $ENT / 32; |
60
|
|
|
|
61
|
28 |
|
$bits = gmp_strval($entropy->getGmp(), 2) . $this->calculateChecksum($entropy, $CS); |
62
|
28 |
|
$bits = str_pad($bits, ($ENT + $CS), '0', STR_PAD_LEFT); |
63
|
|
|
|
64
|
28 |
|
$result = []; |
65
|
28 |
|
foreach (str_split($bits, 11) as $bit) { |
66
|
28 |
|
$idx = gmp_strval(gmp_init($bit, 2), 10); |
67
|
28 |
|
$result[] = $this->wordList->getWord($idx); |
68
|
|
|
} |
69
|
|
|
|
70
|
28 |
|
return $result; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @param BufferInterface $entropy |
75
|
|
|
* @return string |
76
|
|
|
*/ |
77
|
30 |
|
public function entropyToMnemonic(BufferInterface $entropy) |
78
|
|
|
{ |
79
|
30 |
|
return implode(' ', $this->entropyToWords($entropy)); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @param string $mnemonic |
84
|
|
|
* @return BufferInterface |
85
|
|
|
*/ |
86
|
31 |
|
public function mnemonicToEntropy($mnemonic) |
87
|
|
|
{ |
88
|
31 |
|
$words = explode(' ', $mnemonic); |
89
|
|
|
|
90
|
31 |
|
if (count($words) % 3 !== 0) { |
91
|
1 |
|
throw new \InvalidArgumentException('Invalid mnemonic'); |
92
|
|
|
} |
93
|
|
|
|
94
|
30 |
|
$bits = array(); |
95
|
30 |
|
foreach ($words as $word) { |
96
|
30 |
|
$idx = $this->wordList->getIndex($word); |
97
|
30 |
|
$bits[] = str_pad(gmp_strval(gmp_init($idx, 10), 2), 11, '0', STR_PAD_LEFT); |
98
|
|
|
} |
99
|
|
|
|
100
|
30 |
|
$bits = implode('', $bits); |
101
|
|
|
|
102
|
|
|
// max entropy is 1024; (1024×8)+((1024×8)÷32) = 8448 |
|
|
|
|
103
|
30 |
|
if (strlen($bits) > 8448) { |
104
|
1 |
|
throw new \InvalidArgumentException('Invalid mnemonic, too long'); |
105
|
|
|
} |
106
|
|
|
|
107
|
29 |
|
$CS = strlen($bits) / 33; |
108
|
29 |
|
$ENT = strlen($bits) - $CS; |
109
|
|
|
|
110
|
29 |
|
$csBits = substr($bits, -1 * $CS); |
111
|
29 |
|
$entBits = substr($bits, 0, -1 * $CS); |
112
|
|
|
|
113
|
29 |
|
$binary = ''; |
114
|
29 |
|
$bitsInChar = 8; |
115
|
29 |
|
for ($i = 0; $i < $ENT; $i += $bitsInChar) { |
116
|
|
|
// Extract 8 bits at a time, convert to hex, pad, and convert to binary. |
117
|
29 |
|
$eBits = substr($entBits, $i, $bitsInChar); |
118
|
29 |
|
$binary .= pack("H*", (str_pad(gmp_strval(gmp_init($eBits, 2), 16), 2, '0', STR_PAD_LEFT))); |
119
|
|
|
} |
120
|
|
|
|
121
|
29 |
|
$entropy = new Buffer($binary, null); |
122
|
29 |
|
if ($csBits !== $this->calculateChecksum($entropy, $CS)) { |
123
|
1 |
|
throw new \InvalidArgumentException('Checksum does not match'); |
124
|
|
|
} |
125
|
|
|
|
126
|
28 |
|
return $entropy; |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.