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