2
0
mirror of https://github.com/guggero/chantools synced 2024-11-11 01:10:42 +00:00

Add rescuefunding and signrescuefunding commands

This commit is contained in:
Oliver Gugger 2020-08-26 23:14:38 +02:00
parent a95f475c6a
commit 4a633da99e
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
10 changed files with 541 additions and 138 deletions

View File

@ -16,7 +16,9 @@
+ [genimportscript](#genimportscript)
+ [forceclose](#forceclose)
+ [rescueclosed](#rescueclosed)
+ [rescuefunding](#rescuefunding)
+ [showrootkey](#showrootkey)
+ [signrescuefunding](#signrescuefunding)
+ [summary](#summary)
+ [sweeptimelock](#sweeptimelock)
+ [vanitygen](#vanitygen)
@ -207,21 +209,23 @@ Help Options:
-h, --help Show this help message
Available commands:
chanbackup Create a channel.backup file from a channel database.
compactdb Open a source channel.db database file in safe/read-only mode and copy it to a fresh database, compacting it in the process.
derivekey Derive a key with a specific derivation path from the BIP32 HD root key.
dumpbackup Dump the content of a channel.backup file.
dumpchannels Dump all channel information from lnd's channel database.
filterbackup Filter an lnd channel.backup file and remove certain channels.
fixoldbackup Fixes an old channel.backup file that is affected by the lnd issue #3881 (unable to derive shachain root key).
forceclose Force-close the last state that is in the channel.db provided.
genimportscript Generate a script containing the on-chain keys of an lnd wallet that can be imported into other software like bitcoind.
rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels.
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
summary Compile a summary about the current state of channels.
sweeptimelock Sweep the force-closed state after the time lock has expired.
vanitygen Generate a seed with a custom lnd node identity public key that starts with the given p
walletinfo Shows relevant information about an lnd wallet.db file and optionally extracts the BIP32 HD root key.
chanbackup Create a channel.backup file from a channel database.
compactdb Open a source channel.db database file in safe/read-only mode and copy it to a fresh database, compacting it in the process.
derivekey Derive a key with a specific derivation path from the BIP32 HD root key.
dumpbackup Dump the content of a channel.backup file.
dumpchannels Dump all channel information from lnd's channel database.
filterbackup Filter an lnd channel.backup file and remove certain channels.
fixoldbackup Fixes an old channel.backup file that is affected by the lnd issue #3881 (unable to derive shachain root key).
forceclose Force-close the last state that is in the channel.db provided.
genimportscript Generate a script containing the on-chain keys of an lnd wallet that can be imported into other software like bitcoind.
rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels.
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.
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
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.
vanitygen Generate a seed with a custom lnd node identity public key that starts with the given prefix.
walletinfo Shows relevant information about an lnd wallet.db file and optionally extracts the BIP32 HD root key.
```
## Commands
@ -479,6 +483,43 @@ chantools --fromsummary results/summary-xxxx-yyyy.json \
--rootkey xprvxxxxxxxxxx
```
### rescuefunding
```text
Usage:
chantools [OPTIONS] rescuefunding [rescuefunding-OPTIONS]
[rescuefunding command options]
--rootkey= BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed.
--channeldb= The lnd channel.db file to rescue a channel from. Must contain the pending channel specified with --channelpoint.
--channelpoint= The funding transaction outpoint of the channel to rescue (<txid>:<txindex>) as it is recorded in the DB.
--confirmedchannelpoint= The channel outpoint that got confirmed on chain (<txid>:<txindex>). Normally this is the same as the --channelpoint so it will be set to that value if this is left empty.
--sweepaddr= The address to sweep the rescued funds to.
--satperbyte= The fee rate to use in satoshis/vByte.
```
This is part 1 of a two phase process to rescue a channel funding output that
was created on chain by accident but never resulted in a proper channel and no
commitment transactions exist to spend the funds locked in the 2-of-2 multisig.
**You need the cooperation of the channel partner (remote node) for this to
work**! They need to run the second command of this process:
[`signrescuefunding`](#signrescuefunding)
Example command (run against the channel DB of the initiator node):
```bash
chantools rescuefunding \
--channeldb ~/.lnd/data/graph/mainnet/channel.db \
--channelpoint xxxxxxx:xx \
--sweepaddr bc1qxxxxxxxxx \
--satperbyte 10 \
--rootkey xprvxxxxxxxxxx
```
If successful, this will create a PSBT that then has to be sent to the channel
partner (remote node operator).
### showrootkey
This command converts the 24 word `lnd` aezeed phrase and password to the BIP32
@ -491,6 +532,32 @@ Example command:
chantools showrootkey
```
### signrescuefunding
```text
Usage:
chantools [OPTIONS] signrescuefunding [signrescuefunding-OPTIONS]
[signrescuefunding command options]
--rootkey= BIP32 HD root (m/) key to derive the key for our part of the signature from.
--psbt= The Partially Signed Bitcoin Transaction that was provided by the initiator of the channel to rescue.
```
This is part 2 of a two phase process to rescue a channel funding output that
was created on chain by accident but never resulted in a proper channel and no
commitment transactions exist to spend the funds locked in the 2-of-2 multisig.
Example command (run by the non-initiator of the channel):
```bash
chantools signrescuefunding \
--psbt <the_base64_encoded_psbt_from_step_1> \
--rootkey xprvxxxxxxxxxx
```
If successful, this will create a final on-chain transaction that can be
broadcast by any Bitcoin node.
### summary
```text

View File

@ -23,7 +23,7 @@ import (
const (
defaultAPIURL = "https://blockstream.info/api"
version = "0.3.0"
version = "0.4.0"
)
var (
@ -137,12 +137,20 @@ func runCommandParser() error {
"public key that starts with the given prefix.", "",
&vanityGenCommand{},
)
// TODO: uncomment when command is fully implemented.
//_, _ = parser.AddCommand(
// "rescuefunding", "Rescue funds locked in a funding multisig "+
// "output that never resulted in a proper channel.", "",
// &rescueFundingCommand{},
//)
_, _ = parser.AddCommand(
"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.", "",
&rescueFundingCommand{},
)
_, _ = parser.AddCommand(
"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.", "",
&signRescueFundingCommand{},
)
_, err := parser.Parse()
return err

View File

@ -3,25 +3,43 @@ package main
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcutil/psbt"
"github.com/guggero/chantools/lnd"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"path"
)
const (
MaxChannelLookup = 5000
// MultiSigWitnessSize 222 bytes
// - NumberOfWitnessElements: 1 byte
// - NilLength: 1 byte
// - sigAliceLength: 1 byte
// - sigAlice: 73 bytes
// - sigBobLength: 1 byte
// - sigBob: 73 bytes
// - WitnessScriptLength: 1 byte
// - WitnessScript (MultiSig)
MultiSigWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + input.MultiSigSize
)
var (
PsbtKeyTypeOutputMissingSigPubkey = []byte{0xcc}
)
type rescueFundingCommand struct {
RootKey string `long:"rootkey" description:"BIP32 HD root (m/) key to derive the key for our node from."`
OtherNodePub string `long:"othernodepub" description:"The extended public key (xpub) of the other node's multisig branch (m/1017'/<coin_type>'/0'/0)."`
FundingAddr string `long:"fundingaddr" description:"The bech32 script address of the funding output where the coins to be spent are locked in."`
FundingOutpoint string `long:"fundingoutpoint" description:"The funding transaction outpoint (<txid>:<txindex>)."`
FundingAmount int64 `long:"fundingamount" description:"The exact amount in satoshis that is locked in the funding output."`
SweepAddr string `long:"sweepaddr" description:"The address to sweep the rescued funds to."`
SatPerByte int64 `long:"satperbyte" description:"The fee rate to use in satoshis/vByte."`
RootKey string `long:"rootkey" description:"BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed."`
ChannelDB string `long:"channeldb" description:"The lnd channel.db file to rescue a channel from. Must contain the pending channel specified with --channelpoint."`
ChannelPoint string `long:"channelpoint" description:"The funding transaction outpoint of the channel to rescue (<txid>:<txindex>) as it is recorded in the DB."`
ConfirmedOutPoint string `long:"confirmedchannelpoint" description:"The channel outpoint that got confirmed on chain (<txid>:<txindex>). Normally this is the same as the --channelpoint so it will be set to that value if this is left empty."`
SweepAddr string `long:"sweepaddr" description:"The address to sweep the rescued funds to."`
SatPerByte int64 `long:"satperbyte" description:"The fee rate to use in satoshis/vByte."`
}
func (c *rescueFundingCommand) Execute(_ []string) error {
@ -29,7 +47,7 @@ func (c *rescueFundingCommand) Execute(_ []string) error {
var (
extendedKey *hdkeychain.ExtendedKey
otherPub *hdkeychain.ExtendedKey
chainOp *wire.OutPoint
err error
)
@ -39,107 +57,158 @@ func (c *rescueFundingCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default:
extendedKey, _, err = lnd.ReadAezeedFromTerminal(chainParams)
extendedKey, _, err = lnd.ReadAezeedFromTerminal(
chainParams,
)
}
if err != nil {
return fmt.Errorf("error reading root key: %v", err)
}
// Read other node's xpub.
otherPub, err = hdkeychain.NewKeyFromString(c.OtherNodePub)
if err != nil {
return fmt.Errorf("error parsing other node's xpub: %v", err)
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
// Decode target funding address.
hash, isScript, err := lnd.DecodeAddressHash(c.FundingAddr, chainParams)
if err != nil {
return fmt.Errorf("error decoding funding address: %v", err)
// Check that we have a channel DB.
if c.ChannelDB == "" {
return fmt.Errorf("channel DB is required")
}
if !isScript {
return fmt.Errorf("funding address must be a P2WSH address")
}
return rescueFunding(extendedKey, otherPub, hash)
}
func rescueFunding(localNodeKey *hdkeychain.ExtendedKey,
otherNodekey *hdkeychain.ExtendedKey, scriptHash []byte) error {
// First, we need to derive the correct branch from the local root key.
localMultisig, err := lnd.DeriveChildren(localNodeKey, []uint32{
lnd.HardenedKeyStart + uint32(keychain.BIP0043Purpose),
lnd.HardenedKeyStart + chainParams.HDCoinType,
lnd.HardenedKeyStart + uint32(keychain.KeyFamilyMultiSig),
0,
})
if err != nil {
return fmt.Errorf("could not derive local multisig key: %v",
err)
}
log.Infof("Looking for matching multisig keys, this will take a while")
localIndex, otherIndex, script, err := findMatchingIndices(
localMultisig, otherNodekey, scriptHash,
db, err := channeldb.Open(
path.Dir(c.ChannelDB), path.Base(c.ChannelDB),
channeldb.OptionSetSyncFreelist(true),
channeldb.OptionReadOnly(true),
)
if err != nil {
return fmt.Errorf("could not derive keys: %v", err)
return fmt.Errorf("error opening rescue DB: %v", err)
}
log.Infof("Found local key with index %d and other key with index %d "+
"for witness script %x", localIndex, otherIndex, script)
// Parse channel point of channel to rescue as known to the DB.
dbOp, err := lnd.ParseOutpoint(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error parsing channel point: %v", err)
}
// Parse channel point of channel to rescue as confirmed on chain (if
// different).
if len(c.ConfirmedOutPoint) == 0 {
chainOp = dbOp
} else {
chainOp, err = lnd.ParseOutpoint(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error parsing confirmed channel "+
"point: %v", err)
}
}
// Make sure the sweep addr is a P2WKH address so we can do accurate
// fee estimation.
sweepScript, err := lnd.GetP2WPKHScript(c.SweepAddr, chainParams)
if err != nil {
return fmt.Errorf("error parsing sweep addr: %v", err)
}
if c.SatPerByte < 0 {
return fmt.Errorf("satperbyte must be greater than 0")
}
return rescueFunding(
db, signer, dbOp, chainOp, sweepScript,
btcutil.Amount(c.SatPerByte),
)
}
func rescueFunding(db *channeldb.DB, signer *lnd.Signer, dbFundingPoint,
chainPoint *wire.OutPoint, sweepPKScript []byte,
feeRate btcutil.Amount) error {
// First of all make sure the channel can be found in the DB.
pendingChan, err := db.FetchChannel(*dbFundingPoint)
if err != nil {
return fmt.Errorf("error loading pending channel %s from DB: "+
"%v", dbFundingPoint, err)
}
// Prepare the wire part of the PSBT.
txIn := &wire.TxIn{
PreviousOutPoint: *chainPoint,
Sequence: 0,
}
txOut := &wire.TxOut{
PkScript: sweepPKScript,
}
// Locate the output in the funding TX.
utxo := pendingChan.FundingTxn.TxOut[dbFundingPoint.Index]
// We should also be able to create the funding script from the two
// multisig keys.
localKey := pendingChan.LocalChanCfg.MultiSigKey.PubKey
remoteKey := pendingChan.RemoteChanCfg.MultiSigKey.PubKey
witnessScript, fundingTxOut, err := input.GenFundingPkScript(
localKey.SerializeCompressed(), remoteKey.SerializeCompressed(),
utxo.Value,
)
if err != nil {
return fmt.Errorf("could not derive funding script: %v", err)
}
// Some last sanity check that we're working with the correct data.
if !bytes.Equal(fundingTxOut.PkScript, utxo.PkScript) {
return fmt.Errorf("funding output script does not match UTXO")
}
// Now the rest of the known data for the PSBT.
pIn := psbt.PInput{
WitnessUtxo: utxo,
WitnessScript: witnessScript,
Unknowns: []*psbt.Unknown{{
// We add the public key the other party needs to sign
// with as a proprietary field so we can easily read it
// out with the signrescuefunding command.
Key: PsbtKeyTypeOutputMissingSigPubkey,
Value: remoteKey.SerializeCompressed(),
}},
}
// Estimate the transaction weight so we can do the fee estimation.
var estimator input.TxWeightEstimator
estimator.AddWitnessInput(MultiSigWitnessSize)
estimator.AddP2WKHOutput()
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))
txOut.Value = utxo.Value - int64(totalFee)
// Let's now create the PSBT as we have everything we need so far.
wireTx := &wire.MsgTx{
Version: 2,
TxIn: []*wire.TxIn{txIn},
TxOut: []*wire.TxOut{txOut},
}
packet, err := psbt.NewFromUnsignedTx(wireTx)
if err != nil {
return fmt.Errorf("error creating PSBT: %v", err)
}
packet.Inputs[0] = pIn
// Now we add our partial signature.
err = signer.AddPartialSignature(
packet, pendingChan.LocalChanCfg.MultiSigKey, utxo,
witnessScript, 0,
)
if err != nil {
return fmt.Errorf("error adding partial signature: %v", err)
}
// We're done, we can now output the finished PSBT.
base64, err := packet.B64Encode()
if err != nil {
return fmt.Errorf("error encoding PSBT: %v", err)
}
fmt.Printf("Partially signed transaction created. Send this to the "+
"other peer \nand ask them to run the 'chantools "+
"signrescuefunding' command: \n\n%s\n\n", base64)
// TODO(guggero):
// * craft PSBT with input, sweep output and partial signature
// * do fee estimation based on full amount
// * create `signpsbt` command for the other node operator
return nil
}
func findMatchingIndices(localNodeKey *hdkeychain.ExtendedKey,
otherNodekey *hdkeychain.ExtendedKey, scriptHash []byte) (uint32,
uint32, []byte, error) {
// Loop through both the local and the remote indices of the branches up
// to MaxChannelLookup.
for local := uint32(0); local < MaxChannelLookup; local++ {
for other := uint32(0); other < MaxChannelLookup; other++ {
localKey, err := localNodeKey.Child(local)
if err != nil {
return 0, 0, nil, fmt.Errorf("error "+
"deriving local key: %v", err)
}
localPub, err := localKey.ECPubKey()
if err != nil {
return 0, 0, nil, fmt.Errorf("error "+
"deriving local pubkey: %v", err)
}
otherKey, err := otherNodekey.Child(other)
if err != nil {
return 0, 0, nil, fmt.Errorf("error "+
"deriving other key: %v", err)
}
otherPub, err := otherKey.ECPubKey()
if err != nil {
return 0, 0, nil, fmt.Errorf("error "+
"deriving other pubkey: %v", err)
}
script, out, err := input.GenFundingPkScript(
localPub.SerializeCompressed(),
otherPub.SerializeCompressed(), 123,
)
if err != nil {
return 0, 0, nil, fmt.Errorf("error "+
"generating funding script: %v", err)
}
if bytes.Contains(out.PkScript, scriptHash) {
return local, other, script, nil
}
}
if local > 0 && local%100 == 0 {
log.Infof("Checked %d of %d local keys", local,
MaxChannelLookup)
}
}
return 0, 0, nil, fmt.Errorf("no matching pubkeys found")
}

View File

@ -0,0 +1,168 @@
package main
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil/psbt"
"github.com/lightningnetwork/lnd/keychain"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/guggero/chantools/lnd"
)
type signRescueFundingCommand struct {
RootKey string `long:"rootkey" description:"BIP32 HD root (m/) key to derive the key for our part of the signature from."`
Psbt string `long:"psbt" description:"The Partially Signed Bitcoin Transaction that was provided by the initiator of the channel to rescue."`
}
func (c *signRescueFundingCommand) Execute(_ []string) error {
setupChainParams(cfg)
var (
extendedKey *hdkeychain.ExtendedKey
err error
)
// Check that root key is valid or fall back to console input.
switch {
case c.RootKey != "":
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default:
extendedKey, _, err = lnd.ReadAezeedFromTerminal(chainParams)
}
if err != nil {
return fmt.Errorf("error reading root key: %v", err)
}
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
// Decode the PSBT.
packet, err := psbt.NewFromRawBytes(
bytes.NewReader([]byte(c.Psbt)), true,
)
if err != nil {
return fmt.Errorf("error decoding PSBT: %v", err)
}
return signRescueFunding(extendedKey, packet, signer)
}
func signRescueFunding(rootKey *hdkeychain.ExtendedKey,
packet *psbt.Packet, signer *lnd.Signer) error {
// First, we need to derive the correct branch from the local root key.
localMultisig, err := lnd.DeriveChildren(rootKey, []uint32{
lnd.HardenedKeyStart + uint32(keychain.BIP0043Purpose),
lnd.HardenedKeyStart + chainParams.HDCoinType,
lnd.HardenedKeyStart + uint32(keychain.KeyFamilyMultiSig),
0,
})
if err != nil {
return fmt.Errorf("could not derive local multisig key: %v",
err)
}
// Now let's check that the packet has the expected proprietary key with
// our pubkey that we need to sign with.
if len(packet.Inputs) != 1 {
return fmt.Errorf("invalid PSBT, expected 1 input, got %d",
len(packet.Inputs))
}
if len(packet.Inputs[0].Unknowns) != 1 {
return fmt.Errorf("invalid PSBT, expected 1 unknown in input, "+
"got %d", len(packet.Inputs[0].Unknowns))
}
unknown := packet.Inputs[0].Unknowns[0]
if !bytes.Equal(unknown.Key, PsbtKeyTypeOutputMissingSigPubkey) {
return fmt.Errorf("invalid PSBT, unknown has invalid key %x, "+
"expected %x", unknown.Key,
PsbtKeyTypeOutputMissingSigPubkey)
}
targetKey, err := btcec.ParsePubKey(unknown.Value, btcec.S256())
if err != nil {
return fmt.Errorf("invalid PSBT, proprietary key has invalid "+
"pubkey: %v", err)
}
// Now we can look up the local key and check the PSBT further, then
// add our signature.
localKeyDesc, err := findLocalMultisigKey(localMultisig, targetKey)
if err != nil {
return fmt.Errorf("could not find local multisig key: %v", err)
}
if len(packet.Inputs[0].WitnessScript) == 0 {
return fmt.Errorf("invalid PSBT, missing witness script")
}
witnessScript := packet.Inputs[0].WitnessScript
if packet.Inputs[0].WitnessUtxo == nil {
return fmt.Errorf("invalid PSBT, witness UTXO missing")
}
utxo := packet.Inputs[0].WitnessUtxo
err = signer.AddPartialSignature(
packet, *localKeyDesc, utxo, witnessScript, 0,
)
if err != nil {
return fmt.Errorf("error adding partial signature: %v", err)
}
// We're almost done. Now we just need to make sure we can finalize and
// extract the final TX.
err = psbt.MaybeFinalizeAll(packet)
if err != nil {
return fmt.Errorf("error finalizing PSBT: %v", err)
}
finalTx, err := psbt.Extract(packet)
if err != nil {
return fmt.Errorf("unable to extract final TX: %v", err)
}
var buf bytes.Buffer
err = finalTx.Serialize(&buf)
if err != nil {
return fmt.Errorf("unable to serialize final TX: %v", err)
}
fmt.Printf("Success, we counter signed the PSBT and extracted the "+
"final\ntransaction. Please publish this using any bitcoin "+
"node:\n\n%x\n\n", buf.Bytes())
return nil
}
func findLocalMultisigKey(multisigBranch *hdkeychain.ExtendedKey,
targetPubkey *btcec.PublicKey) (*keychain.KeyDescriptor, error) {
// Loop through the local multisig keys to find the target key.
for index := uint32(0); index < MaxChannelLookup; index++ {
currentKey, err := multisigBranch.Child(index)
if err != nil {
return nil, fmt.Errorf("error deriving child key: %v",
err)
}
currentPubkey, err := currentKey.ECPubKey()
if err != nil {
return nil, fmt.Errorf("error deriving public key: %v",
err)
}
if !targetPubkey.IsEqual(currentPubkey) {
continue
}
return &keychain.KeyDescriptor{
PubKey: currentPubkey,
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyMultiSig,
Index: index,
},
}, nil
}
return nil, fmt.Errorf("no matching pubkeys found")
}

View File

@ -182,7 +182,7 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
}
// Add our sweep destination output.
sweepScript, err := getP2WPKHScript(sweepAddr)
sweepScript, err := lnd.GetP2WPKHScript(sweepAddr, chainParams)
if err != nil {
return err
}
@ -256,20 +256,6 @@ func pubKeyFromHex(pubKeyHex string) (*btcec.PublicKey, error) {
)
}
func getP2WPKHScript(addr string) ([]byte, error) {
targetPubKeyHash, _, err := lnd.DecodeAddressHash(
addr, chainParams,
)
if err != nil {
return nil, err
}
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_0)
builder.AddData(targetPubKeyHash)
return builder.Script()
}
func bruteForceDelay(delayPubkey, revocationPubkey *btcec.PublicKey,
targetScriptHex string, maxCsvTimeout int) (int32, []byte, []byte,
error) {

7
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/btcsuite/btcd v0.20.1-beta
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422
github.com/btcsuite/btcutil/psbt v0.0.0-00010101000000-000000000000
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623
github.com/btcsuite/btcwallet/walletdb v1.2.0
github.com/coreos/bbolt v1.3.3
@ -15,7 +16,7 @@ require (
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191224233846-f289a39c1a00
github.com/ltcsuite/ltcd v0.0.0-20191228044241-92166e412499 // indirect
github.com/miekg/dns v1.1.26 // indirect
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 // indirect
gopkg.in/yaml.v2 v2.2.3 // indirect
@ -23,4 +24,8 @@ require (
replace github.com/lightningnetwork/lnd => github.com/guggero/lnd v0.9.0-beta-rc4.0.20200826102054-8c9171307182
replace github.com/btcsuite/btcutil => github.com/btcsuite/btcutil v1.0.2
replace github.com/btcsuite/btcutil/psbt => github.com/btcsuite/btcutil/psbt v1.0.2
go 1.13

6
go.sum
View File

@ -34,6 +34,10 @@ github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422 h1:EqnrgSSg0SFWRlEZLExgjtuUR/IPnuQ6qw6nwRda4Uk=
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/btcutil/psbt v1.0.2 h1:gCVY3KxdoEVU7Q6TjusPO+GANIwVgr9yTLqM+a6CZr8=
github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623 h1:ZuJRjucNsTmlrbZncsqzD0z3EaXrOobCx2I4lc12R4g=
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623/go.mod h1:1O1uRHMPXHdwA4/od8nqYqrgclVKp+wtfXUAqHmeRvE=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
@ -204,6 +208,8 @@ golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

View File

@ -1,10 +1,14 @@
package lnd
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"strconv"
"strings"
)
type LightningChannel struct {
@ -75,3 +79,29 @@ func (lc *LightningChannel) SignedCommitTx() (*wire.MsgTx, error) {
return commitTx, nil
}
// ParseOutpoint parses a transaction outpoint in the format <txid>:<idx> into
// the wire format.
func ParseOutpoint(s string) (*wire.OutPoint, error) {
split := strings.Split(s, ":")
if len(split) != 2 {
return nil, fmt.Errorf("expecting channel point to be in " +
"format of: txid:index")
}
index, err := strconv.ParseInt(split[1], 10, 32)
if err != nil {
return nil, fmt.Errorf("unable to decode output index: %v",
err)
}
txid, err := chainhash.NewHashFromStr(split[0])
if err != nil {
return nil, fmt.Errorf("unable to parse hex string: %v", err)
}
return &wire.OutPoint{
Hash: *txid,
Index: uint32(index),
}, nil
}

View File

@ -2,6 +2,7 @@ package lnd
import (
"fmt"
"github.com/btcsuite/btcd/txscript"
"strconv"
"strings"
@ -169,6 +170,30 @@ func DecodeAddressHash(addr string, chainParams *chaincfg.Params) ([]byte, bool,
return targetHash, isScriptHash, nil
}
// GetP2WPKHScript creates a P2WKH output script from an address. If the address
// is not a P2WKH address, an error is returned.
func GetP2WPKHScript(addr string, chainParams *chaincfg.Params) ([]byte,
error) {
targetPubKeyHash, isScriptHash, err := DecodeAddressHash(
addr, chainParams,
)
if err != nil {
return nil, err
}
if isScriptHash {
return nil, fmt.Errorf("address %s is not a P2WKH address",
addr)
}
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_0)
builder.AddData(targetPubKeyHash)
return builder.Script()
}
type HDKeyRing struct {
ExtendedKey *hdkeychain.ExtendedKey
ChainParams *chaincfg.Params

View File

@ -2,12 +2,12 @@ package lnd
import (
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcutil/psbt"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
@ -64,6 +64,45 @@ func (s *Signer) FetchPrivKey(descriptor *keychain.KeyDescriptor) (
return key.ECPrivKey()
}
func (s *Signer) AddPartialSignature(packet *psbt.Packet,
keyDesc keychain.KeyDescriptor, utxo *wire.TxOut, witnessScript []byte,
inputIndex int) error {
// Now we add our partial signature.
signDesc := &input.SignDescriptor{
KeyDesc: keyDesc,
WitnessScript: witnessScript,
Output: utxo,
InputIndex: inputIndex,
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(packet.UnsignedTx),
}
ourSigRaw, err := s.SignOutputRaw(packet.UnsignedTx, signDesc)
if err != nil {
return fmt.Errorf("error signing with our key: %v", err)
}
ourSig := append(ourSigRaw, 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: %v", err)
}
status, err := updater.Sign(
0, ourSig, keyDesc.PubKey.SerializeCompressed(), nil,
witnessScript,
)
if err != nil {
return fmt.Errorf("error adding signature to PSBT: %v", 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.