mirror of
https://github.com/LBRYFoundation/lbcwallet.git
synced 2025-09-02 10:15:15 +00:00
Merge pull request #720 from guggero/xprv-wallet-init
wallet: allow wallet to be created from extended master root key directly
This commit is contained in:
commit
9b5a201c34
5 changed files with 97 additions and 53 deletions
|
@ -27,6 +27,8 @@ var (
|
|||
0xb6, 0xc4, 0x40, 0xc0, 0x64,
|
||||
}
|
||||
|
||||
rootKey, _ = hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
|
||||
|
||||
pubPassphrase = []byte("_DJr{fL4H0O}*-0\n:V1izc)(6BomK")
|
||||
privPassphrase = []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj")
|
||||
pubPassphrase2 = []byte("-0NV4P~VSJBWbunw}%<Z]fuGpbN[ZI")
|
||||
|
@ -285,7 +287,7 @@ func setupManager(t *testing.T) (tearDownFunc func(), db walletdb.DB, mgr *Manag
|
|||
return err
|
||||
}
|
||||
err = Create(
|
||||
ns, seed, pubPassphrase, privPassphrase,
|
||||
ns, rootKey, pubPassphrase, privPassphrase,
|
||||
&chaincfg.MainNetParams, fastScrypt, time.Time{},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
@ -1800,13 +1800,13 @@ func createManagerKeyScope(ns walletdb.ReadWriteBucket,
|
|||
// A ManagerError with an error code of ErrAlreadyExists will be
|
||||
// returned the address manager already exists in the specified
|
||||
// namespace.
|
||||
func Create(ns walletdb.ReadWriteBucket,
|
||||
seed, pubPassphrase, privPassphrase []byte,
|
||||
func Create(ns walletdb.ReadWriteBucket, rootKey *hdkeychain.ExtendedKey,
|
||||
pubPassphrase, privPassphrase []byte,
|
||||
chainParams *chaincfg.Params, config *ScryptOptions,
|
||||
birthday time.Time) error {
|
||||
|
||||
// If the seed argument is nil we create in watchingOnly mode.
|
||||
isWatchingOnly := seed == nil
|
||||
isWatchingOnly := rootKey == nil
|
||||
|
||||
// Return an error if the manager has already been created in
|
||||
// the given database namespace.
|
||||
|
@ -1922,13 +1922,6 @@ func Create(ns walletdb.ReadWriteBucket,
|
|||
// Generate the BIP0044 HD key structure to ensure the
|
||||
// provided seed can generate the required structure with no
|
||||
// issues.
|
||||
|
||||
// Derive the master extended key from the seed.
|
||||
rootKey, err := hdkeychain.NewMaster(seed, chainParams)
|
||||
if err != nil {
|
||||
str := "failed to derive master extended key"
|
||||
return managerError(ErrKeyChain, str, err)
|
||||
}
|
||||
rootPubKey, err := rootKey.Neuter()
|
||||
if err != nil {
|
||||
str := "failed to neuter master extended key"
|
||||
|
|
|
@ -1816,32 +1816,35 @@ func TestManager(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
createdWatchingOnly bool
|
||||
seed []byte
|
||||
rootKey *hdkeychain.ExtendedKey
|
||||
privPassphrase []byte
|
||||
}{
|
||||
{
|
||||
name: "created with seed",
|
||||
createdWatchingOnly: false,
|
||||
seed: seed,
|
||||
rootKey: rootKey,
|
||||
privPassphrase: privPassphrase,
|
||||
},
|
||||
{
|
||||
name: "created watch-only",
|
||||
createdWatchingOnly: true,
|
||||
seed: nil,
|
||||
rootKey: nil,
|
||||
privPassphrase: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Need to wrap in a call so the defers work correctly.
|
||||
testManagerCase(t, test.name, test.createdWatchingOnly,
|
||||
test.seed, test.privPassphrase)
|
||||
testManagerCase(
|
||||
t, test.name, test.createdWatchingOnly,
|
||||
test.privPassphrase, test.rootKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testManagerCase(t *testing.T, caseName string,
|
||||
caseCreatedWatchingOnly bool, caseSeed, casePrivPassphrase []byte) {
|
||||
caseCreatedWatchingOnly bool, casePrivPassphrase []byte,
|
||||
caseKey *hdkeychain.ExtendedKey) {
|
||||
|
||||
teardown, db := emptyDB(t)
|
||||
defer teardown()
|
||||
|
@ -1867,7 +1870,7 @@ func testManagerCase(t *testing.T, caseName string,
|
|||
return err
|
||||
}
|
||||
err = Create(
|
||||
ns, caseSeed, pubPassphrase, casePrivPassphrase,
|
||||
ns, caseKey, pubPassphrase, casePrivPassphrase,
|
||||
&chaincfg.MainNetParams, fastScrypt, time.Time{},
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -1897,7 +1900,7 @@ func testManagerCase(t *testing.T, caseName string,
|
|||
err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
return Create(
|
||||
ns, caseSeed, pubPassphrase, casePrivPassphrase,
|
||||
ns, caseKey, pubPassphrase, casePrivPassphrase,
|
||||
&chaincfg.MainNetParams, fastScrypt, time.Time{},
|
||||
)
|
||||
})
|
||||
|
@ -2193,7 +2196,7 @@ func TestScopedKeyManagerManagement(t *testing.T) {
|
|||
return err
|
||||
}
|
||||
err = Create(
|
||||
ns, seed, pubPassphrase, privPassphrase,
|
||||
ns, rootKey, pubPassphrase, privPassphrase,
|
||||
&chaincfg.MainNetParams, fastScrypt, time.Time{},
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -2442,7 +2445,7 @@ func TestRootHDKeyNeutering(t *testing.T) {
|
|||
return err
|
||||
}
|
||||
err = Create(
|
||||
ns, seed, pubPassphrase, privPassphrase,
|
||||
ns, rootKey, pubPassphrase, privPassphrase,
|
||||
&chaincfg.MainNetParams, fastScrypt, time.Time{},
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -2534,7 +2537,7 @@ func TestNewRawAccount(t *testing.T) {
|
|||
return err
|
||||
}
|
||||
err = Create(
|
||||
ns, seed, pubPassphrase, privPassphrase,
|
||||
ns, rootKey, pubPassphrase, privPassphrase,
|
||||
&chaincfg.MainNetParams, fastScrypt, time.Time{},
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -2660,7 +2663,7 @@ func TestNewRawAccountHybrid(t *testing.T) {
|
|||
return err
|
||||
}
|
||||
err = Create(
|
||||
ns, seed, pubPassphrase, privPassphrase,
|
||||
ns, rootKey, pubPassphrase, privPassphrase,
|
||||
&chaincfg.MainNetParams, fastScrypt, time.Time{},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcwallet/internal/prompt"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
|
@ -145,8 +146,42 @@ func (l *Loader) OnWalletCreated(fn func(walletdb.ReadWriteTx) error) {
|
|||
func (l *Loader) CreateNewWallet(pubPassphrase, privPassphrase, seed []byte,
|
||||
bday time.Time) (*Wallet, error) {
|
||||
|
||||
var (
|
||||
rootKey *hdkeychain.ExtendedKey
|
||||
err error
|
||||
)
|
||||
|
||||
// If a seed was specified, we check its length now. If no seed is
|
||||
// passed, the wallet will create a new random one.
|
||||
if seed != nil {
|
||||
if len(seed) < hdkeychain.MinSeedBytes ||
|
||||
len(seed) > hdkeychain.MaxSeedBytes {
|
||||
|
||||
return nil, hdkeychain.ErrInvalidSeedLen
|
||||
}
|
||||
|
||||
// Derive the master extended key from the seed.
|
||||
rootKey, err = hdkeychain.NewMaster(seed, l.chainParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive master " +
|
||||
"extended key")
|
||||
}
|
||||
}
|
||||
|
||||
return l.createNewWallet(
|
||||
pubPassphrase, privPassphrase, seed, bday, false,
|
||||
pubPassphrase, privPassphrase, rootKey, bday, false,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateNewWalletExtendedKey creates a new wallet from an extended master root
|
||||
// key using the provided public and private passphrases. The root key is
|
||||
// optional. If non-nil, addresses are derived from this root key. If nil, a
|
||||
// secure random seed is generated and the root key is derived from that.
|
||||
func (l *Loader) CreateNewWalletExtendedKey(pubPassphrase, privPassphrase []byte,
|
||||
rootKey *hdkeychain.ExtendedKey, bday time.Time) (*Wallet, error) {
|
||||
|
||||
return l.createNewWallet(
|
||||
pubPassphrase, privPassphrase, rootKey, bday, false,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -161,8 +196,9 @@ func (l *Loader) CreateNewWatchingOnlyWallet(pubPassphrase []byte,
|
|||
)
|
||||
}
|
||||
|
||||
func (l *Loader) createNewWallet(pubPassphrase, privPassphrase,
|
||||
seed []byte, bday time.Time, isWatchingOnly bool) (*Wallet, error) {
|
||||
func (l *Loader) createNewWallet(pubPassphrase, privPassphrase []byte,
|
||||
rootKey *hdkeychain.ExtendedKey, bday time.Time,
|
||||
isWatchingOnly bool) (*Wallet, error) {
|
||||
|
||||
defer l.mu.Unlock()
|
||||
l.mu.Lock()
|
||||
|
@ -206,7 +242,7 @@ func (l *Loader) createNewWallet(pubPassphrase, privPassphrase,
|
|||
}
|
||||
} else {
|
||||
err := CreateWithCallback(
|
||||
l.db, pubPassphrase, privPassphrase, seed,
|
||||
l.db, pubPassphrase, privPassphrase, rootKey,
|
||||
l.chainParams, bday, l.walletCreated,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
@ -3686,12 +3686,12 @@ func (w *Wallet) Database() walletdb.DB {
|
|||
|
||||
// CreateWithCallback is the same as Create with an added callback that will be
|
||||
// called in the same transaction the wallet structure is initialized.
|
||||
func CreateWithCallback(db walletdb.DB, pubPass, privPass, seed []byte,
|
||||
params *chaincfg.Params, birthday time.Time,
|
||||
cb func(walletdb.ReadWriteTx) error) error {
|
||||
func CreateWithCallback(db walletdb.DB, pubPass, privPass []byte,
|
||||
rootKey *hdkeychain.ExtendedKey, params *chaincfg.Params,
|
||||
birthday time.Time, cb func(walletdb.ReadWriteTx) error) error {
|
||||
|
||||
return create(
|
||||
db, pubPass, privPass, seed, params, birthday, false, cb,
|
||||
db, pubPass, privPass, rootKey, params, birthday, false, cb,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3708,18 +3708,19 @@ func CreateWatchingOnlyWithCallback(db walletdb.DB, pubPass []byte,
|
|||
}
|
||||
|
||||
// Create creates an new wallet, writing it to an empty database. If the passed
|
||||
// seed is non-nil, it is used. Otherwise, a secure random seed of the
|
||||
// root key is non-nil, it is used. Otherwise, a secure random seed of the
|
||||
// recommended length is generated.
|
||||
func Create(db walletdb.DB, pubPass, privPass, seed []byte,
|
||||
params *chaincfg.Params, birthday time.Time) error {
|
||||
func Create(db walletdb.DB, pubPass, privPass []byte,
|
||||
rootKey *hdkeychain.ExtendedKey, params *chaincfg.Params,
|
||||
birthday time.Time) error {
|
||||
|
||||
return create(
|
||||
db, pubPass, privPass, seed, params, birthday, false, nil,
|
||||
db, pubPass, privPass, rootKey, params, birthday, false, nil,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateWatchingOnly creates an new watch-only wallet, writing it to
|
||||
// an empty database. No seed can be provided as this wallet will be
|
||||
// an empty database. No root key can be provided as this wallet will be
|
||||
// watching only. Likewise no private passphrase may be provided
|
||||
// either.
|
||||
func CreateWatchingOnly(db walletdb.DB, pubPass []byte,
|
||||
|
@ -3730,28 +3731,36 @@ func CreateWatchingOnly(db walletdb.DB, pubPass []byte,
|
|||
)
|
||||
}
|
||||
|
||||
func create(db walletdb.DB, pubPass, privPass, seed []byte,
|
||||
params *chaincfg.Params, birthday time.Time, isWatchingOnly bool,
|
||||
func create(db walletdb.DB, pubPass, privPass []byte,
|
||||
rootKey *hdkeychain.ExtendedKey, params *chaincfg.Params,
|
||||
birthday time.Time, isWatchingOnly bool,
|
||||
cb func(walletdb.ReadWriteTx) error) error {
|
||||
|
||||
if !isWatchingOnly {
|
||||
// If a seed was provided, ensure that it is of valid length. Otherwise,
|
||||
// we generate a random seed for the wallet with the recommended seed
|
||||
// length.
|
||||
if seed == nil {
|
||||
hdSeed, err := hdkeychain.GenerateSeed(
|
||||
hdkeychain.RecommendedSeedLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seed = hdSeed
|
||||
// If no root key was provided, we create one now from a random seed.
|
||||
// But only if this is not a watching-only wallet where the accounts are
|
||||
// created individually from their xpubs.
|
||||
if !isWatchingOnly && rootKey == nil {
|
||||
hdSeed, err := hdkeychain.GenerateSeed(
|
||||
hdkeychain.RecommendedSeedLen,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(seed) < hdkeychain.MinSeedBytes ||
|
||||
len(seed) > hdkeychain.MaxSeedBytes {
|
||||
return hdkeychain.ErrInvalidSeedLen
|
||||
|
||||
// Derive the master extended key from the seed.
|
||||
rootKey, err = hdkeychain.NewMaster(hdSeed, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive master extended " +
|
||||
"key")
|
||||
}
|
||||
}
|
||||
|
||||
// We need a private key if this isn't a watching only wallet.
|
||||
if !isWatchingOnly && rootKey != nil && !rootKey.IsPrivate() {
|
||||
return fmt.Errorf("need extended private key for wallet that " +
|
||||
"is not watching only")
|
||||
}
|
||||
|
||||
return walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||
addrmgrNs, err := tx.CreateTopLevelBucket(waddrmgrNamespaceKey)
|
||||
if err != nil {
|
||||
|
@ -3763,7 +3772,8 @@ func create(db walletdb.DB, pubPass, privPass, seed []byte,
|
|||
}
|
||||
|
||||
err = waddrmgr.Create(
|
||||
addrmgrNs, seed, pubPass, privPass, params, nil, birthday,
|
||||
addrmgrNs, rootKey, pubPass, privPass, params, nil,
|
||||
birthday,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
Loading…
Add table
Reference in a new issue