cmd/lncli: extend initial wallet creation with aezeed seed support
In this commit, we extend the `lncli create` command to allow users to specify their own side (if they want). In the case that the user *doesn’t* specify their own seed, we’ll return the entropy generated by the wallet in a 24-word mnemonic format for easy backup. With this change, it’s now possible for users to restore an existing lnd wallet seed.
This commit is contained in:
parent
d8ce90306d
commit
3356a370c7
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@ -776,16 +777,63 @@ func listPeers(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
var createCommand = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Used to set the wallet password at lnd startup",
|
||||
Name: "create",
|
||||
Description: `
|
||||
The create command is used to initialize an lnd wallet from scratch for
|
||||
the very first time. This is interactive command with one required
|
||||
argument (the password), and one optional argument (the mnemonic
|
||||
passphrase).
|
||||
|
||||
The first argument (the password) is required and MUST be greater than
|
||||
8 characters. This will be used to encrypt the wallet within lnd. This
|
||||
MUST be remembered as it will be required to fully start up the daemon.
|
||||
|
||||
The second argument is an optional 24-word mnemonic derived from BIP
|
||||
39. If provided, then the internal wallet will use the seed derived
|
||||
from this mnemonic to generate all keys.
|
||||
|
||||
This command returns a 24-word seed in the scenario that NO mnemonic
|
||||
was provided by the user. This should be written down as it can be used
|
||||
to potentially recover all on-chain funds, and most off-chain funds as
|
||||
well.
|
||||
`,
|
||||
Action: actionDecorator(create),
|
||||
}
|
||||
|
||||
// monowidthColumns takes a set of words, and the number of desired columns,
|
||||
// and returns a new set of words that have had white space appended to the
|
||||
// word in order to create a mono-width column.
|
||||
func monowidthColumns(words []string, ncols int) []string {
|
||||
// Determine max size of words in each column.
|
||||
colWidths := make([]int, ncols)
|
||||
for i, word := range words {
|
||||
col := i % ncols
|
||||
curWidth := colWidths[col]
|
||||
if len(word) > curWidth {
|
||||
colWidths[col] = len(word)
|
||||
}
|
||||
}
|
||||
|
||||
// Append whitespace to each word to make columns mono-width.
|
||||
finalWords := make([]string, len(words))
|
||||
for i, word := range words {
|
||||
col := i % ncols
|
||||
width := colWidths[col]
|
||||
|
||||
diff := width - len(word)
|
||||
finalWords[i] = word + strings.Repeat(" ", diff)
|
||||
}
|
||||
|
||||
return finalWords
|
||||
}
|
||||
|
||||
func create(ctx *cli.Context) error {
|
||||
ctxb := context.Background()
|
||||
client, cleanUp := getWalletUnlockerClient(ctx)
|
||||
defer cleanUp()
|
||||
|
||||
// First, we'll prompt the user for their passphrase twice to ensure
|
||||
// both attempts match up properly.
|
||||
fmt.Printf("Input wallet password: ")
|
||||
pw1, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
@ -800,24 +848,176 @@ func create(ctx *cli.Context) error {
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// If the passwords don't match, then we'll return an error.
|
||||
if !bytes.Equal(pw1, pw2) {
|
||||
return fmt.Errorf("passwords don't match")
|
||||
}
|
||||
|
||||
req := &lnrpc.CreateWalletRequest{
|
||||
Password: pw1,
|
||||
// Next, we'll see if the user has 24-word mnemonic they want to use to
|
||||
// derive a seed within the wallet.
|
||||
var (
|
||||
hasMnemonic bool
|
||||
)
|
||||
|
||||
mnemonicCheck:
|
||||
for {
|
||||
fmt.Println()
|
||||
fmt.Printf("Do you have an existing cipher seed " +
|
||||
"mnemonic you want to use? (Enter y/n): ")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
answer, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
answer = strings.TrimSpace(answer)
|
||||
answer = strings.ToLower(answer)
|
||||
|
||||
switch answer {
|
||||
case "y":
|
||||
hasMnemonic = true
|
||||
break mnemonicCheck
|
||||
case "n":
|
||||
hasMnemonic = false
|
||||
break mnemonicCheck
|
||||
}
|
||||
}
|
||||
_, err = client.CreateWallet(ctxb, req)
|
||||
if err != nil {
|
||||
|
||||
// If the user *does* have an existing seed they want to use, then
|
||||
// we'll read that in directly from the terminal.
|
||||
var (
|
||||
cipherSeedMnemonic []string
|
||||
aezeedPass []byte
|
||||
)
|
||||
if hasMnemonic {
|
||||
// We'll now prompt the user to enter in their 24-word
|
||||
// mnemonic.
|
||||
fmt.Printf("Input your 24-word mnemonic separated by spaces: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
mnemonic, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We'll trim off extra spaces, and ensure the mnemonic is all
|
||||
// lower case, then populate our request.
|
||||
mnemonic = strings.TrimSpace(mnemonic)
|
||||
mnemonic = strings.ToLower(mnemonic)
|
||||
|
||||
cipherSeedMnemonic = strings.Split(mnemonic, " ")
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// Additionally, the user may have a passphrase, that will also
|
||||
// need to be provided so the daemon can properly decipher the
|
||||
// cipher seed.
|
||||
fmt.Printf("Input your cipher seed passphrase (press enter if " +
|
||||
"your seed doesn't have a passphrase): ")
|
||||
passphrase, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aezeedPass = []byte(passphrase)
|
||||
|
||||
fmt.Println()
|
||||
} else {
|
||||
// Otherwise, if the user doesn't have a mnemonic that they
|
||||
// want to use, we'll generate a fresh one with the GenSeed
|
||||
// command.
|
||||
fmt.Println("Your cipher seed can optionally be encrypted.")
|
||||
fmt.Printf("Input your passphrase you wish to encrypt it " +
|
||||
"(or press enter to proceed without a cipher seed " +
|
||||
"passphrase): ")
|
||||
aezeedPass1, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
if len(aezeedPass1) != 0 {
|
||||
fmt.Printf("Confirm cipher seed passphrase: ")
|
||||
aezeedPass2, err := terminal.ReadPassword(
|
||||
int(syscall.Stdin),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// If the passwords don't match, then we'll return an
|
||||
// error.
|
||||
if !bytes.Equal(aezeedPass1, aezeedPass2) {
|
||||
return fmt.Errorf("cipher seed pass phrases " +
|
||||
"don't match")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Generating fresh cipher seed...")
|
||||
fmt.Println()
|
||||
|
||||
genSeedReq := &lnrpc.GenSeedRequest{
|
||||
AezeedPassphrase: aezeedPass1,
|
||||
}
|
||||
seedResp, err := client.GenSeed(ctxb, genSeedReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate seed: %v", err)
|
||||
}
|
||||
|
||||
cipherSeedMnemonic = seedResp.CipherSeedMnemonic
|
||||
aezeedPass = aezeedPass1
|
||||
}
|
||||
|
||||
// Before we initialize the wallet, we'll display the cipher seed to
|
||||
// the user so they can write it down.
|
||||
mnemonicWords := cipherSeedMnemonic
|
||||
|
||||
fmt.Println("!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
|
||||
"RESTORE THE WALLET!!!\n")
|
||||
|
||||
fmt.Println("---------------BEGIN LND CIPHER SEED---------------")
|
||||
|
||||
numCols := 4
|
||||
colWords := monowidthColumns(mnemonicWords, numCols)
|
||||
for i := 0; i < len(colWords); i += numCols {
|
||||
fmt.Printf("%2d. %3s %2d. %3s %2d. %3s %2d. %3s\n",
|
||||
i+1, colWords[i], i+2, colWords[i+1], i+3,
|
||||
colWords[i+2], i+4, colWords[i+3])
|
||||
}
|
||||
|
||||
fmt.Println("---------------END LND CIPHER SEED-----------------")
|
||||
|
||||
fmt.Println("\n!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
|
||||
"RESTORE THE WALLET!!!")
|
||||
|
||||
// With either the user's prior cipher seed, or a newly generated one,
|
||||
// we'll go ahead and initialize the wallet.
|
||||
req := &lnrpc.InitWalletRequest{
|
||||
WalletPassword: pw1,
|
||||
CipherSeedMnemonic: cipherSeedMnemonic,
|
||||
AezeedPassphrase: aezeedPass,
|
||||
}
|
||||
if _, err := client.InitWallet(ctxb, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("\nlnd successfully initialized!")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var unlockCommand = cli.Command{
|
||||
Name: "unlock",
|
||||
Usage: "Unlock encrypted wallet at lnd startup",
|
||||
Name: "unlock",
|
||||
Description: `
|
||||
The unlock command is used to decrypt lnd's wallet state in order to
|
||||
start up. This command MUST be run after booting up lnd before it's
|
||||
able to carry out its duties. An exception is if a user is running with
|
||||
--noencryptwallet, then a default passphrase will be used.
|
||||
`,
|
||||
Action: actionDecorator(unlock),
|
||||
}
|
||||
|
||||
@ -834,13 +1034,15 @@ func unlock(ctx *cli.Context) error {
|
||||
fmt.Println()
|
||||
|
||||
req := &lnrpc.UnlockWalletRequest{
|
||||
Password: pw,
|
||||
WalletPassword: pw,
|
||||
}
|
||||
_, err = client.UnlockWallet(ctxb, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("\nlnd successfully unlocked!")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user