From 7cbf0326c72a6ff447453e1743b07f5172b10dc7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Sun, 10 Mar 2019 16:14:28 -0700 Subject: [PATCH] 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. --- chanbackup/backupfile.go | 6 +- chanbackup/single.go | 183 ++++++++++++++++++++++++++++++++------ chanbackup/single_test.go | 77 ++++++++++++++-- 3 files changed, 226 insertions(+), 40 deletions(-) diff --git a/chanbackup/backupfile.go b/chanbackup/backupfile.go index 67e1d18f..5a996547 100644 --- a/chanbackup/backupfile.go +++ b/chanbackup/backupfile.go @@ -96,17 +96,17 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error { var err error b.tempFile, err = os.Create(b.tempFileName) 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 // remove the temporary file all together once this method exits. _, err = b.tempFile.Write([]byte(newBackup)) if err != nil { - return err + return fmt.Errorf("unable to write backup to temp file: %v", err) } if err := b.tempFile.Sync(); err != nil { - return err + return fmt.Errorf("unable to sync temp file: %v", err) } defer os.Remove(b.tempFileName) diff --git a/chanbackup/single.go b/chanbackup/single.go index 5cef73b8..a708c855 100644 --- a/chanbackup/single.go +++ b/chanbackup/single.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" @@ -40,6 +41,11 @@ type Single struct { // pack the single backup. 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 // channel will be opened within. This value is typically the genesis // 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. Addresses []net.Addr - // CsvDelay is the local CSV delay used within the channel. We may need - // this value to reconstruct our script to recover the funds on-chain - // after a force close. - CsvDelay uint16 + // Capacity is the size of the original channel. + Capacity btcutil.Amount - // PaymentBasePoint describes how to derive base public that's used to - // deriving the key used within the non-delayed 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. - PaymentBasePoint keychain.KeyLocator + // LocalChanCfg is our local channel configuration. It contains all the + // information we need to re-derive the keys we used within the + // channel. Most importantly, it allows to derive the base public + // that's used to deriving the key used within the non-delayed + // 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 // used as the shachain root for this channel. @@ -88,8 +107,6 @@ type Single struct { func NewSingle(channel *channeldb.OpenChannel, nodeAddrs []net.Addr) Single { - chanCfg := channel.LocalChanCfg - // TODO(roasbeef): update after we start to store the KeyLoc for // shachain root @@ -105,13 +122,16 @@ func NewSingle(channel *channeldb.OpenChannel, _, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes()) return Single{ - ChainHash: channel.ChainHash, - FundingOutpoint: channel.FundingOutpoint, - ShortChannelID: channel.ShortChannelID, - RemoteNodePub: channel.IdentityPub, - Addresses: nodeAddrs, - CsvDelay: chanCfg.CsvDelay, - PaymentBasePoint: chanCfg.PaymentBasePoint.KeyLocator, + Version: DefaultSingleVersion, + IsInitiator: channel.IsInitiator, + ChainHash: channel.ChainHash, + FundingOutpoint: channel.FundingOutpoint, + ShortChannelID: channel.ShortChannelID, + RemoteNodePub: channel.IdentityPub, + Addresses: nodeAddrs, + Capacity: channel.Capacity, + LocalChanCfg: channel.LocalChanCfg, + RemoteChanCfg: channel.RemoteChanCfg, ShaChainRootDesc: keychain.KeyDescriptor{ PubKey: shaChainPoint, KeyLocator: keychain.KeyLocator{ @@ -150,14 +170,39 @@ func (s *Single) Serialize(w io.Writer) error { var singleBytes bytes.Buffer if err := lnwire.WriteElements( &singleBytes, + s.IsInitiator, s.ChainHash[:], s.FundingOutpoint, s.ShortChannelID, s.RemoteNodePub, s.Addresses, - s.CsvDelay, - uint32(s.PaymentBasePoint.Family), - s.PaymentBasePoint.Index, + s.Capacity, + + 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[:], uint32(s.ShaChainRootDesc.KeyLocator.Family), s.ShaChainRootDesc.KeyLocator.Index, @@ -201,6 +246,49 @@ func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error { 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 // passed io.Reader. If the method is successful, then the target // StaticChannelBackup will be fully populated. @@ -228,20 +316,59 @@ func (s *Single) Deserialize(r io.Reader) error { } err = lnwire.ReadElements( - r, s.ChainHash[:], &s.FundingOutpoint, &s.ShortChannelID, - &s.RemoteNodePub, &s.Addresses, &s.CsvDelay, + r, &s.IsInitiator, s.ChainHash[:], &s.FundingOutpoint, + &s.ShortChannelID, &s.RemoteNodePub, &s.Addresses, &s.Capacity, ) if err != nil { return err } - var keyFam uint32 - if err := lnwire.ReadElements(r, &keyFam); err != nil { + err = lnwire.ReadElements(r, &s.LocalChanCfg.CsvDelay) + 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 } - 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 { 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 - // ben specified or not. + // been specified or not. if !bytes.Equal(shaChainPub[:], zeroPub[:]) { s.ShaChainRootDesc.PubKey, err = btcec.ParsePubKey( shaChainPub[:], btcec.S256(), diff --git a/chanbackup/single_test.go b/chanbackup/single_test.go index b63a2226..41f7bca8 100644 --- a/chanbackup/single_test.go +++ b/chanbackup/single_test.go @@ -42,6 +42,10 @@ func assertSingleEqual(t *testing.T, a, b Single) { t.Fatalf("versions don't match: %v vs %v", a.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 { t.Fatalf("chainhash doesn't match: %v vs %v", a.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", 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) { t.Fatalf("node pubs don't match %x vs %x", a.RemoteNodePub.SerializeCompressed(), b.RemoteNodePub.SerializeCompressed()) } - if a.CsvDelay != b.CsvDelay { - t.Fatalf("csv delay doesn't match: %v vs %v", a.CsvDelay, - b.CsvDelay) + if !reflect.DeepEqual(a.LocalChanCfg, b.LocalChanCfg) { + t.Fatalf("local chan config doesn't match: %v vs %v", + spew.Sdump(a.LocalChanCfg), + spew.Sdump(b.LocalChanCfg)) } - if !reflect.DeepEqual(a.PaymentBasePoint, b.PaymentBasePoint) { - t.Fatalf("base point doesn't match: %v vs %v", - spew.Sdump(a.PaymentBasePoint), - spew.Sdump(b.PaymentBasePoint)) + if !reflect.DeepEqual(a.RemoteChanCfg, b.RemoteChanCfg) { + t.Fatalf("remote chan config doesn't match: %v vs %v", + spew.Sdump(a.RemoteChanCfg), + spew.Sdump(b.RemoteChanCfg)) } if !reflect.DeepEqual(a.ShaChainRootDesc, b.ShaChainRootDesc) { t.Fatalf("sha chain point doesn't match: %v vs %v", - spew.Sdump(a.PaymentBasePoint), - spew.Sdump(b.PaymentBasePoint)) + spew.Sdump(a.ShaChainRootDesc), + spew.Sdump(b.ShaChainRootDesc)) } if len(a.Addresses) != len(b.Addresses) { @@ -110,8 +119,14 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) { shaChainProducer := shachain.NewRevocationProducer(shaChainRoot) + var isInitiator bool + if rand.Int63()%2 == 0 { + isInitiator = true + } + return &channeldb.OpenChannel{ ChainHash: chainHash, + IsInitiator: isInitiator, FundingOutpoint: chanPoint, ShortChannelID: lnwire.NewShortChanIDFromInt( uint64(rand.Int63()), @@ -121,12 +136,56 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) { ChannelConstraints: channeldb.ChannelConstraints{ 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{ KeyLocator: keychain.KeyLocator{ Family: keychain.KeyFamily(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, }, nil