package bitcoin import ( "fmt" "btcphrasechecker/types" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" ) // DerivationPath represents a BIP derivation path configuration type DerivationPath struct { Name string Path string Purpose uint32 Desc string } // Standard Bitcoin derivation paths var StandardPaths = []DerivationPath{ { Name: "BIP44 (Legacy)", Path: "m/44'/0'/0'/0", Purpose: 44, Desc: "P2PKH addresses (1...)", }, { Name: "BIP49 (SegWit)", Path: "m/49'/0'/0'/0", Purpose: 49, Desc: "P2SH-wrapped SegWit (3...)", }, { Name: "BIP84 (Native SegWit)", Path: "m/84'/0'/0'/0", Purpose: 84, Desc: "Bech32 addresses (bc1...)", }, } // DeriveAddresses derives Bitcoin addresses from a seed for all standard paths func DeriveAddresses(seed []byte, addressCount int) ([]types.AddressInfo, error) { masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) if err != nil { return nil, fmt.Errorf("failed to create master key: %w", err) } var allAddresses []types.AddressInfo for _, pathInfo := range StandardPaths { addresses, err := derivePathAddresses(masterKey, pathInfo, addressCount) if err != nil { return nil, err } allAddresses = append(allAddresses, addresses...) } return allAddresses, nil } // derivePathAddresses derives addresses for a specific derivation path func derivePathAddresses(masterKey *hdkeychain.ExtendedKey, pathInfo DerivationPath, count int) ([]types.AddressInfo, error) { var addresses []types.AddressInfo // Derive base path: m/purpose'/coin_type'/account'/change // For Bitcoin: coin_type = 0, account = 0, change = 0 (external addresses) key := masterKey key, _ = key.Derive(hdkeychain.HardenedKeyStart + pathInfo.Purpose) key, _ = key.Derive(hdkeychain.HardenedKeyStart + 0) // coin_type = 0 for Bitcoin key, _ = key.Derive(hdkeychain.HardenedKeyStart + 0) // account = 0 key, _ = key.Derive(0) // change = 0 (external) // Derive individual addresses for i := uint32(0); i < uint32(count); i++ { childKey, err := key.Derive(i) if err != nil { continue } pubKey, err := childKey.ECPubKey() if err != nil { continue } pubKeyBytes := pubKey.SerializeCompressed() address, err := deriveAddress(pubKeyBytes, pathInfo.Purpose) if err != nil { continue } addresses = append(addresses, types.AddressInfo{ Address: address, Path: fmt.Sprintf("%s/%d", pathInfo.Path, i), Chain: types.ChainBitcoin, }) } return addresses, nil } // deriveAddress creates an address from a public key based on the purpose func deriveAddress(pubKeyBytes []byte, purpose uint32) (string, error) { switch purpose { case 44: // Legacy P2PKH addr, err := btcutil.NewAddressPubKey(pubKeyBytes, &chaincfg.MainNetParams) if err != nil { return "", err } return addr.EncodeAddress(), nil case 49: // P2SH-wrapped SegWit (BIP49) // Create witness program for P2WPKH pubKeyHash := btcutil.Hash160(pubKeyBytes) witnessProgram, err := txscript.NewScriptBuilder(). AddOp(txscript.OP_0). AddData(pubKeyHash). Script() if err != nil { return "", err } // Wrap witness program in P2SH scriptAddr, err := btcutil.NewAddressScriptHash(witnessProgram, &chaincfg.MainNetParams) if err != nil { return "", err } return scriptAddr.EncodeAddress(), nil case 84: // Native SegWit (bech32) addr, err := btcutil.NewAddressWitnessPubKeyHash( btcutil.Hash160(pubKeyBytes), &chaincfg.MainNetParams, ) if err != nil { return "", err } return addr.EncodeAddress(), nil default: return "", fmt.Errorf("unsupported purpose: %d", purpose) } }