Completed
Pull Request — master (#371)
by Ruben de
73:41 queued 69:36
created

Bip39Mnemonic   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 140
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 93.1%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 140
ccs 54
cts 58
cp 0.931
rs 10
wmc 11
lcom 1
cbo 7

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A create() 0 7 1
A calculateChecksum() 0 12 1
A entropyToWords() 0 19 2
A entropyToMnemonic() 0 4 1
B mnemonicToEntropy() 0 47 5
1
<?php
2
3
namespace BitWasp\Bitcoin\Mnemonic\Bip39;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\Hash;
7
use BitWasp\Bitcoin\Crypto\Random\Random;
8
use BitWasp\Bitcoin\Mnemonic\MnemonicInterface;
9
use BitWasp\Buffertools\Buffer;
10
use BitWasp\Buffertools\BufferInterface;
11
12
class Bip39Mnemonic implements MnemonicInterface
13
{
14
    /**
15
     * @var EcAdapterInterface
16
     */
17
    private $ecAdapter;
18
19
    /**
20
     * @var Bip39WordListInterface
21
     */
22
    private $wordList;
23
24
    /**
25
     * @param EcAdapterInterface $ecAdapter
26
     * @param Bip39WordListInterface $wordList
27
     */
28 18
    public function __construct(EcAdapterInterface $ecAdapter, Bip39WordListInterface $wordList)
29
    {
30 18
        $this->ecAdapter = $ecAdapter;
31 18
        $this->wordList = $wordList;
32 18
    }
33
34
    /**
35
     * Creates a new Bip39 mnemonic string.
36
     *
37
     * @param int $entropySize
38
     * @return string
39
     * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
40
     */
41
    public function create($entropySize = 512)
42
    {
43
        $random = new Random();
44
        $entropy = $random->bytes($entropySize / 8);
45
46
        return $this->entropyToMnemonic($entropy);
47
    }
48
49
    /**
50
     * @param BufferInterface $entropy
51
     * @param integer $CSlen
52
     * @return string
53
     */
54 306
    private function calculateChecksum(BufferInterface $entropy, $CSlen)
55
    {
56 306
        $entHash = Hash::sha256($entropy);
57
58
        // Convert byte string to padded binary string of 0/1's.
59 306
        $hashBits = str_pad(gmp_strval($entHash->getGmp(), 2), 256, '0', STR_PAD_LEFT);
60
61
        // Take $CSlen bits for the checksum
62 306
        $checksumBits = substr($hashBits, 0, $CSlen);
63
64 306
        return $checksumBits;
65
    }
66
67
    /**
68
     * @param BufferInterface $entropy
69
     * @return array
70
     */
71 150
    public function entropyToWords(BufferInterface $entropy)
72
    {
73 150
        $math = $this->ecAdapter->getMath();
74
75 150
        $ENT = $entropy->getSize() * 8;
76 150
        $CS = $ENT / 32;
77
78 150
        $entBits = str_pad(gmp_strval($entropy->getGmp(), 2), $ENT, '0', STR_PAD_LEFT);
79
80 150
        $bits = $entBits . $this->calculateChecksum($entropy, $CS);
81
82 150
        $result = [];
83 150
        foreach (str_split($bits, 11) as $bit) {
84 150
            $idx = $math->baseConvert($bit, 2, 10);
85 150
            $result[] = $this->wordList->getWord($idx);
86 50
        }
87
88 150
        return $result;
89
    }
90
91
    /**
92
     * @param BufferInterface $entropy
93
     * @return string
94
     */
95 150
    public function entropyToMnemonic(BufferInterface $entropy)
96
    {
97 150
        return implode(' ', $this->entropyToWords($entropy));
98
    }
99
100
    /**
101
     * @param string $mnemonic
102
     * @return BufferInterface
103
     */
104 162
    public function mnemonicToEntropy($mnemonic)
105
    {
106 162
        $math = $this->ecAdapter->getMath();
107 162
        $words = explode(' ', $mnemonic);
108
109 162
        if (count($words) % 3 !== 0) {
110 6
            throw new \InvalidArgumentException('Invalid mnemonic');
111
        }
112
113 156
        $bits = array();
114 156
        foreach ($words as $word) {
115 156
            $idx = $this->wordList->getIndex($word);
116 156
            $bits[] = str_pad($math->baseConvert($idx, 10, 2), 11, '0', STR_PAD_LEFT);
117 52
        }
118
119 156
        $bits = implode('', $bits);
120
121 156
        $CS = strlen($bits) / 33;
122 156
        $ENT = strlen($bits) - $CS;
123
124 156
        $csBits = substr($bits, -1 * $CS);
125 156
        $entBits = substr($bits, 0, -1 * $CS);
126
127 156
        $binary = '';
128 156
        $bitsInChar = 8;
129 156
        for ($i = 0; $i < $ENT; $i += $bitsInChar) {
130
            // Extract 8 bits at a time, convert to hex, pad, and convert to binary.
131 156
            $eBits = substr($entBits, $i, $bitsInChar);
132 156
            $binary .= pack("H*", (str_pad($math->baseConvert($eBits, 2, 16), 2, '0', STR_PAD_LEFT)));
133 52
        }
134
135 156
        $entropy = new Buffer($binary, null, $math);
136 156
        $newChecksum = $this->calculateChecksum($entropy, $CS);
137
138
        // recreate properly chunked and padded bits to get the properly padded checksum
139 156
        $bits2 = implode('', array_map(function ($chunk) {
140 156
            return str_pad($chunk, 11, '0', STR_PAD_LEFT);
141 156
        }, str_split($entBits . $newChecksum, 11)));
142 156
        $CS2 = strlen($bits2) / 33;
143 156
        $csBits2 = substr($bits2, -1 * $CS2);
144
145 156
        if ($csBits !== $csBits2) {
146 6
            throw new \InvalidArgumentException('Checksum does not match');
147
        }
148
149 150
        return $entropy;
150
    }
151
}
152