149 lines
3.7 KiB
Go
149 lines
3.7 KiB
Go
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)
|
|
}
|
|
}
|