diff --git a/waddrmgr/common_test.go b/waddrmgr/common_test.go index cd4eff9..ceacf48 100644 --- a/waddrmgr/common_test.go +++ b/waddrmgr/common_test.go @@ -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}% 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 { diff --git a/wallet/wallet.go b/wallet/wallet.go index 2456faa..a0e9fc2 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -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