mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
lnwallet: integrate obfuscated state hints into funding workflow
This commit finalizes the implementation of #58 by integrating passing around the obfuscate state hints into the funding workflow of the wallet, and also the daemon’s funding manager. In order to amend the tests, the functions to set and receive the state hints are now publicly exported.
This commit is contained in:
parent
3010412bbc
commit
22074eb737
@ -103,13 +103,14 @@ type fundingErrorMsg struct {
|
||||
type pendingChannels map[uint64]*reservationWithCtx
|
||||
|
||||
// fundingManager acts as an orchestrator/bridge between the wallet's
|
||||
// 'ChannelReservation' workflow, and the wire protocl's funding initiation
|
||||
// messages. Any requests to initaite the funding workflow for a channel, either
|
||||
// kicked-off locally, or remotely is handled by the funding manager. Once a
|
||||
// channels's funding workflow has been completed, any local callers, the local
|
||||
// peer, and possibly the remote peer are notified of the completion of the
|
||||
// channel workflow. Additionally, any temporary or permanent access controls
|
||||
// between the wallet and remote peers are enforced via the funding manager.
|
||||
// 'ChannelReservation' workflow, and the wire protocol's funding initiation
|
||||
// messages. Any requests to initiate the funding workflow for a channel,
|
||||
// either kicked-off locally, or remotely is handled by the funding manager.
|
||||
// Once a channel's funding workflow has been completed, any local callers, the
|
||||
// local peer, and possibly the remote peer are notified of the completion of
|
||||
// the channel workflow. Additionally, any temporary or permanent access
|
||||
// controls between the wallet and remote peers are enforced via the funding
|
||||
// manager.
|
||||
type fundingManager struct {
|
||||
// MUST be used atomically.
|
||||
started int32
|
||||
@ -464,8 +465,10 @@ func (f *fundingManager) handleFundingResponse(fmsg *fundingResponseMsg) {
|
||||
outPoint, msg.ChannelID)
|
||||
|
||||
revocationKey := resCtx.reservation.OurContribution().RevocationKey
|
||||
obsfucator := resCtx.reservation.StateNumObfuscator()
|
||||
|
||||
fundingComplete := lnwire.NewSingleFundingComplete(msg.ChannelID,
|
||||
outPoint, commitSig, revocationKey)
|
||||
outPoint, commitSig, revocationKey, obsfucator)
|
||||
sourcePeer.queueMsg(fundingComplete, nil)
|
||||
}
|
||||
|
||||
@ -491,16 +494,20 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) {
|
||||
// TODO(roasbeef): make case (p vs P) consistent throughout
|
||||
fundingOut := fmsg.msg.FundingOutPoint
|
||||
chanID := fmsg.msg.ChannelID
|
||||
commitSig := fmsg.msg.CommitSignature.Serialize()
|
||||
fndgLog.Infof("completing pendingID(%v) with ChannelPoint(%v)",
|
||||
fmsg.msg.ChannelID, fundingOut,
|
||||
chanID, fundingOut,
|
||||
)
|
||||
|
||||
// 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.
|
||||
revokeKey := fmsg.msg.RevocationKey
|
||||
if err := resCtx.reservation.CompleteReservationSingle(revokeKey, fundingOut, commitSig); err != nil {
|
||||
obsfucator := fmsg.msg.StateHintObsfucator
|
||||
commitSig := fmsg.msg.CommitSignature.Serialize()
|
||||
|
||||
// With all the necessary data available, attempt to advance the
|
||||
// funding workflow to the next stage. If this succeeds then the
|
||||
// funding transaction will broadcast after our next message.
|
||||
err := resCtx.reservation.CompleteReservationSingle(revokeKey,
|
||||
fundingOut, commitSig, obsfucator)
|
||||
if err != nil {
|
||||
// TODO(roasbeef): better error logging: peerID, channelID, etc.
|
||||
fndgLog.Errorf("unable to complete single reservation: %v", err)
|
||||
fmsg.peer.Disconnect()
|
||||
|
@ -108,6 +108,8 @@ type bobNode struct {
|
||||
delay uint32
|
||||
id *btcec.PublicKey
|
||||
|
||||
obsfucator [lnwallet.StateHintSize]byte
|
||||
|
||||
availableOutputs []*wire.TxIn
|
||||
changeOutputs []*wire.TxOut
|
||||
fundingAmt btcutil.Amount
|
||||
@ -241,6 +243,9 @@ func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) {
|
||||
copy(revocation[:], bobsPrivKey)
|
||||
revocation[0] = 0xff
|
||||
|
||||
var obsfucator [lnwallet.StateHintSize]byte
|
||||
copy(obsfucator[:], revocation[:])
|
||||
|
||||
// His ID is just as creative...
|
||||
var id [wire.HashSize]byte
|
||||
id[0] = 0xff
|
||||
@ -364,9 +369,9 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet
|
||||
}
|
||||
|
||||
// The channel reservation should now be populated with a multi-sig key
|
||||
// from our HD chain, a change output with 3 BTC, and 2 outputs selected
|
||||
// of 4 BTC each. Additionally, the rest of the items needed to fufill a
|
||||
// funding contribution should also have been filled in.
|
||||
// from our HD chain, a change output with 3 BTC, and 2 outputs
|
||||
// selected of 4 BTC each. Additionally, the rest of the items needed
|
||||
// to fulfill a funding contribution should also have been filled in.
|
||||
ourContribution := chanReservation.OurContribution()
|
||||
if len(ourContribution.Inputs) != 2 {
|
||||
t.Fatalf("outputs for funding tx not properly selected, have %v "+
|
||||
@ -582,7 +587,7 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
|
||||
// outpoints, will let us fail early/fast instead of querying and
|
||||
// attempting coin selection.
|
||||
|
||||
// Request to fund a new channel should now succeeed.
|
||||
// Request to fund a new channel should now succeed.
|
||||
_, err = wallet.InitChannelReservation(fundingAmount, fundingAmount,
|
||||
testPub, bobAddr, numReqConfs, 4)
|
||||
if err != nil {
|
||||
@ -733,6 +738,13 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness,
|
||||
hex.EncodeToString(channels[0].FundingOutpoint.Hash[:]),
|
||||
hex.EncodeToString(fundingSha[:]))
|
||||
}
|
||||
if !channels[0].IsInitiator {
|
||||
t.Fatalf("alice not detected as channel initiator")
|
||||
}
|
||||
if channels[0].ChanType != channeldb.SingleFunder {
|
||||
t.Fatalf("channel type is incorrect, expected %v instead got %v",
|
||||
channeldb.SingleFunder, channels[0].ChanType)
|
||||
}
|
||||
|
||||
assertChannelOpen(t, miner, uint32(numReqConfs), chanReservation.DispatchChan())
|
||||
}
|
||||
@ -848,15 +860,23 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness,
|
||||
fundingTxID := fundingTx.TxSha()
|
||||
_, multiSigIndex := lnwallet.FindScriptOutputIndex(fundingTx, multiOut.PkScript)
|
||||
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
|
||||
bobObsfucator := bobNode.obsfucator
|
||||
|
||||
// Next, manually create Alice's commitment transaction, signing the
|
||||
// fully sorted and state hinted transaction.
|
||||
fundingTxIn := wire.NewTxIn(fundingOutpoint, nil, nil)
|
||||
aliceCommitTx, err := lnwallet.CreateCommitTx(fundingTxIn, ourContribution.CommitKey,
|
||||
bobContribution.CommitKey, ourContribution.RevocationKey,
|
||||
ourContribution.CsvDelay, 0, capacity)
|
||||
aliceCommitTx, err := lnwallet.CreateCommitTx(fundingTxIn,
|
||||
ourContribution.CommitKey, bobContribution.CommitKey,
|
||||
ourContribution.RevocationKey, ourContribution.CsvDelay, 0,
|
||||
capacity)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create alice's commit tx: %v", err)
|
||||
}
|
||||
txsort.InPlaceSort(aliceCommitTx)
|
||||
err = lnwallet.SetStateNumHint(aliceCommitTx, 0, bobObsfucator)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to set state hint: %v", err)
|
||||
}
|
||||
bobCommitSig, err := bobNode.signCommitTx(aliceCommitTx,
|
||||
// TODO(roasbeef): account for hard-coded fee, remove bob node
|
||||
fundingRedeemScript, int64(capacity)+5000)
|
||||
@ -866,8 +886,9 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness,
|
||||
|
||||
// With this stage complete, Alice can now complete the reservation.
|
||||
bobRevokeKey := bobContribution.RevocationKey
|
||||
if err := chanReservation.CompleteReservationSingle(bobRevokeKey,
|
||||
fundingOutpoint, bobCommitSig); err != nil {
|
||||
err = chanReservation.CompleteReservationSingle(bobRevokeKey,
|
||||
fundingOutpoint, bobCommitSig, bobObsfucator)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to complete reservation: %v", err)
|
||||
}
|
||||
|
||||
@ -1146,10 +1167,10 @@ func TestLightningWallet(t *testing.T) {
|
||||
// up this node with a chain length of 125, so we have plentyyy of BTC
|
||||
// to play around with.
|
||||
miningNode, err := rpctest.New(netParams, nil, nil)
|
||||
defer miningNode.TearDown()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create mining node: %v", err)
|
||||
}
|
||||
defer miningNode.TearDown()
|
||||
if err := miningNode.SetUp(true, 25); err != nil {
|
||||
t.Fatalf("unable to set up mining node: %v", err)
|
||||
}
|
||||
|
@ -324,8 +324,10 @@ func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScr
|
||||
// the .OurSignatures() method. As this method should only be called as a
|
||||
// response to a single funder channel, only a commitment signature will be
|
||||
// populated.
|
||||
func (r *ChannelReservation) CompleteReservationSingle(revocationKey *btcec.PublicKey,
|
||||
fundingPoint *wire.OutPoint, commitSig []byte) error {
|
||||
func (r *ChannelReservation) CompleteReservationSingle(
|
||||
revocationKey *btcec.PublicKey, fundingPoint *wire.OutPoint,
|
||||
commitSig []byte, obsfucator [StateHintSize]byte) error {
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
r.wallet.msgChan <- &addSingleFunderSigsMsg{
|
||||
@ -333,6 +335,7 @@ func (r *ChannelReservation) CompleteReservationSingle(revocationKey *btcec.Publ
|
||||
revokeKey: revocationKey,
|
||||
fundingOutpoint: fundingPoint,
|
||||
theirCommitmentSig: commitSig,
|
||||
obsfucator: obsfucator,
|
||||
err: errChan,
|
||||
}
|
||||
|
||||
@ -395,6 +398,19 @@ func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
|
||||
return r.partialState.FundingOutpoint
|
||||
}
|
||||
|
||||
// StateNumObfuscator returns the bytes to be used to obsfucate the state
|
||||
// number hints for all future states of the commitment transaction for this
|
||||
// workflow.
|
||||
//
|
||||
// NOTE: This value will only be available for a single funder workflow after
|
||||
// the CompleteReservation or CompleteReservationSingle methods have been
|
||||
// successfully executed.
|
||||
func (r *ChannelReservation) StateNumObfuscator() [StateHintSize]byte {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return r.partialState.StateHintObsfucator
|
||||
}
|
||||
|
||||
// Cancel abandons this channel reservation. This method should be called in
|
||||
// the scenario that communications with the counterparty break down. Upon
|
||||
// cancellation, all resources previously reserved for this pending payment
|
||||
|
@ -772,14 +772,15 @@ func deriveElkremRoot(elkremDerivationRoot *btcec.PrivateKey,
|
||||
return elkremRoot
|
||||
}
|
||||
|
||||
// setStateNumHint encodes the current state number within the passed
|
||||
// SetStateNumHint encodes the current state number within the passed
|
||||
// commitment transaction by re-purposing the sequence fields in the input of
|
||||
// the commitment transaction to encode the obfuscated state number. The state
|
||||
// number is encoded using 31-bits of the sequence number, with the top bit set
|
||||
// in order to disable BIP0068 (sequence locks) semantics. Finally before
|
||||
// encoding, the obfuscater is XOR'd against the state number in order to hide
|
||||
// the exact state number from the PoV of outside parties.
|
||||
func setStateNumHint(commitTx *wire.MsgTx, stateNum uint32,
|
||||
// TODO(roasbeef): unexport function after bobNode is gone
|
||||
func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint32,
|
||||
obsfucator [StateHintSize]byte) error {
|
||||
|
||||
// With the current schema we are only able able to encode state num
|
||||
@ -809,13 +810,13 @@ func setStateNumHint(commitTx *wire.MsgTx, stateNum uint32,
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStateNumHint recovers the current state number given a commitment
|
||||
// GetStateNumHint recovers the current state number given a commitment
|
||||
// transaction which has previously had the state number encoded within it via
|
||||
// setStateNumHint and a shared obsfucator.
|
||||
//
|
||||
// See setStateNumHint for further details w.r.t exactly how the state-hints
|
||||
// are encoded.
|
||||
func getStateNumHint(commitTx *wire.MsgTx, obsfucator [StateHintSize]byte) uint32 {
|
||||
func GetStateNumHint(commitTx *wire.MsgTx, obsfucator [StateHintSize]byte) uint32 {
|
||||
// Convert the obfuscater into a uint32, this will be used to
|
||||
// de-obfuscate the final recovered state number.
|
||||
xorInt := binary.BigEndian.Uint32(obsfucator[:]) & (^wire.SequenceLockTimeDisabled)
|
||||
|
@ -547,12 +547,12 @@ func TestCommitTxStateHint(t *testing.T) {
|
||||
for i := 0; i < 10000; i++ {
|
||||
stateNum := uint32(i)
|
||||
|
||||
err := setStateNumHint(commitTx, stateNum, obsfucator)
|
||||
err := SetStateNumHint(commitTx, stateNum, obsfucator)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to set state num %v: %v", i, err)
|
||||
}
|
||||
|
||||
extractedStateNum := getStateNumHint(commitTx, obsfucator)
|
||||
extractedStateNum := GetStateNumHint(commitTx, obsfucator)
|
||||
if extractedStateNum != stateNum {
|
||||
t.Fatalf("state number mismatched, expected %v, got %v",
|
||||
stateNum, extractedStateNum)
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/btcsuite/fastsha256"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
@ -197,10 +198,14 @@ type addSingleFunderSigsMsg struct {
|
||||
// construct for them.
|
||||
revokeKey *btcec.PublicKey
|
||||
|
||||
// This should be 1/2 of the signatures needed to succesfully spend our
|
||||
// version of the commitment transaction.
|
||||
// theirCommitmentSig are the 1/2 of the signatures needed to
|
||||
// succesfully spend our version of the commitment transaction.
|
||||
theirCommitmentSig []byte
|
||||
|
||||
// obsfucator is the bytes to be used to obsfucate the state hints on
|
||||
// the commitment transaction.
|
||||
obsfucator [StateHintSize]byte
|
||||
|
||||
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
|
||||
err chan error
|
||||
}
|
||||
@ -782,6 +787,24 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||
return
|
||||
}
|
||||
|
||||
// With both commitment transactions constructed, generate the state
|
||||
// obsfucator then use it to encode the current state number withi both
|
||||
// commitment transactions.
|
||||
// TODO(roasbeef): define obsfucator scheme for dual funder
|
||||
var stateObsfucator [StateHintSize]byte
|
||||
if pendingReservation.partialState.IsInitiator {
|
||||
stateObsfucator, err = deriveStateHintObsfucator(elkremSender)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
err = initStateHints(ourCommitTx, theirCommitTx, stateObsfucator)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
return
|
||||
}
|
||||
|
||||
// Sort both transactions according to the agreed upon cannonical
|
||||
// ordering. This lets us skip sending the entire transaction over,
|
||||
// instead we'll just send signatures.
|
||||
@ -801,6 +824,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||
pendingReservation.partialState.TheirCommitKey = theirCommitKey
|
||||
pendingReservation.partialState.TheirMultiSigKey = theirContribution.MultiSigKey
|
||||
pendingReservation.partialState.OurCommitTx = ourCommitTx
|
||||
pendingReservation.partialState.StateHintObsfucator = stateObsfucator
|
||||
pendingReservation.ourContribution.RevocationKey = ourRevokeKey
|
||||
|
||||
// Generate a signature for their version of the initial commitment
|
||||
@ -1047,6 +1071,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
||||
pendingReservation.partialState.FundingOutpoint = req.fundingOutpoint
|
||||
pendingReservation.partialState.TheirCurrentRevocation = req.revokeKey
|
||||
pendingReservation.partialState.ChanID = req.fundingOutpoint
|
||||
pendingReservation.partialState.StateHintObsfucator = req.obsfucator
|
||||
fundingTxIn := wire.NewTxIn(req.fundingOutpoint, nil, nil)
|
||||
|
||||
// Now that we have the funding outpoint, we can generate both versions
|
||||
@ -1071,6 +1096,15 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
||||
return
|
||||
}
|
||||
|
||||
// With both commitment transactions constructed, generate the state
|
||||
// obsfucator then use it to encode the current state number within
|
||||
// both commitment transactions.
|
||||
err = initStateHints(ourCommitTx, theirCommitTx, req.obsfucator)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
return
|
||||
}
|
||||
|
||||
// Sort both transactions according to the agreed upon cannonical
|
||||
// ordering. This ensures that both parties sign the same sighash
|
||||
// without further synchronization.
|
||||
@ -1291,6 +1325,40 @@ func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
|
||||
return masterElkremRoot.ECPrivKey()
|
||||
}
|
||||
|
||||
// deriveStateHintObsfucator derives the bytes to be used for obsfucatating the
|
||||
// state hints from the elkerem root to be used for a new channel. The
|
||||
// obsfucator is generated by performing an additional sha256 hash of the first
|
||||
// child derived from the elkrem root. The leading 4 bytes are used for the
|
||||
// obsfucator.
|
||||
func deriveStateHintObsfucator(elkremRoot *elkrem.ElkremSender) ([StateHintSize]byte, error) {
|
||||
var obsfucator [StateHintSize]byte
|
||||
|
||||
firstChild, err := elkremRoot.AtIndex(0)
|
||||
if err != nil {
|
||||
return obsfucator, err
|
||||
}
|
||||
|
||||
grandChild := fastsha256.Sum256(firstChild[:])
|
||||
copy(obsfucator[:], grandChild[:])
|
||||
|
||||
return obsfucator, nil
|
||||
}
|
||||
|
||||
// initStateHints properly sets the obsfucated state ints ob both commitment
|
||||
// transactions using the passed obsfucator.
|
||||
func initStateHints(commit1, commit2 *wire.MsgTx,
|
||||
obsfucator [StateHintSize]byte) error {
|
||||
|
||||
if err := SetStateNumHint(commit1, 0, obsfucator); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SetStateNumHint(commit2, 0, obsfucator); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// selectInputs selects a slice of inputs necessary to meet the specified
|
||||
// selection amount. If input selection is unable to suceed to to insuffcient
|
||||
// funds, a non-nil error is returned. Additionally, the total amount of the
|
||||
|
Loading…
Reference in New Issue
Block a user