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