Key Generator Interface Design
This document proposes an interface-based design for key generation that allows for easy extension to support new key types (Taproot, MuSig2, etc.) as they evolve.
Current State
Current Implementation
go
// internal/infrastructure/wallet/key/key.go
type Generator interface {
CreateKey(seed []byte, actType domainAccount.AccountType, idxFrom, count uint32) ([]domainKey.WalletKey, error)
}
// internal/infrastructure/wallet/key/hd_wallet.go
type HDKey struct {
purpose PurposeType // Fixed to BIP44/BIP49
coinType domainCoin.CoinType
coinTypeCode domainCoin.CoinTypeCode
conf *chaincfg.Params
}Current Limitations
- Fixed Purpose Type:
PurposeTypeBIP44is hardcoded innewKeyGenerator() - Single Implementation: Only
HDKeyimplementsGenerator - No Key Type Selection: Cannot choose key type (BIP44, BIP49, BIP86, etc.) dynamically
- Tight Coupling: Key generation logic is tightly coupled to specific BIP standards
- Difficult to Extend: Adding new key types (Taproot, MuSig2) requires modifying existing code
Proposed Design
1. Key Type Definition
Define key types as an enum/type:
go
// internal/domain/key/types.go
package key
// KeyType represents the type of key generation standard
type KeyType string
const (
// KeyTypeBIP44 represents BIP44 (Legacy P2PKH)
KeyTypeBIP44 KeyType = "bip44"
// KeyTypeBIP49 represents BIP49 (P2SH-SegWit)
KeyTypeBIP49 KeyType = "bip49"
// KeyTypeBIP84 represents BIP84 (Native SegWit P2WPKH)
KeyTypeBIP84 KeyType = "bip84"
// KeyTypeBIP86 represents BIP86 (Taproot)
KeyTypeBIP86 KeyType = "bip86"
// KeyTypeMuSig2 represents MuSig2 aggregated keys
KeyTypeMuSig2 KeyType = "musig2"
)
// String returns the string representation of the key type
func (k KeyType) String() string {
return string(k)
}
// Purpose returns the BIP purpose number for the key type
func (k KeyType) Purpose() uint32 {
switch k {
case KeyTypeBIP44:
return 44
case KeyTypeBIP49:
return 49
case KeyTypeBIP84:
return 84
case KeyTypeBIP86:
return 86
default:
return 44 // Default to BIP44
}
}
// Validate validates the key type
func (k KeyType) Validate() error {
switch k {
case KeyTypeBIP44, KeyTypeBIP49, KeyTypeBIP84, KeyTypeBIP86, KeyTypeMuSig2:
return nil
default:
return fmt.Errorf("invalid key type: %s", k)
}
}2. Enhanced Key Generator Interface
Extend the interface to support key type and additional metadata:
go
// internal/infrastructure/wallet/key/key.go
package key
import (
domainAccount "github.com/hiromaily/go-crypto-wallet/internal/domain/account"
domainKey "github.com/hiromaily/go-crypto-wallet/internal/domain/key"
)
// Generator is the interface for key generation
type Generator interface {
// KeyType returns the key type this generator supports
KeyType() domainKey.KeyType
// CreateKey creates keys based on the seed and account type
CreateKey(
seed []byte,
accountType domainAccount.AccountType,
idxFrom, count uint32,
) ([]domainKey.WalletKey, error)
// SupportsAddressType checks if this generator supports the given address type
SupportsAddressType(addrType address.AddrType) bool
// GetDerivationPath returns the derivation path for the given account and index
GetDerivationPath(accountType domainAccount.AccountType, index uint32) string
}
// GeneratorFactory creates a Generator based on key type
type GeneratorFactory interface {
CreateGenerator(keyType domainKey.KeyType, coinTypeCode domainCoin.CoinTypeCode, conf *chaincfg.Params) (Generator, error)
}3. Key Type-Specific Implementations
Create separate implementations for each key type:
go
// internal/infrastructure/wallet/key/bip44_generator.go
package key
type BIP44Generator struct {
coinType domainCoin.CoinType
coinTypeCode domainCoin.CoinTypeCode
conf *chaincfg.Params
}
func NewBIP44Generator(coinTypeCode domainCoin.CoinTypeCode, conf *chaincfg.Params) *BIP44Generator {
return &BIP44Generator{
coinType: domainCoin.GetCoinType(coinTypeCode, conf),
coinTypeCode: coinTypeCode,
conf: conf,
}
}
func (g *BIP44Generator) KeyType() domainKey.KeyType {
return domainKey.KeyTypeBIP44
}
func (g *BIP44Generator) CreateKey(
seed []byte,
accountType domainAccount.AccountType,
idxFrom, count uint32,
) ([]domainKey.WalletKey, error) {
// BIP44 implementation
// Derivation path: m/44'/coin_type'/account'/0/index
// ...
}
func (g *BIP44Generator) SupportsAddressType(addrType address.AddrType) bool {
return addrType == address.AddrTypeLegacy
}
func (g *BIP44Generator) GetDerivationPath(accountType domainAccount.AccountType, index uint32) string {
return fmt.Sprintf("m/44'/%d'/%d'/0/%d", g.coinType.Uint32(), accountType.Uint32(), index)
}go
// internal/infrastructure/wallet/key/bip86_generator.go
package key
type BIP86Generator struct {
coinType domainCoin.CoinType
coinTypeCode domainCoin.CoinTypeCode
conf *chaincfg.Params
}
func NewBIP86Generator(coinTypeCode domainCoin.CoinTypeCode, conf *chaincfg.Params) *BIP86Generator {
return &BIP86Generator{
coinType: domainCoin.GetCoinType(coinTypeCode, conf),
coinTypeCode: coinTypeCode,
conf: conf,
}
}
func (g *BIP86Generator) KeyType() domainKey.KeyType {
return domainKey.KeyTypeBIP86
}
func (g *BIP86Generator) CreateKey(
seed []byte,
accountType domainAccount.AccountType,
idxFrom, count uint32,
) ([]domainKey.WalletKey, error) {
// BIP86 implementation
// Derivation path: m/86'/coin_type'/account'/0/index
// Generate Taproot addresses (bc1p...)
// ...
}
func (g *BIP86Generator) SupportsAddressType(addrType address.AddrType) bool {
// Taproot addresses
return addrType == address.AddrTypeTaproot
}
func (g *BIP86Generator) GetDerivationPath(accountType domainAccount.AccountType, index uint32) string {
return fmt.Sprintf("m/86'/%d'/%d'/0/%d", g.coinType.Uint32(), accountType.Uint32(), index)
}4. Generator Factory
Create a factory to instantiate the appropriate generator:
go
// internal/infrastructure/wallet/key/factory.go
package key
import (
"fmt"
domainCoin "github.com/hiromaily/go-crypto-wallet/internal/domain/coin"
domainKey "github.com/hiromaily/go-crypto-wallet/internal/domain/key"
"github.com/btcsuite/btcd/chaincfg"
)
// Factory creates key generators based on key type
type Factory struct{}
func NewFactory() *Factory {
return &Factory{}
}
// CreateGenerator creates a generator for the specified key type
func (f *Factory) CreateGenerator(
keyType domainKey.KeyType,
coinTypeCode domainCoin.CoinTypeCode,
conf *chaincfg.Params,
) (Generator, error) {
if err := keyType.Validate(); err != nil {
return nil, fmt.Errorf("invalid key type: %w", err)
}
switch keyType {
case domainKey.KeyTypeBIP44:
return NewBIP44Generator(coinTypeCode, conf), nil
case domainKey.KeyTypeBIP49:
return NewBIP49Generator(coinTypeCode, conf), nil
case domainKey.KeyTypeBIP84:
return NewBIP84Generator(coinTypeCode, conf), nil
case domainKey.KeyTypeBIP86:
return NewBIP86Generator(coinTypeCode, conf), nil
case domainKey.KeyTypeMuSig2:
return NewMuSig2Generator(coinTypeCode, conf), nil
default:
return nil, fmt.Errorf("unsupported key type: %s", keyType)
}
}
// CreateGeneratorFromConfig creates a generator based on configuration
func (f *Factory) CreateGeneratorFromConfig(
keyType domainKey.KeyType,
coinTypeCode domainCoin.CoinTypeCode,
networkType string,
) (Generator, error) {
conf := getChainConfig(coinTypeCode, networkType)
return f.CreateGenerator(keyType, coinTypeCode, conf)
}5. Configuration Support
Add key type to configuration:
go
// pkg/config/wallet_struct.go
type WalletRoot struct {
// ... existing fields
KeyType domainKey.KeyType `toml:"key_type" mapstructure:"key_type" validate:"oneof=bip44 bip49 bip84 bip86 musig2"`
AddressType address.AddrType `toml:"address_type" mapstructure:"address_type" validate:"oneof=p2sh-segwit bech32 bch-cashaddr taproot"`
// ...
}6. Updated DI Container
Update the dependency injection container to use the factory:
go
// internal/di/container.go
func (c *container) newKeyGenerator() key.Generator {
var chainConf *chaincfg.Params
switch {
case domainCoin.IsBTCGroup(c.conf.CoinTypeCode):
chainConf = c.newBTC().GetChainConf()
case domainCoin.IsETHGroup(c.conf.CoinTypeCode):
chainConf = c.newETH().GetChainConf()
case c.conf.CoinTypeCode == domainCoin.XRP:
chainConf = c.newXRP().GetChainConf()
default:
panic(fmt.Sprintf("coinType[%s] is not implemented yet.", c.conf.CoinTypeCode))
}
// Use factory to create generator based on key type
factory := key.NewFactory()
keyType := c.getKeyType() // Get from config or default to BIP44
generator, err := factory.CreateGenerator(keyType, c.conf.CoinTypeCode, chainConf)
if err != nil {
panic(fmt.Sprintf("failed to create key generator: %v", err))
}
return generator
}
func (c *container) getKeyType() domainKey.KeyType {
// Get from config if available, otherwise default to BIP44
if c.conf.KeyType != "" {
return c.conf.KeyType
}
return domainKey.KeyTypeBIP44
}7. Backward Compatibility
Maintain backward compatibility by making HDKey implement the new interface:
go
// internal/infrastructure/wallet/key/hd_wallet.go
// HDKey implements Generator interface (backward compatibility)
func (k *HDKey) KeyType() domainKey.KeyType {
switch k.purpose {
case PurposeTypeBIP44:
return domainKey.KeyTypeBIP44
case PurposeTypeBIP49:
return domainKey.KeyTypeBIP49
default:
return domainKey.KeyTypeBIP44
}
}
func (k *HDKey) SupportsAddressType(addrType address.AddrType) bool {
switch k.purpose {
case PurposeTypeBIP44:
return addrType == address.AddrTypeLegacy
case PurposeTypeBIP49:
return addrType == address.AddrTypeP2shSegwit
default:
return false
}
}
func (k *HDKey) GetDerivationPath(accountType domainAccount.AccountType, index uint32) string {
return fmt.Sprintf("m/%d'/%d'/%d'/0/%d",
k.purpose.Uint32(),
k.coinType.Uint32(),
accountType.Uint32(),
index)
}Benefits
1. Extensibility
- Easy to add new key types (BIP86, MuSig2, etc.)
- No need to modify existing code
- Clear separation of concerns
2. Testability
- Each generator can be tested independently
- Mock generators for testing
- Clear interface contracts
3. Flexibility
- Choose key type at runtime based on configuration
- Support multiple key types simultaneously
- Easy to switch between key types
4. Maintainability
- Clear code organization
- Each key type has its own implementation
- Easier to understand and modify
5. Future-Proof
- Ready for new Bitcoin improvements
- Easy to add quantum-resistant key types
- Supports evolving standards
Migration Strategy
Phase 1: Interface Definition (1-2 weeks)
- Define
KeyTypein domain layer - Extend
Generatorinterface - Create factory interface
Phase 2: Implementation (2-4 weeks)
- Create
BIP44Generator(refactor fromHDKey) - Create
BIP49Generator - Create
BIP86Generator(new) - Update factory implementation
Phase 3: Integration (1-2 weeks)
- Update DI container
- Add configuration support
- Update use cases to use new interface
Phase 4: Testing & Documentation (1-2 weeks)
- Unit tests for each generator
- Integration tests
- Update documentation
Phase 5: Deprecation (Optional)
- Mark old
HDKeyas deprecated - Migrate existing code to use new generators
- Remove old implementation after migration
Example Usage
Configuration
toml
# config/wallet/btc/watch.yaml
[wallet]
coin_type = "btc"
key_type = "bip86" # Use Taproot
address_type = "taproot"Code Usage
go
// Create generator factory
factory := key.NewFactory()
// Create BIP86 generator for Taproot
generator, err := factory.CreateGenerator(
domainKey.KeyTypeBIP86,
domainCoin.BTC,
&chaincfg.MainNetParams,
)
if err != nil {
return err
}
// Generate keys
walletKeys, err := generator.CreateKey(seed, accountType, 0, 10)
if err != nil {
return err
}
// Get derivation path
path := generator.GetDerivationPath(accountType, 0)
// Output: m/86'/0'/0'/0/0Implementation Checklist
- [ ] Define
KeyTypein domain layer - [ ] Extend
Generatorinterface - [ ] Create
BIP44Generator - [ ] Create
BIP49Generator - [ ] Create
BIP84Generator - [ ] Create
BIP86Generator(Taproot) - [ ] Create
MuSig2Generator(future) - [ ] Create
Factory - [ ] Update DI container
- [ ] Add configuration support
- [ ] Update use cases
- [ ] Write unit tests
- [ ] Write integration tests
- [ ] Update documentation
- [ ] Migration guide
Summary
This design provides:
- Interface-based architecture for key generation
- Type-specific implementations for each key standard
- Factory pattern for creating generators
- Configuration-driven key type selection
- Easy extensibility for future key types
- Backward compatibility with existing code
This approach allows the system to evolve with Bitcoin standards while maintaining clean, testable, and maintainable code.