diff --git a/channeldb/db.go b/channeldb/db.go index 1e210d032..c28d1cfbc 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -26,6 +26,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/migration29" "github.com/lightningnetwork/lnd/channeldb/migration30" "github.com/lightningnetwork/lnd/channeldb/migration31" + "github.com/lightningnetwork/lnd/channeldb/migration32" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/invoices" @@ -286,6 +287,10 @@ var ( number: 31, migration: migration31.DeleteLastPublishedTxTLB, }, + { + number: 32, + migration: migration32.MigrateWaitingProofStore, + }, } // optionalVersions stores all optional migrations that are applied diff --git a/channeldb/log.go b/channeldb/log.go index a53d662cd..e50e5054e 100644 --- a/channeldb/log.go +++ b/channeldb/log.go @@ -10,6 +10,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/migration24" "github.com/lightningnetwork/lnd/channeldb/migration30" "github.com/lightningnetwork/lnd/channeldb/migration31" + "github.com/lightningnetwork/lnd/channeldb/migration32" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/kvdb" ) @@ -42,5 +43,6 @@ func UseLogger(logger btclog.Logger) { migration24.UseLogger(logger) migration30.UseLogger(logger) migration31.UseLogger(logger) + migration32.UseLogger(logger) kvdb.UseLogger(logger) } diff --git a/channeldb/migration32/log.go b/channeldb/migration32/log.go new file mode 100644 index 000000000..98709c28e --- /dev/null +++ b/channeldb/migration32/log.go @@ -0,0 +1,14 @@ +package migration32 + +import ( + "github.com/btcsuite/btclog" +) + +// log is a logger that is initialized as disabled. This means the package will +// not perform any logging by default until a logger is set. +var log = btclog.Disabled + +// UseLogger uses a specified Logger to output package logging info. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/channeldb/migration32/migration.go b/channeldb/migration32/migration.go new file mode 100644 index 000000000..c6a3fe818 --- /dev/null +++ b/channeldb/migration32/migration.go @@ -0,0 +1,55 @@ +package migration32 + +import ( + "bytes" + "fmt" + + "github.com/lightningnetwork/lnd/kvdb" +) + +// waitingProofsBucketKey byte string name of the waiting proofs store. +var waitingProofsBucketKey = []byte("waitingproofs") + +// MigrateWaitingProofStore migrates the waiting proof store so that all entries +// are prefixed with a type byte. +func MigrateWaitingProofStore(tx kvdb.RwTx) error { + log.Infof("Migrating waiting proof store") + + bucket := tx.ReadWriteBucket(waitingProofsBucketKey) + + // If the bucket does not exist yet, then there are no entries to + // migrate. + if bucket == nil { + return nil + } + + return bucket.ForEach(func(k, v []byte) error { + // Skip buckets fields. + if v == nil { + return nil + } + + // Read in the waiting proof using the legacy decoding method. + var proof WaitingProof + if err := proof.LegacyDecode(bytes.NewReader(v)); err != nil { + return err + } + + // Do sanity check to ensure that the proof key is the same as + // the key used to store the proof. + key := proof.Key() + if !bytes.Equal(key[:], k) { + return fmt.Errorf("proof key (%x) does not match "+ + "the key used to store the proof: %x", key, k) + } + + // Re-encode the proof using the new, type-prefixed encoding. + var b bytes.Buffer + err := proof.UpdatedEncode(&b) + if err != nil { + return err + } + + return bucket.Put(k, b.Bytes()) + }) +} diff --git a/channeldb/migration32/migration_test.go b/channeldb/migration32/migration_test.go new file mode 100644 index 000000000..980f201af --- /dev/null +++ b/channeldb/migration32/migration_test.go @@ -0,0 +1,100 @@ +package migration32 + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" + "github.com/lightningnetwork/lnd/channeldb/migtest" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/stretchr/testify/require" +) + +var ( + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571" + + "319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a" + + "88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) + + sig, _ = lnwire.NewSigFromSignature(testSig) + + wp1 = &WaitingProof{ + AnnounceSignatures: &lnwire.AnnounceSignatures{ + ChannelID: lnwire.ChannelID{1}, + ShortChannelID: lnwire.NewShortChanIDFromInt(1), + NodeSignature: sig, + BitcoinSignature: sig, + ExtraOpaqueData: []byte{1, 2, 3, 4}, + }, + isRemote: false, + } + + wp2 = &WaitingProof{ + AnnounceSignatures: &lnwire.AnnounceSignatures{ + ChannelID: lnwire.ChannelID{2}, + ShortChannelID: lnwire.NewShortChanIDFromInt(2), + NodeSignature: sig, + BitcoinSignature: sig, + }, + isRemote: true, + } +) + +// TestMigrationWaitingProofStore tests that the MigrateWaitingProofStore +// function works as expected. +func TestMigrateWaitingProofStore(t *testing.T) { + var ( + key1 = wp1.Key() + key2 = wp2.Key() + wp1BytesBefore bytes.Buffer + wp2BytesBefore bytes.Buffer + wp1BytesAfter bytes.Buffer + wp2BytesAfter bytes.Buffer + ) + + err := wp1.LegacyEncode(&wp1BytesBefore) + require.NoError(t, err) + + err = wp2.LegacyEncode(&wp2BytesBefore) + require.NoError(t, err) + + wpStoreBefore := map[string]interface{}{ + string(key1[:]): wp1BytesBefore.String(), + string(key2[:]): wp2BytesBefore.String(), + } + + err = wp1.UpdatedEncode(&wp1BytesAfter) + require.NoError(t, err) + + err = wp2.UpdatedEncode(&wp2BytesAfter) + require.NoError(t, err) + + wpStoreAfter := map[string]interface{}{ + string(key1[:]): wp1BytesAfter.String(), + string(key2[:]): wp2BytesAfter.String(), + } + + before := func(tx kvdb.RwTx) error { + return migtest.RestoreDB( + tx, waitingProofsBucketKey, wpStoreBefore, + ) + } + + after := func(tx kvdb.RwTx) error { + return migtest.VerifyDB( + tx, waitingProofsBucketKey, wpStoreAfter, + ) + } + + migtest.ApplyMigration( + t, before, after, MigrateWaitingProofStore, false, + ) +} diff --git a/channeldb/migration32/waitingproof.go b/channeldb/migration32/waitingproof.go new file mode 100644 index 000000000..c1da23b61 --- /dev/null +++ b/channeldb/migration32/waitingproof.go @@ -0,0 +1,100 @@ +package migration32 + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + + lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" +) + +var byteOrder = binary.BigEndian + +// WaitingProofType represents the type of the encoded waiting proof. +type WaitingProofType uint8 + +const ( + // WaitingProofTypeLegacy represents a waiting proof for legacy P2WSH + // channels. + WaitingProofTypeLegacy WaitingProofType = 0 +) + +// WaitingProofKey is the proof key which uniquely identifies the waiting +// proof object. The goal of this key is distinguish the local and remote +// proof for the same channel id. +type WaitingProofKey [9]byte + +// WaitingProof is the storable object, which encapsulate the half proof and +// the information about from which side this proof came. This structure is +// needed to make channel proof exchange persistent, so that after client +// restart we may receive remote/local half proof and process it. +type WaitingProof struct { + *lnwire.AnnounceSignatures + isRemote bool +} + +// Key returns the key which uniquely identifies waiting proof. +func (p *WaitingProof) Key() WaitingProofKey { + var key [9]byte + binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64()) + + if p.isRemote { + key[8] = 1 + } + + return key +} + +// UpdatedEncode writes the internal representation of waiting proof in byte +// stream using the new format that is prefixed with a type byte. +func (p *WaitingProof) UpdatedEncode(w io.Writer) error { + // Write the type byte. + err := binary.Write(w, byteOrder, WaitingProofTypeLegacy) + if err != nil { + return err + } + + if err := binary.Write(w, byteOrder, p.isRemote); err != nil { + return err + } + + buf, ok := w.(*bytes.Buffer) + if !ok { + return fmt.Errorf("expect io.Writer to be *bytes.Buffer") + } + + return p.AnnounceSignatures.Encode(buf, 0) +} + +// LegacyEncode writes the internal representation of waiting proof in byte +// stream using the legacy format. +func (p *WaitingProof) LegacyEncode(w io.Writer) error { + if err := binary.Write(w, byteOrder, p.isRemote); err != nil { + return err + } + + buf, ok := w.(*bytes.Buffer) + if !ok { + return fmt.Errorf("expect io.Writer to be *bytes.Buffer") + } + + return p.AnnounceSignatures.Encode(buf, 0) +} + +// LegacyDecode reads the data from the byte stream and initializes the +// waiting proof object with it. +func (p *WaitingProof) LegacyDecode(r io.Reader) error { + if err := binary.Read(r, byteOrder, &p.isRemote); err != nil { + return err + } + + msg := &lnwire.AnnounceSignatures{} + if err := msg.Decode(r, 0); err != nil { + return err + } + + p.AnnounceSignatures = msg + + return nil +} diff --git a/channeldb/waitingproof.go b/channeldb/waitingproof.go index faefc620c..419addec3 100644 --- a/channeldb/waitingproof.go +++ b/channeldb/waitingproof.go @@ -7,6 +7,7 @@ import ( "io" "sync" + "github.com/btcsuite/btcd/btcec/v2" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" @@ -20,11 +21,6 @@ var ( // found by db. ErrWaitingProofNotFound = errors.New("waiting proofs haven't been " + "found") - - // ErrWaitingProofAlreadyExist is returned if waiting proofs haven't been - // found by db. - ErrWaitingProofAlreadyExist = errors.New("waiting proof with such " + - "key already exist") ) // WaitingProofStore is the bold db map-like storage for half announcement @@ -186,29 +182,191 @@ func (s *WaitingProofStore) Get(key WaitingProofKey) (*WaitingProof, error) { // proof for the same channel id. type WaitingProofKey [9]byte +// WaitingProofType represents the type of the encoded waiting proof. +type WaitingProofType uint8 + +const ( + // WaitingProofTypeLegacy represents a waiting proof for legacy P2WSH + // channels. + WaitingProofTypeLegacy WaitingProofType = 0 + + // WaitingProofTypeTaproot represents a waiting proof for taproot + // channels. + WaitingProofTypeTaproot WaitingProofType = 1 +) + +// typeToWaitingProofType is a map from WaitingProofType to an empty +// instantiation of the associated type. +func typeToWaitingProof(pt WaitingProofType) (WaitingProofInterface, bool) { + switch pt { + case WaitingProofTypeLegacy: + return &LegacyWaitingProof{}, true + case WaitingProofTypeTaproot: + return &TaprootWaitingProof{}, true + + default: + return nil, false + } +} + +// WaitingProofInterface is an interface that must be implemented by any waiting +// proof to be stored in the waiting proof DB. +type WaitingProofInterface interface { + // SCID returns the short channel ID of the channel that the waiting + // proof is for. + SCID() lnwire.ShortChannelID + + // Encode encodes the waiting proof to the given buffer. + Encode(w *bytes.Buffer, pver uint32) error + + // Decode parses the bytes from the given reader to reconstruct the + // waiting proof. + Decode(r io.Reader, pver uint32) error + + // Type returns the waiting proof type. + Type() WaitingProofType +} + +// LegacyWaitingProof is an implementation of the WaitingProofInterface to be +// used for legacy, P2WSH channels. +type LegacyWaitingProof struct { + lnwire.AnnounceSignatures1 +} + +// SCID returns the short channel ID of the channel that the waiting +// proof is for. +// +// NOTE: this is part of the WaitingProofInterface. +func (l *LegacyWaitingProof) SCID() lnwire.ShortChannelID { + return l.ShortChannelID +} + +// Type returns the waiting proof type. +// +// NOTE: this is part of the WaitingProofInterface. +func (l *LegacyWaitingProof) Type() WaitingProofType { + return WaitingProofTypeLegacy +} + +var _ WaitingProofInterface = (*LegacyWaitingProof)(nil) + +// TaprootWaitingProof is an implementation of the WaitingProofInterface to be +// used for taproot channels. +type TaprootWaitingProof struct { + lnwire.AnnounceSignatures2 + + // AggNonce is the aggregate nonce used to construct the partial + // signatures. It will be used as the R value in the final signature. + AggNonce *btcec.PublicKey +} + +// SCID returns the short channel ID of the channel that the waiting +// proof is for. +// +// NOTE: this is part of the WaitingProofInterface. +func (t *TaprootWaitingProof) SCID() lnwire.ShortChannelID { + return t.ShortChannelID +} + +// Decode parses the bytes from the given reader to reconstruct the +// waiting proof. +// +// NOTE: this is part of the WaitingProofInterface. +func (t *TaprootWaitingProof) Decode(r io.Reader, pver uint32) error { + // Read byte to see if agg nonce is present. + var aggNoncePresent bool + if err := binary.Read(r, byteOrder, &aggNoncePresent); err != nil { + return err + } + + // If agg nonce is present, read it in. + if aggNoncePresent { + var nonceBytes [btcec.PubKeyBytesLenCompressed]byte + if err := binary.Read(r, byteOrder, &nonceBytes); err != nil { + return err + } + + nonce, err := btcec.ParsePubKey(nonceBytes[:]) + if err != nil { + return err + } + + t.AggNonce = nonce + } + + return t.AnnounceSignatures2.Decode(r, pver) +} + +// Encode encodes the waiting proof to the given buffer. +// +// NOTE: this is part of the WaitingProofInterface. +func (t *TaprootWaitingProof) Encode(w *bytes.Buffer, pver uint32) error { + // If agg nonce is present, write a signaling byte for that. + aggNoncePresent := t.AggNonce != nil + if err := binary.Write(w, byteOrder, aggNoncePresent); err != nil { + return err + } + + // Now follow with the actual nonce if present. + if aggNoncePresent { + err := binary.Write( + w, byteOrder, t.AggNonce.SerializeCompressed(), + ) + if err != nil { + return err + } + } + + return t.AnnounceSignatures2.Encode(w, pver) +} + +// Type returns the waiting proof type. +// +// NOTE: this is part of the WaitingProofInterface. +func (t *TaprootWaitingProof) Type() WaitingProofType { + return WaitingProofTypeTaproot +} + +var _ WaitingProofInterface = (*TaprootWaitingProof)(nil) + // WaitingProof is the storable object, which encapsulate the half proof and // the information about from which side this proof came. This structure is // needed to make channel proof exchange persistent, so that after client // restart we may receive remote/local half proof and process it. type WaitingProof struct { - *lnwire.AnnounceSignatures1 + WaitingProofInterface isRemote bool } -// NewWaitingProof constructs a new waiting prof instance. -func NewWaitingProof(isRemote bool, +// NewLegacyWaitingProof constructs a new waiting prof instance for a legacy, +// P2WSH channel. +func NewLegacyWaitingProof(isRemote bool, proof *lnwire.AnnounceSignatures1) *WaitingProof { return &WaitingProof{ - AnnounceSignatures1: proof, - isRemote: isRemote, + WaitingProofInterface: &LegacyWaitingProof{*proof}, + isRemote: isRemote, + } +} + +// NewTaprootWaitingProof constructs a new waiting prof instance for a taproot +// channel. +func NewTaprootWaitingProof(isRemote bool, proof *lnwire.AnnounceSignatures2, + aggNonce *btcec.PublicKey) *WaitingProof { + + return &WaitingProof{ + WaitingProofInterface: &TaprootWaitingProof{ + AnnounceSignatures2: *proof, + AggNonce: aggNonce, + }, + isRemote: isRemote, } } // OppositeKey returns the key which uniquely identifies opposite waiting proof. func (p *WaitingProof) OppositeKey() WaitingProofKey { var key [9]byte - binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64()) + binary.BigEndian.PutUint64(key[:8], p.SCID().ToUint64()) if !p.isRemote { key[8] = 1 @@ -219,7 +377,7 @@ func (p *WaitingProof) OppositeKey() WaitingProofKey { // Key returns the key which uniquely identifies waiting proof. func (p *WaitingProof) Key() WaitingProofKey { var key [9]byte - binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64()) + binary.BigEndian.PutUint64(key[:8], p.SCID().ToUint64()) if p.isRemote { key[8] = 1 @@ -229,6 +387,11 @@ func (p *WaitingProof) Key() WaitingProofKey { // Encode writes the internal representation of waiting proof in byte stream. func (p *WaitingProof) Encode(w io.Writer) error { + // Write the type byte. + if err := binary.Write(w, byteOrder, p.Type()); err != nil { + return err + } + if err := binary.Write(w, byteOrder, p.isRemote); err != nil { return err } @@ -240,26 +403,31 @@ func (p *WaitingProof) Encode(w io.Writer) error { return fmt.Errorf("expect io.Writer to be *bytes.Buffer") } - if err := p.AnnounceSignatures1.Encode(buf, 0); err != nil { - return err - } - - return nil + return p.WaitingProofInterface.Encode(buf, 0) } // Decode reads the data from the byte stream and initializes the // waiting proof object with it. func (p *WaitingProof) Decode(r io.Reader) error { + var proofType WaitingProofType + if err := binary.Read(r, byteOrder, &proofType); err != nil { + return err + } + if err := binary.Read(r, byteOrder, &p.isRemote); err != nil { return err } - msg := &lnwire.AnnounceSignatures1{} - if err := msg.Decode(r, 0); err != nil { + proof, ok := typeToWaitingProof(proofType) + if !ok { + return fmt.Errorf("unknown proof type") + } + + if err := proof.Decode(r, 0); err != nil { return err } - p.AnnounceSignatures1 = msg + p.WaitingProofInterface = proof return nil } diff --git a/channeldb/waitingproof_test.go b/channeldb/waitingproof_test.go index d7113d9e7..21568d169 100644 --- a/channeldb/waitingproof_test.go +++ b/channeldb/waitingproof_test.go @@ -1,10 +1,10 @@ package channeldb import ( - "reflect" + "math/rand" "testing" - "github.com/davecgh/go-spew/spew" + "github.com/btcsuite/btcd/btcec/v2" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" @@ -18,35 +18,54 @@ func TestWaitingProofStore(t *testing.T) { db, err := MakeTestDB(t) require.NoError(t, err, "failed to make test database") - proof1 := NewWaitingProof(true, &lnwire.AnnounceSignatures1{ + proof1 := NewLegacyWaitingProof(true, &lnwire.AnnounceSignatures1{ NodeSignature: wireSig, BitcoinSignature: wireSig, ExtraOpaqueData: make([]byte, 0), }) + // No agg nonce. + proof2 := NewTaprootWaitingProof(true, &lnwire.AnnounceSignatures2{ + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 2000, + }, + PartialSignature: *randPartialSig(t), + ExtraOpaqueData: make([]byte, 0), + }, nil) + + // With agg nonce. + priv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + proof3 := NewTaprootWaitingProof(true, &lnwire.AnnounceSignatures2{ + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 2000, + }, + PartialSignature: *randPartialSig(t), + ExtraOpaqueData: make([]byte, 0), + }, priv.PubKey()) + + proofs := []*WaitingProof{ + proof1, + proof2, + proof3, + } + store, err := NewWaitingProofStore(db) - if err != nil { - t.Fatalf("unable to create the waiting proofs storage: %v", - err) - } + require.NoError(t, err) - if err := store.Add(proof1); err != nil { - t.Fatalf("unable add proof to storage: %v", err) - } + for _, proof := range proofs { + require.NoError(t, store.Add(proof)) - proof2, err := store.Get(proof1.Key()) - require.NoError(t, err, "unable retrieve proof from storage") - if !reflect.DeepEqual(proof1, proof2) { - t.Fatalf("wrong proof retrieved: expected %v, got %v", - spew.Sdump(proof1), spew.Sdump(proof2)) - } + p2, err := store.Get(proof.Key()) + require.NoError(t, err, "unable retrieve proof from storage") + require.Equal(t, proof, p2) - if _, err := store.Get(proof1.OppositeKey()); err != ErrWaitingProofNotFound { - t.Fatalf("proof shouldn't be found: %v", err) - } + _, err = store.Get(proof.OppositeKey()) + require.ErrorIs(t, err, ErrWaitingProofNotFound) - if err := store.Remove(proof1.Key()); err != nil { - t.Fatalf("unable remove proof from storage: %v", err) + err = store.Remove(proof.Key()) + require.NoError(t, err) } if err := store.ForAll(func(proof *WaitingProof) error { @@ -55,3 +74,16 @@ func TestWaitingProofStore(t *testing.T) { t.Fatal(err) } } + +func randPartialSig(t *testing.T) *lnwire.PartialSig { + var sigBytes [32]byte + _, err := rand.Read(sigBytes[:]) + require.NoError(t, err) + + var s btcec.ModNScalar + s.SetByteSlice(sigBytes[:]) + + return &lnwire.PartialSig{ + Sig: s, + } +} diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 01f4adac8..bf5703f9e 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -3347,7 +3347,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg, return nil, false } - proof := channeldb.NewWaitingProof(nMsg.isRemote, ann) + proof := channeldb.NewLegacyWaitingProof(nMsg.isRemote, ann) err := d.cfg.WaitingProofStore.Add(proof) if err != nil { err := fmt.Errorf("unable to store the proof for "+ @@ -3459,8 +3459,8 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg, // announcement. If we didn't receive the opposite half of the proof // then we should store this one, and wait for the opposite to be // received. - proof := channeldb.NewWaitingProof(nMsg.isRemote, ann) - oppProof, err := d.cfg.WaitingProofStore.Get(proof.OppositeKey()) + proof := channeldb.NewLegacyWaitingProof(nMsg.isRemote, ann) + oppositeProof, err := d.cfg.WaitingProofStore.Get(proof.OppositeKey()) if err != nil && err != channeldb.ErrWaitingProofNotFound { err := fmt.Errorf("unable to get the opposite proof for "+ "short_chan_id=%v: %v", shortChanID, err) @@ -3487,6 +3487,14 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg, return nil, false } + oppProof, ok := oppositeProof. + WaitingProofInterface.(*channeldb.LegacyWaitingProof) + if !ok { + nMsg.err <- fmt.Errorf("got wrong waiting proof type") + + return nil, false + } + // We now have both halves of the channel announcement proof, then // we'll reconstruct the initial announcement so we can validate it // shortly below.