lncli: Allow lncli to read binary PSBTs from a file

BIP 174 states that PSBTs can be encoded in 2 formats:
- plain text as base64
- in a file as binary

With this change, lncli will also try to read a PSBT as binary
This commit is contained in:
Antoni Spaanderman
2022-11-06 12:49:06 +01:00
parent d5648e9f80
commit ed112681f5
2 changed files with 51 additions and 24 deletions

View File

@ -44,14 +44,14 @@ Paste the funded PSBT here to continue the funding flow.
If your PSBT is very long (specifically, more than 4096 characters), please save 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 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. truncate the pasted text if it's too long.
Base64 encoded PSBT (or path to text file): ` Base64 encoded PSBT (or path to 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 (or path to text file): ` Signed base64 encoded PSBT or hex encoded raw wire TX (or path to file): `
// psbtMaxFileSize is the maximum file size we allow a PSBT file to be // 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 // in case we want to read a PSBT from a file. This is mainly to protect
@ -595,7 +595,7 @@ func openChannelPsbt(rpcCtx context.Context, ctx *cli.Context,
// 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 := readTerminalOrFile(quit) inputPsbt, err := readTerminalOrFile(quit)
if err == io.EOF { if err == io.EOF {
return nil return nil
} }
@ -603,11 +603,9 @@ func openChannelPsbt(rpcCtx context.Context, ctx *cli.Context,
return fmt.Errorf("reading from terminal or "+ return fmt.Errorf("reading from terminal or "+
"file failed: %v", err) "file failed: %v", err)
} }
fundedPsbt, err := base64.StdEncoding.DecodeString( fundedPsbt, err := decodePsbt(inputPsbt)
strings.TrimSpace(psbtBase64),
)
if err != nil { if err != nil {
return fmt.Errorf("base64 decode failed: %v", return fmt.Errorf("psbt decode failed: %v",
err) err)
} }
verifyMsg := &lnrpc.FundingTransitionMsg{ verifyMsg := &lnrpc.FundingTransitionMsg{
@ -995,17 +993,29 @@ func sendFundingState(cancelCtx context.Context, cliCtx *cli.Context,
} }
// finalizeMsgFromString creates the final message for the PsbtFinalize step // finalizeMsgFromString creates the final message for the PsbtFinalize step
// from either a hex encoded raw wire transaction or a base64 encoded PSBT // from either a hex encoded raw wire transaction or a base64/binary encoded
// packet. // PSBT packet.
func finalizeMsgFromString(tx string, func finalizeMsgFromString(tx string,
pendingChanID []byte) (*lnrpc.FundingTransitionMsg_PsbtFinalize, error) { pendingChanID []byte) (*lnrpc.FundingTransitionMsg_PsbtFinalize, error) {
rawTx, err := hex.DecodeString(strings.TrimSpace(tx)) psbtBytes, err := decodePsbt(tx)
if err == nil { if err == nil {
// Hex decoding succeeded so we assume we have a raw wire format return &lnrpc.FundingTransitionMsg_PsbtFinalize{
// transaction. Let's submit that instead of a PSBT packet. PsbtFinalize: &lnrpc.FundingPsbtFinalize{
tx := &wire.MsgTx{} SignedPsbt: psbtBytes,
err := tx.Deserialize(bytes.NewReader(rawTx)) PendingChanId: pendingChanID,
},
}, nil
}
// PSBT decode failed, try to parse it as a hex encoded Bitcoin
// transaction
rawTx, err := hex.DecodeString(strings.TrimSpace(tx))
if err != nil {
return nil, fmt.Errorf("hex decode failed: %w", err)
}
msgtx := &wire.MsgTx{}
err = msgtx.Deserialize(bytes.NewReader(rawTx))
if err != nil { if err != nil {
return nil, fmt.Errorf("deserializing as raw wire "+ return nil, fmt.Errorf("deserializing as raw wire "+
"transaction failed: %v", err) "transaction failed: %v", err)
@ -1018,16 +1028,29 @@ func finalizeMsgFromString(tx string,
}, nil }, nil
} }
// If the string isn't a hex encoded transaction, we assume it must be // decodePsbt tries to decode the input as a binary or base64 PSBT. If this
// a base64 encoded PSBT packet. // succeeded, the PSBT bytes are returned, an error otherwise.
psbtBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(tx)) func decodePsbt(psbt string) ([]byte, error) {
switch {
case strings.HasPrefix(psbt, "psbt\xff"):
// A binary PSBT (read from a file) always starts with the PSBT
// magic "psbt\xff" according to BIP 174
return []byte(psbt), nil
case strings.HasPrefix(strings.TrimSpace(psbt), "cHNidP"):
// A base64 PSBT always starts with "cHNidP". This is the
// longest base64 representation of the PSBT magic that is not
// dependent on the byte after it.
psbtBytes, err := base64.StdEncoding.DecodeString(
strings.TrimSpace(psbt),
)
if err != nil { if err != nil {
return nil, fmt.Errorf("base64 decode failed: %v", err) return nil, fmt.Errorf("base64 decode failed: %w", err)
}
return psbtBytes, nil
default:
return nil, fmt.Errorf("not a PSBT")
} }
return &lnrpc.FundingTransitionMsg_PsbtFinalize{
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
SignedPsbt: psbtBytes,
PendingChanId: pendingChanID,
},
}, nil
} }

View File

@ -181,6 +181,9 @@ certain large transactions](https://github.com/lightningnetwork/lnd/pull/7100).
the invoice payment request together with the prefix, which throws checksum the invoice payment request together with the prefix, which throws checksum
error when pasting it to the CLI. error when pasting it to the CLI.
* [Allow lncli to read binary PSBTs](https://github.com/lightningnetwork/lnd/pull/7122)
from a file during PSBT channel funding flow to comply with [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#specification)
## Code Health ## Code Health
* [test: use `T.TempDir` to create temporary test * [test: use `T.TempDir` to create temporary test
@ -251,6 +254,7 @@ to refactor the itest for code health and maintenance.
* Alejandro Pedraza * Alejandro Pedraza
* andreihod * andreihod
* Antoni Spaanderman
* Carla Kirk-Cohen * Carla Kirk-Cohen
* Conner Babinchak * Conner Babinchak
* cutiful * cutiful