mirror of https://github.com/guggero/chantools
Merge pull request #124 from lightninglabs/createwallet
Add `createwallet` and `signpsbt` subcommandspull/127/head
commit
997d86cd84
@ -0,0 +1,232 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||||
|
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
|
||||||
|
"github.com/lightninglabs/chantools/lnd"
|
||||||
|
"github.com/lightningnetwork/lnd/aezeed"
|
||||||
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type createWalletCommand struct {
|
||||||
|
WalletDBDir string
|
||||||
|
GenerateSeed bool
|
||||||
|
|
||||||
|
rootKey *rootKey
|
||||||
|
cmd *cobra.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateWalletCommand() *cobra.Command {
|
||||||
|
cc := &createWalletCommand{}
|
||||||
|
cc.cmd = &cobra.Command{
|
||||||
|
Use: "createwallet",
|
||||||
|
Short: "Create a new lnd compatible wallet.db file from an " +
|
||||||
|
"existing seed or by generating a new one",
|
||||||
|
Long: `Creates a new wallet that can be used with lnd or with
|
||||||
|
chantools. The wallet can be created from an existing seed or a new one can be
|
||||||
|
generated (use --generateseed).`,
|
||||||
|
Example: `chantools createwallet \
|
||||||
|
--walletdbdir ~/.lnd/data/chain/bitcoin/mainnet`,
|
||||||
|
RunE: cc.Execute,
|
||||||
|
}
|
||||||
|
cc.cmd.Flags().StringVar(
|
||||||
|
&cc.WalletDBDir, "walletdbdir", "", "the folder to create the "+
|
||||||
|
"new wallet.db file in",
|
||||||
|
)
|
||||||
|
cc.cmd.Flags().BoolVar(
|
||||||
|
&cc.GenerateSeed, "generateseed", false, "generate a new "+
|
||||||
|
"seed instead of using an existing one",
|
||||||
|
)
|
||||||
|
|
||||||
|
cc.rootKey = newRootKey(cc.cmd, "creating the new wallet")
|
||||||
|
|
||||||
|
return cc.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *createWalletCommand) Execute(_ *cobra.Command, _ []string) error {
|
||||||
|
var (
|
||||||
|
publicWalletPw = lnwallet.DefaultPublicPassphrase
|
||||||
|
privateWalletPw = lnwallet.DefaultPrivatePassphrase
|
||||||
|
masterRootKey *hdkeychain.ExtendedKey
|
||||||
|
birthday time.Time
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that we have a wallet DB.
|
||||||
|
if c.WalletDBDir == "" {
|
||||||
|
return fmt.Errorf("wallet DB directory is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the directory (and parents) exists.
|
||||||
|
if err := os.MkdirAll(c.WalletDBDir, 0700); err != nil {
|
||||||
|
return fmt.Errorf("error creating wallet DB directory '%s': %w",
|
||||||
|
c.WalletDBDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should create a new seed or read if from the console or
|
||||||
|
// environment.
|
||||||
|
if c.GenerateSeed {
|
||||||
|
fmt.Printf("Generating new lnd compatible aezeed...\n")
|
||||||
|
seed, err := aezeed.New(
|
||||||
|
keychain.KeyDerivationVersionTaproot, nil, time.Now(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating new seed: %w", err)
|
||||||
|
}
|
||||||
|
birthday = seed.BirthdayTime()
|
||||||
|
|
||||||
|
// Derive the master extended key from the seed.
|
||||||
|
masterRootKey, err = hdkeychain.NewMaster(
|
||||||
|
seed.Entropy[:], chainParams,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to derive master extended "+
|
||||||
|
"key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
passphrase, err := lnd.ReadPassphrase("shouldn't use")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading passphrase: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mnemonic, err := seed.ToMnemonic(passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting seed to "+
|
||||||
|
"mnemonic: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Generated new seed")
|
||||||
|
printCipherSeedWords(mnemonic[:])
|
||||||
|
} else {
|
||||||
|
masterRootKey, birthday, err = c.rootKey.readWithBirthday()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To automate things with chantools, we also offer reading the wallet
|
||||||
|
// password from environment variables.
|
||||||
|
pw := []byte(strings.TrimSpace(os.Getenv(lnd.PasswordEnvName)))
|
||||||
|
|
||||||
|
// Because we cannot differentiate between an empty and a non-existent
|
||||||
|
// environment variable, we need a special character that indicates that
|
||||||
|
// no password should be used. We use a single dash (-) for that as that
|
||||||
|
// would be too short for an explicit password anyway.
|
||||||
|
switch {
|
||||||
|
// The user indicated in the environment variable that no passphrase
|
||||||
|
// should be used. We don't set any value.
|
||||||
|
case string(pw) == "-":
|
||||||
|
|
||||||
|
// The environment variable didn't contain anything, we'll read the
|
||||||
|
// passphrase from the terminal.
|
||||||
|
case len(pw) == 0:
|
||||||
|
fmt.Printf("\n\nThe wallet password is used to encrypt the " +
|
||||||
|
"wallet.db file itself and is unrelated to the seed.\n")
|
||||||
|
pw, err = lnd.PasswordFromConsole("Input new wallet password: ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pw2, err := lnd.PasswordFromConsole(
|
||||||
|
"Confirm new wallet password: ",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(pw, pw2) {
|
||||||
|
return fmt.Errorf("passwords don't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pw) > 0 {
|
||||||
|
publicWalletPw = pw
|
||||||
|
privateWalletPw = pw
|
||||||
|
}
|
||||||
|
|
||||||
|
// There was a password in the environment, just use it directly.
|
||||||
|
default:
|
||||||
|
publicWalletPw = pw
|
||||||
|
privateWalletPw = pw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to create the wallet.
|
||||||
|
loader, err := btcwallet.NewWalletLoader(
|
||||||
|
chainParams, 0, btcwallet.LoaderWithLocalWalletDB(
|
||||||
|
c.WalletDBDir, true, 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating wallet loader: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = loader.CreateNewWalletExtendedKey(
|
||||||
|
publicWalletPw, privateWalletPw, masterRootKey, birthday,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating new wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := loader.UnloadWallet(); err != nil {
|
||||||
|
return fmt.Errorf("error unloading wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Wallet created successfully at %v\n", c.WalletDBDir)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printCipherSeedWords(mnemonicWords []string) {
|
||||||
|
fmt.Println("!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
|
||||||
|
"RESTORE THE WALLET!!!")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
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!!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
@ -0,0 +1,217 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||||
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
|
"github.com/lightninglabs/chantools/lnd"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type signPSBTCommand struct {
|
||||||
|
Psbt string
|
||||||
|
FromRawPsbtFile string
|
||||||
|
ToRawPsbtFile string
|
||||||
|
|
||||||
|
rootKey *rootKey
|
||||||
|
cmd *cobra.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSignPSBTCommand() *cobra.Command {
|
||||||
|
cc := &signPSBTCommand{}
|
||||||
|
cc.cmd = &cobra.Command{
|
||||||
|
Use: "signpsbt",
|
||||||
|
Short: "Sign a Partially Signed Bitcoin Transaction (PSBT)",
|
||||||
|
Long: `Sign a PSBT with a master root key. The PSBT must contain
|
||||||
|
an input that is owned by the master root key.`,
|
||||||
|
Example: `chantools signpsbt \
|
||||||
|
--psbt <the_base64_encoded_psbt>
|
||||||
|
|
||||||
|
chantools signpsbt --fromrawpsbtfile <file_with_psbt>`,
|
||||||
|
RunE: cc.Execute,
|
||||||
|
}
|
||||||
|
cc.cmd.Flags().StringVar(
|
||||||
|
&cc.Psbt, "psbt", "", "Partially Signed Bitcoin Transaction "+
|
||||||
|
"to sign",
|
||||||
|
)
|
||||||
|
cc.cmd.Flags().StringVar(
|
||||||
|
&cc.FromRawPsbtFile, "fromrawpsbtfile", "", "the file containing "+
|
||||||
|
"the raw, binary encoded PSBT packet to sign",
|
||||||
|
)
|
||||||
|
cc.cmd.Flags().StringVar(
|
||||||
|
&cc.ToRawPsbtFile, "torawpsbtfile", "", "the file to write "+
|
||||||
|
"the resulting signed raw, binary encoded PSBT packet "+
|
||||||
|
"to",
|
||||||
|
)
|
||||||
|
|
||||||
|
cc.rootKey = newRootKey(cc.cmd, "signing the PSBT")
|
||||||
|
|
||||||
|
return cc.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *signPSBTCommand) Execute(_ *cobra.Command, _ []string) error {
|
||||||
|
extendedKey, err := c.rootKey.read()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading root key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := &lnd.Signer{
|
||||||
|
ExtendedKey: extendedKey,
|
||||||
|
ChainParams: chainParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
var packet *psbt.Packet
|
||||||
|
|
||||||
|
// Decode the PSBT, either from the command line or the binary file.
|
||||||
|
switch {
|
||||||
|
case c.Psbt != "":
|
||||||
|
packet, err = psbt.NewFromRawBytes(
|
||||||
|
bytes.NewReader([]byte(c.Psbt)), true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error decoding PSBT: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case c.FromRawPsbtFile != "":
|
||||||
|
f, err := os.Open(c.FromRawPsbtFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening PSBT file '%s': %w",
|
||||||
|
c.FromRawPsbtFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packet, err = psbt.NewFromRawBytes(f, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error decoding PSBT from file "+
|
||||||
|
"'%s': %w", c.FromRawPsbtFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("either the PSBT or the raw PSBT file " +
|
||||||
|
"must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = signPsbt(extendedKey, packet, signer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error signing PSBT: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c.ToRawPsbtFile != "":
|
||||||
|
f, err := os.Create(c.ToRawPsbtFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating PSBT file '%s': %w",
|
||||||
|
c.ToRawPsbtFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := packet.Serialize(f); err != nil {
|
||||||
|
return fmt.Errorf("error serializing PSBT to file "+
|
||||||
|
"'%s': %w", c.ToRawPsbtFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Successfully signed PSBT and wrote it to file "+
|
||||||
|
"'%s'\n", c.ToRawPsbtFile)
|
||||||
|
|
||||||
|
default:
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := packet.Serialize(&buf); err != nil {
|
||||||
|
return fmt.Errorf("error serializing PSBT: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Successfully signed PSBT:\n\n%s\n",
|
||||||
|
base64.StdEncoding.EncodeToString(buf.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func signPsbt(rootKey *hdkeychain.ExtendedKey,
|
||||||
|
packet *psbt.Packet, signer *lnd.Signer) error {
|
||||||
|
|
||||||
|
// Check that we have an input with a derivation path that belongs to
|
||||||
|
// the root key.
|
||||||
|
derivationPath, inputIndex, err := findMatchingDerivationPath(
|
||||||
|
rootKey, packet,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not find matching derivation path: %w",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(derivationPath) < 5 {
|
||||||
|
return fmt.Errorf("invalid derivation path, expected at least "+
|
||||||
|
"5 elements, got %d", len(derivationPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
localKey, err := lnd.DeriveChildren(rootKey, derivationPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not derive local key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(packet.Inputs[inputIndex].WitnessScript) == 0 {
|
||||||
|
return fmt.Errorf("invalid PSBT, input %d is missing witness "+
|
||||||
|
"script", inputIndex)
|
||||||
|
}
|
||||||
|
witnessScript := packet.Inputs[inputIndex].WitnessScript
|
||||||
|
if packet.Inputs[inputIndex].WitnessUtxo == nil {
|
||||||
|
return fmt.Errorf("invalid PSBT, input %d is missing witness "+
|
||||||
|
"UTXO", inputIndex)
|
||||||
|
}
|
||||||
|
utxo := packet.Inputs[inputIndex].WitnessUtxo
|
||||||
|
|
||||||
|
localPrivateKey, err := localKey.ECPrivKey()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting private key: %w", err)
|
||||||
|
}
|
||||||
|
err = signer.AddPartialSignatureForPrivateKey(
|
||||||
|
packet, localPrivateKey, utxo, witnessScript, inputIndex,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error adding partial signature: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMatchingDerivationPath(rootKey *hdkeychain.ExtendedKey,
|
||||||
|
packet *psbt.Packet) ([]uint32, int, error) {
|
||||||
|
|
||||||
|
pubKey, err := rootKey.ECPubKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("error getting public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed())
|
||||||
|
fingerprint := binary.LittleEndian.Uint32(pubKeyHash[:4])
|
||||||
|
|
||||||
|
for idx, input := range packet.Inputs {
|
||||||
|
if len(input.Bip32Derivation) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, derivation := range input.Bip32Derivation {
|
||||||
|
// A special case where there is only a single
|
||||||
|
// derivation path and the master key fingerprint is not
|
||||||
|
// set, we assume we are the correct signer... This
|
||||||
|
// might not be correct, but we have no way of knowing.
|
||||||
|
if derivation.MasterKeyFingerprint == 0 &&
|
||||||
|
len(input.Bip32Derivation) == 1 {
|
||||||
|
|
||||||
|
return derivation.Bip32Path, idx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The normal case, where a derivation path has the
|
||||||
|
// master fingerprint set.
|
||||||
|
if derivation.MasterKeyFingerprint == fingerprint {
|
||||||
|
return derivation.Bip32Path, idx, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, fmt.Errorf("no matching derivation path found")
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
## chantools createwallet
|
||||||
|
|
||||||
|
Create a new lnd compatible wallet.db file from an existing seed or by generating a new one
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Creates a new wallet that can be used with lnd or with
|
||||||
|
chantools. The wallet can be created from an existing seed or a new one can be
|
||||||
|
generated (use --generateseed).
|
||||||
|
|
||||||
|
```
|
||||||
|
chantools createwallet [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
chantools createwallet \
|
||||||
|
--walletdbdir ~/.lnd/data/chain/bitcoin/mainnet
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
|
||||||
|
--generateseed generate a new seed instead of using an existing one
|
||||||
|
-h, --help help for createwallet
|
||||||
|
--rootkey string BIP32 HD root key of the wallet to use for creating the new wallet; leave empty to prompt for lnd 24 word aezeed
|
||||||
|
--walletdb string read the seed/master root key to use fro creating the new wallet from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
|
||||||
|
--walletdbdir string the folder to create the new wallet.db file in
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
-r, --regtest Indicates if regtest parameters should be used
|
||||||
|
-s, --signet Indicates if the public signet parameters should be used
|
||||||
|
-t, --testnet Indicates if testnet parameters should be used
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [chantools](chantools.md) - Chantools helps recover funds from lightning channels
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
## chantools signpsbt
|
||||||
|
|
||||||
|
Sign a Partially Signed Bitcoin Transaction (PSBT)
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Sign a PSBT with a master root key. The PSBT must contain
|
||||||
|
an input that is owned by the master root key.
|
||||||
|
|
||||||
|
```
|
||||||
|
chantools signpsbt [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
chantools signpsbt \
|
||||||
|
--psbt <the_base64_encoded_psbt>
|
||||||
|
|
||||||
|
chantools signpsbt --fromrawpsbtfile <file_with_psbt>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
|
||||||
|
--fromrawpsbtfile string the file containing the raw, binary encoded PSBT packet to sign
|
||||||
|
-h, --help help for signpsbt
|
||||||
|
--psbt string Partially Signed Bitcoin Transaction to sign
|
||||||
|
--rootkey string BIP32 HD root key of the wallet to use for signing the PSBT; leave empty to prompt for lnd 24 word aezeed
|
||||||
|
--torawpsbtfile string the file to write the resulting signed raw, binary encoded PSBT packet to
|
||||||
|
--walletdb string read the seed/master root key to use fro signing the PSBT from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
-r, --regtest Indicates if regtest parameters should be used
|
||||||
|
-s, --signet Indicates if the public signet parameters should be used
|
||||||
|
-t, --testnet Indicates if testnet parameters should be used
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [chantools](chantools.md) - Chantools helps recover funds from lightning channels
|
||||||
|
|
Loading…
Reference in New Issue