mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
lnwallet: add a BtcWallet implementation of WalletController
This commit adds the first concrete implementation of the WalletController interface: BtcWallet. This implementation is simply a series of wrapper functions are the base btcwallet struct. Additionally, for ease of use both the BlockChain IO and Signer interface are also implemented by BtcWallet. Finally a new WalletDriver implementation has been implemented, and will be register by the init() method within this new package.
This commit is contained in:
parent
5449ba2b34
commit
6a1d8d0682
55
lnwallet/btcwallet/blockchain.go
Normal file
55
lnwallet/btcwallet/blockchain.go
Normal file
@ -0,0 +1,55 @@
|
||||
package btcwallet
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
)
|
||||
|
||||
// GetCurrentHeight returns the current height of the known block within the
|
||||
// main chain.
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetCurrentHeight() (int32, error) {
|
||||
_, height, err := b.rpc.GetBestBlock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return height, nil
|
||||
}
|
||||
|
||||
// GetTxOut returns the original output referenced by the passed outpoint.
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetUtxo(txid *wire.ShaHash, index uint32) (*wire.TxOut, error) {
|
||||
txout, err := b.rpc.GetTxOut(txid, index, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkScript, err := hex.DecodeString(txout.ScriptPubKey.Hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wire.TxOut{
|
||||
// Sadly, gettxout returns the output value in BTC
|
||||
// instead of satoshis.
|
||||
Value: int64(txout.Value) * 1e8,
|
||||
PkScript: pkScript,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetTransaction returns the full transaction identified by the passed
|
||||
// transaction ID.
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetTransaction(txid *wire.ShaHash) (*wire.MsgTx, error) {
|
||||
tx, err := b.rpc.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tx.MsgTx(), nil
|
||||
}
|
380
lnwallet/btcwallet/btcwallet.go
Normal file
380
lnwallet/btcwallet/btcwallet.go
Normal file
@ -0,0 +1,380 @@
|
||||
package btcwallet
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/txscript"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
"github.com/roasbeef/btcwallet/chain"
|
||||
"github.com/roasbeef/btcwallet/waddrmgr"
|
||||
base "github.com/roasbeef/btcwallet/wallet"
|
||||
"github.com/roasbeef/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAccount = uint32(waddrmgr.DefaultAccountNum)
|
||||
)
|
||||
|
||||
var (
|
||||
lnNamespace = []byte("ln")
|
||||
rootKey = []byte("ln-root")
|
||||
)
|
||||
|
||||
// BtcWallet is an implementation of the lnwallet.WalletController interface
|
||||
// backed by an active instance of btcwallet. At the time of the writing of
|
||||
// this documentation, this implementation requires a full btcd node to
|
||||
// operate.
|
||||
type BtcWallet struct {
|
||||
// wallet is an active instance of btcwallet.
|
||||
wallet *base.Wallet
|
||||
|
||||
// rpc is an an active RPC connection to btcd full-node.
|
||||
rpc *chain.RPCClient
|
||||
|
||||
// lnNamespace is a namespace within btcwallet's walletdb used to store
|
||||
// persistent state required by the WalletController interface but not
|
||||
// natively supported by btcwallet.
|
||||
lnNamespace walletdb.Namespace
|
||||
|
||||
netParams *chaincfg.Params
|
||||
|
||||
// utxoCache is a cache used to speed up repeated calls to
|
||||
// FetchInputInfo.
|
||||
utxoCache map[wire.OutPoint]*wire.TxOut
|
||||
cacheMtx sync.RWMutex
|
||||
}
|
||||
|
||||
// A compile time check to ensure that BtcWallet implements the
|
||||
// WalletController interface.
|
||||
var _ lnwallet.WalletController = (*BtcWallet)(nil)
|
||||
|
||||
// New returns a new fully initialized instance of BtcWallet given a valid
|
||||
// confirguration struct.
|
||||
func New(cfg *Config) (*BtcWallet, error) {
|
||||
// Ensure the wallet exists or create it when the create flag is set.
|
||||
netDir := networkDir(cfg.DataDir, cfg.NetParams)
|
||||
|
||||
var pubPass []byte
|
||||
if cfg.PublicPass == nil {
|
||||
pubPass = defaultPubPassphrase
|
||||
} else {
|
||||
pubPass = cfg.PublicPass
|
||||
}
|
||||
|
||||
loader := base.NewLoader(cfg.NetParams, netDir)
|
||||
walletExists, err := loader.WalletExists()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wallet *base.Wallet
|
||||
if !walletExists {
|
||||
// Wallet has never been created, perform initial set up.
|
||||
wallet, err = loader.CreateNewWallet(pubPass, cfg.PrivatePass,
|
||||
cfg.HdSeed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Wallet has been created and been initialized at this point, open it
|
||||
// along with all the required DB namepsaces, and the DB itself.
|
||||
wallet, err = loader.OpenExistingWallet(pubPass, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := wallet.Manager.Unlock(cfg.PrivatePass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a special websockets rpc client for btcd which will be used
|
||||
// by the wallet for notifications, calls, etc.
|
||||
rpcc, err := chain.NewRPCClient(cfg.NetParams, cfg.RpcHost,
|
||||
cfg.RpcUser, cfg.RpcPass, cfg.CACert, false, 20)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db := wallet.Database()
|
||||
walletNamespace, err := db.Namespace(lnNamespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BtcWallet{
|
||||
wallet: wallet,
|
||||
rpc: rpcc,
|
||||
lnNamespace: walletNamespace,
|
||||
netParams: cfg.NetParams,
|
||||
utxoCache: make(map[wire.OutPoint]*wire.TxOut),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start initializes the underlying rpc connection, the wallet itself, and
|
||||
// begins syncing to the current available blockchain state.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) Start() error {
|
||||
// Establish an RPC connection in additino to starting the goroutines
|
||||
// in the underlying wallet.
|
||||
if err := b.rpc.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the underlying btcwallet core.
|
||||
b.wallet.Start()
|
||||
|
||||
// Pass the rpc client into the wallet so it can sync up to the
|
||||
// current main chain.
|
||||
b.wallet.SynchronizeRPC(b.rpc)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop signals the wallet for shutdown. Shutdown may entail closing
|
||||
// any active sockets, database handles, stopping goroutines, etc.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) Stop() error {
|
||||
b.wallet.Stop()
|
||||
|
||||
b.wallet.WaitForShutdown()
|
||||
|
||||
b.rpc.Shutdown()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfirmedBalance returns the sum of all the wallet's unspent outputs that
|
||||
// have at least confs confirmations. If confs is set to zero, then all unspent
|
||||
// outputs, including those currently in the mempool will be included in the
|
||||
// final sum.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error) {
|
||||
var balance btcutil.Amount
|
||||
|
||||
if witness {
|
||||
witnessOutputs, err := b.ListUnspentWitness(confs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, witnessOutput := range witnessOutputs {
|
||||
balance += witnessOutput.Value
|
||||
}
|
||||
} else {
|
||||
outputSum, err := b.wallet.CalculateBalance(confs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
balance = outputSum
|
||||
}
|
||||
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
// NewAddress returns the next external or internal address for the wallet
|
||||
// dicatated by the value of the `change` paramter. If change is true, then an
|
||||
// internal address will be returned, otherwise an external address should be
|
||||
// returned.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool) (btcutil.Address, error) {
|
||||
var addrType waddrmgr.AddressType
|
||||
|
||||
switch t {
|
||||
case lnwallet.WitnessPubKey:
|
||||
addrType = waddrmgr.WitnessPubKey
|
||||
case lnwallet.NestedWitnessPubKey:
|
||||
addrType = waddrmgr.NestedWitnessPubKey
|
||||
case lnwallet.PubKeyHash:
|
||||
addrType = waddrmgr.PubKeyHash
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown address type")
|
||||
}
|
||||
|
||||
if change {
|
||||
return b.wallet.NewAddress(defaultAccount, addrType)
|
||||
} else {
|
||||
return b.wallet.NewChangeAddress(defaultAccount, addrType)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPrivKey retrives the underlying private key associated with the passed
|
||||
// address. If the we're unable to locate the proper private key, then a
|
||||
// non-nil error will be returned.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) {
|
||||
// Using the ID address, request the private key coresponding to the
|
||||
// address from the wallet's address manager.
|
||||
walletAddr, err := b.wallet.Manager.Address(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return walletAddr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||
}
|
||||
|
||||
// NewRawKey retrieves the next key within our HD key-chain for use within as a
|
||||
// multi-sig key within the funding transaction, or within the commitment
|
||||
// transaction's outputs.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) NewRawKey() (*btcec.PublicKey, error) {
|
||||
nextAddr, err := b.wallet.Manager.NextExternalAddresses(defaultAccount,
|
||||
1, waddrmgr.WitnessPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkAddr := nextAddr[0].(waddrmgr.ManagedPubKeyAddress)
|
||||
|
||||
return pkAddr.PubKey(), nil
|
||||
}
|
||||
|
||||
// FetchRootKey returns a root key which is meanted to be used as an initial
|
||||
// seed/salt to generate any Lightning specific secrets.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) FetchRootKey() (*btcec.PrivateKey, error) {
|
||||
// Fetch the root address hash from the database, this is persisted
|
||||
// locally within the database, then used to obtain the key from the
|
||||
// wallet based on the address hash.
|
||||
var rootAddrHash []byte
|
||||
if err := b.lnNamespace.Update(func(tx walletdb.Tx) error {
|
||||
rootBucket := tx.RootBucket()
|
||||
|
||||
rootAddrHash = rootBucket.Get(rootKey)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rootAddrHash == nil {
|
||||
// Otherwise, we need to generate a fresh address from the
|
||||
// wallet, then stores it's hash160 within the database so we
|
||||
// can look up the exact key later.
|
||||
rootAddr, err := b.wallet.Manager.NextExternalAddresses(defaultAccount,
|
||||
1, waddrmgr.WitnessPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := b.lnNamespace.Update(func(tx walletdb.Tx) error {
|
||||
rootBucket := tx.RootBucket()
|
||||
|
||||
rootAddrHash = rootAddr[0].Address().ScriptAddress()
|
||||
if err := rootBucket.Put(rootKey, rootAddrHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// With the root address hash obtained, generate the corresponding
|
||||
// address, then retrieve the managed address from the wallet which
|
||||
// will allow us to obtain the private key.
|
||||
rootAddr, err := btcutil.NewAddressWitnessPubKeyHash(rootAddrHash,
|
||||
b.netParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
walletAddr, err := b.wallet.Manager.Address(rootAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return walletAddr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||
}
|
||||
|
||||
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
|
||||
// the specified outputs. In the case the wallet has insufficient funds, or the
|
||||
// outputs are non-standard, a non-nil error will be be returned.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error) {
|
||||
return b.wallet.SendOutputs(outputs, defaultAccount, 1)
|
||||
}
|
||||
|
||||
// LockOutpoint marks an outpoint as locked meaning it will no longer be deemed
|
||||
// as eligible for coin selection. Locking outputs are utilized in order to
|
||||
// avoid race conditions when selecting inputs for usage when funding a
|
||||
// channel.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) LockOutpoint(o wire.OutPoint) {
|
||||
b.wallet.LockOutpoint(o)
|
||||
}
|
||||
|
||||
// UnlockOutpoint unlocks an previously locked output, marking it eligible for
|
||||
// coin seleciton.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) UnlockOutpoint(o wire.OutPoint) {
|
||||
b.wallet.UnlockOutpoint(o)
|
||||
}
|
||||
|
||||
// ListUnspentWitness returns a slice of all the unspent outputs the wallet
|
||||
// controls which pay to witness programs either directly or indirectly.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) ListUnspentWitness(minConfs int32) ([]*lnwallet.Utxo, error) {
|
||||
// First, grab all the unfiltered currently unspent outputs.
|
||||
maxConfs := int32(math.MaxInt32)
|
||||
unspentOutputs, err := b.wallet.ListUnspent(minConfs, maxConfs, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next, we'll run through all the regular outputs, only saving those
|
||||
// which are p2wkh outputs or a p2wsh output nested within a p2sh output.
|
||||
witnessOutputs := make([]*lnwallet.Utxo, 0, len(unspentOutputs))
|
||||
for _, output := range unspentOutputs {
|
||||
pkScript, err := hex.DecodeString(output.ScriptPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(roasbeef): this assumes all p2sh outputs returned by
|
||||
// the wallet are nested p2sh...
|
||||
if txscript.IsPayToWitnessPubKeyHash(pkScript) ||
|
||||
txscript.IsPayToScriptHash(pkScript) {
|
||||
txid, err := wire.NewShaHashFromStr(output.TxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
utxo := &lnwallet.Utxo{
|
||||
Value: btcutil.Amount(output.Amount * 1e8),
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: *txid,
|
||||
Index: output.Vout,
|
||||
},
|
||||
}
|
||||
witnessOutputs = append(witnessOutputs, utxo)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return witnessOutputs, nil
|
||||
}
|
||||
|
||||
// PublishTransaction performs cursory validation (dust checks, etc), then
|
||||
// finally broadcasts the passed transaction to the Bitcoin network.
|
||||
func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx) error {
|
||||
return b.wallet.PublishTransaction(tx)
|
||||
}
|
94
lnwallet/btcwallet/config.go
Normal file
94
lnwallet/btcwallet/config.go
Normal file
@ -0,0 +1,94 @@
|
||||
package btcwallet
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
||||
)
|
||||
|
||||
var (
|
||||
lnwalletHomeDir = btcutil.AppDataDir("lnwallet", false)
|
||||
defaultDataDir = lnwalletHomeDir
|
||||
|
||||
defaultLogFilename = "lnwallet.log"
|
||||
defaultLogDirname = "logs"
|
||||
defaultLogDir = filepath.Join(lnwalletHomeDir, defaultLogDirname)
|
||||
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
btcdHomedirCAFile = filepath.Join(btcdHomeDir, "rpc.cert")
|
||||
defaultRPCKeyFile = filepath.Join(lnwalletHomeDir, "rpc.key")
|
||||
defaultRPCCertFile = filepath.Join(lnwalletHomeDir, "rpc.cert")
|
||||
|
||||
// defaultPubPassphrase is the default public wallet passphrase which is
|
||||
// used when the user indicates they do not want additional protection
|
||||
// provided by having all public data in the wallet encrypted by a
|
||||
// passphrase only known to them.
|
||||
defaultPubPassphrase = []byte("public")
|
||||
|
||||
walletDbName = "lnwallet.db"
|
||||
)
|
||||
|
||||
// Config is a struct which houses configuration paramters which modify the
|
||||
// instance of BtcWallet generated by the New() function.
|
||||
type Config struct {
|
||||
// DataDir is the name of the directory where the wallet's persistent
|
||||
// state should be sotred.
|
||||
DataDir string
|
||||
|
||||
// LogDir is the name of the directory which should be used to store
|
||||
// generated log files.
|
||||
LogDir string
|
||||
|
||||
// DebugLevel is a string representing the level of verbosity the
|
||||
// logger should use.
|
||||
DebugLevel string
|
||||
|
||||
// RpcHost is the host and port to use to reach the rpc sever.
|
||||
RpcHost string // localhost:18334
|
||||
|
||||
// RpcUser is the username which should be used to authentiate with the
|
||||
// rpc server.
|
||||
RpcUser string
|
||||
|
||||
// RpcPass is the password which should be used to authenticate the
|
||||
// connection with the RPC server.
|
||||
RpcPass string
|
||||
|
||||
// RpcNoTLS denotes if a TLS connection should be attempted when
|
||||
// connecting to the RPC server.
|
||||
RpcNoTLS bool
|
||||
|
||||
// RPCCert directory where the TLS certificate of the RPC sever is
|
||||
// stored. If the RpcNoTLS is false, then this value will be unused.
|
||||
RPCCert string
|
||||
RPCKey string
|
||||
|
||||
// CACert is the raw RPC cert for btcd.
|
||||
CACert []byte
|
||||
|
||||
PrivatePass []byte
|
||||
PublicPass []byte
|
||||
HdSeed []byte
|
||||
|
||||
NetParams *chaincfg.Params
|
||||
}
|
||||
|
||||
// networkDir returns the directory name of a network directory to hold wallet
|
||||
// files.
|
||||
func networkDir(dataDir string, chainParams *chaincfg.Params) string {
|
||||
netname := chainParams.Name
|
||||
|
||||
// For now, we must always name the testnet data directory as "testnet"
|
||||
// and not "testnet3" or any other version, as the chaincfg testnet3
|
||||
// paramaters will likely be switched to being named "testnet3" in the
|
||||
// future. This is done to future proof that change, and an upgrade
|
||||
// plan to move the testnet3 data directory can be worked out later.
|
||||
if chainParams.Net == wire.TestNet3 {
|
||||
netname = "testnet"
|
||||
}
|
||||
|
||||
return filepath.Join(dataDir, netname)
|
||||
}
|
45
lnwallet/btcwallet/driver.go
Normal file
45
lnwallet/btcwallet/driver.go
Normal file
@ -0,0 +1,45 @@
|
||||
package btcwallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
const (
|
||||
walletType = "btcwallet"
|
||||
)
|
||||
|
||||
// createNewWallet creates a new instance of BtcWallet given the proper list of
|
||||
// initialization parameters. This function is the factory function required to
|
||||
// properly create an instance of the lnwallet.WalletDriver struct for
|
||||
// BtcWallet.
|
||||
func createNewWallet(args ...interface{}) (lnwallet.WalletController, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, fmt.Errorf("incorrect number of arguments to .New(...), "+
|
||||
"expected 1, instead passed %v", len(args))
|
||||
}
|
||||
|
||||
config, ok := args[0].(*Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("first argument to btcdnotifier.New is " +
|
||||
"incorrect, expected a *btcrpcclient.ConnConfig")
|
||||
}
|
||||
|
||||
return New(config)
|
||||
}
|
||||
|
||||
// init registers a driver for the BtcWallet concrete implementation of the
|
||||
// lnwallet.WalletController interface.
|
||||
func init() {
|
||||
// Register the driver.
|
||||
driver := &lnwallet.WalletDriver{
|
||||
WalletType: walletType,
|
||||
New: createNewWallet,
|
||||
}
|
||||
|
||||
if err := lnwallet.RegisterWallet(driver); err != nil {
|
||||
panic(fmt.Sprintf("failed to register wallet driver '%s': %v",
|
||||
walletType, err))
|
||||
}
|
||||
}
|
174
lnwallet/btcwallet/signer.go
Normal file
174
lnwallet/btcwallet/signer.go
Normal file
@ -0,0 +1,174 @@
|
||||
package btcwallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/roasbeef/btcd/txscript"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
"github.com/roasbeef/btcwallet/waddrmgr"
|
||||
)
|
||||
|
||||
// FetchInputInfo queries for the WalletController's knowledge of the passed
|
||||
// outpoint. If the base wallet determines this output is under its control,
|
||||
// then the original txout should be returned. Otherwise, a non-nil error value
|
||||
// of ErrNotMine should be returned instead.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error) {
|
||||
var (
|
||||
err error
|
||||
output *wire.TxOut
|
||||
)
|
||||
|
||||
// First check to see if the output is already within the utxo cache.
|
||||
// If so we can return directly saving usk a disk access.
|
||||
b.cacheMtx.RLock()
|
||||
if output, ok := b.utxoCache[*prevOut]; ok {
|
||||
b.cacheMtx.RUnlock()
|
||||
return output, nil
|
||||
}
|
||||
b.cacheMtx.RUnlock()
|
||||
|
||||
// Otherwse, we manually look up the output within the tx store.
|
||||
txDetail, err := b.wallet.TxStore.TxDetails(&prevOut.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if txDetail == nil {
|
||||
return nil, lnwallet.ErrNotMine
|
||||
}
|
||||
|
||||
output = txDetail.TxRecord.MsgTx.TxOut[prevOut.Index]
|
||||
|
||||
b.cacheMtx.Lock()
|
||||
b.utxoCache[*prevOut] = output
|
||||
b.cacheMtx.Unlock()
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// fetchOutputKey attempts to fetch the managed address corresponding to the
|
||||
// passed output script. This function is used to look up the proper key which
|
||||
// should be used to sign a specified input.
|
||||
func (b *BtcWallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, error) {
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(script, b.netParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the case of a multi-sig output, several address may be extracted.
|
||||
// Therefore, we simply select the key for the first address we know
|
||||
// of.
|
||||
for _, addr := range addrs {
|
||||
wAddr, err := b.wallet.Manager.Address(addr)
|
||||
if err == nil {
|
||||
return wAddr, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(roasbeef): use the errors.wrap package
|
||||
return nil, fmt.Errorf("address not found")
|
||||
}
|
||||
|
||||
// SignOutputRaw generates a signature for the passed transaction according to
|
||||
// the data within the passed SignDescriptor.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, signDesc *lnwallet.SignDescriptor) ([]byte, error) {
|
||||
redeemScript := signDesc.RedeemScript
|
||||
walletAddr, err := b.fetchOutputAddr(redeemScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey, err := walletAddr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
amt := signDesc.Output.Value
|
||||
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, 0,
|
||||
amt, redeemScript, txscript.SigHashAll, privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Chop off the sighash flag at the end of the signature.
|
||||
return sig[:len(sig)-1], nil
|
||||
}
|
||||
|
||||
// ComputeInputScript generates a complete InputIndex for the passed
|
||||
// transaction with the signature as defined within the passed SignDescriptor.
|
||||
// This method is capable of generating the proper input script for both
|
||||
// regular p2wkh output and p2wkh outputs nested within a regualr p2sh output.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
|
||||
signDesc *lnwallet.SignDescriptor) (*lnwallet.InputScript, error) {
|
||||
|
||||
outputScript := signDesc.Output.PkScript
|
||||
walletAddr, err := b.fetchOutputAddr(outputScript)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pka := walletAddr.(waddrmgr.ManagedPubKeyAddress)
|
||||
privKey, err := pka.PrivKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var witnessProgram []byte
|
||||
inputScript := &lnwallet.InputScript{}
|
||||
|
||||
// If we're spending p2wkh output nested within a p2sh output, then
|
||||
// we'll need to attach a sigScript in addition to witness data.
|
||||
switch {
|
||||
case pka.IsNestedWitness():
|
||||
pubKey := privKey.PubKey()
|
||||
pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed())
|
||||
|
||||
// Next, we'll generate a valid sigScript that'll allow us to
|
||||
// spend the p2sh output. The sigScript will contain only a
|
||||
// single push of the p2wkh witness program corresponding to
|
||||
// the matching public key of this address.
|
||||
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash,
|
||||
b.netParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
witnessProgram, err = txscript.PayToAddrScript(p2wkhAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bldr := txscript.NewScriptBuilder()
|
||||
bldr.AddData(witnessProgram)
|
||||
sigScript, err := bldr.Script()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputScript.ScriptSig = sigScript
|
||||
// Otherwise, this is a regular p2wkh output, so we include the
|
||||
// witness program itself as the subscript to generate the proper
|
||||
// sighash digest. As part of the new sighash digest algorithm, the
|
||||
// p2wkh witness program will be expanded into a regular p2kh
|
||||
// script.
|
||||
default:
|
||||
witnessProgram = outputScript
|
||||
}
|
||||
|
||||
// Generate a valid witness stack for the input.
|
||||
witnessScript, err := txscript.WitnessScript(tx, signDesc.SigHashes,
|
||||
signDesc.InputIndex, signDesc.Output.Value, witnessProgram,
|
||||
txscript.SigHashAll, privKey, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputScript.Witness = witnessScript
|
||||
|
||||
return inputScript, nil
|
||||
}
|
@ -1,60 +1,15 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcutil"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO(roasbeef): lnwallet config file
|
||||
lnwalletHomeDir = btcutil.AppDataDir("lnwallet", false)
|
||||
defaultDataDir = lnwalletHomeDir
|
||||
|
||||
defaultLogFilename = "lnwallet.log"
|
||||
defaultLogDirname = "logs"
|
||||
defaultLogDir = filepath.Join(lnwalletHomeDir, defaultLogDirname)
|
||||
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
btcdHomedirCAFile = filepath.Join(btcdHomeDir, "rpc.cert")
|
||||
defaultRPCKeyFile = filepath.Join(lnwalletHomeDir, "rpc.key")
|
||||
defaultRPCCertFile = filepath.Join(lnwalletHomeDir, "rpc.cert")
|
||||
|
||||
// defaultPubPassphrase is the default public wallet passphrase which is
|
||||
// used when the user indicates they do not want additional protection
|
||||
// provided by having all public data in the wallet encrypted by a
|
||||
// passphrase only known to them.
|
||||
defaultPubPassphrase = []byte("public")
|
||||
|
||||
walletDbName = "lnwallet.db"
|
||||
)
|
||||
|
||||
// Config...
|
||||
// Config..
|
||||
type Config struct {
|
||||
DataDir string
|
||||
LogDir string
|
||||
|
||||
DebugLevel string
|
||||
|
||||
RpcHost string // localhost:18334
|
||||
RpcUser string
|
||||
RpcPass string
|
||||
RpcNoTLS bool
|
||||
|
||||
RPCCert string
|
||||
RPCKey string
|
||||
|
||||
CACert []byte
|
||||
|
||||
PrivatePass []byte
|
||||
PublicPass []byte
|
||||
HdSeed []byte
|
||||
|
||||
// Which bitcoin network are we using?
|
||||
NetParams *chaincfg.Params
|
||||
}
|
||||
|
||||
// setDefaults...
|
||||
func setDefaults(confg *Config) {
|
||||
// default csv time
|
||||
// default cltv time
|
||||
// default wait for funding time
|
||||
// default wait for closure time
|
||||
// min amount to accept channel
|
||||
// min fee imformation
|
||||
// * or possibly interface to predict fees
|
||||
// max htlcs in flight?
|
||||
// possible secret derivation functions
|
||||
//
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user