upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/test_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/test_bip32.cpp
parentf34113291da7e5e3dc7d2468d4f9c212c05c584b (diff)
public commit
Diffstat (limited to 'src/test_bip32.cpp')
-rw-r--r--src/test_bip32.cpp249
1 files changed, 249 insertions, 0 deletions
diff --git a/src/test_bip32.cpp b/src/test_bip32.cpp
new file mode 100644
index 0000000..6c11510
--- /dev/null
+++ b/src/test_bip32.cpp
@@ -0,0 +1,249 @@
1// Standalone tests for BIP39/BIP32 primitives.
2//
3// Build:
4// g++ -std=gnu++14 test_bip32.cpp bip32.cpp -lssl -lcrypto -o test_bip32
5
6#include "bip32.h"
7
8#include <openssl/sha.h>
9
10#include <cstdio>
11#include <cstring>
12#include <string>
13#include <vector>
14
15using namespace bitflash;
16
17static int g_fail = 0;
18
19#define CHECK(cond, name) do { \
20 if (cond) { printf(" ok %s\n", name); } \
21 else { printf(" FAIL %s\n", name); g_fail++; } \
22} while (0)
23
24static int HexDigit(char c)
25{
26 if (c >= '0' && c <= '9') return c - '0';
27 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
28 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
29 return -1;
30}
31
32static std::vector<unsigned char> FromHex(const std::string& s)
33{
34 std::vector<unsigned char> out;
35 if (s.size() % 2 != 0)
36 return out;
37 out.reserve(s.size() / 2);
38 for (size_t i = 0; i < s.size(); i += 2)
39 {
40 int hi = HexDigit(s[i]);
41 int lo = HexDigit(s[i + 1]);
42 if (hi < 0 || lo < 0)
43 return std::vector<unsigned char>();
44 out.push_back((unsigned char)((hi << 4) | lo));
45 }
46 return out;
47}
48
49static std::string ToHex(const std::vector<unsigned char>& v)
50{
51 static const char* hex = "0123456789abcdef";
52 std::string out;
53 out.reserve(v.size() * 2);
54 for (unsigned char c : v)
55 {
56 out.push_back(hex[c >> 4]);
57 out.push_back(hex[c & 0x0f]);
58 }
59 return out;
60}
61
62static bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& out)
63{
64 static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
65 out.clear();
66
67 std::vector<unsigned char> b256(str.size() * 733 / 1000 + 1);
68 for (char c : str)
69 {
70 const char* p = strchr(pszBase58, c);
71 if (!p)
72 return false;
73 int carry = (int)(p - pszBase58);
74 for (std::vector<unsigned char>::reverse_iterator it = b256.rbegin();
75 it != b256.rend(); ++it)
76 {
77 carry += 58 * (*it);
78 *it = (unsigned char)(carry & 0xff);
79 carry >>= 8;
80 }
81 if (carry != 0)
82 return false;
83 }
84
85 size_t zeros = 0;
86 while (zeros < str.size() && str[zeros] == '1')
87 zeros++;
88 std::vector<unsigned char>::iterator it = b256.begin();
89 while (it != b256.end() && *it == 0)
90 ++it;
91
92 std::vector<unsigned char> full;
93 full.assign(zeros, 0);
94 while (it != b256.end())
95 full.push_back(*it++);
96 if (full.size() < 4)
97 return false;
98
99 std::vector<unsigned char> payload(full.begin(), full.end() - 4);
100 unsigned char h1[SHA256_DIGEST_LENGTH];
101 unsigned char h2[SHA256_DIGEST_LENGTH];
102 SHA256(&payload[0], payload.size(), h1);
103 SHA256(h1, sizeof(h1), h2);
104 if (memcmp(h2, &full[full.size() - 4], 4) != 0)
105 return false;
106
107 out = payload;
108 return true;
109}
110
111static bool DecodeXPrvNode(const std::string& xprv, BIP32PrivateNode& nodeOut)
112{
113 nodeOut.privateKey.clear();
114 nodeOut.chainCode.clear();
115
116 std::vector<unsigned char> payload;
117 if (!DecodeBase58Check(xprv, payload))
118 return false;
119 if (payload.size() != 78)
120 return false;
121 if (payload[0] != 0x04 || payload[1] != 0x88 ||
122 payload[2] != 0xad || payload[3] != 0xe4)
123 return false;
124 if (payload[45] != 0x00)
125 return false;
126
127 nodeOut.chainCode.assign(payload.begin() + 13, payload.begin() + 45);
128 nodeOut.privateKey.assign(payload.begin() + 46, payload.end());
129 return true;
130}
131
132int main()
133{
134 std::string err;
135
136 printf("bip39_entropy_to_mnemonic\n");
137 {
138 std::vector<unsigned char> entropy(16, 0);
139 std::string mnemonic;
140 CHECK(BIP39EntropyToMnemonic(entropy, mnemonic, err), "zero entropy encodes");
141 CHECK(mnemonic ==
142 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
143 "matches BIP39 vector");
144 }
145
146 printf("bip39_validation\n");
147 {
148 std::string normalized;
149 CHECK(BIP39ValidateMnemonic(
150 " Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ",
151 normalized, err),
152 "valid phrase normalizes");
153 CHECK(normalized ==
154 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
155 "normalized text matches");
156 CHECK(!BIP39ValidateMnemonic(
157 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon",
158 normalized, err),
159 "bad checksum rejected");
160 }
161
162 printf("bip39_seed\n");
163 {
164 std::vector<unsigned char> seed;
165 const std::string mnemonic =
166 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
167 CHECK(BIP39MnemonicToSeed(mnemonic, "TREZOR", seed, err), "mnemonic derives seed");
168 CHECK(ToHex(seed) ==
169 "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e5349553"
170 "1f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
171 "matches BIP39 seed vector");
172 }
173
174 printf("bip32_master\n");
175 {
176 std::vector<unsigned char> seed = FromHex("000102030405060708090a0b0c0d0e0f");
177 BIP32PrivateNode master;
178 CHECK(BIP32MasterFromSeed(seed, master, err), "master key derives");
179 CHECK(ToHex(master.privateKey) ==
180 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
181 "matches BIP32 master private key");
182 CHECK(ToHex(master.chainCode) ==
183 "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508",
184 "matches BIP32 master chain code");
185
186 BIP32PrivateNode childA, childB;
187 CHECK(BIP32DeriveHardenedChild(master, 0, childA, err), "hardened child derives");
188 CHECK(BIP32DeriveHardenedChild(master, 0, childB, err), "hardened child re-derives");
189 CHECK(childA.privateKey == childB.privateKey &&
190 childA.chainCode == childB.chainCode,
191 "same child path is deterministic");
192 CHECK(childA.privateKey != master.privateKey &&
193 childA.chainCode != master.chainCode,
194 "child differs from parent");
195 CHECK(!BIP32DeriveHardenedChild(master, 0x80000000U, childA, err),
196 "pre-hardened index rejected");
197 }
198
199 printf("bip32_paths\n");
200 {
201 std::vector<unsigned char> seed = FromHex("000102030405060708090a0b0c0d0e0f");
202 BIP32PrivateNode master;
203 CHECK(BIP32MasterFromSeed(seed, master, err), "path test master derives");
204
205 struct PathVector
206 {
207 const char* name;
208 std::vector<unsigned int> path;
209 const char* xprv;
210 };
211
212 std::vector<PathVector> vectors;
213 vectors.push_back({"m",
214 {},
215 "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"});
216 vectors.push_back({"m/0'",
217 {0 | BIP32_HARDENED},
218 "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"});
219 vectors.push_back({"m/0'/1",
220 {0 | BIP32_HARDENED, 1},
221 "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"});
222 vectors.push_back({"m/0'/1/2'",
223 {0 | BIP32_HARDENED, 1, 2 | BIP32_HARDENED},
224 "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"});
225 vectors.push_back({"m/0'/1/2'/2",
226 {0 | BIP32_HARDENED, 1, 2 | BIP32_HARDENED, 2},
227 "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334"});
228 vectors.push_back({"m/0'/1/2'/2/1000000000",
229 {0 | BIP32_HARDENED, 1, 2 | BIP32_HARDENED, 2, 1000000000},
230 "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"});
231
232 for (const PathVector& v : vectors)
233 {
234 BIP32PrivateNode actual;
235 BIP32PrivateNode expected;
236 std::string label = std::string("derives ") + v.name;
237 CHECK(BIP32DerivePath(master, v.path, actual, err), label.c_str());
238 label = std::string("decodes vector ") + v.name;
239 CHECK(DecodeXPrvNode(v.xprv, expected), label.c_str());
240 label = std::string("matches vector ") + v.name;
241 CHECK(actual.privateKey == expected.privateKey &&
242 actual.chainCode == expected.chainCode, label.c_str());
243 }
244 }
245
246 printf("\n%s (%d failures)\n",
247 g_fail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", g_fail);
248 return g_fail == 0 ? 0 : 1;
249}