lbcwallet/waddrmgr/db.go
Dave Collins d0938d817f Provide new wallet address manager package.
This commit implements a new secure, scalable, hierarchical deterministic
wallet address manager package.

The following is an overview of features:

- BIP0032 hierarchical deterministic keys
- BIP0043/BIP0044 multi-account hierarchy
- Strong focus on security:
  - Fully encrypted database including public information such as
    addresses as well as private information such as private keys and
    scripts needed to redeem pay-to-script-hash transactions
  - Hardened against memory scraping through the use of actively clearing
    private material from memory when locked
  - Different crypto keys used for public, private, and script data
  - Ability for different passphrases for public and private data
  - Scrypt-based key derivation
  - NaCl-based secretbox cryptography (XSalsa20 and Poly1305)
  - Multi-tier scalable key design to allow instant password changes
    regardless of the number of addresses stored
- Import WIF keys
- Import pay-to-script-hash scripts for things such as multi-signature
  transactions
- Ability to export a watching-only version which does not contain any
  private key material
- Programmatically detectable errors, including encapsulation of errors
  from packages it relies on
- Address synchronization capabilities

This commit only provides the implementation package.  It does not
include integration into to the existing wallet code base or conversion of
existing addresses.  That functionality will be provided by future
commits.
2014-10-13 16:19:09 -05:00

1356 lines
42 KiB
Go

/*
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
*
* 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 (
"bytes"
"encoding/binary"
"fmt"
"io"
"time"
"github.com/conformal/bolt"
"github.com/conformal/btcwire"
"github.com/conformal/fastsha256"
)
const (
// LatestDbVersion is the most recent database version.
LatestDbVersion = 1
)
// 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
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 (
// Bucket names.
acctBucketName = []byte("acct")
addrBucketName = []byte("addr")
addrAcctIdxBucketName = []byte("addracctidx")
mainBucketName = []byte("main")
syncBucketName = []byte("sync")
// Db related key names (main bucket).
dbVersionName = []byte("dbver")
dbCreateDateName = []byte("dbcreated")
// 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")
recentBlocksName = []byte("recentblocks")
// Account related key names (account bucket).
acctNumAcctsName = []byte("numaccts")
)
// managerTx represents a database transaction on which all database reads and
// writes occur. Note that fetched bytes are only valid during the bolt
// transaction, however they are safe to use after a manager transation has
// been terminated. This is why the code make copies of the data fetched from
// bolt buckets.
type managerTx bolt.Tx
// 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 (mtx *managerTx) FetchMasterKeyParams() ([]byte, []byte, error) {
bucket := (*bolt.Tx)(mtx).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 (mtx *managerTx) PutMasterKeyParams(pubParams, privParams []byte) error {
bucket := (*bolt.Tx)(mtx).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
}
// 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 (mtx *managerTx) FetchCryptoKeys() ([]byte, []byte, []byte, error) {
bucket := (*bolt.Tx)(mtx).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 (mtx *managerTx) PutCryptoKeys(pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error {
bucket := (*bolt.Tx)(mtx).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 (mtx *managerTx) FetchWatchingOnly() (bool, error) {
bucket := (*bolt.Tx)(mtx).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 (mtx *managerTx) PutWatchingOnly(watchingOnly bool) error {
bucket := (*bolt.Tx)(mtx).Bucket(mainBucketName)
var encoded byte
if watchingOnly {
encoded = 1
}
if err := bucket.Put(watchingOnlyName, []byte{encoded}); err != nil {
str := "failed to store wathcing only flag"
return managerError(ErrDatabase, str, err)
}
return nil
}
// accountKey returns the account key to use in the database for a given account
// number.
func accountKey(account uint32) []byte {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, account)
return buf
}
// 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:
// <acctType><rdlen><rawdata>
//
// 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:
// <acctType><rdlen><rawdata>
//
// 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:
// <encpubkeylen><encpubkey><encprivkeylen><encprivkey><nextextidx>
// <nextintidx><namelen><name>
//
// 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:
// <encpubkeylen><encpubkey><encprivkeylen><encprivkey><nextextidx>
// <nextintidx><namelen><name>
//
// 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
}
// FetchAccountInfo loads information about the passed account from the
// database.
func (mtx *managerTx) FetchAccountInfo(account uint32) (interface{}, error) {
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
accountID := accountKey(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)
}
// putAccountRow stores the provided account information to the database. This
// is used a common base for storing the various account types.
func (mtx *managerTx) putAccountRow(account uint32, row *dbAccountRow) error {
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
// Write the serialized value keyed by the account number.
err := bucket.Put(accountKey(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 (mtx *managerTx) PutAccountInfo(account uint32, encryptedPubKey,
encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32,
name string) error {
rawData := serializeBIP0044AccountRow(encryptedPubKey, encryptedPrivKey,
nextExternalIndex, nextInternalIndex, name)
acctRow := dbAccountRow{
acctType: actBIP0044,
rawData: rawData,
}
return mtx.putAccountRow(account, &acctRow)
}
// FetchNumAccounts loads the number of accounts that have been created from
// the database.
func (mtx *managerTx) FetchNumAccounts() (uint32, error) {
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
val := bucket.Get(acctNumAcctsName)
if val == nil {
str := "required num accounts not stored in database"
return 0, managerError(ErrDatabase, str, nil)
}
return binary.LittleEndian.Uint32(val), nil
}
// PutNumAccounts stores the number of accounts that have been created to the
// database.
func (mtx *managerTx) PutNumAccounts(numAccounts uint32) error {
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
var val [4]byte
binary.LittleEndian.PutUint32(val[:], numAccounts)
err := bucket.Put(acctNumAcctsName, val[:])
if err != nil {
str := "failed to store num accounts"
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(addressID, serializedAddress []byte) (*dbAddressRow, error) {
// The serialized address format is:
// <addrType><account><addedTime><syncStatus><rawdata>
//
// 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 := fmt.Sprintf("malformed serialized address for key %s",
addressID)
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:
// <addrType><account><addedTime><syncStatus><commentlen><comment>
// <rawdata>
//
// 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(addressID []byte, row *dbAddressRow) (*dbChainAddressRow, error) {
// The serialized chain address raw data format is:
// <branch><index>
//
// 4 bytes branch + 4 bytes address index
if len(row.rawData) != 8 {
str := fmt.Sprintf("malformed serialized chained address for "+
"key %s", addressID)
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:
// <branch><index>
//
// 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(addressID []byte, row *dbAddressRow) (*dbImportedAddressRow, error) {
// The serialized imported address raw data format is:
// <encpubkeylen><encpubkey><encprivkeylen><encprivkey>
//
// 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 := fmt.Sprintf("malformed serialized imported address for "+
"key %s", addressID)
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:
// <encpubkeylen><encpubkey><encprivkeylen><encprivkey>
//
// 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(addressID []byte, row *dbAddressRow) (*dbScriptAddressRow, error) {
// The serialized script address raw data format is:
// <encscripthashlen><encscripthash><encscriptlen><encscript>
//
// 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 := fmt.Sprintf("malformed serialized script address for "+
"key %s", addressID)
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:
// <encscripthashlen><encscripthash><encscriptlen><encscript>
//
// 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
}
// 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.
func (mtx *managerTx) FetchAddress(addressID []byte) (interface{}, error) {
bucket := (*bolt.Tx)(mtx).Bucket(addrBucketName)
addrHash := fastsha256.Sum256(addressID)
serializedRow := bucket.Get(addrHash[:])
if serializedRow == nil {
str := "address not found"
return nil, managerError(ErrAddressNotFound, str, nil)
}
row, err := deserializeAddressRow(addressID, serializedRow)
if err != nil {
return nil, err
}
switch row.addrType {
case adtChain:
return deserializeChainedAddress(addressID, row)
case adtImport:
return deserializeImportedAddress(addressID, row)
case adtScript:
return deserializeScriptAddress(addressID, row)
}
str := fmt.Sprintf("unsupported address type '%d'", row.addrType)
return nil, managerError(ErrDatabase, str, nil)
}
// putAddress stores the provided address information to the database. This
// is used a common base for storing the various address types.
func (mtx *managerTx) putAddress(addressID []byte, row *dbAddressRow) error {
bucket := (*bolt.Tx)(mtx).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)
}
return nil
}
// PutChainedAddress stores the provided chained address information to the
// database.
func (mtx *managerTx) PutChainedAddress(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 := mtx.putAddress(addressID, &addrRow); err != nil {
return err
}
// Update the next index for the appropriate internal or external
// branch.
accountID := accountKey(account)
bucket := (*bolt.Tx)(mtx).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 (mtx *managerTx) PutImportedAddress(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 mtx.putAddress(addressID, &addrRow)
}
// PutScriptAddress stores the provided script address information to the
// database.
func (mtx *managerTx) PutScriptAddress(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 := mtx.putAddress(addressID, &addrRow); err != nil {
return err
}
return nil
}
// ExistsAddress returns whether or not the address id exists in the database.
func (mtx *managerTx) ExistsAddress(addressID []byte) bool {
bucket := (*bolt.Tx)(mtx).Bucket(addrBucketName)
addrHash := fastsha256.Sum256(addressID)
return bucket.Get(addrHash[:]) != 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.
func (mtx *managerTx) FetchAllAddresses() ([]interface{}, error) {
bucket := (*bolt.Tx)(mtx).Bucket(addrBucketName)
var addrs []interface{}
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
// Skip buckets.
if v == nil {
continue
}
// Deserialize the address row first to determine the field
// values.
row, err := deserializeAddressRow(k, v)
if err != nil {
return nil, err
}
var addrRow interface{}
switch row.addrType {
case adtChain:
addrRow, err = deserializeChainedAddress(k, row)
case adtImport:
addrRow, err = deserializeImportedAddress(k, row)
case adtScript:
addrRow, err = deserializeScriptAddress(k, row)
default:
str := fmt.Sprintf("unsupported address type '%d'",
row.addrType)
return nil, managerError(ErrDatabase, str, nil)
}
if err != nil {
return nil, err
}
addrs = append(addrs, addrRow)
}
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 (mtx *managerTx) DeletePrivateKeys() error {
bucket := (*bolt.Tx)(mtx).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)
}
// Delete the account extended private key for all accounts.
bucket = (*bolt.Tx)(mtx).Bucket(acctBucketName)
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
// Skip buckets.
if v == nil || bytes.Equal(k, acctNumAcctsName) {
continue
}
// 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)
}
}
}
// Delete the private key for all imported addresses.
bucket = (*bolt.Tx)(mtx).Bucket(addrBucketName)
cursor = bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
// Skip buckets.
if v == nil {
continue
}
// Deserialize the address row first to determine the field
// values.
row, err := deserializeAddressRow(k, v)
if err != nil {
return err
}
switch row.addrType {
case adtImport:
irow, err := deserializeImportedAddress(k, 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(k, 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
}
// FetchSyncedTo loads the block stamp the manager is synced to from the
// database.
func (mtx *managerTx) FetchSyncedTo() (*BlockStamp, error) {
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
// The serialized synced to format is:
// <blockheight><blockhash>
//
// 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 (mtx *managerTx) PutSyncedTo(bs *BlockStamp) error {
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
// The serialized synced to format is:
// <blockheight><blockhash>
//
// 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 (mtx *managerTx) FetchStartBlock() (*BlockStamp, error) {
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
// The serialized start block format is:
// <blockheight><blockhash>
//
// 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 (mtx *managerTx) PutStartBlock(bs *BlockStamp) error {
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
// The serialized start block format is:
// <blockheight><blockhash>
//
// 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 (mtx *managerTx) FetchRecentBlocks() (int32, []btcwire.ShaHash, error) {
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
// The serialized recent blocks format is:
// <blockheight><numhashes><blockhashes>
//
// 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([]btcwire.ShaHash, numHashes)
offset := 8
for i := uint32(0); i < numHashes; i++ {
copy(recentHashes[i][:], buf[offset:offset+32])
offset += 32
}
return recentHeight, recentHashes, nil
}
// PutStartBlock stores the provided start block stamp to the database.
func (mtx *managerTx) PutRecentBlocks(recentHeight int32, recentHashes []btcwire.ShaHash) error {
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
// The serialized recent blocks format is:
// <blockheight><numhashes><blockhashes>
//
// 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
}
// managerDB provides transactional facilities to read and write the address
// manager data to a bolt database.
type managerDB struct {
db *bolt.DB
version uint32
created time.Time
}
// Close releases all database resources. All transactions must be closed
// before closing the database.
func (db *managerDB) Close() error {
if err := db.db.Close(); err != nil {
str := "failed to close database"
return managerError(ErrDatabase, str, err)
}
return nil
}
// View executes the passed function within the context of a managed read-only
// transaction. Any error that is returned from the passed function is returned
// from this function.
func (db *managerDB) View(fn func(tx *managerTx) error) error {
err := db.db.View(func(tx *bolt.Tx) error {
return fn((*managerTx)(tx))
})
if err != nil {
// Ensure the returned error is a ManagerError.
if _, ok := err.(ManagerError); !ok {
str := "failed during database read transaction"
return managerError(ErrDatabase, str, err)
}
return err
}
return nil
}
// Update executes the passed function within the context of a read-write
// managed transaction. The transaction is committed if no error is returned
// from the function. On the other hand, the entire transaction is rolled back
// if an error is returned. Any error that is returned from the passed function
// or returned from the commit is returned from this function.
func (db *managerDB) Update(fn func(tx *managerTx) error) error {
err := db.db.Update(func(tx *bolt.Tx) error {
return fn((*managerTx)(tx))
})
if err != nil {
// Ensure the returned error is a ManagerError.
if _, ok := err.(ManagerError); !ok {
str := "failed during database write transaction"
return managerError(ErrDatabase, str, err)
}
return err
}
return nil
}
// CopyDB copies the entire database to the provided new database path. A
// reader transaction is maintained during the copy so it is safe to continue
// using the database while a copy is in progress.
func (db *managerDB) CopyDB(newDbPath string) error {
err := db.db.View(func(tx *bolt.Tx) error {
if err := tx.CopyFile(newDbPath, 0600); err != nil {
str := "failed to copy database"
return managerError(ErrDatabase, str, err)
}
return nil
})
if err != nil {
// Ensure the returned error is a ManagerError.
if _, ok := err.(ManagerError); !ok {
str := "failed during database copy"
return managerError(ErrDatabase, str, err)
}
return err
}
return nil
}
// WriteTo writes the entire database to the provided writer. A reader
// transaction is maintained during the copy so it is safe to continue using the
// database while a copy is in progress.
func (db *managerDB) WriteTo(w io.Writer) error {
err := db.db.View(func(tx *bolt.Tx) error {
if err := tx.Copy(w); err != nil {
str := "failed to copy database"
return managerError(ErrDatabase, str, err)
}
return nil
})
if err != nil {
// Ensure the returned error is a ManagerError.
if _, ok := err.(ManagerError); !ok {
str := "failed during database copy"
return managerError(ErrDatabase, str, err)
}
return err
}
return nil
}
// openOrCreateDB opens the database at the provided path or creates and
// initializes it if it does not already exist. It also provides facilities to
// upgrade the database to newer versions.
func openOrCreateDB(dbPath string) (*managerDB, error) {
db, err := bolt.Open(dbPath, 0600, nil)
if err != nil {
str := "failed to open database"
return nil, managerError(ErrDatabase, str, err)
}
// Initialize the buckets and main db fields as needed.
var version uint32
var createDate uint64
err = db.Update(func(tx *bolt.Tx) error {
mainBucket, err := tx.CreateBucketIfNotExists(mainBucketName)
if err != nil {
str := "failed to create main bucket"
return managerError(ErrDatabase, str, err)
}
_, err = tx.CreateBucketIfNotExists(addrBucketName)
if err != nil {
str := "failed to create address bucket"
return managerError(ErrDatabase, str, err)
}
_, err = tx.CreateBucketIfNotExists(acctBucketName)
if err != nil {
str := "failed to create account bucket"
return managerError(ErrDatabase, str, err)
}
_, err = tx.CreateBucketIfNotExists(addrAcctIdxBucketName)
if err != nil {
str := "failed to create address index bucket"
return managerError(ErrDatabase, str, err)
}
_, err = tx.CreateBucketIfNotExists(syncBucketName)
if err != nil {
str := "failed to create sync bucket"
return managerError(ErrDatabase, str, err)
}
// Save the most recent database version if it isn't already
// there, otherwise keep track of it for potential upgrades.
verBytes := mainBucket.Get(dbVersionName)
if verBytes == nil {
version = LatestDbVersion
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], version)
err := mainBucket.Put(dbVersionName, buf[:])
if err != nil {
str := "failed to store latest database version"
return managerError(ErrDatabase, str, err)
}
} else {
version = binary.LittleEndian.Uint32(verBytes)
}
createBytes := mainBucket.Get(dbCreateDateName)
if createBytes == nil {
createDate = uint64(time.Now().Unix())
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], createDate)
err := mainBucket.Put(dbCreateDateName, buf[:])
if err != nil {
str := "failed to store database creation time"
return managerError(ErrDatabase, str, err)
}
} else {
createDate = binary.LittleEndian.Uint64(createBytes)
}
return nil
})
if err != nil {
str := "failed to update database"
return nil, managerError(ErrDatabase, str, err)
}
// Upgrade the database as needed.
if version < LatestDbVersion {
// No upgrades yet.
}
return &managerDB{
db: db,
version: version,
created: time.Unix(int64(createDate), 0),
}, nil
}