Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: store unencrypted scripts in address mgr. #1500

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 40 additions & 26 deletions wallet/udb/addressdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ type dbImportedAddressRow struct {
// address in the database.
type dbScriptAddressRow struct {
dbAddressRow
encryptedHash []byte
encryptedScript []byte
hash []byte
script []byte
encrypted bool
}

// Key names for various database fields.
Expand Down Expand Up @@ -968,51 +969,66 @@ func serializeImportedAddress(encryptedPubKey, encryptedPrivKey []byte) []byte {
// row as a script address.
func deserializeScriptAddress(row *dbAddressRow) (*dbScriptAddressRow, error) {
// The serialized script address raw data format is:
// <encscripthashlen><encscripthash><encscriptlen><encscript>
// <scripthashlen><scripthash><scriptlen><script><encryptedbool>
//
// 4 bytes encrypted script hash len + encrypted script hash + 4 bytes
// encrypted script len + encrypted script
// 4 bytes script hash len + script hash + 4 bytes script len + script +
// 1 byte encrypted boolean

// Given the above, the length of the entry must be at a minimum
// the constant value sizes.
if len(row.rawData) < 8 {
return nil, errors.E(errors.IO, errors.Errorf("bad script address len %d", len(row.rawData)))
if len(row.rawData) < 9 {
return nil, errors.E(errors.IO,
errors.Errorf("bad script address len %d", len(row.rawData)))
}

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])
retRow.hash = make([]byte, hashLen)
copy(retRow.hash, 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])
retRow.script = make([]byte, scriptLen)
copy(retRow.script, row.rawData[offset:offset+scriptLen])
offset += scriptLen

encrypted := row.rawData[offset]
if encrypted == 1 {
retRow.encrypted = true
}

return &retRow, nil
}

// serializeScriptAddress returns the serialization of the raw data field for
// a script address.
func serializeScriptAddress(encryptedHash, encryptedScript []byte) []byte {
func serializeScriptAddress(hash, script []byte, encrypted bool) []byte {
// The serialized script address raw data format is:
// <encscripthashlen><encscripthash><encscriptlen><encscript>
// <scripthashlen><scripthash><scriptlen><script><encryptedbool>
//
// 4 bytes encrypted script hash len + encrypted script hash + 4 bytes
// encrypted script len + encrypted script
// 4 bytes script hash len + script hash + 4 bytes script len + script +
// 1 byte encrypted boolean

hashLen := uint32(len(encryptedHash))
scriptLen := uint32(len(encryptedScript))
rawData := make([]byte, 8+hashLen+scriptLen)
hashLen := uint32(len(hash))
scriptLen := uint32(len(script))
rawData := make([]byte, 8+hashLen+scriptLen+1)
binary.LittleEndian.PutUint32(rawData[0:4], hashLen)
copy(rawData[4:4+hashLen], encryptedHash)
copy(rawData[4:4+hashLen], hash)
offset := 4 + hashLen
binary.LittleEndian.PutUint32(rawData[offset:offset+4], scriptLen)
offset += 4
copy(rawData[offset:offset+scriptLen], encryptedScript)
copy(rawData[offset:offset+scriptLen], script)
offset += scriptLen

if encrypted {
rawData[offset] = byte(1)
} else {
rawData[offset] = byte(0)
}

return rawData
}

Expand Down Expand Up @@ -1111,9 +1127,9 @@ func putImportedAddress(ns walletdb.ReadWriteBucket, addressID []byte, account u
// putScriptAddress stores the provided script address information to the
// database.
func putScriptAddress(ns walletdb.ReadWriteBucket, addressID []byte, account uint32,
status syncStatus, encryptedHash, encryptedScript []byte) error {
status syncStatus, hash, script []byte, encrypted bool) error {

rawData := serializeScriptAddress(encryptedHash, encryptedScript)
rawData := serializeScriptAddress(hash, script, encrypted)
addrRow := dbAddressRow{
addrType: adtScript,
account: account,
Expand Down Expand Up @@ -1316,8 +1332,7 @@ func deletePrivateKeys(ns walletdb.ReadWriteBucket, dbVersion uint32) error {

// Reserialize the imported address without the private
// key and store it.
row.rawData = serializeImportedAddress(
irow.encryptedPubKey, nil)
row.rawData = serializeImportedAddress(irow.encryptedPubKey, nil)
err = bucket.Put([]byte(k), serializeAddressRow(row))
if err != nil {
return errors.E(errors.IO, err)
Expand All @@ -1332,8 +1347,7 @@ func deletePrivateKeys(ns walletdb.ReadWriteBucket, dbVersion uint32) error {

// Reserialize the script address without the script
// and store it.
row.rawData = serializeScriptAddress(srow.encryptedHash,
nil)
row.rawData = serializeScriptAddress(srow.hash, nil, srow.encrypted)
err = bucket.Put([]byte(k), serializeAddressRow(row))
if err != nil {
return errors.E(errors.IO, err)
Expand Down
56 changes: 22 additions & 34 deletions wallet/udb/addressmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,13 +855,17 @@ func (m *Manager) importedAddressRowToManaged(row *dbImportedAddressRow) (Manage
// scriptAddressRowToManaged returns a new managed address based on script
// address data loaded from the database.
func (m *Manager) scriptAddressRowToManaged(row *dbScriptAddressRow) (ManagedAddress, error) {
// Use the crypto public key to decrypt the imported script hash.
scriptHash, err := m.cryptoKeyPub.Decrypt(row.encryptedHash)
if err != nil {
return nil, errors.E(errors.Crypto, errors.Errorf("decrypt imported P2SH address: %v", err))
if row.encrypted {
// Use the crypto public key to decrypt the imported script hash.
scriptHash, err := m.cryptoKeyPub.Decrypt(row.hash)
if err != nil {
return nil, errors.E(errors.Crypto, errors.Errorf("decrypt imported P2SH address: %v", err))
}

return newScriptAddress(m, row.account, scriptHash)
}

return newScriptAddress(m, row.account, scriptHash)
return newScriptAddress(m, row.account, row.hash)
}

// rowInterfaceToManaged returns a new managed address based on the given
Expand Down Expand Up @@ -1245,32 +1249,10 @@ func (m *Manager) ImportScript(ns walletdb.ReadWriteBucket, script []byte) (Mana
return nil, errors.E(errors.Exist, "script already exists")
}

// The manager must be unlocked to encrypt the imported script.
if !m.watchingOnly && m.locked {
return nil, errors.E(errors.Locked)
}

// Encrypt the script hash using the crypto public key so it is
// accessible when the address manager is locked or watching-only.
encryptedHash, err := m.cryptoKeyPub.Encrypt(scriptHash)
if err != nil {
return nil, errors.E(errors.Crypto, errors.Errorf("encrypt script hash: %v", err))
}

// Encrypt the script for storage in database using the crypto script
// key when not a watching-only address manager.
var encryptedScript []byte
if !m.watchingOnly {
encryptedScript, err = m.cryptoKeyScript.Encrypt(script)
if err != nil {
return nil, errors.E(errors.Crypto, errors.Errorf("encrypt script: %v", err))
}
}

// Save the new imported address to the db and update start block (if
// needed) in a single transaction.
err = putScriptAddress(ns, scriptHash, ImportedAddrAccount,
ssNone, encryptedHash, encryptedScript)
err := putScriptAddress(ns, scriptHash, ImportedAddrAccount, ssNone,
scriptHash, script, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1994,16 +1976,22 @@ func (m *Manager) RedeemScript(ns walletdb.ReadBucket, addr dcrutil.Address) (sc
}
switch a := addrInterface.(type) {
case *dbScriptAddressRow:
script, err = m.cryptoKeyScript.Decrypt(a.encryptedScript)
if err != nil {
return nil, nil, errors.E(errors.Crypto, errors.Errorf("decrypt imported script: %v", err))
script = a.script
if a.encrypted {
script, err = m.cryptoKeyScript.Decrypt(a.script)
if err != nil {
return nil, nil, errors.E(errors.Crypto,
errors.Errorf("decrypt imported script: %v", err))
}
}

case *dbChainAddressRow, *dbImportedAddressRow:
return nil, nil, errors.E(errors.Invalid, "redeem script lookup requires P2SH address")
return nil, nil, errors.E(errors.Invalid,
"redeem script lookup requires P2SH address")

default:
return nil, nil, errors.E(errors.Invalid, errors.Errorf("address row type %T", addrInterface))
return nil, nil, errors.E(errors.Invalid,
errors.Errorf("address row type %T", addrInterface))
}

// Lock the RWMutex for reads for the caller and prepare to return the
Expand Down
116 changes: 116 additions & 0 deletions wallet/udb/testdata/v12.db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) 2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

// This file should compiled from the commit the file was introduced, otherwise
// it may not compile due to API changes, or may not create the database with
// the correct old version. This file should not be updated for API changes.

package main

import (
"compress/gzip"
"encoding/hex"
"fmt"
"io"
"os"

"github.com/decred/dcrd/chaincfg/v2"
"github.com/decred/dcrd/hdkeychain/v2"
_ "github.com/decred/dcrwallet/wallet/v3/drivers/bdb"
"github.com/decred/dcrwallet/wallet/v3/udb"
"github.com/decred/dcrwallet/wallet/v3/walletdb"
"github.com/decred/dcrwallet/walletseed"
)

const dbname = "v12.db"

var (
pubPass = []byte("public")
privPass = []byte("private")
)

func main() {
err := setup()
if err != nil {
fmt.Fprintf(os.Stderr, "setup: %v\n", err)
os.Exit(1)
}
err = compress()
if err != nil {
fmt.Fprintf(os.Stderr, "compress: %v\n", err)
os.Exit(1)
}
}

func hexToBytes(origHex string) []byte {
buf, err := hex.DecodeString(origHex)
if err != nil {
panic(err)
}
return buf
}

func setup() error {
db, err := walletdb.Create("bdb", dbname)
if err != nil {
return err
}
defer db.Close()
seed, err := walletseed.GenerateRandomSeed(hdkeychain.RecommendedSeedLen)
if err != nil {
return err
}

params := chaincfg.TestNet3Params()
err = udb.Initialize(db, params, seed, pubPass, privPass)
if err != nil {
return err
}

amgr, txStore, _, err := udb.Open(db, params, pubPass)
if err != nil {
return err
}

err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
amgrns := tx.ReadWriteBucket([]byte("waddrmgr"))
txmgrns := tx.ReadWriteBucket([]byte("wtxmgr"))

if err := amgr.Unlock(amgrns, privPass); err != nil {
return err
}

script := hexToBytes("51210373c717acda38b5aa4c00c33932e059cdbc" +
"11deceb5f00490a9101704cc444c5151ae")

_, err := amgr.ImportScript(amgrns, script)
if err != nil {
return err
}

return txStore.InsertTxScript(txmgrns, script)
})

return err
}

func compress() error {
db, err := os.Open(dbname)
if err != nil {
return err
}
defer os.Remove(dbname)
defer db.Close()
dbgz, err := os.Create(dbname + ".gz")
if err != nil {
return err
}
defer dbgz.Close()
gz := gzip.NewWriter(dbgz)
_, err = io.Copy(gz, db)
if err != nil {
return err
}
return gz.Close()
}
Binary file added wallet/udb/testdata/v12.db.gz
Binary file not shown.
Loading