gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/wallet/unseeded.go (about) 1 package wallet 2 3 import ( 4 "errors" 5 6 "gitlab.com/NebulousLabs/fastrand" 7 "gitlab.com/SiaPrime/SiaPrime/crypto" 8 "gitlab.com/SiaPrime/SiaPrime/encoding" 9 "gitlab.com/SiaPrime/SiaPrime/modules" 10 "gitlab.com/SiaPrime/SiaPrime/types" 11 ) 12 13 const ( 14 // SiagFileExtension is the file extension to be used for siag files 15 SiagFileExtension = ".siakey" 16 17 // SiagFileHeader is the header for all siag files. Do not change. Because siag was created 18 // early in development, compatibility with siag requires manually handling 19 // the headers and version instead of using the persist package. 20 SiagFileHeader = "siag" 21 22 // SiagFileVersion is the version number to be used for siag files 23 SiagFileVersion = "1.0" 24 ) 25 26 var ( 27 errAllDuplicates = errors.New("old wallet has no new seeds") 28 errDuplicateSpendableKey = errors.New("key has already been loaded into the wallet") 29 30 // ErrInconsistentKeys is the error when keyfiles provided are for different addresses 31 ErrInconsistentKeys = errors.New("keyfiles provided that are for different addresses") 32 // ErrInsufficientKeys is the error when there's not enough keys provided to spend the siafunds 33 ErrInsufficientKeys = errors.New("not enough keys provided to spend the siafunds") 34 // ErrNoKeyfile is the error when no keyfile has been presented 35 ErrNoKeyfile = errors.New("no keyfile has been presented") 36 // ErrUnknownHeader is the error when file contains wrong header 37 ErrUnknownHeader = errors.New("file contains the wrong header") 38 // ErrUnknownVersion is the error when the file has an unknown version number 39 ErrUnknownVersion = errors.New("file has an unknown version number") 40 ) 41 42 // A siagKeyPair is the struct representation of the bytes that get saved to 43 // disk by siag when a new keyfile is created. 44 type siagKeyPair struct { 45 Header string 46 Version string 47 Index int // should be uint64 - too late now 48 SecretKey crypto.SecretKey 49 UnlockConditions types.UnlockConditions 50 } 51 52 // savedKey033x is the persist structure that was used to save and load private 53 // keys in versions v0.3.3.x for siad. 54 type savedKey033x struct { 55 SecretKey crypto.SecretKey 56 UnlockConditions types.UnlockConditions 57 Visible bool 58 } 59 60 // decryptSpendableKeyFile decrypts a spendableKeyFile, returning a 61 // spendableKey. 62 func decryptSpendableKeyFile(masterKey crypto.CipherKey, uk spendableKeyFile) (sk spendableKey, err error) { 63 // Verify that the decryption key is correct. 64 decryptionKey := uidEncryptionKey(masterKey, uk.UID) 65 err = verifyEncryption(decryptionKey, uk.EncryptionVerification) 66 if err != nil { 67 return 68 } 69 70 // Decrypt the spendable key and add it to the wallet. 71 encodedKey, err := decryptionKey.DecryptBytes(uk.SpendableKey) 72 if err != nil { 73 return 74 } 75 err = encoding.Unmarshal(encodedKey, &sk) 76 return 77 } 78 79 // integrateSpendableKey loads a spendableKey into the wallet. 80 func (w *Wallet) integrateSpendableKey(masterKey crypto.CipherKey, sk spendableKey) { 81 w.keys[sk.UnlockConditions.UnlockHash()] = sk 82 } 83 84 // loadSpendableKey loads a spendable key into the wallet database. 85 func (w *Wallet) loadSpendableKey(masterKey crypto.CipherKey, sk spendableKey) error { 86 // Duplication is detected by looking at the set of unlock conditions. If 87 // the wallet is locked, correct deduplication is uncertain. 88 if !w.unlocked { 89 return modules.ErrLockedWallet 90 } 91 92 // Check for duplicates. 93 _, exists := w.keys[sk.UnlockConditions.UnlockHash()] 94 if exists { 95 return errDuplicateSpendableKey 96 } 97 98 // TODO: Check that the key is actually spendable. 99 100 // Create a UID and encryption verification. 101 var skf spendableKeyFile 102 fastrand.Read(skf.UID[:]) 103 encryptionKey := uidEncryptionKey(masterKey, skf.UID) 104 skf.EncryptionVerification = encryptionKey.EncryptBytes(verificationPlaintext) 105 106 // Encrypt and save the key. 107 skf.SpendableKey = encryptionKey.EncryptBytes(encoding.Marshal(sk)) 108 109 err := checkMasterKey(w.dbTx, masterKey) 110 var current []spendableKeyFile 111 err = encoding.Unmarshal(w.dbTx.Bucket(bucketWallet).Get(keySpendableKeyFiles), ¤t) 112 if err != nil { 113 return err 114 } 115 return w.dbTx.Bucket(bucketWallet).Put(keySpendableKeyFiles, encoding.Marshal(append(current, skf))) 116 117 // w.keys[sk.UnlockConditions.UnlockHash()] = sk -> aids with duplicate 118 // detection, but causes db inconsistency. Rescanning is probably the 119 // solution. 120 } 121 122 // loadSiagKeys loads a set of siag keyfiles into the wallet, so that the 123 // wallet may spend the siafunds. 124 func (w *Wallet) loadSiagKeys(masterKey crypto.CipherKey, keyfiles []string) error { 125 // Load the keyfiles from disk. 126 if len(keyfiles) < 1 { 127 return ErrNoKeyfile 128 } 129 skps := make([]siagKeyPair, len(keyfiles)) 130 for i, keyfile := range keyfiles { 131 err := encoding.ReadFile(keyfile, &skps[i]) 132 if err != nil { 133 return err 134 } 135 136 if skps[i].Header != SiagFileHeader { 137 return ErrUnknownHeader 138 } 139 if skps[i].Version != SiagFileVersion { 140 return ErrUnknownVersion 141 } 142 } 143 144 // Check that all of the loaded files have the same address, and that there 145 // are enough to create the transaction. 146 baseUnlockHash := skps[0].UnlockConditions.UnlockHash() 147 for _, skp := range skps { 148 if skp.UnlockConditions.UnlockHash() != baseUnlockHash { 149 return ErrInconsistentKeys 150 } 151 } 152 if uint64(len(skps)) < skps[0].UnlockConditions.SignaturesRequired { 153 return ErrInsufficientKeys 154 } 155 // Drop all unneeded keys. 156 skps = skps[0:skps[0].UnlockConditions.SignaturesRequired] 157 158 // Merge the keys into a single spendableKey and save it to the wallet. 159 var sk spendableKey 160 sk.UnlockConditions = skps[0].UnlockConditions 161 for _, skp := range skps { 162 sk.SecretKeys = append(sk.SecretKeys, skp.SecretKey) 163 } 164 err := w.loadSpendableKey(masterKey, sk) 165 if err != nil { 166 return err 167 } 168 w.integrateSpendableKey(masterKey, sk) 169 return nil 170 } 171 172 // LoadSiagKeys loads a set of siag-generated keys into the wallet. 173 func (w *Wallet) LoadSiagKeys(masterKey crypto.CipherKey, keyfiles []string) error { 174 if err := w.tg.Add(); err != nil { 175 return err 176 } 177 defer w.tg.Done() 178 179 // load the keys and reset the consensus change ID and height in preparation for rescan 180 err := func() error { 181 w.mu.Lock() 182 defer w.mu.Unlock() 183 err := w.loadSiagKeys(masterKey, keyfiles) 184 if err != nil { 185 return err 186 } 187 188 if err = w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil { 189 return err 190 } 191 if _, err = w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil { 192 return err 193 } 194 w.unconfirmedProcessedTransactions = nil 195 err = dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning) 196 if err != nil { 197 return err 198 } 199 return dbPutConsensusHeight(w.dbTx, 0) 200 }() 201 if err != nil { 202 return err 203 } 204 205 // rescan the blockchain 206 w.cs.Unsubscribe(w) 207 w.tpool.Unsubscribe(w) 208 209 done := make(chan struct{}) 210 go w.rescanMessage(done) 211 defer close(done) 212 213 err = w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan()) 214 if err != nil { 215 return err 216 } 217 w.tpool.TransactionPoolSubscribe(w) 218 return nil 219 } 220 221 // Load033xWallet loads a v0.3.3.x wallet as an unseeded key, such that the 222 // funds become spendable to the current wallet. 223 func (w *Wallet) Load033xWallet(masterKey crypto.CipherKey, filepath033x string) error { 224 if err := w.tg.Add(); err != nil { 225 return err 226 } 227 defer w.tg.Done() 228 229 // load the keys and reset the consensus change ID and height in preparation for rescan 230 err := func() error { 231 w.mu.Lock() 232 defer w.mu.Unlock() 233 234 var savedKeys []savedKey033x 235 err := encoding.ReadFile(filepath033x, &savedKeys) 236 if err != nil { 237 return err 238 } 239 var seedsLoaded int 240 for _, savedKey := range savedKeys { 241 spendKey := spendableKey{ 242 UnlockConditions: savedKey.UnlockConditions, 243 SecretKeys: []crypto.SecretKey{savedKey.SecretKey}, 244 } 245 err = w.loadSpendableKey(masterKey, spendKey) 246 if err != nil && err != errDuplicateSpendableKey { 247 return err 248 } 249 if err == nil { 250 seedsLoaded++ 251 } 252 w.integrateSpendableKey(masterKey, spendKey) 253 } 254 if seedsLoaded == 0 { 255 return errAllDuplicates 256 } 257 258 if err = w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil { 259 return err 260 } 261 if _, err = w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil { 262 return err 263 } 264 w.unconfirmedProcessedTransactions = nil 265 err = dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning) 266 if err != nil { 267 return err 268 } 269 return dbPutConsensusHeight(w.dbTx, 0) 270 }() 271 if err != nil { 272 return err 273 } 274 275 // rescan the blockchain 276 w.cs.Unsubscribe(w) 277 w.tpool.Unsubscribe(w) 278 279 done := make(chan struct{}) 280 go w.rescanMessage(done) 281 defer close(done) 282 283 err = w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan()) 284 if err != nil { 285 return err 286 } 287 w.tpool.TransactionPoolSubscribe(w) 288 289 return nil 290 }