From 64b9507846846e468ebba7e00fb59971edceca3b Mon Sep 17 00:00:00 2001 From: mtg0001 <106211915+mtg0001@users.noreply.github.com> Date: Sat, 1 Aug 2026 12:23:28 -0300 Subject: Add BIP39 and BIP32 foundation tests (#83) Co-authored-by: Gilcimar Duarte --- src/Makefile | 8 +- src/bip32.cpp | 366 +++++++++ src/bip32.h | 53 ++ src/bip39_english.h | 2062 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/makefile.mingw | 9 +- src/test_bip32.cpp | 130 ++++ 6 files changed, 2626 insertions(+), 2 deletions(-) create mode 100644 src/bip32.cpp create mode 100644 src/bip32.h create mode 100644 src/bip39_english.h create mode 100644 src/test_bip32.cpp (limited to 'src') diff --git a/src/Makefile b/src/Makefile index f0c6706..4e104c0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -121,4 +121,10 @@ bitflash-node: $(NODE_ONLY_OBJS) @echo "Built: bitflash-node (headless, no libGL needed)" clean: - rm -rf obj obj-node bitflash bitflash-node + rm -rf obj obj-node bitflash bitflash-node test_bip32 + +test_bip32: test_bip32.cpp bip32.cpp bip32.h bip39_english.h + $(CXX) $(CXXFLAGS) -o $@ test_bip32.cpp bip32.cpp -lssl -lcrypto + +tests: test_bip32 + ./test_bip32 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 @@ +// Copyright (c) 2026 Bitflash developers +// Distributed under the MIT/X11 software license. + +#include "bip32.h" +#include "bip39_english.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace bitflash +{ + +static bool IsSupportedEntropySize(size_t n) +{ + return n == 16 || n == 20 || n == 24 || n == 28 || n == 32; +} + +static bool GetBit(const unsigned char* data, size_t bit) +{ + return (data[bit / 8] >> (7 - (bit % 8))) & 1; +} + +static void AppendBits(std::vector& out, const unsigned char* data, + size_t nBits) +{ + for (size_t i = 0; i < nBits; i++) + out.push_back(GetBit(data, i)); +} + +static std::string NormalizeMnemonic(const std::string& in) +{ + std::string out; + bool inSpace = true; + for (unsigned char c : in) + { + if (std::isspace(c)) + { + if (!inSpace) + out.push_back(' '); + inSpace = true; + } + else + { + out.push_back((char)std::tolower(c)); + inSpace = false; + } + } + if (!out.empty() && out.back() == ' ') + out.pop_back(); + return out; +} + +static std::vector SplitWords(const std::string& phrase) +{ + std::vector words; + std::istringstream in(phrase); + std::string word; + while (in >> word) + words.push_back(word); + return words; +} + +static const std::map& WordIndex() +{ + static std::map m; + if (m.empty()) + { + for (int i = 0; i < 2048; i++) + m[BIP39_ENGLISH_WORDS[i]] = i; + } + return m; +} + +static bool IsValidPrivateKey(const std::vector& key) +{ + if (key.size() != 32) + return false; + bool fNonZero = false; + for (unsigned char c : key) + fNonZero = fNonZero || c != 0; + if (!fNonZero) + return false; + + bool ok = false; + BIGNUM* bn = BN_bin2bn(&key[0], 32, NULL); + BIGNUM* order = BN_new(); + EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1); + if (bn && order && group && EC_GROUP_get_order(group, order, NULL)) + ok = BN_cmp(bn, order) < 0; + BN_free(bn); + BN_free(order); + EC_GROUP_free(group); + return ok; +} + +static bool HmacSha512(const unsigned char* key, int keyLen, + const unsigned char* data, size_t dataLen, + unsigned char out[64]) +{ + unsigned int len = 64; + return HMAC(EVP_sha512(), key, keyLen, data, dataLen, out, &len) != NULL && + len == 64; +} + +bool BIP39EntropyToMnemonic(const std::vector& entropy, + std::string& mnemonicOut, + std::string& errorOut) +{ + mnemonicOut.clear(); + errorOut.clear(); + if (!IsSupportedEntropySize(entropy.size())) + { + errorOut = "entropy must be 128, 160, 192, 224, or 256 bits"; + return false; + } + + unsigned char checksum[SHA256_DIGEST_LENGTH]; + SHA256(&entropy[0], entropy.size(), checksum); + + const size_t entropyBits = entropy.size() * 8; + const size_t checksumBits = entropyBits / 32; + + std::vector bits; + bits.reserve(entropyBits + checksumBits); + AppendBits(bits, &entropy[0], entropyBits); + AppendBits(bits, checksum, checksumBits); + + const size_t words = bits.size() / 11; + for (size_t i = 0; i < words; i++) + { + int idx = 0; + for (int j = 0; j < 11; j++) + idx = (idx << 1) | (bits[i * 11 + j] ? 1 : 0); + if (i != 0) + mnemonicOut += " "; + mnemonicOut += BIP39_ENGLISH_WORDS[idx]; + } + return true; +} + +bool BIP39GenerateMnemonic(std::string& mnemonicOut, + std::string& errorOut, + unsigned int entropyBytes) +{ + mnemonicOut.clear(); + errorOut.clear(); + if (!IsSupportedEntropySize(entropyBytes)) + { + errorOut = "entropy must be 16, 20, 24, 28, or 32 bytes"; + return false; + } + + std::vector entropy(entropyBytes); + if (RAND_bytes(&entropy[0], entropy.size()) != 1) + { + errorOut = "RAND_bytes failed"; + return false; + } + bool ok = BIP39EntropyToMnemonic(entropy, mnemonicOut, errorOut); + OPENSSL_cleanse(&entropy[0], entropy.size()); + return ok; +} + +bool BIP39ValidateMnemonic(const std::string& mnemonic, + std::string& normalizedOut, + std::string& errorOut) +{ + normalizedOut = NormalizeMnemonic(mnemonic); + errorOut.clear(); + std::vector words = SplitWords(normalizedOut); + const size_t nWords = words.size(); + if (nWords != 12 && nWords != 15 && nWords != 18 && nWords != 21 && nWords != 24) + { + errorOut = "mnemonic must contain 12, 15, 18, 21, or 24 words"; + return false; + } + + const std::map& index = WordIndex(); + std::vector bits; + bits.reserve(nWords * 11); + for (const std::string& word : words) + { + std::map::const_iterator mi = index.find(word); + if (mi == index.end()) + { + errorOut = "mnemonic contains a word outside the BIP39 English list"; + return false; + } + int value = mi->second; + for (int i = 10; i >= 0; i--) + bits.push_back(((value >> i) & 1) != 0); + } + + const size_t totalBits = bits.size(); + const size_t entropyBits = totalBits * 32 / 33; + const size_t checksumBits = totalBits - entropyBits; + std::vector entropy(entropyBits / 8, 0); + for (size_t i = 0; i < entropyBits; i++) + if (bits[i]) + entropy[i / 8] |= (unsigned char)(1 << (7 - (i % 8))); + + unsigned char checksum[SHA256_DIGEST_LENGTH]; + SHA256(&entropy[0], entropy.size(), checksum); + for (size_t i = 0; i < checksumBits; i++) + { + if (bits[entropyBits + i] != GetBit(checksum, i)) + { + errorOut = "mnemonic checksum failed"; + return false; + } + } + return true; +} + +bool BIP39MnemonicToSeed(const std::string& mnemonic, + const std::string& passphrase, + std::vector& seedOut, + std::string& errorOut) +{ + seedOut.assign(64, 0); + std::string normalized; + if (!BIP39ValidateMnemonic(mnemonic, normalized, errorOut)) + { + seedOut.clear(); + return false; + } + + const std::string salt = "mnemonic" + passphrase; + if (!PKCS5_PBKDF2_HMAC(normalized.c_str(), normalized.size(), + (const unsigned char*)salt.data(), salt.size(), + 2048, EVP_sha512(), 64, &seedOut[0])) + { + seedOut.clear(); + errorOut = "PBKDF2-HMAC-SHA512 failed"; + return false; + } + return true; +} + +bool BIP32MasterFromSeed(const std::vector& seed, + BIP32PrivateNode& nodeOut, + std::string& errorOut) +{ + nodeOut.privateKey.clear(); + nodeOut.chainCode.clear(); + errorOut.clear(); + if (seed.size() < 16 || seed.size() > 64) + { + errorOut = "BIP32 seed must be between 128 and 512 bits"; + return false; + } + + unsigned char I[64]; + static const unsigned char key[] = "Bitcoin seed"; + if (!HmacSha512(key, sizeof(key) - 1, &seed[0], seed.size(), I)) + { + errorOut = "HMAC-SHA512 failed"; + return false; + } + + nodeOut.privateKey.assign(I, I + 32); + nodeOut.chainCode.assign(I + 32, I + 64); + OPENSSL_cleanse(I, sizeof(I)); + + if (!IsValidPrivateKey(nodeOut.privateKey)) + { + nodeOut.privateKey.clear(); + nodeOut.chainCode.clear(); + errorOut = "BIP32 master key is invalid"; + return false; + } + return true; +} + +bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent, + unsigned int childIndex, + BIP32PrivateNode& childOut, + std::string& errorOut) +{ + childOut.privateKey.clear(); + childOut.chainCode.clear(); + errorOut.clear(); + if (!IsValidPrivateKey(parent.privateKey) || parent.chainCode.size() != 32) + { + errorOut = "parent private key or chain code is invalid"; + return false; + } + if (childIndex >= 0x80000000U) + { + errorOut = "child index must be non-hardened; hardening is applied here"; + return false; + } + + const unsigned int hardened = childIndex | 0x80000000U; + unsigned char data[37]; + data[0] = 0x00; + memcpy(data + 1, &parent.privateKey[0], 32); + data[33] = (unsigned char)((hardened >> 24) & 0xff); + data[34] = (unsigned char)((hardened >> 16) & 0xff); + data[35] = (unsigned char)((hardened >> 8) & 0xff); + data[36] = (unsigned char)(hardened & 0xff); + + unsigned char I[64]; + if (!HmacSha512(&parent.chainCode[0], 32, data, sizeof(data), I)) + { + OPENSSL_cleanse(data, sizeof(data)); + errorOut = "HMAC-SHA512 failed"; + return false; + } + OPENSSL_cleanse(data, sizeof(data)); + + std::vector IL(I, I + 32); + childOut.chainCode.assign(I + 32, I + 64); + OPENSSL_cleanse(I, sizeof(I)); + + BIGNUM* bnIL = BN_bin2bn(&IL[0], 32, NULL); + BIGNUM* bnParent = BN_bin2bn(&parent.privateKey[0], 32, NULL); + BIGNUM* bnOrder = BN_new(); + BIGNUM* bnChild = BN_new(); + BN_CTX* ctx = BN_CTX_new(); + EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1); + + bool ok = false; + if (bnIL && bnParent && bnOrder && bnChild && ctx && group && + EC_GROUP_get_order(group, bnOrder, NULL) && + BN_cmp(bnIL, bnOrder) < 0 && + BN_mod_add(bnChild, bnIL, bnParent, bnOrder, ctx) && + !BN_is_zero(bnChild)) + { + childOut.privateKey.assign(32, 0); + int n = BN_num_bytes(bnChild); + BN_bn2bin(bnChild, &childOut.privateKey[32 - n]); + ok = true; + } + + BN_free(bnIL); + BN_free(bnParent); + BN_free(bnOrder); + BN_free(bnChild); + BN_CTX_free(ctx); + EC_GROUP_free(group); + OPENSSL_cleanse(&IL[0], IL.size()); + + if (!ok) + { + childOut.privateKey.clear(); + childOut.chainCode.clear(); + errorOut = "BIP32 child derivation produced an invalid key"; + return false; + } + return true; +} + +} // namespace bitflash diff --git a/src/bip32.h b/src/bip32.h new file mode 100644 index 0000000..a029444 --- /dev/null +++ b/src/bip32.h @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Bitflash developers +// Distributed under the MIT/X11 software license. +// +// BIP39/BIP32 primitives for deterministic wallet work. +// +// This module is deliberately independent from wallet.dat and GUI code. It is +// the auditable foundation: mnemonic generation/validation, seed derivation, +// and hardened private child derivation over secp256k1. + +#ifndef BITFLASH_BIP32_H +#define BITFLASH_BIP32_H + +#include +#include + +namespace bitflash +{ + +struct BIP32PrivateNode +{ + std::vector privateKey; // 32-byte scalar + std::vector chainCode; // 32 bytes +}; + +bool BIP39EntropyToMnemonic(const std::vector& entropy, + std::string& mnemonicOut, + std::string& errorOut); + +bool BIP39GenerateMnemonic(std::string& mnemonicOut, + std::string& errorOut, + unsigned int entropyBytes = 16); + +bool BIP39ValidateMnemonic(const std::string& mnemonic, + std::string& normalizedOut, + std::string& errorOut); + +bool BIP39MnemonicToSeed(const std::string& mnemonic, + const std::string& passphrase, + std::vector& seedOut, + std::string& errorOut); + +bool BIP32MasterFromSeed(const std::vector& seed, + BIP32PrivateNode& nodeOut, + std::string& errorOut); + +bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent, + unsigned int childIndex, + BIP32PrivateNode& childOut, + std::string& errorOut); + +} // namespace bitflash + +#endif diff --git a/src/bip39_english.h b/src/bip39_english.h new file mode 100644 index 0000000..04b4ed2 --- /dev/null +++ b/src/bip39_english.h @@ -0,0 +1,2062 @@ +// BIP39 English Wordlist + +#ifndef BITFLASH_BIP39_ENGLISH_H +#define BITFLASH_BIP39_ENGLISH_H + +namespace bitflash +{ + +static const char* BIP39_ENGLISH_WORDS[2048] = { + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo", +}; + +} // namespace bitflash + +#endif diff --git a/src/makefile.mingw b/src/makefile.mingw index ae533ef..e4dee59 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -92,4 +92,11 @@ bitflash.exe: $(IMGUI_OBJS) $(NODE_OBJS) $(GUI_OBJS) @echo "Built: bitflash.exe" clean: - rm -rf obj bitflash.exe + rm -rf obj bitflash.exe test_bip32.exe + +test_bip32.exe: test_bip32.cpp bip32.cpp bip32.h bip39_english.h + $(CXX) $(CXXFLAGS) -o $@ test_bip32.cpp bip32.cpp -lssl -lcrypto -lcrypt32 + @echo "Built: test_bip32.exe" + +tests: test_bip32.exe + ./test_bip32.exe diff --git a/src/test_bip32.cpp b/src/test_bip32.cpp new file mode 100644 index 0000000..610a651 --- /dev/null +++ b/src/test_bip32.cpp @@ -0,0 +1,130 @@ +// Standalone tests for BIP39/BIP32 primitives. +// +// Build: +// g++ -std=gnu++14 test_bip32.cpp bip32.cpp -lssl -lcrypto -o test_bip32 + +#include "bip32.h" + +#include +#include +#include +#include + +using namespace bitflash; + +static int g_fail = 0; + +#define CHECK(cond, name) do { \ + if (cond) { printf(" ok %s\n", name); } \ + else { printf(" FAIL %s\n", name); g_fail++; } \ +} while (0) + +static int HexDigit(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static std::vector FromHex(const std::string& s) +{ + std::vector out; + if (s.size() % 2 != 0) + return out; + out.reserve(s.size() / 2); + for (size_t i = 0; i < s.size(); i += 2) + { + int hi = HexDigit(s[i]); + int lo = HexDigit(s[i + 1]); + if (hi < 0 || lo < 0) + return std::vector(); + out.push_back((unsigned char)((hi << 4) | lo)); + } + return out; +} + +static std::string ToHex(const std::vector& v) +{ + static const char* hex = "0123456789abcdef"; + std::string out; + out.reserve(v.size() * 2); + for (unsigned char c : v) + { + out.push_back(hex[c >> 4]); + out.push_back(hex[c & 0x0f]); + } + return out; +} + +int main() +{ + std::string err; + + printf("bip39_entropy_to_mnemonic\n"); + { + std::vector entropy(16, 0); + std::string mnemonic; + CHECK(BIP39EntropyToMnemonic(entropy, mnemonic, err), "zero entropy encodes"); + CHECK(mnemonic == + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "matches BIP39 vector"); + } + + printf("bip39_validation\n"); + { + std::string normalized; + CHECK(BIP39ValidateMnemonic( + " Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ", + normalized, err), + "valid phrase normalizes"); + CHECK(normalized == + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "normalized text matches"); + CHECK(!BIP39ValidateMnemonic( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon", + normalized, err), + "bad checksum rejected"); + } + + printf("bip39_seed\n"); + { + std::vector seed; + const std::string mnemonic = + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + CHECK(BIP39MnemonicToSeed(mnemonic, "TREZOR", seed, err), "mnemonic derives seed"); + CHECK(ToHex(seed) == + "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e5349553" + "1f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + "matches BIP39 seed vector"); + } + + printf("bip32_master\n"); + { + std::vector seed = FromHex("000102030405060708090a0b0c0d0e0f"); + BIP32PrivateNode master; + CHECK(BIP32MasterFromSeed(seed, master, err), "master key derives"); + CHECK(ToHex(master.privateKey) == + "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", + "matches BIP32 master private key"); + CHECK(ToHex(master.chainCode) == + "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", + "matches BIP32 master chain code"); + + BIP32PrivateNode childA, childB; + CHECK(BIP32DeriveHardenedChild(master, 0, childA, err), "hardened child derives"); + CHECK(BIP32DeriveHardenedChild(master, 0, childB, err), "hardened child re-derives"); + CHECK(childA.privateKey == childB.privateKey && + childA.chainCode == childB.chainCode, + "same child path is deterministic"); + CHECK(childA.privateKey != master.privateKey && + childA.chainCode != master.chainCode, + "child differs from parent"); + CHECK(!BIP32DeriveHardenedChild(master, 0x80000000U, childA, err), + "pre-hardened index rejected"); + } + + printf("\n%s (%d failures)\n", + g_fail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", g_fail); + return g_fail == 0 ? 0 : 1; +} -- cgit v1.2.3