lncli: allow PSBT to be read from file

Fixes #5080.

The N_TTY_BUF_SIZE kernel parameter dictates how many characters can be
pasted into a terminal window. This cannot be circumvented by reading
the input in a different manner.
To avoid the problem fully, we instead allso allow the user to type in a
path to a text file that is read if it exists. That way the PSBT can be
as long as needed.
This commit is contained in:
Oliver Gugger 2021-03-08 11:37:14 +01:00
parent 4bab68a808
commit b3dfe5d1af
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -8,6 +8,8 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os"
"strconv" "strconv"
"strings" "strings"
@ -39,14 +41,23 @@ DO NOT PUBLISH the finished transaction by yourself or with another tool.
lnd MUST publish it in the proper funding flow order OR THE FUNDS CAN BE LOST! lnd MUST publish it in the proper funding flow order OR THE FUNDS CAN BE LOST!
Paste the funded PSBT here to continue the funding flow. Paste the funded PSBT here to continue the funding flow.
Base64 encoded PSBT: ` If your PSBT is very long (specifically, more than 4096 characters), please save
it to a file and paste the full file path here instead as some terminals will
truncate the pasted text if it's too long.
Base64 encoded PSBT (or path to text file): `
userMsgSign = ` userMsgSign = `
PSBT verified by lnd, please continue the funding flow by signing the PSBT by PSBT verified by lnd, please continue the funding flow by signing the PSBT by
all required parties/devices. Once the transaction is fully signed, paste it all required parties/devices. Once the transaction is fully signed, paste it
again here either in base64 PSBT or hex encoded raw wire TX format. again here either in base64 PSBT or hex encoded raw wire TX format.
Signed base64 encoded PSBT or hex encoded raw wire TX: ` Signed base64 encoded PSBT or hex encoded raw wire TX (or path to text file): `
// psbtMaxFileSize is the maximum file size we allow a PSBT file to be
// in case we want to read a PSBT from a file. This is mainly to protect
// the user from choosing a large file by accident and running into out
// of memory issues or other weird errors.
psbtMaxFileSize = 1024 * 1024
) )
// TODO(roasbeef): change default number of confirmations // TODO(roasbeef): change default number of confirmations
@ -506,13 +517,13 @@ func openChannelPsbt(ctx *cli.Context, client lnrpc.LightningClient,
// Read the user's response and send it to the server to // Read the user's response and send it to the server to
// verify everything's correct before anything is // verify everything's correct before anything is
// signed. // signed.
psbtBase64, err := readLine(quit) psbtBase64, err := readTerminalOrFile(quit)
if err == io.EOF { if err == io.EOF {
return nil return nil
} }
if err != nil { if err != nil {
return fmt.Errorf("reading from console "+ return fmt.Errorf("reading from terminal or "+
"failed: %v", err) "file failed: %v", err)
} }
fundedPsbt, err := base64.StdEncoding.DecodeString( fundedPsbt, err := base64.StdEncoding.DecodeString(
strings.TrimSpace(psbtBase64), strings.TrimSpace(psbtBase64),
@ -540,13 +551,13 @@ func openChannelPsbt(ctx *cli.Context, client lnrpc.LightningClient,
fmt.Print(userMsgSign) fmt.Print(userMsgSign)
// Read the signed PSBT and send it to lnd. // Read the signed PSBT and send it to lnd.
finalTxStr, err := readLine(quit) finalTxStr, err := readTerminalOrFile(quit)
if err == io.EOF { if err == io.EOF {
return nil return nil
} }
if err != nil { if err != nil {
return fmt.Errorf("reading from console "+ return fmt.Errorf("reading from terminal or "+
"failed: %v", err) "file failed: %v", err)
} }
finalizeMsg, err := finalizeMsgFromString( finalizeMsg, err := finalizeMsgFromString(
finalTxStr, pendingChanID[:], finalTxStr, pendingChanID[:],
@ -635,6 +646,60 @@ func printChanPending(update *lnrpc.OpenStatusUpdate_ChanPending) error {
return nil return nil
} }
// readTerminalOrFile reads a single line from the terminal. If the line read is
// short enough to be a file and a file with that exact name exists, the content
// of that file is read and returned as a string. If the content is longer or no
// file exists, the string read from the terminal is returned directly. This
// function can be used to circumvent the N_TTY_BUF_SIZE kernel parameter that
// prevents pasting more than 4096 characters (on most systems) into a terminal.
func readTerminalOrFile(quit chan struct{}) (string, error) {
maybeFile, err := readLine(quit)
if err != nil {
return "", err
}
// Absolute file paths normally can't be longer than 255 characters so
// we don't even check if it's a file in that case.
if len(maybeFile) > 255 {
return maybeFile, nil
}
// It might be a file since the length is small enough. Calling os.Stat
// should be safe with any arbitrary input as it will only query info
// about the file, not open or execute it directly.
stat, err := os.Stat(maybeFile)
// The file doesn't exist, we must assume this wasn't a file path after
// all.
if err != nil && os.IsNotExist(err) {
return maybeFile, nil
}
// Some other error, perhaps access denied or something similar, let's
// surface that to the user.
if err != nil {
return "", err
}
// Make sure we don't read a huge file by accident which might lead to
// undesired side effects. Even very large PSBTs should still only be a
// few hundred kilobytes so it makes sense to put a cap here.
if stat.Size() > psbtMaxFileSize {
return "", fmt.Errorf("error reading file %s: size of %d "+
"bytes exceeds max PSBT file size of %d", maybeFile,
stat.Size(), psbtMaxFileSize)
}
// If it's a path to an existing file and it's small enough, let's try
// to read its content now.
content, err := ioutil.ReadFile(maybeFile)
if err != nil {
return "", err
}
return string(content), nil
}
// readLine reads a line from standard in but does not block in case of a // readLine reads a line from standard in but does not block in case of a
// system interrupt like syscall.SIGINT (Ctrl+C). // system interrupt like syscall.SIGINT (Ctrl+C).
func readLine(quit chan struct{}) (string, error) { func readLine(quit chan struct{}) (string, error) {