using NBitcoin; namespace PalladiumWallet.Core.Crypto; /// /// Supported BIP39 wordlist languages (blueprint §4.1). The lists are embedded /// in NBitcoin: no network or filesystem access required. /// public enum MnemonicLanguage { English, Spanish, Japanese, PortugueseBrazil, ChineseSimplified, ChineseTraditional, French, Czech, } /// Supported word counts for mnemonic creation (blueprint §4.1: 12 or 24). public enum MnemonicLength { Twelve = 12, TwentyFour = 24, } /// /// BIP39 facade over NBitcoin.Mnemonic (blueprint §4.1). NBitcoin already handles /// entropy→words, checksum, NFKD normalisation, and PBKDF2-HMAC-SHA512 with 2048 /// rounds using salt "mnemonic"+passphrase. This class narrows the API to the /// blueprint use-cases and centralises the language map. When the native versioned /// seed arrives (§4.1 point 1), multi-scheme recognition will be a chain of /// TryParse calls per scheme. /// public static class Bip39 { /// Generates a new mnemonic using entropy from the system CSPRNG. public static Mnemonic Generate(MnemonicLength length, MnemonicLanguage language = MnemonicLanguage.English) { var wordCount = length == MnemonicLength.Twelve ? WordCount.Twelve : WordCount.TwentyFour; return new Mnemonic(ToWordlist(language), wordCount); } /// /// Recognises and validates a BIP39 mnemonic: word count, wordlist membership, /// and checksum. If is specified it is tried first; /// otherwise the language is auto-detected (words shared between lists can make /// autodetect ambiguous — the user can override it on import). /// public static bool TryParse(string text, out Mnemonic? mnemonic, MnemonicLanguage? language = null) { mnemonic = null; if (string.IsNullOrWhiteSpace(text)) return false; // Outer trim only: NBitcoin handles NFKD normalisation and Japanese // ideographic spaces — the text must not be altered further (§4.1). text = text.Trim(); IEnumerable candidates = language is not null ? [ToWordlist(language.Value)] : [Wordlist.AutoDetect(text)]; foreach (var wordlist in candidates) { try { var parsed = new Mnemonic(text, wordlist); // The constructor does NOT verify the checksum — explicit check is mandatory. if (parsed.IsValidChecksum && parsed.Words.Length is 12 or 15 or 18 or 21 or 24) { mnemonic = parsed; return true; } } catch (FormatException) { // Words outside the wordlist or invalid count — try the next candidate. } } return false; } /// /// Mnemonic → 64-byte seed (PBKDF2-HMAC-SHA512, 2048 rounds, salt /// "mnemonic"+passphrase). The passphrase completely changes the derived /// wallet — mandatory UI warnings required (§4.1). /// public static byte[] ToSeed(Mnemonic mnemonic, string? passphrase = null) => mnemonic.DeriveSeed(passphrase); internal static Wordlist ToWordlist(MnemonicLanguage language) => language switch { MnemonicLanguage.English => Wordlist.English, MnemonicLanguage.Spanish => Wordlist.Spanish, MnemonicLanguage.Japanese => Wordlist.Japanese, MnemonicLanguage.PortugueseBrazil => Wordlist.PortugueseBrazil, MnemonicLanguage.ChineseSimplified => Wordlist.ChineseSimplified, MnemonicLanguage.ChineseTraditional => Wordlist.ChineseTraditional, MnemonicLanguage.French => Wordlist.French, MnemonicLanguage.Czech => Wordlist.Czech, _ => throw new ArgumentOutOfRangeException(nameof(language)), }; }