Completed
Pull Request — master (#327)
by thomas
23:46 queued 05:54
created

Bip39Mnemonic   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 130
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 92.31%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 130
ccs 48
cts 52
cp 0.9231
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 18 2
A entropyToMnemonic() 0 4 1
B mnemonicToEntropy() 0 38 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 9
    public function __construct(EcAdapterInterface $ecAdapter, Bip39WordListInterface $wordList)
29
    {
30 9
        $this->ecAdapter = $ecAdapter;
31 9
        $this->wordList = $wordList;
32 9
    }
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 147
    private function calculateChecksum(BufferInterface $entropy, $CSlen)
55
    {
56 147
        $entHash = Hash::sha256($entropy);
57
58
        // Convert byte string to padded binary string of 0/1's.
59 147
        $hashBits = str_pad(gmp_strval($entHash->getGmp(), 2), 256, '0', STR_PAD_LEFT);
60
61
        // Take $CSlen bits for the checksum
62 147
        $checksumBits = substr($hashBits, 0, $CSlen);
63
64 147
        return $checksumBits;
65
    }
66
67
    /**
68
     * @param BufferInterface $entropy
69
     * @return array
70
     */
71 72
    public function entropyToWords(BufferInterface $entropy)
72
    {
73 72
        $math = $this->ecAdapter->getMath();
74
75 72
        $ENT = $entropy->getSize() * 8;
76 72
        $CS = $ENT / 32;
77
78 72
        $bits = gmp_strval($entropy->getGmp(), 2) . $this->calculateChecksum($entropy, $CS);
79 72
        $bits = str_pad($bits, ($ENT + $CS), '0', STR_PAD_LEFT);
80
81 72
        $result = [];
82 72
        foreach (str_split($bits, 11) as $bit) {
83 72
            $idx = $math->baseConvert($bit, 2, 10);
84 72
            $result[] = $this->wordList->getWord($idx);
85 48
        }
86
87 72
        return $result;
88
    }
89
90
    /**
91
     * @param BufferInterface $entropy
92
     * @return string
93
     */
94 72
    public function entropyToMnemonic(BufferInterface $entropy)
95
    {
96 72
        return implode(' ', $this->entropyToWords($entropy));
97
    }
98
99
    /**
100
     * @param string $mnemonic
101
     * @return BufferInterface
102
     */
103 78
    public function mnemonicToEntropy($mnemonic)
104
    {
105 78
        $math = $this->ecAdapter->getMath();
106 78
        $words = explode(' ', $mnemonic);
107
108 78
        if (count($words) % 3 !== 0) {
109 3
            throw new \InvalidArgumentException('Invalid mnemonic');
110
        }
111
112 75
        $bits = array();
113 75
        foreach ($words as $word) {
114 75
            $idx = $this->wordList->getIndex($word);
115 75
            $bits[] = str_pad($math->baseConvert($idx, 10, 2), 11, '0', STR_PAD_LEFT);
116 50
        }
117
118 75
        $bits = implode('', $bits);
119
120 75
        $CS = strlen($bits) / 33;
121 75
        $ENT = strlen($bits) - $CS;
122
123 75
        $csBits = substr($bits, -1 * $CS);
124 75
        $entBits = substr($bits, 0, -1 * $CS);
125
126 75
        $binary = '';
127 75
        $bitsInChar = 8;
128 75
        for ($i = 0; $i < $ENT; $i += $bitsInChar) {
129
            // Extract 8 bits at a time, convert to hex, pad, and convert to binary.
130 75
            $eBits = substr($entBits, $i, $bitsInChar);
131 75
            $binary .= pack("H*", (str_pad($math->baseConvert($eBits, 2, 16), 2, '0', STR_PAD_LEFT)));
132 50
        }
133
134 75
        $entropy = new Buffer($binary, null, $math);
135 75
        if ($csBits !== $this->calculateChecksum($entropy, $CS)) {
136 3
            throw new \InvalidArgumentException('Checksum does not match');
137
        }
138
139 72
        return $entropy;
140
    }
141
}
142