keys.go raw

   1  package keys
   2  
   3  import (
   4  	"crypto/hmac"
   5  	"crypto/sha512"
   6  	"encoding/hex"
   7  	"errors"
   8  
   9  	"github.com/btcsuite/btcd/btcec/v2"
  10  	"github.com/btcsuite/btcd/btcutil/hdkeychain"
  11  	"github.com/btcsuite/btcd/chaincfg"
  12  
  13  	"github.com/getAlby/go-nostr"
  14  	"github.com/getAlby/hub/config"
  15  	"github.com/getAlby/hub/logger"
  16  	"github.com/tyler-smith/go-bip32"
  17  	"github.com/tyler-smith/go-bip39"
  18  )
  19  
  20  type Keys interface {
  21  	Init(cfg config.Config, encryptionKey string) error
  22  	// Wallet Service Nostr pubkey (DEPRECATED)
  23  	GetNostrPublicKey() string
  24  	// Wallet Service Nostr secret key (DEPRECATED)
  25  	GetNostrSecretKey() string
  26  	// Swap rescue key derived from master key using BIP-85
  27  	GetSwapMnemonic() string
  28  	// Derives a BIP32 child key from appKey derived child dedicated for app wallet keys
  29  	GetAppWalletKey(childIndex uint) (string, error)
  30  	// Derives a child BIP-32 key from the app key (derived from the mnemonic)
  31  	DeriveKey(path []uint32) (*bip32.Key, error)
  32  	// Derives a BIP32 child key from appKey derived child dedicated for swaps
  33  	GetSwapKey(childIndex uint) (*btcec.PrivateKey, error)
  34  }
  35  
  36  type keys struct {
  37  	nostrSecretKey string
  38  	nostrPublicKey string
  39  	appKey         *bip32.Key
  40  	swapKey        *hdkeychain.ExtendedKey
  41  	swapMnemonic   string
  42  }
  43  
  44  func NewKeys() *keys {
  45  	return &keys{}
  46  }
  47  
  48  func (keys *keys) Init(cfg config.Config, encryptionKey string) error {
  49  	nostrSecretKey, err := cfg.Get("NostrSecretKey", encryptionKey)
  50  	if err != nil {
  51  		logger.Logger.WithError(err).Error("Failed to decrypt nostr secret key")
  52  		return err
  53  	}
  54  
  55  	if nostrSecretKey == "" {
  56  		nostrSecretKey = nostr.GeneratePrivateKey()
  57  		err = cfg.SetUpdate("NostrSecretKey", nostrSecretKey, encryptionKey)
  58  		if err != nil {
  59  			logger.Logger.WithError(err).Error("Failed to save generated nostr secret key")
  60  			return err
  61  		}
  62  	}
  63  	nostrPublicKey, err := nostr.GetPublicKey(nostrSecretKey)
  64  	if err != nil {
  65  		logger.Logger.WithError(err).Error("Error converting nostr privkey to pubkey")
  66  		return err
  67  	}
  68  	keys.nostrSecretKey = nostrSecretKey
  69  	keys.nostrPublicKey = nostrPublicKey
  70  
  71  	mnemonic, err := cfg.Get("Mnemonic", encryptionKey)
  72  	if err != nil {
  73  		logger.Logger.WithError(err).Error("Failed to decrypt mnemonic")
  74  		return err
  75  	}
  76  
  77  	if mnemonic == "" {
  78  		// for backends that don't use a mnemonic, create one anyway for deriving keys
  79  		entropy, err := bip39.NewEntropy(128)
  80  		if err != nil {
  81  			logger.Logger.WithError(err).Error("Failed to generate entropy for mnemonic")
  82  			return err
  83  		}
  84  		mnemonic, err = bip39.NewMnemonic(entropy)
  85  		if err != nil {
  86  			logger.Logger.WithError(err).Error("Failed to generate mnemonic")
  87  			return err
  88  		}
  89  		err = cfg.SetUpdate("Mnemonic", mnemonic, encryptionKey)
  90  		if err != nil {
  91  			logger.Logger.WithError(err).Error("Failed to save mnemonic")
  92  			return err
  93  		}
  94  	}
  95  
  96  	masterKey, err := bip32.NewMasterKey(bip39.NewSeed(mnemonic, ""))
  97  	if err != nil {
  98  		logger.Logger.WithError(err).Error("Failed to create seed from mnemonic")
  99  		return err
 100  	}
 101  
 102  	albyHubIndex := uint32(bip32.FirstHardenedChild + 128029 /* 🐝 */)
 103  	appKey, err := masterKey.NewChildKey(albyHubIndex)
 104  	if err != nil {
 105  		logger.Logger.WithError(err).Error("Failed to derive app key")
 106  		return err
 107  	}
 108  	keys.appKey = appKey
 109  
 110  	swapMnemonic, err := keys.GenerateSwapMnemonic(masterKey)
 111  	if err != nil {
 112  		logger.Logger.WithError(err).Error("Failed to generate swap mnemonic")
 113  		return err
 114  	}
 115  	keys.swapMnemonic = swapMnemonic
 116  
 117  	netParams := &chaincfg.MainNetParams
 118  	network := cfg.GetNetwork()
 119  	if network == "testnet" {
 120  		netParams = &chaincfg.TestNet3Params
 121  	}
 122  
 123  	swapKey, err := hdkeychain.NewMaster(bip39.NewSeed(swapMnemonic, ""), netParams)
 124  	if err != nil {
 125  		logger.Logger.WithError(err).Error("Failed to create seed from swap mnemonic")
 126  		return err
 127  	}
 128  	keys.swapKey = swapKey
 129  
 130  	return nil
 131  }
 132  
 133  func (keys *keys) GetSwapMnemonic() string {
 134  	return keys.swapMnemonic
 135  }
 136  
 137  func (keys *keys) GetNostrPublicKey() string {
 138  	return keys.nostrPublicKey
 139  }
 140  
 141  func (keys *keys) GetNostrSecretKey() string {
 142  	return keys.nostrSecretKey
 143  }
 144  
 145  func (keys *keys) GetAppWalletKey(appID uint) (string, error) {
 146  	path := []uint32{bip32.FirstHardenedChild + 1, bip32.FirstHardenedChild + uint32(appID)}
 147  	key, err := keys.DeriveKey(path)
 148  	if err != nil {
 149  		return "", err
 150  	}
 151  	childPrivKey, _ := btcec.PrivKeyFromBytes(key.Key)
 152  	return hex.EncodeToString(childPrivKey.Serialize()), nil
 153  }
 154  
 155  func (keys *keys) DeriveKey(path []uint32) (*bip32.Key, error) {
 156  	if len(path) == 0 {
 157  		return nil, errors.New("path must have at least one element")
 158  	}
 159  	if keys.appKey == nil {
 160  		return nil, errors.New("app key not set")
 161  	}
 162  	key := keys.appKey
 163  	for _, index := range path {
 164  		var err error
 165  		key, err = key.NewChildKey(index)
 166  		if err != nil {
 167  			return nil, err
 168  		}
 169  	}
 170  
 171  	return key, nil
 172  }
 173  
 174  func (keys *keys) GetSwapKey(swapID uint) (*btcec.PrivateKey, error) {
 175  	path := []uint32{44, 0, 0, 0, uint32(swapID)}
 176  
 177  	key := keys.swapKey
 178  	for _, index := range path {
 179  		var err error
 180  		key, err = key.Derive(index)
 181  		if err != nil {
 182  			return nil, err
 183  		}
 184  	}
 185  
 186  	return key.ECPrivKey()
 187  }
 188  
 189  // Taken from https://github.com/e4coder/bip85/blob/main/bip85.go
 190  func (keys *keys) GenerateSwapMnemonic(key *bip32.Key) (string, error) {
 191  	swapIndex := uint32(bip32.FirstHardenedChild + 128260 /* 🔄 */)
 192  	path := []uint32{bip32.FirstHardenedChild + 83696968, bip32.FirstHardenedChild + 39, bip32.FirstHardenedChild + 0, bip32.FirstHardenedChild + 12, swapIndex}
 193  
 194  	for _, index := range path {
 195  		var err error
 196  		key, err = key.NewChildKey(index)
 197  		if err != nil {
 198  			return "", err
 199  		}
 200  	}
 201  
 202  	hash := hmac.New(sha512.New, []byte("bip-entropy-from-k"))
 203  	_, err := hash.Write(key.Key)
 204  	if err != nil {
 205  		return "", err
 206  	}
 207  	entropy := hash.Sum(nil)
 208  	entropyBIP39 := entropy[:16]
 209  	mnemonic, err := bip39.NewMnemonic(entropyBIP39)
 210  	if err != nil {
 211  		return "", err
 212  	}
 213  	return mnemonic, nil
 214  }
 215