2015-12-01 19:44:58 +01:00
|
|
|
// Copyright (c) 2014-2016 The btcsuite developers
|
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
package waddrmgr
|
|
|
|
|
|
|
|
import (
|
2021-12-15 01:54:55 +01:00
|
|
|
"container/list"
|
2015-01-30 22:36:19 +01:00
|
|
|
"crypto/rand"
|
|
|
|
"crypto/sha512"
|
2014-08-08 22:43:50 +02:00
|
|
|
"fmt"
|
|
|
|
"sync"
|
2017-09-19 23:53:38 +02:00
|
|
|
"time"
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2021-08-26 00:58:28 +02:00
|
|
|
"github.com/lbryio/lbcd/chaincfg"
|
|
|
|
btcutil "github.com/lbryio/lbcutil"
|
|
|
|
"github.com/lbryio/lbcutil/hdkeychain"
|
|
|
|
"github.com/lbryio/lbcwallet/internal/zero"
|
|
|
|
"github.com/lbryio/lbcwallet/snacl"
|
|
|
|
"github.com/lbryio/lbcwallet/walletdb"
|
2014-08-08 22:43:50 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// MaxAccountNum is the maximum allowed account number. This value was
|
2018-02-14 06:27:05 +01:00
|
|
|
// chosen because accounts are hardened children and therefore must not
|
|
|
|
// exceed the hardened child range of extended keys and it provides a
|
|
|
|
// reserved account at the top of the range for supporting imported
|
2014-08-08 22:43:50 +02:00
|
|
|
// addresses.
|
2022-08-04 06:51:37 +02:00
|
|
|
MaxAccountNum = hdkeychain.HardenedKeyStart - 3 // 2^31 - 3
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
// MaxAddressesPerAccount is the maximum allowed number of addresses
|
2018-02-14 06:27:05 +01:00
|
|
|
// per account number. This value is based on the limitation of the
|
|
|
|
// underlying hierarchical deterministic key derivation.
|
2014-08-08 22:43:50 +02:00
|
|
|
MaxAddressesPerAccount = hdkeychain.HardenedKeyStart - 1
|
|
|
|
|
2015-02-06 06:12:48 +01:00
|
|
|
// ImportedAddrAccount is the account number to use for all imported
|
2018-02-14 06:27:05 +01:00
|
|
|
// addresses. This is useful since normal accounts are derived from
|
|
|
|
// the root hierarchical deterministic key and imported addresses do
|
|
|
|
// not fit into that model.
|
2022-08-04 06:51:37 +02:00
|
|
|
ImportedAddrAccount = hdkeychain.HardenedKeyStart - 1 // 2^31 - 1
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2014-12-12 09:54:26 +01:00
|
|
|
// ImportedAddrAccountName is the name of the imported account.
|
|
|
|
ImportedAddrAccountName = "imported"
|
|
|
|
|
2022-09-17 05:10:47 +02:00
|
|
|
// AccountGapLimit is used for account discovery defined in BIP0044
|
|
|
|
AccountGapLimit = 20
|
|
|
|
|
2014-12-12 09:54:26 +01:00
|
|
|
// DefaultAccountNum is the number of the default account.
|
|
|
|
DefaultAccountNum = 0
|
|
|
|
|
2015-04-16 18:54:56 +02:00
|
|
|
// defaultAccountName is the initial name of the default account. Note
|
|
|
|
// that the default account may be renamed and is not a reserved name,
|
|
|
|
// so the default account might not be named "default" and non-default
|
|
|
|
// accounts may be named "default".
|
|
|
|
//
|
2018-02-14 06:27:05 +01:00
|
|
|
// Account numbers never change, so the DefaultAccountNum should be
|
|
|
|
// used to refer to (and only to) the default account.
|
2015-04-16 18:54:56 +02:00
|
|
|
defaultAccountName = "default"
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
// The hierarchy described by BIP0043 is:
|
|
|
|
// m/<purpose>'/*
|
|
|
|
// This is further extended by BIP0044 to:
|
|
|
|
// m/44'/<coin type>'/<account>'/<branch>/<address index>
|
|
|
|
//
|
|
|
|
// The branch is 0 for external addresses and 1 for internal addresses.
|
|
|
|
|
|
|
|
// maxCoinType is the maximum allowed coin type used when structuring
|
|
|
|
// the BIP0044 multi-account hierarchy. This value is based on the
|
|
|
|
// limitation of the underlying hierarchical deterministic key
|
|
|
|
// derivation.
|
|
|
|
maxCoinType = hdkeychain.HardenedKeyStart - 1
|
|
|
|
|
2018-03-21 02:00:28 +01:00
|
|
|
// ExternalBranch is the child number to use when performing BIP0044
|
2014-08-08 22:43:50 +02:00
|
|
|
// style hierarchical deterministic key derivation for the external
|
|
|
|
// branch.
|
2018-03-21 02:00:28 +01:00
|
|
|
ExternalBranch uint32 = 0
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-03-21 02:00:28 +01:00
|
|
|
// InternalBranch is the child number to use when performing BIP0044
|
2014-08-08 22:43:50 +02:00
|
|
|
// style hierarchical deterministic key derivation for the internal
|
|
|
|
// branch.
|
2018-03-21 02:00:28 +01:00
|
|
|
InternalBranch uint32 = 1
|
2015-01-30 22:36:19 +01:00
|
|
|
|
|
|
|
// saltSize is the number of bytes of the salt used when hashing
|
2022-09-29 00:16:15 +02:00
|
|
|
// passphrases.
|
2015-01-30 22:36:19 +01:00
|
|
|
saltSize = 32
|
2014-08-08 22:43:50 +02:00
|
|
|
)
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// isReservedAccountName returns true if the account name is reserved.
|
|
|
|
// Reserved accounts may never be renamed, and other accounts may not be
|
|
|
|
// renamed to a reserved name.
|
2015-04-16 18:54:56 +02:00
|
|
|
func isReservedAccountName(name string) bool {
|
|
|
|
return name == ImportedAddrAccountName
|
|
|
|
}
|
|
|
|
|
|
|
|
// isReservedAccountNum returns true if the account number is reserved.
|
|
|
|
// Reserved accounts may not be renamed.
|
|
|
|
func isReservedAccountNum(acct uint32) bool {
|
|
|
|
return acct == ImportedAddrAccount
|
|
|
|
}
|
2014-12-12 09:54:26 +01:00
|
|
|
|
2015-05-13 19:11:40 +02:00
|
|
|
// ScryptOptions is used to hold the scrypt parameters needed when deriving new
|
|
|
|
// passphrase keys.
|
|
|
|
type ScryptOptions struct {
|
|
|
|
N, R, P int
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenCallbacks houses caller-provided callbacks that may be called when
|
|
|
|
// opening an existing manager. The open blocks on the execution of these
|
|
|
|
// functions.
|
|
|
|
type OpenCallbacks struct {
|
2014-12-12 09:54:26 +01:00
|
|
|
// ObtainSeed is a callback function that is potentially invoked during
|
|
|
|
// upgrades. It is intended to be used to request the wallet seed
|
|
|
|
// from the user (or any other mechanism the caller deems fit).
|
|
|
|
ObtainSeed ObtainUserInputFunc
|
2018-02-14 06:27:05 +01:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// ObtainPassphrase is a callback function that is potentially invoked
|
2014-12-12 09:54:26 +01:00
|
|
|
// during upgrades. It is intended to be used to request the wallet
|
2022-09-29 00:16:15 +02:00
|
|
|
// passphrase from the user (or any other mechanism the caller deems
|
|
|
|
// fit).
|
|
|
|
ObtainPassphrase ObtainUserInputFunc
|
2014-10-29 08:27:38 +01:00
|
|
|
}
|
|
|
|
|
2016-03-25 20:11:25 +01:00
|
|
|
// DefaultScryptOptions is the default options used with scrypt.
|
2015-05-13 19:11:40 +02:00
|
|
|
var DefaultScryptOptions = ScryptOptions{
|
|
|
|
N: 262144, // 2^18
|
|
|
|
R: 8,
|
|
|
|
P: 1,
|
2014-10-29 08:27:38 +01:00
|
|
|
}
|
|
|
|
|
2020-01-17 11:39:38 +01:00
|
|
|
// FastScryptOptions are the scrypt options that should be used for testing
|
|
|
|
// purposes only where speed is more important than security.
|
|
|
|
var FastScryptOptions = ScryptOptions{
|
|
|
|
N: 16,
|
|
|
|
R: 8,
|
|
|
|
P: 1,
|
|
|
|
}
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// addrKey is used to uniquely identify an address even when those addresses
|
2018-02-14 06:27:05 +01:00
|
|
|
// would end up being the same bitcoin address (as is the case for
|
|
|
|
// pay-to-pubkey and pay-to-pubkey-hash style of addresses).
|
2014-08-08 22:43:50 +02:00
|
|
|
type addrKey string
|
|
|
|
|
|
|
|
// accountInfo houses the current state of the internal and external branches
|
|
|
|
// of an account along with the extended keys needed to derive new keys. It
|
|
|
|
// also handles locking by keeping an encrypted version of the serialized
|
|
|
|
// private extended key so the unencrypted versions can be cleared from memory
|
|
|
|
// when the address manager is locked.
|
|
|
|
type accountInfo struct {
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
acctName string
|
|
|
|
|
2021-03-11 22:50:09 +01:00
|
|
|
acctType accountType
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// The account key is used to derive the branches which in turn derive
|
2018-02-14 06:27:05 +01:00
|
|
|
// the internal and external addresses. The accountKeyPriv will be nil
|
|
|
|
// when the address manager is locked.
|
2014-08-08 22:43:50 +02:00
|
|
|
acctKeyEncrypted []byte
|
|
|
|
acctKeyPriv *hdkeychain.ExtendedKey
|
|
|
|
acctKeyPub *hdkeychain.ExtendedKey
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// The external branch is used for all addresses which are intended for
|
|
|
|
// external use.
|
2022-09-16 18:13:09 +02:00
|
|
|
nextIndex [2]uint32
|
|
|
|
lastAddr [2]ManagedAddress
|
2021-03-11 02:03:45 +01:00
|
|
|
|
|
|
|
// addrSchema serves as a way for an account to override its
|
2022-08-30 08:31:59 +02:00
|
|
|
// corresponding address schema with a custom one.
|
2021-03-11 02:03:45 +01:00
|
|
|
addrSchema *ScopeAddrSchema
|
2021-02-18 01:24:28 +01:00
|
|
|
|
|
|
|
// masterKeyFingerprint represents the fingerprint of the root key
|
|
|
|
// corresponding to the master public key (also known as the key with
|
|
|
|
// derivation path m/). This may be required by some hardware wallets
|
|
|
|
// for proper identification and signing.
|
|
|
|
masterKeyFingerprint uint32
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
// AccountProperties contains properties associated with each account, such as
|
|
|
|
// the account name, number, and the nubmer of derived and imported keys.
|
|
|
|
type AccountProperties struct {
|
2021-02-18 01:24:28 +01:00
|
|
|
// AccountNumber is the internal number used to reference the account.
|
|
|
|
AccountNumber uint32
|
|
|
|
|
|
|
|
// AccountName is the user-identifying name of the account.
|
|
|
|
AccountName string
|
|
|
|
|
|
|
|
// ExternalKeyCount is the number of internal keys that have been
|
|
|
|
// derived for the account.
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
ExternalKeyCount uint32
|
2021-02-18 01:24:28 +01:00
|
|
|
|
|
|
|
// InternalKeyCount is the number of internal keys that have been
|
|
|
|
// derived for the account.
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
InternalKeyCount uint32
|
2021-02-18 01:24:28 +01:00
|
|
|
|
|
|
|
// ImportedKeyCount is the number of imported keys found within the
|
|
|
|
// account.
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
ImportedKeyCount uint32
|
2021-02-18 01:24:28 +01:00
|
|
|
|
|
|
|
// AccountPubKey is the account's public key that can be used to
|
|
|
|
// derive any address relevant to said account.
|
|
|
|
//
|
|
|
|
// NOTE: This may be nil for imported accounts.
|
|
|
|
AccountPubKey *hdkeychain.ExtendedKey
|
|
|
|
|
|
|
|
// MasterKeyFingerprint represents the fingerprint of the root key
|
|
|
|
// corresponding to the master public key (also known as the key with
|
|
|
|
// derivation path m/). This may be required by some hardware wallets
|
|
|
|
// for proper identification and signing.
|
|
|
|
MasterKeyFingerprint uint32
|
|
|
|
|
|
|
|
// KeyScope is the key scope the account belongs to.
|
|
|
|
KeyScope KeyScope
|
|
|
|
|
|
|
|
// AddrSchema, if non-nil, specifies an address schema override for
|
|
|
|
// address generation only applicable to the account.
|
|
|
|
AddrSchema *ScopeAddrSchema
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
}
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// unlockDeriveInfo houses the information needed to derive a private key for a
|
2018-02-14 06:27:05 +01:00
|
|
|
// managed address when the address manager is unlocked. See the
|
|
|
|
// deriveOnUnlock field in the Manager struct for more details on how this is
|
|
|
|
// used.
|
2014-08-08 22:43:50 +02:00
|
|
|
type unlockDeriveInfo struct {
|
2016-04-25 21:30:38 +02:00
|
|
|
managedAddr ManagedAddress
|
2014-08-08 22:43:50 +02:00
|
|
|
branch uint32
|
|
|
|
index uint32
|
|
|
|
}
|
|
|
|
|
2018-09-01 01:27:04 +02:00
|
|
|
// SecretKeyGenerator is the function signature of a method that can generate
|
|
|
|
// secret keys for the address manager.
|
|
|
|
type SecretKeyGenerator func(
|
|
|
|
passphrase *[]byte, config *ScryptOptions) (*snacl.SecretKey, error)
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// defaultNewSecretKey returns a new secret key. See newSecretKey.
|
2018-09-01 01:27:04 +02:00
|
|
|
func defaultNewSecretKey(passphrase *[]byte,
|
|
|
|
config *ScryptOptions) (*snacl.SecretKey, error) {
|
2015-05-13 19:11:40 +02:00
|
|
|
return snacl.NewSecretKey(passphrase, config.N, config.R, config.P)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-09-01 01:27:04 +02:00
|
|
|
var (
|
|
|
|
// secretKeyGen is the inner method that is executed when calling
|
|
|
|
// newSecretKey.
|
|
|
|
secretKeyGen = defaultNewSecretKey
|
|
|
|
|
|
|
|
// secretKeyGenMtx protects access to secretKeyGen, so that it can be
|
|
|
|
// replaced in testing.
|
|
|
|
secretKeyGenMtx sync.RWMutex
|
|
|
|
)
|
|
|
|
|
|
|
|
// SetSecretKeyGen replaces the existing secret key generator, and returns the
|
|
|
|
// previous generator.
|
|
|
|
func SetSecretKeyGen(keyGen SecretKeyGenerator) SecretKeyGenerator {
|
|
|
|
secretKeyGenMtx.Lock()
|
|
|
|
oldKeyGen := secretKeyGen
|
|
|
|
secretKeyGen = keyGen
|
|
|
|
secretKeyGenMtx.Unlock()
|
|
|
|
|
|
|
|
return oldKeyGen
|
|
|
|
}
|
|
|
|
|
|
|
|
// newSecretKey generates a new secret key using the active secretKeyGen.
|
|
|
|
func newSecretKey(passphrase *[]byte,
|
|
|
|
config *ScryptOptions) (*snacl.SecretKey, error) {
|
|
|
|
|
|
|
|
secretKeyGenMtx.RLock()
|
|
|
|
defer secretKeyGenMtx.RUnlock()
|
|
|
|
return secretKeyGen(passphrase, config)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2014-10-29 07:43:29 +01:00
|
|
|
// EncryptorDecryptor provides an abstraction on top of snacl.CryptoKey so that
|
|
|
|
// our tests can use dependency injection to force the behaviour they need.
|
2014-09-11 20:19:46 +02:00
|
|
|
type EncryptorDecryptor interface {
|
|
|
|
Encrypt(in []byte) ([]byte, error)
|
|
|
|
Decrypt(in []byte) ([]byte, error)
|
|
|
|
Bytes() []byte
|
|
|
|
CopyBytes([]byte)
|
|
|
|
Zero()
|
|
|
|
}
|
|
|
|
|
2014-09-27 18:18:18 +02:00
|
|
|
// cryptoKey extends snacl.CryptoKey to implement EncryptorDecryptor.
|
|
|
|
type cryptoKey struct {
|
2014-09-11 20:19:46 +02:00
|
|
|
snacl.CryptoKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bytes returns a copy of this crypto key's byte slice.
|
2014-09-27 18:18:18 +02:00
|
|
|
func (ck *cryptoKey) Bytes() []byte {
|
2014-09-11 20:19:46 +02:00
|
|
|
return ck.CryptoKey[:]
|
|
|
|
}
|
|
|
|
|
|
|
|
// CopyBytes copies the bytes from the given slice into this CryptoKey.
|
2014-09-27 18:18:18 +02:00
|
|
|
func (ck *cryptoKey) CopyBytes(from []byte) {
|
2014-09-11 20:19:46 +02:00
|
|
|
copy(ck.CryptoKey[:], from)
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultNewCryptoKey returns a new CryptoKey. See newCryptoKey.
|
2014-09-27 18:18:18 +02:00
|
|
|
func defaultNewCryptoKey() (EncryptorDecryptor, error) {
|
2014-09-11 20:19:46 +02:00
|
|
|
key, err := snacl.GenerateCryptoKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-09-27 18:18:18 +02:00
|
|
|
return &cryptoKey{*key}, nil
|
2014-09-11 20:19:46 +02:00
|
|
|
}
|
|
|
|
|
2014-10-23 11:57:22 +02:00
|
|
|
// CryptoKeyType is used to differentiate between different kinds of
|
|
|
|
// crypto keys.
|
|
|
|
type CryptoKeyType byte
|
|
|
|
|
|
|
|
// Crypto key types.
|
|
|
|
const (
|
2014-10-31 16:09:44 +01:00
|
|
|
// CKTPrivate specifies the key that is used for encryption of private
|
|
|
|
// key material such as derived extended private keys and imported
|
|
|
|
// private keys.
|
2014-10-23 11:57:22 +02:00
|
|
|
CKTPrivate CryptoKeyType = iota
|
2014-10-31 16:09:44 +01:00
|
|
|
|
|
|
|
// CKTScript specifies the key that is used for encryption of scripts.
|
2014-10-23 11:57:22 +02:00
|
|
|
CKTScript
|
2014-10-31 16:09:44 +01:00
|
|
|
|
|
|
|
// CKTPublic specifies the key that is used for encryption of public
|
|
|
|
// key material such as dervied extended public keys and imported public
|
|
|
|
// keys.
|
2014-10-23 11:57:22 +02:00
|
|
|
CKTPublic
|
|
|
|
)
|
|
|
|
|
2014-09-27 18:18:18 +02:00
|
|
|
// newCryptoKey is used as a way to replace the new crypto key generation
|
2014-09-11 20:19:46 +02:00
|
|
|
// function used so tests can provide a version that fails for testing error
|
|
|
|
// paths.
|
|
|
|
var newCryptoKey = defaultNewCryptoKey
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// Manager represents a concurrency safe crypto currency address manager and
|
|
|
|
// key store.
|
|
|
|
type Manager struct {
|
|
|
|
mtx sync.RWMutex
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// scopedManager is a mapping of scope of scoped manager, the manager
|
|
|
|
// itself loaded into memory.
|
|
|
|
scopedManagers map[KeyScope]*ScopedKeyManager
|
|
|
|
|
|
|
|
externalAddrSchemas map[AddressType][]KeyScope
|
|
|
|
internalAddrSchemas map[AddressType][]KeyScope
|
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
syncState syncState
|
|
|
|
birthday time.Time
|
|
|
|
locked bool
|
|
|
|
closed bool
|
|
|
|
chainParams *chaincfg.Params
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
// masterKeyPub is the secret key used to secure the cryptoKeyPub key
|
|
|
|
// and masterKeyPriv is the secret key used to secure the cryptoKeyPriv
|
|
|
|
// key. This approach is used because it makes changing the passwords
|
|
|
|
// much simpler as it then becomes just changing these keys. It also
|
|
|
|
// provides future flexibility.
|
|
|
|
//
|
|
|
|
// NOTE: This is not the same thing as BIP0032 master node extended
|
|
|
|
// key.
|
|
|
|
//
|
|
|
|
// The underlying master private key will be zeroed when the address
|
|
|
|
// manager is locked.
|
|
|
|
masterKeyPub *snacl.SecretKey
|
|
|
|
masterKeyPriv *snacl.SecretKey
|
|
|
|
|
|
|
|
// cryptoKeyPub is the key used to encrypt public extended keys and
|
|
|
|
// addresses.
|
2014-09-11 20:19:46 +02:00
|
|
|
cryptoKeyPub EncryptorDecryptor
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
// cryptoKeyPriv is the key used to encrypt private data such as the
|
|
|
|
// master hierarchical deterministic extended key.
|
|
|
|
//
|
|
|
|
// This key will be zeroed when the address manager is locked.
|
|
|
|
cryptoKeyPrivEncrypted []byte
|
2014-09-11 20:19:46 +02:00
|
|
|
cryptoKeyPriv EncryptorDecryptor
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
// cryptoKeyScript is the key used to encrypt script data.
|
|
|
|
//
|
|
|
|
// This key will be zeroed when the address manager is locked.
|
|
|
|
cryptoKeyScriptEncrypted []byte
|
2014-09-11 20:19:46 +02:00
|
|
|
cryptoKeyScript EncryptorDecryptor
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// pssphraseSalt and hashedPassphrase allow for the secure detection
|
|
|
|
// of a correct passphrase on manager unlock when the manager is already
|
|
|
|
// unlocked. The hash is zeroed each lock.
|
|
|
|
passphraseSalt [saltSize]byte
|
|
|
|
hashedPassphrase [sha512.Size]byte
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// lock performs a best try effort to remove and zero all secret keys associated
|
|
|
|
// with the address manager.
|
|
|
|
//
|
|
|
|
// This function MUST be called with the manager lock held for writes.
|
|
|
|
func (m *Manager) lock() {
|
2018-02-14 06:27:05 +01:00
|
|
|
for _, manager := range m.scopedManagers {
|
|
|
|
// Clear all of the account private keys.
|
|
|
|
for _, acctInfo := range manager.acctInfo {
|
|
|
|
if acctInfo.acctKeyPriv != nil {
|
|
|
|
acctInfo.acctKeyPriv.Zero()
|
|
|
|
}
|
|
|
|
acctInfo.acctKeyPriv = nil
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove clear text private keys and scripts from all address entries.
|
2018-02-14 06:27:05 +01:00
|
|
|
for _, manager := range m.scopedManagers {
|
|
|
|
for _, ma := range manager.addrs {
|
|
|
|
switch addr := ma.(type) {
|
|
|
|
case *managedAddress:
|
|
|
|
addr.lock()
|
|
|
|
case *scriptAddress:
|
|
|
|
addr.lock()
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove clear text private master and crypto keys from memory.
|
|
|
|
m.cryptoKeyScript.Zero()
|
|
|
|
m.cryptoKeyPriv.Zero()
|
|
|
|
m.masterKeyPriv.Zero()
|
|
|
|
|
2015-01-30 22:36:19 +01:00
|
|
|
// Zero the hashed passphrase.
|
2022-09-29 00:16:15 +02:00
|
|
|
zero.Bytea64(&m.hashedPassphrase)
|
2015-01-30 22:36:19 +01:00
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// NOTE: m.cryptoKeyPub is intentionally not cleared here as the address
|
|
|
|
// manager needs to be able to continue to read and decrypt public data
|
|
|
|
// which uses a separate derived key from the database even when it is
|
|
|
|
// locked.
|
|
|
|
|
|
|
|
m.locked = true
|
|
|
|
}
|
|
|
|
|
2014-11-02 22:06:11 +01:00
|
|
|
// Close cleanly shuts down the manager. It makes a best try effort to remove
|
|
|
|
// and zero all private key and sensitive public key material associated with
|
|
|
|
// the address manager from memory.
|
2016-12-13 17:02:23 +01:00
|
|
|
func (m *Manager) Close() {
|
2014-08-08 22:43:50 +02:00
|
|
|
m.mtx.Lock()
|
|
|
|
defer m.mtx.Unlock()
|
|
|
|
|
2016-12-13 17:02:23 +01:00
|
|
|
if m.closed {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
for _, manager := range m.scopedManagers {
|
|
|
|
// Zero out the account keys (if any) of all sub key managers.
|
|
|
|
manager.Close()
|
|
|
|
}
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// Attempt to clear private key material from memory.
|
2022-09-20 08:54:03 +02:00
|
|
|
if !m.locked {
|
2014-08-08 22:43:50 +02:00
|
|
|
m.lock()
|
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Remove clear text public master and crypto keys from memory.
|
|
|
|
m.cryptoKeyPub.Zero()
|
|
|
|
m.masterKeyPub.Zero()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
m.closed = true
|
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// NewScopedKeyManager creates a new scoped key manager from the root manager. A
|
|
|
|
// scoped key manager is a sub-manager that only has the coin type key of a
|
|
|
|
// particular coin type and BIP0043 purpose. This is useful as it enables
|
|
|
|
// callers to create an arbitrary BIP0043 like schema with a stand alone
|
|
|
|
// manager. Note that a new scoped manager cannot be created if: the wallet is
|
|
|
|
// watch only, the manager hasn't been unlocked, or the root key has been.
|
|
|
|
// neutered from the database.
|
2014-08-08 22:43:50 +02:00
|
|
|
//
|
2018-02-14 06:27:05 +01:00
|
|
|
// TODO(roasbeef): addrtype of raw key means it'll look in scripts to possibly
|
|
|
|
// mark as gucci?
|
2020-04-25 02:44:21 +02:00
|
|
|
func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket,
|
|
|
|
scope KeyScope, addrSchema ScopeAddrSchema) (*ScopedKeyManager, error) {
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
m.mtx.Lock()
|
|
|
|
defer m.mtx.Unlock()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2020-04-25 02:44:21 +02:00
|
|
|
var rootPriv *hdkeychain.ExtendedKey
|
2022-09-20 08:54:03 +02:00
|
|
|
// If the manager is locked, then we can't create a new scoped
|
|
|
|
// manager.
|
|
|
|
if m.locked {
|
|
|
|
return nil, managerError(ErrLocked, errLocked, nil)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
// Now that we know the manager is unlocked, we'll need to
|
|
|
|
// fetch the root master HD private key. This is required as
|
|
|
|
// we'll be attempting the following derivation:
|
|
|
|
// m/purpose'/cointype'
|
|
|
|
//
|
|
|
|
// Note that the path to the coin type is requires hardened
|
|
|
|
// derivation, therefore this can only be done if the wallet's
|
|
|
|
// root key hasn't been neutered.
|
|
|
|
masterRootPrivEnc, _ := fetchMasterHDKeys(ns)
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
// If the master root private key isn't found within the
|
|
|
|
// database, but we need to bail here as we can't create the
|
|
|
|
// cointype key without the master root private key.
|
|
|
|
if masterRootPrivEnc == nil {
|
|
|
|
str := fmt.Sprintf("no master root private key found")
|
|
|
|
return nil, managerError(ErrKeyChain, str, nil)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
// Before we can derive any new scoped managers using this
|
|
|
|
// key, we'll need to fully decrypt it.
|
|
|
|
serializedMasterRootPriv, err :=
|
|
|
|
m.cryptoKeyPriv.Decrypt(masterRootPrivEnc)
|
|
|
|
if err != nil {
|
|
|
|
str := fmt.Sprintf("failed to decrypt master root " +
|
|
|
|
"serialized private key")
|
|
|
|
return nil, managerError(ErrLocked, str, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we know the root priv is within the database,
|
|
|
|
// we'll decode it into a usable object.
|
|
|
|
rootPriv, err = hdkeychain.NewKeyFromString(
|
|
|
|
string(serializedMasterRootPriv),
|
|
|
|
)
|
|
|
|
zero.Bytes(serializedMasterRootPriv)
|
|
|
|
if err != nil {
|
|
|
|
str := fmt.Sprintf("failed to create master extended " +
|
|
|
|
"private key")
|
|
|
|
return nil, managerError(ErrKeyChain, str, err)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Now that we have the root private key, we'll fetch the scope bucket
|
|
|
|
// so we can create the proper internal name spaces.
|
|
|
|
scopeBucket := ns.NestedReadWriteBucket(scopeBucketName)
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Now that we know it's possible to actually create a new scoped
|
|
|
|
// manager, we'll carve out its bucket space within the database.
|
|
|
|
if err := createScopedManagerNS(scopeBucket, &scope); err != nil {
|
|
|
|
return nil, err
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// With the database state created, we'll now write down the address
|
|
|
|
// schema of this particular scope type.
|
|
|
|
scopeSchemas := ns.NestedReadWriteBucket(scopeSchemaBucketName)
|
|
|
|
if scopeSchemas == nil {
|
|
|
|
str := "scope schema bucket not found"
|
|
|
|
return nil, managerError(ErrDatabase, str, nil)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
2018-02-14 06:27:05 +01:00
|
|
|
scopeKey := scopeToBytes(&scope)
|
|
|
|
schemaBytes := scopeSchemaToBytes(&addrSchema)
|
2022-09-20 08:54:03 +02:00
|
|
|
err = scopeSchemas.Put(scopeKey[:], schemaBytes)
|
2014-08-08 22:43:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
// With the database state created, we'll now derive the
|
|
|
|
// cointype key using the master HD private key, then encrypt
|
|
|
|
// it along with the first account using our crypto keys.
|
|
|
|
err = createManagerKeyScope(
|
|
|
|
ns, scope, rootPriv, m.cryptoKeyPub, m.cryptoKeyPriv,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
2018-02-14 06:27:05 +01:00
|
|
|
|
|
|
|
// Finally, we'll register this new scoped manager with the root
|
|
|
|
// manager.
|
|
|
|
m.scopedManagers[scope] = &ScopedKeyManager{
|
2021-08-14 01:13:06 +02:00
|
|
|
scope: scope,
|
|
|
|
addrSchema: addrSchema,
|
|
|
|
rootManager: m,
|
|
|
|
addrs: make(map[addrKey]ManagedAddress),
|
|
|
|
acctInfo: make(map[uint32]*accountInfo),
|
2021-12-15 01:54:55 +01:00
|
|
|
privKeyCache: map[DerivationPath]*list.Element{},
|
|
|
|
privKeyLru: list.New(),
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
2018-02-14 06:27:05 +01:00
|
|
|
m.externalAddrSchemas[addrSchema.ExternalAddrType] = append(
|
|
|
|
m.externalAddrSchemas[addrSchema.ExternalAddrType], scope,
|
|
|
|
)
|
|
|
|
m.internalAddrSchemas[addrSchema.InternalAddrType] = append(
|
|
|
|
m.internalAddrSchemas[addrSchema.InternalAddrType], scope,
|
|
|
|
)
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
return m.scopedManagers[scope], nil
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// FetchScopedKeyManager attempts to fetch an active scoped manager according to
|
|
|
|
// its registered scope. If the manger is found, then a nil error is returned
|
|
|
|
// along with the active scoped manager. Otherwise, a nil manager and a non-nil
|
|
|
|
// error will be returned.
|
|
|
|
func (m *Manager) FetchScopedKeyManager(scope KeyScope) (*ScopedKeyManager, error) {
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
m.mtx.RLock()
|
2018-02-14 06:27:05 +01:00
|
|
|
defer m.mtx.RUnlock()
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
sm, ok := m.scopedManagers[scope]
|
|
|
|
if !ok {
|
|
|
|
str := fmt.Sprintf("scope %v not found", scope)
|
|
|
|
return nil, managerError(ErrScopeNotFound, str, nil)
|
|
|
|
}
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
return sm, nil
|
|
|
|
}
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// ActiveScopedKeyManagers returns a slice of all the active scoped key
|
|
|
|
// managers currently known by the root key manager.
|
|
|
|
func (m *Manager) ActiveScopedKeyManagers() []*ScopedKeyManager {
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
|
|
|
|
|
|
|
var scopedManagers []*ScopedKeyManager
|
|
|
|
for _, smgr := range m.scopedManagers {
|
|
|
|
scopedManagers = append(scopedManagers, smgr)
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
return scopedManagers
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
}
|
|
|
|
|
2018-05-24 04:09:09 +02:00
|
|
|
// ScopesForExternalAddrType returns the set of key scopes that are able to
|
2018-02-14 06:27:05 +01:00
|
|
|
// produce the target address type as external addresses.
|
|
|
|
func (m *Manager) ScopesForExternalAddrType(addrType AddressType) []KeyScope {
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2021-03-24 14:43:24 +01:00
|
|
|
return m.externalAddrSchemas[addrType]
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// ScopesForInternalAddrTypes returns the set of key scopes that are able to
|
|
|
|
// produce the target address type as internal addresses.
|
|
|
|
func (m *Manager) ScopesForInternalAddrTypes(addrType AddressType) []KeyScope {
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2021-03-24 14:43:24 +01:00
|
|
|
return m.internalAddrSchemas[addrType]
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// NeuterRootKey is a special method that should be used once a caller is
|
|
|
|
// *certain* that no further scoped managers are to be created. This method
|
|
|
|
// will *delete* the encrypted master HD root private key from the database.
|
|
|
|
func (m *Manager) NeuterRootKey(ns walletdb.ReadWriteBucket) error {
|
|
|
|
m.mtx.Lock()
|
|
|
|
defer m.mtx.Unlock()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// First, we'll fetch the current master HD keys from the database.
|
2021-03-24 14:43:24 +01:00
|
|
|
masterRootPrivEnc, _ := fetchMasterHDKeys(ns)
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// If the root master private key is already nil, then we'll return a
|
|
|
|
// nil error here as the root key has already been permanently
|
|
|
|
// neutered.
|
|
|
|
if masterRootPrivEnc == nil {
|
|
|
|
return nil
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
2018-02-14 06:27:05 +01:00
|
|
|
zero.Bytes(masterRootPrivEnc)
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Otherwise, we'll neuter the root key permanently by deleting the
|
|
|
|
// encrypted master HD key from the database.
|
|
|
|
return ns.NestedReadWriteBucket(mainBucketName).Delete(masterHDPrivName)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Address returns a managed address given the passed address if it is known to
|
|
|
|
// the address manager. A managed address differs from the passed address in
|
|
|
|
// that it also potentially contains extra information needed to sign
|
|
|
|
// transactions such as the associated private key for pay-to-pubkey and
|
|
|
|
// pay-to-pubkey-hash addresses and the script associated with
|
|
|
|
// pay-to-script-hash addresses.
|
|
|
|
func (m *Manager) Address(ns walletdb.ReadBucket,
|
|
|
|
address btcutil.Address) (ManagedAddress, error) {
|
|
|
|
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
|
|
|
|
|
|
|
// We'll iterate through each of the known scoped managers, and see if
|
|
|
|
// any of them now of the target address.
|
|
|
|
for _, scopedMgr := range m.scopedManagers {
|
|
|
|
addr, err := scopedMgr.Address(ns, address)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return addr, nil
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// If the address wasn't known to any of the scoped managers, then
|
|
|
|
// we'll return an error.
|
|
|
|
str := fmt.Sprintf("unable to find key for addr %v", address)
|
|
|
|
return nil, managerError(ErrAddressNotFound, str, nil)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// MarkUsed updates the used flag for the provided address.
|
|
|
|
func (m *Manager) MarkUsed(ns walletdb.ReadWriteBucket, address btcutil.Address) error {
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
|
|
|
|
|
|
|
// Run through all the known scoped managers, and attempt to mark the
|
|
|
|
// address as used for each one.
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// First, we'll figure out which scoped manager this address belong to.
|
|
|
|
for _, scopedMgr := range m.scopedManagers {
|
|
|
|
if _, err := scopedMgr.Address(ns, address); err != nil {
|
|
|
|
continue
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// We've found the manager that this address belongs to, so we
|
|
|
|
// can mark the address as used and return.
|
|
|
|
return scopedMgr.MarkUsed(ns, address)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// If we get to this point, then we weren't able to find the address in
|
|
|
|
// any of the managers, so we'll exit with an error.
|
|
|
|
str := fmt.Sprintf("unable to find key for addr %v", address)
|
|
|
|
return managerError(ErrAddressNotFound, str, nil)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// AddrAccount returns the account to which the given address belongs. We also
|
|
|
|
// return the scoped manager that owns the addr+account combo.
|
|
|
|
func (m *Manager) AddrAccount(ns walletdb.ReadBucket,
|
|
|
|
address btcutil.Address) (*ScopedKeyManager, uint32, error) {
|
|
|
|
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
|
|
|
|
|
|
|
for _, scopedMgr := range m.scopedManagers {
|
|
|
|
if _, err := scopedMgr.Address(ns, address); err != nil {
|
|
|
|
continue
|
2014-12-12 09:54:26 +01:00
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// We've found the manager that this address belongs to, so we
|
|
|
|
// can retrieve the address' account along with the manager
|
|
|
|
// that the addr belongs to.
|
|
|
|
accNo, err := scopedMgr.AddrAccount(ns, address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return scopedMgr, accNo, err
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// If we get to this point, then we weren't able to find the address in
|
|
|
|
// any of the managers, so we'll exit with an error.
|
|
|
|
str := fmt.Sprintf("unable to find key for addr %v", address)
|
|
|
|
return nil, 0, managerError(ErrAddressNotFound, str, nil)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// ForEachActiveAccountAddress calls the given function with each active
|
|
|
|
// address of the given account stored in the manager, across all active
|
|
|
|
// scopes, breaking early on error.
|
|
|
|
//
|
|
|
|
// TODO(tuxcanfly): actually return only active addresses
|
|
|
|
func (m *Manager) ForEachActiveAccountAddress(ns walletdb.ReadBucket,
|
|
|
|
account uint32, fn func(maddr ManagedAddress) error) error {
|
2016-04-25 21:30:38 +02:00
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
m.mtx.RLock()
|
2018-02-14 06:27:05 +01:00
|
|
|
defer m.mtx.RUnlock()
|
|
|
|
|
|
|
|
for _, scopedMgr := range m.scopedManagers {
|
|
|
|
err := scopedMgr.ForEachActiveAccountAddress(ns, account, fn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForEachActiveAddress calls the given function with each active address
|
|
|
|
// stored in the manager, breaking early on error.
|
|
|
|
func (m *Manager) ForEachActiveAddress(ns walletdb.ReadBucket, fn func(addr btcutil.Address) error) error {
|
2018-02-20 03:31:29 +01:00
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
for _, scopedMgr := range m.scopedManagers {
|
|
|
|
err := scopedMgr.ForEachActiveAddress(ns, fn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2020-03-31 00:32:34 +02:00
|
|
|
// ForEachRelevantActiveAddress invokes the given closure on each active
|
|
|
|
// address relevant to the wallet. Ideally, only addresses within the default
|
|
|
|
// key scopes would be relevant, but due to a bug (now fixed) in which change
|
|
|
|
// addresses could be created outside of the default key scopes, we now need to
|
|
|
|
// check for those as well.
|
|
|
|
func (m *Manager) ForEachRelevantActiveAddress(ns walletdb.ReadBucket,
|
|
|
|
fn func(addr btcutil.Address) error) error {
|
|
|
|
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
|
|
|
|
|
|
|
for _, scopedMgr := range m.scopedManagers {
|
|
|
|
// If the manager is for a default key scope, we'll return all
|
|
|
|
// addresses, otherwise we'll only return internal addresses, as
|
|
|
|
// that's the branch used for change addresses.
|
|
|
|
isDefaultKeyScope := false
|
|
|
|
for _, defaultKeyScope := range DefaultKeyScopes {
|
|
|
|
if scopedMgr.Scope() == defaultKeyScope {
|
|
|
|
isDefaultKeyScope = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
if isDefaultKeyScope {
|
|
|
|
err = scopedMgr.ForEachActiveAddress(ns, fn)
|
|
|
|
} else {
|
|
|
|
err = scopedMgr.ForEachInternalActiveAddress(ns, fn)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// ForEachAccountAddress calls the given function with each address of
|
|
|
|
// the given account stored in the manager, breaking early on error.
|
|
|
|
func (m *Manager) ForEachAccountAddress(ns walletdb.ReadBucket, account uint32,
|
|
|
|
fn func(maddr ManagedAddress) error) error {
|
|
|
|
|
2018-02-20 03:31:29 +01:00
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
2018-02-14 06:27:05 +01:00
|
|
|
|
|
|
|
for _, scopedMgr := range m.scopedManagers {
|
|
|
|
err := scopedMgr.ForEachAccountAddress(ns, account, fn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-12-12 09:54:26 +01:00
|
|
|
}
|
2018-02-14 06:27:05 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChainParams returns the chain parameters for this address manager.
|
|
|
|
func (m *Manager) ChainParams() *chaincfg.Params {
|
|
|
|
// NOTE: No need for mutex here since the net field does not change
|
|
|
|
// after the manager instance is created.
|
|
|
|
|
|
|
|
return m.chainParams
|
2014-12-12 09:54:26 +01:00
|
|
|
}
|
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// ChangePassphrase changes passphrase to the provided value. The new
|
|
|
|
// passphrase keys are derived using the scrypt parameters in the options, so
|
|
|
|
// changing the passphrase may be used to bump the computational difficulty
|
|
|
|
// needed to brute force the passphrase.
|
2018-02-14 06:27:05 +01:00
|
|
|
func (m *Manager) ChangePassphrase(ns walletdb.ReadWriteBucket, oldPassphrase,
|
2022-09-29 00:16:15 +02:00
|
|
|
newPassphrase []byte, config *ScryptOptions) error {
|
2018-02-14 06:27:05 +01:00
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
m.mtx.Lock()
|
|
|
|
defer m.mtx.Unlock()
|
|
|
|
|
|
|
|
// Ensure the provided old passphrase is correct. This check is done
|
|
|
|
// using a copy of the appropriate master key depending on the private
|
|
|
|
// flag to ensure the current state is not altered. The temp key is
|
|
|
|
// cleared when done to avoid leaving a copy in memory.
|
|
|
|
var keyName string
|
|
|
|
secretKey := snacl.SecretKey{Key: &snacl.CryptoKey{}}
|
2022-09-29 00:16:15 +02:00
|
|
|
keyName = "private"
|
|
|
|
secretKey.Parameters = m.masterKeyPriv.Parameters
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
if err := secretKey.DeriveKey(&oldPassphrase); err != nil {
|
|
|
|
if err == snacl.ErrInvalidPassword {
|
|
|
|
str := fmt.Sprintf("invalid passphrase for %s master "+
|
|
|
|
"key", keyName)
|
|
|
|
return managerError(ErrWrongPassphrase, str, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
str := fmt.Sprintf("failed to derive %s master key", keyName)
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
defer secretKey.Zero()
|
|
|
|
|
|
|
|
// Generate a new master key from the passphrase which is used to secure
|
|
|
|
// the actual secret keys.
|
2015-05-13 19:11:40 +02:00
|
|
|
newMasterKey, err := newSecretKey(&newPassphrase, config)
|
2014-08-08 22:43:50 +02:00
|
|
|
if err != nil {
|
|
|
|
str := "failed to create new master private key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
newKeyParams := newMasterKey.Marshal()
|
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Technically, the locked state could be checked here to only
|
|
|
|
// do the decrypts when the address manager is locked as the
|
|
|
|
// clear text keys are already available in memory when it is
|
|
|
|
// unlocked, but this is not a hot path, decryption is quite
|
|
|
|
// fast, and it's less cyclomatic complexity to simply decrypt
|
|
|
|
// in either case.
|
|
|
|
|
|
|
|
// Create a new salt that will be used for hashing the new
|
|
|
|
// passphrase each unlock.
|
|
|
|
var passphraseSalt [saltSize]byte
|
|
|
|
_, err = rand.Read(passphraseSalt[:])
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to read random source for passhprase salt"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Re-encrypt the crypto private key using the new master
|
|
|
|
// private key.
|
|
|
|
decPriv, err := secretKey.Decrypt(m.cryptoKeyPrivEncrypted)
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to decrypt crypto private key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
encPriv, err := newMasterKey.Encrypt(decPriv)
|
|
|
|
zero.Bytes(decPriv)
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to encrypt crypto private key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Re-encrypt the crypto script key using the new master
|
|
|
|
// private key.
|
|
|
|
decScript, err := secretKey.Decrypt(m.cryptoKeyScriptEncrypted)
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to decrypt crypto script key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
encScript, err := newMasterKey.Encrypt(decScript)
|
|
|
|
zero.Bytes(decScript)
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to encrypt crypto script key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// When the manager is locked, ensure the new clear text master
|
|
|
|
// key is cleared from memory now that it is no longer needed.
|
|
|
|
// If unlocked, create the new passphrase hash with the new
|
|
|
|
// passphrase and salt.
|
|
|
|
var hashedPassphrase [sha512.Size]byte
|
|
|
|
if m.locked {
|
|
|
|
newMasterKey.Zero()
|
2014-08-08 22:43:50 +02:00
|
|
|
} else {
|
2022-09-29 00:16:15 +02:00
|
|
|
saltedPassphrase := append(passphraseSalt[:],
|
|
|
|
newPassphrase...)
|
|
|
|
hashedPassphrase = sha512.Sum512(saltedPassphrase)
|
|
|
|
zero.Bytes(saltedPassphrase)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Save the new keys and params to the db in a single
|
|
|
|
// transaction.
|
|
|
|
err = putCryptoKeys(ns, nil, encPriv, encScript)
|
|
|
|
if err != nil {
|
|
|
|
return maybeConvertDbError(err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
err = putMasterKeyParams(ns, nil, newKeyParams)
|
|
|
|
if err != nil {
|
|
|
|
return maybeConvertDbError(err)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Now that the db has been successfully updated, clear the old
|
|
|
|
// key and set the new one.
|
|
|
|
copy(m.cryptoKeyPrivEncrypted, encPriv)
|
|
|
|
copy(m.cryptoKeyScriptEncrypted, encScript)
|
|
|
|
m.masterKeyPriv.Zero() // Clear the old key.
|
|
|
|
m.masterKeyPriv = newMasterKey
|
|
|
|
m.passphraseSalt = passphraseSalt
|
|
|
|
m.hashedPassphrase = hashedPassphrase
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// IsLocked returns whether or not the address managed is locked. When it is
|
|
|
|
// unlocked, the decryption key needed to decrypt private keys used for signing
|
|
|
|
// is in memory.
|
|
|
|
func (m *Manager) IsLocked() bool {
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-03-08 08:18:35 +01:00
|
|
|
return m.isLocked()
|
|
|
|
}
|
|
|
|
|
|
|
|
// isLocked is an internal method returning whether or not the address manager
|
|
|
|
// is locked via an unprotected read.
|
|
|
|
//
|
|
|
|
// NOTE: The caller *MUST* acquire the Manager's mutex before invocation to
|
|
|
|
// avoid data races.
|
|
|
|
func (m *Manager) isLocked() bool {
|
2018-02-14 06:27:05 +01:00
|
|
|
return m.locked
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Lock performs a best try effort to remove and zero all secret keys associated
|
|
|
|
// with the address manager.
|
|
|
|
func (m *Manager) Lock() error {
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
m.mtx.Lock()
|
|
|
|
defer m.mtx.Unlock()
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Error on attempt to lock an already locked manager.
|
|
|
|
if m.locked {
|
|
|
|
return managerError(ErrLocked, errLocked, nil)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
m.lock()
|
|
|
|
return nil
|
|
|
|
}
|
2014-12-12 09:54:26 +01:00
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// Unlock derives the master private key from the specified passphrase. An
|
|
|
|
// invalid passphrase will return an error. Otherwise, the derived secret key
|
|
|
|
// is stored in memory until the address manager is locked. Any failures that
|
|
|
|
// occur during this function will result in the address manager being locked,
|
|
|
|
// even if it was already unlocked prior to calling this function.
|
2017-01-17 01:19:02 +01:00
|
|
|
func (m *Manager) Unlock(ns walletdb.ReadBucket, passphrase []byte) error {
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
m.mtx.Lock()
|
|
|
|
defer m.mtx.Unlock()
|
|
|
|
|
2015-01-30 22:36:19 +01:00
|
|
|
// Avoid actually unlocking if the manager is already unlocked
|
|
|
|
// and the passphrases match.
|
|
|
|
if !m.locked {
|
2022-09-29 00:16:15 +02:00
|
|
|
saltedPassphrase := append(m.passphraseSalt[:], passphrase...)
|
2015-01-30 22:36:19 +01:00
|
|
|
hashedPassphrase := sha512.Sum512(saltedPassphrase)
|
2015-02-04 02:42:40 +01:00
|
|
|
zero.Bytes(saltedPassphrase)
|
2022-09-29 00:16:15 +02:00
|
|
|
if hashedPassphrase != m.hashedPassphrase {
|
2015-01-30 22:36:19 +01:00
|
|
|
m.lock()
|
|
|
|
str := "invalid passphrase for master private key"
|
|
|
|
return managerError(ErrWrongPassphrase, str, nil)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// Derive the master private key using the provided passphrase.
|
|
|
|
if err := m.masterKeyPriv.DeriveKey(&passphrase); err != nil {
|
|
|
|
m.lock()
|
|
|
|
if err == snacl.ErrInvalidPassword {
|
|
|
|
str := "invalid passphrase for master private key"
|
|
|
|
return managerError(ErrWrongPassphrase, str, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
str := "failed to derive master private key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use the master private key to decrypt the crypto private key.
|
|
|
|
decryptedKey, err := m.masterKeyPriv.Decrypt(m.cryptoKeyPrivEncrypted)
|
|
|
|
if err != nil {
|
|
|
|
m.lock()
|
|
|
|
str := "failed to decrypt crypto private key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-09-11 20:19:46 +02:00
|
|
|
m.cryptoKeyPriv.CopyBytes(decryptedKey)
|
2015-02-04 02:42:40 +01:00
|
|
|
zero.Bytes(decryptedKey)
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
// Use the crypto private key to decrypt all of the account private
|
|
|
|
// extended keys.
|
2018-05-15 07:16:53 +02:00
|
|
|
for _, manager := range m.scopedManagers {
|
|
|
|
for account, acctInfo := range manager.acctInfo {
|
|
|
|
decrypted, err := m.cryptoKeyPriv.Decrypt(acctInfo.acctKeyEncrypted)
|
|
|
|
if err != nil {
|
|
|
|
m.lock()
|
|
|
|
str := fmt.Sprintf("failed to decrypt account %d "+
|
|
|
|
"private key", account)
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-05-15 07:16:53 +02:00
|
|
|
acctKeyPriv, err := hdkeychain.NewKeyFromString(string(decrypted))
|
|
|
|
zero.Bytes(decrypted)
|
|
|
|
if err != nil {
|
|
|
|
m.lock()
|
|
|
|
str := fmt.Sprintf("failed to regenerate account %d "+
|
|
|
|
"extended key", account)
|
|
|
|
return managerError(ErrKeyChain, str, err)
|
|
|
|
}
|
|
|
|
acctInfo.acctKeyPriv = acctKeyPriv
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-05-15 07:16:53 +02:00
|
|
|
// We'll also derive any private keys that are pending due to
|
|
|
|
// them being created while the address manager was locked.
|
|
|
|
for _, info := range manager.deriveOnUnlock {
|
2021-02-18 01:24:11 +01:00
|
|
|
addressKey, _, _, err := manager.deriveKeyFromPath(
|
2021-02-17 02:01:12 +01:00
|
|
|
ns, info.managedAddr.InternalAccount(),
|
|
|
|
info.branch, info.index, true,
|
2018-05-15 07:16:53 +02:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
m.lock()
|
|
|
|
return err
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-05-15 07:16:53 +02:00
|
|
|
// It's ok to ignore the error here since it can only
|
|
|
|
// fail if the extended key is not private, however it
|
|
|
|
// was just derived as a private key.
|
|
|
|
privKey, _ := addressKey.ECPrivKey()
|
|
|
|
addressKey.Zero()
|
|
|
|
|
|
|
|
privKeyBytes := privKey.Serialize()
|
|
|
|
privKeyEncrypted, err := m.cryptoKeyPriv.Encrypt(privKeyBytes)
|
|
|
|
zero.BigInt(privKey.D)
|
|
|
|
if err != nil {
|
|
|
|
m.lock()
|
|
|
|
str := fmt.Sprintf("failed to encrypt private key for "+
|
|
|
|
"address %s", info.managedAddr.Address())
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-05-15 07:16:53 +02:00
|
|
|
switch a := info.managedAddr.(type) {
|
|
|
|
case *managedAddress:
|
|
|
|
a.privKeyEncrypted = privKeyEncrypted
|
|
|
|
a.privKeyCT = privKeyBytes
|
|
|
|
case *scriptAddress:
|
|
|
|
}
|
2016-04-25 21:30:38 +02:00
|
|
|
|
2018-05-15 07:16:53 +02:00
|
|
|
// Avoid re-deriving this key on subsequent unlocks.
|
|
|
|
manager.deriveOnUnlock[0] = nil
|
|
|
|
manager.deriveOnUnlock = manager.deriveOnUnlock[1:]
|
2016-04-25 21:30:38 +02:00
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
m.locked = false
|
2022-09-29 00:16:15 +02:00
|
|
|
saltedPassphrase := append(m.passphraseSalt[:], passphrase...)
|
|
|
|
m.hashedPassphrase = sha512.Sum512(saltedPassphrase)
|
2015-02-04 02:42:40 +01:00
|
|
|
zero.Bytes(saltedPassphrase)
|
2018-02-14 06:27:05 +01:00
|
|
|
return nil
|
2014-12-12 09:54:26 +01:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// ValidateAccountName validates the given account name and returns an error, if any.
|
|
|
|
func ValidateAccountName(name string) error {
|
|
|
|
if name == "" {
|
|
|
|
str := "accounts may not be named the empty string"
|
|
|
|
return managerError(ErrInvalidAccount, str, nil)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
2018-02-14 06:27:05 +01:00
|
|
|
if isReservedAccountName(name) {
|
|
|
|
str := "reserved account name"
|
|
|
|
return managerError(ErrInvalidAccount, str, nil)
|
2015-03-26 19:22:59 +01:00
|
|
|
}
|
|
|
|
return nil
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 01:24:23 +01:00
|
|
|
// LookupAccount returns the corresponding key scope and account number for the
|
|
|
|
// account with the given name.
|
|
|
|
func (m *Manager) LookupAccount(ns walletdb.ReadBucket, name string) (KeyScope,
|
|
|
|
uint32, error) {
|
|
|
|
|
|
|
|
m.mtx.RLock()
|
|
|
|
defer m.mtx.RUnlock()
|
|
|
|
|
|
|
|
for keyScope, scopedMgr := range m.scopedManagers {
|
|
|
|
acct, err := scopedMgr.LookupAccount(ns, name)
|
|
|
|
if err == nil {
|
|
|
|
return keyScope, acct, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
str := fmt.Sprintf("account name '%s' not found", name)
|
|
|
|
return KeyScope{}, 0, managerError(ErrAccountNotFound, str, nil)
|
|
|
|
}
|
|
|
|
|
2014-10-31 16:09:44 +01:00
|
|
|
// selectCryptoKey selects the appropriate crypto key based on the key type. An
|
|
|
|
// error is returned when an invalid key type is specified or the requested key
|
|
|
|
// requires the manager to be unlocked when it isn't.
|
|
|
|
//
|
|
|
|
// This function MUST be called with the manager lock held for reads.
|
|
|
|
func (m *Manager) selectCryptoKey(keyType CryptoKeyType) (EncryptorDecryptor, error) {
|
|
|
|
if keyType == CKTPrivate || keyType == CKTScript {
|
2014-11-02 22:06:11 +01:00
|
|
|
// The manager must be unlocked to work with the private keys.
|
2022-09-20 08:54:03 +02:00
|
|
|
if m.locked {
|
2014-10-31 16:09:44 +01:00
|
|
|
return nil, managerError(ErrLocked, errLocked, nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var cryptoKey EncryptorDecryptor
|
|
|
|
switch keyType {
|
|
|
|
case CKTPrivate:
|
|
|
|
cryptoKey = m.cryptoKeyPriv
|
|
|
|
case CKTScript:
|
|
|
|
cryptoKey = m.cryptoKeyScript
|
|
|
|
case CKTPublic:
|
|
|
|
cryptoKey = m.cryptoKeyPub
|
|
|
|
default:
|
|
|
|
return nil, managerError(ErrInvalidKeyType, "invalid key type",
|
|
|
|
nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cryptoKey, nil
|
|
|
|
}
|
|
|
|
|
2014-10-23 11:57:22 +02:00
|
|
|
// Encrypt in using the crypto key type specified by keyType.
|
|
|
|
func (m *Manager) Encrypt(keyType CryptoKeyType, in []byte) ([]byte, error) {
|
|
|
|
// Encryption must be performed under the manager mutex since the
|
|
|
|
// keys are cleared when the manager is locked.
|
|
|
|
m.mtx.Lock()
|
|
|
|
defer m.mtx.Unlock()
|
|
|
|
|
|
|
|
cryptoKey, err := m.selectCryptoKey(keyType)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encrypted, err := cryptoKey.Encrypt(in)
|
|
|
|
if err != nil {
|
|
|
|
return nil, managerError(ErrCrypto, "failed to encrypt", err)
|
|
|
|
}
|
|
|
|
return encrypted, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt in using the crypto key type specified by keyType.
|
|
|
|
func (m *Manager) Decrypt(keyType CryptoKeyType, in []byte) ([]byte, error) {
|
2018-02-14 06:27:05 +01:00
|
|
|
// Decryption must be performed under the manager mutex since the keys
|
|
|
|
// are cleared when the manager is locked.
|
2014-10-23 11:57:22 +02:00
|
|
|
m.mtx.Lock()
|
|
|
|
defer m.mtx.Unlock()
|
|
|
|
|
|
|
|
cryptoKey, err := m.selectCryptoKey(keyType)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
decrypted, err := cryptoKey.Decrypt(in)
|
|
|
|
if err != nil {
|
|
|
|
return nil, managerError(ErrCrypto, "failed to decrypt", err)
|
|
|
|
}
|
|
|
|
return decrypted, nil
|
|
|
|
}
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// newManager returns a new locked address manager with the given parameters.
|
2017-01-17 01:19:02 +01:00
|
|
|
func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey,
|
|
|
|
masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor,
|
|
|
|
cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte, syncInfo *syncState,
|
2022-09-29 00:16:15 +02:00
|
|
|
birthday time.Time, passphraseSalt [saltSize]byte,
|
2022-09-20 08:54:03 +02:00
|
|
|
scopedManagers map[KeyScope]*ScopedKeyManager) *Manager {
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
m := &Manager{
|
2015-02-06 06:51:37 +01:00
|
|
|
chainParams: chainParams,
|
2014-08-08 22:43:50 +02:00
|
|
|
syncState: *syncInfo,
|
|
|
|
locked: true,
|
2018-02-14 06:27:05 +01:00
|
|
|
birthday: birthday,
|
2014-08-08 22:43:50 +02:00
|
|
|
masterKeyPub: masterKeyPub,
|
|
|
|
masterKeyPriv: masterKeyPriv,
|
|
|
|
cryptoKeyPub: cryptoKeyPub,
|
|
|
|
cryptoKeyPrivEncrypted: cryptoKeyPrivEncrypted,
|
2014-09-27 18:18:18 +02:00
|
|
|
cryptoKeyPriv: &cryptoKey{},
|
2014-08-08 22:43:50 +02:00
|
|
|
cryptoKeyScriptEncrypted: cryptoKeyScriptEncrypted,
|
2014-09-27 18:18:18 +02:00
|
|
|
cryptoKeyScript: &cryptoKey{},
|
2022-09-29 00:16:15 +02:00
|
|
|
passphraseSalt: passphraseSalt,
|
2018-02-14 06:27:05 +01:00
|
|
|
scopedManagers: scopedManagers,
|
|
|
|
externalAddrSchemas: make(map[AddressType][]KeyScope),
|
|
|
|
internalAddrSchemas: make(map[AddressType][]KeyScope),
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
2018-02-14 06:27:05 +01:00
|
|
|
|
|
|
|
for _, sMgr := range m.scopedManagers {
|
|
|
|
externalType := sMgr.AddrSchema().ExternalAddrType
|
|
|
|
internalType := sMgr.AddrSchema().InternalAddrType
|
|
|
|
scope := sMgr.Scope()
|
|
|
|
|
|
|
|
m.externalAddrSchemas[externalType] = append(
|
|
|
|
m.externalAddrSchemas[externalType], scope,
|
|
|
|
)
|
|
|
|
m.internalAddrSchemas[internalType] = append(
|
|
|
|
m.internalAddrSchemas[internalType], scope,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return m
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2014-12-12 09:54:26 +01:00
|
|
|
// deriveCoinTypeKey derives the cointype key which can be used to derive the
|
|
|
|
// extended key for an account according to the hierarchy described by BIP0044
|
|
|
|
// given the coin type key.
|
2014-08-08 22:43:50 +02:00
|
|
|
//
|
|
|
|
// In particular this is the hierarchical deterministic extended key path:
|
2018-02-14 06:27:05 +01:00
|
|
|
// m/purpose'/<coin type>'
|
2014-12-12 09:54:26 +01:00
|
|
|
func deriveCoinTypeKey(masterNode *hdkeychain.ExtendedKey,
|
2018-02-14 06:27:05 +01:00
|
|
|
scope KeyScope) (*hdkeychain.ExtendedKey, error) {
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// Enforce maximum coin type.
|
2018-02-14 06:27:05 +01:00
|
|
|
if scope.Coin > maxCoinType {
|
2014-08-08 22:43:50 +02:00
|
|
|
err := managerError(ErrCoinTypeTooHigh, errCoinTypeTooHigh, nil)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// The hierarchy described by BIP0043 is:
|
|
|
|
// m/<purpose>'/*
|
2018-02-14 06:27:05 +01:00
|
|
|
//
|
2014-08-08 22:43:50 +02:00
|
|
|
// This is further extended by BIP0044 to:
|
|
|
|
// m/44'/<coin type>'/<account>'/<branch>/<address index>
|
|
|
|
//
|
2018-02-14 06:27:05 +01:00
|
|
|
// However, as this is a generic key store for any family for BIP0044
|
|
|
|
// standards, we'll use the custom scope to govern our key derivation.
|
|
|
|
//
|
2014-08-08 22:43:50 +02:00
|
|
|
// The branch is 0 for external addresses and 1 for internal addresses.
|
|
|
|
|
|
|
|
// Derive the purpose key as a child of the master node.
|
2022-09-16 07:20:02 +02:00
|
|
|
purpose, err := masterNode.Derive(
|
2021-02-17 02:01:02 +01:00
|
|
|
scope.Purpose + hdkeychain.HardenedKeyStart,
|
|
|
|
)
|
2014-08-08 22:43:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Derive the coin type key as a child of the purpose key.
|
2022-09-16 07:20:02 +02:00
|
|
|
coinTypeKey, err := purpose.Derive(
|
2021-02-17 02:01:02 +01:00
|
|
|
scope.Coin + hdkeychain.HardenedKeyStart,
|
|
|
|
)
|
2014-08-08 22:43:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-12-12 09:54:26 +01:00
|
|
|
return coinTypeKey, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// deriveAccountKey derives the extended key for an account according to the
|
|
|
|
// hierarchy described by BIP0044 given the master node.
|
|
|
|
//
|
|
|
|
// In particular this is the hierarchical deterministic extended key path:
|
2022-08-05 06:38:43 +02:00
|
|
|
//
|
|
|
|
// m/purpose'/<coin type>'/<account>'
|
2014-12-12 09:54:26 +01:00
|
|
|
func deriveAccountKey(coinTypeKey *hdkeychain.ExtendedKey,
|
|
|
|
account uint32) (*hdkeychain.ExtendedKey, error) {
|
2018-02-14 06:27:05 +01:00
|
|
|
|
2014-12-12 09:54:26 +01:00
|
|
|
// Enforce maximum account number.
|
|
|
|
if account > MaxAccountNum {
|
|
|
|
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
// Derive the account key as a child of the coin type key.
|
2022-09-16 07:20:02 +02:00
|
|
|
return coinTypeKey.Derive(
|
2021-02-17 02:01:02 +01:00
|
|
|
account + hdkeychain.HardenedKeyStart,
|
|
|
|
)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// checkBranchKeys ensures deriving the extended keys for the internal and
|
|
|
|
// external branches given an account key does not result in an invalid child
|
|
|
|
// error which means the chosen seed is not usable. This conforms to the
|
2018-02-14 06:27:05 +01:00
|
|
|
// hierarchy described by the BIP0044 family so long as the account key is
|
|
|
|
// already derived accordingly.
|
2014-08-08 22:43:50 +02:00
|
|
|
//
|
|
|
|
// In particular this is the hierarchical deterministic extended key path:
|
2022-08-05 06:38:43 +02:00
|
|
|
//
|
|
|
|
// m/purpose'/<coin type>'/<account>'/<branch>
|
2014-08-08 22:43:50 +02:00
|
|
|
//
|
|
|
|
// The branch is 0 for external addresses and 1 for internal addresses.
|
|
|
|
func checkBranchKeys(acctKey *hdkeychain.ExtendedKey) error {
|
|
|
|
// Derive the external branch as the first child of the account key.
|
2022-09-16 07:20:02 +02:00
|
|
|
if _, err := acctKey.Derive(ExternalBranch); err != nil {
|
2014-08-08 22:43:50 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-17 02:01:02 +01:00
|
|
|
// Derive the internal branch as the second child of the account key.
|
2022-09-16 07:20:02 +02:00
|
|
|
_, err := acctKey.Derive(InternalBranch)
|
2014-08-08 22:43:50 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// loadManager returns a new address manager that results from loading it from
|
2022-09-29 00:16:15 +02:00
|
|
|
// the passed opened database.
|
|
|
|
func loadManager(ns walletdb.ReadBucket, chainParams *chaincfg.Params) (
|
|
|
|
*Manager, error) {
|
2018-02-14 06:27:05 +01:00
|
|
|
|
2017-01-17 01:19:02 +01:00
|
|
|
// Verify the version is neither too old or too new.
|
|
|
|
version, err := fetchManagerVersion(ns)
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to fetch version for update"
|
|
|
|
return nil, managerError(ErrDatabase, str, err)
|
|
|
|
}
|
|
|
|
if version < latestMgrVersion {
|
|
|
|
str := "database upgrade required"
|
|
|
|
return nil, managerError(ErrUpgrade, str, nil)
|
|
|
|
} else if version > latestMgrVersion {
|
|
|
|
str := "database version is greater than latest understood version"
|
|
|
|
return nil, managerError(ErrUpgrade, str, nil)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2017-01-17 01:19:02 +01:00
|
|
|
// Load the master key params from the db.
|
2022-09-29 00:16:15 +02:00
|
|
|
masterKeyPubParams, masterKeyparams, err := fetchMasterKeyParams(ns)
|
2017-01-17 01:19:02 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, maybeConvertDbError(err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2017-01-17 01:19:02 +01:00
|
|
|
// Load the crypto keys from the db.
|
|
|
|
cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc, err :=
|
|
|
|
fetchCryptoKeys(ns)
|
|
|
|
if err != nil {
|
|
|
|
return nil, maybeConvertDbError(err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2017-01-17 01:19:02 +01:00
|
|
|
// Load the sync state from the db.
|
|
|
|
syncedTo, err := fetchSyncedTo(ns)
|
|
|
|
if err != nil {
|
|
|
|
return nil, maybeConvertDbError(err)
|
|
|
|
}
|
2019-01-08 18:44:44 +01:00
|
|
|
startBlock, err := FetchStartBlock(ns)
|
2017-01-17 01:19:02 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, maybeConvertDbError(err)
|
|
|
|
}
|
2017-09-19 23:53:38 +02:00
|
|
|
birthday, err := fetchBirthday(ns)
|
|
|
|
if err != nil {
|
|
|
|
return nil, maybeConvertDbError(err)
|
|
|
|
}
|
2017-01-17 01:19:02 +01:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
// Set the master private key params, but don't derive it now since the
|
|
|
|
// manager starts off locked.
|
2014-08-08 22:43:50 +02:00
|
|
|
var masterKeyPriv snacl.SecretKey
|
2022-09-29 00:16:15 +02:00
|
|
|
err = masterKeyPriv.Unmarshal(masterKeyparams)
|
2022-09-20 08:54:03 +02:00
|
|
|
if err != nil {
|
|
|
|
str := "failed to unmarshal master private key"
|
|
|
|
return nil, managerError(ErrCrypto, str, err)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Derive the master public key using the serialized params and provided
|
|
|
|
// passphrase.
|
|
|
|
var masterKeyPub snacl.SecretKey
|
|
|
|
if err := masterKeyPub.Unmarshal(masterKeyPubParams); err != nil {
|
|
|
|
str := "failed to unmarshal master public key"
|
|
|
|
return nil, managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2022-09-29 00:16:15 +02:00
|
|
|
|
|
|
|
pubPassphrase := []byte("public") // Hardcoded salt.
|
2014-08-08 22:43:50 +02:00
|
|
|
if err := masterKeyPub.DeriveKey(&pubPassphrase); err != nil {
|
|
|
|
str := "invalid passphrase for master public key"
|
|
|
|
return nil, managerError(ErrWrongPassphrase, str, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use the master public key to decrypt the crypto public key.
|
2014-09-27 18:18:18 +02:00
|
|
|
cryptoKeyPub := &cryptoKey{snacl.CryptoKey{}}
|
2014-08-08 22:43:50 +02:00
|
|
|
cryptoKeyPubCT, err := masterKeyPub.Decrypt(cryptoKeyPubEnc)
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to decrypt crypto public key"
|
|
|
|
return nil, managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-09-11 20:19:46 +02:00
|
|
|
cryptoKeyPub.CopyBytes(cryptoKeyPubCT)
|
2015-02-04 02:42:40 +01:00
|
|
|
zero.Bytes(cryptoKeyPubCT)
|
2014-08-08 22:43:50 +02:00
|
|
|
|
|
|
|
// Create the sync state struct.
|
2017-07-12 01:13:10 +02:00
|
|
|
syncInfo := newSyncState(startBlock, syncedTo)
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Generate passphrase salt.
|
|
|
|
var passphraseSalt [saltSize]byte
|
|
|
|
_, err = rand.Read(passphraseSalt[:])
|
2015-01-30 22:36:19 +01:00
|
|
|
if err != nil {
|
|
|
|
str := "failed to read random source for passphrase salt"
|
|
|
|
return nil, managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Next, we'll need to load all known manager scopes from disk. Each
|
|
|
|
// scope is on a distinct top-level path within our HD key chain.
|
|
|
|
scopedManagers := make(map[KeyScope]*ScopedKeyManager)
|
|
|
|
err = forEachKeyScope(ns, func(scope KeyScope) error {
|
|
|
|
scopeSchema, err := fetchScopeAddrSchema(ns, &scope)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
scopedManagers[scope] = &ScopedKeyManager{
|
2021-08-14 01:13:06 +02:00
|
|
|
scope: scope,
|
|
|
|
addrSchema: *scopeSchema,
|
|
|
|
addrs: make(map[addrKey]ManagedAddress),
|
|
|
|
acctInfo: make(map[uint32]*accountInfo),
|
2021-12-15 01:54:55 +01:00
|
|
|
privKeyCache: map[DerivationPath]*list.Element{},
|
|
|
|
privKeyLru: list.New(),
|
2018-02-14 06:27:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create new address manager with the given parameters. Also,
|
|
|
|
// override the defaults for the additional fields which are not
|
|
|
|
// specified in the call to new with the values loaded from the
|
|
|
|
// database.
|
|
|
|
mgr := newManager(
|
|
|
|
chainParams, &masterKeyPub, &masterKeyPriv,
|
2014-11-02 22:06:11 +01:00
|
|
|
cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo,
|
2022-09-29 00:16:15 +02:00
|
|
|
birthday, passphraseSalt, scopedManagers)
|
2018-02-14 06:27:05 +01:00
|
|
|
|
|
|
|
for _, scopedManager := range scopedManagers {
|
|
|
|
scopedManager.rootManager = mgr
|
|
|
|
}
|
|
|
|
|
2014-08-08 22:43:50 +02:00
|
|
|
return mgr, nil
|
|
|
|
}
|
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Open loads an existing address manager from the given namespace.
|
2014-08-08 22:43:50 +02:00
|
|
|
//
|
2018-02-14 06:27:05 +01:00
|
|
|
// If a config structure is passed to the function, that configuration will
|
|
|
|
// override the defaults.
|
2014-10-24 08:56:54 +02:00
|
|
|
//
|
2014-08-08 22:43:50 +02:00
|
|
|
// A ManagerError with an error code of ErrNoExist will be returned if the
|
2014-11-02 22:06:11 +01:00
|
|
|
// passed manager does not exist in the specified namespace.
|
2022-09-29 00:16:15 +02:00
|
|
|
func Open(ns walletdb.ReadBucket, chainParams *chaincfg.Params) (
|
|
|
|
*Manager, error) {
|
2018-02-14 06:27:05 +01:00
|
|
|
|
2014-11-02 22:06:11 +01:00
|
|
|
// Return an error if the manager has NOT already been created in the
|
|
|
|
// given database namespace.
|
2017-01-17 01:19:02 +01:00
|
|
|
exists := managerExists(ns)
|
2014-11-02 22:06:11 +01:00
|
|
|
if !exists {
|
2014-08-08 22:43:50 +02:00
|
|
|
str := "the specified address manager does not exist"
|
|
|
|
return nil, managerError(ErrNoExist, str, nil)
|
|
|
|
}
|
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
return loadManager(ns, chainParams)
|
2017-01-17 01:19:02 +01:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// createManagerKeyScope creates a new key scoped for a target manager's scope.
|
|
|
|
// This partitions key derivation for a particular purpose+coin tuple, allowing
|
|
|
|
// multiple address derivation schems to be maintained concurrently.
|
|
|
|
func createManagerKeyScope(ns walletdb.ReadWriteBucket,
|
|
|
|
scope KeyScope, root *hdkeychain.ExtendedKey,
|
|
|
|
cryptoKeyPub, cryptoKeyPriv EncryptorDecryptor) error {
|
|
|
|
|
|
|
|
// Derive the cointype key according to the passed scope.
|
|
|
|
coinTypeKeyPriv, err := deriveCoinTypeKey(root, scope)
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to derive cointype extended key"
|
|
|
|
return managerError(ErrKeyChain, str, err)
|
|
|
|
}
|
|
|
|
defer coinTypeKeyPriv.Zero()
|
|
|
|
|
|
|
|
// Derive the account key for the first account according our
|
|
|
|
// BIP0044-like derivation.
|
2022-09-17 05:10:47 +02:00
|
|
|
acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, DefaultAccountNum)
|
2018-02-14 06:27:05 +01:00
|
|
|
if err != nil {
|
|
|
|
// The seed is unusable if the any of the children in the
|
|
|
|
// required hierarchy can't be derived due to invalid child.
|
|
|
|
if err == hdkeychain.ErrInvalidChild {
|
|
|
|
str := "the provided seed is unusable"
|
|
|
|
return managerError(ErrKeyChain, str,
|
|
|
|
hdkeychain.ErrUnusableSeed)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the branch keys can be derived for the provided seed according
|
|
|
|
// to our BIP0044-like derivation.
|
|
|
|
if err := checkBranchKeys(acctKeyPriv); err != nil {
|
|
|
|
// The seed is unusable if the any of the children in the
|
|
|
|
// required hierarchy can't be derived due to invalid child.
|
|
|
|
if err == hdkeychain.ErrInvalidChild {
|
|
|
|
str := "the provided seed is unusable"
|
|
|
|
return managerError(ErrKeyChain, str,
|
|
|
|
hdkeychain.ErrUnusableSeed)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// The address manager needs the public extended key for the account.
|
|
|
|
acctKeyPub, err := acctKeyPriv.Neuter()
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to convert private key for account 0"
|
|
|
|
return managerError(ErrKeyChain, str, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encrypt the default account keys with the associated crypto keys.
|
|
|
|
acctPubEnc, err := cryptoKeyPub.Encrypt([]byte(acctKeyPub.String()))
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to encrypt public key for account 0"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
acctPrivEnc, err := cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String()))
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to encrypt private key for account 0"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the encrypted cointype keys to the database.
|
|
|
|
err = putCoinTypeKeys(ns, &scope, coinTypePubEnc, coinTypePrivEnc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the information for the default account to the database.
|
2021-02-17 02:01:15 +01:00
|
|
|
err = putDefaultAccountInfo(
|
2018-02-14 06:27:05 +01:00
|
|
|
ns, &scope, DefaultAccountNum, acctPubEnc, acctPrivEnc, 0, 0,
|
|
|
|
defaultAccountName,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-17 02:01:15 +01:00
|
|
|
return putDefaultAccountInfo(
|
2018-02-14 06:27:05 +01:00
|
|
|
ns, &scope, ImportedAddrAccount, nil, nil, 0, 0,
|
|
|
|
ImportedAddrAccountName,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-04-25 02:44:21 +02:00
|
|
|
// Create creates a new address manager in the given namespace.
|
2014-08-08 22:43:50 +02:00
|
|
|
//
|
2020-04-25 02:44:21 +02:00
|
|
|
// The seed must conform to the standards described in
|
|
|
|
// hdkeychain.NewMaster and will be used to create the master root
|
|
|
|
// node from which all hierarchical deterministic addresses are
|
|
|
|
// derived. This allows all chained addresses in the address manager
|
|
|
|
// to be recovered by using the same seed.
|
2014-08-08 22:43:50 +02:00
|
|
|
//
|
2022-09-29 00:16:15 +02:00
|
|
|
// All private keys and information are protected by secret keys derived
|
|
|
|
// from the provided passphrase.
|
|
|
|
// The passphrase is required to unlock the address manager in order to gain
|
|
|
|
// access to any private keys and information.
|
2014-10-24 08:56:54 +02:00
|
|
|
//
|
2020-04-25 02:44:21 +02:00
|
|
|
// If a config structure is passed to the function, that configuration
|
|
|
|
// will override the defaults.
|
|
|
|
//
|
|
|
|
// A ManagerError with an error code of ErrAlreadyExists will be
|
|
|
|
// returned the address manager already exists in the specified
|
|
|
|
// namespace.
|
2020-09-26 12:14:09 +02:00
|
|
|
func Create(ns walletdb.ReadWriteBucket, rootKey *hdkeychain.ExtendedKey,
|
2022-09-29 00:16:15 +02:00
|
|
|
passphrase []byte, chainParams *chaincfg.Params,
|
|
|
|
config *ScryptOptions, birthday time.Time) error {
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Return an error if the manager has already been created in
|
|
|
|
// the given database namespace.
|
|
|
|
exists := managerExists(ns)
|
|
|
|
if exists {
|
|
|
|
return managerError(ErrAlreadyExists, errAlreadyExists, nil)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Ensure the passphrase is not empty.
|
|
|
|
if len(passphrase) == 0 {
|
|
|
|
str := "passphrase may not be empty"
|
2018-02-14 06:27:05 +01:00
|
|
|
return managerError(ErrEmptyPassphrase, str, nil)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Perform the initial bucket creation and database namespace setup.
|
2022-09-20 08:54:03 +02:00
|
|
|
if err := createManagerNS(ns, ScopeAddrMap); err != nil {
|
2018-02-14 06:27:05 +01:00
|
|
|
return maybeConvertDbError(err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
if config == nil {
|
|
|
|
config = &DefaultScryptOptions
|
|
|
|
}
|
2015-01-30 22:36:19 +01:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Generate new master keys. These master keys are used to protect the
|
|
|
|
// crypto keys that will be generated next.
|
2022-09-29 00:16:15 +02:00
|
|
|
pubPassphrase := []byte("public") // Hardcoded salt.
|
2018-02-14 06:27:05 +01:00
|
|
|
masterKeyPub, err := newSecretKey(&pubPassphrase, config)
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to master public key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Generate new crypto public, private, and script keys. These keys are
|
|
|
|
// used to protect the actual public and private data such as addresses,
|
|
|
|
// extended keys, and scripts.
|
|
|
|
cryptoKeyPub, err := newCryptoKey()
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to generate crypto public key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-12-12 09:54:26 +01:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Encrypt the crypto keys with the associated master keys.
|
|
|
|
cryptoKeyPubEnc, err := masterKeyPub.Encrypt(cryptoKeyPub.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to encrypt crypto public key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Use the genesis block for the passed chain as the created at block
|
|
|
|
// for the default.
|
2020-06-01 21:51:58 +02:00
|
|
|
createdAt := &BlockStamp{
|
|
|
|
Hash: *chainParams.GenesisHash,
|
|
|
|
Height: 0,
|
|
|
|
Timestamp: chainParams.GenesisBlock.Header.Timestamp,
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Create the initial sync state.
|
|
|
|
syncInfo := newSyncState(createdAt, createdAt)
|
2017-01-17 01:19:02 +01:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
pubParams := masterKeyPub.Marshal()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
var params []byte
|
2020-04-25 02:44:21 +02:00
|
|
|
var masterKeyPriv *snacl.SecretKey
|
2021-02-23 02:52:30 +01:00
|
|
|
var cryptoKeyPrivEnc []byte
|
|
|
|
var cryptoKeyScriptEnc []byte
|
2022-09-29 00:16:15 +02:00
|
|
|
masterKeyPriv, err = newSecretKey(&passphrase, config)
|
2022-09-20 08:54:03 +02:00
|
|
|
if err != nil {
|
|
|
|
str := "failed to master private key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
defer masterKeyPriv.Zero()
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
// Generate the passphrase salt. This is used when hashing passwords
|
|
|
|
// to detect whether an unlock can be avoided when the manager is
|
|
|
|
// already unlocked.
|
|
|
|
var passphraseSalt [saltSize]byte
|
|
|
|
_, err = rand.Read(passphraseSalt[:])
|
2022-09-20 08:54:03 +02:00
|
|
|
if err != nil {
|
|
|
|
str := "failed to read random source for passphrase salt"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2020-04-25 02:44:21 +02:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
cryptoKeyPriv, err := newCryptoKey()
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to generate crypto private key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
defer cryptoKeyPriv.Zero()
|
|
|
|
cryptoKeyScript, err := newCryptoKey()
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to generate crypto script key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
defer cryptoKeyScript.Zero()
|
2020-04-25 02:44:21 +02:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
cryptoKeyPrivEnc, err =
|
|
|
|
masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to encrypt crypto private key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
|
|
|
cryptoKeyScriptEnc, err =
|
|
|
|
masterKeyPriv.Encrypt(cryptoKeyScript.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to encrypt crypto script key"
|
|
|
|
return managerError(ErrCrypto, str, err)
|
|
|
|
}
|
2020-04-25 02:44:21 +02:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
// Generate the BIP0044 HD key structure to ensure the
|
|
|
|
// provided seed can generate the required structure with no
|
|
|
|
// issues.
|
|
|
|
rootPubKey, err := rootKey.Neuter()
|
|
|
|
if err != nil {
|
|
|
|
str := "failed to neuter master extended key"
|
|
|
|
return managerError(ErrKeyChain, str, err)
|
|
|
|
}
|
2020-04-25 02:44:21 +02:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
// Next, for each registers default manager scope, we'll
|
|
|
|
// create the hardened cointype key for it, as well as the
|
|
|
|
// first default account.
|
|
|
|
for _, defaultScope := range DefaultKeyScopes {
|
|
|
|
err := createManagerKeyScope(
|
|
|
|
ns, defaultScope, rootKey, cryptoKeyPub, cryptoKeyPriv,
|
|
|
|
)
|
2014-12-12 09:54:26 +01:00
|
|
|
if err != nil {
|
2018-02-14 06:27:05 +01:00
|
|
|
return maybeConvertDbError(err)
|
2014-12-12 09:54:26 +01:00
|
|
|
}
|
2022-09-20 08:54:03 +02:00
|
|
|
}
|
2014-12-12 09:54:26 +01:00
|
|
|
|
2022-09-20 08:54:03 +02:00
|
|
|
// Before we proceed, we'll also store the root master private
|
|
|
|
// key within the database in an encrypted format. This is
|
|
|
|
// required as in the future, we may need to create additional
|
|
|
|
// scoped key managers.
|
|
|
|
masterHDPrivKeyEnc, err :=
|
|
|
|
cryptoKeyPriv.Encrypt([]byte(rootKey.String()))
|
|
|
|
if err != nil {
|
|
|
|
return maybeConvertDbError(err)
|
|
|
|
}
|
|
|
|
masterHDPubKeyEnc, err :=
|
|
|
|
cryptoKeyPub.Encrypt([]byte(rootPubKey.String()))
|
|
|
|
if err != nil {
|
|
|
|
return maybeConvertDbError(err)
|
|
|
|
}
|
|
|
|
err = putMasterHDKeys(ns, masterHDPrivKeyEnc, masterHDPubKeyEnc)
|
|
|
|
if err != nil {
|
|
|
|
return maybeConvertDbError(err)
|
2018-02-14 06:27:05 +01:00
|
|
|
}
|
2020-04-25 02:44:21 +02:00
|
|
|
|
2022-09-29 00:16:15 +02:00
|
|
|
params = masterKeyPriv.Marshal()
|
2022-09-20 08:54:03 +02:00
|
|
|
|
2020-04-25 02:44:21 +02:00
|
|
|
// Save the master key params to the database.
|
2022-09-29 00:16:15 +02:00
|
|
|
err = putMasterKeyParams(ns, pubParams, params)
|
2018-02-14 06:27:05 +01:00
|
|
|
if err != nil {
|
|
|
|
return maybeConvertDbError(err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Save the encrypted crypto keys to the database.
|
|
|
|
err = putCryptoKeys(ns, cryptoKeyPubEnc, cryptoKeyPrivEnc,
|
|
|
|
cryptoKeyScriptEnc)
|
|
|
|
if err != nil {
|
|
|
|
return maybeConvertDbError(err)
|
|
|
|
}
|
2014-08-08 22:43:50 +02:00
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Save the initial synced to state.
|
2019-01-08 18:44:44 +01:00
|
|
|
err = PutSyncedTo(ns, &syncInfo.syncedTo)
|
2018-02-14 06:27:05 +01:00
|
|
|
if err != nil {
|
|
|
|
return maybeConvertDbError(err)
|
|
|
|
}
|
|
|
|
err = putStartBlock(ns, &syncInfo.startBlock)
|
2014-08-08 22:43:50 +02:00
|
|
|
if err != nil {
|
2016-03-17 15:05:11 +01:00
|
|
|
return maybeConvertDbError(err)
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:27:05 +01:00
|
|
|
// Use 48 hours as margin of safety for wallet birthday.
|
2018-03-21 02:00:28 +01:00
|
|
|
return putBirthday(ns, birthday.Add(-48*time.Hour))
|
2014-08-08 22:43:50 +02:00
|
|
|
}
|