github.com/decred/dcrlnd@v0.7.6/walletunlocker/service_test.go (about)

     1  package walletunlocker_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path"
    10  	"testing"
    11  	"time"
    12  
    13  	"decred.org/dcrwallet/v4/wallet"
    14  	"github.com/decred/dcrd/chaincfg/v3"
    15  	"github.com/decred/dcrlnd/aezeed"
    16  	"github.com/decred/dcrlnd/keychain"
    17  	"github.com/decred/dcrlnd/kvdb"
    18  	"github.com/decred/dcrlnd/lnrpc"
    19  	"github.com/decred/dcrlnd/lnwallet"
    20  	"github.com/decred/dcrlnd/lnwallet/dcrwallet"
    21  	walletloader "github.com/decred/dcrlnd/lnwallet/dcrwallet/loader"
    22  	"github.com/decred/dcrlnd/macaroons"
    23  	"github.com/decred/dcrlnd/walletunlocker"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  var (
    28  	testPassword = []byte("test-password")
    29  	testSeed     = []byte("test-seed-123456789")
    30  	testMac      = []byte("fakemacaroon")
    31  
    32  	testEntropy = [aezeed.EntropySize]byte{
    33  		0x81, 0xb6, 0x37, 0xd8,
    34  		0x63, 0x59, 0xe6, 0x96,
    35  		0x0d, 0xe7, 0x95, 0xe4,
    36  		0x1e, 0x0b, 0x4c, 0xfd,
    37  	}
    38  
    39  	testNetParams = chaincfg.SimNetParams()
    40  
    41  	testRecoveryWindow uint32 = 150
    42  
    43  	defaultTestTimeout = 30 * time.Second
    44  
    45  	defaultRootKeyIDContext = macaroons.ContextWithRootKeyID(
    46  		context.Background(), macaroons.DefaultRootKeyID,
    47  	)
    48  )
    49  
    50  func createTestWallet(t *testing.T, dir string, netParams *chaincfg.Params) {
    51  	createTestWalletWithPw(t, testPassword, testPassword, dir, netParams)
    52  }
    53  
    54  func createTestWalletWithPw(t *testing.T, pubPw, privPw []byte, dir string,
    55  	netParams *chaincfg.Params) {
    56  
    57  	/*
    58  		// Instruct waddrmgr to use the cranked down scrypt parameters when
    59  		// creating new wallet encryption keys.
    60  		fastScrypt := waddrmgr.FastScryptOptions
    61  		keyGen := func(passphrase *[]byte, config *waddrmgr.ScryptOptions) (
    62  			*snacl.SecretKey, error) {
    63  
    64  			return snacl.NewSecretKey(
    65  				passphrase, fastScrypt.N, fastScrypt.R, fastScrypt.P,
    66  			)
    67  		}
    68  		waddrmgr.SetSecretKeyGen(keyGen)
    69  	*/
    70  
    71  	// Create a new test wallet that uses fast scrypt as KDF.
    72  	birthday := time.Now().Add(-time.Hour * 24)
    73  	netDir := dcrwallet.NetworkDir(dir, netParams)
    74  	loader := walletloader.NewLoader(netParams, netDir, wallet.DefaultGapLimit)
    75  	_, err := loader.CreateNewWallet(
    76  		context.Background(), pubPw, privPw, testSeed, birthday,
    77  	)
    78  	require.NoError(t, err)
    79  	err = loader.UnloadWallet()
    80  	require.NoError(t, err)
    81  }
    82  
    83  func createSeedAndMnemonic(t *testing.T,
    84  	pass []byte) (*aezeed.CipherSeed, aezeed.Mnemonic) {
    85  	cipherSeed, err := aezeed.New(
    86  		keychain.KeyDerivationVersion, &testEntropy, time.Now(),
    87  	)
    88  	require.NoError(t, err)
    89  
    90  	// With the new seed created, we'll convert it into a mnemonic phrase
    91  	// that we'll send over to initialize the wallet.
    92  	mnemonic, err := cipherSeed.ToMnemonic(pass)
    93  	require.NoError(t, err)
    94  	return cipherSeed, mnemonic
    95  }
    96  
    97  // openOrCreateTestMacStore opens or creates a bbolt DB and then initializes a
    98  // root key storage for that DB and then unlocks it, creating a root key in the
    99  // process.
   100  func openOrCreateTestMacStore(tempDir string, pw *[]byte,
   101  	netParams *chaincfg.Params) (*macaroons.RootKeyStorage, error) {
   102  
   103  	netDir := dcrwallet.NetworkDir(tempDir, netParams)
   104  	err := os.MkdirAll(netDir, 0700)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	db, err := kvdb.Create(
   109  		kvdb.BoltBackendName, path.Join(netDir, "macaroons.db"),
   110  		true, kvdb.DefaultDBTimeout,
   111  	)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	store, err := macaroons.NewRootKeyStorage(db)
   117  	if err != nil {
   118  		_ = db.Close()
   119  		return nil, err
   120  	}
   121  
   122  	err = store.CreateUnlock(pw)
   123  	if err != nil {
   124  		_ = store.Close()
   125  		return nil, err
   126  	}
   127  	_, _, err = store.RootKey(defaultRootKeyIDContext)
   128  	if err != nil {
   129  		_ = store.Close()
   130  		return nil, err
   131  	}
   132  
   133  	return store, nil
   134  }
   135  
   136  // TestGenSeedUserEntropy tests that the gen seed method generates a valid
   137  // cipher seed mnemonic phrase and user provided source of entropy.
   138  func TestGenSeed(t *testing.T) {
   139  	t.Parallel()
   140  
   141  	// First, we'll create a new test directory and unlocker service for
   142  	// that directory.
   143  	testDir, err := ioutil.TempDir("", "testcreate")
   144  	require.NoError(t, err)
   145  	defer func() {
   146  		_ = os.RemoveAll(testDir)
   147  	}()
   148  
   149  	service := walletunlocker.New(
   150  		testDir, testNetParams, nil, kvdb.DefaultDBTimeout,
   151  		"", "", "", "", 0,
   152  	)
   153  
   154  	// Now that the service has been created, we'll ask it to generate a
   155  	// new seed for us given a test passphrase.
   156  	aezeedPass := []byte("kek")
   157  	genSeedReq := &lnrpc.GenSeedRequest{
   158  		AezeedPassphrase: aezeedPass,
   159  		SeedEntropy:      testEntropy[:],
   160  	}
   161  
   162  	ctx := context.Background()
   163  	seedResp, err := service.GenSeed(ctx, genSeedReq)
   164  	require.NoError(t, err)
   165  
   166  	// We should then be able to take the generated mnemonic, and properly
   167  	// decipher both it.
   168  	var mnemonic aezeed.Mnemonic
   169  	copy(mnemonic[:], seedResp.CipherSeedMnemonic)
   170  	_, err = mnemonic.ToCipherSeed(aezeedPass)
   171  	require.NoError(t, err)
   172  }
   173  
   174  // TestGenSeedInvalidEntropy tests that the gen seed method generates a valid
   175  // cipher seed mnemonic pass phrase even when the user doesn't provide its own
   176  // source of entropy.
   177  func TestGenSeedGenerateEntropy(t *testing.T) {
   178  	t.Parallel()
   179  
   180  	// First, we'll create a new test directory and unlocker service for
   181  	// that directory.
   182  	testDir, err := ioutil.TempDir("", "testcreate")
   183  	require.NoError(t, err)
   184  	defer func() {
   185  		_ = os.RemoveAll(testDir)
   186  	}()
   187  	service := walletunlocker.New(
   188  		testDir, testNetParams, nil, kvdb.DefaultDBTimeout,
   189  		"", "", "", "", 0,
   190  	)
   191  
   192  	// Now that the service has been created, we'll ask it to generate a
   193  	// new seed for us given a test passphrase. Note that we don't actually
   194  	aezeedPass := []byte("kek")
   195  	genSeedReq := &lnrpc.GenSeedRequest{
   196  		AezeedPassphrase: aezeedPass,
   197  	}
   198  
   199  	ctx := context.Background()
   200  	seedResp, err := service.GenSeed(ctx, genSeedReq)
   201  	require.NoError(t, err)
   202  
   203  	// We should then be able to take the generated mnemonic, and properly
   204  	// decipher both it.
   205  	var mnemonic aezeed.Mnemonic
   206  	copy(mnemonic[:], seedResp.CipherSeedMnemonic)
   207  	_, err = mnemonic.ToCipherSeed(aezeedPass)
   208  	require.NoError(t, err)
   209  }
   210  
   211  // TestGenSeedInvalidEntropy tests that if a user attempt to create a seed with
   212  // the wrong number of bytes for the initial entropy, then the proper error is
   213  // returned.
   214  func TestGenSeedInvalidEntropy(t *testing.T) {
   215  	t.Parallel()
   216  
   217  	// First, we'll create a new test directory and unlocker service for
   218  	// that directory.
   219  	testDir, err := ioutil.TempDir("", "testcreate")
   220  	require.NoError(t, err)
   221  	defer func() {
   222  		_ = os.RemoveAll(testDir)
   223  	}()
   224  	service := walletunlocker.New(
   225  		testDir, testNetParams, nil, kvdb.DefaultDBTimeout,
   226  		"", "", "", "", 0,
   227  	)
   228  
   229  	// Now that the service has been created, we'll ask it to generate a
   230  	// new seed for us given a test passphrase. However, we'll be using an
   231  	// invalid set of entropy that's 55 bytes, instead of 15 bytes.
   232  	aezeedPass := []byte("kek")
   233  	genSeedReq := &lnrpc.GenSeedRequest{
   234  		AezeedPassphrase: aezeedPass,
   235  		SeedEntropy:      bytes.Repeat([]byte("a"), 55),
   236  	}
   237  
   238  	// We should get an error now since the entropy source was invalid.
   239  	ctx := context.Background()
   240  	_, err = service.GenSeed(ctx, genSeedReq)
   241  	require.Error(t, err)
   242  	require.Contains(t, err.Error(), "incorrect entropy length")
   243  }
   244  
   245  // TestInitWallet tests that the user is able to properly initialize the wallet
   246  // given an existing cipher seed passphrase.
   247  func TestInitWallet(t *testing.T) {
   248  	t.Parallel()
   249  
   250  	// testDir is empty, meaning wallet was not created from before.
   251  	testDir, err := ioutil.TempDir("", "testcreate")
   252  	require.NoError(t, err)
   253  	defer func() {
   254  		_ = os.RemoveAll(testDir)
   255  	}()
   256  
   257  	// Create new UnlockerService.
   258  	service := walletunlocker.New(
   259  		testDir, testNetParams, nil, kvdb.DefaultDBTimeout,
   260  		"", "", "", "", 0,
   261  	)
   262  
   263  	// Once we have the unlocker service created, we'll now instantiate a
   264  	// new cipher seed and its mnemonic.
   265  	pass := []byte("test")
   266  	cipherSeed, mnemonic := createSeedAndMnemonic(t, pass)
   267  
   268  	// Now that we have all the necessary items, we'll now issue the Init
   269  	// command to the wallet. This should check the validity of the cipher
   270  	// seed, then send over the initialization information over the init
   271  	// channel.
   272  	ctx := context.Background()
   273  	req := &lnrpc.InitWalletRequest{
   274  		WalletPassword:     testPassword,
   275  		CipherSeedMnemonic: mnemonic[:],
   276  		AezeedPassphrase:   pass,
   277  		RecoveryWindow:     int32(testRecoveryWindow),
   278  		StatelessInit:      true,
   279  	}
   280  	errChan := make(chan error, 1)
   281  	go func() {
   282  		response, err := service.InitWallet(ctx, req)
   283  		if err != nil {
   284  			errChan <- err
   285  			return
   286  		}
   287  
   288  		if !bytes.Equal(response.AdminMacaroon, testMac) {
   289  			errChan <- fmt.Errorf("mismatched macaroon: "+
   290  				"expected %x, got %x", testMac,
   291  				response.AdminMacaroon)
   292  		}
   293  	}()
   294  
   295  	// The same user passphrase, and also the plaintext cipher seed
   296  	// should be sent over and match exactly.
   297  	select {
   298  	case err := <-errChan:
   299  		t.Fatalf("InitWallet call failed: %v", err)
   300  
   301  	case msg := <-service.InitMsgs:
   302  		msgSeed := msg.WalletSeed
   303  		require.Equal(t, testPassword, msg.Passphrase)
   304  		require.Equal(
   305  			t, cipherSeed.InternalVersion, msgSeed.InternalVersion,
   306  		)
   307  		require.Equal(t, cipherSeed.Birthday, msgSeed.Birthday)
   308  		require.Equal(t, cipherSeed.Entropy, msgSeed.Entropy)
   309  		require.Equal(t, testRecoveryWindow, msg.RecoveryWindow)
   310  		require.Equal(t, true, msg.StatelessInit)
   311  
   312  		// Send a fake macaroon that should be returned in the response
   313  		// in the async code above.
   314  		service.MacResponseChan <- testMac
   315  
   316  	case <-time.After(defaultTestTimeout):
   317  		t.Fatalf("password not received")
   318  	}
   319  
   320  	// Create a wallet in testDir.
   321  	createTestWallet(t, testDir, testNetParams)
   322  
   323  	// Now calling InitWallet should fail, since a wallet already exists in
   324  	// the directory.
   325  	_, err = service.InitWallet(ctx, req)
   326  	require.Error(t, err)
   327  
   328  	// Similarly, if we try to do GenSeed again, we should get an error as
   329  	// the wallet already exists.
   330  	_, err = service.GenSeed(ctx, &lnrpc.GenSeedRequest{})
   331  	require.Error(t, err)
   332  }
   333  
   334  // TestInitWalletInvalidCipherSeed tests that if we attempt to create a wallet
   335  // with an invalid cipher seed, then we'll receive an error.
   336  func TestCreateWalletInvalidEntropy(t *testing.T) {
   337  	t.Parallel()
   338  
   339  	// testDir is empty, meaning wallet was not created from before.
   340  	testDir, err := ioutil.TempDir("", "testcreate")
   341  	require.NoError(t, err)
   342  	defer func() {
   343  		_ = os.RemoveAll(testDir)
   344  	}()
   345  
   346  	// Create new UnlockerService.
   347  	service := walletunlocker.New(
   348  		testDir, testNetParams, nil, kvdb.DefaultDBTimeout,
   349  		"", "", "", "", 0,
   350  	)
   351  
   352  	// We'll attempt to init the wallet with an invalid cipher seed and
   353  	// passphrase.
   354  	req := &lnrpc.InitWalletRequest{
   355  		WalletPassword:     testPassword,
   356  		CipherSeedMnemonic: []string{"invalid", "seed"},
   357  		AezeedPassphrase:   []byte("fake pass"),
   358  	}
   359  
   360  	ctx := context.Background()
   361  	_, err = service.InitWallet(ctx, req)
   362  	require.Error(t, err)
   363  }
   364  
   365  // TestUnlockWallet checks that trying to unlock non-existing wallet fail, that
   366  // unlocking existing wallet with wrong passphrase fails, and that unlocking
   367  // existing wallet with correct passphrase succeeds.
   368  func TestUnlockWallet(t *testing.T) {
   369  	t.Parallel()
   370  
   371  	// testDir is empty, meaning wallet was not created from before.
   372  	testDir, err := ioutil.TempDir("", "testunlock")
   373  	require.NoError(t, err)
   374  	defer func() {
   375  		_ = os.RemoveAll(testDir)
   376  	}()
   377  
   378  	// Create new UnlockerService that'll also drop the wallet's history on
   379  	// unlock.
   380  	service := walletunlocker.New(
   381  		testDir, testNetParams, nil, kvdb.DefaultDBTimeout,
   382  		"", "", "", "", 0,
   383  	)
   384  
   385  	ctx := context.Background()
   386  	req := &lnrpc.UnlockWalletRequest{
   387  		WalletPassword: testPassword,
   388  		RecoveryWindow: int32(testRecoveryWindow),
   389  		StatelessInit:  true,
   390  	}
   391  
   392  	// Should fail to unlock non-existing wallet.
   393  	_, err = service.UnlockWallet(ctx, req)
   394  	require.Error(t, err)
   395  
   396  	// Create a wallet we can try to unlock.
   397  	createTestWallet(t, testDir, testNetParams)
   398  
   399  	// Try unlocking this wallet with the wrong passphrase.
   400  	wrongReq := &lnrpc.UnlockWalletRequest{
   401  		WalletPassword: []byte("wrong-ofc"),
   402  	}
   403  	_, err = service.UnlockWallet(ctx, wrongReq)
   404  	require.Error(t, err)
   405  
   406  	// With the correct password, we should be able to unlock the wallet.
   407  	errChan := make(chan error, 1)
   408  	go func() {
   409  		// With the correct password, we should be able to unlock the
   410  		// wallet.
   411  		_, err := service.UnlockWallet(ctx, req)
   412  		if err != nil {
   413  			errChan <- err
   414  		}
   415  	}()
   416  
   417  	// Password and recovery window should be sent over the channel.
   418  	select {
   419  	case err := <-errChan:
   420  		t.Fatalf("UnlockWallet call failed: %v", err)
   421  
   422  	case unlockMsg := <-service.UnlockMsgs:
   423  		require.Equal(t, testPassword, unlockMsg.Passphrase)
   424  		require.Equal(t, testRecoveryWindow, unlockMsg.RecoveryWindow)
   425  		require.Equal(t, true, unlockMsg.StatelessInit)
   426  
   427  		// Send a fake macaroon that should be returned in the response
   428  		// in the async code above.
   429  		service.MacResponseChan <- testMac
   430  
   431  	case <-time.After(defaultTestTimeout):
   432  		t.Fatalf("password not received")
   433  	}
   434  }
   435  
   436  // TestChangeWalletPasswordNewRootkey tests that we can successfully change the
   437  // wallet's password needed to unlock it and rotate the root key for the
   438  // macaroons in the same process.
   439  func TestChangeWalletPasswordNewRootkey(t *testing.T) {
   440  	t.Parallel()
   441  
   442  	// testDir is empty, meaning wallet was not created from before.
   443  	testDir, err := ioutil.TempDir("", "testchangepassword")
   444  	require.NoError(t, err)
   445  	defer func() {
   446  		_ = os.RemoveAll(testDir)
   447  	}()
   448  
   449  	// Changing the password of the wallet will also try to change the
   450  	// password of the macaroon DB. We create a default DB here but close it
   451  	// immediately so the service does not fail when trying to open it.
   452  	store, err := openOrCreateTestMacStore(
   453  		testDir, &testPassword, testNetParams,
   454  	)
   455  	require.NoError(t, err)
   456  	require.NoError(t, store.Close())
   457  
   458  	// Create some files that will act as macaroon files that should be
   459  	// deleted after a password change is successful with a new root key
   460  	// requested.
   461  	var tempFiles []string
   462  	for i := 0; i < 3; i++ {
   463  		file, err := ioutil.TempFile(testDir, "")
   464  		if err != nil {
   465  			t.Fatalf("unable to create temp file: %v", err)
   466  		}
   467  		tempFiles = append(tempFiles, file.Name())
   468  		require.NoError(t, file.Close())
   469  	}
   470  
   471  	// Create a new UnlockerService with our temp files.
   472  	service := walletunlocker.New(
   473  		testDir, testNetParams, tempFiles, kvdb.DefaultDBTimeout,
   474  		"", "", "", "", 0,
   475  	)
   476  	service.SetMacaroonDB(store.Backend)
   477  
   478  	ctx := context.Background()
   479  	newPassword := []byte("hunter2???")
   480  
   481  	req := &lnrpc.ChangePasswordRequest{
   482  		CurrentPassword:    testPassword,
   483  		NewPassword:        newPassword,
   484  		NewMacaroonRootKey: true,
   485  	}
   486  
   487  	// Changing the password to a non-existing wallet should fail.
   488  	_, err = service.ChangePassword(ctx, req)
   489  	require.Error(t, err)
   490  
   491  	// Create a wallet to test changing the password.
   492  	createTestWallet(t, testDir, testNetParams)
   493  
   494  	// Attempting to change the wallet's password using an incorrect
   495  	// current password should fail.
   496  	wrongReq := &lnrpc.ChangePasswordRequest{
   497  		CurrentPassword: []byte("wrong-ofc"),
   498  		NewPassword:     newPassword,
   499  	}
   500  	_, err = service.ChangePassword(ctx, wrongReq)
   501  	require.Error(t, err)
   502  
   503  	// The files should still exist after an unsuccessful attempt to change
   504  	// the wallet's password.
   505  	for _, tempFile := range tempFiles {
   506  		if _, err := os.Stat(tempFile); os.IsNotExist(err) {
   507  			t.Fatal("file does not exist but it should")
   508  		}
   509  	}
   510  
   511  	// Attempting to change the wallet's password using an invalid
   512  	// new password should fail.
   513  	wrongReq.NewPassword = []byte("8")
   514  	_, err = service.ChangePassword(ctx, wrongReq)
   515  	require.Error(t, err)
   516  
   517  	// When providing the correct wallet's current password and a new
   518  	// password that meets the length requirement, the password change
   519  	// should succeed.
   520  	errChan := make(chan error, 1)
   521  	go doChangePassword(service, testDir, req, errChan)
   522  
   523  	// The new password should be sent over the channel.
   524  	select {
   525  	case err := <-errChan:
   526  		t.Fatalf("ChangePassword call failed: %v", err)
   527  
   528  	case unlockMsg := <-service.UnlockMsgs:
   529  		require.Equal(t, newPassword, unlockMsg.Passphrase)
   530  
   531  		// Send a fake macaroon that should be returned in the response
   532  		// in the async code above.
   533  		service.MacResponseChan <- testMac
   534  
   535  	case <-time.After(defaultTestTimeout):
   536  		t.Fatalf("password not received")
   537  	}
   538  
   539  	// The files should no longer exist.
   540  	for _, tempFile := range tempFiles {
   541  		if _, err := os.Open(tempFile); err == nil {
   542  			t.Fatal("file exists but it shouldn't")
   543  		}
   544  	}
   545  }
   546  
   547  // TestChangeWalletPasswordStateless checks that trying to change the password
   548  // of an existing wallet that was initialized stateless works when when the
   549  // --stateless_init flat is set. Also checks that if no password is given,
   550  // the default password is used.
   551  func TestChangeWalletPasswordStateless(t *testing.T) {
   552  	t.Parallel()
   553  
   554  	// testDir is empty, meaning wallet was not created from before.
   555  	testDir, err := ioutil.TempDir("", "testchangepasswordstateless")
   556  	require.NoError(t, err)
   557  	defer func() {
   558  		_ = os.RemoveAll(testDir)
   559  	}()
   560  
   561  	// Changing the password of the wallet will also try to change the
   562  	// password of the macaroon DB. We create a default DB here but close it
   563  	// immediately so the service does not fail when trying to open it.
   564  	store, err := openOrCreateTestMacStore(
   565  		testDir, &lnwallet.DefaultPrivatePassphrase, testNetParams,
   566  	)
   567  	require.NoError(t, err)
   568  	require.NoError(t, store.Close())
   569  
   570  	// Create a temp file that will act as the macaroon DB file that will
   571  	// be deleted by changing the password.
   572  	tmpFile, err := ioutil.TempFile(testDir, "")
   573  	require.NoError(t, err)
   574  	tempMacFile := tmpFile.Name()
   575  	err = tmpFile.Close()
   576  	require.NoError(t, err)
   577  
   578  	// Create a file name that does not exist that will be used as a
   579  	// macaroon file reference. The fact that the file does not exist should
   580  	// not throw an error when --stateless_init is used.
   581  	nonExistingFile := path.Join(testDir, "does-not-exist")
   582  
   583  	// Create a new UnlockerService with our temp files.
   584  	service := walletunlocker.New(
   585  		testDir, testNetParams, []string{
   586  			tempMacFile, nonExistingFile,
   587  		}, kvdb.DefaultDBTimeout, "", "", "", "", 0,
   588  	)
   589  	service.SetMacaroonDB(store.Backend)
   590  
   591  	// Create a wallet we can try to unlock. We use the default password
   592  	// so we can check that the unlocker service defaults to this when
   593  	// we give it an empty CurrentPassword to indicate we come from a
   594  	// --noencryptwallet state.
   595  	createTestWalletWithPw(
   596  		t, lnwallet.DefaultPublicPassphrase,
   597  		lnwallet.DefaultPrivatePassphrase, testDir, testNetParams,
   598  	)
   599  
   600  	// We make sure that we get a proper error message if we forget to
   601  	// add the --stateless_init flag but the macaroon files don't exist.
   602  	badReq := &lnrpc.ChangePasswordRequest{
   603  		NewPassword:        testPassword,
   604  		NewMacaroonRootKey: true,
   605  	}
   606  	ctx := context.Background()
   607  	_, err = service.ChangePassword(ctx, badReq)
   608  	require.Error(t, err)
   609  
   610  	// Prepare the correct request we are going to send to the unlocker
   611  	// service. We don't provide a current password to indicate there
   612  	// was none set before.
   613  	req := &lnrpc.ChangePasswordRequest{
   614  		NewPassword:        testPassword,
   615  		StatelessInit:      true,
   616  		NewMacaroonRootKey: true,
   617  	}
   618  
   619  	// Since we indicated the wallet was initialized stateless, the service
   620  	// will block until it receives the macaroon through the channel
   621  	// provided in the message in UnlockMsgs. So we need to call the service
   622  	// async and then wait for the unlock message to arrive so we can send
   623  	// back a fake macaroon.
   624  	errChan := make(chan error, 1)
   625  	go doChangePassword(service, testDir, req, errChan)
   626  
   627  	// Password and recovery window should be sent over the channel.
   628  	select {
   629  	case err := <-errChan:
   630  		t.Fatalf("ChangePassword call failed: %v", err)
   631  
   632  	case unlockMsg := <-service.UnlockMsgs:
   633  		require.Equal(t, testPassword, unlockMsg.Passphrase)
   634  
   635  		// Send a fake macaroon that should be returned in the response
   636  		// in the async code above.
   637  		service.MacResponseChan <- testMac
   638  
   639  	case <-time.After(defaultTestTimeout):
   640  		t.Fatalf("password not received")
   641  	}
   642  }
   643  
   644  func doChangePassword(service *walletunlocker.UnlockerService, testDir string,
   645  	req *lnrpc.ChangePasswordRequest, errChan chan error) {
   646  
   647  	// When providing the correct wallet's current password and a
   648  	// new password that meets the length requirement, the password
   649  	// change should succeed.
   650  	ctx := context.Background()
   651  	response, err := service.ChangePassword(ctx, req)
   652  	if err != nil {
   653  		errChan <- fmt.Errorf("could not change password: %v", err)
   654  		return
   655  	}
   656  
   657  	if !bytes.Equal(response.AdminMacaroon, testMac) {
   658  		errChan <- fmt.Errorf("mismatched macaroon: expected "+
   659  			"%x, got %x", testMac, response.AdminMacaroon)
   660  	}
   661  
   662  	// Close the macaroon DB and try to open it and read the root
   663  	// key with the new password.
   664  	store, err := openOrCreateTestMacStore(
   665  		testDir, &testPassword, testNetParams,
   666  	)
   667  	if err != nil {
   668  		errChan <- fmt.Errorf("could not create test store: %v", err)
   669  		return
   670  	}
   671  	_, _, err = store.RootKey(defaultRootKeyIDContext)
   672  	if err != nil {
   673  		errChan <- fmt.Errorf("could not get root key: %v", err)
   674  		return
   675  	}
   676  
   677  	// Do cleanup now. Since we are in a go func, the defer at the
   678  	// top of the outer would not work, because it would delete
   679  	// the directory before we could check the content in here.
   680  	err = store.Close()
   681  	if err != nil {
   682  		errChan <- fmt.Errorf("could not close store: %v", err)
   683  		return
   684  	}
   685  	err = os.RemoveAll(testDir)
   686  	if err != nil {
   687  		errChan <- err
   688  		return
   689  	}
   690  }