Merge pull request #17 from lightningnetwork/wallet-interface-refactor

Refactor the lnwallet package to directly use the WalletController interface
This commit is contained in:
Olaoluwa Osuntokun 2016-09-08 14:35:35 -05:00 committed by GitHub
commit dfe37cd699
26 changed files with 1757 additions and 1034 deletions

@ -106,7 +106,7 @@ type OpenChannel struct {
//ReserveAmount btcutil.Amount
// Keys for both sides to be used for the commitment transactions.
OurCommitKey *btcec.PrivateKey
OurCommitKey *btcec.PublicKey
TheirCommitKey *btcec.PublicKey
// Tracking total channel capacity, and the amount of funds allocated
@ -123,7 +123,7 @@ type OpenChannel struct {
// The outpoint of the final funding transaction.
FundingOutpoint *wire.OutPoint
OurMultiSigKey *btcec.PrivateKey
OurMultiSigKey *btcec.PublicKey
TheirMultiSigKey *btcec.PublicKey
FundingRedeemScript []byte
@ -200,7 +200,7 @@ func (c *OpenChannel) FullSync() error {
chanIDBucket.Put(b.Bytes(), nil)
}
return putOpenChannel(chanBucket, nodeChanBucket, c, c.Db.cryptoSystem)
return putOpenChannel(chanBucket, nodeChanBucket, c)
})
}
@ -362,7 +362,7 @@ func putClosedChannelSummary(tx *bolt.Tx, chanID []byte) error {
// putChannel serializes, and stores the current state of the channel in its
// entirety.
func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
channel *OpenChannel, encryptor EncryptorDecryptor) error {
channel *OpenChannel) error {
// First write out all the "common" fields using the field's prefix
// appened with the channel's ID. These fields go into a top-level bucket
@ -387,13 +387,13 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
if err := putChannelIDs(nodeChanBucket, channel); err != nil {
return err
}
if err := putChanCommitKeys(nodeChanBucket, channel, encryptor); err != nil {
if err := putChanCommitKeys(nodeChanBucket, channel); err != nil {
return err
}
if err := putChanCommitTxns(nodeChanBucket, channel); err != nil {
return err
}
if err := putChanFundingInfo(nodeChanBucket, channel, encryptor); err != nil {
if err := putChanFundingInfo(nodeChanBucket, channel); err != nil {
return err
}
if err := putChanEklremState(nodeChanBucket, channel); err != nil {
@ -411,7 +411,7 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
// An EncryptorDecryptor is required to decrypt sensitive information stored
// within the database.
func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
chanID *wire.OutPoint, decryptor EncryptorDecryptor) (*OpenChannel, error) {
chanID *wire.OutPoint) (*OpenChannel, error) {
channel := &OpenChannel{
ChanID: chanID,
@ -421,13 +421,13 @@ func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
if err := fetchChannelIDs(nodeChanBucket, channel); err != nil {
return nil, err
}
if err := fetchChanCommitKeys(nodeChanBucket, channel, decryptor); err != nil {
if err := fetchChanCommitKeys(nodeChanBucket, channel); err != nil {
return nil, err
}
if err := fetchChanCommitTxns(nodeChanBucket, channel); err != nil {
return nil, err
}
if err := fetchChanFundingInfo(nodeChanBucket, channel, decryptor); err != nil {
if err := fetchChanFundingInfo(nodeChanBucket, channel); err != nil {
return nil, err
}
if err := fetchChanEklremState(nodeChanBucket, channel); err != nil {
@ -791,8 +791,7 @@ func fetchChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
return nil
}
func putChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
ed EncryptorDecryptor) error {
func putChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
// Construct the key which stores the commitment keys: ckk || channelID.
// TODO(roasbeef): factor into func
@ -810,12 +809,7 @@ func putChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
return err
}
encryptedPriv, err := ed.Encrypt(channel.OurCommitKey.Serialize())
if err != nil {
return err
}
if _, err := b.Write(encryptedPriv); err != nil {
if _, err := b.Write(channel.OurCommitKey.SerializeCompressed()); err != nil {
return err
}
@ -829,8 +823,7 @@ func deleteChanCommitKeys(nodeChanBucket *bolt.Bucket, chanID []byte) error {
return nodeChanBucket.Delete(commitKey)
}
func fetchChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
ed EncryptorDecryptor) error {
func fetchChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
// Construct the key which stores the commitment keys: ckk || channelID.
// TODO(roasbeef): factor into func
@ -850,12 +843,7 @@ func fetchChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
return err
}
decryptedPriv, err := ed.Decrypt(keyBytes[33:])
if err != nil {
return err
}
channel.OurCommitKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), decryptedPriv)
channel.OurCommitKey, err = btcec.ParsePubKey(keyBytes[33:], btcec.S256())
if err != nil {
return err
}
@ -939,9 +927,7 @@ func fetchChanCommitTxns(nodeChanBucket *bolt.Bucket, channel *OpenChannel) erro
return nil
}
func putChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
ed EncryptorDecryptor) error {
func putChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
var bc bytes.Buffer
if err := writeOutpoint(&bc, channel.ChanID); err != nil {
return err
@ -956,11 +942,8 @@ func putChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
return err
}
encryptedPriv, err := ed.Encrypt(channel.OurMultiSigKey.Serialize())
if err != nil {
return err
}
if err := wire.WriteVarBytes(&b, 0, encryptedPriv); err != nil {
ourSerKey := channel.OurMultiSigKey.SerializeCompressed()
if err := wire.WriteVarBytes(&b, 0, ourSerKey); err != nil {
return err
}
theirSerKey := channel.TheirMultiSigKey.SerializeCompressed()
@ -989,9 +972,7 @@ func deleteChanFundingInfo(nodeChanBucket *bolt.Bucket, chanID []byte) error {
return nodeChanBucket.Delete(fundTxnKey)
}
func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
ed EncryptorDecryptor) error {
func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
var b bytes.Buffer
if err := writeOutpoint(&b, channel.ChanID); err != nil {
return err
@ -1008,17 +989,16 @@ func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
return err
}
encryptedPrivBytes, err := wire.ReadVarBytes(infoBytes, 0, 100, "")
ourKeyBytes, err := wire.ReadVarBytes(infoBytes, 0, 34, "")
if err != nil {
return err
}
decryptedPriv, err := ed.Decrypt(encryptedPrivBytes)
channel.OurMultiSigKey, err = btcec.ParsePubKey(ourKeyBytes, btcec.S256())
if err != nil {
return err
}
channel.OurMultiSigKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), decryptedPriv)
theirKeyBytes, err := wire.ReadVarBytes(infoBytes, 0, 33, "")
theirKeyBytes, err := wire.ReadVarBytes(infoBytes, 0, 34, "")
if err != nil {
return err
}

@ -78,23 +78,6 @@ var (
}
)
type MockEncryptorDecryptor struct {
}
func (m *MockEncryptorDecryptor) Encrypt(n []byte) ([]byte, error) {
return n, nil
}
func (m *MockEncryptorDecryptor) Decrypt(n []byte) ([]byte, error) {
return n, nil
}
func (m *MockEncryptorDecryptor) OverheadSize() uint32 {
return 0
}
var _ EncryptorDecryptor = (*MockEncryptorDecryptor)(nil)
func TestOpenChannelPutGetDelete(t *testing.T) {
// First, create a temporary directory to be used for the duration of
// this test.
@ -111,7 +94,6 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
if err != nil {
t.Fatalf("unable to create channeldb: %v", err)
}
cdb.RegisterCryptoSystem(&MockEncryptorDecryptor{})
defer cdb.Close()
privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
@ -144,7 +126,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
TheirLNID: key,
ChanID: id,
MinFeePerKb: btcutil.Amount(5000),
OurCommitKey: privKey,
OurCommitKey: privKey.PubKey(),
TheirCommitKey: pubKey,
Capacity: btcutil.Amount(10000),
OurBalance: btcutil.Amount(3000),
@ -154,7 +136,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
LocalElkrem: sender,
RemoteElkrem: receiver,
FundingOutpoint: testOutpoint,
OurMultiSigKey: privKey,
OurMultiSigKey: privKey.PubKey(),
TheirMultiSigKey: privKey.PubKey(),
FundingRedeemScript: script,
TheirCurrentRevocation: privKey.PubKey(),
@ -195,8 +177,8 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
t.Fatalf("fee/kb doens't match")
}
if !bytes.Equal(state.OurCommitKey.Serialize(),
newState.OurCommitKey.Serialize()) {
if !bytes.Equal(state.OurCommitKey.SerializeCompressed(),
newState.OurCommitKey.SerializeCompressed()) {
t.Fatalf("our commit key dont't match")
}
if !bytes.Equal(state.TheirCommitKey.SerializeCompressed(),
@ -234,8 +216,8 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
t.Fatalf("funding outpoint doesn't match")
}
if !bytes.Equal(state.OurMultiSigKey.Serialize(),
newState.OurMultiSigKey.Serialize()) {
if !bytes.Equal(state.OurMultiSigKey.SerializeCompressed(),
newState.OurMultiSigKey.SerializeCompressed()) {
t.Fatalf("our multisig key doesn't match")
}
if !bytes.Equal(state.TheirMultiSigKey.SerializeCompressed(),

@ -27,14 +27,6 @@ var bufPool = &sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
// EncryptorDecryptor...
// TODO(roasbeef): ability to rotate EncryptorDecryptor's across DB
type EncryptorDecryptor interface {
Encrypt(in []byte) ([]byte, error)
Decrypt(in []byte) ([]byte, error)
OverheadSize() uint32
}
// DB is the primary datastore for the LND daemon. The database stores
// information related to nodes, routing data, open/closed channels, fee
// schedules, and reputation data.
@ -42,8 +34,6 @@ type DB struct {
store *bolt.DB
netParams *chaincfg.Params
cryptoSystem EncryptorDecryptor
}
// Open opens an existing channeldb created under the passed namespace with
@ -66,12 +56,6 @@ func Open(dbPath string, netParams *chaincfg.Params) (*DB, error) {
return &DB{store: bdb, netParams: netParams}, nil
}
// RegisterCryptoSystem registers an implementation of the EncryptorDecryptor
// interface for use within the database to encrypt/decrypt sensitive data.
func (d *DB) RegisterCryptoSystem(ed EncryptorDecryptor) {
d.cryptoSystem = ed
}
// Wipe completely deletes all saved state within all used buckets within the
// database. The deletion is done in a single transaction, therefore this
// operation is fully atomic.
@ -179,7 +163,7 @@ func (d *DB) FetchOpenChannels(nodeID *wire.ShaHash) ([]*OpenChannel, error) {
}
oChannel, err := fetchOpenChannel(openChanBucket,
nodeChanBucket, chanID, d.cryptoSystem)
nodeChanBucket, chanID)
if err != nil {
return err
}

@ -469,7 +469,6 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) {
// Append a sighash type of SigHashAll to the signature as it's the
// sighash type used implicitly within this type of channel for
// commitment transactions.
commitSig = append(commitSig, byte(txscript.SigHashAll))
revokeKey := fmsg.msg.RevocationKey
if err := resCtx.reservation.CompleteReservationSingle(revokeKey, fundingOut, commitSig); err != nil {
// TODO(roasbeef): better error logging: peerID, channelID, etc.
@ -521,7 +520,7 @@ func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg)
// The remote peer has responded with a signature for our commitment
// transaction. We'll verify the signature for validity, then commit
// the state to disk as we can now open the channel.
commitSig := append(fmsg.msg.CommitSignature.Serialize(), byte(txscript.SigHashAll))
commitSig := fmsg.msg.CommitSignature.Serialize()
if err := resCtx.reservation.CompleteReservation(nil, commitSig); err != nil {
fndgLog.Errorf("unable to complete reservation sign complete: %v", err)
fmsg.peer.Disconnect()

14
glide.lock generated

@ -1,5 +1,5 @@
hash: 348cab6c25a05211caed34dc711bd1cb5a51800bc87d3609127ff9271b206c42
updated: 2016-09-02T08:34:25.843272647-04:00
hash: d11bb1bf4ed6df842559ffff8fcf859509382395ba38fc29a0de546526ecbcd4
updated: 2016-09-08T12:08:13.98576317-07:00
imports:
- name: github.com/awalterschulze/gographviz
version: cafbade2d58068c3992f12afe46742195c673d2b
@ -51,7 +51,7 @@ imports:
- name: github.com/codahale/chacha20poly1305
version: f8a5c48301822c3d7dd26d78e68ea2968db0ab20
- name: github.com/davecgh/go-spew
version: 6cf5744a041a0022271cefed95ba843f6d87fd51
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
subpackages:
- spew
- name: github.com/golang/protobuf
@ -82,7 +82,7 @@ imports:
- txsort
- base58
- name: github.com/roasbeef/btcwallet
version: d7d402cc4135a53230ce068dcc51252c58a11a3d
version: 1fd2d6224698e14591d06f2a10b24e86494cc19f
subpackages:
- chain
- waddrmgr
@ -102,7 +102,7 @@ imports:
- name: github.com/urfave/cli
version: a14d7d367bc02b1f57d88de97926727f2d936387
- name: golang.org/x/crypto
version: f160b6bf95857cd862817875dd958be022e587c4
version: 9e590154d2353f3f5e1b24da7275686040dcf491
subpackages:
- hkdf
- nacl/secretbox
@ -113,7 +113,7 @@ imports:
- pbkdf2
- ssh/terminal
- name: golang.org/x/net
version: 1358eff22f0dd0c54fc521042cc607f6ff4b531a
version: 9313baa13d9262e49d07b20ed57dceafcd7240cc
subpackages:
- context
- http2
@ -122,7 +122,7 @@ imports:
- lex/httplex
- internal/timeseries
- name: golang.org/x/sys
version: a646d33e2ee3172a661fc09bca23bb4889a41bc8
version: 30de6d19a3bd89a5f38ae4028e23aaa5582648af
subpackages:
- unix
- name: google.golang.org/grpc

@ -37,6 +37,7 @@ import:
- hdkeychain
- txsort
- package: github.com/roasbeef/btcwallet
version: master
subpackages:
- chain
- waddrmgr

22
lnd.go

@ -18,6 +18,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/roasbeef/btcrpcclient"
)
@ -116,9 +117,8 @@ func lndMain() error {
return err
}
// Create, and start the lnwallet, which handles the core payment
// channel logic, and exposes control via proxy state machines.
walletConfig := &lnwallet.Config{
// TODO(roasbeef): paarse config here select chosen WalletController
walletConfig := &btcwallet.Config{
PrivatePass: []byte("hello"),
DataDir: filepath.Join(loadedConfig.DataDir, "lnwallet"),
RpcHost: fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort),
@ -127,7 +127,18 @@ func lndMain() error {
CACert: rpcCert,
NetParams: activeNetParams.Params,
}
wallet, err := lnwallet.NewLightningWallet(walletConfig, chanDB, notifier)
wc, err := btcwallet.New(walletConfig)
if err != nil {
fmt.Printf("unable to create wallet controller: %v\n", err)
return err
}
signer := wc
bio := wc
// Create, and start the lnwallet, which handles the core payment
// channel logic, and exposes control via proxy state machines.
wallet, err := lnwallet.NewLightningWallet(chanDB, notifier,
wc, signer, bio, activeNetParams.Params)
if err != nil {
fmt.Printf("unable to create wallet: %v\n", err)
return err
@ -138,9 +149,6 @@ func lndMain() error {
}
ltndLog.Info("LightningWallet opened")
ec := &lnwallet.WaddrmgrEncryptorDecryptor{wallet.Manager}
chanDB.RegisterCryptoSystem(ec)
// Set up the core server which will listen for incoming peer
// connections.
defaultListenAddrs := []string{

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

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

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

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

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

@ -274,6 +274,9 @@ type LightningChannel struct {
// wallet can add back.
lnwallet *LightningWallet
signer Signer
signDesc *SignDescriptor
channelEvents chainntnfs.ChainNotifier
sync.RWMutex
@ -281,7 +284,8 @@ type LightningChannel struct {
ourLogCounter uint32
theirLogCounter uint32
status channelState
status channelState
Capacity btcutil.Amount
// currentHeight is the current height of our local commitment chain.
// This is also the same as the number of updates to the channel we've
@ -337,10 +341,12 @@ type LightningChannel struct {
ourLogIndex map[uint32]*list.Element
theirLogIndex map[uint32]*list.Element
fundingTxIn *wire.TxIn
fundingP2WSH []byte
LocalDeliveryScript []byte
RemoteDeliveryScript []byte
channelDB *channeldb.DB
FundingRedeemScript []byte
fundingTxIn *wire.TxIn
fundingP2WSH []byte
started int32
shutdown int32
@ -354,11 +360,13 @@ type LightningChannel struct {
// and the current settled channel state. Throughout state transitions, then
// channel will automatically persist pertinent state to the database in an
// efficient manner.
func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifier,
chanDB *channeldb.DB, state *channeldb.OpenChannel) (*LightningChannel, error) {
func NewLightningChannel(signer Signer, wallet *LightningWallet,
events chainntnfs.ChainNotifier,
state *channeldb.OpenChannel) (*LightningChannel, error) {
// TODO(roasbeef): remove events+wallet
lc := &LightningChannel{
signer: signer,
lnwallet: wallet,
channelEvents: events,
currentHeight: state.NumUpdates,
@ -370,7 +378,10 @@ func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifie
theirUpdateLog: list.New(),
ourLogIndex: make(map[uint32]*list.Element),
theirLogIndex: make(map[uint32]*list.Element),
channelDB: chanDB,
Capacity: state.Capacity,
LocalDeliveryScript: state.OurDeliveryScript,
RemoteDeliveryScript: state.TheirDeliveryScript,
FundingRedeemScript: state.FundingRedeemScript,
}
// Initialize both of our chains the current un-revoked commitment for
@ -395,6 +406,17 @@ func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifie
lc.fundingTxIn = wire.NewTxIn(state.FundingOutpoint, nil, nil)
lc.fundingP2WSH = fundingPkScript
lc.signDesc = &SignDescriptor{
PubKey: lc.channelState.OurMultiSigKey,
RedeemScript: lc.channelState.FundingRedeemScript,
Output: &wire.TxOut{
PkScript: lc.fundingP2WSH,
Value: int64(lc.channelState.Capacity),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
return lc, nil
}
@ -480,12 +502,12 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
var delayBalance, p2wkhBalance btcutil.Amount
if remoteChain {
selfKey = lc.channelState.TheirCommitKey
remoteKey = lc.channelState.OurCommitKey.PubKey()
remoteKey = lc.channelState.OurCommitKey
delay = lc.channelState.RemoteCsvDelay
delayBalance = theirBalance
p2wkhBalance = ourBalance
} else {
selfKey = lc.channelState.OurCommitKey.PubKey()
selfKey = lc.channelState.OurCommitKey
remoteKey = lc.channelState.TheirCommitKey
delay = lc.channelState.LocalCsvDelay
delayBalance = ourBalance
@ -495,7 +517,7 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
// Generate a new commitment transaction with all the latest
// unsettled/un-timed out HTLC's.
ourCommitTx := !remoteChain
commitTx, err := createCommitTx(lc.fundingTxIn, selfKey, remoteKey,
commitTx, err := CreateCommitTx(lc.fundingTxIn, selfKey, remoteKey,
revocationKey, delay, delayBalance, p2wkhBalance)
if err != nil {
return nil, err
@ -722,11 +744,8 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, uint32, error) {
}))
// Sign their version of the new commitment transaction.
hashCache := txscript.NewTxSigHashes(newCommitView.txn)
sig, err := txscript.RawTxInWitnessSignature(newCommitView.txn,
hashCache, 0, int64(lc.channelState.Capacity),
lc.channelState.FundingRedeemScript, txscript.SigHashAll,
lc.channelState.OurMultiSigKey)
lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn)
sig, err := lc.signer.SignOutputRaw(newCommitView.txn, lc.signDesc)
if err != nil {
return nil, 0, err
}
@ -744,7 +763,7 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, uint32, error) {
// Strip off the sighash flag on the signature in order to send it over
// the wire.
return sig[:len(sig)], lc.theirLogCounter, nil
return sig, lc.theirLogCounter, nil
}
// ReceiveNewCommitment processs a signature for a new commitment state sent by
@ -770,7 +789,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(rawSig []byte,
if err != nil {
return err
}
revocationKey := deriveRevocationPubkey(theirCommitKey, revocation[:])
revocationKey := DeriveRevocationPubkey(theirCommitKey, revocation[:])
revocationHash := fastsha256.Sum256(revocation[:])
// With the revocation information calculated, construct the new
@ -857,7 +876,7 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.CommitRevocation,
if err != nil {
return nil, err
}
revocationMsg.NextRevocationKey = deriveRevocationPubkey(theirCommitKey,
revocationMsg.NextRevocationKey = DeriveRevocationPubkey(theirCommitKey,
revocationEdge[:])
revocationMsg.NextRevocationHash = fastsha256.Sum256(revocationEdge[:])
@ -921,8 +940,8 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.CommitRevocation) (
// Verify that the revocation public key we can derive using this
// pre-image and our private key is identical to the revocation key we
// were given for their current (prior) commitment transaction.
revocationPriv := deriveRevocationPrivKey(ourCommitKey, pendingRevocation[:])
if !revocationPriv.PubKey().IsEqual(currentRevocationKey) {
revocationPub := DeriveRevocationPubkey(ourCommitKey, pendingRevocation[:])
if !revocationPub.IsEqual(currentRevocationKey) {
return nil, fmt.Errorf("revocation key mismatch")
}
@ -1067,7 +1086,7 @@ func (lc *LightningChannel) ExtendRevocationWindow() (*lnwire.CommitRevocation,
}
theirCommitKey := lc.channelState.TheirCommitKey
revMsg.NextRevocationKey = deriveRevocationPubkey(theirCommitKey,
revMsg.NextRevocationKey = DeriveRevocationPubkey(theirCommitKey,
revocation[:])
revMsg.NextRevocationHash = fastsha256.Sum256(revocation[:])
@ -1201,7 +1220,7 @@ func (lc *LightningChannel) addHTLC(commitTx *wire.MsgTx, ourCommit bool,
paymentDesc *PaymentDescriptor, revocation [32]byte, delay uint32,
isIncoming bool) error {
localKey := lc.channelState.OurCommitKey.PubKey()
localKey := lc.channelState.OurCommitKey
remoteKey := lc.channelState.TheirCommitKey
timeout := paymentDesc.Timeout
rHash := paymentDesc.RHash
@ -1288,7 +1307,7 @@ func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error
lc.status = channelClosing
// TODO(roasbeef): assumes initiator pays fees
closeTx := createCooperativeCloseTx(lc.fundingTxIn,
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurBalance, lc.channelState.TheirBalance,
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
true)
@ -1298,11 +1317,8 @@ func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error
// initiator we'll simply send our signature over the the remote party,
// using the generated txid to be notified once the closure transaction
// has been confirmed.
hashCache := txscript.NewTxSigHashes(closeTx)
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
hashCache, 0, int64(lc.channelState.Capacity),
lc.channelState.FundingRedeemScript, txscript.SigHashAll,
lc.channelState.OurMultiSigKey)
lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx)
closeSig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, nil, err
}
@ -1315,6 +1331,9 @@ func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error
// remote node initating a cooperative channel closure. A fully signed closure
// transaction is returned. It is the duty of the responding node to broadcast
// a signed+valid closure transaction to the network.
//
// NOTE: The passed remote sig is expected to the a fully complete signature
// including the proper sighash byte.
func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.MsgTx, error) {
lc.Lock()
defer lc.Unlock()
@ -1330,35 +1349,34 @@ func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.Ms
// Create the transaction used to return the current settled balance
// on this active channel back to both parties. In this current model,
// the initiator pays full fees for the cooperative close transaction.
closeTx := createCooperativeCloseTx(lc.fundingTxIn,
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurBalance, lc.channelState.TheirBalance,
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
false)
// With the transaction created, we can finally generate our half of
// the 2-of-2 multi-sig needed to redeem the funding output.
redeemScript := lc.channelState.FundingRedeemScript
hashCache := txscript.NewTxSigHashes(closeTx)
capacity := int64(lc.channelState.Capacity)
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
hashCache, 0, capacity, redeemScript, txscript.SigHashAll,
lc.channelState.OurMultiSigKey)
lc.signDesc.SigHashes = hashCache
closeSig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, err
}
// Finally, construct the witness stack minding the order of the
// pubkeys+sigs on the stack.
ourKey := lc.channelState.OurMultiSigKey.PubKey().SerializeCompressed()
ourKey := lc.channelState.OurMultiSigKey.SerializeCompressed()
theirKey := lc.channelState.TheirMultiSigKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKey, closeSig,
ourSig := append(closeSig, byte(txscript.SigHashAll))
witness := SpendMultiSig(lc.signDesc.RedeemScript, ourKey, ourSig,
theirKey, remoteSig)
closeTx.TxIn[0].Witness = witness
// Validate the finalized transaction to ensure the output script is
// properly met, and that the remote peer supplied a valid signature.
vm, err := txscript.NewEngine(lc.fundingP2WSH, closeTx, 0,
txscript.StandardVerifyFlags, nil, hashCache, capacity)
txscript.StandardVerifyFlags, nil, hashCache,
int64(lc.channelState.Capacity))
if err != nil {
return nil, err
}
@ -1376,7 +1394,8 @@ func (lc *LightningChannel) DeleteState() error {
return lc.channelState.CloseChannel()
}
// StateSnapshot returns a snapshot b
// StateSnapshot returns a snapshot of the current fully committed state within
// the channel.
func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {
lc.stateMtx.RLock()
defer lc.stateMtx.RUnlock()
@ -1384,12 +1403,12 @@ func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {
return lc.channelState.Snapshot()
}
// createCommitTx creates a commitment transaction, spending from specified
// CreateCommitTx creates a commitment transaction, spending from specified
// funding output. The commitment transaction contains two outputs: one paying
// to the "owner" of the commitment transaction which can be spent after a
// relative block delay or revocation event, and the other paying the the
// counter-party within the channel, which can be spent immediately.
func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey,
func CreateCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey,
revokeKey *btcec.PublicKey, csvTimeout uint32, amountToSelf,
amountToThem btcutil.Amount) (*wire.MsgTx, error) {
@ -1433,13 +1452,13 @@ func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey
return commitTx, nil
}
// createCooperativeCloseTx creates a transaction which if signed by both
// CreateCooperativeCloseTx creates a transaction which if signed by both
// parties, then broadcast cooperatively closes an active channel. The creation
// of the closure transaction is modified by a boolean indicating if the party
// constructing the channel is the initiator of the closure. Currently it is
// expected that the initiator pays the transaction fees for the closing
// transaction in full.
func createCooperativeCloseTx(fundingTxIn *wire.TxIn,
func CreateCooperativeCloseTx(fundingTxIn *wire.TxIn,
ourBalance, theirBalance btcutil.Amount,
ourDeliveryScript, theirDeliveryScript []byte,
initiator bool) *wire.MsgTx {

@ -13,27 +13,64 @@ import (
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
// MockEncryptorDecryptor is a mock implementation of EncryptorDecryptor that
// simply returns the passed bytes without encrypting or decrypting. This is
// used for testing purposes to be able to create a channldb instance which
// doesn't use encryption.
type MockEncryptorDecryptor struct {
var (
privPass = []byte("private-test")
// For simplicity a single priv key controls all of our test outputs.
testWalletPrivKey = []byte{
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
}
// We're alice :)
bobsPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
// Use a hard-coded HD seed.
testHdSeed = [32]byte{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
// The number of confirmations required to consider any created channel
// open.
numReqConfs = uint16(1)
)
type mockSigner struct {
key *btcec.PrivateKey
}
func (m *MockEncryptorDecryptor) Encrypt(n []byte) ([]byte, error) {
return n, nil
func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) {
amt := signDesc.Output.Value
redeemScript := signDesc.RedeemScript
privKey := m.key
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, amt, redeemScript, txscript.SigHashAll, privKey)
if err != nil {
return nil, err
}
return sig[:len(sig)-1], nil
}
func (m *MockEncryptorDecryptor) Decrypt(n []byte) ([]byte, error) {
return n, nil
}
func (m *MockEncryptorDecryptor) OverheadSize() uint32 {
return 0
// ComputeInputScript...
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) {
return nil, nil
}
// createTestChannels creates two test channels funded with 10 BTC, with 5 BTC
@ -49,7 +86,7 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
redeemScript, _, err := genFundingPkScript(aliceKeyPub.SerializeCompressed(),
redeemScript, _, err := GenFundingPkScript(aliceKeyPub.SerializeCompressed(),
bobKeyPub.SerializeCompressed(), int64(channelCapacity))
if err != nil {
return nil, nil, nil, err
@ -61,26 +98,26 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, aliceKeyPub))
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, bobKeyPub, aliceKeyPub))
bobFirstRevoke, err := bobElkrem.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
bobRevokeKey := deriveRevocationPubkey(aliceKeyPub, bobFirstRevoke[:])
bobRevokeKey := DeriveRevocationPubkey(aliceKeyPub, bobFirstRevoke[:])
aliceElkrem := elkrem.NewElkremSender(deriveElkremRoot(aliceKeyPriv, bobKeyPub))
aliceElkrem := elkrem.NewElkremSender(deriveElkremRoot(aliceKeyPriv, aliceKeyPub, bobKeyPub))
aliceFirstRevoke, err := aliceElkrem.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
aliceRevokeKey := deriveRevocationPubkey(bobKeyPub, aliceFirstRevoke[:])
aliceRevokeKey := DeriveRevocationPubkey(bobKeyPub, aliceFirstRevoke[:])
aliceCommitTx, err := createCommitTx(fundingTxIn, aliceKeyPub,
aliceCommitTx, err := CreateCommitTx(fundingTxIn, aliceKeyPub,
bobKeyPub, aliceRevokeKey, csvTimeoutAlice, channelBal, channelBal)
if err != nil {
return nil, nil, nil, err
}
bobCommitTx, err := createCommitTx(fundingTxIn, bobKeyPub,
bobCommitTx, err := CreateCommitTx(fundingTxIn, bobKeyPub,
aliceKeyPub, bobRevokeKey, csvTimeoutBob, channelBal, channelBal)
if err != nil {
return nil, nil, nil, err
@ -91,25 +128,24 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
if err != nil {
return nil, nil, nil, err
}
dbAlice.RegisterCryptoSystem(&MockEncryptorDecryptor{})
bobPath, err := ioutil.TempDir("", "bobdb")
dbBob, err := channeldb.Open(bobPath, &chaincfg.TestNet3Params)
if err != nil {
return nil, nil, nil, err
}
dbBob.RegisterCryptoSystem(&MockEncryptorDecryptor{})
aliceChannelState := &channeldb.OpenChannel{
TheirLNID: testHdSeed,
ChanID: prevOut,
OurCommitKey: aliceKeyPriv,
OurCommitKey: aliceKeyPub,
TheirCommitKey: bobKeyPub,
Capacity: channelCapacity,
OurBalance: channelBal,
TheirBalance: channelBal,
OurCommitTx: aliceCommitTx,
FundingOutpoint: prevOut,
OurMultiSigKey: aliceKeyPriv,
OurMultiSigKey: aliceKeyPub,
TheirMultiSigKey: bobKeyPub,
FundingRedeemScript: redeemScript,
LocalCsvDelay: csvTimeoutAlice,
@ -122,14 +158,14 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
bobChannelState := &channeldb.OpenChannel{
TheirLNID: testHdSeed,
ChanID: prevOut,
OurCommitKey: bobKeyPriv,
OurCommitKey: bobKeyPub,
TheirCommitKey: aliceKeyPub,
Capacity: channelCapacity,
OurBalance: channelBal,
TheirBalance: channelBal,
OurCommitTx: bobCommitTx,
FundingOutpoint: prevOut,
OurMultiSigKey: bobKeyPriv,
OurMultiSigKey: bobKeyPub,
TheirMultiSigKey: aliceKeyPub,
FundingRedeemScript: redeemScript,
LocalCsvDelay: csvTimeoutBob,
@ -145,11 +181,14 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
os.RemoveAll(alicePath)
}
channelAlice, err := NewLightningChannel(nil, nil, dbAlice, aliceChannelState)
aliceSigner := &mockSigner{aliceKeyPriv}
bobSigner := &mockSigner{bobKeyPriv}
channelAlice, err := NewLightningChannel(aliceSigner, nil, nil, aliceChannelState)
if err != nil {
return nil, nil, nil, err
}
channelBob, err := NewLightningChannel(nil, nil, dbBob, bobChannelState)
channelBob, err := NewLightningChannel(bobSigner, nil, nil, bobChannelState)
if err != nil {
return nil, nil, nil, err
}
@ -456,7 +495,8 @@ func TestCooperativeChannelClosure(t *testing.T) {
if err != nil {
t.Fatalf("unable to initiate alice cooperative close: %v", err)
}
closeTx, err := bobChannel.CompleteCooperativeClose(sig)
finalSig := append(sig, byte(txscript.SigHashAll))
closeTx, err := bobChannel.CompleteCooperativeClose(finalSig)
if err != nil {
t.Fatalf("unable to complete alice cooperative close: %v", err)
}
@ -475,7 +515,8 @@ func TestCooperativeChannelClosure(t *testing.T) {
if err != nil {
t.Fatalf("unable to initiate bob cooperative close: %v", err)
}
closeTx, err = aliceChannel.CompleteCooperativeClose(sig)
finalSig = append(sig, byte(txscript.SigHashAll))
closeTx, err = aliceChannel.CompleteCooperativeClose(finalSig)
if err != nil {
t.Fatalf("unable to complete bob cooperative close: %v", err)
}

@ -1,73 +0,0 @@
package lnwallet
import (
"encoding/hex"
"github.com/roasbeef/btcd/btcjson"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcutil/coinset"
)
// lnCoin represents a single unspet output. Its purpose is to convert a regular
// output to a struct adhering to the coinset.Coin interface
type lnCoin struct {
hash *wire.ShaHash
index uint32
value btcutil.Amount
pkScript []byte
numConfs int64
valueAge int64
}
func (l *lnCoin) Hash() *wire.ShaHash { return l.hash }
func (l *lnCoin) Index() uint32 { return l.index }
func (l *lnCoin) Value() btcutil.Amount { return l.value }
func (l *lnCoin) PkScript() []byte { return l.pkScript }
func (l *lnCoin) NumConfs() int64 { return l.numConfs }
func (l *lnCoin) ValueAge() int64 { return l.valueAge }
// Ensure lnCoin adheres to the coinset.Coin interface.
var _ coinset.Coin = (*lnCoin)(nil)
// newLnCoin creates a new "coin" from the passed output. Coins are required
// in order to perform coin selection upon.
func newLnCoin(output *btcjson.ListUnspentResult) (coinset.Coin, error) {
txid, err := wire.NewShaHashFromStr(output.TxID)
if err != nil {
return nil, err
}
pkScript, err := hex.DecodeString(output.ScriptPubKey)
if err != nil {
return nil, err
}
return &lnCoin{
hash: txid,
// btcjson.ListUnspentResult shows the amount in BTC,
// translate into Satoshi so coin selection can work properly.
value: btcutil.Amount(output.Amount * 1e8),
index: output.Vout,
pkScript: pkScript,
numConfs: output.Confirmations,
// TODO(roasbeef): output.Amount should be a int64, damn json-RPC :/
valueAge: output.Confirmations * int64(output.Amount),
}, nil
}
// outputsToCoins converts a slice of transaction outputs to a coin-selectable
// slice of "Coins"s.
func outputsToCoins(outputs []*btcjson.ListUnspentResult) ([]coinset.Coin, error) {
coins := make([]coinset.Coin, len(outputs))
for i, output := range outputs {
coin, err := newLnCoin(output)
if err != nil {
return nil, err
}
coins[i] = coin
}
return coins, 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
//
}

@ -1,11 +1,43 @@
package lnwallet
import (
"errors"
"fmt"
"sync"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
// ErrNotMine is an error denoting that a WalletController instance is unable
// to spend a specifid output.
var ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
// AddressType is a enum-like type which denotes the possible address types
// WalletController supports.
type AddressType uint8
const (
// WitnessPubKey represents a p2wkh address.
WitnessPubKey AddressType = iota
// NestedWitnessPubKey represents a p2sh output which is itself a
// nested p2wkh output.
NestedWitnessPubKey
// PublicKey represents a regular p2pkh output.
PubKeyHash
)
// Utxo is an unspent output denoted by its outpoint, and output value of the
// original output.
type Utxo struct {
Value btcutil.Amount
wire.OutPoint
}
// WalletController defines an abstract interface for controlling a local Pure
// Go wallet, a local or remote wallet via an RPC mechanism, or possibly even
// a daemon assisted hardware wallet. This interface serves the purpose of
@ -17,82 +49,71 @@ import (
// behavior of all interface methods in order to ensure identical behavior
// across all concrete implementations.
type WalletController interface {
// 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.
FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error)
// 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.
ConfirmedBalance(confs int32) btcutil.Amount
ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error)
// NewAddress returns the next external address for the wallet. The
// type of address returned is dictated by the wallet's capabilities,
// and may be of type: p2sh, p2pkh, p2wkh, p2wsh, etc.
NewAddress(witness bool) (btcutil.Address, error)
// NewChangeAddress returns a new change address for the wallet. If the
// underlying wallet supports hd key chains, then this address should be
// dervied from an internal branch.
NewChangeAddress(witness bool) (btcutil.Address, error)
// 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 should be used, otherwise an external
// address should be returned. The type of address returned is dictated
// by the wallet's capabilities, and may be of type: p2sh, p2pkh,
// p2wkh, p2wsh, etc.
NewAddress(addrType AddressType, change bool) (btcutil.Address, error)
// GetPrivKey retrives the underlying private key associated with the
// passed address. If the wallet is unable to locate this private key
// due to the address not being under control of the wallet, then an
// error should be returned.
GetPrivKey(a *btcutil.Address) (*btcec.PrivateKey, error)
// TODO(roasbeef): should instead take tadge's derivation scheme in
GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error)
// NewRawKey returns a raw private key controlled by the wallet. These
// keys are used for the 2-of-2 multi-sig outputs for funding
// transactions, as well as the pub key used for commitment transactions.
// TODO(roasbeef): key pool due to cancelled reservations??
NewRawKey() (*btcec.PrivateKey, error)
// TODO(roasbeef): may be scrapped, see above TODO
NewRawKey() (*btcec.PublicKey, error)
// FetchIdentityKey returns a private key which will be utilized as the
// wallet's Lightning Network identity for authentication purposes.
// TODO(roasbeef): rotate identity key?
FetchIdentityKey() (*btcec.PrivateKey, error)
// FetchRootKey returns a root key which will be used by the
// LightningWallet to deterministically generate secrets. The private
// key returned by this method should remain constant in-between
// WalletController restarts.
FetchRootKey() (*btcec.PrivateKey, error)
// FundTransaction creates a new unsigned transactions paying to the
// passed outputs, possibly using the specified change address. The
// includeFee parameter dictates if the wallet should also provide
// enough the funds necessary to create an adequate fee or not.
FundTransaction(outputs []*wire.TxOut, changeAddr btcutil.Address,
includeFee bool) (*wire.MsgTx, error)
// SignTransaction performs potentially a sparse, or full signing of
// all inputs within the passed transaction that are spendable by the
// wallet.
SignTransaction(tx *wire.MsgTx) error
// BroadcastTransaction performs cursory validation (dust checks, etc),
// then finally broadcasts the passed transaction to the Bitcoin network.
BroadcastTransaction(tx *wire.MsgTx) error
// SendMany 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, and error should be returned.
SendMany(outputs []*wire.TxOut) (*wire.ShaHash, error)
// 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, and error
// should be returned.
SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error)
// ListUnspentWitness returns all unspent outputs which are version 0
// witness programs. The 'confirms' parameter indicates the minimum
// number of confirmations an output needs in order to be returned by
// this method. Passing -1 as 'confirms' indicates that even unconfirmed
// outputs should be returned.
ListUnspentWitness(confirms int32) ([]*wire.OutPoint, error)
// this method. Passing -1 as 'confirms' indicates that even
// unconfirmed outputs should be returned.
ListUnspentWitness(confirms int32) ([]*Utxo, error)
// LockOutpoint marks an outpoint as locked meaning it will no longer
// be deemed as eligble for coin selection. Locking outputs are utilized
// in order to avoid race conditions when selecting inputs for usage when
// funding a channel.
// 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.
LockOutpoint(o wire.OutPoint)
// UnlockOutpoint unlocks an previously locked output, marking it
// eligible for coin seleciton.
UnlockOutpoint(o wire.OutPoint)
// ImportScript imports the serialize public key script, or redeem
// script into the wallet's database. Scripts to be imported include
// the 2-of-2 script for funding transactions, commitment scripts,
// HTLCs scripts, and so on.
ImportScript(b []byte) error
// PublishTransaction performs cursory validation (dust checks, etc),
// then finally broadcasts the passed transaction to the Bitcoin network.
PublishTransaction(tx *wire.MsgTx) error
// Start initializes the wallet, making any neccessary connections,
// starting up required goroutines etc.
@ -101,11 +122,147 @@ type WalletController interface {
// Stop signals the wallet for shutdown. Shutdown may entail closing
// any active sockets, database handles, stopping goroutines, etc.
Stop() error
// WaitForShutdown blocks until the wallet finishes the shutdown
// procedure triggered by a prior call to Stop().
WaitForShutdown() error
// TODO(roasbeef): ImportPriv?
// * segwitty flag?
}
// BlockChainIO is a dedicated source which will be used to obtain queries
// related to the current state of the blockchain. The data returned by each of
// the defined methods within this interface should always return the most up
// to date data possible.
//
// TODO(roasbeef): move to diff package perhaps?
type BlockChainIO interface {
// GetCurrentHeight returns the current height of the valid most-work
// chain the implementation is aware of.
GetCurrentHeight() (int32, error)
// GetTxOut returns the original output referenced by the passed
// outpoint.
GetUtxo(txid *wire.ShaHash, index uint32) (*wire.TxOut, error)
// GetTransaction returns the full transaction identified by the passed
// transaction ID.
GetTransaction(txid *wire.ShaHash) (*wire.MsgTx, error)
}
// SignDescriptor houses the necessary information required to succesfully sign
// a given output. This struct is used by the Signer interface in order to gain
// access to critial data needed to generate a valid signature.
type SignDescriptor struct {
// Pubkey is the public key to which the signature should be generated
// over. The Signer should then generate a signature with the private
// key corresponding to this public key.
PubKey *btcec.PublicKey
// RedeemScript is the full script required to properly redeem the
// output. This field will only be populated if a p2wsh or a p2sh
// output is being signed.
RedeemScript []byte
// Output is the target output which should be signed. The PkScript and
// Value fields within the output should be properly populated,
// otherwise an invalid signature may be generated.
Output *wire.TxOut
// HashType is the target sighash type that should be used when
// generating the final sighash, and signature.
HashType txscript.SigHashType
// SigHashes is the pre-computed sighash midstate to be used when
// generating the final sighash for signing.
SigHashes *txscript.TxSigHashes
// InputIndex is the target input within the transaction that should be
// signed.
InputIndex int
}
// Signer represents an abstract object capable of generating raw signatures as
// well as full complete input scripts given a valid SignDescriptor and
// transaction. This interface fully abstracts away signing paving the way for
// Signer implementations such as hardware wallets, hardware tokens, HSM's, or
// simply a regular wallet.
type Signer interface {
// SignOutputRaw generates a signature for the passed transaction
// according to the data within the passed SignDescriptor.
//
// NOTE: The resulting signature should be void of a sighash byte.
SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error)
// ComputeInputScript generates a complete InputIndex for the passed
// transaction with the signature as defined within the passed
// SignDescriptor. This method should be capable of generating the
// proper input script for both regular p2wkh output and p2wkh outputs
// nested within a regualr p2sh output.
ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error)
}
// WalletDriver represents a "driver" for a particular concrete
// WalletController implementation. A driver is indentified by a globally
// unique string identifier along with a 'New()' method which is responsible
// for initializing a particular WalletController concrete implementation.
type WalletDriver struct {
// WalletType is a string which uniquely identifes the WalletController
// that this driver, drives.
WalletType string
// New creates a new instance of a concrete WalletController
// implementation given a variadic set up arguments. The function takes
// a varidaic number of interface paramters in order to provide
// initialization flexibility, thereby accomodating several potential
// WalletController implementations.
New func(args ...interface{}) (WalletController, error)
}
var (
wallets = make(map[string]*WalletDriver)
registerMtx sync.Mutex
)
// RegisteredWallets returns a slice of all currently registered notifiers.
//
// NOTE: This function is safe for concurrent access.
func RegisteredWallets() []*WalletDriver {
registerMtx.Lock()
defer registerMtx.Unlock()
registeredWallets := make([]*WalletDriver, 0, len(wallets))
for _, wallet := range wallets {
registeredWallets = append(registeredWallets, wallet)
}
return registeredWallets
}
// RegisterWallet registers a WalletDriver which is capable of driving a
// concrete WalletController interface. In the case that this driver has
// already been registered, an error is returned.
//
// NOTE: This function is safe for concurrent access.
func RegisterWallet(driver *WalletDriver) error {
registerMtx.Lock()
defer registerMtx.Unlock()
if _, ok := wallets[driver.WalletType]; ok {
return fmt.Errorf("wallet already registered")
}
wallets[driver.WalletType] = driver
return nil
}
// SupportedWallets returns a slice of strings that represents the walelt
// drivers that have been registered and are therefore supported.
//
// NOTE: This function is safe for concurrent access.
func SupportedWallets() []string {
registerMtx.Lock()
defer registerMtx.Unlock()
supportedWallets := make([]string, 0, len(wallets))
for walletName := range wallets {
supportedWallets = append(supportedWallets, walletName)
}
return supportedWallets
}

@ -1,4 +1,4 @@
package lnwallet
package lnwallet_test
import (
"bytes"
@ -11,18 +11,20 @@ import (
"time"
"github.com/boltdb/bolt"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcutil/txsort"
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/rpctest"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcutil/coinset"
"github.com/roasbeef/btcwallet/waddrmgr"
)
var (
@ -60,8 +62,8 @@ var (
// assertProperBalance asserts than the total value of the unspent outputs
// within the wallet are *exactly* amount. If unable to retrieve the current
// balance, or the assertion fails, the test will halt with a fatal error.
func assertProperBalance(t *testing.T, lw *LightningWallet, numConfirms int32, amount int64) {
balance, err := lw.CalculateBalance(numConfirms)
func assertProperBalance(t *testing.T, lw *lnwallet.LightningWallet, numConfirms int32, amount int64) {
balance, err := lw.ConfirmedBalance(numConfirms, false)
if err != nil {
t.Fatalf("unable to query for balance: %v", err)
}
@ -72,7 +74,7 @@ func assertProperBalance(t *testing.T, lw *LightningWallet, numConfirms int32, a
}
func assertChannelOpen(t *testing.T, miner *rpctest.Harness, numConfs uint32,
c <-chan *LightningChannel) *LightningChannel {
c <-chan *lnwallet.LightningChannel) *lnwallet.LightningChannel {
// Mine a single block. After this block is mined, the channel should
// be considered fully open.
if _, err := miner.Node.Generate(1); err != nil {
@ -108,9 +110,9 @@ type bobNode struct {
// Contribution returns bobNode's contribution necessary to open a payment
// channel with Alice.
func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *ChannelContribution {
revokeKey := deriveRevocationPubkey(aliceCommitKey, b.revocation[:])
return &ChannelContribution{
func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *lnwallet.ChannelContribution {
revokeKey := lnwallet.DeriveRevocationPubkey(aliceCommitKey, b.revocation[:])
return &lnwallet.ChannelContribution{
FundingAmount: b.fundingAmt,
Inputs: b.availableOutputs,
ChangeOutputs: b.changeOutputs,
@ -124,9 +126,9 @@ func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *ChannelContribu
// SingleContribution returns bobNode's contribution to a single funded
// channel. This contribution contains no inputs nor change outputs.
func (b *bobNode) SingleContribution(aliceCommitKey *btcec.PublicKey) *ChannelContribution {
revokeKey := deriveRevocationPubkey(aliceCommitKey, b.revocation[:])
return &ChannelContribution{
func (b *bobNode) SingleContribution(aliceCommitKey *btcec.PublicKey) *lnwallet.ChannelContribution {
revokeKey := lnwallet.DeriveRevocationPubkey(aliceCommitKey, b.revocation[:])
return &lnwallet.ChannelContribution{
FundingAmount: b.fundingAmt,
MultiSigKey: b.channelKey,
CommitKey: b.channelKey,
@ -139,8 +141,8 @@ func (b *bobNode) SingleContribution(aliceCommitKey *btcec.PublicKey) *ChannelCo
// signFundingTx generates signatures for all the inputs in the funding tx
// belonging to Bob.
// NOTE: This generates the full witness stack.
func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*InputScript, error) {
bobInputScripts := make([]*InputScript, 0, len(b.availableOutputs))
func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*lnwallet.InputScript, error) {
bobInputScripts := make([]*lnwallet.InputScript, 0, len(b.availableOutputs))
bobPkScript := b.changeOutputs[0].PkScript
inputValue := int64(7e8)
@ -158,7 +160,7 @@ func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*InputScript, error) {
return nil, err
}
inputScript := &InputScript{Witness: witness}
inputScript := &lnwallet.InputScript{Witness: witness}
bobInputScripts = append(bobInputScripts, inputScript)
}
@ -217,7 +219,7 @@ func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) {
if err != nil {
return nil, err
}
found, index := findScriptOutputIndex(tx.MsgTx(), bobAddrScript)
found, index := lnwallet.FindScriptOutputIndex(tx.MsgTx(), bobAddrScript)
if !found {
return nil, fmt.Errorf("output to bob never created")
}
@ -251,14 +253,14 @@ func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) {
}, nil
}
func loadTestCredits(miner *rpctest.Harness, w *LightningWallet, numOutputs, btcPerOutput int) error {
func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet, numOutputs, btcPerOutput int) error {
// Using the mining node, spend from a coinbase output numOutputs to
// give us btcPerOutput with each output.
satoshiPerOutput := int64(btcPerOutput * 1e8)
addrs := make([]btcutil.Address, 0, numOutputs)
for i := 0; i < numOutputs; i++ {
// Grab a fresh address from the wallet to house this output.
walletAddr, err := w.NewAddress(waddrmgr.DefaultAccountNum, waddrmgr.WitnessPubKey)
walletAddr, err := w.NewAddress(lnwallet.WitnessPubKey, false)
if err != nil {
return err
}
@ -285,87 +287,59 @@ func loadTestCredits(miner *rpctest.Harness, w *LightningWallet, numOutputs, btc
return err
}
_, bestHeight, err := miner.Node.GetBestBlock()
if err != nil {
return err
}
// Wait until the wallet has finished syncing up to the main chain.
ticker := time.NewTicker(100 * time.Millisecond)
expectedBalance := btcutil.Amount(satoshiPerOutput * int64(numOutputs))
out:
for {
select {
case <-ticker.C:
if w.Manager.SyncedTo().Height == bestHeight {
balance, err := w.ConfirmedBalance(1, false)
if err != nil {
return err
}
if balance == expectedBalance {
break out
}
}
}
ticker.Stop()
// Trigger a re-scan to ensure the wallet knows of the newly created
// outputs it can spend.
if err := w.Rescan(addrs, nil); err != nil {
return err
}
return nil
}
// createTestWallet creates a test LightningWallet will a total of 20BTC
// available for funding channels.
func createTestWallet(miningNode *rpctest.Harness, netParams *chaincfg.Params) (string, *LightningWallet, error) {
privPass := []byte("private-test")
tempTestDir, err := ioutil.TempDir("", "lnwallet")
if err != nil {
return "", nil, nil
}
rpcConfig := miningNode.RPCConfig()
config := &Config{
PrivatePass: privPass,
HdSeed: testHdSeed[:],
DataDir: tempTestDir,
NetParams: netParams,
RpcHost: rpcConfig.Host,
RpcUser: rpcConfig.User,
RpcPass: rpcConfig.Pass,
CACert: rpcConfig.Certificates,
}
func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
netParams *chaincfg.Params, notifier chainntnfs.ChainNotifier,
wc lnwallet.WalletController, signer lnwallet.Signer,
bio lnwallet.BlockChainIO) (*lnwallet.LightningWallet, error) {
dbDir := filepath.Join(tempTestDir, "cdb")
cdb, err := channeldb.Open(dbDir, &chaincfg.SegNet4Params)
if err != nil {
return "", nil, err
return nil, err
}
chainNotifier, err := btcdnotify.New(&rpcConfig)
wallet, err := lnwallet.NewLightningWallet(cdb, notifier, wc, signer,
bio, netParams)
if err != nil {
return "", nil, err
}
if err := chainNotifier.Start(); err != nil {
return "", nil, err
return nil, err
}
wallet, err := NewLightningWallet(config, cdb, chainNotifier)
if err != nil {
return "", nil, err
}
if err := wallet.Startup(); err != nil {
return "", nil, err
return nil, err
}
cdb.RegisterCryptoSystem(&WaddrmgrEncryptorDecryptor{wallet.Manager})
// Load our test wallet with 10 outputs each holding 4BTC.
if err := loadTestCredits(miningNode, wallet, 10, 4); err != nil {
return "", nil, err
// Load our test wallet with 20 outputs each holding 4BTC.
if err := loadTestCredits(miningNode, wallet, 20, 4); err != nil {
return nil, err
}
return tempTestDir, wallet, nil
return wallet, nil
}
func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) {
// Create the bob-test wallet which will be the other side of our funding
// channel.
fundingAmount := btcutil.Amount(5 * 1e8)
@ -376,7 +350,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
// Bob initiates a channel funded with 5 BTC for each side, so 10
// BTC total. He also generates 2 BTC in change.
chanReservation, err := lnwallet.InitChannelReservation(fundingAmount*2,
chanReservation, err := wallet.InitChannelReservation(fundingAmount*2,
fundingAmount, bobNode.id, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
@ -426,10 +400,13 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
if ourContribution.RevocationKey == nil {
t.Fatalf("alice's revocation key not found")
}
// Additionally, the funding tx should have been populated.
if chanReservation.fundingTx == nil {
fundingTx := chanReservation.FinalFundingTx()
if fundingTx == nil {
t.Fatalf("funding transaction never created!")
}
// Their funds should also be filled in.
if len(theirContribution.Inputs) != 1 {
t.Fatalf("bob's outputs for funding tx not properly selected, have %v "+
@ -455,13 +432,13 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
// Alice responds with her output, change addr, multi-sig key and signatures.
// Bob then responds with his signatures.
bobsSigs, err := bobNode.signFundingTx(chanReservation.fundingTx)
bobsSigs, err := bobNode.signFundingTx(fundingTx)
if err != nil {
t.Fatalf("unable to sign inputs for bob: %v", err)
}
commitSig, err := bobNode.signCommitTx(
chanReservation.partialState.OurCommitTx,
chanReservation.partialState.FundingRedeemScript,
chanReservation.LocalCommitTx(),
chanReservation.FundingRedeemScript(),
10e8)
if err != nil {
t.Fatalf("bob is unable to sign alice's commit tx: %v", err)
@ -474,10 +451,9 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
// txn hits a "comfortable" depth.
// The resulting active channel state should have been persisted to the DB.
fundingTx := chanReservation.FinalFundingTx()
fundingSha := fundingTx.TxSha()
nodeID := wire.ShaHash(bobNode.id)
channels, err := lnwallet.channelDB.FetchOpenChannels(&nodeID)
channels, err := wallet.ChannelDB.FetchOpenChannels(&nodeID)
if err != nil {
t.Fatalf("unable to retrieve channel from DB: %v", err)
}
@ -495,46 +471,49 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
if err != nil {
t.Fatalf("unable to init cooperative closure: %v", err)
}
aliceCloseSig = append(aliceCloseSig, byte(txscript.SigHashAll))
chanInfo := lnc.StateSnapshot()
// Obtain bob's signature for the closure transaction.
redeemScript := lnc.channelState.FundingRedeemScript
redeemScript := lnc.FundingRedeemScript
fundingOut := lnc.ChannelPoint()
fundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
bobCloseTx := createCooperativeCloseTx(fundingTxIn,
lnc.channelState.TheirBalance, lnc.channelState.OurBalance,
lnc.channelState.TheirDeliveryScript, lnc.channelState.OurDeliveryScript,
bobCloseTx := lnwallet.CreateCooperativeCloseTx(fundingTxIn,
chanInfo.RemoteBalance, chanInfo.LocalBalance,
lnc.RemoteDeliveryScript, lnc.LocalDeliveryScript,
false)
bobSig, err := bobNode.signCommitTx(bobCloseTx,
redeemScript,
int64(lnc.channelState.Capacity))
bobSig, err := bobNode.signCommitTx(bobCloseTx, redeemScript, int64(lnc.Capacity))
if err != nil {
t.Fatalf("unable to generate bob's signature for closing tx: %v", err)
}
// Broadcast the transaction to the network. This transaction should
// be accepted, and found in the next mined block.
ourKey := lnc.channelState.OurMultiSigKey.PubKey().SerializeCompressed()
theirKey := lnc.channelState.TheirMultiSigKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKey, aliceCloseSig,
ourKey := chanReservation.OurContribution().MultiSigKey.SerializeCompressed()
theirKey := chanReservation.TheirContribution().MultiSigKey.SerializeCompressed()
witness := lnwallet.SpendMultiSig(redeemScript, ourKey, aliceCloseSig,
theirKey, bobSig)
bobCloseTx.TxIn[0].Witness = witness
if err := lnwallet.PublishTransaction(bobCloseTx); err != nil {
if err := wallet.PublishTransaction(bobCloseTx); err != nil {
t.Fatalf("broadcast of close tx rejected: %v", err)
}
}
func testFundingTransactionLockedOutputs(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
wallet *lnwallet.LightningWallet, t *testing.T) {
// Create two channels, both asking for 8 BTC each, totalling 16
// BTC.
// TODO(roasbeef): tests for concurrent funding.
// * also func for below
fundingAmount := btcutil.Amount(8 * 1e8)
chanReservation1, err := lnwallet.InitChannelReservation(fundingAmount,
chanReservation1, err := wallet.InitChannelReservation(fundingAmount,
fundingAmount, testHdSeed, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation 1: %v", err)
}
chanReservation2, err := lnwallet.InitChannelReservation(fundingAmount,
chanReservation2, err := wallet.InitChannelReservation(fundingAmount,
fundingAmount, testHdSeed, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation 2: %v", err)
@ -563,12 +542,12 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, lnwallet *Light
// 90 BTC. We only have around 24BTC worth of outpoints that aren't locked, so
// this should fail.
amt := btcutil.Amount(90 * 1e8)
failedReservation, err := lnwallet.InitChannelReservation(amt, amt,
failedReservation, err := wallet.InitChannelReservation(amt, amt,
testHdSeed, numReqConfs, 4)
if err == nil {
t.Fatalf("not error returned, should fail on coin selection")
}
if err != coinset.ErrCoinsNoSelectionAvailable {
if err != lnwallet.ErrInsufficientFunds {
t.Fatalf("error not coinselect error: %v", err)
}
if failedReservation != nil {
@ -576,28 +555,30 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, lnwallet *Light
}
}
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
// Create a reservation for 22 BTC.
fundingAmount := btcutil.Amount(22 * 1e8)
chanReservation, err := lnwallet.InitChannelReservation(fundingAmount,
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
wallet *lnwallet.LightningWallet, t *testing.T) {
// Create a reservation for 44 BTC.
fundingAmount := btcutil.Amount(44 * 1e8)
chanReservation, err := wallet.InitChannelReservation(fundingAmount,
fundingAmount, testHdSeed, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
}
// There should be three locked outpoints.
lockedOutPoints := lnwallet.LockedOutpoints()
if len(lockedOutPoints) != 6 {
// There should be 12 locked outpoints.
lockedOutPoints := wallet.LockedOutpoints()
if len(lockedOutPoints) != 12 {
t.Fatalf("two outpoints should now be locked, instead %v are",
len(lockedOutPoints))
}
// Attempt to create another channel with 22 BTC, this should fail.
failedReservation, err := lnwallet.InitChannelReservation(fundingAmount,
// Attempt to create another channel with 44 BTC, this should fail.
_, err = wallet.InitChannelReservation(fundingAmount,
fundingAmount, testHdSeed, numReqConfs, 4)
if err != coinset.ErrCoinsNoSelectionAvailable {
t.Fatalf("coin selection succeded should have insufficient funds: %+v",
failedReservation)
if err != lnwallet.ErrInsufficientFunds {
t.Fatalf("coin selection succeded should have insufficient funds: %v",
err)
}
// Now cancel that old reservation.
@ -606,15 +587,16 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *Lig
}
// Those outpoints should no longer be locked.
lockedOutPoints = lnwallet.LockedOutpoints()
lockedOutPoints = wallet.LockedOutpoints()
if len(lockedOutPoints) != 0 {
t.Fatalf("outpoints still locked")
}
// Reservation ID should now longer be tracked.
_, ok := lnwallet.fundingLimbo[chanReservation.reservationID]
if ok {
t.Fatalf("funding reservation still in map")
// Reservation ID should no longer be tracked.
numReservations := wallet.ActiveReservations()
if len(wallet.ActiveReservations()) != 0 {
t.Fatalf("should have 0 reservations, instead have %v",
numReservations)
}
// TODO(roasbeef): create method like Balance that ignores locked
@ -622,16 +604,18 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *Lig
// attempting coin selection.
// Request to fund a new channel should now succeeed.
_, err = lnwallet.InitChannelReservation(fundingAmount, fundingAmount,
_, err = wallet.InitChannelReservation(fundingAmount, fundingAmount,
testHdSeed, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
}
}
func testCancelNonExistantReservation(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testCancelNonExistantReservation(miner *rpctest.Harness,
wallet *lnwallet.LightningWallet, t *testing.T) {
// Create our own reservation, give it some ID.
res := newChannelReservation(1000, 1000, 5000, lnwallet, 22, numReqConfs)
res := lnwallet.NewChannelReservation(1000, 1000, 5000, wallet, 22, numReqConfs)
// Attempt to cancel this reservation. This should fail, we know
// nothing of it.
@ -640,7 +624,9 @@ func testCancelNonExistantReservation(miner *rpctest.Harness, lnwallet *Lightnin
}
}
func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness,
lnwallet *lnwallet.LightningWallet, t *testing.T) {
// For this scenario, we (lnwallet) will be the channel initiator while bob
// will be the recipient.
@ -702,7 +688,7 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
t.Fatalf("commitment sig not found")
}
// Additionally, the funding tx should have been populated.
if chanReservation.fundingTx == nil {
if chanReservation.FinalFundingTx() == nil {
t.Fatalf("funding transaction never created!")
}
// Their funds should also be filled in.
@ -737,8 +723,8 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
// Now Bob can generate a signature for our version of the commitment
// transaction, allowing us to complete the reservation.
bobCommitSig, err := bobNode.signCommitTx(
chanReservation.partialState.OurCommitTx,
chanReservation.partialState.FundingRedeemScript,
chanReservation.LocalCommitTx(),
chanReservation.FundingRedeemScript(),
int64(fundingAmt))
if err != nil {
t.Fatalf("bob is unable to sign alice's commit tx: %v", err)
@ -755,7 +741,7 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
fundingTx := chanReservation.FinalFundingTx()
fundingSha := fundingTx.TxSha()
nodeID := wire.ShaHash(bobNode.id)
channels, err := lnwallet.channelDB.FetchOpenChannels(&nodeID)
channels, err := lnwallet.ChannelDB.FetchOpenChannels(&nodeID)
if err != nil {
t.Fatalf("unable to retrieve channel from DB: %v", err)
}
@ -768,7 +754,9 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
assertChannelOpen(t, miner, uint32(numReqConfs), chanReservation.DispatchChan())
}
func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness,
wallet *lnwallet.LightningWallet, t *testing.T) {
// For this scenario, bob will initiate the channel, while we simply act as
// the responder.
capacity := btcutil.Amount(4 * 1e8)
@ -783,7 +771,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
// Bob sends over a single funding request, so we allocate our
// contribution and the necessary resources.
fundingAmt := btcutil.Amount(0)
chanReservation, err := lnwallet.InitChannelReservation(capacity,
chanReservation, err := wallet.InitChannelReservation(capacity,
fundingAmt, bobNode.id, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to init channel reservation: %v", err)
@ -821,7 +809,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
if err := chanReservation.ProcessSingleContribution(bobContribution); err != nil {
t.Fatalf("unable to process bob's contribution: %v", err)
}
if chanReservation.fundingTx != nil {
if chanReservation.FinalFundingTx() != nil {
t.Fatalf("funding transaction populated!")
}
if len(bobContribution.Inputs) != 1 {
@ -848,7 +836,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
t.Fatalf("bob's revocaiton key not found")
}
fundingRedeemScript, multiOut, err := genFundingPkScript(
fundingRedeemScript, multiOut, err := lnwallet.GenFundingPkScript(
ourContribution.MultiSigKey.SerializeCompressed(),
bobContribution.MultiSigKey.SerializeCompressed(),
int64(capacity))
@ -872,11 +860,11 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
// wallet so it can finalize the transaction by signing bob's commitment
// transaction.
fundingTxID := fundingTx.TxSha()
_, multiSigIndex := findScriptOutputIndex(fundingTx, multiOut.PkScript)
_, multiSigIndex := lnwallet.FindScriptOutputIndex(fundingTx, multiOut.PkScript)
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
fundingTxIn := wire.NewTxIn(fundingOutpoint, nil, nil)
aliceCommitTx, err := createCommitTx(fundingTxIn, ourContribution.CommitKey,
aliceCommitTx, err := lnwallet.CreateCommitTx(fundingTxIn, ourContribution.CommitKey,
bobContribution.CommitKey, ourContribution.RevocationKey,
ourContribution.CsvDelay, 0, capacity)
if err != nil {
@ -897,10 +885,9 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
}
// Alice should have saved the funding output.
if chanReservation.partialState.FundingOutpoint != fundingOutpoint {
if chanReservation.FundingOutpoint() != fundingOutpoint {
t.Fatalf("funding outputs don't match: %#v vs %#v",
chanReservation.partialState.FundingOutpoint,
fundingOutpoint)
chanReservation.FundingOutpoint(), fundingOutpoint)
}
// Some period of time later, Bob presents us with an SPV proof
@ -913,13 +900,13 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
// TODO(roasbeef): bob verify alice's sig
}
func testFundingReservationInvalidCounterpartySigs(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testFundingReservationInvalidCounterpartySigs(miner *rpctest.Harness, lnwallet *lnwallet.LightningWallet, t *testing.T) {
}
func testFundingTransactionTxFees(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testFundingTransactionTxFees(miner *rpctest.Harness, lnwallet *lnwallet.LightningWallet, t *testing.T) {
}
var walletTests = []func(miner *rpctest.Harness, w *LightningWallet, test *testing.T){
var walletTests = []func(miner *rpctest.Harness, w *lnwallet.LightningWallet, test *testing.T){
testDualFundingReservationWorkflow,
testSingleFunderReservationWorkflowInitiator,
testSingleFunderReservationWorkflowResponder,
@ -934,20 +921,29 @@ var walletTests = []func(miner *rpctest.Harness, w *LightningWallet, test *testi
}
type testLnWallet struct {
lnwallet *LightningWallet
lnwallet *lnwallet.LightningWallet
cleanUpFunc func()
}
func clearWalletState(w *LightningWallet) error {
w.nextFundingID = 0
w.fundingLimbo = make(map[uint64]*ChannelReservation)
w.ResetLockedOutpoints()
func clearWalletState(w *lnwallet.LightningWallet) error {
// TODO(roasbeef): should also restore outputs to original state.
w.ResetReservations()
return w.channelDB.Wipe()
return w.ChannelDB.Wipe()
}
// TestInterfaces tests all registered interfaces with a unified set of tests
// which excersie each of the required methods found within the WalletController
// interface.
//
// NOTE: In the future, when additional implementations of the WalletController
// interface have been implemented, in order to ensure the new concrete
// implementation is automatically tested, two steps must be undertaken. First,
// one needs add a "non-captured" (_) import from the new sub-package. This
// import should trigger an init() method within the package which registeres
// the interface. Second, an additional case in the switch within the main loop
// below needs to be added which properly initializes the interface.
//
// TODO(roasbeef): purge bobNode in favor of dual lnwallet's
func TestLightningWallet(t *testing.T) {
netParams := &chaincfg.SimNetParams
@ -965,27 +961,72 @@ func TestLightningWallet(t *testing.T) {
t.Fatalf("unable to set up mining node: %v", err)
}
// Funding via 10 outputs with 4BTC each.
testDir, lnwallet, err := createTestWallet(miningNode, netParams)
rpcConfig := miningNode.RPCConfig()
chainNotifier, err := btcdnotify.New(&rpcConfig)
if err != nil {
t.Fatalf("unable to create test ln wallet: %v", err)
t.Fatalf("unable to create notifier: %v", err)
}
if err := chainNotifier.Start(); err != nil {
t.Fatalf("unable to start notifier: %v", err)
}
defer os.RemoveAll(testDir)
defer lnwallet.Shutdown()
// The wallet should now have 40BTC available for spending.
assertProperBalance(t, lnwallet, 1, 40)
// Execute every test, clearing possibly mutated wallet state after
// each step.
for _, walletTest := range walletTests {
walletTest(miningNode, lnwallet, t)
// TODO(roasbeef): possible reset mining node's chainstate to
// initial level, cleanly wipe buckets
if err := clearWalletState(lnwallet); err != nil &&
err != bolt.ErrBucketNotFound {
t.Fatalf("unable to wipe wallet state: %v", err)
var bio lnwallet.BlockChainIO
var signer lnwallet.Signer
var wc lnwallet.WalletController
for _, walletDriver := range lnwallet.RegisteredWallets() {
tempTestDir, err := ioutil.TempDir("", "lnwallet")
if err != nil {
t.Fatalf("unable to create temp directory: %v", err)
}
defer os.RemoveAll(tempTestDir)
walletType := walletDriver.WalletType
switch walletType {
case "btcwallet":
btcwalletConfig := &btcwallet.Config{
PrivatePass: privPass,
HdSeed: testHdSeed[:],
DataDir: tempTestDir,
NetParams: netParams,
RpcHost: rpcConfig.Host,
RpcUser: rpcConfig.User,
RpcPass: rpcConfig.Pass,
CACert: rpcConfig.Certificates,
}
wc, err = walletDriver.New(btcwalletConfig)
if err != nil {
t.Fatalf("unable to create btcwallet: %v", err)
}
signer = wc.(*btcwallet.BtcWallet)
bio = wc.(*btcwallet.BtcWallet)
default:
t.Fatalf("unknown wallet driver: %v", walletType)
}
// Funding via 20 outputs with 4BTC each.
lnwallet, err := createTestWallet(tempTestDir, miningNode, netParams,
chainNotifier, wc, signer, bio)
if err != nil {
t.Fatalf("unable to create test ln wallet: %v", err)
}
// The wallet should now have 80BTC available for spending.
assertProperBalance(t, lnwallet, 1, 80)
// Execute every test, clearing possibly mutated wallet state after
// each step.
for _, walletTest := range walletTests {
walletTest(miningNode, lnwallet, t)
// TODO(roasbeef): possible reset mining node's chainstate to
// initial level, cleanly wipe buckets
if err := clearWalletState(lnwallet); err != nil &&
err != bolt.ErrBucketNotFound {
t.Fatalf("unable to wipe wallet state: %v", err)
}
}
lnwallet.Shutdown()
}
}

@ -88,7 +88,6 @@ type InputScript struct {
// as a signature to our version of the commitment transaction.
// * We then verify the validity of all signatures before considering the
// channel "open".
// TODO(roasbeef): update with single funder description
type ChannelReservation struct {
// This mutex MUST be held when either reading or modifying any of the
// fields below.
@ -131,11 +130,11 @@ type ChannelReservation struct {
wallet *LightningWallet
}
// newChannelReservation creates a new channel reservation. This function is
// NewChannelReservation creates a new channel reservation. This function is
// used only internally by lnwallet. In order to concurrent safety, the creation
// of all channel reservations should be carried out via the
// lnwallet.InitChannelReservation interface.
func newChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcutil.Amount,
func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcutil.Amount,
wallet *LightningWallet, id uint64, numConfs uint16) *ChannelReservation {
var ourBalance btcutil.Amount
var theirBalance btcutil.Amount
@ -160,7 +159,7 @@ func newChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut
OurBalance: ourBalance,
TheirBalance: theirBalance,
MinFeePerKb: minFeeRate,
Db: wallet.channelDB,
Db: wallet.ChannelDB,
},
numConfsToOpen: numConfs,
reservationID: id,
@ -315,6 +314,26 @@ func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
return r.fundingTx
}
// FundingRedeemScript returns the fully populated funding redeem script.
//
// NOTE: This method will only return a non-nil value after either
// ProcesContribution or ProcessSingleContribution have been executed and
// returned without error.
func (r *ChannelReservation) FundingRedeemScript() []byte {
r.RLock()
defer r.RUnlock()
return r.partialState.FundingRedeemScript
}
// LocalCommitTx returns the commitment transaction for the local node involved
// in this funding reservation.
func (r *ChannelReservation) LocalCommitTx() *wire.MsgTx {
r.RLock()
defer r.RUnlock()
return r.partialState.OurCommitTx
}
// FundingOutpoint returns the outpoint of the funding transaction.
//
// NOTE: The pointer returned will only be set once the .ProcesContribution()
@ -346,6 +365,7 @@ func (r *ChannelReservation) Cancel() error {
// transaction for this pending payment channel obtains the configured number
// of confirmations. Once confirmations have been obtained, a fully initialized
// LightningChannel instance is returned, allowing for channel updates.
//
// NOTE: If this method is called before .CompleteReservation(), it will block
// indefinitely.
func (r *ChannelReservation) DispatchChan() <-chan *LightningChannel {

@ -58,9 +58,9 @@ func genMultiSigScript(aPub, bPub []byte) ([]byte, error) {
return bldr.Script()
}
// genFundingPkScript creates a redeem script, and its matching p2wsh
// GenFundingPkScript creates a redeem script, and its matching p2wsh
// output for the funding transaction.
func genFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) {
func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) {
// As a sanity check, ensure that the passed amount is above zero.
if amt <= 0 {
return nil, nil, fmt.Errorf("can't create FundTx script with " +
@ -85,7 +85,7 @@ func genFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro
// spendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh
// multi-sig output.
func spendMultiSig(redeemScript, pubA, sigA, pubB, sigB []byte) [][]byte {
func SpendMultiSig(redeemScript, pubA, sigA, pubB, sigB []byte) [][]byte {
witness := make([][]byte, 4)
// When spending a p2wsh multi-sig script, rather than an OP_0, we add
@ -114,7 +114,8 @@ func spendMultiSig(redeemScript, pubA, sigA, pubB, sigB []byte) [][]byte {
// matching 'script'. Additionally, a boolean is returned indicating if
// a matching output was found at all.
// NOTE: The search stops after the first matching script is found.
func findScriptOutputIndex(tx *wire.MsgTx, script []byte) (bool, uint32) {
// TODO(roasbeef): shouldn't be public?
func FindScriptOutputIndex(tx *wire.MsgTx, script []byte) (bool, uint32) {
found := false
index := uint32(0)
for i, txOut := range tx.TxOut {
@ -670,7 +671,7 @@ func commitSpendNoDelay(commitScript []byte, outputAmt btcutil.Amount,
return wire.TxWitness(witness), nil
}
// deriveRevocationPubkey derives the revocation public key given the
// DeriveRevocationPubkey derives the revocation public key given the
// counter-party's commitment key, and revocation pre-image derived via a
// pseudo-random-function. In the event that we (for some reason) broadcast a
// revoked commitment transaction, then if the other party knows the revocation
@ -689,7 +690,7 @@ func commitSpendNoDelay(commitScript []byte, outputAmt btcutil.Amount,
// revokePriv := commitPriv + revokePreimge mod N
//
// Where N is the order of the sub-group.
func deriveRevocationPubkey(commitPubKey *btcec.PublicKey,
func DeriveRevocationPubkey(commitPubKey *btcec.PublicKey,
revokePreimage []byte) *btcec.PublicKey {
// First we need to convert the revocation hash into a point on the
@ -703,7 +704,7 @@ func deriveRevocationPubkey(commitPubKey *btcec.PublicKey,
return &btcec.PublicKey{X: revokeX, Y: revokeY}
}
// deriveRevocationPrivKey derives the revocation private key given a node's
// DeriveRevocationPrivKey derives the revocation private key given a node's
// commitment private key, and the pre-image to a previously seen revocation
// hash. Using this derived private key, a node is able to claim the output
// within the commitment transaction of a node in the case that they broadcast
@ -713,7 +714,7 @@ func deriveRevocationPubkey(commitPubKey *btcec.PublicKey,
// revokePriv := commitPriv + revokePreimage mod N
//
// Where N is the order of the sub-group.
func deriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
func DeriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
revokePreimage []byte) *btcec.PrivateKey {
// Convert the revocation pre-image into a scalar value so we can
@ -742,12 +743,13 @@ func deriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
//
// [1]: https://eprint.iacr.org/2010/264.pdf
// [2]: https://tools.ietf.org/html/rfc5869
func deriveElkremRoot(localMultiSigKey *btcec.PrivateKey,
func deriveElkremRoot(elkremDerivationRoot *btcec.PrivateKey,
localMultiSigKey *btcec.PublicKey,
remoteMultiSigKey *btcec.PublicKey) wire.ShaHash {
secret := localMultiSigKey.Serialize()
salt := remoteMultiSigKey.SerializeCompressed()
info := []byte("elkrem")
secret := elkremDerivationRoot.Serialize()
salt := localMultiSigKey.SerializeCompressed()
info := remoteMultiSigKey.SerializeCompressed()
rootReader := hkdf.New(sha256.New, secret, salt, info)

@ -41,7 +41,7 @@ func TestCommitmentSpendValidation(t *testing.T) {
channelBalance := btcutil.Amount(1 * 10e8)
csvTimeout := uint32(5)
revocationPreimage := testHdSeed[:]
revokePubKey := deriveRevocationPubkey(bobKeyPub, revocationPreimage)
revokePubKey := DeriveRevocationPubkey(bobKeyPub, revocationPreimage)
// With all the test data set up, we create the commitment transaction.
// We only focus on a single party's transactions, as the scripts are
@ -50,7 +50,7 @@ func TestCommitmentSpendValidation(t *testing.T) {
// This is Alice's commitment transaction, so she must wait a CSV delay
// of 5 blocks before sweeping the output, while bob can spend
// immediately with either the revocation key, or his regular key.
commitmentTx, err := createCommitTx(fakeFundingTxIn, aliceKeyPub,
commitmentTx, err := CreateCommitTx(fakeFundingTxIn, aliceKeyPub,
bobKeyPub, revokePubKey, csvTimeout, channelBalance, channelBalance)
if err != nil {
t.Fatalf("unable to create commitment transaction: %v", nil)
@ -96,7 +96,7 @@ func TestCommitmentSpendValidation(t *testing.T) {
// Next, we'll test bob spending with the derived revocation key to
// simulate the scenario when alice broadcasts this commitmen
// transaction after it's been revoked.
revokePrivKey := deriveRevocationPrivKey(bobKeyPriv, revocationPreimage)
revokePrivKey := DeriveRevocationPrivKey(bobKeyPriv, revocationPreimage)
bobWitnessSpend, err := commitSpendRevoke(delayScript, channelBalance,
revokePrivKey, sweepTx)
if err != nil {
@ -144,9 +144,9 @@ func TestRevocationKeyDerivation(t *testing.T) {
priv, pub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey)
revocationPub := deriveRevocationPubkey(pub, revocationPreimage)
revocationPub := DeriveRevocationPubkey(pub, revocationPreimage)
revocationPriv := deriveRevocationPrivKey(priv, revocationPreimage)
revocationPriv := DeriveRevocationPrivKey(priv, revocationPreimage)
x, y := btcec.S256().ScalarBaseMult(revocationPriv.D.Bytes())
derivedRevPub := &btcec.PublicKey{
Curve: btcec.S256(),

@ -1,26 +0,0 @@
package lnwallet
import (
"path/filepath"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcd/wire"
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
)
// 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)
}

@ -4,7 +4,6 @@ import (
"encoding/hex"
"errors"
"fmt"
"math"
"sync"
"sync/atomic"
@ -12,23 +11,31 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/elkrem"
"github.com/roasbeef/btcd/btcjson"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcutil/hdkeychain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcutil/coinset"
"github.com/roasbeef/btcutil/txsort"
"github.com/roasbeef/btcwallet/chain"
"github.com/roasbeef/btcwallet/waddrmgr"
btcwallet "github.com/roasbeef/btcwallet/wallet"
)
const (
// The size of the buffered queue of requests to the wallet from the
// outside word.
msgBufferSize = 100
// elkremRootIndex is the top level HD key index from which secrets
// used to generate elkrem roots should be derived from.
elkremRootIndex = hdkeychain.HardenedKeyStart + 1
// identityKeyIndex is the top level HD key index which is used to
// generate/rotate identity keys.
//
// TODO(roasbeef): should instead be child to make room for future
// rotations, etc.
identityKeyIndex = hdkeychain.HardenedKeyStart + 2
)
var (
@ -217,7 +224,7 @@ type channelOpenMsg struct {
type LightningWallet struct {
// This mutex is to be held when generating external keys to be used
// as multi-sig, and commitment keys within the channel.
KeyGenMtx sync.RWMutex
keyGenMtx sync.RWMutex
// This mutex MUST be held when performing coin selection in order to
// avoid inadvertently creating multiple funding transaction which
@ -227,25 +234,30 @@ type LightningWallet struct {
// A wrapper around a namespace within boltdb reserved for ln-based
// wallet meta-data. See the 'channeldb' package for further
// information.
channelDB *channeldb.DB
ChannelDB *channeldb.DB
// Used by in order to obtain notifications about funding transaction
// reaching a specified confirmation depth, and to catch
// counterparty's broadcasting revoked commitment states.
chainNotifier chainntnfs.ChainNotifier
// The core wallet, all non Lightning Network specific interaction is
// proxied to the internal wallet.
*btcwallet.Wallet
// wallet is the the core wallet, all non Lightning Network specific
// interaction is proxied to the internal wallet.
WalletController
// An active RPC connection to a full-node. In the case of a btcd node,
// websockets are used for notifications. If using Bitcoin Core,
// notifications are either generated via long-polling or the usage of
// ZeroMQ.
// TODO(roasbeef): make into interface need: getrawtransaction + gettxout
// * getrawtransaction -> verify proof of channel links
// * gettxout -> verify inputs to funding tx exist and are unspent
rpc *chain.RPCClient
// Signer is the wallet's current Signer implementation. This Signer is
// used to generate signature for all inputs to potential funding
// transactions, as well as for spends from the funding transaction to
// update the commitment state.
Signer Signer
// chainIO is an instance of the BlockChainIO interface. chainIO is
// used to lookup the existance of outputs within the utxo set.
chainIO BlockChainIO
// rootKey is the root HD key dervied from a WalletController private
// key. This rootKey is used to derive all LN specific secrets.
rootKey *hdkeychain.ExtendedKey
// All messages to the wallet are to be sent accross this channel.
msgChan chan interface{}
@ -262,7 +274,12 @@ type LightningWallet struct {
// TODO(roasbeef): zombie garbage collection routine to solve
// lost-object/starvation problem/attack.
cfg *Config
// lockedOutPoints is a set of the currently locked outpoint. This
// information is kept in order to provide an easy way to unlock all
// the currently locked outpoints.
lockedOutPoints map[wire.OutPoint]struct{}
netParams *chaincfg.Params
started int32
shutdown int32
@ -279,86 +296,38 @@ type LightningWallet struct {
//
// NOTE: The passed channeldb, and ChainNotifier should already be fully
// initialized/started before being passed as a function arugment.
func NewLightningWallet(config *Config, cdb *channeldb.DB,
notifier chainntnfs.ChainNotifier) (*LightningWallet, error) {
func NewLightningWallet(cdb *channeldb.DB, notifier chainntnfs.ChainNotifier,
wallet WalletController, signer Signer, bio BlockChainIO,
netParams *chaincfg.Params) (*LightningWallet, error) {
// Ensure the wallet exists or create it when the create flag is set.
netDir := networkDir(config.DataDir, config.NetParams)
// TODO(roasbeef): need a another wallet level config
var pubPass []byte
if config.PublicPass == nil {
pubPass = defaultPubPassphrase
} else {
pubPass = config.PublicPass
}
loader := btcwallet.NewLoader(config.NetParams, netDir)
walletExists, err := loader.WalletExists()
// Fetch the root derivation key from the wallet's HD chain. We'll use
// this to generate specific Lightning related secrets on the fly.
rootKey, err := wallet.FetchRootKey()
if err != nil {
return nil, err
}
var createID bool
var wallet *btcwallet.Wallet
if !walletExists {
// Wallet has never been created, perform initial set up.
wallet, err = loader.CreateNewWallet(pubPass, config.PrivatePass,
config.HdSeed)
if err != nil {
return nil, err
}
createID = true
} 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(config.PrivatePass); err != nil {
return nil, err
}
// If we just created the wallet, then reserve, and store a key for
// our ID within the Lightning Network.
if createID {
account := uint32(waddrmgr.DefaultAccountNum)
adrs, err := wallet.Manager.NextInternalAddresses(account, 1, waddrmgr.WitnessPubKey)
if err != nil {
return nil, err
}
idPubkeyHash := adrs[0].Address().ScriptAddress()
if err := cdb.PutIdKey(idPubkeyHash); err != nil {
return nil, err
}
walletLog.Infof("stored identity key pubkey hash in channeldb")
}
// Create a special websockets rpc client for btcd which will be used
// by the wallet for notifications, calls, etc.
rpcc, err := chain.NewRPCClient(config.NetParams, config.RpcHost,
config.RpcUser, config.RpcPass, config.CACert, false, 20)
// TODO(roasbeef): always re-derive on the fly?
rootKeyRaw := rootKey.Serialize()
rootMasterKey, err := hdkeychain.NewMaster(rootKeyRaw, netParams)
if err != nil {
return nil, err
}
return &LightningWallet{
chainNotifier: notifier,
rpc: rpcc,
Wallet: wallet,
channelDB: cdb,
msgChan: make(chan interface{}, msgBufferSize),
// TODO(roasbeef): make this atomic.Uint32 instead? Which is
// faster, locks or CAS? I'm guessing CAS because assembly:
// * https://golang.org/src/sync/atomic/asm_amd64.s
nextFundingID: 0,
cfg: config,
fundingLimbo: make(map[uint64]*ChannelReservation),
quit: make(chan struct{}),
rootKey: rootMasterKey,
chainNotifier: notifier,
Signer: signer,
WalletController: wallet,
chainIO: bio,
ChannelDB: cdb,
msgChan: make(chan interface{}, msgBufferSize),
nextFundingID: 0,
fundingLimbo: make(map[uint64]*ChannelReservation),
lockedOutPoints: make(map[wire.OutPoint]struct{}),
quit: make(chan struct{}),
}, nil
}
@ -370,16 +339,10 @@ func (l *LightningWallet) Startup() error {
return nil
}
// Establish an RPC connection in additino to starting the goroutines
// in the underlying wallet.
if err := l.rpc.Start(); err != nil {
// Start the underlying wallet controller.
if err := l.Start(); err != nil {
return err
}
l.Start()
// Pass the rpc client into the wallet so it can sync up to the
// current main chain.
l.SynchronizeRPC(l.rpc)
l.wg.Add(1)
// TODO(roasbeef): multiple request handlers?
@ -396,16 +359,59 @@ func (l *LightningWallet) Shutdown() error {
// Signal the underlying wallet controller to shutdown, waiting until
// all active goroutines have been shutdown.
l.Stop()
l.WaitForShutdown()
l.rpc.Shutdown()
if err := l.Stop(); err != nil {
return err
}
close(l.quit)
l.wg.Wait()
return nil
}
// LockOutpoints returns a list of all currently locked outpoint.
func (l *LightningWallet) LockedOutpoints() []*wire.OutPoint {
outPoints := make([]*wire.OutPoint, 0, len(l.lockedOutPoints))
for outPoint := range l.lockedOutPoints {
outPoints = append(outPoints, &outPoint)
}
return outPoints
}
// ResetReservations reset the volatile wallet state which trakcs all currently
// active reservations.
func (l *LightningWallet) ResetReservations() {
l.nextFundingID = 0
l.fundingLimbo = make(map[uint64]*ChannelReservation)
for outpoint := range l.lockedOutPoints {
l.UnlockOutpoint(outpoint)
}
l.lockedOutPoints = make(map[wire.OutPoint]struct{})
}
// ActiveReservations returns a slice of all the currently active
// (non-cancalled) reservations.
func (l *LightningWallet) ActiveReservations() []*ChannelReservation {
reservations := make([]*ChannelReservation, 0, len(l.fundingLimbo))
for _, reservation := range l.fundingLimbo {
reservations = append(reservations, reservation)
}
return reservations
}
// GetIdentitykey returns the identity private key of the wallet.
// TODO(roasbeef): should be moved elsewhere
func (l *LightningWallet) GetIdentitykey() (*btcec.PrivateKey, error) {
identityKey, err := l.rootKey.Child(identityKeyIndex)
if err != nil {
return nil, err
}
return identityKey.ECPrivKey()
}
// requestHandler is the primary goroutine(s) resposible for handling, and
// dispatching relies to all messages.
func (l *LightningWallet) requestHandler() {
@ -478,16 +484,9 @@ func (l *LightningWallet) InitChannelReservation(capacity,
// handleFundingReserveRequest processes a message intending to create, and
// validate a funding reservation request.
func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) {
// Create a limbo and record entry for this newly pending funding request.
l.limboMtx.Lock()
id := l.nextFundingID
reservation := newChannelReservation(req.capacity, req.fundingAmount,
id := atomic.AddUint64(&l.nextFundingID, 1)
reservation := NewChannelReservation(req.capacity, req.fundingAmount,
req.minFeeRate, l, id, req.numConfs)
l.nextFundingID++
l.fundingLimbo[id] = reservation
l.limboMtx.Unlock()
// Grab the mutex on the ChannelReservation to ensure thead-safety
reservation.Lock()
@ -502,7 +501,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
// don't need to perform any coin selection. Otherwise, attempt to
// obtain enough coins to meet the required funding amount.
if req.fundingAmount != 0 {
if err := l.selectCoinsAndChange(req.fundingAmount,
// TODO(roasbeef): consult model for proper fee rate
feeRate := uint64(10)
if err := l.selectCoinsAndChange(feeRate, req.fundingAmount,
ourContribution); err != nil {
req.err <- err
req.resp <- nil
@ -513,27 +514,26 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
// Grab two fresh keys from our HD chain, one will be used for the
// multi-sig funding transaction, and the other for the commitment
// transaction.
multiSigKey, err := l.getNextRawKey()
multiSigKey, err := l.NewRawKey()
if err != nil {
req.err <- err
req.resp <- nil
return
}
commitKey, err := l.getNextRawKey()
commitKey, err := l.NewRawKey()
if err != nil {
req.err <- err
req.resp <- nil
return
}
reservation.partialState.OurMultiSigKey = multiSigKey
ourContribution.MultiSigKey = multiSigKey.PubKey()
ourContribution.MultiSigKey = multiSigKey
reservation.partialState.OurCommitKey = commitKey
ourContribution.CommitKey = commitKey.PubKey()
ourContribution.CommitKey = commitKey
// Generate a fresh address to be used in the case of a cooperative
// channel close.
deliveryAddress, err := l.NewAddress(waddrmgr.DefaultAccountNum,
waddrmgr.WitnessPubKey)
deliveryAddress, err := l.NewAddress(WitnessPubKey, false)
if err != nil {
req.err <- err
req.resp <- nil
@ -548,6 +548,12 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
reservation.partialState.OurDeliveryScript = deliveryScript
ourContribution.DeliveryAddress = deliveryAddress
// Create a limbo and record entry for this newly pending funding
// request.
l.limboMtx.Lock()
l.fundingLimbo[id] = reservation
l.limboMtx.Unlock()
// Funding reservation request succesfully handled. The funding inputs
// will be marked as unavailable until the reservation is either
// completed, or cancecled.
@ -578,6 +584,7 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
// Mark all previously locked outpoints as usuable for future funding
// requests.
for _, unusedInput := range pendingReservation.ourContribution.Inputs {
delete(l.lockedOutPoints, unusedInput.PreviousOutPoint)
l.UnlockOutpoint(unusedInput.PreviousOutPoint)
}
@ -639,7 +646,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Finally, add the 2-of-2 multi-sig output which will set up the lightning
// channel.
channelCapacity := int64(pendingReservation.partialState.Capacity)
redeemScript, multiSigOut, err := genFundingPkScript(ourKey.PubKey().SerializeCompressed(),
redeemScript, multiSigOut, err := GenFundingPkScript(ourKey.SerializeCompressed(),
theirKey.SerializeCompressed(), channelCapacity)
if err != nil {
req.err <- err
@ -647,21 +654,6 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
}
pendingReservation.partialState.FundingRedeemScript = redeemScript
// Register intent for notifications related to the funding output.
// This'll allow us to properly track the number of confirmations the
// funding tx has once it has been broadcasted.
// TODO(roasbeef): remove
lastBlock := l.Manager.SyncedTo()
scriptAddr, err := l.Manager.ImportScript(redeemScript, &lastBlock)
if err != nil {
req.err <- err
return
}
if err := l.rpc.NotifyReceived([]btcutil.Address{scriptAddr.Address()}); err != nil {
req.err <- err
return
}
// Sort the transaction. Since both side agree to a cannonical
// ordering, by sorting we no longer need to send the entire
// transaction. Only signatures will be exchanged.
@ -671,81 +663,30 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Next, sign all inputs that are ours, collecting the signatures in
// order of the inputs.
pendingReservation.ourFundingInputScripts = make([]*InputScript, 0, len(ourContribution.Inputs))
hashCache := txscript.NewTxSigHashes(fundingTx)
signDesc := SignDescriptor{
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(fundingTx),
}
for i, txIn := range fundingTx.TxIn {
// Does the wallet know about the txin?
txDetail, _ := l.TxStore.TxDetails(&txIn.PreviousOutPoint.Hash)
if txDetail == nil {
info, err := l.FetchInputInfo(&txIn.PreviousOutPoint)
if err == ErrNotMine {
continue
}
// Is this our txin?
prevIndex := txIn.PreviousOutPoint.Index
prevOut := txDetail.TxRecord.MsgTx.TxOut[prevIndex]
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(prevOut.PkScript, l.cfg.NetParams)
apkh := addrs[0]
ai, err := l.Manager.Address(apkh)
if err != nil {
req.err <- fmt.Errorf("cannot get address info: %v", err)
return
}
pka := ai.(waddrmgr.ManagedPubKeyAddress)
privKey, err := pka.PrivKey()
if err != nil {
req.err <- fmt.Errorf("cannot get private key: %v", err)
} else if err != nil {
req.err <- err
return
}
var witnessProgram []byte
inputScript := &InputScript{}
signDesc.Output = info
signDesc.InputIndex = i
// If we're spending p2wkh output nested within a p2sh output,
// then we'll need to attach a sigScript in addition to witness
// data.
if 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,
l.cfg.NetParams)
if err != nil {
req.err <- fmt.Errorf("unable to create p2wkh addr: %v", err)
return
}
witnessProgram, err = txscript.PayToAddrScript(p2wkhAddr)
if err != nil {
req.err <- fmt.Errorf("unable to create witness program: %v", err)
return
}
bldr := txscript.NewScriptBuilder()
bldr.AddData(witnessProgram)
sigScript, err := bldr.Script()
if err != nil {
req.err <- fmt.Errorf("unable to create scriptsig: %v", err)
return
}
txIn.SignatureScript = sigScript
inputScript.ScriptSig = sigScript
} else {
witnessProgram = prevOut.PkScript
}
// Generate a valid witness stack for the input.
inputValue := prevOut.Value
witnessScript, err := txscript.WitnessScript(fundingTx, hashCache, i,
inputValue, witnessProgram, txscript.SigHashAll, privKey, true)
inputScript, err := l.Signer.ComputeInputScript(fundingTx, &signDesc)
if err != nil {
req.err <- fmt.Errorf("cannot create witnessscript: %s", err)
req.err <- err
return
}
txIn.Witness = witnessScript
inputScript.Witness = witnessScript
txIn.SignatureScript = inputScript.ScriptSig
txIn.Witness = inputScript.Witness
pendingReservation.ourFundingInputScripts = append(
pendingReservation.ourFundingInputScripts,
inputScript,
@ -753,10 +694,10 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
}
// Locate the index of the multi-sig outpoint in order to record it
// since the outputs are cannonically sorted. If this is a sigle funder
// since the outputs are cannonically sorted. If this is a single funder
// workflow, then we'll also need to send this to the remote node.
fundingTxID := fundingTx.TxSha()
_, multiSigIndex := findScriptOutputIndex(fundingTx, multiSigOut.PkScript)
_, multiSigIndex := FindScriptOutputIndex(fundingTx, multiSigOut.PkScript)
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
pendingReservation.partialState.FundingOutpoint = fundingOutpoint
@ -767,11 +708,17 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
pendingReservation.partialState.RemoteElkrem = e
pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey
masterElkremRoot, err := l.deriveMasterElkremRoot()
if err != nil {
req.err <- err
return
}
// Now that we have their commitment key, we can create the revocation
// key for the first version of our commitment transaction. To do so,
// we'll first create our elkrem root, then grab the first pre-iamge
// from it.
elkremRoot := deriveElkremRoot(ourKey, theirKey)
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
elkremSender := elkrem.NewElkremSender(elkremRoot)
pendingReservation.partialState.LocalElkrem = elkremSender
firstPreimage, err := elkremSender.AtIndex(0)
@ -780,7 +727,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
return
}
theirCommitKey := theirContribution.CommitKey
ourRevokeKey := deriveRevocationPubkey(theirCommitKey, firstPreimage[:])
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
// Create the txIn to our commitment transaction; required to construct
// the commitment transactions.
@ -792,14 +739,14 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
ourBalance := ourContribution.FundingAmount
theirBalance := theirContribution.FundingAmount
ourCommitKey := ourContribution.CommitKey
ourCommitTx, err := createCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
ourRevokeKey, ourContribution.CsvDelay,
ourBalance, theirBalance)
if err != nil {
req.err <- err
return
}
theirCommitTx, err := createCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
theirContribution.RevocationKey, theirContribution.CsvDelay,
theirBalance, ourBalance)
if err != nil {
@ -830,10 +777,15 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Generate a signature for their version of the initial commitment
// transaction.
hashCache = txscript.NewTxSigHashes(theirCommitTx)
channelBalance := pendingReservation.partialState.Capacity
sigTheirCommit, err := txscript.RawTxInWitnessSignature(theirCommitTx, hashCache, 0,
int64(channelBalance), redeemScript, txscript.SigHashAll, ourKey)
signDesc = SignDescriptor{
RedeemScript: redeemScript,
PubKey: ourKey,
Output: multiSigOut,
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(theirCommitTx),
InputIndex: 0,
}
sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc)
if err != nil {
req.err <- err
return
@ -871,7 +823,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
ourKey := pendingReservation.partialState.OurMultiSigKey
theirKey := theirContribution.MultiSigKey
channelCapacity := int64(pendingReservation.partialState.Capacity)
redeemScript, _, err := genFundingPkScript(ourKey.PubKey().SerializeCompressed(),
redeemScript, _, err := GenFundingPkScript(ourKey.SerializeCompressed(),
theirKey.SerializeCompressed(), channelCapacity)
if err != nil {
req.err <- err
@ -879,9 +831,15 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
}
pendingReservation.partialState.FundingRedeemScript = redeemScript
masterElkremRoot, err := l.deriveMasterElkremRoot()
if err != nil {
req.err <- err
return
}
// Now that we know their commitment key, we can create the revocation
// key for our version of the initial commitment transaction.
elkremRoot := deriveElkremRoot(ourKey, theirKey)
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
elkremSender := elkrem.NewElkremSender(elkremRoot)
firstPreimage, err := elkremSender.AtIndex(0)
if err != nil {
@ -890,7 +848,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
}
pendingReservation.partialState.LocalElkrem = elkremSender
theirCommitKey := theirContribution.CommitKey
ourRevokeKey := deriveRevocationPubkey(theirCommitKey, firstPreimage[:])
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
// Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the pre-image so we can't add it
@ -951,25 +909,16 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
// Fetch the alleged previous output along with the
// pkscript referenced by this input.
prevOut := txin.PreviousOutPoint
output, err := l.rpc.GetTxOut(&prevOut.Hash, prevOut.Index, false)
output, err := l.chainIO.GetUtxo(&prevOut.Hash, prevOut.Index)
if output == nil {
msg.err <- fmt.Errorf("input to funding tx does not exist: %v", err)
return
}
pkScript, err := hex.DecodeString(output.ScriptPubKey.Hex)
if err != nil {
msg.err <- err
return
}
// Sadly, gettxout returns the output value in BTC
// instead of satoshis.
inputValue := int64(output.Value) * 1e8
// Ensure that the witness+sigScript combo is valid.
vm, err := txscript.NewEngine(pkScript,
vm, err := txscript.NewEngine(output.PkScript,
fundingTx, i, txscript.StandardVerifyFlags, nil,
fundingHashCache, inputValue)
fundingHashCache, output.Value)
if err != nil {
// TODO(roasbeef): cancel at this stage if invalid sigs?
msg.err <- fmt.Errorf("cannot create script engine: %s", err)
@ -989,54 +938,37 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
pendingReservation.theirCommitmentSig = msg.theirCommitmentSig
commitTx := pendingReservation.partialState.OurCommitTx
theirKey := pendingReservation.theirContribution.MultiSigKey
ourKey := pendingReservation.partialState.OurMultiSigKey
// Re-generate both the redeemScript and p2sh output. We sign the
// redeemScript script, but include the p2sh output as the subscript
// for verification.
redeemScript := pendingReservation.partialState.FundingRedeemScript
p2wsh, err := witnessScriptHash(redeemScript)
if err != nil {
msg.err <- err
return
}
// First, we sign our copy of the commitment transaction ourselves.
channelValue := int64(pendingReservation.partialState.Capacity)
hashCache := txscript.NewTxSigHashes(commitTx)
ourCommitSig, err := txscript.RawTxInWitnessSignature(commitTx, hashCache, 0,
channelValue, redeemScript, txscript.SigHashAll, ourKey)
if err != nil {
msg.err <- err
return
}
// Next, create the spending scriptSig, and then verify that the script
// is complete, allowing us to spend from the funding transaction.
theirCommitSig := msg.theirCommitmentSig
ourKeySer := ourKey.PubKey().SerializeCompressed()
theirKeySer := theirKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKeySer, ourCommitSig,
theirKeySer, theirCommitSig)
// Finally, create an instance of a Script VM, and ensure that the
// Script executes succesfully.
commitTx.TxIn[0].Witness = witness
vm, err := txscript.NewEngine(p2wsh,
commitTx, 0, txscript.StandardVerifyFlags, nil,
nil, channelValue)
channelValue := int64(pendingReservation.partialState.Capacity)
hashCache := txscript.NewTxSigHashes(commitTx)
sigHash, err := txscript.CalcWitnessSigHash(redeemScript, hashCache,
txscript.SigHashAll, commitTx, 0, channelValue)
if err != nil {
msg.err <- err
return
}
if err := vm.Execute(); err != nil {
msg.err <- fmt.Errorf("counterparty's commitment signature is invalid: %v", err)
return
}
// Strip and store the signature to ensure that our commitment
// transaction doesn't stay hot.
commitTx.TxIn[0].Witness = nil
walletLog.Infof("sighash verify: %v", hex.EncodeToString(sigHash))
walletLog.Infof("initer verifying tx: %v", spew.Sdump(commitTx))
// Verify that we've received a valid signature from the remote party
// for our version of the commitment transaction.
sig, err := btcec.ParseSignature(theirCommitSig, btcec.S256())
if err != nil {
msg.err <- err
return
} else if !sig.Verify(sigHash, theirKey) {
msg.err <- fmt.Errorf("counterparty's commitment signature is invalid")
return
}
pendingReservation.partialState.OurCommitSig = theirCommitSig
// Funding complete, this entry can be removed from limbo.
@ -1101,14 +1033,14 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
theirCommitKey := pendingReservation.theirContribution.CommitKey
ourBalance := pendingReservation.ourContribution.FundingAmount
theirBalance := pendingReservation.theirContribution.FundingAmount
ourCommitTx, err := createCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
pendingReservation.ourContribution.RevocationKey,
pendingReservation.ourContribution.CsvDelay, ourBalance, theirBalance)
if err != nil {
req.err <- err
return
}
theirCommitTx, err := createCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
req.revokeKey, pendingReservation.theirContribution.CsvDelay,
theirBalance, ourBalance)
if err != nil {
@ -1123,65 +1055,51 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
pendingReservation.partialState.OurCommitTx = ourCommitTx
txsort.InPlaceSort(theirCommitTx)
// Verify that their signature of valid for our current commitment
// transaction. Re-generate both the redeemScript and p2sh output. We
// sign the redeemScript script, but include the p2sh output as the
// subscript for verification.
// TODO(roasbeef): replace with regular sighash calculation once the PR
// is merged.
redeemScript := pendingReservation.partialState.FundingRedeemScript
p2wsh, err := witnessScriptHash(redeemScript)
if err != nil {
req.err <- err
return
}
// TODO(roasbeef): remove all duplication after merge.
// First, we sign our copy of the commitment transaction ourselves.
channelValue := int64(pendingReservation.partialState.Capacity)
hashCache := txscript.NewTxSigHashes(ourCommitTx)
theirKey := pendingReservation.theirContribution.MultiSigKey
ourKey := pendingReservation.partialState.OurMultiSigKey
ourCommitSig, err := txscript.RawTxInWitnessSignature(ourCommitTx, hashCache, 0,
channelValue, redeemScript, txscript.SigHashAll, ourKey)
sigHash, err := txscript.CalcWitnessSigHash(redeemScript, hashCache,
txscript.SigHashAll, ourCommitTx, 0, channelValue)
if err != nil {
req.err <- err
return
}
// Next, create the spending scriptSig, and then verify that the script
// is complete, allowing us to spend from the funding transaction.
ourKeySer := ourKey.PubKey().SerializeCompressed()
theirKeySer := theirKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKeySer, ourCommitSig,
theirKeySer, req.theirCommitmentSig)
// Finally, create an instance of a Script VM, and ensure that the
// Script executes succesfully.
ourCommitTx.TxIn[0].Witness = witness
// TODO(roasbeef): replace engine with plain sighash check
vm, err := txscript.NewEngine(p2wsh, ourCommitTx, 0,
txscript.StandardVerifyFlags, nil, nil, channelValue)
// Verify that we've received a valid signature from the remote party
// for our version of the commitment transaction.
sig, err := btcec.ParseSignature(req.theirCommitmentSig, btcec.S256())
if err != nil {
req.err <- err
return
}
if err := vm.Execute(); err != nil {
req.err <- fmt.Errorf("counterparty's commitment signature is invalid: %v", err)
} else if !sig.Verify(sigHash, theirKey) {
req.err <- fmt.Errorf("counterparty's commitment signature is invalid")
return
}
// Strip and store the signature to ensure that our commitment
// transaction doesn't stay hot.
ourCommitTx.TxIn[0].Witness = nil
pendingReservation.partialState.OurCommitSig = req.theirCommitmentSig
// With their signature for our version of the commitment transactions
// verified, we can now generate a signature for their version,
// allowing the funding transaction to be safely broadcast.
hashCache = txscript.NewTxSigHashes(theirCommitTx)
sigTheirCommit, err := txscript.RawTxInWitnessSignature(theirCommitTx, hashCache, 0,
channelValue, redeemScript, txscript.SigHashAll, ourKey)
p2wsh, err := witnessScriptHash(redeemScript)
if err != nil {
req.err <- err
return
}
signDesc := SignDescriptor{
RedeemScript: redeemScript,
PubKey: ourKey,
Output: &wire.TxOut{
PkScript: p2wsh,
Value: channelValue,
},
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(theirCommitTx),
InputIndex: 0,
}
sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc)
if err != nil {
req.err <- err
return
@ -1224,8 +1142,7 @@ func (l *LightningWallet) handleChannelOpen(req *channelOpenMsg) {
// Finally, create and officially open the payment channel!
// TODO(roasbeef): CreationTime once tx is 'open'
channel, _ := NewLightningChannel(l, l.chainNotifier, l.channelDB,
res.partialState)
channel, _ := NewLightningChannel(l.Signer, l, l.chainNotifier, res.partialState)
res.chanOpen <- channel
req.err <- nil
@ -1266,68 +1183,18 @@ out:
// Finally, create and officially open the payment channel!
// TODO(roasbeef): CreationTime once tx is 'open'
channel, _ := NewLightningChannel(l, l.chainNotifier, l.channelDB,
channel, _ := NewLightningChannel(l.Signer, l, l.chainNotifier,
res.partialState)
res.chanOpen <- channel
}
// getNextRawKey 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.
// TODO(roasbeef): on shutdown, write state of pending keys, then read back?
func (l *LightningWallet) getNextRawKey() (*btcec.PrivateKey, error) {
l.KeyGenMtx.Lock()
defer l.KeyGenMtx.Unlock()
nextAddr, err := l.Manager.NextExternalAddresses(waddrmgr.DefaultAccountNum,
1, waddrmgr.WitnessPubKey)
if err != nil {
return nil, err
}
pkAddr := nextAddr[0].(waddrmgr.ManagedPubKeyAddress)
return pkAddr.PrivKey()
}
// ListUnspentWitness returns a slice of all the unspent outputs the wallet
// controls which pay to witness programs either directly or indirectly.
func (l *LightningWallet) ListUnspentWitness(minConfs int32) ([]*btcjson.ListUnspentResult, error) {
// First, grab all the unfiltered currently unspent outputs.
maxConfs := int32(math.MaxInt32)
unspentOutputs, err := l.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([]*btcjson.ListUnspentResult, 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) {
witnessOutputs = append(witnessOutputs, output)
}
}
return witnessOutputs, nil
}
// selectCoinsAndChange performs coin selection in order to obtain witness
// outputs which sum to at least 'numCoins' amount of satoshis. If coin
// selection is succesful/possible, then the selected coins are available within
// the passed contribution's inputs. If necessary, a change address will also be
// generated.
// TODO(roasbeef): remove hardcoded fees and req'd confs for outputs.
func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amount,
contribution *ChannelContribution) error {
// We hold the coin select mutex while querying for outputs, and
@ -1337,22 +1204,10 @@ func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
// when we encounter an error condition.
l.coinSelectMtx.Lock()
// TODO(roasbeef): check if balance is insufficient, if so then select
// on two channels, one is a time.After that will bail out with
// insuffcient funds, the other is a notification that the balance has
// been updated make(chan struct{}, 1).
// Find all unlocked unspent witness outputs with greater than 1
// confirmation.
// TODO(roasbeef): make num confs a configuration paramter
unspentOutputs, err := l.ListUnspentWitness(1)
if err != nil {
l.coinSelectMtx.Unlock()
return err
}
// Convert the outputs to coins for coin selection below.
coins, err := outputsToCoins(unspentOutputs)
coins, err := l.ListUnspentWitness(1)
if err != nil {
l.coinSelectMtx.Unlock()
return err
@ -1360,17 +1215,8 @@ func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
// Peform coin selection over our available, unlocked unspent outputs
// in order to find enough coins to meet the funding amount requirements.
//
// TODO(roasbeef): Should extend coinset with optimal coin selection
// heuristics for our use case.
// NOTE: this current selection assumes "priority" is still a thing.
selector := &coinset.MaxValueAgeCoinSelector{
MaxInputs: 10,
MinChangeAmount: 10000,
}
// TODO(roasbeef): don't hardcode fee...
totalWithFee := numCoins + 10000
selectedCoins, err := selector.CoinSelect(totalWithFee, coins)
// TODO(roasbeef): take in sat/byte
selectedCoins, changeAmt, err := coinSelect(feeRate, amt, coins)
if err != nil {
l.coinSelectMtx.Unlock()
return err
@ -1379,57 +1225,151 @@ func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
// Lock the selected coins. These coins are now "reserved", this
// prevents concurrent funding requests from referring to and this
// double-spending the same set of coins.
contribution.Inputs = make([]*wire.TxIn, len(selectedCoins.Coins()))
for i, coin := range selectedCoins.Coins() {
txout := wire.NewOutPoint(coin.Hash(), coin.Index())
l.LockOutpoint(*txout)
contribution.Inputs = make([]*wire.TxIn, len(selectedCoins))
for i, coin := range selectedCoins {
l.lockedOutPoints[*coin] = struct{}{}
l.LockOutpoint(*coin)
// Empty sig script, we'll actually sign if this reservation is
// queued up to be completed (the other side accepts).
outPoint := wire.NewOutPoint(coin.Hash(), coin.Index())
contribution.Inputs[i] = wire.NewTxIn(outPoint, nil, nil)
contribution.Inputs[i] = wire.NewTxIn(coin, nil, nil)
}
// Record any change output(s) generated as a result of the coin
// selection.
if changeAmt != 0 {
changeAddr, err := l.NewAddress(WitnessPubKey, true)
if err != nil {
return err
}
changeScript, err := txscript.PayToAddrScript(changeAddr)
if err != nil {
return err
}
contribution.ChangeOutputs = make([]*wire.TxOut, 1)
contribution.ChangeOutputs[0] = &wire.TxOut{
Value: int64(changeAmt),
PkScript: changeScript,
}
}
l.coinSelectMtx.Unlock()
// Create some possibly neccessary change outputs.
selectedTotalValue := coinset.NewCoinSet(selectedCoins.Coins()).TotalValue()
if selectedTotalValue > totalWithFee {
// Change is necessary. Query for an available change address to
// send the remainder to.
contribution.ChangeOutputs = make([]*wire.TxOut, 1)
changeAddr, err := l.NewChangeAddress(waddrmgr.DefaultAccountNum,
waddrmgr.WitnessPubKey)
if err != nil {
return err
}
changeAddrScript, err := txscript.PayToAddrScript(changeAddr)
if err != nil {
return err
}
changeAmount := selectedTotalValue - totalWithFee
contribution.ChangeOutputs[0] = wire.NewTxOut(int64(changeAmount),
changeAddrScript)
}
// TODO(roasbeef): re-calculate fees here to minFeePerKB, may need more inputs
return nil
}
type WaddrmgrEncryptorDecryptor struct {
M *waddrmgr.Manager
// deriveMasterElkremRoot derives the private key which serves as the master
// elkrem root. This master secret is used as the secret input to a HKDF to
// generate elkrem secrets based on random, but public data.
func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
masterElkremRoot, err := l.rootKey.Child(elkremRootIndex)
if err != nil {
return nil, err
}
return masterElkremRoot.ECPrivKey()
}
func (w *WaddrmgrEncryptorDecryptor) Encrypt(p []byte) ([]byte, error) {
return w.M.Encrypt(waddrmgr.CKTPrivate, p)
// selectInputs selects a slice of inputs necessary to meet the specified
// selection amount. If input selectino is unable to suceed to to insuffcient
// funds, a non-nil error is returned. Additionally, the total amount of the
// selected coins are returned in order for the caller to properly handle
// change+fees.
func selectInputs(amt btcutil.Amount, coins []*Utxo) (btcutil.Amount, []*wire.OutPoint, error) {
var (
selectedUtxos []*wire.OutPoint
satSelected btcutil.Amount
)
i := 0
for satSelected < amt {
// If we're about to go past the number of available coins,
// then exit with an error.
if i > len(coins)-1 {
return 0, nil, ErrInsufficientFunds
}
// Otherwise, collect this new coin as it may be used for final
// coin selection.
coin := coins[i]
utxo := &wire.OutPoint{
Hash: coin.Hash,
Index: coin.Index,
}
selectedUtxos = append(selectedUtxos, utxo)
satSelected += coin.Value
i++
}
return satSelected, selectedUtxos, nil
}
func (w *WaddrmgrEncryptorDecryptor) Decrypt(c []byte) ([]byte, error) {
return w.M.Decrypt(waddrmgr.CKTPrivate, c)
}
// coinSelect attemps to select a sufficient amount of coins, including a
// change output to fund amt satoshis, adhearing to the specified fee rate. The
// specified fee rate should be expressed in sat/byte for coin selection to
// function properly.
func coinSelect(feeRate uint64, amt btcutil.Amount,
coins []*Utxo) ([]*wire.OutPoint, btcutil.Amount, error) {
func (w *WaddrmgrEncryptorDecryptor) OverheadSize() uint32 {
return 24
const (
// txOverhead is the overhead of a transaction residing within
// the version number and lock time.
txOverhead = 8
// p2wkhSpendSize an estimate of the number of bytes it takes
// to spend a p2wkh output.
//
// (p2wkh witness) + txid + index + varint script size + sequence
// TODO(roasbeef): div by 3 due to witness size?
p2wkhSpendSize = (1 + 73 + 1 + 33) + 32 + 4 + 1 + 4
// p2wkhOutputSize is an estimate of the size of a regualr
// p2wkh output.
//
// 8 (output) + 1 (var int script) + 22 (p2wkh output)
p2wkhOutputSize = 8 + 1 + 22
// p2wkhOutputSize is an estimate of the p2wsh funding uotput.
p2wshOutputSize = 8 + 1 + 34
)
var estimatedSize int
amtNeeded := amt
for {
// First perform an initial round of coin selection to estimate
// the required fee.
totalSat, selectedUtxos, err := selectInputs(amtNeeded, coins)
if err != nil {
return nil, 0, err
}
// Based on the selected coins, estimate the size of the final
// fully signed transaction.
estimatedSize = ((len(selectedUtxos) * p2wkhSpendSize) +
p2wshOutputSize + txOverhead)
// The difference bteween the selected amount and the amount
// requested will be used to pay fees, and generate a change
// output with the remaining.
overShootAmt := totalSat - amtNeeded
// Based on the estimated size and fee rate, if the excess
// amount isn't enough to pay fees, then increase the requested
// coin amount by the estimate required fee, performing another
// round of coin selection.
requiredFee := btcutil.Amount(uint64(estimatedSize) * feeRate)
if overShootAmt < requiredFee {
amtNeeded += requiredFee
continue
}
// If the fee is sufficient, then calculate the size of the change output.
changeAmt := overShootAmt - requiredFee
return selectedUtxos, changeAmt, nil
}
}

@ -218,8 +218,8 @@ func newPeer(conn net.Conn, server *server, btcNet wire.BitcoinNet, inbound bool
func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
for _, dbChan := range chans {
chanID := dbChan.ChanID
lnChan, err := lnwallet.NewLightningChannel(p.server.lnwallet,
p.server.chainNotifier, p.server.chanDB, dbChan)
lnChan, err := lnwallet.NewLightningChannel(p.server.lnwallet.Signer,
p.server.lnwallet, p.server.chainNotifier, dbChan)
if err != nil {
return err
}

@ -10,6 +10,7 @@ import (
"github.com/lightningnetwork/lnd/lndc"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
@ -96,7 +97,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64) (*wire.ShaHash
return nil, err
}
return r.server.lnwallet.SendOutputs(outputs, defaultAccount, 1)
return r.server.lnwallet.SendOutputs(outputs)
}
// SendCoins executes a request to send coins to a particular address. Unlike
@ -136,23 +137,19 @@ func (r *rpcServer) SendMany(ctx context.Context,
func (r *rpcServer) NewAddress(ctx context.Context,
in *lnrpc.NewAddressRequest) (*lnrpc.NewAddressResponse, error) {
r.server.lnwallet.KeyGenMtx.Lock()
defer r.server.lnwallet.KeyGenMtx.Unlock()
// Translate the gRPC proto address type to the wallet controller's
// available address types.
var addrType waddrmgr.AddressType
var addrType lnwallet.AddressType
switch in.Type {
case lnrpc.NewAddressRequest_WITNESS_PUBKEY_HASH:
addrType = waddrmgr.WitnessPubKey
addrType = lnwallet.WitnessPubKey
case lnrpc.NewAddressRequest_NESTED_PUBKEY_HASH:
addrType = waddrmgr.NestedWitnessPubKey
addrType = lnwallet.NestedWitnessPubKey
case lnrpc.NewAddressRequest_PUBKEY_HASH:
addrType = waddrmgr.PubKeyHash
addrType = lnwallet.PubKeyHash
}
addr, err := r.server.lnwallet.NewAddress(defaultAccount,
addrType)
addr, err := r.server.lnwallet.NewAddress(addrType, false)
if err != nil {
return nil, err
}
@ -378,35 +375,14 @@ func (r *rpcServer) ListPeers(ctx context.Context,
func (r *rpcServer) WalletBalance(ctx context.Context,
in *lnrpc.WalletBalanceRequest) (*lnrpc.WalletBalanceResponse, error) {
var balance float64
if in.WitnessOnly {
witnessOutputs, err := r.server.lnwallet.ListUnspentWitness(1)
if err != nil {
return nil, err
}
// We need to convert from BTC to satoshi here otherwise, and
// incorrect sum will be returned.
var outputSum btcutil.Amount
for _, witnessOutput := range witnessOutputs {
outputSum += btcutil.Amount(witnessOutput.Amount * 1e8)
}
balance = outputSum.ToBTC()
} else {
// TODO(roasbeef): make num confs a param
outputSum, err := r.server.lnwallet.CalculateBalance(1)
if err != nil {
return nil, err
}
balance = outputSum.ToBTC()
balance, err := r.server.lnwallet.ConfirmedBalance(1, in.WitnessOnly)
if err != nil {
return nil, err
}
rpcsLog.Debugf("[walletbalance] balance=%v", balance)
return &lnrpc.WalletBalanceResponse{balance}, nil
return &lnrpc.WalletBalanceResponse{balance.ToBTC()}, nil
}
// PendingChannels returns a list of all the channels that are currently

@ -18,7 +18,6 @@ import (
"github.com/BitfuryLightning/tools/routing"
"github.com/BitfuryLightning/tools/rt/graph"
"github.com/roasbeef/btcwallet/waddrmgr"
)
// server is the main server of the Lightning Network Daemon. The server
@ -66,7 +65,7 @@ type server struct {
func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
wallet *lnwallet.LightningWallet, chanDB *channeldb.DB) (*server, error) {
privKey, err := getIdentityPrivKey(chanDB, wallet)
privKey, err := wallet.GetIdentitykey()
if err != nil {
return nil, err
}
@ -493,33 +492,3 @@ func (s *server) listener(l net.Listener) {
s.wg.Done()
}
// getIdentityPrivKey gets the identity private key out of the wallet DB.
func getIdentityPrivKey(c *channeldb.DB,
w *lnwallet.LightningWallet) (*btcec.PrivateKey, error) {
// First retrieve the current identity address for this peer.
adr, err := c.GetIdAdr()
if err != nil {
return nil, err
}
// Using the ID address, request the private key coresponding to the
// address from the wallet's address manager.
adr2, err := w.Manager.Address(adr)
if err != nil {
return nil, err
}
serializedKey := adr2.(waddrmgr.ManagedPubKeyAddress).PubKey().SerializeCompressed()
keyEncoded := hex.EncodeToString(serializedKey)
ltndLog.Infof("identity address: %v", adr)
ltndLog.Infof("identity pubkey retrieved: %v", keyEncoded)
priv, err := adr2.(waddrmgr.ManagedPubKeyAddress).PrivKey()
if err != nil {
return nil, err
}
return priv, nil
}