upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/bip32.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bip32.cpp')
-rw-r--r--src/bip32.cpp366
1 files changed, 366 insertions, 0 deletions
diff --git a/src/bip32.cpp b/src/bip32.cpp
new file mode 100644
index 0000000..6423974
--- /dev/null
+++ b/src/bip32.cpp
@@ -0,0 +1,366 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3
4#include "bip32.h"
5#include "bip39_english.h"
6
7#include <openssl/bn.h>
8#include <openssl/crypto.h>
9#include <openssl/ec.h>
10#include <openssl/evp.h>
11#include <openssl/hmac.h>
12#include <openssl/obj_mac.h>
13#include <openssl/rand.h>
14#include <openssl/sha.h>
15
16#include <algorithm>
17#include <cctype>
18#include <cstring>
19#include <map>
20#include <sstream>
21
22namespace bitflash
23{
24
25static bool IsSupportedEntropySize(size_t n)
26{
27 return n == 16 || n == 20 || n == 24 || n == 28 || n == 32;
28}
29
30static bool GetBit(const unsigned char* data, size_t bit)
31{
32 return (data[bit / 8] >> (7 - (bit % 8))) & 1;
33}
34
35static void AppendBits(std::vector<bool>& out, const unsigned char* data,
36 size_t nBits)
37{
38 for (size_t i = 0; i < nBits; i++)
39 out.push_back(GetBit(data, i));
40}
41
42static std::string NormalizeMnemonic(const std::string& in)
43{
44 std::string out;
45 bool inSpace = true;
46 for (unsigned char c : in)
47 {
48 if (std::isspace(c))
49 {
50 if (!inSpace)
51 out.push_back(' ');
52 inSpace = true;
53 }
54 else
55 {
56 out.push_back((char)std::tolower(c));
57 inSpace = false;
58 }
59 }
60 if (!out.empty() && out.back() == ' ')
61 out.pop_back();
62 return out;
63}
64
65static std::vector<std::string> SplitWords(const std::string& phrase)
66{
67 std::vector<std::string> words;
68 std::istringstream in(phrase);
69 std::string word;
70 while (in >> word)
71 words.push_back(word);
72 return words;
73}
74
75static const std::map<std::string, int>& WordIndex()
76{
77 static std::map<std::string, int> m;
78 if (m.empty())
79 {
80 for (int i = 0; i < 2048; i++)
81 m[BIP39_ENGLISH_WORDS[i]] = i;
82 }
83 return m;
84}
85
86static bool IsValidPrivateKey(const std::vector<unsigned char>& key)
87{
88 if (key.size() != 32)
89 return false;
90 bool fNonZero = false;
91 for (unsigned char c : key)
92 fNonZero = fNonZero || c != 0;
93 if (!fNonZero)
94 return false;
95
96 bool ok = false;
97 BIGNUM* bn = BN_bin2bn(&key[0], 32, NULL);
98 BIGNUM* order = BN_new();
99 EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1);
100 if (bn && order && group && EC_GROUP_get_order(group, order, NULL))
101 ok = BN_cmp(bn, order) < 0;
102 BN_free(bn);
103 BN_free(order);
104 EC_GROUP_free(group);
105 return ok;
106}
107
108static bool HmacSha512(const unsigned char* key, int keyLen,
109 const unsigned char* data, size_t dataLen,
110 unsigned char out[64])
111{
112 unsigned int len = 64;
113 return HMAC(EVP_sha512(), key, keyLen, data, dataLen, out, &len) != NULL &&
114 len == 64;
115}
116
117bool BIP39EntropyToMnemonic(const std::vector<unsigned char>& entropy,
118 std::string& mnemonicOut,
119 std::string& errorOut)
120{
121 mnemonicOut.clear();
122 errorOut.clear();
123 if (!IsSupportedEntropySize(entropy.size()))
124 {
125 errorOut = "entropy must be 128, 160, 192, 224, or 256 bits";
126 return false;
127 }
128
129 unsigned char checksum[SHA256_DIGEST_LENGTH];
130 SHA256(&entropy[0], entropy.size(), checksum);
131
132 const size_t entropyBits = entropy.size() * 8;
133 const size_t checksumBits = entropyBits / 32;
134
135 std::vector<bool> bits;
136 bits.reserve(entropyBits + checksumBits);
137 AppendBits(bits, &entropy[0], entropyBits);
138 AppendBits(bits, checksum, checksumBits);
139
140 const size_t words = bits.size() / 11;
141 for (size_t i = 0; i < words; i++)
142 {
143 int idx = 0;
144 for (int j = 0; j < 11; j++)
145 idx = (idx << 1) | (bits[i * 11 + j] ? 1 : 0);
146 if (i != 0)
147 mnemonicOut += " ";
148 mnemonicOut += BIP39_ENGLISH_WORDS[idx];
149 }
150 return true;
151}
152
153bool BIP39GenerateMnemonic(std::string& mnemonicOut,
154 std::string& errorOut,
155 unsigned int entropyBytes)
156{
157 mnemonicOut.clear();
158 errorOut.clear();
159 if (!IsSupportedEntropySize(entropyBytes))
160 {
161 errorOut = "entropy must be 16, 20, 24, 28, or 32 bytes";
162 return false;
163 }
164
165 std::vector<unsigned char> entropy(entropyBytes);
166 if (RAND_bytes(&entropy[0], entropy.size()) != 1)
167 {
168 errorOut = "RAND_bytes failed";
169 return false;
170 }
171 bool ok = BIP39EntropyToMnemonic(entropy, mnemonicOut, errorOut);
172 OPENSSL_cleanse(&entropy[0], entropy.size());
173 return ok;
174}
175
176bool BIP39ValidateMnemonic(const std::string& mnemonic,
177 std::string& normalizedOut,
178 std::string& errorOut)
179{
180 normalizedOut = NormalizeMnemonic(mnemonic);
181 errorOut.clear();
182 std::vector<std::string> words = SplitWords(normalizedOut);
183 const size_t nWords = words.size();
184 if (nWords != 12 && nWords != 15 && nWords != 18 && nWords != 21 && nWords != 24)
185 {
186 errorOut = "mnemonic must contain 12, 15, 18, 21, or 24 words";
187 return false;
188 }
189
190 const std::map<std::string, int>& index = WordIndex();
191 std::vector<bool> bits;
192 bits.reserve(nWords * 11);
193 for (const std::string& word : words)
194 {
195 std::map<std::string, int>::const_iterator mi = index.find(word);
196 if (mi == index.end())
197 {
198 errorOut = "mnemonic contains a word outside the BIP39 English list";
199 return false;
200 }
201 int value = mi->second;
202 for (int i = 10; i >= 0; i--)
203 bits.push_back(((value >> i) & 1) != 0);
204 }
205
206 const size_t totalBits = bits.size();
207 const size_t entropyBits = totalBits * 32 / 33;
208 const size_t checksumBits = totalBits - entropyBits;
209 std::vector<unsigned char> entropy(entropyBits / 8, 0);
210 for (size_t i = 0; i < entropyBits; i++)
211 if (bits[i])
212 entropy[i / 8] |= (unsigned char)(1 << (7 - (i % 8)));
213
214 unsigned char checksum[SHA256_DIGEST_LENGTH];
215 SHA256(&entropy[0], entropy.size(), checksum);
216 for (size_t i = 0; i < checksumBits; i++)
217 {
218 if (bits[entropyBits + i] != GetBit(checksum, i))
219 {
220 errorOut = "mnemonic checksum failed";
221 return false;
222 }
223 }
224 return true;
225}
226
227bool BIP39MnemonicToSeed(const std::string& mnemonic,
228 const std::string& passphrase,
229 std::vector<unsigned char>& seedOut,
230 std::string& errorOut)
231{
232 seedOut.assign(64, 0);
233 std::string normalized;
234 if (!BIP39ValidateMnemonic(mnemonic, normalized, errorOut))
235 {
236 seedOut.clear();
237 return false;
238 }
239
240 const std::string salt = "mnemonic" + passphrase;
241 if (!PKCS5_PBKDF2_HMAC(normalized.c_str(), normalized.size(),
242 (const unsigned char*)salt.data(), salt.size(),
243 2048, EVP_sha512(), 64, &seedOut[0]))
244 {
245 seedOut.clear();
246 errorOut = "PBKDF2-HMAC-SHA512 failed";
247 return false;
248 }
249 return true;
250}
251
252bool BIP32MasterFromSeed(const std::vector<unsigned char>& seed,
253 BIP32PrivateNode& nodeOut,
254 std::string& errorOut)
255{
256 nodeOut.privateKey.clear();
257 nodeOut.chainCode.clear();
258 errorOut.clear();
259 if (seed.size() < 16 || seed.size() > 64)
260 {
261 errorOut = "BIP32 seed must be between 128 and 512 bits";
262 return false;
263 }
264
265 unsigned char I[64];
266 static const unsigned char key[] = "Bitcoin seed";
267 if (!HmacSha512(key, sizeof(key) - 1, &seed[0], seed.size(), I))
268 {
269 errorOut = "HMAC-SHA512 failed";
270 return false;
271 }
272
273 nodeOut.privateKey.assign(I, I + 32);
274 nodeOut.chainCode.assign(I + 32, I + 64);
275 OPENSSL_cleanse(I, sizeof(I));
276
277 if (!IsValidPrivateKey(nodeOut.privateKey))
278 {
279 nodeOut.privateKey.clear();
280 nodeOut.chainCode.clear();
281 errorOut = "BIP32 master key is invalid";
282 return false;
283 }
284 return true;
285}
286
287bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent,
288 unsigned int childIndex,
289 BIP32PrivateNode& childOut,
290 std::string& errorOut)
291{
292 childOut.privateKey.clear();
293 childOut.chainCode.clear();
294 errorOut.clear();
295 if (!IsValidPrivateKey(parent.privateKey) || parent.chainCode.size() != 32)
296 {
297 errorOut = "parent private key or chain code is invalid";
298 return false;
299 }
300 if (childIndex >= 0x80000000U)
301 {
302 errorOut = "child index must be non-hardened; hardening is applied here";
303 return false;
304 }
305
306 const unsigned int hardened = childIndex | 0x80000000U;
307 unsigned char data[37];
308 data[0] = 0x00;
309 memcpy(data + 1, &parent.privateKey[0], 32);
310 data[33] = (unsigned char)((hardened >> 24) & 0xff);
311 data[34] = (unsigned char)((hardened >> 16) & 0xff);
312 data[35] = (unsigned char)((hardened >> 8) & 0xff);
313 data[36] = (unsigned char)(hardened & 0xff);
314
315 unsigned char I[64];
316 if (!HmacSha512(&parent.chainCode[0], 32, data, sizeof(data), I))
317 {
318 OPENSSL_cleanse(data, sizeof(data));
319 errorOut = "HMAC-SHA512 failed";
320 return false;
321 }
322 OPENSSL_cleanse(data, sizeof(data));
323
324 std::vector<unsigned char> IL(I, I + 32);
325 childOut.chainCode.assign(I + 32, I + 64);
326 OPENSSL_cleanse(I, sizeof(I));
327
328 BIGNUM* bnIL = BN_bin2bn(&IL[0], 32, NULL);
329 BIGNUM* bnParent = BN_bin2bn(&parent.privateKey[0], 32, NULL);
330 BIGNUM* bnOrder = BN_new();
331 BIGNUM* bnChild = BN_new();
332 BN_CTX* ctx = BN_CTX_new();
333 EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1);
334
335 bool ok = false;
336 if (bnIL && bnParent && bnOrder && bnChild && ctx && group &&
337 EC_GROUP_get_order(group, bnOrder, NULL) &&
338 BN_cmp(bnIL, bnOrder) < 0 &&
339 BN_mod_add(bnChild, bnIL, bnParent, bnOrder, ctx) &&
340 !BN_is_zero(bnChild))
341 {
342 childOut.privateKey.assign(32, 0);
343 int n = BN_num_bytes(bnChild);
344 BN_bn2bin(bnChild, &childOut.privateKey[32 - n]);
345 ok = true;
346 }
347
348 BN_free(bnIL);
349 BN_free(bnParent);
350 BN_free(bnOrder);
351 BN_free(bnChild);
352 BN_CTX_free(ctx);
353 EC_GROUP_free(group);
354 OPENSSL_cleanse(&IL[0], IL.size());
355
356 if (!ok)
357 {
358 childOut.privateKey.clear();
359 childOut.chainCode.clear();
360 errorOut = "BIP32 child derivation produced an invalid key";
361 return false;
362 }
363 return true;
364}
365
366} // namespace bitflash