chanbackup: extend channel backups to include entire local+remote chan config

In this commit, we extend the prior Single format to include the entire
channel config, other than the constraints, but including the CSV delay
for both sides. We do this as we'll need more of the keying information
in order to properly execute the DLP protocol. Additionally, in the
future, if warranted, this would allow channels to be resumed if deemed
safe.
This commit is contained in:
Olaoluwa Osuntokun 2019-03-10 16:14:28 -07:00
parent d7bc93b6d3
commit 7cbf0326c7
No known key found for this signature in database
GPG Key ID: CE58F7F8E20FD9A2
3 changed files with 226 additions and 40 deletions

@ -96,17 +96,17 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
var err error var err error
b.tempFile, err = os.Create(b.tempFileName) b.tempFile, err = os.Create(b.tempFileName)
if err != nil { if err != nil {
return err return fmt.Errorf("unable to create temp file: %v", err)
} }
// With the file created, we'll write the new packed multi backup and // With the file created, we'll write the new packed multi backup and
// remove the temporary file all together once this method exits. // remove the temporary file all together once this method exits.
_, err = b.tempFile.Write([]byte(newBackup)) _, err = b.tempFile.Write([]byte(newBackup))
if err != nil { if err != nil {
return err return fmt.Errorf("unable to write backup to temp file: %v", err)
} }
if err := b.tempFile.Sync(); err != nil { if err := b.tempFile.Sync(); err != nil {
return err return fmt.Errorf("unable to sync temp file: %v", err)
} }
defer os.Remove(b.tempFileName) defer os.Remove(b.tempFileName)

@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@ -40,6 +41,11 @@ type Single struct {
// pack the single backup. // pack the single backup.
Version SingleBackupVersion Version SingleBackupVersion
// IsInitiator is true if we were the initiator of the channel, and
// false otherwise. We'll need to know this information in order to
// properly re-derive the state hint information.
IsInitiator bool
// ChainHash is a hash which represents the blockchain that this // ChainHash is a hash which represents the blockchain that this
// channel will be opened within. This value is typically the genesis // channel will be opened within. This value is typically the genesis
// hash. In the case that the original chain went through a contentious // hash. In the case that the original chain went through a contentious
@ -66,16 +72,29 @@ type Single struct {
// authenticated connection for the stored identity public key. // authenticated connection for the stored identity public key.
Addresses []net.Addr Addresses []net.Addr
// CsvDelay is the local CSV delay used within the channel. We may need // Capacity is the size of the original channel.
// this value to reconstruct our script to recover the funds on-chain Capacity btcutil.Amount
// after a force close.
CsvDelay uint16
// PaymentBasePoint describes how to derive base public that's used to // LocalChanCfg is our local channel configuration. It contains all the
// deriving the key used within the non-delayed pay-to-self output on // information we need to re-derive the keys we used within the
// the commitment transaction for a node. With this information, we can // channel. Most importantly, it allows to derive the base public
// re-derive the private key needed to sweep the funds on-chain. // that's used to deriving the key used within the non-delayed
PaymentBasePoint keychain.KeyLocator // pay-to-self output on the commitment transaction for a node. With
// this information, we can re-derive the private key needed to sweep
// the funds on-chain.
//
// NOTE: Of the items in the ChannelConstraints, we only write the CSV
// delay.
LocalChanCfg channeldb.ChannelConfig
// RemoteChanCfg is the remote channel confirmation. We store this as
// well since we'll need some of their keys to re-derive things like
// the state hint obfuscator which will allow us to recognize the state
// their broadcast on chain.
//
// NOTE: Of the items in the ChannelConstraints, we only write the CSV
// delay.
RemoteChanCfg channeldb.ChannelConfig
// ShaChainRootDesc describes how to derive the private key that was // ShaChainRootDesc describes how to derive the private key that was
// used as the shachain root for this channel. // used as the shachain root for this channel.
@ -88,8 +107,6 @@ type Single struct {
func NewSingle(channel *channeldb.OpenChannel, func NewSingle(channel *channeldb.OpenChannel,
nodeAddrs []net.Addr) Single { nodeAddrs []net.Addr) Single {
chanCfg := channel.LocalChanCfg
// TODO(roasbeef): update after we start to store the KeyLoc for // TODO(roasbeef): update after we start to store the KeyLoc for
// shachain root // shachain root
@ -105,13 +122,16 @@ func NewSingle(channel *channeldb.OpenChannel,
_, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes()) _, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes())
return Single{ return Single{
ChainHash: channel.ChainHash, Version: DefaultSingleVersion,
FundingOutpoint: channel.FundingOutpoint, IsInitiator: channel.IsInitiator,
ShortChannelID: channel.ShortChannelID, ChainHash: channel.ChainHash,
RemoteNodePub: channel.IdentityPub, FundingOutpoint: channel.FundingOutpoint,
Addresses: nodeAddrs, ShortChannelID: channel.ShortChannelID,
CsvDelay: chanCfg.CsvDelay, RemoteNodePub: channel.IdentityPub,
PaymentBasePoint: chanCfg.PaymentBasePoint.KeyLocator, Addresses: nodeAddrs,
Capacity: channel.Capacity,
LocalChanCfg: channel.LocalChanCfg,
RemoteChanCfg: channel.RemoteChanCfg,
ShaChainRootDesc: keychain.KeyDescriptor{ ShaChainRootDesc: keychain.KeyDescriptor{
PubKey: shaChainPoint, PubKey: shaChainPoint,
KeyLocator: keychain.KeyLocator{ KeyLocator: keychain.KeyLocator{
@ -150,14 +170,39 @@ func (s *Single) Serialize(w io.Writer) error {
var singleBytes bytes.Buffer var singleBytes bytes.Buffer
if err := lnwire.WriteElements( if err := lnwire.WriteElements(
&singleBytes, &singleBytes,
s.IsInitiator,
s.ChainHash[:], s.ChainHash[:],
s.FundingOutpoint, s.FundingOutpoint,
s.ShortChannelID, s.ShortChannelID,
s.RemoteNodePub, s.RemoteNodePub,
s.Addresses, s.Addresses,
s.CsvDelay, s.Capacity,
uint32(s.PaymentBasePoint.Family),
s.PaymentBasePoint.Index, s.LocalChanCfg.CsvDelay,
// We only need to write out the KeyLocator portion of the
// local channel config.
uint32(s.LocalChanCfg.MultiSigKey.Family),
s.LocalChanCfg.MultiSigKey.Index,
uint32(s.LocalChanCfg.RevocationBasePoint.Family),
s.LocalChanCfg.RevocationBasePoint.Index,
uint32(s.LocalChanCfg.PaymentBasePoint.Family),
s.LocalChanCfg.PaymentBasePoint.Index,
uint32(s.LocalChanCfg.DelayBasePoint.Family),
s.LocalChanCfg.DelayBasePoint.Index,
uint32(s.LocalChanCfg.HtlcBasePoint.Family),
s.LocalChanCfg.HtlcBasePoint.Index,
s.RemoteChanCfg.CsvDelay,
// We only need to write out the raw pubkey for the remote
// channel config.
s.RemoteChanCfg.MultiSigKey.PubKey,
s.RemoteChanCfg.RevocationBasePoint.PubKey,
s.RemoteChanCfg.PaymentBasePoint.PubKey,
s.RemoteChanCfg.DelayBasePoint.PubKey,
s.RemoteChanCfg.HtlcBasePoint.PubKey,
shaChainPub[:], shaChainPub[:],
uint32(s.ShaChainRootDesc.KeyLocator.Family), uint32(s.ShaChainRootDesc.KeyLocator.Family),
s.ShaChainRootDesc.KeyLocator.Index, s.ShaChainRootDesc.KeyLocator.Index,
@ -201,6 +246,49 @@ func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
return encryptPayloadToWriter(rawBytes, w, keyRing) return encryptPayloadToWriter(rawBytes, w, keyRing)
} }
// readLocalKeyDesc reads a KeyDescriptor encoded within an unpacked Single.
// For local KeyDescs, we only write out the KeyLocator information as we can
// re-derive the pubkey from it.
func readLocalKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
var keyDesc keychain.KeyDescriptor
var keyFam uint32
if err := lnwire.ReadElements(r, &keyFam); err != nil {
return keyDesc, err
}
keyDesc.Family = keychain.KeyFamily(keyFam)
if err := lnwire.ReadElements(r, &keyDesc.Index); err != nil {
return keyDesc, err
}
return keyDesc, nil
}
// readRemoteKeyDesc reads a remote KeyDescriptor encoded within an unpacked
// Single. For remote KeyDescs, we write out only the PubKey since we don't
// actually have the KeyLocator data.
func readRemoteKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
var (
keyDesc keychain.KeyDescriptor
pub [33]byte
)
_, err := io.ReadFull(r, pub[:])
if err != nil {
return keyDesc, nil
}
keyDesc.PubKey, err = btcec.ParsePubKey(pub[:], btcec.S256())
if err != nil {
return keyDesc, nil
}
keyDesc.PubKey.Curve = nil
return keyDesc, nil
}
// Deserialize attempts to read the raw plaintext serialized SCB from the // Deserialize attempts to read the raw plaintext serialized SCB from the
// passed io.Reader. If the method is successful, then the target // passed io.Reader. If the method is successful, then the target
// StaticChannelBackup will be fully populated. // StaticChannelBackup will be fully populated.
@ -228,20 +316,59 @@ func (s *Single) Deserialize(r io.Reader) error {
} }
err = lnwire.ReadElements( err = lnwire.ReadElements(
r, s.ChainHash[:], &s.FundingOutpoint, &s.ShortChannelID, r, &s.IsInitiator, s.ChainHash[:], &s.FundingOutpoint,
&s.RemoteNodePub, &s.Addresses, &s.CsvDelay, &s.ShortChannelID, &s.RemoteNodePub, &s.Addresses, &s.Capacity,
) )
if err != nil { if err != nil {
return err return err
} }
var keyFam uint32 err = lnwire.ReadElements(r, &s.LocalChanCfg.CsvDelay)
if err := lnwire.ReadElements(r, &keyFam); err != nil { if err != nil {
return err
}
s.LocalChanCfg.MultiSigKey, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.RevocationBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.PaymentBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.DelayBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.HtlcBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err return err
} }
s.PaymentBasePoint.Family = keychain.KeyFamily(keyFam)
err = lnwire.ReadElements(r, &s.PaymentBasePoint.Index) err = lnwire.ReadElements(r, &s.RemoteChanCfg.CsvDelay)
if err != nil {
return err
}
s.RemoteChanCfg.MultiSigKey, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.RevocationBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.PaymentBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.DelayBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.HtlcBasePoint, err = readRemoteKeyDesc(r)
if err != nil { if err != nil {
return err return err
} }
@ -256,7 +383,7 @@ func (s *Single) Deserialize(r io.Reader) error {
} }
// Since this field is optional, we'll check to see if the pubkey has // Since this field is optional, we'll check to see if the pubkey has
// ben specified or not. // been specified or not.
if !bytes.Equal(shaChainPub[:], zeroPub[:]) { if !bytes.Equal(shaChainPub[:], zeroPub[:]) {
s.ShaChainRootDesc.PubKey, err = btcec.ParsePubKey( s.ShaChainRootDesc.PubKey, err = btcec.ParsePubKey(
shaChainPub[:], btcec.S256(), shaChainPub[:], btcec.S256(),

@ -42,6 +42,10 @@ func assertSingleEqual(t *testing.T, a, b Single) {
t.Fatalf("versions don't match: %v vs %v", a.Version, t.Fatalf("versions don't match: %v vs %v", a.Version,
b.Version) b.Version)
} }
if a.IsInitiator != b.IsInitiator {
t.Fatalf("initiators don't match: %v vs %v", a.IsInitiator,
b.IsInitiator)
}
if a.ChainHash != b.ChainHash { if a.ChainHash != b.ChainHash {
t.Fatalf("chainhash doesn't match: %v vs %v", a.ChainHash, t.Fatalf("chainhash doesn't match: %v vs %v", a.ChainHash,
b.ChainHash) b.ChainHash)
@ -54,24 +58,29 @@ func assertSingleEqual(t *testing.T, a, b Single) {
t.Fatalf("chan id doesn't match: %v vs %v", t.Fatalf("chan id doesn't match: %v vs %v",
a.ShortChannelID, b.ShortChannelID) a.ShortChannelID, b.ShortChannelID)
} }
if a.Capacity != b.Capacity {
t.Fatalf("capacity doesn't match: %v vs %v",
a.Capacity, b.Capacity)
}
if !a.RemoteNodePub.IsEqual(b.RemoteNodePub) { if !a.RemoteNodePub.IsEqual(b.RemoteNodePub) {
t.Fatalf("node pubs don't match %x vs %x", t.Fatalf("node pubs don't match %x vs %x",
a.RemoteNodePub.SerializeCompressed(), a.RemoteNodePub.SerializeCompressed(),
b.RemoteNodePub.SerializeCompressed()) b.RemoteNodePub.SerializeCompressed())
} }
if a.CsvDelay != b.CsvDelay { if !reflect.DeepEqual(a.LocalChanCfg, b.LocalChanCfg) {
t.Fatalf("csv delay doesn't match: %v vs %v", a.CsvDelay, t.Fatalf("local chan config doesn't match: %v vs %v",
b.CsvDelay) spew.Sdump(a.LocalChanCfg),
spew.Sdump(b.LocalChanCfg))
} }
if !reflect.DeepEqual(a.PaymentBasePoint, b.PaymentBasePoint) { if !reflect.DeepEqual(a.RemoteChanCfg, b.RemoteChanCfg) {
t.Fatalf("base point doesn't match: %v vs %v", t.Fatalf("remote chan config doesn't match: %v vs %v",
spew.Sdump(a.PaymentBasePoint), spew.Sdump(a.RemoteChanCfg),
spew.Sdump(b.PaymentBasePoint)) spew.Sdump(b.RemoteChanCfg))
} }
if !reflect.DeepEqual(a.ShaChainRootDesc, b.ShaChainRootDesc) { if !reflect.DeepEqual(a.ShaChainRootDesc, b.ShaChainRootDesc) {
t.Fatalf("sha chain point doesn't match: %v vs %v", t.Fatalf("sha chain point doesn't match: %v vs %v",
spew.Sdump(a.PaymentBasePoint), spew.Sdump(a.ShaChainRootDesc),
spew.Sdump(b.PaymentBasePoint)) spew.Sdump(b.ShaChainRootDesc))
} }
if len(a.Addresses) != len(b.Addresses) { if len(a.Addresses) != len(b.Addresses) {
@ -110,8 +119,14 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) {
shaChainProducer := shachain.NewRevocationProducer(shaChainRoot) shaChainProducer := shachain.NewRevocationProducer(shaChainRoot)
var isInitiator bool
if rand.Int63()%2 == 0 {
isInitiator = true
}
return &channeldb.OpenChannel{ return &channeldb.OpenChannel{
ChainHash: chainHash, ChainHash: chainHash,
IsInitiator: isInitiator,
FundingOutpoint: chanPoint, FundingOutpoint: chanPoint,
ShortChannelID: lnwire.NewShortChanIDFromInt( ShortChannelID: lnwire.NewShortChanIDFromInt(
uint64(rand.Int63()), uint64(rand.Int63()),
@ -121,12 +136,56 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) {
ChannelConstraints: channeldb.ChannelConstraints{ ChannelConstraints: channeldb.ChannelConstraints{
CsvDelay: uint16(rand.Int63()), CsvDelay: uint16(rand.Int63()),
}, },
MultiSigKey: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
RevocationBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
PaymentBasePoint: keychain.KeyDescriptor{ PaymentBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{ KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()), Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()), Index: uint32(rand.Int63()),
}, },
}, },
DelayBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
HtlcBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
},
RemoteChanCfg: channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
CsvDelay: uint16(rand.Int63()),
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: pub,
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
}, },
RevocationProducer: shaChainProducer, RevocationProducer: shaChainProducer,
}, nil }, nil