package walletunlocker import ( "fmt" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/macaroons" "github.com/roasbeef/btcd/chaincfg" "github.com/roasbeef/btcwallet/wallet" "golang.org/x/net/context" ) // UnlockerService implements the WalletUnlocker service used to provide lnd // with a password for wallet encryption at startup. type UnlockerService struct { // CreatePasswords is a channel where passwords provided by the rpc // client to be used to initially create and encrypt a wallet will be // sent. CreatePasswords chan []byte // UnlockPasswords is a channel where passwords provided by the rpc // client to be used to unlock and decrypt an existing wallet will be // sent. UnlockPasswords chan []byte chainDir string netParams *chaincfg.Params authSvc *macaroons.Service } // New creates and returns a new UnlockerService. func New(authSvc *macaroons.Service, chainDir string, params *chaincfg.Params) *UnlockerService { return &UnlockerService{ CreatePasswords: make(chan []byte, 1), UnlockPasswords: make(chan []byte, 1), chainDir: chainDir, netParams: params, } } // CreateWallet will read the password provided in the CreateWalletRequest and // send it over the CreatePasswords channel in case no wallet already exist in // the chain's wallet database directory. func (u *UnlockerService) CreateWallet(ctx context.Context, in *lnrpc.CreateWalletRequest) (*lnrpc.CreateWalletResponse, error) { // Require the provided password to have a length of at // least 8 characters. password := in.Password if len(password) < 8 { return nil, fmt.Errorf("password must have " + "at least 8 characters") } netDir := btcwallet.NetworkDir(u.chainDir, u.netParams) loader := wallet.NewLoader(u.netParams, netDir) // Check if wallet already exists. walletExists, err := loader.WalletExists() if err != nil { return nil, err } if walletExists { // Cannot create wallet if it already exists! return nil, fmt.Errorf("wallet already exists") } // 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) } } // We send the password over the CreatePasswords channel, such that it // can be used by lnd to open or create the wallet. u.CreatePasswords <- password return &lnrpc.CreateWalletResponse{}, nil } // UnlockWallet sends the password provided by the incoming UnlockWalletRequest // over the UnlockPasswords channel in case it successfully decrypts an // existing wallet found in the chain's wallet database directory. func (u *UnlockerService) UnlockWallet(ctx context.Context, in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) { netDir := btcwallet.NetworkDir(u.chainDir, u.netParams) loader := wallet.NewLoader(u.netParams, netDir) // Check if wallet already exists. walletExists, err := loader.WalletExists() if err != nil { return nil, err } if !walletExists { // Cannot unlock a wallet that does not exist! return nil, fmt.Errorf("wallet not found") } // Try opening the existing wallet with the provided password. _, err = loader.OpenExistingWallet(in.Password, false) if err != nil { // Could not open wallet, most likely this means that // provided password was incorrect. return nil, err } // We successfully opened the wallet, but we'll need to unload // it to make sure lnd can open it later. if err := loader.UnloadWallet(); err != nil { // TODO: not return error here? return nil, err } // Attempt to create a password for the macaroon service. if u.authSvc != nil { err = u.authSvc.CreateUnlock(&in.Password) if err != nil { return nil, fmt.Errorf("unable to create/unlock "+ "macaroon store: %v", err) } } // At this point we was able to open the existing wallet with the // provided password. We send the password over the UnlockPasswords // channel, such that it can be used by lnd to open the wallet. u.UnlockPasswords <- in.Password return &lnrpc.UnlockWalletResponse{}, nil }