using NBitcoin; using PalladiumWallet.Core.Chain; namespace PalladiumWallet.Core.Crypto; /// /// BIP44/49/84 derivation path construction (blueprint §4.2) and mapping of /// ScriptKind → purpose / ScriptPubKeyType. Purposes are BIP protocol constants /// (chain-independent); coin_type always comes from the ChainProfile. /// public static class DerivationPaths { /// BIP44 purpose (P2PKH legacy). public const int PurposeLegacy = 44; /// BIP49 purpose (P2SH-P2WPKH). public const int PurposeWrappedSegwit = 49; /// BIP84 purpose (native P2WPKH). public const int PurposeNativeSegwit = 84; /// BIP48 purpose (multisig — next phase, §16 step 8). public const int PurposeMultisig = 48; /// BIP86 purpose (P2TR key-path, Taproot). public const int PurposeTaproot = 86; public static int PurposeFor(ScriptKind kind) => kind switch { ScriptKind.Legacy => PurposeLegacy, ScriptKind.WrappedSegwit => PurposeWrappedSegwit, ScriptKind.NativeSegwit => PurposeNativeSegwit, ScriptKind.WrappedSegwitMultisig or ScriptKind.NativeSegwitMultisig => PurposeMultisig, ScriptKind.Taproot => PurposeTaproot, _ => throw new ArgumentOutOfRangeException(nameof(kind)), }; /// /// NBitcoin scriptPubKey type for single-pubkey derivation. /// Multisig types are derived from a redeem script, not a pubkey — they will /// arrive with M-of-N wallet support (§4.5). /// public static ScriptPubKeyType ScriptPubKeyTypeFor(ScriptKind kind) => kind switch { ScriptKind.Legacy => ScriptPubKeyType.Legacy, ScriptKind.WrappedSegwit => ScriptPubKeyType.SegwitP2SH, ScriptKind.NativeSegwit => ScriptPubKeyType.Segwit, ScriptKind.Taproot => ScriptPubKeyType.TaprootBIP86, ScriptKind.WrappedSegwitMultisig or ScriptKind.NativeSegwitMultisig => throw new NotSupportedException( "Multisig types are derived from a redeem script: support planned with M-of-N wallets (§4.5)."), _ => throw new ArgumentOutOfRangeException(nameof(kind)), }; /// /// Account path relative to the root: purpose'/coin'/account' (§4.2). /// coin_type is taken from the profile (746 mainnet, 1 testnet). /// public static KeyPath AccountPath(ScriptKind kind, ChainProfile profile, int account = 0) { if (account < 0) throw new ArgumentOutOfRangeException(nameof(account)); return new KeyPath( $"{PurposeFor(kind)}'/{profile.Bip44CoinType}'/{account}'"); } /// Non-hardened change/index sub-path under the account (change=0 receiving, change=1 change). public static KeyPath AddressSubPath(bool isChange, int index) { if (index < 0) throw new ArgumentOutOfRangeException(nameof(index)); return new KeyPath($"{(isChange ? 1 : 0)}/{index}"); } /// /// Parsing of custom derivation paths on import (Sparrow-like, §4.2): /// accepts the optional "m/" prefix and hardened markers ' or h. /// public static bool TryParse(string path, out KeyPath? keyPath) { keyPath = null; if (string.IsNullOrWhiteSpace(path)) return false; try { keyPath = KeyPath.Parse(path.Trim()); return true; } catch (FormatException) { return false; } } }