2
0
mirror of https://github.com/guggero/chantools synced 2024-11-07 03:20:43 +00:00

Add sweeptimelockmanual command

This commit is contained in:
Oliver Gugger 2020-08-30 14:00:19 +02:00
parent 18ef40fe5a
commit 4f343dd8f1
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
6 changed files with 486 additions and 62 deletions

View File

@ -21,6 +21,7 @@
+ [signrescuefunding](#signrescuefunding)
+ [summary](#summary)
+ [sweeptimelock](#sweeptimelock)
+ [sweeptimelockmanual](#sweeptimelockmanual)
+ [vanitygen](#vanitygen)
+ [walletinfo](#walletinfo)
@ -209,23 +210,24 @@ 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.
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.
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.
sweeptimelockmanual Sweep the force-closed state of a single channel manually if only a channel backup file is available
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
@ -610,6 +612,47 @@ chantools --fromsummary results/forceclose-xxxx-yyyy.json \
--sweepaddr bc1q.....
```
### sweeptimelockmanual
```text
Usage:
chantools [OPTIONS] sweeptimelockmanual [sweeptimelockmanual-OPTIONS]
[sweeptimelockmanual command options]
--rootkey= BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed.
--publish Should the sweep TX be published to the chain API?
--sweepaddr= The address the funds should be sweeped to.
--maxcsvlimit= Maximum CSV limit to use. (default 2000)
--feerate= The fee rate to use for the sweep transaction in sat/vByte. (default 2 sat/vByte)
--timelockaddr= The address of the time locked commitment output where the funds are stuck in.
--remoterevbasepoint= The remote's revocation base point, can be found in a channel.backup file.
```
Sweep the locally force closed state of a single channel manually if only a
channel backup file is available. This can only be used if a channel is force
closed from the local node but then that node's state is lost and only the
`channel.backup` file is available.
To get the value for `--remoterevbasepoint` you must use the
[`dumpbackup`](#dumpbackup) command, then look up the value for
`RemoteChanCfg -> RevocationBasePoint -> PubKey`.
To get the value for `--timelockaddr` you must look up the channel's funding
output on chain, then follow it to the force close output. The time locked
address is always the one that's longer (because it's P2WSH and not P2PKH).
Example command:
```bash
chantools sweeptimelockmanual \
--rootkey xprvxxxxxxxxxx \
--sweepaddr bc1q..... \
--timelockaddr bc1q............ \
--remoterevbasepoint 03xxxxxxx \
--feerate 10 \
--publish
```
### vanitygen
```

View File

@ -18,6 +18,7 @@ type ExplorerAPI struct {
}
type TX struct {
TXID string `json:"txid"`
Vin []*Vin `json:"vin"`
Vout []*Vout `json:"vout"`
}
@ -71,6 +72,23 @@ func (a *ExplorerAPI) Transaction(txid string) (*TX, error) {
return tx, nil
}
func (a *ExplorerAPI) Outpoint(addr string) (*TX, int, error) {
var txs []*TX
err := fetchJSON(fmt.Sprintf("%s/address/%s/txs", a.BaseURL, addr), &txs)
if err != nil {
return nil, 0, err
}
for _, tx := range txs {
for idx, vout := range tx.Vout {
if vout.ScriptPubkeyAddr == addr {
return tx, idx, nil
}
}
}
return nil, 0, fmt.Errorf("no tx found")
}
func (a *ExplorerAPI) PublishTx(rawTxHex string) (string, error) {
url := fmt.Sprintf("%s/tx", a.BaseURL)
resp, err := http.Post(url, "text/plain", strings.NewReader(rawTxHex))

View File

@ -23,7 +23,7 @@ import (
const (
defaultAPIURL = "https://blockstream.info/api"
version = "0.4.1"
version = "0.5.0"
)
var (
@ -86,6 +86,11 @@ func runCommandParser() error {
"sweeptimelock", "Sweep the force-closed state after the time "+
"lock has expired.", "", &sweepTimeLockCommand{},
)
_, _ = parser.AddCommand(
"sweeptimelockmanual", "Sweep the force-closed state of a "+
"single channel manually if only a channel backup "+
"file is available", "", &sweepTimeLockManualCommand{},
)
_, _ = parser.AddCommand(
"dumpchannels", "Dump all channel information from lnd's "+
"channel database.", "", &dumpChannelsCommand{},

View File

@ -4,8 +4,9 @@ import (
"bytes"
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
@ -17,14 +18,16 @@ import (
)
const (
feeSatPerByte = 2
defaultFeeSatPerVByte = 2
defaultCsvLimit = 2000
)
type sweepTimeLockCommand struct {
RootKey string `long:"rootkey" description:"BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed."`
Publish bool `long:"publish" description:"Should the sweep TX be published to the chain API?"`
SweepAddr string `long:"sweepaddr" description:"The address the funds should be sweeped to"`
SweepAddr string `long:"sweepaddr" description:"The address the funds should be sweeped to."`
MaxCsvLimit int `long:"maxcsvlimit" description:"Maximum CSV limit to use. (default 2000)"`
FeeRate uint32 `long:"feerate" description:"The fee rate to use for the sweep transaction in sat/vByte. (default 2 sat/vByte)"`
}
func (c *sweepTimeLockCommand) Execute(_ []string) error {
@ -58,19 +61,22 @@ func (c *sweepTimeLockCommand) Execute(_ []string) error {
return err
}
// Set default value
// Set default values.
if c.MaxCsvLimit == 0 {
c.MaxCsvLimit = 2000
c.MaxCsvLimit = defaultCsvLimit
}
if c.FeeRate == 0 {
c.FeeRate = defaultFeeSatPerVByte
}
return sweepTimeLock(
extendedKey, cfg.APIURL, entries, c.SweepAddr, c.MaxCsvLimit,
c.Publish,
c.Publish, c.FeeRate,
)
}
func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
entries []*dataformat.SummaryEntry, sweepAddr string, maxCsvTimeout int,
publish bool) error {
publish bool, feeRate uint32) error {
// Create signer and transaction template.
signer := &lnd.Signer{
@ -82,6 +88,7 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
sweepTx := wire.NewMsgTx(2)
totalOutputValue := int64(0)
signDescs := make([]*input.SignDescriptor, 0)
var estimator input.TxWeightEstimator
for _, entry := range entries {
// Skip entries that can't be swept.
@ -135,12 +142,18 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
}
delayBase := delayPrivKey.PubKey()
lockScript, err := hex.DecodeString(fc.Outs[txindex].Script)
if err != nil {
return fmt.Errorf("error parsing target script: %v",
err)
}
// We can't rely on the CSV delay of the channel DB to be
// correct. But it doesn't cost us a lot to just brute force it.
csvTimeout, script, scriptHash, err := bruteForceDelay(
input.TweakPubKey(delayBase, commitPoint),
input.DeriveRevocationPubkey(revBase, commitPoint),
fc.Outs[txindex].Script, maxCsvTimeout,
lockScript, maxCsvTimeout,
)
if err != nil {
log.Errorf("Could not create matching script for %s "+
@ -179,6 +192,9 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
}
totalOutputValue += int64(fc.Outs[txindex].Value)
signDescs = append(signDescs, signDesc)
// Account for the input weight.
estimator.AddWitnessInput(input.ToLocalTimeoutWitnessSize)
}
// Add our sweep destination output.
@ -186,33 +202,23 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
if err != nil {
return err
}
estimator.AddP2WKHOutput()
// Calculate the fee based on the given fee rate and our weight
// estimation.
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))
log.Infof("Fee %d sats of %d total amount (estimated weight %d)",
totalFee, totalOutputValue, estimator.Weight())
sweepTx.TxOut = []*wire.TxOut{{
Value: totalOutputValue,
Value: totalOutputValue - int64(totalFee),
PkScript: sweepScript,
}}
// Very naive fee estimation algorithm: Sign a first time as if we would
// send the whole amount with zero fee, just to estimate how big the
// transaction would get in bytes. Then adjust the fee and sign again.
// Sign the transaction now.
sigHashes := txscript.NewTxSigHashes(sweepTx)
for idx, desc := range signDescs {
desc.SigHashes = sigHashes
desc.InputIndex = idx
witness, err := input.CommitSpendTimeout(signer, desc, sweepTx)
if err != nil {
return err
}
sweepTx.TxIn[idx].Witness = witness
}
// Calculate a fee. This won't be very accurate so the feeSatPerByte
// should at least be 2 to not risk falling below the 1 sat/byte limit.
size := sweepTx.SerializeSize()
fee := int64(size * feeSatPerByte)
sweepTx.TxOut[0].Value = totalOutputValue - fee
// Sign again after output fixing.
sigHashes = txscript.NewTxSigHashes(sweepTx)
for idx, desc := range signDescs {
desc.SigHashes = sigHashes
witness, err := input.CommitSpendTimeout(signer, desc, sweepTx)
@ -227,8 +233,6 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
if err != nil {
return err
}
log.Infof("Fee %d sats of %d total amount (for size %d)",
fee, totalOutputValue, sweepTx.SerializeSize())
// Publish TX.
if publish {
@ -251,23 +255,16 @@ func pubKeyFromHex(pubKeyHex string) (*btcec.PublicKey, error) {
if err != nil {
return nil, fmt.Errorf("error hex decoding pub key: %v", err)
}
return btcec.ParsePubKey(
pointBytes, btcec.S256(),
)
return btcec.ParsePubKey(pointBytes, btcec.S256())
}
func bruteForceDelay(delayPubkey, revocationPubkey *btcec.PublicKey,
targetScriptHex string, maxCsvTimeout int) (int32, []byte, []byte,
targetScript []byte, maxCsvTimeout int) (int32, []byte, []byte,
error) {
targetScript, err := hex.DecodeString(targetScriptHex)
if err != nil {
return 0, nil, nil, fmt.Errorf("error parsing target script: "+
"%v", err)
}
if len(targetScript) != 34 {
return 0, nil, nil, fmt.Errorf("invalid target script: %s",
targetScriptHex)
targetScript)
}
for i := 0; i <= maxCsvTimeout; i++ {
s, err := input.CommitScriptToSelf(
@ -287,5 +284,5 @@ func bruteForceDelay(delayPubkey, revocationPubkey *btcec.PublicKey,
}
}
return 0, nil, nil, fmt.Errorf("csv timeout not found for target "+
"script %s", targetScriptHex)
"script %s", targetScript)
}

View File

@ -0,0 +1,302 @@
package main
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/guggero/chantools/btc"
"github.com/guggero/chantools/lnd"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/shachain"
)
const (
keyBasePath = "m/1017'/%d'"
maxKeys = 500
maxPoints = 500
)
type sweepTimeLockManualCommand struct {
RootKey string `long:"rootkey" description:"BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed."`
Publish bool `long:"publish" description:"Should the sweep TX be published to the chain API?"`
SweepAddr string `long:"sweepaddr" description:"The address the funds should be sweeped to."`
MaxCsvLimit int `long:"maxcsvlimit" description:"Maximum CSV limit to use. (default 2000)"`
FeeRate uint32 `long:"feerate" description:"The fee rate to use for the sweep transaction in sat/vByte. (default 2 sat/vByte)"`
TimeLockAddr string `long:"timelockaddr" description:"The address of the time locked commitment output where the funds are stuck in."`
RemoteRevocationBasePoint string `long:"remoterevbasepoint" description:"The remote's revocation base point, can be found in a channel.backup file."`
}
func (c *sweepTimeLockManualCommand) 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)
}
// Make sure the sweep and time lock addrs are set.
if c.SweepAddr == "" {
return fmt.Errorf("sweep addr is required")
}
if c.TimeLockAddr == "" {
return fmt.Errorf("time lock addr is required")
}
// The remote revocation base point must also be set and a valid EC
// point.
remoteRevPoint, err := pubKeyFromHex(c.RemoteRevocationBasePoint)
if err != nil {
return fmt.Errorf("invalid remote revocation base point: %v",
err)
}
// Set default values.
if c.MaxCsvLimit == 0 {
c.MaxCsvLimit = defaultCsvLimit
}
if c.FeeRate == 0 {
c.FeeRate = defaultFeeSatPerVByte
}
return sweepTimeLockManual(
extendedKey, cfg.APIURL, c.SweepAddr, c.TimeLockAddr,
remoteRevPoint, c.MaxCsvLimit, c.Publish, c.FeeRate,
)
}
func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
sweepAddr, timeLockAddr string, remoteRevPoint *btcec.PublicKey,
maxCsvTimeout int, publish bool, feeRate uint32) error {
// First of all, we need to parse the lock addr and make sure we can
// brute force the script with the information we have. If not, we can't
// continue anyway.
lockScript, err := lnd.GetP2WSHScript(timeLockAddr, chainParams)
if err != nil {
return fmt.Errorf("invalid time lock addr: %v", err)
}
// We need to go through a lot of our keys so it makes sense to
// pre-derive the static part of our key path.
basePath, err := lnd.ParsePath(fmt.Sprintf(
keyBasePath, chainParams.HDCoinType,
))
if err != nil {
return fmt.Errorf("could not derive base path: %v", err)
}
baseKey, err := lnd.DeriveChildren(extendedKey, basePath)
if err != nil {
return fmt.Errorf("could not derive base key: %v", err)
}
// Go through all our keys now and try to find the ones that can derive
// the script. This loop can take very long as it'll nest three times,
// once for the key index, once for the commit points and once for the
// CSV values. Most of the calculations should be rather cheap but the
// number of iterations can go up to maxKeys*maxPoints*maxCsvTimeout.
var (
csvTimeout int32
script []byte
scriptHash []byte
delayDesc *keychain.KeyDescriptor
commitPoint *btcec.PublicKey
)
for i := uint32(0); i < maxKeys; i++ {
// The easy part first, let's derive the delay base point.
delayPath := []uint32{
lnd.HardenedKey(uint32(keychain.KeyFamilyDelayBase)), 0,
i,
}
delayPrivKey, err := lnd.PrivKeyFromPath(baseKey, delayPath)
if err != nil {
return err
}
// Get the revocation base point first so we can calculate our
// commit point.
revPath := []uint32{
lnd.HardenedKey(uint32(
keychain.KeyFamilyRevocationRoot,
)), 0, i,
}
revRoot, err := lnd.ShaChainFromPath(baseKey, revPath)
if err != nil {
return err
}
// We now have everything to brute force the lock script. This
// will take a long while as we both have to go through commit
// points and CSV values.
csvTimeout, script, scriptHash, commitPoint, err =
bruteForceDelayPoint(
delayPrivKey.PubKey(), remoteRevPoint, revRoot,
lockScript, maxCsvTimeout,
)
if err == nil {
delayDesc = &keychain.KeyDescriptor{
PubKey: delayPrivKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyDelayBase,
Index: i,
},
}
break
}
if i != 0 && i%20 == 0 {
fmt.Printf("Tried %d of %d keys.", i, maxKeys)
}
}
// Did we find what we looked for or did we just exhaust all
// possibilities?
if script == nil || delayDesc == nil {
return fmt.Errorf("target script not derived")
}
// Create signer and transaction template.
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
api := &btc.ExplorerAPI{BaseURL: apiURL}
// We now know everything we need to construct the sweep transaction,
// except for what outpoint to sweep. We'll ask the chain API to give
// us this information.
tx, txindex, err := api.Outpoint(timeLockAddr)
if err != nil {
return fmt.Errorf("error looking up lock address %s on chain: "+
"%v", timeLockAddr, err)
}
sweepTx := wire.NewMsgTx(2)
sweepValue := int64(tx.Vout[txindex].Value)
// Create the transaction input.
txHash, err := chainhash.NewHashFromStr(tx.TXID)
if err != nil {
return fmt.Errorf("error parsing tx hash: %v", err)
}
sweepTx.TxIn = []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{
Hash: *txHash,
Index: uint32(txindex),
},
Sequence: input.LockTimeToSequence(
false, uint32(csvTimeout),
),
}}
// Calculate the fee based on the given fee rate and our weight
// estimation.
var estimator input.TxWeightEstimator
estimator.AddWitnessInput(input.ToLocalTimeoutWitnessSize)
estimator.AddP2WKHOutput()
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))
// Add our sweep destination output.
sweepScript, err := lnd.GetP2WPKHScript(sweepAddr, chainParams)
if err != nil {
return err
}
sweepTx.TxOut = []*wire.TxOut{{
Value: sweepValue - int64(totalFee),
PkScript: sweepScript,
}}
log.Infof("Fee %d sats of %d total amount (estimated weight %d)",
totalFee, sweepValue, estimator.Weight())
// Create the sign descriptor for the input then sign the transaction.
sigHashes := txscript.NewTxSigHashes(sweepTx)
signDesc := &input.SignDescriptor{
KeyDesc: *delayDesc,
SingleTweak: input.SingleTweakBytes(
commitPoint, delayDesc.PubKey,
),
WitnessScript: script,
Output: &wire.TxOut{
PkScript: scriptHash,
Value: sweepValue,
},
InputIndex: 0,
SigHashes: sigHashes,
HashType: txscript.SigHashAll,
}
witness, err := input.CommitSpendTimeout(signer, signDesc, sweepTx)
if err != nil {
return err
}
sweepTx.TxIn[0].Witness = witness
var buf bytes.Buffer
err = sweepTx.Serialize(&buf)
if err != nil {
return err
}
// Publish TX.
if publish {
response, err := api.PublishTx(
hex.EncodeToString(buf.Bytes()),
)
if err != nil {
return err
}
log.Infof("Published TX %s, response: %s",
sweepTx.TxHash().String(), response)
}
log.Infof("Transaction: %x", buf.Bytes())
return nil
}
func bruteForceDelayPoint(delayBase, revBase *btcec.PublicKey,
revRoot *shachain.RevocationProducer, lockScript []byte,
maxCsvTimeout int) (int32, []byte, []byte, *btcec.PublicKey, error) {
for i := uint64(0); i < maxPoints; i++ {
revPreimage, err := revRoot.AtIndex(i)
if err != nil {
return 0, nil, nil, nil, err
}
commitPoint := input.ComputeCommitmentPoint(revPreimage[:])
csvTimeout, script, scriptHash, err := bruteForceDelay(
input.TweakPubKey(delayBase, commitPoint),
input.DeriveRevocationPubkey(revBase, commitPoint),
lockScript, maxCsvTimeout,
)
if err != nil {
continue
}
return csvTimeout, script, scriptHash, commitPoint, nil
}
return 0, nil, nil, nil, fmt.Errorf("target script not derived")
}

View File

@ -2,7 +2,9 @@ package lnd
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/lightningnetwork/lnd/shachain"
"strconv"
"strings"
@ -62,6 +64,10 @@ func ParsePath(path string) ([]uint32, error) {
return indices, nil
}
func HardenedKey(key uint32) uint32 {
return key + HardenedKeyStart
}
// DeriveKey derives the public key and private key in the WIF format for a
// given key path of the extended key.
func DeriveKey(extendedKey *hdkeychain.ExtendedKey, path string,
@ -98,6 +104,35 @@ func DeriveKey(extendedKey *hdkeychain.ExtendedKey, path string,
return derivedKey, pubKey, wif, nil
}
func PrivKeyFromPath(extendedKey *hdkeychain.ExtendedKey,
path []uint32) (*btcec.PrivateKey, error) {
derivedKey, err := DeriveChildren(extendedKey, path)
if err != nil {
return nil, fmt.Errorf("could not derive children: %v", err)
}
privKey, err := derivedKey.ECPrivKey()
if err != nil {
return nil, fmt.Errorf("could not derive private key: %v", err)
}
return privKey, nil
}
func ShaChainFromPath(extendedKey *hdkeychain.ExtendedKey,
path []uint32) (*shachain.RevocationProducer, error) {
privKey, err := PrivKeyFromPath(extendedKey, path)
if err != nil {
return nil, err
}
revRoot, err := chainhash.NewHash(privKey.Serialize())
if err != nil {
return nil, fmt.Errorf("could not create revocation root "+
"hash: %v", err)
}
return shachain.NewRevocationProducer(*revRoot), nil
}
func AllDerivationPaths(params *chaincfg.Params) ([]string, [][]uint32, error) {
mkPath := func(f keychain.KeyFamily) string {
return fmt.Sprintf(
@ -194,6 +229,30 @@ func GetP2WPKHScript(addr string, chainParams *chaincfg.Params) ([]byte,
return builder.Script()
}
// GetP2WSHScript creates a P2WSH output script from an address. If the address
// is not a P2WSH address, an error is returned.
func GetP2WSHScript(addr string, chainParams *chaincfg.Params) ([]byte,
error) {
targetScriptHash, isScriptHash, err := DecodeAddressHash(
addr, chainParams,
)
if err != nil {
return nil, err
}
if !isScriptHash {
return nil, fmt.Errorf("address %s is not a P2WSH address",
addr)
}
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_0)
builder.AddData(targetScriptHash)
return builder.Script()
}
type HDKeyRing struct {
ExtendedKey *hdkeychain.ExtendedKey
ChainParams *chaincfg.Params