github.com/decred/dcrlnd@v0.7.6/channeldb/keychain.go (about) 1 package channeldb 2 3 import ( 4 "bytes" 5 "errors" 6 7 "github.com/decred/dcrlnd/kvdb" 8 ) 9 10 const ( 11 // lastUsableKeyFamily is the last key family index that can be stored 12 // by the database. This value matches the last account number that can 13 // be used to create an account in HD wallets, assuming accounts are 14 // created as hardened branches. 15 lastUsableKeyFamily = 0x7fffffff 16 17 // lastUsableFamilyIndex is the last index that can be returned by a 18 // given key family. 19 lastUsableKeyFamilyIndex = 0x7fffffff 20 ) 21 22 var ( 23 // errInvalidKeyFamily is returned when an invalid key family is 24 // requested. 25 errInvalidKeyFamily = errors.New("invalid key family") 26 27 // errKeyFamilyExchausted is returned when a given keyfamily has 28 // generated enough indexes that no more can be generated. 29 errKeyFamilyExhausted = errors.New("keyfamily indexes exhausted") 30 31 // errDifferentAccountID is returned when the account ID provided to 32 // CompareAndStoreAccountID is not the same as the one stored in the 33 // database. 34 errDifferentAccountID = errors.New("account ID is different than stored in the database") 35 36 // keychainBucket is the root bucket used to store keychain/keyring 37 // data. 38 keychainBucket = []byte("keychain") 39 40 // keyFamilyIndexesBucket is the bucket used to store the current index 41 // of each requested key famiy. 42 // 43 // Keys are byte-ordered uint32 slices, and values are byte-ordered 44 // uint32 values that represent the last returned index for a family. 45 keyFamilyIndexesBucket = []byte("kfidxs") 46 47 // keyAccountIDBucket is the bucket used to store the identifier of the 48 // account previously used with this keychain. By convention, this is 49 // the first pubkey of the first keyfamily of the keychain/account. 50 keyAccountIDBucket = []byte("acctid") 51 ) 52 53 // NextFamilyIndex returns the next index for a given family of keys from the 54 // database-backed keyring. 55 // 56 // A _KeyFamily_ is an uint32 that maps to the key families of the keychain 57 // package, while the returned index can be considered the index of a 58 // (possibly) unused key. 59 // 60 // Repeated calls to NextKeyFamilyIndex will return different values. This 61 // function errors if the requested family would create an invalid HD extended 62 // key or if it the key family has been exhausted and no more keys can be 63 // generated for it. 64 func (d *DB) NextKeyFamilyIndex(keyFamily uint32) (uint32, error) { 65 var index uint32 66 67 // Key families higher than this limit would cause a numeric overflow 68 // due to accounts using hardened HD branches. 69 if keyFamily > lastUsableKeyFamily { 70 return 0, errInvalidKeyFamily 71 } 72 73 err := kvdb.Update(d, func(tx kvdb.RwTx) error { 74 keychain, err := tx.CreateTopLevelBucket(keychainBucket) 75 if err != nil { 76 return err 77 } 78 79 keyFamilies, err := keychain.CreateBucketIfNotExists( 80 keyFamilyIndexesBucket, 81 ) 82 if err != nil { 83 return err 84 } 85 86 // Attempt to read the existing value for the given family. 87 var k [4]byte 88 var v [4]byte 89 byteOrder.PutUint32(k[:], keyFamily) 90 oldv := keyFamilies.Get(k[:]) 91 92 // If there is a value, decode it to get the next usable index. 93 if len(oldv) == 4 { 94 index = byteOrder.Uint32(oldv) 95 96 // If we've passed the usable range for this keyfamily, 97 // return an error. 98 if index >= lastUsableKeyFamilyIndex { 99 return errKeyFamilyExhausted 100 } 101 } 102 103 // Update the database with the next usable index. 104 byteOrder.PutUint32(v[:], index+1) 105 keyFamilies.Put(k[:], v[:]) 106 107 return nil 108 }, func() {}) 109 110 return index, err 111 } 112 113 // CompareAndStoreAccountID attempts to compare an existing account ID to a 114 // given parameter if the ID exists in the database and returns an error if the 115 // IDs don't match. If the database is empty, then the ID is stored. 116 func (d *DB) CompareAndStoreAccountID(id []byte) error { 117 return kvdb.Update(d, func(tx kvdb.RwTx) error { 118 keychain, err := tx.CreateTopLevelBucket(keychainBucket) 119 if err != nil { 120 return err 121 } 122 123 acctId := keychain.Get(keyAccountIDBucket) 124 if acctId == nil { 125 keychain.Put(keyAccountIDBucket, id) 126 return nil 127 } 128 129 if !bytes.Equal(acctId, id) { 130 return errDifferentAccountID 131 } 132 133 return nil 134 }, func() {}) 135 }