Merge pull request #618 from wpaulino/change-wallet-password
Change wallet password
This commit is contained in:
11 files changed
+990
-574
No files matched your search
+66
-6
@@ -1382,6 +1382,66 @@ func unlock(ctx *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var changePasswordCommand = cli.Command{
|
||||||
|
Name: "changepassword",
|
||||||
|
Category: "Startup",
|
||||||
|
Usage: "Change an encrypted wallet's password at startup.",
|
||||||
|
Description: `
|
||||||
|
The changepassword command is used to Change lnd's encrypted wallet's
|
||||||
|
password. It will automatically unlock the daemon if the password change
|
||||||
|
is successful.
|
||||||
|
|
||||||
|
If one did not specify a password for their wallet (running lnd with
|
||||||
|
--noencryptwallet), one must restart their daemon without
|
||||||
|
--noencryptwallet and use this command. The "current password" field
|
||||||
|
should be left empty.
|
||||||
|
`,
|
||||||
|
Action: actionDecorator(changePassword),
|
||||||
|
}
|
||||||
|
|
||||||
|
func changePassword(ctx *cli.Context) error {
|
||||||
|
ctxb := context.Background()
|
||||||
|
client, cleanUp := getWalletUnlockerClient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
fmt.Printf("Input current wallet password: ")
|
||||||
|
currentPw, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Printf("Input new wallet password: ")
|
||||||
|
newPw, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Printf("Confirm new wallet password: ")
|
||||||
|
confirmPw, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
if !bytes.Equal(newPw, confirmPw) {
|
||||||
|
return fmt.Errorf("passwords don't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &lnrpc.ChangePasswordRequest{
|
||||||
|
CurrentPassword: currentPw,
|
||||||
|
NewPassword: newPw,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.ChangePassword(ctxb, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var walletBalanceCommand = cli.Command{
|
var walletBalanceCommand = cli.Command{
|
||||||
Name: "walletbalance",
|
Name: "walletbalance",
|
||||||
Category: "Wallet",
|
Category: "Wallet",
|
||||||
@@ -1407,9 +1467,9 @@ func walletBalance(ctx *cli.Context) error {
|
|||||||
var channelBalanceCommand = cli.Command{
|
var channelBalanceCommand = cli.Command{
|
||||||
Name: "channelbalance",
|
Name: "channelbalance",
|
||||||
Category: "Channels",
|
Category: "Channels",
|
||||||
Usage: "Returns the sum of the total available channel balance across " +
|
Usage: "Returns the sum of the total available channel balance across " +
|
||||||
"all open channels.",
|
"all open channels.",
|
||||||
Action: actionDecorator(channelBalance),
|
Action: actionDecorator(channelBalance),
|
||||||
}
|
}
|
||||||
|
|
||||||
func channelBalance(ctx *cli.Context) error {
|
func channelBalance(ctx *cli.Context) error {
|
||||||
@@ -2356,7 +2416,7 @@ func queryRoutes(ctx *cli.Context) error {
|
|||||||
var getNetworkInfoCommand = cli.Command{
|
var getNetworkInfoCommand = cli.Command{
|
||||||
Name: "getnetworkinfo",
|
Name: "getnetworkinfo",
|
||||||
Category: "Channels",
|
Category: "Channels",
|
||||||
Usage: "Get statistical information about the current " +
|
Usage: "Get statistical information about the current " +
|
||||||
"state of the network.",
|
"state of the network.",
|
||||||
Description: "Returns a set of statistics pertaining to the known " +
|
Description: "Returns a set of statistics pertaining to the known " +
|
||||||
"channel graph",
|
"channel graph",
|
||||||
@@ -2639,9 +2699,9 @@ func feeReport(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updateChannelPolicyCommand = cli.Command{
|
var updateChannelPolicyCommand = cli.Command{
|
||||||
Name: "updatechanpolicy",
|
Name: "updatechanpolicy",
|
||||||
Category: "Channels",
|
Category: "Channels",
|
||||||
Usage: "Update the channel policy for all channels, or a single " +
|
Usage: "Update the channel policy for all channels, or a single " +
|
||||||
"channel.",
|
"channel.",
|
||||||
ArgsUsage: "base_fee_msat fee_rate time_lock_delta [channel_point]",
|
ArgsUsage: "base_fee_msat fee_rate time_lock_delta [channel_point]",
|
||||||
Description: `
|
Description: `
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ func main() {
|
|||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
createCommand,
|
createCommand,
|
||||||
unlockCommand,
|
unlockCommand,
|
||||||
|
changePasswordCommand,
|
||||||
newAddressCommand,
|
newAddressCommand,
|
||||||
sendManyCommand,
|
sendManyCommand,
|
||||||
sendCoinsCommand,
|
sendCoinsCommand,
|
||||||
|
|||||||
@@ -195,32 +195,20 @@ func lndMain() error {
|
|||||||
}
|
}
|
||||||
proxyOpts := []grpc.DialOption{grpc.WithTransportCredentials(cCreds)}
|
proxyOpts := []grpc.DialOption{grpc.WithTransportCredentials(cCreds)}
|
||||||
|
|
||||||
var macaroonService *macaroons.Service
|
|
||||||
if !cfg.NoMacaroons {
|
|
||||||
// Create the macaroon authentication/authorization service.
|
|
||||||
macaroonService, err = macaroons.NewService(macaroonDatabaseDir,
|
|
||||||
macaroons.IPLockChecker)
|
|
||||||
if err != nil {
|
|
||||||
srvrLog.Errorf("unable to create macaroon service: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer macaroonService.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
privateWalletPw = []byte("hello")
|
privateWalletPw = lnwallet.DefaultPrivatePassphrase
|
||||||
publicWalletPw = []byte("public")
|
publicWalletPw = lnwallet.DefaultPublicPassphrase
|
||||||
birthday time.Time
|
birthday time.Time
|
||||||
recoveryWindow uint32
|
recoveryWindow uint32
|
||||||
)
|
)
|
||||||
|
|
||||||
// We wait until the user provides a password over RPC. In case lnd is
|
// We wait until the user provides a password over RPC. In case lnd is
|
||||||
// started with the --noencryptwallet flag, we use the default password
|
// started with the --noencryptwallet flag, we use the default password
|
||||||
// "hello" for wallet encryption.
|
// for wallet encryption.
|
||||||
if !cfg.NoEncryptWallet {
|
if !cfg.NoEncryptWallet {
|
||||||
walletInitParams, err := waitForWalletPassword(
|
walletInitParams, err := waitForWalletPassword(
|
||||||
cfg.RPCListeners, cfg.RESTListeners, serverOpts,
|
cfg.RPCListeners, cfg.RESTListeners, serverOpts,
|
||||||
proxyOpts, tlsConf, macaroonService,
|
proxyOpts, tlsConf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -238,12 +226,20 @@ func lndMain() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var macaroonService *macaroons.Service
|
||||||
if !cfg.NoMacaroons {
|
if !cfg.NoMacaroons {
|
||||||
|
// Create the macaroon authentication/authorization service.
|
||||||
|
macaroonService, err = macaroons.NewService(macaroonDatabaseDir,
|
||||||
|
macaroons.IPLockChecker)
|
||||||
|
if err != nil {
|
||||||
|
srvrLog.Errorf("unable to create macaroon service: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer macaroonService.Close()
|
||||||
|
|
||||||
// Try to unlock the macaroon store with the private password.
|
// Try to unlock the macaroon store with the private password.
|
||||||
// Ignore ErrAlreadyUnlocked since it could be unlocked by the
|
|
||||||
// wallet unlocker.
|
|
||||||
err = macaroonService.CreateUnlock(&privateWalletPw)
|
err = macaroonService.CreateUnlock(&privateWalletPw)
|
||||||
if err != nil && err != macaroons.ErrAlreadyUnlocked {
|
if err != nil {
|
||||||
srvrLog.Error(err)
|
srvrLog.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -879,23 +875,30 @@ type WalletUnlockParams struct {
|
|||||||
// waitForWalletPassword will spin up gRPC and REST endpoints for the
|
// waitForWalletPassword will spin up gRPC and REST endpoints for the
|
||||||
// WalletUnlocker server, and block until a password is provided by
|
// WalletUnlocker server, and block until a password is provided by
|
||||||
// the user to this RPC server.
|
// the user to this RPC server.
|
||||||
func waitForWalletPassword(
|
func waitForWalletPassword(grpcEndpoints, restEndpoints []string,
|
||||||
grpcEndpoints, restEndpoints []string,
|
serverOpts []grpc.ServerOption, proxyOpts []grpc.DialOption,
|
||||||
serverOpts []grpc.ServerOption,
|
tlsConf *tls.Config) (*WalletUnlockParams, error) {
|
||||||
proxyOpts []grpc.DialOption,
|
|
||||||
tlsConf *tls.Config,
|
|
||||||
macaroonService *macaroons.Service) (*WalletUnlockParams, error) {
|
|
||||||
|
|
||||||
// Set up a new PasswordService, which will listen
|
// Set up a new PasswordService, which will listen for passwords
|
||||||
// for passwords provided over RPC.
|
// provided over RPC.
|
||||||
grpcServer := grpc.NewServer(serverOpts...)
|
grpcServer := grpc.NewServer(serverOpts...)
|
||||||
|
|
||||||
chainConfig := cfg.Bitcoin
|
chainConfig := cfg.Bitcoin
|
||||||
if registeredChains.PrimaryChain() == litecoinChain {
|
if registeredChains.PrimaryChain() == litecoinChain {
|
||||||
chainConfig = cfg.Litecoin
|
chainConfig = cfg.Litecoin
|
||||||
}
|
}
|
||||||
pwService := walletunlocker.New(macaroonService,
|
|
||||||
chainConfig.ChainDir, activeNetParams.Params)
|
// The macaroon files are passed to the wallet unlocker since they are
|
||||||
|
// also encrypted with the wallet's password. These files will be
|
||||||
|
// deleted within it and recreated when successfully changing the
|
||||||
|
// wallet's password.
|
||||||
|
macaroonFiles := []string{
|
||||||
|
filepath.Join(macaroonDatabaseDir, macaroons.DBFilename),
|
||||||
|
cfg.AdminMacPath, cfg.ReadMacPath, cfg.InvoiceMacPath,
|
||||||
|
}
|
||||||
|
pwService := walletunlocker.New(
|
||||||
|
chainConfig.ChainDir, activeNetParams.Params, macaroonFiles,
|
||||||
|
)
|
||||||
lnrpc.RegisterWalletUnlockerServer(grpcServer, pwService)
|
lnrpc.RegisterWalletUnlockerServer(grpcServer, pwService)
|
||||||
|
|
||||||
// Use a WaitGroup so we can be sure the instructions on how to input the
|
// Use a WaitGroup so we can be sure the instructions on how to input the
|
||||||
@@ -957,9 +960,10 @@ func waitForWalletPassword(
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
// Wait for user to provide the password.
|
// Wait for user to provide the password.
|
||||||
ltndLog.Infof("Waiting for wallet encryption password. " +
|
ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
|
||||||
"Use `lncli create` to create wallet, or " +
|
"create` to create a wallet, `lncli unlock` to unlock an " +
|
||||||
"`lncli unlock` to unlock already created wallet.")
|
"existing wallet, or `lncli changepassword` to change the " +
|
||||||
|
"password of an existing wallet and unlock it.")
|
||||||
|
|
||||||
// We currently don't distinguish between getting a password to be used
|
// We currently don't distinguish between getting a password to be used
|
||||||
// for creation or unlocking, as a new wallet db will be created if
|
// for creation or unlocking, as a new wallet db will be created if
|
||||||
|
|||||||
+543
-465
File diff suppressed because it is too large.
Load diff
+48
-18
@@ -71,6 +71,19 @@ func request_WalletUnlocker_UnlockWallet_0(ctx context.Context, marshaler runtim
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func request_WalletUnlocker_ChangePassword_0(ctx context.Context, marshaler runtime.Marshaler, client WalletUnlockerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq ChangePasswordRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.ChangePassword(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func request_Lightning_WalletBalance_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
func request_Lightning_WalletBalance_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
var protoReq WalletBalanceRequest
|
var protoReq WalletBalanceRequest
|
||||||
var metadata runtime.ServerMetadata
|
var metadata runtime.ServerMetadata
|
||||||
@@ -592,15 +605,7 @@ func RegisterWalletUnlockerHandlerFromEndpoint(ctx context.Context, mux *runtime
|
|||||||
// RegisterWalletUnlockerHandler registers the http handlers for service WalletUnlocker to "mux".
|
// RegisterWalletUnlockerHandler registers the http handlers for service WalletUnlocker to "mux".
|
||||||
// The handlers forward requests to the grpc endpoint over "conn".
|
// The handlers forward requests to the grpc endpoint over "conn".
|
||||||
func RegisterWalletUnlockerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
func RegisterWalletUnlockerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||||
return RegisterWalletUnlockerHandlerClient(ctx, mux, NewWalletUnlockerClient(conn))
|
client := NewWalletUnlockerClient(conn)
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterWalletUnlockerHandler registers the http handlers for service WalletUnlocker to "mux".
|
|
||||||
// The handlers forward requests to the grpc endpoint over the given implementation of "WalletUnlockerClient".
|
|
||||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WalletUnlockerClient"
|
|
||||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
|
||||||
// "WalletUnlockerClient" to call the correct interceptors.
|
|
||||||
func RegisterWalletUnlockerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WalletUnlockerClient) error {
|
|
||||||
|
|
||||||
mux.Handle("GET", pattern_WalletUnlocker_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("GET", pattern_WalletUnlocker_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
@@ -689,6 +694,35 @@ func RegisterWalletUnlockerHandlerClient(ctx context.Context, mux *runtime.Serve
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_WalletUnlocker_ChangePassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
if cn, ok := w.(http.CloseNotifier); ok {
|
||||||
|
go func(done <-chan struct{}, closed <-chan bool) {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-closed:
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}(ctx.Done(), cn.CloseNotify())
|
||||||
|
}
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_WalletUnlocker_ChangePassword_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_WalletUnlocker_ChangePassword_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,6 +732,8 @@ var (
|
|||||||
pattern_WalletUnlocker_InitWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "initwallet"}, ""))
|
pattern_WalletUnlocker_InitWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "initwallet"}, ""))
|
||||||
|
|
||||||
pattern_WalletUnlocker_UnlockWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "unlockwallet"}, ""))
|
pattern_WalletUnlocker_UnlockWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "unlockwallet"}, ""))
|
||||||
|
|
||||||
|
pattern_WalletUnlocker_ChangePassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "changepassword"}, ""))
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -706,6 +742,8 @@ var (
|
|||||||
forward_WalletUnlocker_InitWallet_0 = runtime.ForwardResponseMessage
|
forward_WalletUnlocker_InitWallet_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_WalletUnlocker_UnlockWallet_0 = runtime.ForwardResponseMessage
|
forward_WalletUnlocker_UnlockWallet_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_WalletUnlocker_ChangePassword_0 = runtime.ForwardResponseMessage
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterLightningHandlerFromEndpoint is same as RegisterLightningHandler but
|
// RegisterLightningHandlerFromEndpoint is same as RegisterLightningHandler but
|
||||||
@@ -736,15 +774,7 @@ func RegisterLightningHandlerFromEndpoint(ctx context.Context, mux *runtime.Serv
|
|||||||
// RegisterLightningHandler registers the http handlers for service Lightning to "mux".
|
// RegisterLightningHandler registers the http handlers for service Lightning to "mux".
|
||||||
// The handlers forward requests to the grpc endpoint over "conn".
|
// The handlers forward requests to the grpc endpoint over "conn".
|
||||||
func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||||
return RegisterLightningHandlerClient(ctx, mux, NewLightningClient(conn))
|
client := NewLightningClient(conn)
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterLightningHandler registers the http handlers for service Lightning to "mux".
|
|
||||||
// The handlers forward requests to the grpc endpoint over the given implementation of "LightningClient".
|
|
||||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "LightningClient"
|
|
||||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
|
||||||
// "LightningClient" to call the correct interceptors.
|
|
||||||
func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux, client LightningClient) error {
|
|
||||||
|
|
||||||
mux.Handle("GET", pattern_Lightning_WalletBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("GET", pattern_Lightning_WalletBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|||||||
@@ -75,6 +75,17 @@ service WalletUnlocker {
|
|||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** lncli: `changepassword`
|
||||||
|
ChangePassword changes the password of the encrypted wallet. This will
|
||||||
|
automatically unlock the wallet database if successful.
|
||||||
|
*/
|
||||||
|
rpc ChangePassword (ChangePasswordRequest) returns (ChangePasswordResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v1/changepassword"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message GenSeedRequest {
|
message GenSeedRequest {
|
||||||
@@ -159,6 +170,21 @@ message UnlockWalletRequest {
|
|||||||
}
|
}
|
||||||
message UnlockWalletResponse {}
|
message UnlockWalletResponse {}
|
||||||
|
|
||||||
|
message ChangePasswordRequest {
|
||||||
|
/**
|
||||||
|
current_password should be the current valid passphrase used to unlock the
|
||||||
|
daemon.
|
||||||
|
*/
|
||||||
|
bytes current_password = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
new_password should be the new passphrase that will be needed to unlock the
|
||||||
|
daemon.
|
||||||
|
*/
|
||||||
|
bytes new_password = 2;
|
||||||
|
}
|
||||||
|
message ChangePasswordResponse {}
|
||||||
|
|
||||||
service Lightning {
|
service Lightning {
|
||||||
/** lncli: `walletbalance`
|
/** lncli: `walletbalance`
|
||||||
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
|
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
|
||||||
|
|||||||
@@ -49,6 +49,33 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/changepassword": {
|
||||||
|
"post": {
|
||||||
|
"summary": "* lncli: `changepassword`\nChangePassword changes the password of the encrypted wallet. This will\nautomatically unlock the wallet database if successful.",
|
||||||
|
"operationId": "ChangePassword",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/lnrpcChangePasswordResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/lnrpcChangePasswordRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"WalletUnlocker"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/channels": {
|
"/v1/channels": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "* lncli: `listchannels`\nListChannels returns a description of all the open channels that this node\nis a participant in.",
|
"summary": "* lncli: `listchannels`\nListChannels returns a description of all the open channels that this node\nis a participant in.",
|
||||||
@@ -920,6 +947,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lnrpcChangePasswordRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"current_password": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "*\ncurrent_password should be the current valid passphrase used to unlock the\ndaemon."
|
||||||
|
},
|
||||||
|
"new_password": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "*\nnew_password should be the new passphrase that will be needed to unlock the\ndaemon."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lnrpcChangePasswordResponse": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"lnrpcChannel": {
|
"lnrpcChannel": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+18
-8
@@ -11,10 +11,6 @@ import (
|
|||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNotMine is an error denoting that a WalletController instance is unable
|
|
||||||
// to spend a specified output.
|
|
||||||
var ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
|
|
||||||
|
|
||||||
// AddressType is an enum-like type which denotes the possible address types
|
// AddressType is an enum-like type which denotes the possible address types
|
||||||
// WalletController supports.
|
// WalletController supports.
|
||||||
type AddressType uint8
|
type AddressType uint8
|
||||||
@@ -32,10 +28,24 @@ const (
|
|||||||
UnknownAddressType
|
UnknownAddressType
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrDoubleSpend is returned from PublishTransaction in case the
|
var (
|
||||||
// tx being published is spending an output spent by a conflicting
|
// DefaultPublicPassphrase is the default public passphrase used for the
|
||||||
// transaction.
|
// wallet.
|
||||||
var ErrDoubleSpend = errors.New("Transaction rejected: output already spent")
|
DefaultPublicPassphrase = []byte("public")
|
||||||
|
|
||||||
|
// DefaultPrivatePassphrase is the default private passphrase used for
|
||||||
|
// the wallet.
|
||||||
|
DefaultPrivatePassphrase = []byte("hello")
|
||||||
|
|
||||||
|
// ErrDoubleSpend is returned from PublishTransaction in case the
|
||||||
|
// tx being published is spending an output spent by a conflicting
|
||||||
|
// transaction.
|
||||||
|
ErrDoubleSpend = errors.New("Transaction rejected: output already spent")
|
||||||
|
|
||||||
|
// ErrNotMine is an error denoting that a WalletController instance is
|
||||||
|
// unable to spend a specified output.
|
||||||
|
ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
|
||||||
|
)
|
||||||
|
|
||||||
// Utxo is an unspent output denoted by its outpoint, and output value of the
|
// Utxo is an unspent output denoted by its outpoint, and output value of the
|
||||||
// original output.
|
// original output.
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// dbFileName is the filename within the data directory which contains
|
// DBFilename is the filename within the data directory which contains
|
||||||
// the macaroon stores.
|
// the macaroon stores.
|
||||||
dbFilename = "macaroons.db"
|
DBFilename = "macaroons.db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
|
// Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
|
||||||
@@ -42,7 +42,7 @@ type Service struct {
|
|||||||
func NewService(dir string, checks ...Checker) (*Service, error) {
|
func NewService(dir string, checks ...Checker) (*Service, error) {
|
||||||
// Open the database that we'll use to store the primary macaroon key,
|
// Open the database that we'll use to store the primary macaroon key,
|
||||||
// and all generated macaroons+caveats.
|
// and all generated macaroons+caveats.
|
||||||
macaroonDB, err := bolt.Open(path.Join(dir, dbFilename), 0600,
|
macaroonDB, err := bolt.Open(path.Join(dir, DBFilename), 0600,
|
||||||
bolt.DefaultOptions)
|
bolt.DefaultOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+98
-33
@@ -2,13 +2,15 @@ package walletunlocker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/aezeed"
|
"github.com/lightningnetwork/lnd/aezeed"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||||
"github.com/lightningnetwork/lnd/macaroons"
|
|
||||||
"github.com/roasbeef/btcd/chaincfg"
|
"github.com/roasbeef/btcd/chaincfg"
|
||||||
"github.com/roasbeef/btcwallet/wallet"
|
"github.com/roasbeef/btcwallet/wallet"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
@@ -65,20 +67,21 @@ type UnlockerService struct {
|
|||||||
// sent.
|
// sent.
|
||||||
UnlockMsgs chan *WalletUnlockMsg
|
UnlockMsgs chan *WalletUnlockMsg
|
||||||
|
|
||||||
chainDir string
|
chainDir string
|
||||||
netParams *chaincfg.Params
|
netParams *chaincfg.Params
|
||||||
authSvc *macaroons.Service
|
macaroonFiles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates and returns a new UnlockerService.
|
// New creates and returns a new UnlockerService.
|
||||||
func New(authSvc *macaroons.Service, chainDir string,
|
func New(chainDir string, params *chaincfg.Params,
|
||||||
params *chaincfg.Params) *UnlockerService {
|
macaroonFiles []string) *UnlockerService {
|
||||||
|
|
||||||
return &UnlockerService{
|
return &UnlockerService{
|
||||||
InitMsgs: make(chan *WalletInitMsg, 1),
|
InitMsgs: make(chan *WalletInitMsg, 1),
|
||||||
UnlockMsgs: make(chan *WalletUnlockMsg, 1),
|
UnlockMsgs: make(chan *WalletUnlockMsg, 1),
|
||||||
chainDir: chainDir,
|
chainDir: chainDir,
|
||||||
netParams: params,
|
netParams: params,
|
||||||
|
macaroonFiles: macaroonFiles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,12 +175,10 @@ func (u *UnlockerService) GenSeed(ctx context.Context,
|
|||||||
func (u *UnlockerService) InitWallet(ctx context.Context,
|
func (u *UnlockerService) InitWallet(ctx context.Context,
|
||||||
in *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) {
|
in *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) {
|
||||||
|
|
||||||
// Require the provided password to have a length of at least 8
|
// Make sure the password meets our constraints.
|
||||||
// characters.
|
|
||||||
password := in.WalletPassword
|
password := in.WalletPassword
|
||||||
if len(password) < 8 {
|
if err := validatePassword(password); err != nil {
|
||||||
return nil, fmt.Errorf("password must have " +
|
return nil, err
|
||||||
"at least 8 characters")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require that the recovery window be non-negative.
|
// Require that the recovery window be non-negative.
|
||||||
@@ -216,15 +217,6 @@ func (u *UnlockerService) InitWallet(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to create a password for the macaroon service.
|
|
||||||
if u.authSvc != nil {
|
|
||||||
err = u.authSvc.CreateUnlock(&password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create/unlock "+
|
|
||||||
"macaroon store: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the cipher seed deciphered, and the auth service created, we'll
|
// With the cipher seed deciphered, and the auth service created, we'll
|
||||||
// now send over the wallet password and the seed. This will allow the
|
// now send over the wallet password and the seed. This will allow the
|
||||||
// daemon to initialize itself and startup.
|
// daemon to initialize itself and startup.
|
||||||
@@ -277,15 +269,6 @@ func (u *UnlockerService) UnlockWallet(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to create a password for the macaroon service.
|
|
||||||
if u.authSvc != nil {
|
|
||||||
err = u.authSvc.CreateUnlock(&password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create/unlock "+
|
|
||||||
"macaroon store: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walletUnlockMsg := &WalletUnlockMsg{
|
walletUnlockMsg := &WalletUnlockMsg{
|
||||||
Passphrase: password,
|
Passphrase: password,
|
||||||
RecoveryWindow: recoveryWindow,
|
RecoveryWindow: recoveryWindow,
|
||||||
@@ -298,3 +281,85 @@ func (u *UnlockerService) UnlockWallet(ctx context.Context,
|
|||||||
|
|
||||||
return &lnrpc.UnlockWalletResponse{}, nil
|
return &lnrpc.UnlockWalletResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChangePassword changes the password of the wallet and sends the new password
|
||||||
|
// across the UnlockPasswords channel to automatically unlock the wallet if
|
||||||
|
// successful.
|
||||||
|
func (u *UnlockerService) ChangePassword(ctx context.Context,
|
||||||
|
in *lnrpc.ChangePasswordRequest) (*lnrpc.ChangePasswordResponse, error) {
|
||||||
|
|
||||||
|
netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
|
||||||
|
loader := wallet.NewLoader(u.netParams, netDir, 0)
|
||||||
|
|
||||||
|
// First, we'll make sure the wallet exists for the specific chain and
|
||||||
|
// network.
|
||||||
|
walletExists, err := loader.WalletExists()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !walletExists {
|
||||||
|
return nil, errors.New("wallet not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicPw := in.CurrentPassword
|
||||||
|
privatePw := in.CurrentPassword
|
||||||
|
|
||||||
|
// If the current password is blank, we'll assume the user is coming
|
||||||
|
// from a --noencryptwallet state, so we'll use the default passwords.
|
||||||
|
if len(in.CurrentPassword) == 0 {
|
||||||
|
publicPw = lnwallet.DefaultPublicPassphrase
|
||||||
|
privatePw = lnwallet.DefaultPrivatePassphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the new password meets our constraints.
|
||||||
|
if err := validatePassword(in.NewPassword); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the existing wallet in order to proceed with the password change.
|
||||||
|
w, err := loader.OpenExistingWallet(publicPw, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Unload the wallet to allow lnd to open it later on.
|
||||||
|
defer loader.UnloadWallet()
|
||||||
|
|
||||||
|
// Since the macaroon database is also encrypted with the wallet's
|
||||||
|
// password, we'll remove all of the macaroon files so that they're
|
||||||
|
// re-generated at startup using the new password. We'll make sure to do
|
||||||
|
// this after unlocking the wallet to ensure macaroon files don't get
|
||||||
|
// deleted with incorrect password attempts.
|
||||||
|
for _, file := range u.macaroonFiles {
|
||||||
|
if err := os.Remove(file); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to change both the public and private passphrases for the
|
||||||
|
// wallet. This will be done atomically in order to prevent one
|
||||||
|
// passphrase change from being successful and not the other.
|
||||||
|
err = w.ChangePassphrases(
|
||||||
|
publicPw, in.NewPassword, privatePw, in.NewPassword,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to change wallet passphrase: "+
|
||||||
|
"%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, send the new password across the UnlockPasswords channel to
|
||||||
|
// automatically unlock the wallet.
|
||||||
|
u.UnlockMsgs <- &WalletUnlockMsg{Passphrase: in.NewPassword}
|
||||||
|
|
||||||
|
return &lnrpc.ChangePasswordResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validatePassword assures the password meets all of our constraints.
|
||||||
|
func validatePassword(password []byte) error {
|
||||||
|
// Passwords should have a length of at least 8 characters.
|
||||||
|
if len(password) < 8 {
|
||||||
|
return errors.New("password must have at least 8 characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -64,10 +64,9 @@ func TestGenSeed(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create temp directory: %v", err)
|
t.Fatalf("unable to create temp directory: %v", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer os.RemoveAll(testDir)
|
||||||
os.RemoveAll(testDir)
|
|
||||||
}()
|
service := walletunlocker.New(testDir, testNetParams, nil)
|
||||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
|
||||||
|
|
||||||
// Now that the service has been created, we'll ask it to generate a
|
// Now that the service has been created, we'll ask it to generate a
|
||||||
// new seed for us given a test passphrase.
|
// new seed for us given a test passphrase.
|
||||||
@@ -108,7 +107,7 @@ func TestGenSeedGenerateEntropy(t *testing.T) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
os.RemoveAll(testDir)
|
os.RemoveAll(testDir)
|
||||||
}()
|
}()
|
||||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
service := walletunlocker.New(testDir, testNetParams, nil)
|
||||||
|
|
||||||
// Now that the service has been created, we'll ask it to generate a
|
// Now that the service has been created, we'll ask it to generate a
|
||||||
// new seed for us given a test passphrase. Note that we don't actually
|
// new seed for us given a test passphrase. Note that we don't actually
|
||||||
@@ -148,7 +147,7 @@ func TestGenSeedInvalidEntropy(t *testing.T) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
os.RemoveAll(testDir)
|
os.RemoveAll(testDir)
|
||||||
}()
|
}()
|
||||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
service := walletunlocker.New(testDir, testNetParams, nil)
|
||||||
|
|
||||||
// Now that the service has been created, we'll ask it to generate a
|
// Now that the service has been created, we'll ask it to generate a
|
||||||
// new seed for us given a test passphrase. However, we'll be using an
|
// new seed for us given a test passphrase. However, we'll be using an
|
||||||
@@ -186,7 +185,7 @@ func TestInitWallet(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Create new UnlockerService.
|
// Create new UnlockerService.
|
||||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
service := walletunlocker.New(testDir, testNetParams, nil)
|
||||||
|
|
||||||
// Once we have the unlocker service created, we'll now instantiate a
|
// Once we have the unlocker service created, we'll now instantiate a
|
||||||
// new cipher seed instance.
|
// new cipher seed instance.
|
||||||
@@ -287,7 +286,7 @@ func TestCreateWalletInvalidEntropy(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Create new UnlockerService.
|
// Create new UnlockerService.
|
||||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
service := walletunlocker.New(testDir, testNetParams, nil)
|
||||||
|
|
||||||
// We'll attempt to init the wallet with an invalid cipher seed and
|
// We'll attempt to init the wallet with an invalid cipher seed and
|
||||||
// passphrase.
|
// passphrase.
|
||||||
@@ -320,7 +319,7 @@ func TestUnlockWallet(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Create new UnlockerService.
|
// Create new UnlockerService.
|
||||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
service := walletunlocker.New(testDir, testNetParams, nil)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
req := &lnrpc.UnlockWalletRequest{
|
req := &lnrpc.UnlockWalletRequest{
|
||||||
@@ -368,3 +367,101 @@ func TestUnlockWallet(t *testing.T) {
|
|||||||
t.Fatalf("password not received")
|
t.Fatalf("password not received")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestChangeWalletPassword tests that we can successfully change the wallet's
|
||||||
|
// password needed to unlock it.
|
||||||
|
func TestChangeWalletPassword(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// testDir is empty, meaning wallet was not created from before.
|
||||||
|
testDir, err := ioutil.TempDir("", "testchangepassword")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create temp directory: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
// Create some files that will act as macaroon files that should be
|
||||||
|
// deleted after a password change is successful.
|
||||||
|
var tempFiles []string
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
file, err := ioutil.TempFile(testDir, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create temp file: %v", err)
|
||||||
|
}
|
||||||
|
tempFiles = append(tempFiles, file.Name())
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new UnlockerService with our temp files.
|
||||||
|
service := walletunlocker.New(testDir, testNetParams, tempFiles)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
newPassword := []byte("hunter2???")
|
||||||
|
|
||||||
|
req := &lnrpc.ChangePasswordRequest{
|
||||||
|
CurrentPassword: testPassword,
|
||||||
|
NewPassword: newPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changing the password to a non-existing wallet should fail.
|
||||||
|
_, err = service.ChangePassword(ctx, req)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected call to ChangePassword to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a wallet to test changing the password.
|
||||||
|
createTestWallet(t, testDir, testNetParams)
|
||||||
|
|
||||||
|
// Attempting to change the wallet's password using an incorrect
|
||||||
|
// current password should fail.
|
||||||
|
wrongReq := &lnrpc.ChangePasswordRequest{
|
||||||
|
CurrentPassword: []byte("wrong-ofc"),
|
||||||
|
NewPassword: newPassword,
|
||||||
|
}
|
||||||
|
_, err = service.ChangePassword(ctx, wrongReq)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected call to ChangePassword to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The files should still exist after an unsuccessful attempt to change
|
||||||
|
// the wallet's password.
|
||||||
|
for _, tempFile := range tempFiles {
|
||||||
|
if _, err := os.Stat(tempFile); os.IsNotExist(err) {
|
||||||
|
t.Fatal("file does not exist but it should")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempting to change the wallet's password using an invalid
|
||||||
|
// new password should fail.
|
||||||
|
wrongReq.NewPassword = []byte("8")
|
||||||
|
_, err = service.ChangePassword(ctx, wrongReq)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected call to ChangePassword to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
// When providing the correct wallet's current password and a new
|
||||||
|
// password that meets the length requirement, the password change
|
||||||
|
// should succeed.
|
||||||
|
_, err = service.ChangePassword(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to change wallet's password: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The files should no longer exist.
|
||||||
|
for _, tempFile := range tempFiles {
|
||||||
|
if _, err := os.Open(tempFile); err == nil {
|
||||||
|
t.Fatal("file exists but it shouldn't")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The new password should be sent over the channel.
|
||||||
|
select {
|
||||||
|
case unlockMsg := <-service.UnlockMsgs:
|
||||||
|
if !bytes.Equal(unlockMsg.Passphrase, newPassword) {
|
||||||
|
t.Fatalf("expected to receive password %x, got %x",
|
||||||
|
testPassword, unlockMsg.Passphrase)
|
||||||
|
}
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
t.Fatalf("password not received")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user