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:
authorbitflash-coin <bookofshamers@outlook.com>2026-08-04 06:34:32 -0700
committerbitflash-coin <bookofshamers@outlook.com>2026-08-04 06:34:32 -0700
commit5ce650382352c1503f40abf08129ada69402af55 (patch)
tree6a18198901f1e2c4933f1ac53c9365eec512bf63 /src/bip32.cpp
parentf34113291da7e5e3dc7d2468d4f9c212c05c584b (diff)
public commit
Diffstat (limited to 'src/bip32.cpp')
-rw-r--r--src/bip32.cpp443
1 files changed, 443 insertions, 0 deletions
diff --git a/src/bip32.cpp b/src/bip32.cpp
new file mode 100644
index 0000000..6ba946a
--- /dev/null
+++ b/src/bip32.cpp
@@ -0,0 +1,443 @@
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
117static bool PrivateKeyToCompressedPubKey(const std::vector<unsigned char>& privateKey,
118 unsigned char out[33],
119 std::string& errorOut)
120{
121 if (!IsValidPrivateKey(privateKey))
122 {
123 errorOut = "private key is invalid";
124 return false;
125 }
126
127 BIGNUM* bn = BN_bin2bn(&privateKey[0], 32, NULL);
128 BN_CTX* ctx = BN_CTX_new();
129 EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1);
130 EC_POINT* pub = group ? EC_POINT_new(group) : NULL;
131
132 bool ok = false;
133 if (bn && ctx && group && pub &&
134 EC_POINT_mul(group, pub, bn, NULL, NULL, ctx))
135 {
136 size_t n = EC_POINT_point2oct(group, pub, POINT_CONVERSION_COMPRESSED,
137 out, 33, ctx);
138 ok = n == 33;
139 }
140
141 BN_free(bn);
142 BN_CTX_free(ctx);
143 EC_POINT_free(pub);
144 EC_GROUP_free(group);
145
146 if (!ok)
147 {
148 errorOut = "could not serialize parent public key";
149 return false;
150 }
151 return true;
152}
153
154bool BIP39EntropyToMnemonic(const std::vector<unsigned char>& entropy,
155 std::string& mnemonicOut,
156 std::string& errorOut)
157{
158 mnemonicOut.clear();
159 errorOut.clear();
160 if (!IsSupportedEntropySize(entropy.size()))
161 {
162 errorOut = "entropy must be 128, 160, 192, 224, or 256 bits";
163 return false;
164 }
165
166 unsigned char checksum[SHA256_DIGEST_LENGTH];
167 SHA256(&entropy[0], entropy.size(), checksum);
168
169 const size_t entropyBits = entropy.size() * 8;
170 const size_t checksumBits = entropyBits / 32;
171
172 std::vector<bool> bits;
173 bits.reserve(entropyBits + checksumBits);
174 AppendBits(bits, &entropy[0], entropyBits);
175 AppendBits(bits, checksum, checksumBits);
176
177 const size_t words = bits.size() / 11;
178 for (size_t i = 0; i < words; i++)
179 {
180 int idx = 0;
181 for (int j = 0; j < 11; j++)
182 idx = (idx << 1) | (bits[i * 11 + j] ? 1 : 0);
183 if (i != 0)
184 mnemonicOut += " ";
185 mnemonicOut += BIP39_ENGLISH_WORDS[idx];
186 }
187 return true;
188}
189
190bool BIP39GenerateMnemonic(std::string& mnemonicOut,
191 std::string& errorOut,
192 unsigned int entropyBytes)
193{
194 mnemonicOut.clear();
195 errorOut.clear();
196 if (!IsSupportedEntropySize(entropyBytes))
197 {
198 errorOut = "entropy must be 16, 20, 24, 28, or 32 bytes";
199 return false;
200 }
201
202 std::vector<unsigned char> entropy(entropyBytes);
203 if (RAND_bytes(&entropy[0], entropy.size()) != 1)
204 {
205 errorOut = "RAND_bytes failed";
206 return false;
207 }
208 bool ok = BIP39EntropyToMnemonic(entropy, mnemonicOut, errorOut);
209 OPENSSL_cleanse(&entropy[0], entropy.size());
210 return ok;
211}
212
213bool BIP39ValidateMnemonic(const std::string& mnemonic,
214 std::string& normalizedOut,
215 std::string& errorOut)
216{
217 normalizedOut = NormalizeMnemonic(mnemonic);
218 errorOut.clear();
219 std::vector<std::string> words = SplitWords(normalizedOut);
220 const size_t nWords = words.size();
221 if (nWords != 12 && nWords != 15 && nWords != 18 && nWords != 21 && nWords != 24)
222 {
223 errorOut = "mnemonic must contain 12, 15, 18, 21, or 24 words";
224 return false;
225 }
226
227 const std::map<std::string, int>& index = WordIndex();
228 std::vector<bool> bits;
229 bits.reserve(nWords * 11);
230 for (const std::string& word : words)
231 {
232 std::map<std::string, int>::const_iterator mi = index.find(word);
233 if (mi == index.end())
234 {
235 errorOut = "mnemonic contains a word outside the BIP39 English list";
236 return false;
237 }
238 int value = mi->second;
239 for (int i = 10; i >= 0; i--)
240 bits.push_back(((value >> i) & 1) != 0);
241 }
242
243 const size_t totalBits = bits.size();
244 const size_t entropyBits = totalBits * 32 / 33;
245 const size_t checksumBits = totalBits - entropyBits;
246 std::vector<unsigned char> entropy(entropyBits / 8, 0);
247 for (size_t i = 0; i < entropyBits; i++)
248 if (bits[i])
249 entropy[i / 8] |= (unsigned char)(1 << (7 - (i % 8)));
250
251 unsigned char checksum[SHA256_DIGEST_LENGTH];
252 SHA256(&entropy[0], entropy.size(), checksum);
253 for (size_t i = 0; i < checksumBits; i++)
254 {
255 if (bits[entropyBits + i] != GetBit(checksum, i))
256 {
257 errorOut = "mnemonic checksum failed";
258 return false;
259 }
260 }
261 return true;
262}
263
264bool BIP39MnemonicToSeed(const std::string& mnemonic,
265 const std::string& passphrase,
266 std::vector<unsigned char>& seedOut,
267 std::string& errorOut)
268{
269 seedOut.assign(64, 0);
270 std::string normalized;
271 if (!BIP39ValidateMnemonic(mnemonic, normalized, errorOut))
272 {
273 seedOut.clear();
274 return false;
275 }
276
277 const std::string salt = "mnemonic" + passphrase;
278 if (!PKCS5_PBKDF2_HMAC(normalized.c_str(), normalized.size(),
279 (const unsigned char*)salt.data(), salt.size(),
280 2048, EVP_sha512(), 64, &seedOut[0]))
281 {
282 seedOut.clear();
283 errorOut = "PBKDF2-HMAC-SHA512 failed";
284 return false;
285 }
286 return true;
287}
288
289bool BIP32MasterFromSeed(const std::vector<unsigned char>& seed,
290 BIP32PrivateNode& nodeOut,
291 std::string& errorOut)
292{
293 nodeOut.privateKey.clear();
294 nodeOut.chainCode.clear();
295 errorOut.clear();
296 if (seed.size() < 16 || seed.size() > 64)
297 {
298 errorOut = "BIP32 seed must be between 128 and 512 bits";
299 return false;
300 }
301
302 unsigned char I[64];
303 static const unsigned char key[] = "Bitcoin seed";
304 if (!HmacSha512(key, sizeof(key) - 1, &seed[0], seed.size(), I))
305 {
306 errorOut = "HMAC-SHA512 failed";
307 return false;
308 }
309
310 nodeOut.privateKey.assign(I, I + 32);
311 nodeOut.chainCode.assign(I + 32, I + 64);
312 OPENSSL_cleanse(I, sizeof(I));
313
314 if (!IsValidPrivateKey(nodeOut.privateKey))
315 {
316 nodeOut.privateKey.clear();
317 nodeOut.chainCode.clear();
318 errorOut = "BIP32 master key is invalid";
319 return false;
320 }
321 return true;
322}
323
324bool BIP32DeriveChild(const BIP32PrivateNode& parent,
325 unsigned int childNumber,
326 BIP32PrivateNode& childOut,
327 std::string& errorOut)
328{
329 childOut.privateKey.clear();
330 childOut.chainCode.clear();
331 errorOut.clear();
332 if (!IsValidPrivateKey(parent.privateKey) || parent.chainCode.size() != 32)
333 {
334 errorOut = "parent private key or chain code is invalid";
335 return false;
336 }
337 unsigned char data[37];
338 if (childNumber & BIP32_HARDENED)
339 {
340 data[0] = 0x00;
341 memcpy(data + 1, &parent.privateKey[0], 32);
342 }
343 else
344 {
345 if (!PrivateKeyToCompressedPubKey(parent.privateKey, data, errorOut))
346 return false;
347 }
348 data[33] = (unsigned char)((childNumber >> 24) & 0xff);
349 data[34] = (unsigned char)((childNumber >> 16) & 0xff);
350 data[35] = (unsigned char)((childNumber >> 8) & 0xff);
351 data[36] = (unsigned char)(childNumber & 0xff);
352
353 unsigned char I[64];
354 if (!HmacSha512(&parent.chainCode[0], 32, data, sizeof(data), I))
355 {
356 OPENSSL_cleanse(data, sizeof(data));
357 errorOut = "HMAC-SHA512 failed";
358 return false;
359 }
360 OPENSSL_cleanse(data, sizeof(data));
361
362 std::vector<unsigned char> IL(I, I + 32);
363 childOut.chainCode.assign(I + 32, I + 64);
364 OPENSSL_cleanse(I, sizeof(I));
365
366 BIGNUM* bnIL = BN_bin2bn(&IL[0], 32, NULL);
367 BIGNUM* bnParent = BN_bin2bn(&parent.privateKey[0], 32, NULL);
368 BIGNUM* bnOrder = BN_new();
369 BIGNUM* bnChild = BN_new();
370 BN_CTX* ctx = BN_CTX_new();
371 EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1);
372
373 bool ok = false;
374 if (bnIL && bnParent && bnOrder && bnChild && ctx && group &&
375 EC_GROUP_get_order(group, bnOrder, NULL) &&
376 BN_cmp(bnIL, bnOrder) < 0 &&
377 BN_mod_add(bnChild, bnIL, bnParent, bnOrder, ctx) &&
378 !BN_is_zero(bnChild))
379 {
380 childOut.privateKey.assign(32, 0);
381 int n = BN_num_bytes(bnChild);
382 BN_bn2bin(bnChild, &childOut.privateKey[32 - n]);
383 ok = true;
384 }
385
386 BN_free(bnIL);
387 BN_free(bnParent);
388 BN_free(bnOrder);
389 BN_free(bnChild);
390 BN_CTX_free(ctx);
391 EC_GROUP_free(group);
392 OPENSSL_cleanse(&IL[0], IL.size());
393
394 if (!ok)
395 {
396 childOut.privateKey.clear();
397 childOut.chainCode.clear();
398 errorOut = "BIP32 child derivation produced an invalid key";
399 return false;
400 }
401 return true;
402}
403
404bool BIP32DerivePath(const BIP32PrivateNode& root,
405 const std::vector<unsigned int>& path,
406 BIP32PrivateNode& nodeOut,
407 std::string& errorOut)
408{
409 nodeOut.privateKey.clear();
410 nodeOut.chainCode.clear();
411 errorOut.clear();
412 if (!IsValidPrivateKey(root.privateKey) || root.chainCode.size() != 32)
413 {
414 errorOut = "root private key or chain code is invalid";
415 return false;
416 }
417
418 BIP32PrivateNode cur = root;
419 for (size_t i = 0; i < path.size(); i++)
420 {
421 BIP32PrivateNode next;
422 if (!BIP32DeriveChild(cur, path[i], next, errorOut))
423 return false;
424 cur = next;
425 }
426 nodeOut = cur;
427 return true;
428}
429
430bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent,
431 unsigned int childIndex,
432 BIP32PrivateNode& childOut,
433 std::string& errorOut)
434{
435 if (childIndex >= BIP32_HARDENED)
436 {
437 errorOut = "child index must be non-hardened; hardening is applied here";
438 return false;
439 }
440 return BIP32DeriveChild(parent, childIndex | BIP32_HARDENED, childOut, errorOut);
441}
442
443} // namespace bitflash