parent
3f6133e44b
commit
6ad3f8786e
9 changed files with 398 additions and 51 deletions
|
@ -130,7 +130,7 @@ func (a *Account) Lock() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock unlocks the underlying wallet for an account.
|
// Unlock unlocks the underlying wallet for an account.
|
||||||
func (a *Account) Unlock(passphrase []byte, timeout int64) error {
|
func (a *Account) Unlock(passphrase []byte) error {
|
||||||
a.mtx.Lock()
|
a.mtx.Lock()
|
||||||
defer a.mtx.Unlock()
|
defer a.mtx.Unlock()
|
||||||
|
|
||||||
|
|
|
@ -226,6 +226,75 @@ func (store *AccountStore) CreateEncryptedWallet(name, desc string, passphrase [
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChangePassphrase unlocks all account wallets with the old
|
||||||
|
// passphrase, and re-encrypts each using the new passphrase.
|
||||||
|
func (store *AccountStore) ChangePassphrase(old, new []byte) error {
|
||||||
|
store.RLock()
|
||||||
|
defer store.RUnlock()
|
||||||
|
|
||||||
|
// Check that each account can be unlocked with the old passphrase.
|
||||||
|
// Each's account's wallet mutex is unlocked with a defer so they
|
||||||
|
// will be held for the duration of this function. This prevents
|
||||||
|
// a wallet from being locked after some timeout after a RPC call
|
||||||
|
// to walletpassphrase.
|
||||||
|
for _, a := range store.accounts {
|
||||||
|
a.mtx.Lock()
|
||||||
|
defer a.mtx.Unlock()
|
||||||
|
|
||||||
|
if locked := a.Wallet.IsLocked(); !locked {
|
||||||
|
if err := a.Wallet.Lock(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Wallet.Unlock(old); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer a.Wallet.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change passphrase for each unlocked wallet.
|
||||||
|
for _, a := range store.accounts {
|
||||||
|
if err := a.Wallet.ChangePassphrase(new); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediately write out to disk. Create a new temporary network
|
||||||
|
// directory to write to, write all account files there, then move
|
||||||
|
// to the real network directory. This provides an safe
|
||||||
|
// replacement of all account files and ensures that all wallets
|
||||||
|
// are using either the old or new passphrase, but never two wallets
|
||||||
|
// with different passphrases.
|
||||||
|
netDir := networkDir(cfg.Net())
|
||||||
|
tmpNetDir := tmpNetworkDir(cfg.Net())
|
||||||
|
for _, a := range store.accounts {
|
||||||
|
// Writer locks must be held for the tx and utxo stores as well,
|
||||||
|
// to unset the dirty flag.
|
||||||
|
a.UtxoStore.Lock()
|
||||||
|
defer a.UtxoStore.Unlock()
|
||||||
|
a.TxStore.Lock()
|
||||||
|
defer a.TxStore.Unlock()
|
||||||
|
|
||||||
|
if err := a.writeAllToFreshDir(tmpNetDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is technically NOT an atomic operation, but at startup, if the
|
||||||
|
// network directory is missing but the temporary network directory
|
||||||
|
// exists, the temporary is moved before accounts are opened.
|
||||||
|
if err := os.RemoveAll(netDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := Rename(tmpNetDir, netDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DumpKeys returns all WIF-encoded private keys associated with all
|
// DumpKeys returns all WIF-encoded private keys associated with all
|
||||||
// accounts. All wallets must be unlocked for this operation to succeed.
|
// accounts. All wallets must be unlocked for this operation to succeed.
|
||||||
func (store *AccountStore) DumpKeys() ([]string, error) {
|
func (store *AccountStore) DumpKeys() ([]string, error) {
|
||||||
|
|
17
cmd.go
17
cmd.go
|
@ -248,6 +248,21 @@ func main() {
|
||||||
|
|
||||||
// OpenAccounts attempts to open all saved accounts.
|
// OpenAccounts attempts to open all saved accounts.
|
||||||
func OpenAccounts() {
|
func OpenAccounts() {
|
||||||
|
// If the network (account) directory is missing, but the temporary
|
||||||
|
// directory exists, move it. This is unlikely to happen, but possible,
|
||||||
|
// if writing out every account file at once to a tmp directory (as is
|
||||||
|
// done for changing a wallet passphrase) and btcwallet closes after
|
||||||
|
// removing the network directory but before renaming the temporary
|
||||||
|
// directory.
|
||||||
|
netDir := networkDir(cfg.Net())
|
||||||
|
tmpNetDir := tmpNetworkDir(cfg.Net())
|
||||||
|
if !fileExists(netDir) && fileExists(tmpNetDir) {
|
||||||
|
if err := Rename(tmpNetDir, netDir); err != nil {
|
||||||
|
log.Errorf("Cannot move temporary network dir: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The default account must exist, or btcwallet acts as if no
|
// The default account must exist, or btcwallet acts as if no
|
||||||
// wallets/accounts have been created yet.
|
// wallets/accounts have been created yet.
|
||||||
if err := accountstore.OpenAccount("", cfg); err != nil {
|
if err := accountstore.OpenAccount("", cfg); err != nil {
|
||||||
|
@ -264,7 +279,7 @@ func OpenAccounts() {
|
||||||
// Read all filenames in the account directory, and look for any
|
// Read all filenames in the account directory, and look for any
|
||||||
// filenames matching '*-wallet.bin'. These are wallets for
|
// filenames matching '*-wallet.bin'. These are wallets for
|
||||||
// additional saved accounts.
|
// additional saved accounts.
|
||||||
accountDir, err := os.Open(networkDir(cfg.Net()))
|
accountDir, err := os.Open(netDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Can't continue.
|
// Can't continue.
|
||||||
log.Errorf("Unable to open account directory: %v", err)
|
log.Errorf("Unable to open account directory: %v", err)
|
||||||
|
|
119
cmdmgr.go
119
cmdmgr.go
|
@ -32,51 +32,51 @@ type cmdHandler func(btcjson.Cmd) (interface{}, *btcjson.Error)
|
||||||
|
|
||||||
var rpcHandlers = map[string]cmdHandler{
|
var rpcHandlers = map[string]cmdHandler{
|
||||||
// Standard bitcoind methods (implemented)
|
// Standard bitcoind methods (implemented)
|
||||||
"dumpprivkey": DumpPrivKey,
|
"dumpprivkey": DumpPrivKey,
|
||||||
"getaccount": GetAccount,
|
"getaccount": GetAccount,
|
||||||
"getaccountaddress": GetAccountAddress,
|
"getaccountaddress": GetAccountAddress,
|
||||||
"getaddressesbyaccount": GetAddressesByAccount,
|
"getaddressesbyaccount": GetAddressesByAccount,
|
||||||
"getbalance": GetBalance,
|
"getbalance": GetBalance,
|
||||||
"getnewaddress": GetNewAddress,
|
"getnewaddress": GetNewAddress,
|
||||||
"importprivkey": ImportPrivKey,
|
"importprivkey": ImportPrivKey,
|
||||||
"keypoolrefill": KeypoolRefill,
|
"keypoolrefill": KeypoolRefill,
|
||||||
"listaccounts": ListAccounts,
|
"listaccounts": ListAccounts,
|
||||||
"listtransactions": ListTransactions,
|
"listtransactions": ListTransactions,
|
||||||
"sendfrom": SendFrom,
|
"sendfrom": SendFrom,
|
||||||
"sendmany": SendMany,
|
"sendmany": SendMany,
|
||||||
"settxfee": SetTxFee,
|
"settxfee": SetTxFee,
|
||||||
"walletlock": WalletLock,
|
"walletlock": WalletLock,
|
||||||
"walletpassphrase": WalletPassphrase,
|
"walletpassphrase": WalletPassphrase,
|
||||||
|
"walletpassphrasechange": WalletPassphraseChange,
|
||||||
|
|
||||||
// Standard bitcoind methods (currently unimplemented)
|
// Standard bitcoind methods (currently unimplemented)
|
||||||
"addmultisigaddress": Unimplemented,
|
"addmultisigaddress": Unimplemented,
|
||||||
"backupwallet": Unimplemented,
|
"backupwallet": Unimplemented,
|
||||||
"createmultisig": Unimplemented,
|
"createmultisig": Unimplemented,
|
||||||
"dumpwallet": Unimplemented,
|
"dumpwallet": Unimplemented,
|
||||||
"getblocktemplate": Unimplemented,
|
"getblocktemplate": Unimplemented,
|
||||||
"getrawchangeaddress": Unimplemented,
|
"getrawchangeaddress": Unimplemented,
|
||||||
"getreceivedbyaccount": Unimplemented,
|
"getreceivedbyaccount": Unimplemented,
|
||||||
"getreceivedbyaddress": Unimplemented,
|
"getreceivedbyaddress": Unimplemented,
|
||||||
"gettransaction": Unimplemented,
|
"gettransaction": Unimplemented,
|
||||||
"gettxout": Unimplemented,
|
"gettxout": Unimplemented,
|
||||||
"gettxoutsetinfo": Unimplemented,
|
"gettxoutsetinfo": Unimplemented,
|
||||||
"getwork": Unimplemented,
|
"getwork": Unimplemented,
|
||||||
"importwallet": Unimplemented,
|
"importwallet": Unimplemented,
|
||||||
"listaddressgroupings": Unimplemented,
|
"listaddressgroupings": Unimplemented,
|
||||||
"listlockunspent": Unimplemented,
|
"listlockunspent": Unimplemented,
|
||||||
"listreceivedbyaccount": Unimplemented,
|
"listreceivedbyaccount": Unimplemented,
|
||||||
"listsinceblock": Unimplemented,
|
"listsinceblock": Unimplemented,
|
||||||
"listreceivedbyaddress": Unimplemented,
|
"listreceivedbyaddress": Unimplemented,
|
||||||
"listunspent": Unimplemented,
|
"listunspent": Unimplemented,
|
||||||
"lockunspent": Unimplemented,
|
"lockunspent": Unimplemented,
|
||||||
"move": Unimplemented,
|
"move": Unimplemented,
|
||||||
"sendtoaddress": Unimplemented,
|
"sendtoaddress": Unimplemented,
|
||||||
"setaccount": Unimplemented,
|
"setaccount": Unimplemented,
|
||||||
"signmessage": Unimplemented,
|
"signmessage": Unimplemented,
|
||||||
"signrawtransaction": Unimplemented,
|
"signrawtransaction": Unimplemented,
|
||||||
"validateaddress": Unimplemented,
|
"validateaddress": Unimplemented,
|
||||||
"verifymessage": Unimplemented,
|
"verifymessage": Unimplemented,
|
||||||
"walletpassphrasechange": Unimplemented,
|
|
||||||
|
|
||||||
// Standard bitcoind methods which won't be implemented by btcwallet.
|
// Standard bitcoind methods which won't be implemented by btcwallet.
|
||||||
"encryptwallet": Unsupported,
|
"encryptwallet": Unsupported,
|
||||||
|
@ -1277,7 +1277,7 @@ func WalletPassphrase(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
||||||
return nil, &e
|
return nil, &e
|
||||||
}
|
}
|
||||||
|
|
||||||
switch err := a.Unlock([]byte(cmd.Passphrase), cmd.Timeout); err {
|
switch err := a.Unlock([]byte(cmd.Passphrase)); err {
|
||||||
case nil:
|
case nil:
|
||||||
go func(timeout int64) {
|
go func(timeout int64) {
|
||||||
time.Sleep(time.Second * time.Duration(timeout))
|
time.Sleep(time.Second * time.Duration(timeout))
|
||||||
|
@ -1293,6 +1293,37 @@ func WalletPassphrase(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WalletPassphraseChange responds to the walletpassphrasechange request
|
||||||
|
// by unlocking all accounts with the provided old passphrase, and
|
||||||
|
// re-encrypting each private key with an AES key derived from the new
|
||||||
|
// passphrase.
|
||||||
|
//
|
||||||
|
// If the old passphrase is correct and the passphrase is changed, all
|
||||||
|
// wallets will be immediately locked.
|
||||||
|
func WalletPassphraseChange(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
||||||
|
cmd, ok := icmd.(*btcjson.WalletPassphraseChangeCmd)
|
||||||
|
if !ok {
|
||||||
|
return nil, &btcjson.ErrInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
err := accountstore.ChangePassphrase([]byte(cmd.OldPassphrase),
|
||||||
|
[]byte(cmd.NewPassphrase))
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
case wallet.ErrWrongPassphrase:
|
||||||
|
return nil, &btcjson.ErrWalletPassphraseIncorrect
|
||||||
|
|
||||||
|
default: // all other non-nil errors
|
||||||
|
e := btcjson.Error{
|
||||||
|
Code: btcjson.ErrWallet.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
return nil, &e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AccountNtfn is a struct for marshalling any generic notification
|
// AccountNtfn is a struct for marshalling any generic notification
|
||||||
// about a account for a wallet frontend.
|
// about a account for a wallet frontend.
|
||||||
//
|
//
|
||||||
|
|
85
disksync.go
85
disksync.go
|
@ -36,8 +36,8 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// networkDir returns the base directory name for the bitcoin network
|
// networkDir returns the directory name of a network directory to hold account
|
||||||
// net.
|
// files.
|
||||||
func networkDir(net btcwire.BitcoinNet) string {
|
func networkDir(net btcwire.BitcoinNet) string {
|
||||||
var netname string
|
var netname string
|
||||||
if net == btcwire.MainNet {
|
if net == btcwire.MainNet {
|
||||||
|
@ -48,6 +48,11 @@ func networkDir(net btcwire.BitcoinNet) string {
|
||||||
return filepath.Join(cfg.DataDir, netname)
|
return filepath.Join(cfg.DataDir, netname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tmpNetworkDir returns the temporary directory name for a given network.
|
||||||
|
func tmpNetworkDir(net btcwire.BitcoinNet) string {
|
||||||
|
return networkDir(net) + "_tmp"
|
||||||
|
}
|
||||||
|
|
||||||
// checkCreateDir checks that the path exists and is a directory.
|
// checkCreateDir checks that the path exists and is a directory.
|
||||||
// If path does not exist, it is created.
|
// If path does not exist, it is created.
|
||||||
func checkCreateDir(path string) error {
|
func checkCreateDir(path string) error {
|
||||||
|
@ -109,6 +114,82 @@ func DirtyAccountSyncer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// freshDir creates a new directory specified by path if it does not
|
||||||
|
// exist. If the directory already exists, all files contained in the
|
||||||
|
// directory are removed.
|
||||||
|
func freshDir(path string) error {
|
||||||
|
if err := checkCreateDir(path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all files in the directory.
|
||||||
|
fd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
names, err := fd.Readdirnames(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, name := range names {
|
||||||
|
if err := os.RemoveAll(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeAllToFreshDir writes all account files to the specified directory.
|
||||||
|
// If dir already exists, any old files are removed. If dir does not
|
||||||
|
// exist, it is created.
|
||||||
|
//
|
||||||
|
// It is a runtime error to call this function while not holding each
|
||||||
|
// wallet, tx store, and utxo store writer lock.
|
||||||
|
func (a *Account) writeAllToFreshDir(dir string) error {
|
||||||
|
if err := freshDir(dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wfilepath := accountFilename("wallet.bin", a.name, dir)
|
||||||
|
txfilepath := accountFilename("tx.bin", a.name, dir)
|
||||||
|
utxofilepath := accountFilename("utxo.bin", a.name, dir)
|
||||||
|
|
||||||
|
wfile, err := os.Create(wfilepath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer wfile.Close()
|
||||||
|
txfile, err := os.Create(txfilepath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer txfile.Close()
|
||||||
|
utxofile, err := os.Create(utxofilepath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer utxofile.Close()
|
||||||
|
|
||||||
|
if _, err := a.Wallet.WriteTo(wfile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.dirty = false
|
||||||
|
|
||||||
|
if _, err := a.TxStore.s.WriteTo(txfile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.TxStore.dirty = false
|
||||||
|
|
||||||
|
if _, err := a.UtxoStore.s.WriteTo(utxofile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.UtxoStore.dirty = false
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// writeDirtyToDisk checks for the dirty flag on an account's wallet,
|
// writeDirtyToDisk checks for the dirty flag on an account's wallet,
|
||||||
// txstore, and utxostore, writing them to disk if any are dirty.
|
// txstore, and utxostore, writing them to disk if any are dirty.
|
||||||
func (a *Account) writeDirtyToDisk() error {
|
func (a *Account) writeDirtyToDisk() error {
|
||||||
|
|
1
tx/tx.go
1
tx/tx.go
|
@ -109,6 +109,7 @@ type Tx interface {
|
||||||
ReadFromVersion(uint32, io.Reader) (int64, error)
|
ReadFromVersion(uint32, io.Reader) (int64, error)
|
||||||
TxInfo(string, int32, btcwire.BitcoinNet) []map[string]interface{}
|
TxInfo(string, int32, btcwire.BitcoinNet) []map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxStore is a slice holding RecvTx and SendTx pointers.
|
// TxStore is a slice holding RecvTx and SendTx pointers.
|
||||||
type TxStore []Tx
|
type TxStore []Tx
|
||||||
|
|
||||||
|
|
16
updates.go
16
updates.go
|
@ -75,6 +75,22 @@ func updateOldFileLocations() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
acctsExist := false
|
||||||
|
for i := range fi {
|
||||||
|
// Ignore non-directories.
|
||||||
|
if !fi[i].IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(fi[i].Name(), "btcwallet") {
|
||||||
|
acctsExist = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !acctsExist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Create testnet directory, if it doesn't already exist.
|
// Create testnet directory, if it doesn't already exist.
|
||||||
netdir := filepath.Join(cfg.DataDir, "testnet")
|
netdir := filepath.Join(cfg.DataDir, "testnet")
|
||||||
if err := checkCreateDir(netdir); err != nil {
|
if err := checkCreateDir(netdir); err != nil {
|
||||||
|
|
|
@ -61,6 +61,7 @@ var (
|
||||||
ErrWalletDoesNotExist = errors.New("non-existant wallet")
|
ErrWalletDoesNotExist = errors.New("non-existant wallet")
|
||||||
ErrWalletIsWatchingOnly = errors.New("wallet is watching-only")
|
ErrWalletIsWatchingOnly = errors.New("wallet is watching-only")
|
||||||
ErrWalletLocked = errors.New("wallet is locked")
|
ErrWalletLocked = errors.New("wallet is locked")
|
||||||
|
ErrWrongPassphrase = errors.New("wrong passphrase")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -845,6 +846,37 @@ func (w *Wallet) Passphrase() ([]byte, error) {
|
||||||
return nil, ErrWalletLocked
|
return nil, ErrWalletLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChangePassphrase creates a new AES key from a new passphrase and
|
||||||
|
// re-encrypts all encrypted private keys with the new key.
|
||||||
|
func (w *Wallet) ChangePassphrase(new []byte) error {
|
||||||
|
if w.flags.watchingOnly {
|
||||||
|
return ErrWalletIsWatchingOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.secret) != 32 {
|
||||||
|
return ErrWalletLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
oldkey := w.secret
|
||||||
|
newkey := Key(new, &w.kdfParams)
|
||||||
|
|
||||||
|
for _, a := range w.addrMap {
|
||||||
|
if err := a.changeEncryptionKey(oldkey, newkey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero old secrets.
|
||||||
|
zero(w.passphrase)
|
||||||
|
zero(w.secret)
|
||||||
|
|
||||||
|
// Save new secrets.
|
||||||
|
w.passphrase = new
|
||||||
|
w.secret = newkey
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func zero(b []byte) {
|
func zero(b []byte) {
|
||||||
for i := range b {
|
for i := range b {
|
||||||
b[i] = 0
|
b[i] = 0
|
||||||
|
@ -2151,7 +2183,7 @@ func (a *btcAddress) unlock(key []byte) (privKeyCT []byte, err error) {
|
||||||
}
|
}
|
||||||
x, y := btcec.S256().ScalarBaseMult(privkey)
|
x, y := btcec.S256().ScalarBaseMult(privkey)
|
||||||
if x.Cmp(pubKey.X) != 0 || y.Cmp(pubKey.Y) != 0 {
|
if x.Cmp(pubKey.X) != 0 || y.Cmp(pubKey.Y) != 0 {
|
||||||
return nil, errors.New("decryption failed")
|
return nil, ErrWrongPassphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
privkeyCopy := make([]byte, 32)
|
privkeyCopy := make([]byte, 32)
|
||||||
|
@ -2160,9 +2192,36 @@ func (a *btcAddress) unlock(key []byte) (privKeyCT []byte, err error) {
|
||||||
return privkeyCopy, nil
|
return privkeyCopy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jrick)
|
// changeEncryptionKey re-encrypts the private keys for an address
|
||||||
|
// with a new AES encryption key. oldkey must be the old AES encryption key
|
||||||
|
// and is used to decrypt the private key.
|
||||||
func (a *btcAddress) changeEncryptionKey(oldkey, newkey []byte) error {
|
func (a *btcAddress) changeEncryptionKey(oldkey, newkey []byte) error {
|
||||||
return errors.New("unimplemented")
|
// Address must have a private key and be encrypted to continue.
|
||||||
|
if !a.flags.hasPrivKey {
|
||||||
|
return errors.New("no private key")
|
||||||
|
}
|
||||||
|
if !a.flags.encrypted {
|
||||||
|
return errors.New("address is not encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
privKeyCT, err := a.unlock(oldkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
aesBlockEncrypter, err := aes.NewCipher(newkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newIV := make([]byte, len(a.initVector))
|
||||||
|
if _, err := rand.Read(newIV); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy(a.initVector[:], newIV)
|
||||||
|
aesEncrypter := cipher.NewCFBEncrypter(aesBlockEncrypter, a.initVector[:])
|
||||||
|
aesEncrypter.XORKeyStream(a.privKey[:], privKeyCT)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// address returns a btcutil.AddressPubKeyHash for a btcAddress.
|
// address returns a btcutil.AddressPubKeyHash for a btcAddress.
|
||||||
|
|
|
@ -667,3 +667,78 @@ func TestWatchingWalletExport(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChangePassphrase(t *testing.T) {
|
||||||
|
const keypoolSize = 10
|
||||||
|
createdAt := &BlockStamp{}
|
||||||
|
w, err := NewWallet("banana wallet", "A wallet for testing.",
|
||||||
|
[]byte("banana"), btcwire.MainNet, createdAt, keypoolSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error creating new wallet: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changing the passphrase with a locked wallet must fail with ErrWalletLocked.
|
||||||
|
if err := w.ChangePassphrase([]byte("potato")); err != ErrWalletLocked {
|
||||||
|
t.Errorf("Changing passphrase on a locked wallet did not fail correctly: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock wallet so the passphrase can be changed.
|
||||||
|
if err := w.Unlock([]byte("banana")); err != nil {
|
||||||
|
t.Errorf("Cannot unlock: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get root address and its private key. This is compared to the private
|
||||||
|
// key post passphrase change.
|
||||||
|
rootAddr := w.LastChainedAddress()
|
||||||
|
rootPrivKey, err := w.AddressKey(rootAddr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot get root address' private key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change passphrase.
|
||||||
|
if err := w.ChangePassphrase([]byte("potato")); err != nil {
|
||||||
|
t.Errorf("Changing passhprase failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallet should still be unlocked.
|
||||||
|
if w.IsLocked() {
|
||||||
|
t.Errorf("Wallet should be unlocked after passphrase change.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock it.
|
||||||
|
if err := w.Lock(); err != nil {
|
||||||
|
t.Errorf("Cannot lock wallet after passphrase change: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock with old passphrase. This must fail with ErrWrongPassphrase.
|
||||||
|
if err := w.Unlock([]byte("banana")); err != ErrWrongPassphrase {
|
||||||
|
t.Errorf("Unlocking with old passphrases did not fail correctly: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock with new passphrase. This must succeed.
|
||||||
|
if err := w.Unlock([]byte("potato")); err != nil {
|
||||||
|
t.Errorf("Unlocking with new passphrase failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get root address' private key again.
|
||||||
|
rootPrivKey2, err := w.AddressKey(rootAddr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot get root address' private key after passphrase change: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private keys must match.
|
||||||
|
if !reflect.DeepEqual(rootPrivKey, rootPrivKey2) {
|
||||||
|
t.Errorf("Private keys before and after unlock differ.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue