/* * Copyright (c) 2014 Conformal Systems LLC * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package waddrmgr import ( "encoding/binary" "fmt" "time" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/fastsha256" ) const ( // LatestMgrVersion is the most recent manager version. LatestMgrVersion = 3 ) var ( // latestMgrVersion is the most recent manager version as a variable so // the tests can change it to force errors. latestMgrVersion uint32 = LatestMgrVersion ) // ObtainUserInputFunc is a function that reads a user input and returns it as // a byte stream. It is used to accept data required during upgrades, for e.g. // wallet seed and private passphrase. type ObtainUserInputFunc func() ([]byte, error) // maybeConvertDbError converts the passed error to a ManagerError with an // error code of ErrDatabase if it is not already a ManagerError. This is // useful for potential errors returned from managed transaction an other parts // of the walletdb database. func maybeConvertDbError(err error) error { // When the error is already a ManagerError, just return it. if _, ok := err.(ManagerError); ok { return err } return managerError(ErrDatabase, err.Error(), err) } // syncStatus represents a address synchronization status stored in the // database. type syncStatus uint8 // These constants define the various supported sync status types. // // NOTE: These are currently unused but are being defined for the possibility of // supporting sync status on a per-address basis. const ( ssNone syncStatus = 0 // not iota as they need to be stable for db ssPartial syncStatus = 1 ssFull syncStatus = 2 ) // addressType represents a type of address stored in the database. type addressType uint8 // These constants define the various supported address types. const ( adtChain addressType = 0 // not iota as they need to be stable for db adtImport addressType = 1 adtScript addressType = 2 ) // accountType represents a type of address stored in the database. type accountType uint8 // These constants define the various supported account types. const ( actBIP0044 accountType = 0 // not iota as they need to be stable for db ) // dbAccountRow houses information stored about an account in the database. type dbAccountRow struct { acctType accountType rawData []byte // Varies based on account type field. } // dbBIP0044AccountRow houses additional information stored about a BIP0044 // account in the database. type dbBIP0044AccountRow struct { dbAccountRow pubKeyEncrypted []byte privKeyEncrypted []byte nextExternalIndex uint32 nextInternalIndex uint32 name string } // dbAddressRow houses common information stored about an address in the // database. type dbAddressRow struct { addrType addressType account uint32 addTime uint64 syncStatus syncStatus used bool rawData []byte // Varies based on address type field. } // dbChainAddressRow houses additional information stored about a chained // address in the database. type dbChainAddressRow struct { dbAddressRow branch uint32 index uint32 } // dbImportedAddressRow houses additional information stored about an imported // public key address in the database. type dbImportedAddressRow struct { dbAddressRow encryptedPubKey []byte encryptedPrivKey []byte } // dbImportedAddressRow houses additional information stored about a script // address in the database. type dbScriptAddressRow struct { dbAddressRow encryptedHash []byte encryptedScript []byte } // Key names for various database fields. var ( // nullVall is null byte used as a flag value in a bucket entry nullVal = []byte{0} // Bucket names. acctBucketName = []byte("acct") addrBucketName = []byte("addr") // addrAcctIdxBucketName is used to index account addresses // Entries in this index may map: // * addr hash => account id // * account bucket -> addr hash => null // To fetch the account of an address, lookup the value using // the address hash. // To fetch all addresses of an account, fetch the account bucket, iterate // over the keys and fetch the address row from the addr bucket. // The index needs to be updated whenever an address is created e.g. // NewAddress addrAcctIdxBucketName = []byte("addracctidx") // acctNameIdxBucketName is used to create an index // mapping an account name string to the corresponding // account id. // The index needs to be updated whenever the account name // and id changes e.g. RenameAccount acctNameIdxBucketName = []byte("acctnameidx") // acctIDIdxBucketName is used to create an index // mapping an account id to the corresponding // account name string. // The index needs to be updated whenever the account name // and id changes e.g. RenameAccount acctIDIdxBucketName = []byte("acctididx") // meta is used to store meta-data about the address manager // e.g. last account number metaBucketName = []byte("meta") // lastAccountName is used to store the metadata - last account // in the manager lastAccountName = []byte("lastaccount") mainBucketName = []byte("main") syncBucketName = []byte("sync") // Db related key names (main bucket). mgrVersionName = []byte("mgrver") mgrCreateDateName = []byte("mgrcreated") // Crypto related key names (main bucket). masterPrivKeyName = []byte("mpriv") masterPubKeyName = []byte("mpub") cryptoPrivKeyName = []byte("cpriv") cryptoPubKeyName = []byte("cpub") cryptoScriptKeyName = []byte("cscript") coinTypePrivKeyName = []byte("ctpriv") coinTypePubKeyName = []byte("ctpub") watchingOnlyName = []byte("watchonly") // Sync related key names (sync bucket). syncedToName = []byte("syncedto") startBlockName = []byte("startblock") recentBlocksName = []byte("recentblocks") // Account related key names (account bucket). acctNumAcctsName = []byte("numaccts") // Used addresses (used bucket) usedAddrBucketName = []byte("usedaddrs") ) // uint32ToBytes converts a 32 bit unsigned integer into a 4-byte slice in // little-endian order: 1 -> [1 0 0 0]. func uint32ToBytes(number uint32) []byte { buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, number) return buf } // uint64ToBytes converts a 64 bit unsigned integer into a 8-byte slice in // little-endian order: 1 -> [1 0 0 0 0 0 0 0]. func uint64ToBytes(number uint64) []byte { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, number) return buf } // stringToBytes converts a string into a variable length byte slice in // little-endian order: "abc" -> [3 0 0 0 61 62 63] func stringToBytes(s string) []byte { // The serialized format is: // // // 4 bytes string size + string size := len(s) buf := make([]byte, 4+size) copy(buf[0:4], uint32ToBytes(uint32(size))) copy(buf[4:4+size], s) return buf } // fetchManagerVersion fetches the current manager version from the database. func fetchManagerVersion(tx walletdb.Tx) (uint32, error) { mainBucket := tx.RootBucket().Bucket(mainBucketName) verBytes := mainBucket.Get(mgrVersionName) if verBytes == nil { str := "required version number not stored in database" return 0, managerError(ErrDatabase, str, nil) } version := binary.LittleEndian.Uint32(verBytes) return version, nil } // putManagerVersion stores the provided version to the database. func putManagerVersion(tx walletdb.Tx, version uint32) error { bucket := tx.RootBucket().Bucket(mainBucketName) verBytes := uint32ToBytes(version) err := bucket.Put(mgrVersionName, verBytes) if err != nil { str := "failed to store version" return managerError(ErrDatabase, str, err) } return nil } // fetchMasterKeyParams loads the master key parameters needed to derive them // (when given the correct user-supplied passphrase) from the database. Either // returned value can be nil, but in practice only the private key params will // be nil for a watching-only database. func fetchMasterKeyParams(tx walletdb.Tx) ([]byte, []byte, error) { bucket := tx.RootBucket().Bucket(mainBucketName) // Load the master public key parameters. Required. val := bucket.Get(masterPubKeyName) if val == nil { str := "required master public key parameters not stored in " + "database" return nil, nil, managerError(ErrDatabase, str, nil) } pubParams := make([]byte, len(val)) copy(pubParams, val) // Load the master private key parameters if they were stored. var privParams []byte val = bucket.Get(masterPrivKeyName) if val != nil { privParams = make([]byte, len(val)) copy(privParams, val) } return pubParams, privParams, nil } // putMasterKeyParams stores the master key parameters needed to derive them // to the database. Either parameter can be nil in which case no value is // written for the parameter. func putMasterKeyParams(tx walletdb.Tx, pubParams, privParams []byte) error { bucket := tx.RootBucket().Bucket(mainBucketName) if privParams != nil { err := bucket.Put(masterPrivKeyName, privParams) if err != nil { str := "failed to store master private key parameters" return managerError(ErrDatabase, str, err) } } if pubParams != nil { err := bucket.Put(masterPubKeyName, pubParams) if err != nil { str := "failed to store master public key parameters" return managerError(ErrDatabase, str, err) } } return nil } // fetchCoinTypeKeys loads the encrypted cointype keys which are in turn used to // derive the extended keys for all accounts. func fetchCoinTypeKeys(tx walletdb.Tx) ([]byte, []byte, error) { bucket := tx.RootBucket().Bucket(mainBucketName) coinTypePubKeyEnc := bucket.Get(coinTypePubKeyName) if coinTypePubKeyEnc == nil { str := "required encrypted cointype public key not stored in database" return nil, nil, managerError(ErrDatabase, str, nil) } coinTypePrivKeyEnc := bucket.Get(coinTypePrivKeyName) if coinTypePrivKeyEnc == nil { str := "required encrypted cointype private key not stored in database" return nil, nil, managerError(ErrDatabase, str, nil) } return coinTypePubKeyEnc, coinTypePrivKeyEnc, nil } // putCoinTypeKeys stores the encrypted cointype keys which are in turn used to // derive the extended keys for all accounts. Either parameter can be nil in which // case no value is written for the parameter. func putCoinTypeKeys(tx walletdb.Tx, coinTypePubKeyEnc []byte, coinTypePrivKeyEnc []byte) error { bucket := tx.RootBucket().Bucket(mainBucketName) if coinTypePubKeyEnc != nil { err := bucket.Put(coinTypePubKeyName, coinTypePubKeyEnc) if err != nil { str := "failed to store encrypted cointype public key" return managerError(ErrDatabase, str, err) } } if coinTypePrivKeyEnc != nil { err := bucket.Put(coinTypePrivKeyName, coinTypePrivKeyEnc) if err != nil { str := "failed to store encrypted cointype private key" return managerError(ErrDatabase, str, err) } } return nil } // fetchCryptoKeys loads the encrypted crypto keys which are in turn used to // protect the extended keys, imported keys, and scripts. Any of the returned // values can be nil, but in practice only the crypto private and script keys // will be nil for a watching-only database. func fetchCryptoKeys(tx walletdb.Tx) ([]byte, []byte, []byte, error) { bucket := tx.RootBucket().Bucket(mainBucketName) // Load the crypto public key parameters. Required. val := bucket.Get(cryptoPubKeyName) if val == nil { str := "required encrypted crypto public not stored in database" return nil, nil, nil, managerError(ErrDatabase, str, nil) } pubKey := make([]byte, len(val)) copy(pubKey, val) // Load the crypto private key parameters if they were stored. var privKey []byte val = bucket.Get(cryptoPrivKeyName) if val != nil { privKey = make([]byte, len(val)) copy(privKey, val) } // Load the crypto script key parameters if they were stored. var scriptKey []byte val = bucket.Get(cryptoScriptKeyName) if val != nil { scriptKey = make([]byte, len(val)) copy(scriptKey, val) } return pubKey, privKey, scriptKey, nil } // putCryptoKeys stores the encrypted crypto keys which are in turn used to // protect the extended and imported keys. Either parameter can be nil in which // case no value is written for the parameter. func putCryptoKeys(tx walletdb.Tx, pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error { bucket := tx.RootBucket().Bucket(mainBucketName) if pubKeyEncrypted != nil { err := bucket.Put(cryptoPubKeyName, pubKeyEncrypted) if err != nil { str := "failed to store encrypted crypto public key" return managerError(ErrDatabase, str, err) } } if privKeyEncrypted != nil { err := bucket.Put(cryptoPrivKeyName, privKeyEncrypted) if err != nil { str := "failed to store encrypted crypto private key" return managerError(ErrDatabase, str, err) } } if scriptKeyEncrypted != nil { err := bucket.Put(cryptoScriptKeyName, scriptKeyEncrypted) if err != nil { str := "failed to store encrypted crypto script key" return managerError(ErrDatabase, str, err) } } return nil } // fetchWatchingOnly loads the watching-only flag from the database. func fetchWatchingOnly(tx walletdb.Tx) (bool, error) { bucket := tx.RootBucket().Bucket(mainBucketName) buf := bucket.Get(watchingOnlyName) if len(buf) != 1 { str := "malformed watching-only flag stored in database" return false, managerError(ErrDatabase, str, nil) } return buf[0] != 0, nil } // putWatchingOnly stores the watching-only flag to the database. func putWatchingOnly(tx walletdb.Tx, watchingOnly bool) error { bucket := tx.RootBucket().Bucket(mainBucketName) var encoded byte if watchingOnly { encoded = 1 } if err := bucket.Put(watchingOnlyName, []byte{encoded}); err != nil { str := "failed to store watching only flag" return managerError(ErrDatabase, str, err) } return nil } // deserializeAccountRow deserializes the passed serialized account information. // This is used as a common base for the various account types to deserialize // the common parts. func deserializeAccountRow(accountID []byte, serializedAccount []byte) (*dbAccountRow, error) { // The serialized account format is: // // // 1 byte acctType + 4 bytes raw data length + raw data // Given the above, the length of the entry must be at a minimum // the constant value sizes. if len(serializedAccount) < 5 { str := fmt.Sprintf("malformed serialized account for key %x", accountID) return nil, managerError(ErrDatabase, str, nil) } row := dbAccountRow{} row.acctType = accountType(serializedAccount[0]) rdlen := binary.LittleEndian.Uint32(serializedAccount[1:5]) row.rawData = make([]byte, rdlen) copy(row.rawData, serializedAccount[5:5+rdlen]) return &row, nil } // serializeAccountRow returns the serialization of the passed account row. func serializeAccountRow(row *dbAccountRow) []byte { // The serialized account format is: // // // 1 byte acctType + 4 bytes raw data length + raw data rdlen := len(row.rawData) buf := make([]byte, 5+rdlen) buf[0] = byte(row.acctType) binary.LittleEndian.PutUint32(buf[1:5], uint32(rdlen)) copy(buf[5:5+rdlen], row.rawData) return buf } // deserializeBIP0044AccountRow deserializes the raw data from the passed // account row as a BIP0044 account. func deserializeBIP0044AccountRow(accountID []byte, row *dbAccountRow) (*dbBIP0044AccountRow, error) { // The serialized BIP0044 account raw data format is: // // // // 4 bytes encrypted pubkey len + encrypted pubkey + 4 bytes encrypted // privkey len + encrypted privkey + 4 bytes next external index + // 4 bytes next internal index + 4 bytes name len + name // Given the above, the length of the entry must be at a minimum // the constant value sizes. if len(row.rawData) < 20 { str := fmt.Sprintf("malformed serialized bip0044 account for "+ "key %x", accountID) return nil, managerError(ErrDatabase, str, nil) } retRow := dbBIP0044AccountRow{ dbAccountRow: *row, } pubLen := binary.LittleEndian.Uint32(row.rawData[0:4]) retRow.pubKeyEncrypted = make([]byte, pubLen) copy(retRow.pubKeyEncrypted, row.rawData[4:4+pubLen]) offset := 4 + pubLen privLen := binary.LittleEndian.Uint32(row.rawData[offset : offset+4]) offset += 4 retRow.privKeyEncrypted = make([]byte, privLen) copy(retRow.privKeyEncrypted, row.rawData[offset:offset+privLen]) offset += privLen retRow.nextExternalIndex = binary.LittleEndian.Uint32(row.rawData[offset : offset+4]) offset += 4 retRow.nextInternalIndex = binary.LittleEndian.Uint32(row.rawData[offset : offset+4]) offset += 4 nameLen := binary.LittleEndian.Uint32(row.rawData[offset : offset+4]) offset += 4 retRow.name = string(row.rawData[offset : offset+nameLen]) return &retRow, nil } // serializeBIP0044AccountRow returns the serialization of the raw data field // for a BIP0044 account. func serializeBIP0044AccountRow(encryptedPubKey, encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32, name string) []byte { // The serialized BIP0044 account raw data format is: // // // // 4 bytes encrypted pubkey len + encrypted pubkey + 4 bytes encrypted // privkey len + encrypted privkey + 4 bytes next external index + // 4 bytes next internal index + 4 bytes name len + name pubLen := uint32(len(encryptedPubKey)) privLen := uint32(len(encryptedPrivKey)) nameLen := uint32(len(name)) rawData := make([]byte, 20+pubLen+privLen+nameLen) binary.LittleEndian.PutUint32(rawData[0:4], pubLen) copy(rawData[4:4+pubLen], encryptedPubKey) offset := 4 + pubLen binary.LittleEndian.PutUint32(rawData[offset:offset+4], privLen) offset += 4 copy(rawData[offset:offset+privLen], encryptedPrivKey) offset += privLen binary.LittleEndian.PutUint32(rawData[offset:offset+4], nextExternalIndex) offset += 4 binary.LittleEndian.PutUint32(rawData[offset:offset+4], nextInternalIndex) offset += 4 binary.LittleEndian.PutUint32(rawData[offset:offset+4], nameLen) offset += 4 copy(rawData[offset:offset+nameLen], name) return rawData } // fetchAllAccounts loads information about all accounts from the database. // The returned value is a slice of account numbers which can be used to load // the respective account rows. // TODO(tuxcanfly): Switch over to an iterator to support the maximum of 2^31-2 accounts func fetchAllAccounts(tx walletdb.Tx) ([]uint32, error) { bucket := tx.RootBucket().Bucket(acctBucketName) var accounts []uint32 err := bucket.ForEach(func(k, v []byte) error { // Skip buckets. if v == nil { return nil } accounts = append(accounts, binary.LittleEndian.Uint32(k)) return nil }) return accounts, err } // fetchLastAccount retreives the last account from the database. func fetchLastAccount(tx walletdb.Tx) (uint32, error) { bucket := tx.RootBucket().Bucket(metaBucketName) val := bucket.Get(lastAccountName) if len(val) != 4 { str := fmt.Sprintf("malformed metadata '%s' stored in database", lastAccountName) return 0, managerError(ErrDatabase, str, nil) } account := binary.LittleEndian.Uint32(val[0:4]) return account, nil } // fetchAccountName retreives the account name given an account number from // the database. func fetchAccountName(tx walletdb.Tx, account uint32) (string, error) { bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) val := bucket.Get(uint32ToBytes(account)) if val == nil { str := fmt.Sprintf("account %d not found", account) return "", managerError(ErrAccountNotFound, str, nil) } offset := uint32(0) nameLen := binary.LittleEndian.Uint32(val[offset : offset+4]) offset += 4 acctName := string(val[offset : offset+nameLen]) return acctName, nil } // fetchAccountByName retreives the account number given an account name // from the database. func fetchAccountByName(tx walletdb.Tx, name string) (uint32, error) { bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) val := bucket.Get(stringToBytes(name)) if val == nil { str := fmt.Sprintf("account name '%s' not found", name) return 0, managerError(ErrAccountNotFound, str, nil) } return binary.LittleEndian.Uint32(val), nil } // fetchAccountInfo loads information about the passed account from the // database. func fetchAccountInfo(tx walletdb.Tx, account uint32) (interface{}, error) { bucket := tx.RootBucket().Bucket(acctBucketName) accountID := uint32ToBytes(account) serializedRow := bucket.Get(accountID) if serializedRow == nil { str := fmt.Sprintf("account %d not found", account) return nil, managerError(ErrAccountNotFound, str, nil) } row, err := deserializeAccountRow(accountID, serializedRow) if err != nil { return nil, err } switch row.acctType { case actBIP0044: return deserializeBIP0044AccountRow(accountID, row) } str := fmt.Sprintf("unsupported account type '%d'", row.acctType) return nil, managerError(ErrDatabase, str, nil) } // deleteAccountNameIndex deletes the given key from the account name index of the database. func deleteAccountNameIndex(tx walletdb.Tx, name string) error { bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) // Delete the account name key err := bucket.Delete(stringToBytes(name)) if err != nil { str := fmt.Sprintf("failed to delete account name index key %s", name) return managerError(ErrDatabase, str, err) } return nil } // deleteAccounIdIndex deletes the given key from the account id index of the database. func deleteAccountIDIndex(tx walletdb.Tx, account uint32) error { bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) // Delete the account id key err := bucket.Delete(uint32ToBytes(account)) if err != nil { str := fmt.Sprintf("failed to delete account id index key %d", account) return managerError(ErrDatabase, str, err) } return nil } // putAccountNameIndex stores the given key to the account name index of the database. func putAccountNameIndex(tx walletdb.Tx, account uint32, name string) error { bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) // Write the account number keyed by the account name. err := bucket.Put(stringToBytes(name), uint32ToBytes(account)) if err != nil { str := fmt.Sprintf("failed to store account name index key %s", name) return managerError(ErrDatabase, str, err) } return nil } // putAccountIDIndex stores the given key to the account id index of the database. func putAccountIDIndex(tx walletdb.Tx, account uint32, name string) error { bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) // Write the account number keyed by the account id. err := bucket.Put(uint32ToBytes(account), stringToBytes(name)) if err != nil { str := fmt.Sprintf("failed to store account id index key %s", name) return managerError(ErrDatabase, str, err) } return nil } // putAddrAccountIndex stores the given key to the address account index of the database. func putAddrAccountIndex(tx walletdb.Tx, account uint32, addrHash []byte) error { bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName) // Write account keyed by address hash err := bucket.Put(addrHash, uint32ToBytes(account)) if err != nil { return nil } bucket, err = bucket.CreateBucketIfNotExists(uint32ToBytes(account)) if err != nil { return err } // In account bucket, write a null value keyed by the address hash err = bucket.Put(addrHash, nullVal) if err != nil { str := fmt.Sprintf("failed to store address account index key %s", addrHash) return managerError(ErrDatabase, str, err) } return nil } // putAccountRow stores the provided account information to the database. This // is used a common base for storing the various account types. func putAccountRow(tx walletdb.Tx, account uint32, row *dbAccountRow) error { bucket := tx.RootBucket().Bucket(acctBucketName) // Write the serialized value keyed by the account number. err := bucket.Put(uint32ToBytes(account), serializeAccountRow(row)) if err != nil { str := fmt.Sprintf("failed to store account %d", account) return managerError(ErrDatabase, str, err) } return nil } // putAccountInfo stores the provided account information to the database. func putAccountInfo(tx walletdb.Tx, account uint32, encryptedPubKey, encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32, name string) error { rawData := serializeBIP0044AccountRow(encryptedPubKey, encryptedPrivKey, nextExternalIndex, nextInternalIndex, name) acctRow := dbAccountRow{ acctType: actBIP0044, rawData: rawData, } if err := putAccountRow(tx, account, &acctRow); err != nil { return err } // Update account id index if err := putAccountIDIndex(tx, account, name); err != nil { return err } // Update account name index if err := putAccountNameIndex(tx, account, name); err != nil { return err } return nil } // putLastAccount stores the provided metadata - last account - to the database. func putLastAccount(tx walletdb.Tx, account uint32) error { bucket := tx.RootBucket().Bucket(metaBucketName) err := bucket.Put(lastAccountName, uint32ToBytes(account)) if err != nil { str := fmt.Sprintf("failed to update metadata '%s'", lastAccountName) return managerError(ErrDatabase, str, err) } return nil } // fetchAddressRow loads address information for the provided address id from // the database. This is used as a common base for the various address types // to load the common information. // deserializeAddressRow deserializes the passed serialized address information. // This is used as a common base for the various address types to deserialize // the common parts. func deserializeAddressRow(serializedAddress []byte) (*dbAddressRow, error) { // The serialized address format is: // // // 1 byte addrType + 4 bytes account + 8 bytes addTime + 1 byte // syncStatus + 4 bytes raw data length + raw data // Given the above, the length of the entry must be at a minimum // the constant value sizes. if len(serializedAddress) < 18 { str := "malformed serialized address" return nil, managerError(ErrDatabase, str, nil) } row := dbAddressRow{} row.addrType = addressType(serializedAddress[0]) row.account = binary.LittleEndian.Uint32(serializedAddress[1:5]) row.addTime = binary.LittleEndian.Uint64(serializedAddress[5:13]) row.syncStatus = syncStatus(serializedAddress[13]) rdlen := binary.LittleEndian.Uint32(serializedAddress[14:18]) row.rawData = make([]byte, rdlen) copy(row.rawData, serializedAddress[18:18+rdlen]) return &row, nil } // serializeAddressRow returns the serialization of the passed address row. func serializeAddressRow(row *dbAddressRow) []byte { // The serialized address format is: // // // // 1 byte addrType + 4 bytes account + 8 bytes addTime + 1 byte // syncStatus + 4 bytes raw data length + raw data rdlen := len(row.rawData) buf := make([]byte, 18+rdlen) buf[0] = byte(row.addrType) binary.LittleEndian.PutUint32(buf[1:5], row.account) binary.LittleEndian.PutUint64(buf[5:13], row.addTime) buf[13] = byte(row.syncStatus) binary.LittleEndian.PutUint32(buf[14:18], uint32(rdlen)) copy(buf[18:18+rdlen], row.rawData) return buf } // deserializeChainedAddress deserializes the raw data from the passed address // row as a chained address. func deserializeChainedAddress(row *dbAddressRow) (*dbChainAddressRow, error) { // The serialized chain address raw data format is: // // // 4 bytes branch + 4 bytes address index if len(row.rawData) != 8 { str := "malformed serialized chained address" return nil, managerError(ErrDatabase, str, nil) } retRow := dbChainAddressRow{ dbAddressRow: *row, } retRow.branch = binary.LittleEndian.Uint32(row.rawData[0:4]) retRow.index = binary.LittleEndian.Uint32(row.rawData[4:8]) return &retRow, nil } // serializeChainedAddress returns the serialization of the raw data field for // a chained address. func serializeChainedAddress(branch, index uint32) []byte { // The serialized chain address raw data format is: // // // 4 bytes branch + 4 bytes address index rawData := make([]byte, 8) binary.LittleEndian.PutUint32(rawData[0:4], branch) binary.LittleEndian.PutUint32(rawData[4:8], index) return rawData } // deserializeImportedAddress deserializes the raw data from the passed address // row as an imported address. func deserializeImportedAddress(row *dbAddressRow) (*dbImportedAddressRow, error) { // The serialized imported address raw data format is: // // // 4 bytes encrypted pubkey len + encrypted pubkey + 4 bytes encrypted // privkey len + encrypted privkey // Given the above, the length of the entry must be at a minimum // the constant value sizes. if len(row.rawData) < 8 { str := "malformed serialized imported address" return nil, managerError(ErrDatabase, str, nil) } retRow := dbImportedAddressRow{ dbAddressRow: *row, } pubLen := binary.LittleEndian.Uint32(row.rawData[0:4]) retRow.encryptedPubKey = make([]byte, pubLen) copy(retRow.encryptedPubKey, row.rawData[4:4+pubLen]) offset := 4 + pubLen privLen := binary.LittleEndian.Uint32(row.rawData[offset : offset+4]) offset += 4 retRow.encryptedPrivKey = make([]byte, privLen) copy(retRow.encryptedPrivKey, row.rawData[offset:offset+privLen]) return &retRow, nil } // serializeImportedAddress returns the serialization of the raw data field for // an imported address. func serializeImportedAddress(encryptedPubKey, encryptedPrivKey []byte) []byte { // The serialized imported address raw data format is: // // // 4 bytes encrypted pubkey len + encrypted pubkey + 4 bytes encrypted // privkey len + encrypted privkey pubLen := uint32(len(encryptedPubKey)) privLen := uint32(len(encryptedPrivKey)) rawData := make([]byte, 8+pubLen+privLen) binary.LittleEndian.PutUint32(rawData[0:4], pubLen) copy(rawData[4:4+pubLen], encryptedPubKey) offset := 4 + pubLen binary.LittleEndian.PutUint32(rawData[offset:offset+4], privLen) offset += 4 copy(rawData[offset:offset+privLen], encryptedPrivKey) return rawData } // deserializeScriptAddress deserializes the raw data from the passed address // row as a script address. func deserializeScriptAddress(row *dbAddressRow) (*dbScriptAddressRow, error) { // The serialized script address raw data format is: // // // 4 bytes encrypted script hash len + encrypted script hash + 4 bytes // encrypted script len + encrypted script // Given the above, the length of the entry must be at a minimum // the constant value sizes. if len(row.rawData) < 8 { str := "malformed serialized script address" return nil, managerError(ErrDatabase, str, nil) } retRow := dbScriptAddressRow{ dbAddressRow: *row, } hashLen := binary.LittleEndian.Uint32(row.rawData[0:4]) retRow.encryptedHash = make([]byte, hashLen) copy(retRow.encryptedHash, row.rawData[4:4+hashLen]) offset := 4 + hashLen scriptLen := binary.LittleEndian.Uint32(row.rawData[offset : offset+4]) offset += 4 retRow.encryptedScript = make([]byte, scriptLen) copy(retRow.encryptedScript, row.rawData[offset:offset+scriptLen]) return &retRow, nil } // serializeScriptAddress returns the serialization of the raw data field for // a script address. func serializeScriptAddress(encryptedHash, encryptedScript []byte) []byte { // The serialized script address raw data format is: // // // 4 bytes encrypted script hash len + encrypted script hash + 4 bytes // encrypted script len + encrypted script hashLen := uint32(len(encryptedHash)) scriptLen := uint32(len(encryptedScript)) rawData := make([]byte, 8+hashLen+scriptLen) binary.LittleEndian.PutUint32(rawData[0:4], hashLen) copy(rawData[4:4+hashLen], encryptedHash) offset := 4 + hashLen binary.LittleEndian.PutUint32(rawData[offset:offset+4], scriptLen) offset += 4 copy(rawData[offset:offset+scriptLen], encryptedScript) return rawData } // fetchAddressUsed returns true if the provided address hash was flagged as used. func fetchAddressUsed(tx walletdb.Tx, addrHash []byte) bool { bucket := tx.RootBucket().Bucket(usedAddrBucketName) val := bucket.Get(addrHash[:]) if val != nil { return true } return false } // fetchAddressByHash loads address information for the provided address hash // from the database. The returned value is one of the address rows for the // specific address type. The caller should use type assertions to ascertain // the type. The caller should prefix the error message with the address hash // which caused the failure. func fetchAddressByHash(tx walletdb.Tx, addrHash []byte) (interface{}, error) { bucket := tx.RootBucket().Bucket(addrBucketName) serializedRow := bucket.Get(addrHash[:]) if serializedRow == nil { str := "address not found" return nil, managerError(ErrAddressNotFound, str, nil) } row, err := deserializeAddressRow(serializedRow) if err != nil { return nil, err } row.used = fetchAddressUsed(tx, addrHash[:]) switch row.addrType { case adtChain: return deserializeChainedAddress(row) case adtImport: return deserializeImportedAddress(row) case adtScript: return deserializeScriptAddress(row) } str := fmt.Sprintf("unsupported address type '%d'", row.addrType) return nil, managerError(ErrDatabase, str, nil) } // markAddressUsed flags the provided address id as used in the database. func markAddressUsed(tx walletdb.Tx, addressID []byte) error { bucket := tx.RootBucket().Bucket(usedAddrBucketName) addrHash := fastsha256.Sum256(addressID) val := bucket.Get(addrHash[:]) if val != nil { return nil } err := bucket.Put(addrHash[:], []byte{0}) if err != nil { str := fmt.Sprintf("failed to mark address used %x", addressID) return managerError(ErrDatabase, str, err) } return nil } // fetchAddress loads address information for the provided address id from the // database. The returned value is one of the address rows for the specific // address type. The caller should use type assertions to ascertain the type. // The caller should prefix the error message with the address which caused the // failure. func fetchAddress(tx walletdb.Tx, addressID []byte) (interface{}, error) { addrHash := fastsha256.Sum256(addressID) return fetchAddressByHash(tx, addrHash[:]) } // putAddress stores the provided address information to the database. This // is used a common base for storing the various address types. func putAddress(tx walletdb.Tx, addressID []byte, row *dbAddressRow) error { bucket := tx.RootBucket().Bucket(addrBucketName) // Write the serialized value keyed by the hash of the address. The // additional hash is used to conceal the actual address while still // allowed keyed lookups. addrHash := fastsha256.Sum256(addressID) err := bucket.Put(addrHash[:], serializeAddressRow(row)) if err != nil { str := fmt.Sprintf("failed to store address %x", addressID) return managerError(ErrDatabase, str, err) } // Update address account index return putAddrAccountIndex(tx, row.account, addrHash[:]) } // putChainedAddress stores the provided chained address information to the // database. func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32, status syncStatus, branch, index uint32) error { addrRow := dbAddressRow{ addrType: adtChain, account: account, addTime: uint64(time.Now().Unix()), syncStatus: status, rawData: serializeChainedAddress(branch, index), } if err := putAddress(tx, addressID, &addrRow); err != nil { return err } // Update the next index for the appropriate internal or external // branch. accountID := uint32ToBytes(account) bucket := tx.RootBucket().Bucket(acctBucketName) serializedAccount := bucket.Get(accountID) // Deserialize the account row. row, err := deserializeAccountRow(accountID, serializedAccount) if err != nil { return err } arow, err := deserializeBIP0044AccountRow(accountID, row) if err != nil { return err } // Increment the appropriate next index depending on whether the branch // is internal or external. nextExternalIndex := arow.nextExternalIndex nextInternalIndex := arow.nextInternalIndex if branch == internalBranch { nextInternalIndex = index + 1 } else { nextExternalIndex = index + 1 } // Reserialize the account with the updated index and store it. row.rawData = serializeBIP0044AccountRow(arow.pubKeyEncrypted, arow.privKeyEncrypted, nextExternalIndex, nextInternalIndex, arow.name) err = bucket.Put(accountID, serializeAccountRow(row)) if err != nil { str := fmt.Sprintf("failed to update next index for "+ "address %x, account %d", addressID, account) return managerError(ErrDatabase, str, err) } return nil } // putImportedAddress stores the provided imported address information to the // database. func putImportedAddress(tx walletdb.Tx, addressID []byte, account uint32, status syncStatus, encryptedPubKey, encryptedPrivKey []byte) error { rawData := serializeImportedAddress(encryptedPubKey, encryptedPrivKey) addrRow := dbAddressRow{ addrType: adtImport, account: account, addTime: uint64(time.Now().Unix()), syncStatus: status, rawData: rawData, } return putAddress(tx, addressID, &addrRow) } // putScriptAddress stores the provided script address information to the // database. func putScriptAddress(tx walletdb.Tx, addressID []byte, account uint32, status syncStatus, encryptedHash, encryptedScript []byte) error { rawData := serializeScriptAddress(encryptedHash, encryptedScript) addrRow := dbAddressRow{ addrType: adtScript, account: account, addTime: uint64(time.Now().Unix()), syncStatus: status, rawData: rawData, } if err := putAddress(tx, addressID, &addrRow); err != nil { return err } return nil } // existsAddress returns whether or not the address id exists in the database. func existsAddress(tx walletdb.Tx, addressID []byte) bool { bucket := tx.RootBucket().Bucket(addrBucketName) addrHash := fastsha256.Sum256(addressID) return bucket.Get(addrHash[:]) != nil } // fetchAddrAccount returns the account to which the given address belongs to. // It looks up the account using the addracctidx index which maps the address // hash to its corresponding account id. func fetchAddrAccount(tx walletdb.Tx, addressID []byte) (uint32, error) { bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName) addrHash := fastsha256.Sum256(addressID) val := bucket.Get(addrHash[:]) if val == nil { str := "address not found" return 0, managerError(ErrAddressNotFound, str, nil) } return binary.LittleEndian.Uint32(val), nil } // fetchAccountAddresses loads information about addresses of an account from the database. // The returned value is a slice address rows for each specific address type. // The caller should use type assertions to ascertain the types. func fetchAccountAddresses(tx walletdb.Tx, account uint32) ([]interface{}, error) { bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName). Bucket(uint32ToBytes(account)) // if index bucket is missing the account, there hasn't been any address // entries yet if bucket == nil { return nil, nil } var addrs []interface{} err := bucket.ForEach(func(k, v []byte) error { // Skip buckets. if v == nil { return nil } addrRow, err := fetchAddressByHash(tx, k) if err != nil { if merr, ok := err.(*ManagerError); ok { desc := fmt.Sprintf("failed to fetch address hash '%s': %v", k, merr.Description) merr.Description = desc return merr } return err } addrs = append(addrs, addrRow) return nil }) if err != nil { return nil, maybeConvertDbError(err) } return addrs, nil } // fetchAllAddresses loads information about all addresses from the database. // The returned value is a slice of address rows for each specific address type. // The caller should use type assertions to ascertain the types. // TODO(tuxcanfly): Switch over to an iterator to support the maximum of 2^62 - 2^32 - 2^31 + 2 addrs func fetchAllAddresses(tx walletdb.Tx) ([]interface{}, error) { bucket := tx.RootBucket().Bucket(addrBucketName) var addrs []interface{} err := bucket.ForEach(func(k, v []byte) error { // Skip buckets. if v == nil { return nil } // Deserialize the address row first to determine the field // values. addrRow, err := fetchAddressByHash(tx, k) if merr, ok := err.(*ManagerError); ok { desc := fmt.Sprintf("failed to fetch address hash '%s': %v", k, merr.Description) merr.Description = desc return merr } if err != nil { return err } addrs = append(addrs, addrRow) return nil }) if err != nil { return nil, maybeConvertDbError(err) } return addrs, nil } // deletePrivateKeys removes all private key material from the database. // // NOTE: Care should be taken when calling this function. It is primarily // intended for use in converting to a watching-only copy. Removing the private // keys from the main database without also marking it watching-only will result // in an unusable database. It will also make any imported scripts and private // keys unrecoverable unless there is a backup copy available. func deletePrivateKeys(tx walletdb.Tx) error { bucket := tx.RootBucket().Bucket(mainBucketName) // Delete the master private key params and the crypto private and // script keys. if err := bucket.Delete(masterPrivKeyName); err != nil { str := "failed to delete master private key parameters" return managerError(ErrDatabase, str, err) } if err := bucket.Delete(cryptoPrivKeyName); err != nil { str := "failed to delete crypto private key" return managerError(ErrDatabase, str, err) } if err := bucket.Delete(cryptoScriptKeyName); err != nil { str := "failed to delete crypto script key" return managerError(ErrDatabase, str, err) } if err := bucket.Delete(coinTypePrivKeyName); err != nil { str := "failed to delete cointype private key" return managerError(ErrDatabase, str, err) } // Delete the account extended private key for all accounts. bucket = tx.RootBucket().Bucket(acctBucketName) err := bucket.ForEach(func(k, v []byte) error { // Skip buckets. if v == nil { return nil } // Deserialize the account row first to determine the type. row, err := deserializeAccountRow(k, v) if err != nil { return err } switch row.acctType { case actBIP0044: arow, err := deserializeBIP0044AccountRow(k, row) if err != nil { return err } // Reserialize the account without the private key and // store it. row.rawData = serializeBIP0044AccountRow( arow.pubKeyEncrypted, nil, arow.nextExternalIndex, arow.nextInternalIndex, arow.name) err = bucket.Put(k, serializeAccountRow(row)) if err != nil { str := "failed to delete account private key" return managerError(ErrDatabase, str, err) } } return nil }) if err != nil { return maybeConvertDbError(err) } // Delete the private key for all imported addresses. bucket = tx.RootBucket().Bucket(addrBucketName) err = bucket.ForEach(func(k, v []byte) error { // Skip buckets. if v == nil { return nil } // Deserialize the address row first to determine the field // values. row, err := deserializeAddressRow(v) if err != nil { return err } switch row.addrType { case adtImport: irow, err := deserializeImportedAddress(row) if err != nil { return err } // Reserialize the imported address without the private // key and store it. row.rawData = serializeImportedAddress( irow.encryptedPubKey, nil) err = bucket.Put(k, serializeAddressRow(row)) if err != nil { str := "failed to delete imported private key" return managerError(ErrDatabase, str, err) } case adtScript: srow, err := deserializeScriptAddress(row) if err != nil { return err } // Reserialize the script address without the script // and store it. row.rawData = serializeScriptAddress(srow.encryptedHash, nil) err = bucket.Put(k, serializeAddressRow(row)) if err != nil { str := "failed to delete imported script" return managerError(ErrDatabase, str, err) } } return nil }) if err != nil { return maybeConvertDbError(err) } return nil } // fetchSyncedTo loads the block stamp the manager is synced to from the // database. func fetchSyncedTo(tx walletdb.Tx) (*BlockStamp, error) { bucket := tx.RootBucket().Bucket(syncBucketName) // The serialized synced to format is: // // // 4 bytes block height + 32 bytes hash length buf := bucket.Get(syncedToName) if len(buf) != 36 { str := "malformed sync information stored in database" return nil, managerError(ErrDatabase, str, nil) } var bs BlockStamp bs.Height = int32(binary.LittleEndian.Uint32(buf[0:4])) copy(bs.Hash[:], buf[4:36]) return &bs, nil } // putSyncedTo stores the provided synced to blockstamp to the database. func putSyncedTo(tx walletdb.Tx, bs *BlockStamp) error { bucket := tx.RootBucket().Bucket(syncBucketName) // The serialized synced to format is: // // // 4 bytes block height + 32 bytes hash length buf := make([]byte, 36) binary.LittleEndian.PutUint32(buf[0:4], uint32(bs.Height)) copy(buf[4:36], bs.Hash[0:32]) err := bucket.Put(syncedToName, buf) if err != nil { str := fmt.Sprintf("failed to store sync information %v", bs.Hash) return managerError(ErrDatabase, str, err) } return nil } // fetchStartBlock loads the start block stamp for the manager from the // database. func fetchStartBlock(tx walletdb.Tx) (*BlockStamp, error) { bucket := tx.RootBucket().Bucket(syncBucketName) // The serialized start block format is: // // // 4 bytes block height + 32 bytes hash length buf := bucket.Get(startBlockName) if len(buf) != 36 { str := "malformed start block stored in database" return nil, managerError(ErrDatabase, str, nil) } var bs BlockStamp bs.Height = int32(binary.LittleEndian.Uint32(buf[0:4])) copy(bs.Hash[:], buf[4:36]) return &bs, nil } // putStartBlock stores the provided start block stamp to the database. func putStartBlock(tx walletdb.Tx, bs *BlockStamp) error { bucket := tx.RootBucket().Bucket(syncBucketName) // The serialized start block format is: // // // 4 bytes block height + 32 bytes hash length buf := make([]byte, 36) binary.LittleEndian.PutUint32(buf[0:4], uint32(bs.Height)) copy(buf[4:36], bs.Hash[0:32]) err := bucket.Put(startBlockName, buf) if err != nil { str := fmt.Sprintf("failed to store start block %v", bs.Hash) return managerError(ErrDatabase, str, err) } return nil } // fetchRecentBlocks returns the height of the most recent block height and // hashes of the most recent blocks. func fetchRecentBlocks(tx walletdb.Tx) (int32, []wire.ShaHash, error) { bucket := tx.RootBucket().Bucket(syncBucketName) // The serialized recent blocks format is: // // // 4 bytes recent block height + 4 bytes number of hashes + raw hashes // at 32 bytes each. // Given the above, the length of the entry must be at a minimum // the constant value sizes. buf := bucket.Get(recentBlocksName) if len(buf) < 8 { str := "malformed recent blocks stored in database" return 0, nil, managerError(ErrDatabase, str, nil) } recentHeight := int32(binary.LittleEndian.Uint32(buf[0:4])) numHashes := binary.LittleEndian.Uint32(buf[4:8]) recentHashes := make([]wire.ShaHash, numHashes) offset := 8 for i := uint32(0); i < numHashes; i++ { copy(recentHashes[i][:], buf[offset:offset+32]) offset += 32 } return recentHeight, recentHashes, nil } // putRecentBlocks stores the provided start block stamp to the database. func putRecentBlocks(tx walletdb.Tx, recentHeight int32, recentHashes []wire.ShaHash) error { bucket := tx.RootBucket().Bucket(syncBucketName) // The serialized recent blocks format is: // // // 4 bytes recent block height + 4 bytes number of hashes + raw hashes // at 32 bytes each. numHashes := uint32(len(recentHashes)) buf := make([]byte, 8+(numHashes*32)) binary.LittleEndian.PutUint32(buf[0:4], uint32(recentHeight)) binary.LittleEndian.PutUint32(buf[4:8], numHashes) offset := 8 for i := uint32(0); i < numHashes; i++ { copy(buf[offset:offset+32], recentHashes[i][:]) offset += 32 } err := bucket.Put(recentBlocksName, buf) if err != nil { str := "failed to store recent blocks" return managerError(ErrDatabase, str, err) } return nil } // managerExists returns whether or not the manager has already been created // in the given database namespace. func managerExists(namespace walletdb.Namespace) (bool, error) { var exists bool err := namespace.View(func(tx walletdb.Tx) error { mainBucket := tx.RootBucket().Bucket(mainBucketName) exists = mainBucket != nil return nil }) if err != nil { str := fmt.Sprintf("failed to obtain database view: %v", err) return false, managerError(ErrDatabase, str, err) } return exists, nil } // createManagerNS creates the initial namespace structure needed for all of the // manager data. This includes things such as all of the buckets as well as the // version and creation date. func createManagerNS(namespace walletdb.Namespace) error { err := namespace.Update(func(tx walletdb.Tx) error { rootBucket := tx.RootBucket() mainBucket, err := rootBucket.CreateBucket(mainBucketName) if err != nil { str := "failed to create main bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(addrBucketName) if err != nil { str := "failed to create address bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(acctBucketName) if err != nil { str := "failed to create account bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(addrAcctIdxBucketName) if err != nil { str := "failed to create address index bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(syncBucketName) if err != nil { str := "failed to create sync bucket" return managerError(ErrDatabase, str, err) } // usedAddrBucketName bucket was added after manager version 1 release _, err = rootBucket.CreateBucket(usedAddrBucketName) if err != nil { str := "failed to create used addresses bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(acctNameIdxBucketName) if err != nil { str := "failed to create an account name index bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(acctIDIdxBucketName) if err != nil { str := "failed to create an account id index bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(metaBucketName) if err != nil { str := "failed to create a meta bucket" return managerError(ErrDatabase, str, err) } if err := putLastAccount(tx, DefaultAccountNum); err != nil { return err } if err := putManagerVersion(tx, latestMgrVersion); err != nil { return err } createDate := uint64(time.Now().Unix()) var dateBytes [8]byte binary.LittleEndian.PutUint64(dateBytes[:], createDate) err = mainBucket.Put(mgrCreateDateName, dateBytes[:]) if err != nil { str := "failed to store database creation time" return managerError(ErrDatabase, str, err) } return nil }) if err != nil { str := "failed to update database" return managerError(ErrDatabase, str, err) } return nil } // upgradeToVersion2 upgrades the database from version 1 to version 2 // 'usedAddrBucketName' a bucket for storing addrs flagged as marked is // initialized and it will be updated on the next rescan. func upgradeToVersion2(namespace walletdb.Namespace) error { err := namespace.Update(func(tx walletdb.Tx) error { currentMgrVersion := uint32(2) rootBucket := tx.RootBucket() _, err := rootBucket.CreateBucket(usedAddrBucketName) if err != nil { str := "failed to create used addresses bucket" return managerError(ErrDatabase, str, err) } if err := putManagerVersion(tx, currentMgrVersion); err != nil { return err } return nil }) if err != nil { return maybeConvertDbError(err) } return nil } // upgradeManager upgrades the data in the provided manager namespace to newer // versions as neeeded. func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, config *Options) error { var version uint32 err := namespace.View(func(tx walletdb.Tx) error { var err error version, err = fetchManagerVersion(tx) return err }) if err != nil { str := "failed to fetch version for update" return managerError(ErrDatabase, str, err) } // NOTE: There are currently no upgrades, but this is provided here as a // template for how to properly do upgrades. Each function to upgrade // to the next version must include serializing the new version as a // part of the same transaction so any failures in upgrades to later // versions won't leave the database in an inconsistent state. The // putManagerVersion function provides a convenient mechanism for that // purpose. // // Upgrade one version at a time so it is possible to upgrade across // an aribtary number of versions without needing to write a bunch of // additional code to go directly from version X to Y. // if version < 2 { // // Upgrade from version 1 to 2. // if err := upgradeToVersion2(namespace); err != nil { // return err // } // // // The manager is now at version 2. // version = 2 // } // if version < 3 { // // Upgrade from version 2 to 3. // if err := upgradeToVersion3(namespace); err != nil { // return err // } // // // The manager is now at version 3. // version = 3 // } if version < 2 { // Upgrade from version 1 to 2. if err := upgradeToVersion2(namespace); err != nil { return err } // The manager is now at version 2. version = 2 } if version < 3 { if config.ObtainSeed == nil || config.ObtainPrivatePass == nil { str := "failed to obtain seed and private passphrase required for upgrade" return managerError(ErrDatabase, str, err) } seed, err := config.ObtainSeed() if err != nil { return err } privPassPhrase, err := config.ObtainPrivatePass() if err != nil { return err } // Upgrade from version 2 to 3. if err := upgradeToVersion3(namespace, seed, privPassPhrase, pubPassPhrase); err != nil { return err } // The manager is now at version 3. version = 3 } // Ensure the manager is upraded to the latest version. This check is // to intentionally cause a failure if the manager version is updated // without writing code to handle the upgrade. if version < latestMgrVersion { str := fmt.Sprintf("the latest manager version is %d, but the "+ "current version after upgrades is only %d", latestMgrVersion, version) return managerError(ErrUpgrade, str, nil) } return nil } // upgradeToVersion3 upgrades the database from version 2 to version 3 // The following buckets were introduced in version 3 to support account names: // * acctNameIdxBucketName // * acctIDIdxBucketName // * metaBucketName func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPassPhrase []byte) error { err := namespace.Update(func(tx walletdb.Tx) error { currentMgrVersion := uint32(3) rootBucket := tx.RootBucket() woMgr, err := loadManager(namespace, pubPassPhrase, &chaincfg.SimNetParams, nil) if err != nil { return err } defer woMgr.Close() err = woMgr.Unlock(privPassPhrase) if err != nil { return err } // Derive the master extended key from the seed. root, err := hdkeychain.NewMaster(seed) if err != nil { str := "failed to derive master extended key" return managerError(ErrKeyChain, str, err) } // Derive the cointype key according to BIP0044. coinTypeKeyPriv, err := deriveCoinTypeKey(root, chaincfg.SimNetParams.HDCoinType) if err != nil { str := "failed to derive cointype extended key" return managerError(ErrKeyChain, str, err) } cryptoKeyPub := woMgr.cryptoKeyPub cryptoKeyPriv := woMgr.cryptoKeyPriv // Encrypt the cointype keys with the associated crypto keys. coinTypeKeyPub, err := coinTypeKeyPriv.Neuter() if err != nil { str := "failed to convert cointype private key" return managerError(ErrKeyChain, str, err) } coinTypePubEnc, err := cryptoKeyPub.Encrypt([]byte(coinTypeKeyPub.String())) if err != nil { str := "failed to encrypt cointype public key" return managerError(ErrCrypto, str, err) } coinTypePrivEnc, err := cryptoKeyPriv.Encrypt([]byte(coinTypeKeyPriv.String())) if err != nil { str := "failed to encrypt cointype private key" return managerError(ErrCrypto, str, err) } // Save the encrypted cointype keys to the database. err = putCoinTypeKeys(tx, coinTypePubEnc, coinTypePrivEnc) if err != nil { return err } _, err = rootBucket.CreateBucket(acctNameIdxBucketName) if err != nil { str := "failed to create an account name index bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(acctIDIdxBucketName) if err != nil { str := "failed to create an account id index bucket" return managerError(ErrDatabase, str, err) } _, err = rootBucket.CreateBucket(metaBucketName) if err != nil { str := "failed to create a meta bucket" return managerError(ErrDatabase, str, err) } // Initialize metadata for all keys if err := putLastAccount(tx, DefaultAccountNum); err != nil { return err } // Update default account indexes if err := putAccountIDIndex(tx, DefaultAccountNum, DefaultAccountName); err != nil { return err } if err := putAccountNameIndex(tx, DefaultAccountNum, DefaultAccountName); err != nil { return err } // Update imported account indexes if err := putAccountIDIndex(tx, ImportedAddrAccount, ImportedAddrAccountName); err != nil { return err } if err := putAccountNameIndex(tx, ImportedAddrAccount, ImportedAddrAccountName); err != nil { return err } // Write current manager version if err := putManagerVersion(tx, currentMgrVersion); err != nil { return err } // Save "" alias for default account name for backward compat return putAccountNameIndex(tx, DefaultAccountNum, "") }) if err != nil { return maybeConvertDbError(err) } return nil }