server+htlcswitch: prevent privacy leaks, allow alias routing

This intent of this change is to prevent privacy leaks when routing
with aliases and also to allow routing when using an alias. The
aliases are our aliases.

Introduces are two maps:

* aliasToReal:
  This is an N->1 mapping for a channel. The keys are the set of
  aliases and the value is the confirmed, on-chain SCID.

* baseIndex:
  This is also an N->1 mapping for a channel. The keys are the set
  of aliases and the value is the "base" SCID (whatever is in the
  OpenChannel.ShortChannelID field). There is also a base->base
  mapping, so not all keys are aliases.

The above maps are populated when a link is added to the switch and
when the channel has confirmed on-chain. The maps are not removed
from if the link is removed, but this is fine since forwarding won't
occur.

* getLinkByMapping
  This function is introduced to adhere to the spec requirements that
  using the confirmed SCID of a private, scid-alias-feature-bit
  channel does not work. Lnd implements a stricter version of the spec
  and disallows this behavior if the feature-bit was negotiated, rather
  than just the channel type. The old, privacy-leak behavior is
  preserved.

The spec also requires that if we must fail back an HTLC, the
ChannelUpdate must use the SCID of whatever was in the onion, to avoid
a privacy leak. This is also done by passing in the relevant SCID to
the mailbox and link. Lnd will also cancel back on the "incoming" side
if the InterceptableSwitch was used or if the link failed to decrypt
the onion. In this case, we are cautious and replace the SCID if an
alias exists.
This commit is contained in:
eugene
2022-04-04 16:44:51 -04:00
parent 59fea192b7
commit 13c15e8038
12 changed files with 2406 additions and 298 deletions

View File

@ -15,6 +15,7 @@ import (
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
@ -33,6 +34,10 @@ import (
"github.com/lightningnetwork/lnd/ticker"
)
func isAlias(scid lnwire.ShortChannelID) bool {
return scid.BlockHeight >= 16_000_000 && scid.BlockHeight < 16_250_000
}
type mockPreimageCache struct {
sync.Mutex
preimageMap map[lntypes.Hash]lntypes.Preimage
@ -180,6 +185,12 @@ func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error)
}
}
signAliasUpdate := func(u *lnwire.ChannelUpdate) (*ecdsa.Signature,
error) {
return testSig, nil
}
cfg := Config{
DB: db,
FetchAllOpenChannels: db.ChannelStateDB().FetchAllOpenChannels,
@ -188,21 +199,27 @@ func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error)
FwdingLog: &mockForwardingLog{
events: make(map[time.Time]channeldb.ForwardingEvent),
},
FetchLastChannelUpdate: func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) {
return &lnwire.ChannelUpdate{}, nil
FetchLastChannelUpdate: func(scid lnwire.ShortChannelID) (
*lnwire.ChannelUpdate, error) {
return &lnwire.ChannelUpdate{
ShortChannelID: scid,
}, nil
},
Notifier: &mock.ChainNotifier{
SpendChan: make(chan *chainntnfs.SpendDetail),
EpochChan: make(chan *chainntnfs.BlockEpoch),
ConfChan: make(chan *chainntnfs.TxConfirmation),
},
FwdEventTicker: ticker.NewForce(DefaultFwdEventInterval),
LogEventTicker: ticker.NewForce(DefaultLogInterval),
AckEventTicker: ticker.NewForce(DefaultAckInterval),
HtlcNotifier: &mockHTLCNotifier{},
Clock: clock.NewDefaultClock(),
HTLCExpiry: time.Hour,
DustThreshold: DefaultDustThreshold,
FwdEventTicker: ticker.NewForce(DefaultFwdEventInterval),
LogEventTicker: ticker.NewForce(DefaultLogInterval),
AckEventTicker: ticker.NewForce(DefaultAckInterval),
HtlcNotifier: &mockHTLCNotifier{},
Clock: clock.NewDefaultClock(),
HTLCExpiry: time.Hour,
DustThreshold: DefaultDustThreshold,
SignAliasUpdate: signAliasUpdate,
IsAlias: isAlias,
}
return New(cfg, startingHeight)
@ -658,6 +675,11 @@ type mockChannelLink struct {
shortChanID lnwire.ShortChannelID
// Only used for zero-conf channels.
realScid lnwire.ShortChannelID
aliases []lnwire.ShortChannelID
chanID lnwire.ChannelID
peer lnpeer.Peer
@ -668,11 +690,22 @@ type mockChannelLink struct {
eligible bool
unadvertised bool
zeroConf bool
optionFeature bool
htlcID uint64
checkHtlcTransitResult *LinkError
checkHtlcForwardResult *LinkError
failAliasUpdate func(sid lnwire.ShortChannelID,
incoming bool) *lnwire.ChannelUpdate
confirmedZC bool
}
// completeCircuit is a helper method for adding the finalized payment circuit
@ -712,16 +745,39 @@ func (f *mockChannelLink) deleteCircuit(pkt *htlcPacket) error {
}
func newMockChannelLink(htlcSwitch *Switch, chanID lnwire.ChannelID,
shortChanID lnwire.ShortChannelID, peer lnpeer.Peer, eligible bool,
shortChanID, realScid lnwire.ShortChannelID, peer lnpeer.Peer,
eligible, unadvertised, zeroConf, optionFeature bool,
) *mockChannelLink {
return &mockChannelLink{
htlcSwitch: htlcSwitch,
chanID: chanID,
shortChanID: shortChanID,
peer: peer,
eligible: eligible,
aliases := make([]lnwire.ShortChannelID, 0)
var realConfirmed bool
if zeroConf {
aliases = append(aliases, shortChanID)
}
if realScid != hop.Source {
realConfirmed = true
}
return &mockChannelLink{
htlcSwitch: htlcSwitch,
chanID: chanID,
shortChanID: shortChanID,
realScid: realScid,
peer: peer,
eligible: eligible,
unadvertised: unadvertised,
zeroConf: zeroConf,
optionFeature: optionFeature,
aliases: aliases,
confirmedZC: realConfirmed,
}
}
// addAlias is not part of any interface method.
func (f *mockChannelLink) addAlias(alias lnwire.ShortChannelID) {
f.aliases = append(f.aliases, alias)
}
func (f *mockChannelLink) handleSwitchPacket(pkt *htlcPacket) error {
@ -750,7 +806,8 @@ func (f *mockChannelLink) HandleChannelUpdate(lnwire.Message) {
func (f *mockChannelLink) UpdateForwardingPolicy(_ ForwardingPolicy) {
}
func (f *mockChannelLink) CheckHtlcForward([32]byte, lnwire.MilliSatoshi,
lnwire.MilliSatoshi, uint32, uint32, uint32) *LinkError {
lnwire.MilliSatoshi, uint32, uint32, uint32,
lnwire.ShortChannelID) *LinkError {
return f.checkHtlcForwardResult
}
@ -772,6 +829,32 @@ func (f *mockChannelLink) AttachMailBox(mailBox MailBox) {
mailBox.SetDustClosure(f.getDustClosure())
}
func (f *mockChannelLink) attachFailAliasUpdate(closure func(
sid lnwire.ShortChannelID, incoming bool) *lnwire.ChannelUpdate) {
f.failAliasUpdate = closure
}
func (f *mockChannelLink) getAliases() []lnwire.ShortChannelID {
return f.aliases
}
func (f *mockChannelLink) isZeroConf() bool {
return f.zeroConf
}
func (f *mockChannelLink) negotiatedAliasFeature() bool {
return f.optionFeature
}
func (f *mockChannelLink) confirmedScid() lnwire.ShortChannelID {
return f.realScid
}
func (f *mockChannelLink) zeroConfConfirmed() bool {
return f.confirmedZC
}
func (f *mockChannelLink) Start() error {
f.mailBox.ResetMessages()
f.mailBox.ResetPackets()
@ -788,6 +871,7 @@ func (f *mockChannelLink) EligibleToForward() bool { return
func (f *mockChannelLink) MayAddOutgoingHtlc(lnwire.MilliSatoshi) error { return nil }
func (f *mockChannelLink) ShutdownIfChannelClean() error { return nil }
func (f *mockChannelLink) setLiveShortChanID(sid lnwire.ShortChannelID) { f.shortChanID = sid }
func (f *mockChannelLink) IsUnadvertised() bool { return f.unadvertised }
func (f *mockChannelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
f.eligible = true
return f.shortChanID, nil