// Copyright (c) 2014-2017 The btcsuite developers // Copyright (c) 2015 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package waddrmgr import ( "bytes" "crypto/sha256" "encoding/binary" "fmt" "time" "github.com/roasbeef/btcd/chaincfg" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcutil/hdkeychain" "github.com/roasbeef/btcwallet/walletdb" ) const ( // LatestMgrVersion is the most recent manager version. LatestMgrVersion = 5 ) 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 adtImport addressType = 1 // not iota as they need to be stable for db 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 ( // accountDefault is the current "default" account type within the // database. This is an account that re-uses the key derivation schema // of BIP0044-like accounts. accountDefault accountType = 0 // not iota as they need to be stable ) // dbAccountRow houses information stored about an account in the database. type dbAccountRow struct { acctType accountType rawData []byte // Varies based on account type field. } // dbDefaultAccountRow houses additional information stored about a default // BIP0044-like account in the database. type dbDefaultAccountRow 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 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. // scopeSchemaBucket is the name of the bucket that maps a particular // manager scope to the type of addresses that should be derived for // particular branches during key derivation. scopeSchemaBucketName = []byte("scope-schema") // scopeBucketNme is the name of the top-level bucket within the // hierarchy. It maps: purpose || coinType to a new sub-bucket that // will house a scoped address manager. All buckets below are a child // of this bucket: // // scopeBucket -> scope -> acctBucket // scopeBucket -> scope -> addrBucket // scopeBucket -> scope -> usedAddrBucket // scopeBucket -> scope -> addrAcctIdxBucket // scopeBucket -> scope -> acctNameIdxBucket // scopeBucket -> scope -> acctIDIdxBucketName // scopeBucket -> scope -> metaBucket // scopeBucket -> scope -> metaBucket -> lastAccountNameKey // scopeBucket -> scope -> coinTypePrivKey // scopeBucket -> scope -> coinTypePubKey scopeBucketName = []byte("scope") // coinTypePrivKeyName is the name of the key within a particular scope // bucket that stores the encrypted cointype private keys. Each scope // within the database will have its own set of coin type keys. coinTypePrivKeyName = []byte("ctpriv") // coinTypePrivKeyName is the name of the key within a particular scope // bucket that stores the encrypted cointype public keys. Each scope // will have its own set of coin type public keys. coinTypePubKeyName = []byte("ctpub") // acctBucketName is the bucket directly below the scope bucket in the // hierarchy. This bucket stores all the information and indexes // relevant to an account. acctBucketName = []byte("acct") // addrBucketName is the name of the bucket that stores a mapping of // pubkey hash to address type. This will be used to quickly determine // if a given address is under our control. 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 // // string => account_id 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 // // account_id => string acctIDIdxBucketName = []byte("acctididx") // usedAddrBucketName is the name of the bucket that stores an // addresses hash if the address has been used or not. usedAddrBucketName = []byte("usedaddrs") // 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 is the name of the bucket that stores the encrypted // crypto keys that encrypt all other generated keys, the watch only // flag, the master private key (encrypted), the master HD private key // (encrypted), and also versioning information. mainBucketName = []byte("main") // masterHDPrivName is the name of the key that stores the master HD // private key. This key is encrypted with the master private crypto // encryption key. This resides under the main bucket. masterHDPrivName = []byte("mhdpriv") // masterHDPubName is the name of the key that stores the master HD // public key. This key is encrypted with the master public crypto // encryption key. This reside under the main bucket. masterHDPubName = []byte("mhdpub") // syncBucketName is the name of the bucket that stores the current // sync state of the root manager. 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") watchingOnlyName = []byte("watchonly") // Sync related key names (sync bucket). syncedToName = []byte("syncedto") startBlockName = []byte("startblock") birthdayName = []byte("birthday") ) // 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 } // scopeKeySize is the size of a scope as stored within the database. const scopeKeySize = 8 // scopeToBytes transforms a manager's scope into the form that will be used to // retrieve the bucket that all information for a particular scope is stored // under func scopeToBytes(scope *KeyScope) [scopeKeySize]byte { var scopeBytes [scopeKeySize]byte binary.LittleEndian.PutUint32(scopeBytes[:], scope.Purpose) binary.LittleEndian.PutUint32(scopeBytes[4:], scope.Coin) return scopeBytes } // scopeFromBytes decodes a serializes manager scope into its concrete manager // scope struct. func scopeFromBytes(scopeBytes []byte) KeyScope { return KeyScope{ Purpose: binary.LittleEndian.Uint32(scopeBytes[:]), Coin: binary.LittleEndian.Uint32(scopeBytes[4:]), } } // scopeSchemaToBytes encodes the passed scope schema as a set of bytes // suitable for storage within the database. func scopeSchemaToBytes(schema *ScopeAddrSchema) []byte { var schemaBytes [2]byte schemaBytes[0] = byte(schema.InternalAddrType) schemaBytes[1] = byte(schema.ExternalAddrType) return schemaBytes[:] } // scopeSchemaFromBytes decodes a new scope schema instance from the set of // serialized bytes. func scopeSchemaFromBytes(schemaBytes []byte) *ScopeAddrSchema { return &ScopeAddrSchema{ InternalAddrType: AddressType(schemaBytes[0]), ExternalAddrType: AddressType(schemaBytes[1]), } } // fetchScopeAddrSchema will attempt to retrieve the address schema for a // particular manager scope stored within the database. These are used in order // to properly type each address generated by the scope address manager. func fetchScopeAddrSchema(ns walletdb.ReadBucket, scope *KeyScope) (*ScopeAddrSchema, error) { schemaBucket := ns.NestedReadBucket(scopeSchemaBucketName) if schemaBucket == nil { str := fmt.Sprintf("unable to find scope schema bucket") return nil, managerError(ErrScopeNotFound, str, nil) } scopeKey := scopeToBytes(scope) schemaBytes := schemaBucket.Get(scopeKey[:]) if schemaBytes == nil { str := fmt.Sprintf("unable to find scope %v", scope) return nil, managerError(ErrScopeNotFound, str, nil) } return scopeSchemaFromBytes(schemaBytes), nil } // putScopeAddrSchema attempts to store the passed addr scehma for the given // manager scope. func putScopeAddrTypes(ns walletdb.ReadWriteBucket, scope *KeyScope, schema *ScopeAddrSchema) error { scopeSchemaBucket := ns.NestedReadWriteBucket(scopeSchemaBucketName) if scopeSchemaBucket == nil { str := fmt.Sprintf("unable to find scope schema bucket") return managerError(ErrScopeNotFound, str, nil) } scopeKey := scopeToBytes(scope) schemaBytes := scopeSchemaToBytes(schema) return scopeSchemaBucket.Put(scopeKey[:], schemaBytes) } func fetchReadScopeBucket(ns walletdb.ReadBucket, scope *KeyScope) (walletdb.ReadBucket, error) { rootScopeBucket := ns.NestedReadBucket(scopeBucketName) scopeKey := scopeToBytes(scope) scopedBucket := rootScopeBucket.NestedReadBucket(scopeKey[:]) if scopedBucket == nil { str := fmt.Sprintf("unable to find scope %v", scope) return nil, managerError(ErrScopeNotFound, str, nil) } return scopedBucket, nil } func fetchWriteScopeBucket(ns walletdb.ReadWriteBucket, scope *KeyScope) (walletdb.ReadWriteBucket, error) { rootScopeBucket := ns.NestedReadWriteBucket(scopeBucketName) scopeKey := scopeToBytes(scope) scopedBucket := rootScopeBucket.NestedReadWriteBucket(scopeKey[:]) if scopedBucket == nil { str := fmt.Sprintf("unable to find scope %v", scope) return nil, managerError(ErrScopeNotFound, str, nil) } return scopedBucket, nil } // fetchManagerVersion fetches the current manager version from the database. func fetchManagerVersion(ns walletdb.ReadBucket) (uint32, error) { mainBucket := ns.NestedReadBucket(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(ns walletdb.ReadWriteBucket, version uint32) error { bucket := ns.NestedReadWriteBucket(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(ns walletdb.ReadBucket) ([]byte, []byte, error) { bucket := ns.NestedReadBucket(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(ns walletdb.ReadWriteBucket, pubParams, privParams []byte) error { bucket := ns.NestedReadWriteBucket(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. Each cointype key is // associated with a particular manager scoped. func fetchCoinTypeKeys(ns walletdb.ReadBucket, scope *KeyScope) ([]byte, []byte, error) { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return nil, nil, err } coinTypePubKeyEnc := scopedBucket.Get(coinTypePubKeyName) if coinTypePubKeyEnc == nil { str := "required encrypted cointype public key not stored in database" return nil, nil, managerError(ErrDatabase, str, nil) } coinTypePrivKeyEnc := scopedBucket.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. Each cointype key is // associated with a particular manager scope. func putCoinTypeKeys(ns walletdb.ReadWriteBucket, scope *KeyScope, coinTypePubKeyEnc []byte, coinTypePrivKeyEnc []byte) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } if coinTypePubKeyEnc != nil { err := scopedBucket.Put(coinTypePubKeyName, coinTypePubKeyEnc) if err != nil { str := "failed to store encrypted cointype public key" return managerError(ErrDatabase, str, err) } } if coinTypePrivKeyEnc != nil { err := scopedBucket.Put(coinTypePrivKeyName, coinTypePrivKeyEnc) if err != nil { str := "failed to store encrypted cointype private key" return managerError(ErrDatabase, str, err) } } return nil } // putMasterHDKeys stores the encrypted master HD keys in the top level main // bucket. These are required in order to create any new manager scopes, as // those are created via hardened derivation of the children of this key. func putMasterHDKeys(ns walletdb.ReadWriteBucket, masterHDPrivEnc, masterHDPubEnc []byte) error { // As this is the key for the root manager, we don't need to fetch any // particular scope, and can insert directly within the main bucket. bucket := ns.NestedReadWriteBucket(mainBucketName) // Now that we have the main bucket, we can directly store each of the // relevant keys. If we're in watch only mode, then some or all of // these keys might not be available. if masterHDPrivEnc != nil { err := bucket.Put(masterHDPrivName, masterHDPrivEnc) if err != nil { str := "failed to store encrypted master HD private key" return managerError(ErrDatabase, str, err) } } if masterHDPubEnc != nil { err := bucket.Put(masterHDPubName, masterHDPubEnc) if err != nil { str := "failed to store encrypted master HD public key" return managerError(ErrDatabase, str, err) } } return nil } // fetchMasterHDKeys attempts to fetch both the master HD private and public // keys from the database. If this is a watch only wallet, then it's possible // that the master private key isn't stored. func fetchMasterHDKeys(ns walletdb.ReadBucket) ([]byte, []byte, error) { bucket := ns.NestedReadBucket(mainBucketName) var masterHDPrivEnc, masterHDPubEnc []byte // First, we'll try to fetch the master private key. If this database // is watch only, or the master has been neutered, then this won't be // found on disk. key := bucket.Get(masterHDPrivName) if key != nil { masterHDPrivEnc = make([]byte, len(key)) copy(masterHDPrivEnc[:], key) } key = bucket.Get(masterHDPubName) if key != nil { masterHDPubEnc = make([]byte, len(key)) copy(masterHDPubEnc[:], key) } return masterHDPrivEnc, masterHDPubEnc, 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(ns walletdb.ReadBucket) ([]byte, []byte, []byte, error) { bucket := ns.NestedReadBucket(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(ns walletdb.ReadWriteBucket, pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error { bucket := ns.NestedReadWriteBucket(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(ns walletdb.ReadBucket) (bool, error) { bucket := ns.NestedReadBucket(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(ns walletdb.ReadWriteBucket, watchingOnly bool) error { bucket := ns.NestedReadWriteBucket(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 } // deserializeDefaultAccountRow deserializes the raw data from the passed // account row as a BIP0044-like account. func deserializeDefaultAccountRow(accountID []byte, row *dbAccountRow) (*dbDefaultAccountRow, 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 := dbDefaultAccountRow{ 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 } // serializeDefaultAccountRow returns the serialization of the raw data field // for a BIP0044-like account. func serializeDefaultAccountRow(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 } // forEachKeyScope calls the given function for each known manager scope // within the set of scopes known by the root manager. func forEachKeyScope(ns walletdb.ReadBucket, fn func(KeyScope) error) error { bucket := ns.NestedReadBucket(scopeBucketName) return bucket.ForEach(func(k, v []byte) error { // skip non-bucket if len(k) != 8 { return nil } scope := KeyScope{ Purpose: binary.LittleEndian.Uint32(k[:]), Coin: binary.LittleEndian.Uint32(k[4:]), } return fn(scope) }) } // forEachAccount calls the given function with each account stored in the // manager, breaking early on error. func forEachAccount(ns walletdb.ReadBucket, scope *KeyScope, fn func(account uint32) error) error { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return err } acctBucket := scopedBucket.NestedReadBucket(acctBucketName) return acctBucket.ForEach(func(k, v []byte) error { // Skip buckets. if v == nil { return nil } return fn(binary.LittleEndian.Uint32(k)) }) } // fetchLastAccount retrieves the last account from the database. func fetchLastAccount(ns walletdb.ReadBucket, scope *KeyScope) (uint32, error) { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return 0, err } metaBucket := scopedBucket.NestedReadBucket(metaBucketName) val := metaBucket.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 retrieves the account name given an account number from the // database. func fetchAccountName(ns walletdb.ReadBucket, scope *KeyScope, account uint32) (string, error) { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return "", err } acctIDxBucket := scopedBucket.NestedReadBucket(acctIDIdxBucketName) val := acctIDxBucket.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 retrieves the account number given an account name from // the database. func fetchAccountByName(ns walletdb.ReadBucket, scope *KeyScope, name string) (uint32, error) { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return 0, err } idxBucket := scopedBucket.NestedReadBucket(acctNameIdxBucketName) val := idxBucket.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(ns walletdb.ReadBucket, scope *KeyScope, account uint32) (interface{}, error) { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return nil, err } acctBucket := scopedBucket.NestedReadBucket(acctBucketName) accountID := uint32ToBytes(account) serializedRow := acctBucket.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 accountDefault: return deserializeDefaultAccountRow(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(ns walletdb.ReadWriteBucket, scope *KeyScope, name string) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(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(ns walletdb.ReadWriteBucket, scope *KeyScope, account uint32) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(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(ns walletdb.ReadWriteBucket, scope *KeyScope, account uint32, name string) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(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(ns walletdb.ReadWriteBucket, scope *KeyScope, account uint32, name string) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(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(ns walletdb.ReadWriteBucket, scope *KeyScope, account uint32, addrHash []byte) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(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(ns walletdb.ReadWriteBucket, scope *KeyScope, account uint32, row *dbAccountRow) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(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(ns walletdb.ReadWriteBucket, scope *KeyScope, account uint32, encryptedPubKey, encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32, name string) error { rawData := serializeDefaultAccountRow( encryptedPubKey, encryptedPrivKey, nextExternalIndex, nextInternalIndex, name, ) // TODO(roasbeef): pass scope bucket directly?? acctRow := dbAccountRow{ acctType: accountDefault, rawData: rawData, } if err := putAccountRow(ns, scope, account, &acctRow); err != nil { return err } // Update account id index. if err := putAccountIDIndex(ns, scope, account, name); err != nil { return err } // Update account name index. if err := putAccountNameIndex(ns, scope, account, name); err != nil { return err } return nil } // putLastAccount stores the provided metadata - last account - to the // database. func putLastAccount(ns walletdb.ReadWriteBucket, scope *KeyScope, account uint32) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(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 } // 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 } // 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(ns walletdb.ReadBucket, scope *KeyScope, addrHash []byte) (interface{}, error) { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return nil, err } bucket := scopedBucket.NestedReadBucket(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 } 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) } // fetchAddressUsed returns true if the provided address id was flagged as used. func fetchAddressUsed(ns walletdb.ReadBucket, scope *KeyScope, addressID []byte) bool { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return false } bucket := scopedBucket.NestedReadBucket(usedAddrBucketName) addrHash := sha256.Sum256(addressID) return bucket.Get(addrHash[:]) != nil } // markAddressUsed flags the provided address id as used in the database. func markAddressUsed(ns walletdb.ReadWriteBucket, scope *KeyScope, addressID []byte) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(usedAddrBucketName) addrHash := sha256.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(ns walletdb.ReadBucket, scope *KeyScope, addressID []byte) (interface{}, error) { addrHash := sha256.Sum256(addressID) return fetchAddressByHash(ns, scope, addrHash[:]) } // putAddress stores the provided address information to the database. This is // used a common base for storing the various address types. func putAddress(ns walletdb.ReadWriteBucket, scope *KeyScope, addressID []byte, row *dbAddressRow) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadWriteBucket(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 := sha256.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(ns, scope, row.account, addrHash[:]) } // putChainedAddress stores the provided chained address information to the // database. func putChainedAddress(ns walletdb.ReadWriteBucket, scope *KeyScope, addressID []byte, account uint32, status syncStatus, branch, index uint32, addrType addressType) error { scopedBucket, err := fetchWriteScopeBucket(ns, scope) if err != nil { return err } addrRow := dbAddressRow{ addrType: addrType, account: account, addTime: uint64(time.Now().Unix()), syncStatus: status, rawData: serializeChainedAddress(branch, index), } if err := putAddress(ns, scope, addressID, &addrRow); err != nil { return err } // Update the next index for the appropriate internal or external // branch. accountID := uint32ToBytes(account) bucket := scopedBucket.NestedReadWriteBucket(acctBucketName) serializedAccount := bucket.Get(accountID) // Deserialize the account row. row, err := deserializeAccountRow(accountID, serializedAccount) if err != nil { return err } arow, err := deserializeDefaultAccountRow(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 = serializeDefaultAccountRow( 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(ns walletdb.ReadWriteBucket, scope *KeyScope, 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(ns, scope, addressID, &addrRow) } // putScriptAddress stores the provided script address information to the // database. func putScriptAddress(ns walletdb.ReadWriteBucket, scope *KeyScope, 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(ns, scope, addressID, &addrRow); err != nil { return err } return nil } // existsAddress returns whether or not the address id exists in the database. func existsAddress(ns walletdb.ReadBucket, scope *KeyScope, addressID []byte) bool { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return false } bucket := scopedBucket.NestedReadBucket(addrBucketName) addrHash := sha256.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(ns walletdb.ReadBucket, scope *KeyScope, addressID []byte) (uint32, error) { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return 0, err } bucket := scopedBucket.NestedReadBucket(addrAcctIdxBucketName) addrHash := sha256.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 } // forEachAccountAddress calls the given function with each address of the // given account stored in the manager, breaking early on error. func forEachAccountAddress(ns walletdb.ReadBucket, scope *KeyScope, account uint32, fn func(rowInterface interface{}) error) error { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadBucket(addrAcctIdxBucketName). NestedReadBucket(uint32ToBytes(account)) // If index bucket is missing the account, there hasn't been any // address entries yet if bucket == nil { return nil } err = bucket.ForEach(func(k, v []byte) error { // Skip buckets. if v == nil { return nil } addrRow, err := fetchAddressByHash(ns, scope, 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 } return fn(addrRow) }) if err != nil { return maybeConvertDbError(err) } return nil } // forEachActiveAddress calls the given function with each active address // stored in the manager, breaking early on error. func forEachActiveAddress(ns walletdb.ReadBucket, scope *KeyScope, fn func(rowInterface interface{}) error) error { scopedBucket, err := fetchReadScopeBucket(ns, scope) if err != nil { return err } bucket := scopedBucket.NestedReadBucket(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. addrRow, err := fetchAddressByHash(ns, scope, 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 } return fn(addrRow) }) if err != nil { return maybeConvertDbError(err) } return 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(ns walletdb.ReadWriteBucket) error { bucket := ns.NestedReadWriteBucket(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(masterHDPrivName); err != nil { str := "failed to delete master HD priv key" return managerError(ErrDatabase, str, err) } // With the master key and meta encryption keys deleted, we'll need to // delete the keys for all known scopes as well. scopeBucket := ns.NestedReadWriteBucket(scopeBucketName) err := scopeBucket.ForEach(func(scopeKey, _ []byte) error { if len(scopeKey) != 8 { return nil } managerScopeBucket := scopeBucket.NestedReadWriteBucket(scopeKey) if err := managerScopeBucket.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 = managerScopeBucket.NestedReadWriteBucket(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 accountDefault: arow, err := deserializeDefaultAccountRow(k, row) if err != nil { return err } // Reserialize the account without the private key and // store it. row.rawData = serializeDefaultAccountRow( 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 = managerScopeBucket.NestedReadWriteBucket(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 }) if err != nil { return maybeConvertDbError(err) } return nil } // fetchSyncedTo loads the block stamp the manager is synced to from the // database. func fetchSyncedTo(ns walletdb.ReadBucket) (*BlockStamp, error) { bucket := ns.NestedReadBucket(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]) if len(buf) == 40 { bs.Timestamp = time.Unix( int64(binary.LittleEndian.Uint32(buf[36:])), 0, ) } return &bs, nil } // putSyncedTo stores the provided synced to blockstamp to the database. func putSyncedTo(ns walletdb.ReadWriteBucket, bs *BlockStamp) error { bucket := ns.NestedReadWriteBucket(syncBucketName) errStr := fmt.Sprintf("failed to store sync information %v", bs.Hash) // If the block height is greater than zero, check that the previous // block height exists. This prevents reorg issues in the future. // We use BigEndian so that keys/values are added to the bucket in // order, making writes more efficient for some database backends. if bs.Height > 0 { if _, err := fetchBlockHash(ns, bs.Height-1); err != nil { return managerError(ErrDatabase, errStr, err) } } // Store the block hash by block height. height := make([]byte, 4) binary.BigEndian.PutUint32(height, uint32(bs.Height)) err := bucket.Put(height, bs.Hash[0:32]) if err != nil { return managerError(ErrDatabase, errStr, err) } // The serialized synced to format is: // // // 4 bytes block height + 32 bytes hash length + 4 byte timestamp length buf := make([]byte, 40) binary.LittleEndian.PutUint32(buf[0:4], uint32(bs.Height)) copy(buf[4:36], bs.Hash[0:32]) binary.LittleEndian.PutUint32(buf[36:], uint32(bs.Timestamp.Unix())) err = bucket.Put(syncedToName, buf) if err != nil { return managerError(ErrDatabase, errStr, err) } return nil } // fetchBlockHash loads the block hash for the provided height from the // database. func fetchBlockHash(ns walletdb.ReadBucket, height int32) (*chainhash.Hash, error) { bucket := ns.NestedReadBucket(syncBucketName) errStr := fmt.Sprintf("failed to fetch block hash for height %d", height) heightBytes := make([]byte, 4) binary.BigEndian.PutUint32(heightBytes, uint32(height)) hashBytes := bucket.Get(heightBytes) if len(hashBytes) != 32 { err := fmt.Errorf("couldn't get hash from database") return nil, managerError(ErrDatabase, errStr, err) } var hash chainhash.Hash if err := hash.SetBytes(hashBytes); err != nil { return nil, managerError(ErrDatabase, errStr, err) } return &hash, nil } // fetchStartBlock loads the start block stamp for the manager from the // database. func fetchStartBlock(ns walletdb.ReadBucket) (*BlockStamp, error) { bucket := ns.NestedReadBucket(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(ns walletdb.ReadWriteBucket, bs *BlockStamp) error { bucket := ns.NestedReadWriteBucket(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 } // fetchBirthday loads the manager's bithday timestamp from the database. func fetchBirthday(ns walletdb.ReadBucket) (time.Time, error) { bucket := ns.NestedReadBucket(syncBucketName) var t time.Time buf := bucket.Get(birthdayName) if len(buf) != 8 { str := "malformed birthday stored in database" return t, managerError(ErrDatabase, str, nil) } t = time.Unix(int64(binary.BigEndian.Uint64(buf)), 0) return t, nil } // putBirthday stores the provided birthday timestamp to the database. func putBirthday(ns walletdb.ReadWriteBucket, t time.Time) error { bucket := ns.NestedReadWriteBucket(syncBucketName) buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, uint64(t.Unix())) err := bucket.Put(birthdayName, buf) if err != nil { str := "failed to store birthday" 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(ns walletdb.ReadBucket) bool { if ns == nil { return false } mainBucket := ns.NestedReadBucket(mainBucketName) return mainBucket != nil } // createScopedManagerNS creates the namespace buckets for a new registered // manager scope within the top level bucket. All relevant sub-buckets that a // ScopedManager needs to perform its duties are also created. func createScopedManagerNS(ns walletdb.ReadWriteBucket, scope *KeyScope) error { // First, we'll create the scope bucket itself for this particular // scope. scopeKey := scopeToBytes(scope) scopeBucket, err := ns.CreateBucket(scopeKey[:]) if err != nil { str := "failed to create sync bucket" return managerError(ErrDatabase, str, err) } _, err = scopeBucket.CreateBucket(acctBucketName) if err != nil { str := "failed to create account bucket" return managerError(ErrDatabase, str, err) } _, err = scopeBucket.CreateBucket(addrBucketName) if err != nil { str := "failed to create address bucket" return managerError(ErrDatabase, str, err) } // usedAddrBucketName bucket was added after manager version 1 release _, err = scopeBucket.CreateBucket(usedAddrBucketName) if err != nil { str := "failed to create used addresses bucket" return managerError(ErrDatabase, str, err) } _, err = scopeBucket.CreateBucket(addrAcctIdxBucketName) if err != nil { str := "failed to create address index bucket" return managerError(ErrDatabase, str, err) } _, err = scopeBucket.CreateBucket(acctNameIdxBucketName) if err != nil { str := "failed to create an account name index bucket" return managerError(ErrDatabase, str, err) } _, err = scopeBucket.CreateBucket(acctIDIdxBucketName) if err != nil { str := "failed to create an account id index bucket" return managerError(ErrDatabase, str, err) } _, err = scopeBucket.CreateBucket(metaBucketName) if err != nil { str := "failed to create a meta bucket" return managerError(ErrDatabase, str, err) } return 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. In addition to creating the key space for // the root address manager, we'll also create internal scopes for all the // default manager scope types. func createManagerNS(ns walletdb.ReadWriteBucket, defaultScopes map[KeyScope]ScopeAddrSchema) error { // First, we'll create all the relevant buckets that stem off of the // main bucket. mainBucket, err := ns.CreateBucket(mainBucketName) if err != nil { str := "failed to create main bucket" return managerError(ErrDatabase, str, err) } _, err = ns.CreateBucket(syncBucketName) if err != nil { str := "failed to create sync bucket" return managerError(ErrDatabase, str, err) } // We'll also create the two top-level scope related buckets as // preparation for the operations below. scopeBucket, err := ns.CreateBucket(scopeBucketName) if err != nil { str := "failed to create scope bucket" return managerError(ErrDatabase, str, err) } scopeSchemas, err := ns.CreateBucket(scopeSchemaBucketName) if err != nil { str := "failed to create scope schema bucket" return managerError(ErrDatabase, str, err) } // Next, we'll create the namespace for each of the relevant default // manager scopes. for scope, scopeSchema := range defaultScopes { // Before we create the entire namespace of this scope, we'll // update the schema mapping to note what types of addresses it // prefers. scopeKey := scopeToBytes(&scope) schemaBytes := scopeSchemaToBytes(&scopeSchema) err := scopeSchemas.Put(scopeKey[:], schemaBytes) if err != nil { return err } err = createScopedManagerNS(scopeBucket, &scope) if err != nil { return err } err = putLastAccount(ns, &scope, DefaultAccountNum) if err != nil { return err } } if err := putManagerVersion(ns, 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 } // 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(ns walletdb.ReadWriteBucket) error { currentMgrVersion := uint32(2) _, err := ns.CreateBucketIfNotExists(usedAddrBucketName) if err != nil { str := "failed to create used addresses bucket" return managerError(ErrDatabase, str, err) } return putManagerVersion(ns, currentMgrVersion) } // upgradeManager upgrades the data in the provided manager namespace to newer // versions as neeeded. func upgradeManager(db walletdb.DB, namespaceKey []byte, pubPassPhrase []byte, chainParams *chaincfg.Params, cbs *OpenCallbacks) error { var version uint32 err := walletdb.View(db, func(tx walletdb.ReadTx) error { ns := tx.ReadBucket(namespaceKey) var err error version, err = fetchManagerVersion(ns) 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. err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(namespaceKey) return upgradeToVersion2(ns) }) if err != nil { return err } // The manager is now at version 2. version = 2 } if version < 3 { if cbs == nil || cbs.ObtainSeed == nil || cbs.ObtainPrivatePass == nil { str := "failed to obtain seed and private passphrase required for upgrade" return managerError(ErrDatabase, str, err) } seed, err := cbs.ObtainSeed() if err != nil { return err } privPassPhrase, err := cbs.ObtainPrivatePass() if err != nil { return err } err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(namespaceKey) return upgradeToVersion3(ns, seed, privPassPhrase, pubPassPhrase, chainParams) }) if err != nil { return err } // The manager is now at version 3. version = 3 } if version < 4 { err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(namespaceKey) return upgradeToVersion4(ns, pubPassPhrase) }) if err != nil { return err } // The manager is now at version 4. version = 4 } // 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(ns walletdb.ReadWriteBucket, seed, privPassPhrase, pubPassPhrase []byte, chainParams *chaincfg.Params) error { priorScope := &KeyScope{ Purpose: 44, Coin: chainParams.HDCoinType, } err := func() error { currentMgrVersion := uint32(3) woMgr, err := loadManager(ns, pubPassPhrase, chainParams) if err != nil { return err } defer woMgr.Close() err = woMgr.Unlock(ns, privPassPhrase) if err != nil { return err } // Derive the master extended key from the seed. root, err := hdkeychain.NewMaster(seed, chainParams) 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, *priorScope, ) 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(ns, priorScope, coinTypePubEnc, coinTypePrivEnc) if err != nil { return err } _, err = ns.CreateBucketIfNotExists(acctNameIdxBucketName) if err != nil { str := "failed to create an account name index bucket" return managerError(ErrDatabase, str, err) } _, err = ns.CreateBucketIfNotExists(acctIDIdxBucketName) if err != nil { str := "failed to create an account id index bucket" return managerError(ErrDatabase, str, err) } _, err = ns.CreateBucketIfNotExists(metaBucketName) if err != nil { str := "failed to create a meta bucket" return managerError(ErrDatabase, str, err) } // Initialize metadata for all keys if err := putLastAccount(ns, priorScope, DefaultAccountNum); err != nil { return err } // Update default account indexes if err := putAccountIDIndex(ns, priorScope, DefaultAccountNum, defaultAccountName); err != nil { return err } if err := putAccountNameIndex(ns, priorScope, DefaultAccountNum, defaultAccountName); err != nil { return err } // Update imported account indexes if err := putAccountIDIndex(ns, priorScope, ImportedAddrAccount, ImportedAddrAccountName); err != nil { return err } if err := putAccountNameIndex(ns, priorScope, ImportedAddrAccount, ImportedAddrAccountName); err != nil { return err } // Write current manager version if err := putManagerVersion(ns, currentMgrVersion); err != nil { return err } // Save "" alias for default account name for backward compat return putAccountNameIndex(ns, priorScope, DefaultAccountNum, "") }() if err != nil { return maybeConvertDbError(err) } return nil } // upgradeToVersion4 upgrades the database from version 3 to version 4. The // default account remains unchanged (even if it was modified by the user), but // the empty string alias to the default account is removed. func upgradeToVersion4(ns walletdb.ReadWriteBucket, pubPassPhrase []byte) error { priorScope := &KeyScope{ Purpose: 44, Coin: 0, } err := func() error { // Write new manager version. err := putManagerVersion(ns, 4) if err != nil { return err } // Lookup the old account info to determine the real name of the // default account. All other names will be removed. acctInfoIface, err := fetchAccountInfo(ns, priorScope, DefaultAccountNum) if err != nil { return err } acctInfo, ok := acctInfoIface.(*dbDefaultAccountRow) if !ok { str := fmt.Sprintf("unsupported account type %T", acctInfoIface) return managerError(ErrDatabase, str, nil) } var oldName string // Delete any other names for the default account. c := ns.NestedReadWriteBucket(acctNameIdxBucketName).ReadWriteCursor() for k, v := c.First(); k != nil; k, v = c.Next() { // Skip nested buckets. if v == nil { continue } // Skip account names which aren't for the default account. account := binary.LittleEndian.Uint32(v) if account != DefaultAccountNum { continue } if !bytes.Equal(k[4:], []byte(acctInfo.name)) { err := c.Delete() if err != nil { const str = "error deleting default account alias" return managerError(ErrUpgrade, str, err) } oldName = string(k[4:]) break } } // The account number to name index may map to the wrong name, // so rewrite the entry with the true name from the account row // instead of leaving it set to an incorrect alias. err = putAccountIDIndex(ns, priorScope, DefaultAccountNum, acctInfo.name) if err != nil { const str = "account number to name index could not be " + "rewritten with actual account name" return managerError(ErrUpgrade, str, err) } // Ensure that the true name for the default account maps // forwards and backwards to the default account number. name, err := fetchAccountName(ns, priorScope, DefaultAccountNum) if err != nil { return err } if name != acctInfo.name { const str = "account name index does not map default account number to correct name" return managerError(ErrUpgrade, str, nil) } acct, err := fetchAccountByName(ns, priorScope, acctInfo.name) if err != nil { return err } if acct != DefaultAccountNum { const str = "default account not accessible under correct name" return managerError(ErrUpgrade, str, nil) } // Ensure that looking up the default account by the old name // cannot succeed. _, err = fetchAccountByName(ns, priorScope, oldName) if err == nil { const str = "default account exists under old name" return managerError(ErrUpgrade, str, nil) } merr, ok := err.(ManagerError) if !ok || merr.ErrorCode != ErrAccountNotFound { return err } return nil }() if err != nil { return maybeConvertDbError(err) } return nil }