Compare commits

...

38 Commits

Author SHA1 Message Date
Oliver Gugger 85f207c58f
Merge pull request #137 from hieblmi/gitignore-idea
gitignore: .idea folder
1 week ago
Slyghtning da904ae1d7
gitignore: .idea folder 1 week ago
Oliver Gugger 14aa06fa41
Merge pull request #134 from lightninglabs/dependabot/go_modules/tools/github.com/btcsuite/btcd-0.24.0
build(deps): bump github.com/btcsuite/btcd from 0.23.4 to 0.24.0 in /tools
4 weeks ago
dependabot[bot] 601789f445
build(deps): bump github.com/btcsuite/btcd in /tools
Bumps [github.com/btcsuite/btcd](https://github.com/btcsuite/btcd) from 0.23.4 to 0.24.0.
- [Release notes](https://github.com/btcsuite/btcd/releases)
- [Changelog](https://github.com/btcsuite/btcd/blob/master/CHANGES)
- [Commits](https://github.com/btcsuite/btcd/compare/v0.23.4...v0.24.0)

---
updated-dependencies:
- dependency-name: github.com/btcsuite/btcd
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
4 weeks ago
Oliver Gugger cc284baa67
Merge pull request #132 from lightninglabs/triggerforceclose
triggerforceclose: make compatible with all nodes, add Tor support
1 month ago
Oliver Gugger 6acc81815e
root: bump version to v0.13.1 1 month ago
Oliver Gugger e3285daf5b
triggerforceclose: support Tor connections 1 month ago
Oliver Gugger 179773fdb9
triggerforceclose: make cmd compatible with all nodes 1 month ago
Oliver Gugger 0fd58ee7eb
Merge pull request #130 from lightninglabs/dependabot/go_modules/golang.org/x/net-0.23.0
build(deps): bump golang.org/x/net from 0.21.0 to 0.23.0
1 month ago
dependabot[bot] 7c405057bd
build(deps): bump golang.org/x/net from 0.21.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.21.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
Oliver Gugger d07251df9c
Merge pull request #129 from lightninglabs/fix-signpsbt
signpsbt+lnd: fix signing for P2WKH
2 months ago
Oliver Gugger 7f840cf03b
signpsbt+lnd: fix signing for P2WKH 2 months ago
Oliver Gugger f062b53a21
Merge pull request #127 from lightninglabs/dependabot/go_modules/github.com/docker/docker-24.0.9incompatible
build(deps): bump github.com/docker/docker from 24.0.7+incompatible to 24.0.9+incompatible
2 months ago
Oliver Gugger 24cd530c65
make: update build targets due to sqlite 2 months ago
dependabot[bot] c54184b8d0
build(deps): bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 24.0.9+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.7...v24.0.9)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
Oliver Gugger 997d86cd84
Merge pull request #124 from lightninglabs/createwallet
Add `createwallet` and `signpsbt` subcommands
2 months ago
Oliver Gugger 676ba60197
Merge pull request #113 from sputn1ck/recoverloopin_sqlite
`recoverloopin`: Sqlite option
2 months ago
sputn1ck 3e419da317
recoverloopin: add sqlite option
This commit will allow to recover loop ins that have been made with
sqlite.
2 months ago
sputn1ck 6a81614b1b
go.mod: update loop to v0.26.6-beta 2 months ago
Oliver Gugger 71b824e105
README+doc: update all docs 2 months ago
Oliver Gugger 1a46f9099f
root: bump version to v0.13.0 2 months ago
Oliver Gugger 9f8484bb89
cmd/chantools: add signpsbt subcommand 2 months ago
Oliver Gugger e80dcbfb67
lnd+cmd/chantools: add AddPartialSignatureForPrivateKey to signer 2 months ago
Oliver Gugger 5c39df02d3
cmd/chantools: allow root key to be read from wallet DB
With this commit we allow a third option for reading the master root key
for any command that requires access to it: Reading and decrypting it
directly from an lnd wallet password.
2 months ago
Oliver Gugger 37179e5215
lnd+cmd/chantools: extract functions from walletinfo 2 months ago
Oliver Gugger b169634d85
cmd/chantools: add new createwallet subcommand
This commit adds a new subcommand for creating a new lnd compatible
wallet.db file from an existing aezeed, master root key (xprv) or by
generating a new aezeed.
2 months ago
Oliver Gugger a3a00d410a
lnd: extract ReadPassphrase into own function 2 months ago
Oliver Gugger 450c2777af
Merge pull request #126 from lightninglabs/dependabot/go_modules/tools/google.golang.org/protobuf-1.33.0
build(deps): bump google.golang.org/protobuf from 1.28.0 to 1.33.0 in /tools
3 months ago
dependabot[bot] c4162303b7
build(deps): bump google.golang.org/protobuf in /tools
Bumps google.golang.org/protobuf from 1.28.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
Oliver Gugger 486af2e99a
Merge pull request #125 from lightninglabs/dependabot/go_modules/github.com/jackc/pgx/v4-4.18.2
build(deps): bump github.com/jackc/pgx/v4 from 4.18.1 to 4.18.2
3 months ago
dependabot[bot] 3b3daddfee
build(deps): bump github.com/jackc/pgx/v4 from 4.18.1 to 4.18.2
Bumps [github.com/jackc/pgx/v4](https://github.com/jackc/pgx) from 4.18.1 to 4.18.2.
- [Changelog](https://github.com/jackc/pgx/blob/v4.18.2/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v4.18.1...v4.18.2)

---
updated-dependencies:
- dependency-name: github.com/jackc/pgx/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
Oliver Gugger c75f9ff91a
Merge pull request #121 from sputn1ck/signmessage
signmessage: add signmessage cmd
4 months ago
sputn1ck 3fbf8d0bd2
signmessage: add signmessage cmd
This commit adds the signmessage command which allows a user to sign a
message with the nodes identity key, similiar to `lncli signmessage`.
4 months ago
Oliver Gugger cf4cabbd2a
Merge pull request #120 from ziggie1984/master
Fix Partial Signature Signing.
4 months ago
ziggie 78c41b4acf
zombierecovery-makeoffer: fix witness data for psbt package.
We need to make sure we populate all the necessary
witness/nonwitness because for taproot inputs we need the prevout
so we check for it in the hashcash creation.
4 months ago
Oliver Gugger a0e5f0613d
Merge pull request #118 from Tetrix42/doublespend_rbf
doublespendinputs: allow RBF per default
4 months ago
Felix Passenberg d9af0e36e5
doublespendinputs: remove RBF argument, RBF always on 4 months ago
Felix Passenberg 3b50a5ce16
doublespendinputs: allow RBF per default 4 months ago

1
.gitignore vendored

@ -1,3 +1,4 @@
.idea
/chantools
results
/chantools-v*

@ -20,14 +20,13 @@ VERSION_TAG = $(shell git describe --tags)
VERSION_CHECK = @$(call print, "Building master with date version tag")
BUILD_SYSTEM = darwin-amd64 \
darwin-arm64 \
linux-386 \
linux-amd64 \
linux-armv6 \
linux-armv7 \
linux-arm64 \
windows-386 \
windows-amd64 \
windows-arm
windows-amd64
# By default we will build all systems. But with the 'sys' tag, a specific
# system can be specified. This is useful to release for a subset of

@ -107,7 +107,8 @@ Scenarios:
Another reason might be that the peer is a CLN node with a specific version
that doesn't react to force close requests normally. You can use the
[`chantools triggerforceclose` command](doc/chantools_triggerforceclose.md) in
that case (ONLY works with CLN peers of a certain version).
that case (should work with CLN peers of a certain version that don't respond
to normal force close requests).
## What should I NEVER do?
@ -410,6 +411,7 @@ Available Commands:
chanbackup Create a channel.backup file from a channel database
closepoolaccount Tries to close a Pool account that has expired
compactdb Create a copy of a channel.db file in safe/read-only mode
createwallet Create a new lnd compatible wallet.db file from an existing seed or by generating a new one
deletepayments Remove all (failed) payments from a channel DB
derivekey Derive a key with a specific derivation path
doublespendinputs Tries to double spend the given inputs by deriving the private for the address and sweeping the funds to the given address. This can only be used with inputs that belong to an lnd wallet.
@ -429,12 +431,14 @@ Available Commands:
rescuefunding Rescue funds locked in a funding multisig output that never resulted in a proper channel; this is the command the initiator of the channel needs to run
rescuetweakedkey Attempt to rescue funds locked in an address with a key that was affected by a specific bug in lnd
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed
signmessage Sign a message with the nodes identity pubkey.
signpsbt Sign a Partially Signed Bitcoin Transaction (PSBT)
signrescuefunding Rescue funds locked in a funding multisig output that never resulted in a proper channel; this is the command the remote node (the non-initiator) of the channel needs to run
summary Compile a summary about the current state of channels
sweeptimelock Sweep the force-closed state after the time lock has expired
sweeptimelockmanual Sweep the force-closed state of a single channel manually if only a channel backup file is available
sweepremoteclosed Go through all the addresses that could have funds of channels that were force-closed by the remote party. A public block explorer is queried for each address and if any balance is found, all funds are swept to a given address
triggerforceclose Connect to a peer and send a custom message to trigger a force close of the specified channel
triggerforceclose Connect to a peer and send request to trigger a force close of the specified channel
vanitygen Generate a seed with a custom lnd node identity public key that starts with the given prefix
walletinfo Shows info about an lnd wallet.db file and optionally extracts the BIP32 HD root key
zombierecovery Try rescuing funds stuck in channels with zombie nodes
@ -469,6 +473,7 @@ Legend:
| [chanbackup](doc/chantools_chanbackup.md) | :pencil: Extract a `channel.backup` file from a `channel.db` file |
| [closepoolaccount](doc/chantools_closepoolaccount.md) | :pencil: Manually close an expired Lightning Pool account |
| [compactdb](doc/chantools_compactdb.md) | Run database compaction manually to reclaim space |
| [createwallet](doc/chantools_createwallet.md) | :pencil: Create a new lnd compatible wallet.db file from an existing seed or by generating a new one |
| [deletepayments](doc/chantools_deletepayments.md) | Remove ALL payments from a `channel.db` file to reduce size |
| [derivekey](doc/chantools_derivekey.md) | :pencil: Derive a single private/public key from `lnd`'s seed, use to test seed |
| [doublespendinputs](doc/chantools_doublespendinputs.md) | :pencil: Tries to double spend the given inputs by deriving the private for the address and sweeping the funds to the given address |
@ -482,18 +487,20 @@ Legend:
| [forceclose](doc/chantools_forceclose.md) | :pencil: (:skull: :warning:) Publish an old channel state from a `channel.db` file |
| [genimportscript](doc/chantools_genimportscript.md) | :pencil: Create a script/text file that can be used to import `lnd` keys into other software |
| [migratedb](doc/chantools_migratedb.md) | Upgrade the `channel.db` file to the latest version |
| [pullanchor](doc/chantools_pullanchor.md) | :pencil: Attempt to CPFP an anchor output of a channel |
| [pullanchor](doc/chantools_pullanchor.md) | :pencil: Attempt to CPFP an anchor output of a channel |
| [recoverloopin](doc/chantools_recoverloopin.md) | :pencil: Recover funds from a failed Lightning Loop inbound swap |
| [removechannel](doc/chantools_removechannel.md) | (:skull: :warning:) Remove a single channel from a `channel.db` file |
| [rescueclosed](doc/chantools_rescueclosed.md) | :pencil: (:pushpin:) Rescue funds in a legacy (pre `STATIC_REMOTE_KEY`) channel output |
| [rescuefunding](doc/chantools_rescuefunding.md) | :pencil: (:pushpin:) Rescue funds from a funding transaction. Deprecated, use [zombierecovery](doc/chantools_zombierecovery.md) instead |
| [showrootkey](doc/chantools_showrootkey.md) | :pencil: Display the master root key (`xprv`) from your seed (DO NOT SHARE WITH ANYONE) |
| [signmessage](doc/chantools_signmessage.md) | :pencil: Sign a message with the nodes identity pubkey. |
| [signpsbt](doc/chantools_signpsbt.md) | :pencil: Sign a Partially Signed Bitcoin Transaction (PSBT) |
| [signrescuefunding](doc/chantools_signrescuefunding.md) | :pencil: (:pushpin:) Sign to funds from a funding transaction. Deprecated, use [zombierecovery](doc/chantools_zombierecovery.md) instead |
| [summary](doc/chantools_summary.md) | Create a summary of channel funds from a `channel.db` file |
| [sweepremoteclosed](doc/chantools_sweepremoteclosed.md) | :pencil: Find channel funds from remotely force closed channels and sweep them |
| [sweeptimelock](doc/chantools_sweeptimelock.md) | :pencil: Sweep funds in locally force closed channels once time lock has expired (requires `channel.db`) |
| [sweeptimelockmanual](doc/chantools_sweeptimelockmanual.md) | :pencil: Manually sweep funds in a locally force closed channel where no `channel.db` file is available |
| [triggerforceclose](doc/chantools_triggerforceclose.md) | :pencil: (:pushpin:) Request certain CLN peers to force close a channel that don't react to normal SCB recovery requests |
| [triggerforceclose](doc/chantools_triggerforceclose.md) | :pencil: (:pushpin:) Request a peer to force close a channel |
| [vanitygen](doc/chantools_vanitygen.md) | Generate an `lnd` seed for a node public key that starts with a certain sequence of hex digits |
| [walletinfo](doc/chantools_walletinfo.md) | Show information from a `wallet.db` file, requires access to the wallet password |
| [zombierecovery](doc/chantools_zombierecovery.md) | :pencil: Cooperatively rescue funds from channels where normal recovery is not possible (see [full guide here][zombie-recovery]) |

@ -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
}

@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
@ -232,7 +233,10 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
// Add the inputs.
for _, outpoint := range outpoints {
tx.AddTxIn(wire.NewTxIn(outpoint, nil, nil))
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *outpoint,
Sequence: mempool.MaxRBFSequence,
})
}
tx.AddTxOut(wire.NewTxOut(int64(totalInput-totalFee), sweepScript))

@ -2,8 +2,12 @@ package main
import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"path/filepath"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@ -15,10 +19,15 @@ import (
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/spf13/cobra"
)
var (
errSwapNotFound = fmt.Errorf("loop in swap not found")
)
type recoverLoopInCommand struct {
TxID string
Vout uint32
@ -32,7 +41,8 @@ type recoverLoopInCommand struct {
APIURL string
Publish bool
LoopDbDir string
LoopDbDir string
SqliteFile string
rootKey *rootKey
cmd *cobra.Command
@ -97,6 +107,11 @@ func newRecoverLoopInCommand() *cobra.Command {
cc.cmd.Flags().Uint64Var(
&cc.OutputAmt, "output_amt", 0, "amount of the output to sweep",
)
cc.cmd.Flags().StringVar(
&cc.SqliteFile, "sqlite_file", "", "optional path to the loop "+
"sqlite database file, if not specified, the default "+
"location will be loaded from --loop_db_dir",
)
cc.rootKey = newRootKey(cc.cmd, "deriving starting key")
@ -130,32 +145,62 @@ func (c *recoverLoopInCommand) Execute(_ *cobra.Command, _ []string) error {
}
api := newExplorerAPI(c.APIURL)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
// Try to fetch the swap from the database.
store, err := loopdb.NewBoltSwapStore(c.LoopDbDir, chainParams)
if err != nil {
return err
}
defer store.Close()
// Try to fetch the swap from the boltdb.
var (
store loopdb.SwapStore
loopIn *loopdb.LoopIn
)
swaps, err := store.FetchLoopInSwaps()
if err != nil {
return err
// First check if a boltdb file exists.
if lnrpc.FileExists(filepath.Join(c.LoopDbDir, "loop.db")) {
store, err = loopdb.NewBoltSwapStore(c.LoopDbDir, chainParams)
if err != nil {
return err
}
defer store.Close()
loopIn, err = findLoopInSwap(ctx, store, c.SwapHash)
if err != nil && !errors.Is(err, errSwapNotFound) {
return err
}
}
var loopIn *loopdb.LoopIn
for _, s := range swaps {
if s.Hash.String() == c.SwapHash {
loopIn = s
break
// If the loopin is not found yet, try to fetch it from the sqlite db.
if loopIn == nil {
if c.SqliteFile == "" {
c.SqliteFile = filepath.Join(
c.LoopDbDir, "loop_sqlite.db",
)
}
sqliteDb, err := loopdb.NewSqliteStore(
&loopdb.SqliteConfig{
DatabaseFileName: c.SqliteFile,
SkipMigrations: true,
}, chainParams,
)
if err != nil {
return err
}
defer sqliteDb.Close()
loopIn, err = findLoopInSwap(ctx, sqliteDb, c.SwapHash)
if err != nil && !errors.Is(err, errSwapNotFound) {
return err
}
}
// If the loopin is still not found, return an error.
if loopIn == nil {
return fmt.Errorf("swap not found")
return errSwapNotFound
}
// If the swap is an external htlc, we require the output amount to be
@ -350,6 +395,23 @@ func getSignedTx(signer *lnd.Signer, sweepTx *wire.MsgTx, htlc *swap.Htlc,
return rawTx, nil
}
func findLoopInSwap(ctx context.Context, store loopdb.SwapStore,
swapHash string) (*loopdb.LoopIn, error) {
swaps, err := store.FetchLoopInSwaps(ctx)
if err != nil {
return nil, err
}
for _, s := range swaps {
if s.Hash.String() == swapHash {
return s, nil
}
}
return nil, errSwapNotFound
}
// encodeTx encodes a tx to raw bytes.
func encodeTx(tx *wire.MsgTx) ([]byte, error) {
var buffer bytes.Buffer

@ -209,7 +209,7 @@ func (c *rescueFundingCommand) Execute(_ *cobra.Command, _ []string) error {
Index: c.LocalKeyIndex,
},
}
privKey, err := signer.FetchPrivKey(localKeyDesc)
privKey, err := signer.FetchPrivateKey(localKeyDesc)
if err != nil {
return fmt.Errorf("error deriving local key: %w", err)
}

@ -1,14 +1,12 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"syscall"
"time"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
@ -22,7 +20,6 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/peer"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
const (
@ -33,12 +30,12 @@ const (
// version is the current version of the tool. It is set during build.
// NOTE: When changing this, please also update the version in the
// download link shown in the README.
version = "0.12.2"
version = "0.13.1"
na = "n/a"
// lndVersion is the current version of lnd that we support. This is
// shown in some commands that affect the database and its migrations.
lndVersion = "v0.17.0-beta"
lndVersion = "v0.17.4-beta"
Commit = ""
)
@ -101,6 +98,7 @@ func main() {
rootCmd.AddCommand(
newChanBackupCommand(),
newClosePoolAccountCommand(),
newCreateWalletCommand(),
newCompactDBCommand(),
newDeletePaymentsCommand(),
newDeriveKeyCommand(),
@ -123,7 +121,9 @@ func main() {
newRescueFundingCommand(),
newRescueTweakedKeyCommand(),
newShowRootKeyCommand(),
newSignMessageCommand(),
newSignRescueFundingCommand(),
newSignPSBTCommand(),
newSummaryCommand(),
newSweepTimeLockCommand(),
newSweepTimeLockManualCommand(),
@ -141,8 +141,9 @@ func main() {
}
type rootKey struct {
RootKey string
BIP39 bool
RootKey string
BIP39 bool
WalletDB string
}
func newRootKey(cmd *cobra.Command, desc string) *rootKey {
@ -157,6 +158,12 @@ func newRootKey(cmd *cobra.Command, desc string) *rootKey {
"passphrase from the terminal instead of asking for "+
"lnd seed format or providing the --rootkey flag",
)
cmd.Flags().StringVar(
&r.WalletDB, "walletdb", "", "read the seed/master root key "+
"to use fro "+desc+" from an lnd wallet.db file "+
"instead of asking for a seed or providing the "+
"--rootkey flag",
)
return r
}
@ -179,6 +186,39 @@ func (r *rootKey) readWithBirthday() (*hdkeychain.ExtendedKey, time.Time,
extendedKey, err := btc.ReadMnemonicFromTerminal(chainParams)
return extendedKey, time.Unix(0, 0), err
case r.WalletDB != "":
wallet, pw, cleanup, err := lnd.OpenWallet(
r.WalletDB, chainParams,
)
if err != nil {
return nil, time.Unix(0, 0), fmt.Errorf("error "+
"opening wallet '%s': %w", r.WalletDB, err)
}
defer func() {
if err := cleanup(); err != nil {
log.Errorf("error closing wallet: %v", err)
}
}()
extendedKeyBytes, err := lnd.DecryptWalletRootKey(
wallet.Database(), pw,
)
if err != nil {
return nil, time.Unix(0, 0), fmt.Errorf("error "+
"decrypting wallet root key: %w", err)
}
extendedKey, err := hdkeychain.NewKeyFromString(
string(extendedKeyBytes),
)
if err != nil {
return nil, time.Unix(0, 0), fmt.Errorf("error "+
"parsing master key: %w", err)
}
return extendedKey, wallet.Manager.Birthday(), nil
default:
return lnd.ReadAezeed(chainParams)
}
@ -263,27 +303,6 @@ func readInput(input string) ([]byte, error) {
return ioutil.ReadFile(input)
}
func passwordFromConsole(userQuery string) ([]byte, error) {
// Read from terminal (if there is one).
if terminal.IsTerminal(int(syscall.Stdin)) { //nolint
fmt.Print(userQuery)
pw, err := terminal.ReadPassword(int(syscall.Stdin)) //nolint
if err != nil {
return nil, err
}
fmt.Println()
return pw, nil
}
// Read from stdin as a fallback.
reader := bufio.NewReader(os.Stdin)
pw, err := reader.ReadBytes('\n')
if err != nil {
return nil, err
}
return pw, nil
}
func setupLogging() {
setSubLogger("CHAN", log)
addSubLogger("CHDB", channeldb.UseLogger)
@ -326,10 +345,6 @@ func setSubLogger(subsystem string, logger btclog.Logger,
}
}
func noConsole() ([]byte, error) {
return nil, fmt.Errorf("wallet db requires console access")
}
func newExplorerAPI(apiURL string) *btc.ExplorerAPI {
// Override for testnet if default is used.
if apiURL == defaultAPIURL &&

@ -0,0 +1,90 @@
package main
import (
"fmt"
chantools_lnd "github.com/lightninglabs/chantools/lnd"
"github.com/lightningnetwork/lnd/keychain"
"github.com/spf13/cobra"
"github.com/tv42/zbase32"
)
var (
signedMsgPrefix = []byte("Lightning Signed Message:")
)
type signMessageCommand struct {
Msg string
rootKey *rootKey
cmd *cobra.Command
}
func newSignMessageCommand() *cobra.Command {
cc := &signMessageCommand{}
cc.cmd = &cobra.Command{
Use: "signmessage",
Short: "Sign a message with the node's private key.",
Long: `Sign msg with the resident node's private key.
Returns the signature as a zbase32 string.`,
Example: `chantools signmessage --msg=foobar`,
RunE: cc.Execute,
}
cc.cmd.Flags().StringVar(
&cc.Msg, "msg", "", "the message to sign",
)
cc.rootKey = newRootKey(cc.cmd, "decrypting the backup")
return cc.cmd
}
func (c *signMessageCommand) Execute(_ *cobra.Command, _ []string) error {
if c.Msg == "" {
return fmt.Errorf("please enter a valid msg")
}
extendedKey, err := c.rootKey.read()
if err != nil {
return fmt.Errorf("error reading root key: %w", err)
}
signer := &chantools_lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
// Create the key locator for the node key.
keyLocator := keychain.KeyLocator{
Family: keychain.KeyFamilyNodeKey,
Index: 0,
}
// Fetch the private key for node key.
privKey, err := signer.FetchPrivateKey(&keychain.KeyDescriptor{
KeyLocator: keyLocator,
})
if err != nil {
return err
}
// Create a new signer.
privKeyMsgSigner := keychain.NewPrivKeyMessageSigner(
privKey, keyLocator,
)
// Prepend the special lnd prefix.
// See: https://github.com/lightningnetwork/lnd/blob/63e698ec4990e678089533561fd95cfd684b67db/rpcserver.go#L1576 .
msg := []byte(c.Msg)
msg = append(signedMsgPrefix, msg...)
sigBytes, err := privKeyMsgSigner.SignMessageCompact(msg, true)
if err != nil {
return err
}
// Encode the signature.
sig := zbase32.EncodeToString(sigBytes)
fmt.Println(sig)
return nil
}

@ -0,0 +1,226 @@
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/btcsuite/btcd/txscript"
"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 packet.Inputs[inputIndex].WitnessUtxo == nil {
return fmt.Errorf("invalid PSBT, input %d is missing witness "+
"UTXO", inputIndex)
}
utxo := packet.Inputs[inputIndex].WitnessUtxo
// The signing is a bit different for P2WPKH, we need to specify the
// pk script as the witness script.
var witnessScript []byte
if txscript.IsPayToWitnessPubKeyHash(utxo.PkScript) {
witnessScript = utxo.PkScript
} else {
if len(packet.Inputs[inputIndex].WitnessScript) == 0 {
return fmt.Errorf("invalid PSBT, input %d is missing "+
"witness script", inputIndex)
}
witnessScript = packet.Inputs[inputIndex].WitnessScript
}
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")
}

@ -2,11 +2,11 @@ package main
import (
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr"
"github.com/btcsuite/btcd/wire"
@ -15,12 +15,15 @@ import (
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/peer"
"github.com/lightningnetwork/lnd/tor"
"github.com/spf13/cobra"
)
var (
dialTimeout = time.Minute
defaultTorDNSHostPort = "soa.nodes.lightning.directory:53"
)
type triggerForceCloseCommand struct {
@ -29,6 +32,8 @@ type triggerForceCloseCommand struct {
APIURL string
TorProxy string
rootKey *rootKey
cmd *cobra.Command
}
@ -37,13 +42,13 @@ func newTriggerForceCloseCommand() *cobra.Command {
cc := &triggerForceCloseCommand{}
cc.cmd = &cobra.Command{
Use: "triggerforceclose",
Short: "Connect to a CLN peer and send a custom message to " +
"trigger a force close of the specified channel",
Long: `Certain versions of CLN didn't properly react to error
messages sent by peers and therefore didn't follow the DLP protocol to recover
channel funds using SCB. This command can be used to trigger a force close with
those earlier versions of CLN (this command will not work for lnd peers or CLN
peers of a different version).`,
Short: "Connect to a Lightning Network peer and send " +
"specific messages to trigger a force close of the " +
"specified channel",
Long: `Asks the specified remote peer to force close a specific
channel by first sending a channel re-establish message, and if that doesn't
work, a custom error message (in case the peer is a specific version of CLN that
does not properly respond to a Data Loss Protection re-establish message).'`,
Example: `chantools triggerforceclose \
--peer 03abce...@xx.yy.zz.aa:9735 \
--channel_point abcdef01234...:x`,
@ -62,6 +67,10 @@ peers of a different version).`,
&cc.APIURL, "apiurl", defaultAPIURL, "API URL to use (must "+
"be esplora compatible)",
)
cc.cmd.Flags().StringVar(
&cc.TorProxy, "torproxy", "", "SOCKS5 proxy to use for Tor "+
"connections (to .onion addresses)",
)
cc.rootKey = newRootKey(cc.cmd, "deriving the identity key")
return cc.cmd
@ -88,101 +97,152 @@ func (c *triggerForceCloseCommand) Execute(_ *cobra.Command, _ []string) error {
PrivKey: identityPriv,
}
peerAddr, err := lncfg.ParseLNAddressString(
c.Peer, "9735", net.ResolveTCPAddr,
outPoint, err := parseOutPoint(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error parsing channel point: %w", err)
}
err = requestForceClose(
c.Peer, c.TorProxy, pubKey, outPoint, identityECDH,
)
if err != nil {
return fmt.Errorf("error parsing peer address: %w", err)
return fmt.Errorf("error requesting force close: %w", err)
}
outPoint, err := parseOutPoint(c.ChannelPoint)
log.Infof("Message sent, waiting for force close transaction to " +
"appear in mempool")
api := newExplorerAPI(c.APIURL)
channelAddress, err := api.Address(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error parsing channel point: %w", err)
return fmt.Errorf("error getting channel address: %w", err)
}
spends, err := api.Spends(channelAddress)
if err != nil {
return fmt.Errorf("error getting spends: %w", err)
}
for len(spends) == 0 {
log.Infof("No spends found yet, waiting 5 seconds...")
time.Sleep(5 * time.Second)
spends, err = api.Spends(channelAddress)
if err != nil {
return fmt.Errorf("error getting spends: %w", err)
}
}
channelID := lnwire.NewChanIDFromOutPoint(outPoint)
conn, err := noiseDial(
identityECDH, peerAddr, &tor.ClearNet{}, dialTimeout,
log.Infof("Found force close transaction %v", spends[0].TXID)
log.Infof("You can now use the sweepremoteclosed command to sweep " +
"the funds from the channel")
return nil
}
func noiseDial(idKey keychain.SingleKeyECDH, lnAddr *lnwire.NetAddress,
netCfg tor.Net, timeout time.Duration) (*brontide.Conn, error) {
return brontide.Dial(idKey, lnAddr, timeout, netCfg.Dial)
}
func connectPeer(peerHost, torProxy string, peerPubKey *btcec.PublicKey,
identity keychain.SingleKeyECDH,
dialTimeout time.Duration) (*peer.Brontide, error) {
var dialNet tor.Net = &tor.ClearNet{}
if torProxy != "" {
dialNet = &tor.ProxyNet{
SOCKS: torProxy,
DNS: defaultTorDNSHostPort,
StreamIsolation: false,
SkipProxyForClearNetTargets: true,
}
}
log.Debugf("Attempting to resolve peer address %v", peerHost)
peerAddr, err := lncfg.ParseLNAddressString(
peerHost, "9735", dialNet.ResolveTCPAddr,
)
if err != nil {
return fmt.Errorf("error dialing peer: %w", err)
return nil, fmt.Errorf("error parsing peer address: %w", err)
}
log.Infof("Attempting to connect to peer %x, dial timeout is %v",
pubKey.SerializeCompressed(), dialTimeout)
log.Debugf("Attempting to dial resolved peer address %v",
peerAddr.String())
conn, err := noiseDial(identity, peerAddr, dialNet, dialTimeout)
if err != nil {
return nil, fmt.Errorf("error dialing peer: %w", err)
}
log.Infof("Attempting to establish p2p connection to peer %x, dial"+
"timeout is %v", peerPubKey.SerializeCompressed(), dialTimeout)
req := &connmgr.ConnReq{
Addr: peerAddr,
Permanent: false,
}
p, err := lnd.ConnectPeer(conn, req, chainParams, identityECDH)
p, err := lnd.ConnectPeer(conn, req, chainParams, identity)
if err != nil {
return fmt.Errorf("error connecting to peer: %w", err)
return nil, fmt.Errorf("error connecting to peer: %w", err)
}
log.Infof("Connection established to peer %x",
pubKey.SerializeCompressed())
peerPubKey.SerializeCompressed())
// We'll wait until the peer is active.
select {
case <-p.ActiveSignal():
case <-p.QuitSignal():
return fmt.Errorf("peer %x disconnected",
pubKey.SerializeCompressed())
return nil, fmt.Errorf("peer %x disconnected",
peerPubKey.SerializeCompressed())
}
return p, nil
}
func requestForceClose(peerHost, torProxy string, peerPubKey *btcec.PublicKey,
channelPoint *wire.OutPoint, identity keychain.SingleKeyECDH) error {
p, err := connectPeer(
peerHost, torProxy, peerPubKey, identity, dialTimeout,
)
if err != nil {
return fmt.Errorf("error connecting to peer: %w", err)
}
channelID := lnwire.NewChanIDFromOutPoint(channelPoint)
// Channel ID (32 byte) + u16 for the data length (which will be 0).
data := make([]byte, 34)
copy(data[:32], channelID[:])
log.Infof("Sending channel error message to peer to trigger force "+
"close of channel %v", c.ChannelPoint)
log.Infof("Sending channel re-establish to peer to trigger force "+
"close of channel %v", channelPoint)
_ = lnwire.SetCustomOverrides([]uint16{lnwire.MsgError})
msg, err := lnwire.NewCustom(lnwire.MsgError, data)
err = p.SendMessageLazy(true, &lnwire.ChannelReestablish{
ChanID: channelID,
})
if err != nil {
return err
}
err = p.SendMessageLazy(true, msg)
if err != nil {
return fmt.Errorf("error sending message: %w", err)
}
log.Infof("Message sent, waiting for force close transaction to " +
"appear in mempool")
log.Infof("Sending channel error message to peer to trigger force "+
"close of channel %v", channelPoint)
api := newExplorerAPI(c.APIURL)
channelAddress, err := api.Address(c.ChannelPoint)
_ = lnwire.SetCustomOverrides([]uint16{
lnwire.MsgError, lnwire.MsgChannelReestablish,
})
msg, err := lnwire.NewCustom(lnwire.MsgError, data)
if err != nil {
return fmt.Errorf("error getting channel address: %w", err)
return err
}
spends, err := api.Spends(channelAddress)
err = p.SendMessageLazy(true, msg)
if err != nil {
return fmt.Errorf("error getting spends: %w", err)
}
for len(spends) == 0 {
log.Infof("No spends found yet, waiting 5 seconds...")
time.Sleep(5 * time.Second)
spends, err = api.Spends(channelAddress)
if err != nil {
return fmt.Errorf("error getting spends: %w", err)
}
return fmt.Errorf("error sending message: %w", err)
}
log.Infof("Found force close transaction %v", spends[0].TXID)
log.Infof("You can now use the sweepremoteclosed command to sweep " +
"the funds from the channel")
return nil
}
func noiseDial(idKey keychain.SingleKeyECDH, lnAddr *lnwire.NetAddress,
netCfg tor.Net, timeout time.Duration) (*brontide.Conn, error) {
return brontide.Dial(idKey, lnAddr, timeout, netCfg.Dial)
}
func parseOutPoint(s string) (*wire.OutPoint, error) {
split := strings.Split(s, ":")
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {

@ -1,28 +1,19 @@
package main
import (
"errors"
"fmt"
"os"
"strings"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcwallet/snacl"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
"github.com/lightninglabs/chantools/lnd"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/spf13/cobra"
"go.etcd.io/bbolt"
)
const (
passwordEnvName = "WALLET_PASSWORD"
walletInfoFormat = `
Identity Pubkey: %x
BIP32 HD extended root key: %s
@ -38,19 +29,7 @@ Scope: m/%d'/%d'
)
var (
// Namespace from github.com/btcsuite/btcwallet/wallet/wallet.go.
waddrmgrNamespaceKey = []byte("waddrmgr")
// Bucket names from github.com/btcsuite/btcwallet/waddrmgr/db.go.
mainBucketName = []byte("main")
masterPrivKeyName = []byte("mpriv")
cryptoPrivKeyName = []byte("cpriv")
masterHDPrivName = []byte("mhdpriv")
defaultAccount = uint32(waddrmgr.DefaultAccountNum)
openCallbacks = &waddrmgr.OpenCallbacks{
ObtainSeed: noConsole,
ObtainPrivatePass: noConsole,
}
defaultAccount = uint32(waddrmgr.DefaultAccountNum)
)
type walletInfoCommand struct {
@ -98,75 +77,24 @@ or simply press <enter> without entering a password when being prompted.`,
}
func (c *walletInfoCommand) Execute(_ *cobra.Command, _ []string) error {
var (
publicWalletPw = lnwallet.DefaultPublicPassphrase
privateWalletPw = lnwallet.DefaultPrivatePassphrase
err error
)
// Check that we have a wallet DB.
if c.WalletDB == "" {
return fmt.Errorf("wallet DB is required")
}
// To automate things with chantools, we also offer reading the wallet
// password from environment variables.
pw := []byte(strings.TrimSpace(os.Getenv(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:
pw, err = passwordFromConsole("Input wallet password: ")
if err != nil {
return err
}
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 load and open the wallet.
db, err := walletdb.Open(
"bdb", lncfg.CleanAndExpandPath(c.WalletDB), false,
lnd.DefaultOpenTimeout,
w, privateWalletPw, cleanup, err := lnd.OpenWallet(
c.WalletDB, chainParams,
)
if errors.Is(err, bbolt.ErrTimeout) {
return fmt.Errorf("error opening wallet database, make sure " +
"lnd is not running and holding the exclusive lock " +
"on the wallet")
}
if err != nil {
return fmt.Errorf("error opening wallet database: %w", err)
return fmt.Errorf("error opening wallet file '%s': %w",
c.WalletDB, err)
}
defer func() { _ = db.Close() }()
w, err := wallet.Open(db, publicWalletPw, openCallbacks, chainParams, 0)
if err != nil {
return err
}
// Start and unlock the wallet.
w.Start()
defer w.Stop()
err = w.Unlock(privateWalletPw, nil)
if err != nil {
return err
}
defer func() {
if err := cleanup(); err != nil {
log.Errorf("error closing wallet: %v", err)
}
}()
// Print the wallet info and if requested the root key.
identityKey, scopeInfo, err := walletInfo(w, c.DumpAddrs)
@ -175,7 +103,9 @@ func (c *walletInfoCommand) Execute(_ *cobra.Command, _ []string) error {
}
rootKey := na
if c.WithRootKey {
masterHDPrivKey, err := decryptRootKey(db, privateWalletPw)
masterHDPrivKey, err := lnd.DecryptWalletRootKey(
w.Database(), privateWalletPw,
)
if err != nil {
return err
}
@ -259,7 +189,7 @@ func walletInfo(w *wallet.Wallet, dumpAddrs bool) (*btcec.PublicKey, string,
err = walletdb.View(
w.Database(), func(tx walletdb.ReadTx) error {
waddrmgrNs := tx.ReadBucket(
waddrmgrNamespaceKey,
lnd.WaddrmgrNamespaceKey,
)
return mgr.ForEachAccountAddress(
@ -304,64 +234,3 @@ func printScopeInfo(name string, w *wallet.Wallet,
return scopeInfo, nil
}
func decryptRootKey(db walletdb.DB, privPassphrase []byte) ([]byte, error) {
// Step 1: Load the encryption parameters and encrypted keys from the
// database.
var masterKeyPrivParams []byte
var cryptoKeyPrivEnc []byte
var masterHDPrivEnc []byte
err := walletdb.View(db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
if ns == nil {
return fmt.Errorf("namespace '%s' does not exist",
waddrmgrNamespaceKey)
}
mainBucket := ns.NestedReadBucket(mainBucketName)
if mainBucket == nil {
return fmt.Errorf("bucket '%s' does not exist",
mainBucketName)
}
val := mainBucket.Get(masterPrivKeyName)
if val != nil {
masterKeyPrivParams = make([]byte, len(val))
copy(masterKeyPrivParams, val)
}
val = mainBucket.Get(cryptoPrivKeyName)
if val != nil {
cryptoKeyPrivEnc = make([]byte, len(val))
copy(cryptoKeyPrivEnc, val)
}
val = mainBucket.Get(masterHDPrivName)
if val != nil {
masterHDPrivEnc = make([]byte, len(val))
copy(masterHDPrivEnc, val)
}
return nil
})
if err != nil {
return nil, err
}
// Step 2: Unmarshal the master private key parameters and derive
// key from passphrase.
var masterKeyPriv snacl.SecretKey
if err := masterKeyPriv.Unmarshal(masterKeyPrivParams); err != nil {
return nil, err
}
if err := masterKeyPriv.DeriveKey(&privPassphrase); err != nil {
return nil, err
}
// Step 3: Decrypt the keys in the correct order.
cryptoKeyPriv := &snacl.CryptoKey{}
cryptoKeyPrivBytes, err := masterKeyPriv.Decrypt(cryptoKeyPrivEnc)
if err != nil {
return nil, err
}
copy(cryptoKeyPriv[:], cryptoKeyPrivBytes)
return cryptoKeyPriv.Decrypt(masterHDPrivEnc)
}

@ -3,6 +3,7 @@ package main
import (
"testing"
"github.com/lightninglabs/chantools/lnd"
"github.com/stretchr/testify/require"
)
@ -20,7 +21,7 @@ func TestWalletInfo(t *testing.T) {
WithRootKey: true,
}
t.Setenv(passwordEnvName, testPassPhrase)
t.Setenv(lnd.PasswordEnvName, testPassPhrase)
err := info.Execute(nil, nil)
require.NoError(t, err)

@ -369,10 +369,8 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
return fmt.Errorf("error creating PSBT from TX: %w", err)
}
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
// First we add the necessary information to the psbt package so that
// we can sign the transaction with SIGHASH_ALL.
for idx, txIn := range inputs {
channel := keys1.Channels[idx]
@ -399,6 +397,16 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
Value: channel.theirKey.SerializeCompressed(),
},
)
}
// Loop a second time through the inputs and sign each input. We now
// have all the witness/nonwitness data filled in the psbt package.
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
for idx, txIn := range inputs {
channel := keys1.Channels[idx]
keyDesc := keychain.KeyDescriptor{
PubKey: channel.ourKey,

@ -23,6 +23,7 @@ https://github.com/lightninglabs/chantools/.
* [chantools chanbackup](chantools_chanbackup.md) - Create a channel.backup file from a channel database
* [chantools closepoolaccount](chantools_closepoolaccount.md) - Tries to close a Pool account that has expired
* [chantools compactdb](chantools_compactdb.md) - Create a copy of a channel.db file in safe/read-only mode
* [chantools createwallet](chantools_createwallet.md) - Create a new lnd compatible wallet.db file from an existing seed or by generating a new one
* [chantools deletepayments](chantools_deletepayments.md) - Remove all (failed) payments from a channel DB
* [chantools derivekey](chantools_derivekey.md) - Derive a key with a specific derivation path
* [chantools doublespendinputs](chantools_doublespendinputs.md) - Replace a transaction by double spending its input
@ -43,12 +44,14 @@ https://github.com/lightninglabs/chantools/.
* [chantools rescuefunding](chantools_rescuefunding.md) - Rescue funds locked in a funding multisig output that never resulted in a proper channel; this is the command the initiator of the channel needs to run
* [chantools rescuetweakedkey](chantools_rescuetweakedkey.md) - Attempt to rescue funds locked in an address with a key that was affected by a specific bug in lnd
* [chantools showrootkey](chantools_showrootkey.md) - Extract and show the BIP32 HD root key from the 24 word lnd aezeed
* [chantools signmessage](chantools_signmessage.md) - Sign a message with the node's private key.
* [chantools signpsbt](chantools_signpsbt.md) - Sign a Partially Signed Bitcoin Transaction (PSBT)
* [chantools signrescuefunding](chantools_signrescuefunding.md) - Rescue funds locked in a funding multisig output that never resulted in a proper channel; this is the command the remote node (the non-initiator) of the channel needs to run
* [chantools summary](chantools_summary.md) - Compile a summary about the current state of channels
* [chantools sweepremoteclosed](chantools_sweepremoteclosed.md) - Go through all the addresses that could have funds of channels that were force-closed by the remote party. A public block explorer is queried for each address and if any balance is found, all funds are swept to a given address
* [chantools sweeptimelock](chantools_sweeptimelock.md) - Sweep the force-closed state after the time lock has expired
* [chantools sweeptimelockmanual](chantools_sweeptimelockmanual.md) - Sweep the force-closed state of a single channel manually if only a channel backup file is available
* [chantools triggerforceclose](chantools_triggerforceclose.md) - Connect to a CLN peer and send a custom message to trigger a force close of the specified channel
* [chantools triggerforceclose](chantools_triggerforceclose.md) - Connect to a Lightning Network peer and send specific messages to trigger a force close of the specified channel
* [chantools vanitygen](chantools_vanitygen.md) - Generate a seed with a custom lnd node identity public key that starts with the given prefix
* [chantools walletinfo](chantools_walletinfo.md) - Shows info about an lnd wallet.db file and optionally extracts the BIP32 HD root key
* [chantools zombierecovery](chantools_zombierecovery.md) - Try rescuing funds stuck in channels with zombie nodes

@ -27,6 +27,7 @@ chantools chanbackup \
-h, --help help for chanbackup
--multi_file string lnd channel.backup file to create
--rootkey string BIP32 HD root key of the wallet to use for creating the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro creating the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -42,6 +42,7 @@ chantools closepoolaccount \
--publish publish sweep TX to the chain API instead of just printing the TX
--rootkey string BIP32 HD root key of the wallet to use for deriving keys; leave empty to prompt for lnd 24 word aezeed
--sweepaddr string address to recover the funds to; specify 'fromseed' to derive a new address from the seed automatically
--walletdb string read the seed/master root key to use fro deriving keys from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -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

@ -10,7 +10,7 @@ If only the failed payments should be deleted (and not the successful ones), the
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!'
run lnd v0.17.4-beta or later after using this command!'
```
chantools deletepayments [flags]

@ -23,12 +23,13 @@ chantools derivekey --identity
### Options
```
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for derivekey
--identity derive the lnd identity_pubkey
--neuter don't output private key(s), only public key(s)
--path string BIP32 derivation path to derive; must start with "m/"
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for derivekey
--identity derive the lnd identity_pubkey
--neuter don't output private key(s), only public key(s)
--path string BIP32 derivation path to derive; must start with "m/"
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro decrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -34,6 +34,7 @@ chantools doublespendinputs \
--recoverywindow uint32 number of keys to scan per internal/external branch; output will consist of double this amount of keys (default 2500)
--rootkey string BIP32 HD root key of the wallet to use for deriving the input keys; leave empty to prompt for lnd 24 word aezeed
--sweepaddr string address to recover the funds to; specify 'fromseed' to derive a new address from the seed automatically
--walletdb string read the seed/master root key to use fro deriving the input keys from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -12,7 +12,7 @@ without removing any other data.
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!'
run lnd v0.17.4-beta or later after using this command!'
```
chantools dropchannelgraph [flags]

@ -12,7 +12,7 @@ be helpful to fix a graph that is out of sync with the network.
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!'
run lnd v0.17.4-beta or later after using this command!'
```
chantools dropgraphzombies [flags]

@ -25,6 +25,7 @@ chantools dumpbackup \
-h, --help help for dumpbackup
--multi_file string lnd channel.backup file to dump
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro decrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -61,10 +61,11 @@ chantools fakechanbackup --from_channel_graph lncli_describegraph.json \
--channelpoint string funding transaction outpoint of the channel to rescue (<txid>:<txindex>) as it is displayed on 1ml.com
--from_channel_graph string the full LN channel graph in the JSON format that the 'lncli describegraph' returns
-h, --help help for fakechanbackup
--multi_file string the fake channel backup file to create (default "results/fake-2023-04-11-16-33-35.backup")
--multi_file string the fake channel backup file to create (default "results/fake-2024-01-26-02-27-52.backup")
--remote_node_addr string the remote node connection information in the format pubkey@host:port
--rootkey string BIP32 HD root key of the wallet to use for encrypting the backup; leave empty to prompt for lnd 24 word aezeed
--short_channel_id string the short channel ID in the format <blockheight>x<transactionindex>x<outputindex>
--walletdb string read the seed/master root key to use fro encrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -27,6 +27,7 @@ chantools filterbackup \
-h, --help help for filterbackup
--multi_file string lnd channel.backup file to filter
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro decrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -28,6 +28,7 @@ chantools fixoldbackup \
-h, --help help for fixoldbackup
--multi_file string lnd channel.backup file to fix
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro decrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -43,6 +43,7 @@ chantools forceclose \
--pendingchannels string channel input is in the format of lncli's pendingchannels format; specify '-' to read from stdin
--publish publish force-closing TX to the chain API instead of just printing the TX
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro decrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -51,6 +51,7 @@ chantools genimportscript --format bitcoin-cli \
--rescanfrom uint32 block number to rescan from; will be set automatically from the wallet birthday if the lnd 24 word aezeed is entered (default 500000)
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--stdout write generated import script to standard out instead of writing it to a file
--walletdb string read the seed/master root key to use fro decrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -11,7 +11,7 @@ needs to read the database content.
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!'
run lnd v0.17.4-beta or later after using this command!'
```
chantools migratedb [flags]

@ -33,6 +33,7 @@ chantools pullanchor \
-h, --help help for pullanchor
--rootkey string BIP32 HD root key of the wallet to use for deriving keys; leave empty to prompt for lnd 24 word aezeed
--sponsorinput string the input to use to sponsor the CPFP transaction; must be owned by the lnd node that owns the anchor output
--walletdb string read the seed/master root key to use fro deriving keys from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -27,13 +27,16 @@ chantools recoverloopin \
-h, --help help for recoverloopin
--loop_db_dir string path to the loop database directory, where the loop.db file is located
--num_tries int number of tries to try to find the correct key index (default 1000)
--output_amt uint amount of the output to sweep
--publish publish sweep TX to the chain API instead of just printing the TX
--rootkey string BIP32 HD root key of the wallet to use for deriving starting key; leave empty to prompt for lnd 24 word aezeed
--sqlite_file string optional path to the loop sqlite database file, if not specified, the default location will be loaded from --loop_db_dir
--start_key_index int start key index to try to find the correct key index
--swap_hash string swap hash of the loop in swap
--sweepaddr string address to recover the funds to; specify 'fromseed' to derive a new address from the seed automatically
--txid string transaction id of the on-chain transaction that created the HTLC
--vout uint32 output index of the on-chain transaction that created the HTLC
--walletdb string read the seed/master root key to use fro deriving starting key from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -11,7 +11,7 @@ channel was never confirmed on chain!
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!
run lnd v0.17.4-beta or later after using this command!
```
chantools removechannel [flags]

@ -60,6 +60,7 @@ chantools rescueclosed --fromsummary results/summary-xxxxxx.json \
--lnd_log string the lnd log file to read to get the commit_point values when rescuing multiple channels at the same time
--pendingchannels string channel input is in the format of lncli's pendingchannels format; specify '-' to read from stdin
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro decrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -50,6 +50,7 @@ chantools rescuefunding \
--remotepubkey string in case a channel DB is not available (but perhaps a channel backup file), the remote multisig public key can be specified manually
--rootkey string BIP32 HD root key of the wallet to use for deriving keys; leave empty to prompt for lnd 24 word aezeed
--sweepaddr string address to recover the funds to; specify 'fromseed' to derive a new address from the seed automatically
--walletdb string read the seed/master root key to use fro deriving keys from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -29,6 +29,7 @@ chantools rescuetweakedkey \
--path string BIP32 derivation path to derive the starting key from; must start with "m/"
--rootkey string BIP32 HD root key of the wallet to use for deriving starting key; leave empty to prompt for lnd 24 word aezeed
--targetaddr string address the funds are locked in
--walletdb string read the seed/master root key to use fro deriving starting key from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -21,9 +21,10 @@ chantools showrootkey
### Options
```
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for showrootkey
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for showrootkey
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro decrypting the backup from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -0,0 +1,41 @@
## chantools signmessage
Sign a message with the node's private key.
### Synopsis
Sign msg with the resident node's private key.
Returns the signature as a zbase32 string.
```
chantools signmessage [flags]
```
### Examples
```
chantools signmessage --msg=foobar
```
### Options
```
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for signmessage
--msg string the message to sign
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro decrypting the backup 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

@ -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

@ -26,10 +26,11 @@ chantools signrescuefunding \
### Options
```
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for signrescuefunding
--psbt string Partially Signed Bitcoin Transaction that was provided by the initiator of the channel to rescue
--rootkey string BIP32 HD root key of the wallet to use for deriving keys; leave empty to prompt for lnd 24 word aezeed
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for signrescuefunding
--psbt string Partially Signed Bitcoin Transaction that was provided by the initiator of the channel to rescue
--rootkey string BIP32 HD root key of the wallet to use for deriving keys; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro deriving keys from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -41,6 +41,7 @@ chantools sweepremoteclosed \
--recoverywindow uint32 number of keys to scan per derivation path (default 200)
--rootkey string BIP32 HD root key of the wallet to use for sweeping the wallet; leave empty to prompt for lnd 24 word aezeed
--sweepaddr string address to recover the funds to; specify 'fromseed' to derive a new address from the seed automatically
--walletdb string read the seed/master root key to use fro sweeping the wallet from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -41,6 +41,7 @@ chantools sweeptimelock \
--publish publish sweep TX to the chain API instead of just printing the TX
--rootkey string BIP32 HD root key of the wallet to use for deriving keys; leave empty to prompt for lnd 24 word aezeed
--sweepaddr string address to recover the funds to; specify 'fromseed' to derive a new address from the seed automatically
--walletdb string read the seed/master root key to use fro deriving keys from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -63,6 +63,7 @@ chantools sweeptimelockmanual \
--rootkey string BIP32 HD root key of the wallet to use for deriving keys; leave empty to prompt for lnd 24 word aezeed
--sweepaddr string address to recover the funds to; specify 'fromseed' to derive a new address from the seed automatically
--timelockaddr string address of the time locked commitment output where the funds are stuck in
--walletdb string read the seed/master root key to use fro deriving keys from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -1,14 +1,13 @@
## chantools triggerforceclose
Connect to a CLN peer and send a custom message to trigger a force close of the specified channel
Connect to a Lightning Network peer and send specific messages to trigger a force close of the specified channel
### Synopsis
Certain versions of CLN didn't properly react to error
messages sent by peers and therefore didn't follow the DLP protocol to recover
channel funds using SCB. This command can be used to trigger a force close with
those earlier versions of CLN (this command will not work for lnd peers or CLN
peers of a different version).
Asks the specified remote peer to force close a specific
channel by first sending a channel re-establish message, and if that doesn't
work, a custom error message (in case the peer is a specific version of CLN that
does not properly respond to a Data Loss Protection re-establish message).'
```
chantools triggerforceclose [flags]
@ -31,6 +30,8 @@ chantools triggerforceclose \
-h, --help help for triggerforceclose
--peer string remote peer address (<pubkey>@<host>[:<port>])
--rootkey string BIP32 HD root key of the wallet to use for deriving the identity key; leave empty to prompt for lnd 24 word aezeed
--torproxy string SOCKS5 proxy to use for Tor connections (to .onion addresses)
--walletdb string read the seed/master root key to use fro deriving the identity key from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -31,9 +31,11 @@ chantools zombierecovery makeoffer \
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
--feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30)
-h, --help help for makeoffer
--matchonly only match the keys, don't create an offer
--node1_keys string the JSON file generated in theprevious step ('preparekeys') command of node 1
--node2_keys string the JSON file generated in theprevious step ('preparekeys') command of node 2
--rootkey string BIP32 HD root key of the wallet to use for signing the offer; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro signing the offer from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -28,8 +28,10 @@ chantools zombierecovery preparekeys \
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for preparekeys
--match_file string the match JSON file that was sent to both nodes by the match maker
--num_keys uint32 the number of multisig keys to derive (default 2500)
--payout_addr string the address where this node's rescued funds should be sent to, must be a P2WPKH (native SegWit) address
--rootkey string BIP32 HD root key of the wallet to use for deriving the multisig keys; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro deriving the multisig keys from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -21,10 +21,11 @@ chantools zombierecovery signoffer \
### Options
```
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for signoffer
--psbt string the base64 encoded PSBT that the other party sent as an offer to rescue funds
--rootkey string BIP32 HD root key of the wallet to use for signing the offer; leave empty to prompt for lnd 24 word aezeed
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
-h, --help help for signoffer
--psbt string the base64 encoded PSBT that the other party sent as an offer to rescue funds
--rootkey string BIP32 HD root key of the wallet to use for signing the offer; leave empty to prompt for lnd 24 word aezeed
--walletdb string read the seed/master root key to use fro signing the offer from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
```
### Options inherited from parent commands

@ -3,13 +3,13 @@ module github.com/lightninglabs/chantools
go 1.21
require (
github.com/btcsuite/btcd v0.23.5-0.20230905170901-80f5a0ffdf36
github.com/btcsuite/btcd v0.24.1-0.20240123000108-62e6af035ec5
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.4-0.20230904040416-d4f519f5dc05
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/btcsuite/btcd/btcutil/psbt v1.1.8
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcwallet v0.16.10-0.20230804184612-07be54bc22cf
github.com/btcsuite/btcwallet v0.16.10-0.20240127010340-16b422a2e8bf
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0
github.com/btcsuite/btcwallet/walletdb v1.4.0
github.com/coreos/bbolt v1.3.3
@ -17,13 +17,13 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
github.com/gogo/protobuf v1.3.2 // indirect
github.com/hasura/go-graphql-client v0.9.1
github.com/lightninglabs/loop v0.23.0-beta
github.com/lightninglabs/loop v0.26.6-beta
github.com/lightninglabs/pool v0.6.2-beta.0.20230329135228-c3bffb52df3a
// The current version of lnd we are compatible with, mostly affects the
// commands that touch the channel DB and has an impact on the DB schema.
// NOTE: When updating this version, make sure to also update the string in
// cmd/chantools/root.go.
github.com/lightningnetwork/lnd v0.17.0-beta
github.com/lightningnetwork/lnd v0.17.4-beta
github.com/lightningnetwork/lnd/kvdb v1.4.4
github.com/lightningnetwork/lnd/queue v1.1.1
github.com/lightningnetwork/lnd/ticker v1.1.1
@ -31,15 +31,20 @@ require (
github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.8.4
go.etcd.io/bbolt v1.3.7
golang.org/x/crypto v0.17.0
golang.org/x/crypto v0.21.0
golang.org/x/oauth2 v0.11.0
)
require github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa
require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/siphash v1.0.1 // indirect
github.com/andybalholm/brotli v1.0.3 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 // indirect
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect
@ -49,11 +54,16 @@ require (
github.com/btcsuite/winsvc v1.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/lru v1.0.0 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v24.0.9+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fergusstrange/embedded-postgres v1.10.0 // indirect
@ -61,40 +71,45 @@ require (
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang-migrate/migrate/v4 v4.16.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.0 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.1 // indirect
github.com/jackc/pgx/v4 v4.18.2 // indirect
github.com/jessevdk/go-flags v1.4.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/jrick/logrotate v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kkdai/bstream v1.0.0 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/lib/pq v1.10.3 // indirect
github.com/lightninglabs/aperture v0.1.20-beta // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/lightninglabs/aperture v0.1.21-beta.0.20230705004936-87bb996a4030 // indirect
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
github.com/lightninglabs/lndclient v0.16.0-10 // indirect
github.com/lightninglabs/loop/swapserverrpc v1.0.4 // indirect
github.com/lightninglabs/lndclient v0.17.4-1 // indirect
github.com/lightninglabs/loop/swapserverrpc v1.0.5 // indirect
github.com/lightninglabs/neutrino v0.16.0 // indirect
github.com/lightninglabs/neutrino/cache v1.1.1 // indirect
github.com/lightninglabs/pool/auctioneerrpc v1.0.7 // indirect
@ -103,19 +118,26 @@ require (
github.com/lightningnetwork/lnd/healthcheck v1.2.3 // indirect
github.com/lightningnetwork/lnd/tlv v1.1.1 // indirect
github.com/ltcsuite/ltcd v0.0.0-20191228044241-92166e412499 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mholt/archiver/v3 v3.5.0 // indirect
github.com/miekg/dns v1.1.43 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nwaples/rardecode v1.1.2 // indirect
github.com/pierrec/lz4/v4 v4.1.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.12 // indirect
github.com/ory/dockertest/v3 v3.10.0 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
@ -127,6 +149,9 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
@ -139,23 +164,24 @@ require (
go.etcd.io/etcd/server/v3 v3.5.7 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect
go.opentelemetry.io/otel v1.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 // indirect
go.opentelemetry.io/otel/metric v1.20.0 // indirect
go.opentelemetry.io/otel/sdk v1.0.1 // indirect
go.opentelemetry.io/otel/sdk v1.3.0 // indirect
go.opentelemetry.io/otel/trace v1.20.0 // indirect
go.opentelemetry.io/proto/otlp v0.9.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.17.0 // indirect
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
@ -164,7 +190,7 @@ require (
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/errgo.v1 v1.0.1 // indirect
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
gopkg.in/macaroon-bakery.v2 v2.1.0 // indirect
gopkg.in/macaroon.v2 v2.1.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

894
go.sum

File diff suppressed because it is too large Load Diff

@ -2,6 +2,7 @@ package lnd
import (
"bufio"
"errors"
"fmt"
"os"
"regexp"
@ -11,20 +12,47 @@ import (
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/snacl"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnwallet"
"go.etcd.io/bbolt"
"golang.org/x/crypto/ssh/terminal"
)
const (
MnemonicEnvName = "AEZEED_MNEMONIC"
PassphraseEnvName = "AEZEED_PASSPHRASE"
PasswordEnvName = "WALLET_PASSWORD"
)
var (
numberDotsRegex = regexp.MustCompile(`[\d.\-\n\r\t]*`)
multipleSpaces = regexp.MustCompile(" [ ]+")
openCallbacks = &waddrmgr.OpenCallbacks{
ObtainSeed: noConsole,
ObtainPrivatePass: noConsole,
}
// Namespace from github.com/btcsuite/btcwallet/wallet/wallet.go.
WaddrmgrNamespaceKey = []byte("waddrmgr")
// Bucket names from github.com/btcsuite/btcwallet/waddrmgr/db.go.
mainBucketName = []byte("main")
masterPrivKeyName = []byte("mpriv")
cryptoPrivKeyName = []byte("cpriv")
masterHDPrivName = []byte("mhdpriv")
)
func noConsole() ([]byte, error) {
return nil, fmt.Errorf("wallet db requires console access")
}
// ReadAezeed reads an aezeed from the console or the environment variable.
func ReadAezeed(params *chaincfg.Params) (*hdkeychain.ExtendedKey, time.Time,
error) {
@ -67,6 +95,32 @@ func ReadAezeed(params *chaincfg.Params) (*hdkeychain.ExtendedKey, time.Time,
len(cipherSeedMnemonic), 24)
}
passphraseBytes, err := ReadPassphrase("doesn't have")
if err != nil {
return nil, time.Unix(0, 0), err
}
var mnemonic aezeed.Mnemonic
copy(mnemonic[:], cipherSeedMnemonic)
// If we're unable to map it back into the ciphertext, then either the
// mnemonic is wrong, or the passphrase is wrong.
cipherSeed, err := mnemonic.ToCipherSeed(passphraseBytes)
if err != nil {
return nil, time.Unix(0, 0), fmt.Errorf("failed to decrypt "+
"seed with passphrase: %w", err)
}
rootKey, err := hdkeychain.NewMaster(cipherSeed.Entropy[:], params)
if err != nil {
return nil, time.Unix(0, 0), fmt.Errorf("failed to derive " +
"master extended key")
}
return rootKey, cipherSeed.BirthdayTime(), nil
}
// ReadPassphrase reads a cipher seed passphrase from the console or the
// environment variable.
func ReadPassphrase(verb string) ([]byte, error) {
// Additionally, the user may have a passphrase, that will also need to
// be provided so the daemon can properly decipher the cipher seed.
// Try the environment variable first.
@ -85,14 +139,14 @@ func ReadAezeed(params *chaincfg.Params) (*hdkeychain.ExtendedKey, time.Time,
// The environment variable didn't contain anything, we'll read the
// passphrase from the terminal.
case passphrase == "":
fmt.Printf("Input your cipher seed passphrase (press enter " +
"if your seed doesn't have a passphrase): ")
fmt.Printf("Input your cipher seed passphrase (press enter "+
"if your seed %s a passphrase): ", verb)
var err error
passphraseBytes, err = terminal.ReadPassword(
int(syscall.Stdin), //nolint
)
if err != nil {
return nil, time.Unix(0, 0), err
return nil, err
}
fmt.Println()
@ -101,20 +155,176 @@ func ReadAezeed(params *chaincfg.Params) (*hdkeychain.ExtendedKey, time.Time,
passphraseBytes = []byte(passphrase)
}
var mnemonic aezeed.Mnemonic
copy(mnemonic[:], cipherSeedMnemonic)
return passphraseBytes, nil
}
// If we're unable to map it back into the ciphertext, then either the
// mnemonic is wrong, or the passphrase is wrong.
cipherSeed, err := mnemonic.ToCipherSeed(passphraseBytes)
// PasswordFromConsole reads a password from the console or stdin.
func PasswordFromConsole(userQuery string) ([]byte, error) {
// Read from terminal (if there is one).
if terminal.IsTerminal(int(syscall.Stdin)) { //nolint
fmt.Print(userQuery)
pw, err := terminal.ReadPassword(int(syscall.Stdin)) //nolint
if err != nil {
return nil, err
}
fmt.Println()
return pw, nil
}
// Read from stdin as a fallback.
reader := bufio.NewReader(os.Stdin)
pw, err := reader.ReadBytes('\n')
if err != nil {
return nil, time.Unix(0, 0), fmt.Errorf("failed to decrypt "+
"seed with passphrase: %w", err)
return nil, err
}
return pw, nil
}
// OpenWallet opens a lnd compatible wallet and returns it, along with the
// private wallet password.
func OpenWallet(walletDbPath string,
chainParams *chaincfg.Params) (*wallet.Wallet, []byte, func() error,
error) {
var (
publicWalletPw = lnwallet.DefaultPublicPassphrase
privateWalletPw = lnwallet.DefaultPrivatePassphrase
err error
)
// To automate things with chantools, we also offer reading the wallet
// password from environment variables.
pw := []byte(strings.TrimSpace(os.Getenv(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:
pw, err = PasswordFromConsole("Input wallet password: ")
if err != nil {
return nil, nil, nil, err
}
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 load and open the wallet.
db, err := walletdb.Open(
"bdb", lncfg.CleanAndExpandPath(walletDbPath), false,
DefaultOpenTimeout,
)
if errors.Is(err, bbolt.ErrTimeout) {
return nil, nil, nil, fmt.Errorf("error opening wallet " +
"database, make sure lnd is not running and holding " +
"the exclusive lock on the wallet")
}
rootKey, err := hdkeychain.NewMaster(cipherSeed.Entropy[:], params)
if err != nil {
return nil, time.Unix(0, 0), fmt.Errorf("failed to derive " +
"master extended key")
return nil, nil, nil, fmt.Errorf("error opening wallet "+
"database: %w", err)
}
return rootKey, cipherSeed.BirthdayTime(), nil
w, err := wallet.Open(db, publicWalletPw, openCallbacks, chainParams, 0)
if err != nil {
_ = db.Close()
return nil, nil, nil, fmt.Errorf("error opening wallet %w", err)
}
// Start and unlock the wallet.
w.Start()
err = w.Unlock(privateWalletPw, nil)
if err != nil {
w.Stop()
_ = db.Close()
return nil, nil, nil, err
}
cleanup := func() error {
w.Stop()
if err := db.Close(); err != nil {
return err
}
return nil
}
return w, privateWalletPw, cleanup, nil
}
// DecryptWalletRootKey decrypts a lnd compatible wallet's root key.
func DecryptWalletRootKey(db walletdb.DB,
privatePassphrase []byte) ([]byte, error) {
// Step 1: Load the encryption parameters and encrypted keys from the
// database.
var masterKeyPrivParams []byte
var cryptoKeyPrivEnc []byte
var masterHDPrivEnc []byte
err := walletdb.View(db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(WaddrmgrNamespaceKey)
if ns == nil {
return fmt.Errorf("namespace '%s' does not exist",
WaddrmgrNamespaceKey)
}
mainBucket := ns.NestedReadBucket(mainBucketName)
if mainBucket == nil {
return fmt.Errorf("bucket '%s' does not exist",
mainBucketName)
}
val := mainBucket.Get(masterPrivKeyName)
if val != nil {
masterKeyPrivParams = make([]byte, len(val))
copy(masterKeyPrivParams, val)
}
val = mainBucket.Get(cryptoPrivKeyName)
if val != nil {
cryptoKeyPrivEnc = make([]byte, len(val))
copy(cryptoKeyPrivEnc, val)
}
val = mainBucket.Get(masterHDPrivName)
if val != nil {
masterHDPrivEnc = make([]byte, len(val))
copy(masterHDPrivEnc, val)
}
return nil
})
if err != nil {
return nil, err
}
// Step 2: Unmarshal the master private key parameters and derive
// key from passphrase.
var masterKeyPriv snacl.SecretKey
if err := masterKeyPriv.Unmarshal(masterKeyPrivParams); err != nil {
return nil, err
}
if err := masterKeyPriv.DeriveKey(&privatePassphrase); err != nil {
return nil, err
}
// Step 3: Decrypt the keys in the correct order.
cryptoKeyPriv := &snacl.CryptoKey{}
cryptoKeyPrivBytes, err := masterKeyPriv.Decrypt(cryptoKeyPrivEnc)
if err != nil {
return nil, err
}
copy(cryptoKeyPriv[:], cryptoKeyPrivBytes)
return cryptoKeyPriv.Decrypt(masterHDPrivEnc)
}

@ -30,15 +30,15 @@ func (s *Signer) SignOutputRaw(tx *wire.MsgTx,
// First attempt to fetch the private key which corresponds to the
// specified public key.
privKey, err := s.FetchPrivKey(&signDesc.KeyDesc)
privKey, err := s.FetchPrivateKey(&signDesc.KeyDesc)
if err != nil {
return nil, err
}
return s.SignOutputRawWithPrivkey(tx, signDesc, privKey)
return s.SignOutputRawWithPrivateKey(tx, signDesc, privKey)
}
func (s *Signer) SignOutputRawWithPrivkey(tx *wire.MsgTx,
func (s *Signer) SignOutputRawWithPrivateKey(tx *wire.MsgTx,
signDesc *input.SignDescriptor,
privKey *secp256k1.PrivateKey) (input.Signature, error) {
@ -122,7 +122,7 @@ func (s *Signer) ComputeInputScript(_ *wire.MsgTx, _ *input.SignDescriptor) (
return nil, fmt.Errorf("unimplemented")
}
func (s *Signer) FetchPrivKey(descriptor *keychain.KeyDescriptor) (
func (s *Signer) FetchPrivateKey(descriptor *keychain.KeyDescriptor) (
*btcec.PrivateKey, error) {
key, err := DeriveChildren(s.ExtendedKey, []uint32{
@ -181,6 +181,58 @@ func (s *Signer) AddPartialSignature(packet *psbt.Packet,
return nil
}
func (s *Signer) AddPartialSignatureForPrivateKey(packet *psbt.Packet,
privateKey *btcec.PrivateKey, utxo *wire.TxOut, witnessScript []byte,
inputIndex int) error {
// Now we add our partial signature.
prevOutFetcher := wallet.PsbtPrevOutputFetcher(packet)
signDesc := &input.SignDescriptor{
WitnessScript: witnessScript,
Output: utxo,
InputIndex: inputIndex,
HashType: txscript.SigHashAll,
PrevOutputFetcher: prevOutFetcher,
SigHashes: txscript.NewTxSigHashes(
packet.UnsignedTx, prevOutFetcher,
),
}
ourSigRaw, err := s.SignOutputRawWithPrivateKey(
packet.UnsignedTx, signDesc, privateKey,
)
if err != nil {
return fmt.Errorf("error signing with our key: %w", err)
}
ourSig := append(ourSigRaw.Serialize(), byte(txscript.SigHashAll))
// Great, we were able to create our sig, let's add it to the PSBT.
updater, err := psbt.NewUpdater(packet)
if err != nil {
return fmt.Errorf("error creating PSBT updater: %w", err)
}
// If the witness script is the pk script for a P2WPKH output, then we
// need to blank it out for the PSBT code, otherwise it interprets it as
// a P2WSH.
if txscript.IsPayToWitnessPubKeyHash(utxo.PkScript) {
witnessScript = nil
}
status, err := updater.Sign(
inputIndex, ourSig, privateKey.PubKey().SerializeCompressed(),
nil, witnessScript,
)
if err != nil {
return fmt.Errorf("error adding signature to PSBT: %w", err)
}
if status != 0 {
return fmt.Errorf("unexpected status for signature update, "+
"got %d wanted 0", status)
}
return nil
}
// maybeTweakPrivKey examines the single tweak parameters on the passed sign
// descriptor and may perform a mapping on the passed private key in order to
// utilize the tweaks, if populated.

@ -3,7 +3,7 @@ module github.com/lightninglabs/chantools/tools
go 1.18
require (
github.com/btcsuite/btcd v0.23.4
github.com/btcsuite/btcd v0.24.0
github.com/golangci/golangci-lint v1.51.2
github.com/ory/go-acc v0.2.8
github.com/rinchsan/gosimports v0.1.5
@ -32,8 +32,8 @@ require (
github.com/breml/bidichk v0.2.3 // indirect
github.com/breml/errchkjson v0.3.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect
github.com/btcsuite/btcd/btcutil v1.1.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
@ -194,7 +194,7 @@ require (
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

@ -92,16 +92,20 @@ github.com/breml/errchkjson v0.3.0 h1:YdDqhfqMT+I1vIxPSas44P+9Z9HzJwCeAzjB8PxP1x
github.com/breml/errchkjson v0.3.0/go.mod h1:9Cogkyv9gcT8HREpzi3TiqBxCqDzo8awa92zSDFcofU=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.4 h1:IzV6qqkfwbItOS/sg/aDfPDsjPP8twrCOE2R93hxMlQ=
github.com/btcsuite/btcd v0.23.4/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
github.com/btcsuite/btcd v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo=
github.com/btcsuite/btcd v0.24.0/go.mod h1:K4IDc1593s8jKXIF7yS7yCTSxrknB9z0STzc2j6XgE4=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0 h1:MO4klnGY+EWJdoWF12Wkuf4AWDBPMpZNeN/jRLrklUU=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@ -320,6 +324,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U=
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw=
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
@ -1088,8 +1093,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

Loading…
Cancel
Save