multi: add ToChannelAnnouncement helper on ChannelEdgeInfo
What changed, and why it matters
This commit is a code cleanup: it moves the logic that builds a Lightning network channel announcement message into a single helper method on the channel data object. It also makes an existing function use that helper instead of duplicating the conversion code. There is no direct security fix here, but centralizing the logic reduces the chance of future bugs where different code paths build announcements differently. The helper also adds a small safety check that refuses to build an announcement if required proof data is missing.
Treat as a normal refactoring commit. Reviewers should verify that all callers now attach the proof to ChannelEdgeInfo before invoking CreateChanAnnouncement, and that the new helper's version check does not break v2 channel handling elsewhere. No urgent security action is required.
Security signals we found
Refactoring centralizes channel-announcement construction, reducing duplicated conversion logic
New helper adds explicit validation that AuthProof is present before creating announcement
New helper rejects unsupported channel versions (currently only v1 supported)
No direct vulnerability, exploit primitive, or security bug is fixed in the diff
Evidence from the diff
The change adds ChannelEdgeInfo.ToChannelAnnouncement() in graph/db/models/channel_edge_info.go, which converts a database channel edge info struct into an lnwire.ChannelAnnouncement1. It validates that AuthProof is non-nil and that the channel version is v1. netann.CreateChanAnnouncement is refactored to call this helper and no longer accepts a separate ChannelAuthProof argument; callers in discovery/chan_series.go, discovery/gossiper.go, and tests are updated to pass only ChannelEdgeInfo. In two places in gossiper.go, the proof is now attached to chanInfo before calling CreateChanAnnouncement. This is a refactoring/encapsulation improvement, not a patch for a known vulnerability.
Changed components
graph/db/models/channel_edge_info.gonetann/channel_announcement.godiscovery/chan_series.godiscovery/gossiper.gonetann/channel_announcement_test.goInspect captured patch +82 / −80
diff --git a/discovery/chan_series.go b/discovery/chan_series.go
index 050b82b..9ba9607 100644
--- a/discovery/chan_series.go
+++ b/discovery/chan_series.go
@@ -133,8 +133,7 @@ func (c *ChanSeries) UpdatesInHorizon(chain chainhash.Hash,
//nolint:ll
chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement(
- channel.Info.AuthProof, channel.Info,
- channel.Policy1, channel.Policy2,
+ channel.Info, channel.Policy1, channel.Policy2,
)
if err != nil {
if !yield(nil, err) {
@@ -281,8 +280,7 @@ func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash,
}
chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement(
- channel.Info.AuthProof, channel.Info, channel.Policy1,
- channel.Policy2,
+ channel.Info, channel.Policy1, channel.Policy2,
)
if err != nil {
return nil, err
diff --git a/discovery/gossiper.go b/discovery/gossiper.go
index b26435c..88ac241 100644
--- a/discovery/gossiper.go
+++ b/discovery/gossiper.go
@@ -2096,10 +2096,14 @@ func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context,
return nil, nil
}
+ // Attach the proof to the channel info before creating the
+ // announcement.
+ chanInfo.AuthProof = proof
+
// We'll then create then validate the new fully assembled
// announcement.
chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement(
- proof, chanInfo, e1, e2,
+ chanInfo, e1, e2,
)
if err != nil {
return nil, err
@@ -2492,44 +2496,13 @@ func (d *AuthenticatedGossiper) updateChannel(ctx context.Context,
// have a full channel announcement for this channel.
var chanAnn *lnwire.ChannelAnnouncement1
if info.AuthProof != nil {
- chanID := lnwire.NewShortChanIDFromInt(info.ChannelID)
- chanAnn = &lnwire.ChannelAnnouncement1{
- ShortChannelID: chanID,
- NodeID1: info.NodeKey1Bytes,
- NodeID2: info.NodeKey2Bytes,
- ChainHash: info.ChainHash,
- BitcoinKey1: info.BitcoinKey1Bytes,
- Features: lnwire.NewRawFeatureVector(),
- BitcoinKey2: info.BitcoinKey2Bytes,
- ExtraOpaqueData: info.ExtraOpaqueData,
- }
- chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature(
- info.AuthProof.NodeSig1(),
- )
- if err != nil {
- return nil, nil, err
- }
- chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature(
- info.AuthProof.NodeSig2(),
- )
- if err != nil {
- return nil, nil, err
- }
- chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature(
- info.AuthProof.BitcoinSig1(),
- )
- if err != nil {
- return nil, nil, err
- }
- chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature(
- info.AuthProof.BitcoinSig2(),
- )
+ chanAnn, err = info.ToChannelAnnouncement()
if err != nil {
return nil, nil, err
}
}
- return chanAnn, chanUpdate, err
+ return chanAnn, chanUpdate, nil
}
// SyncManager returns the gossiper's SyncManager instance.
@@ -3703,7 +3676,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context,
ann.ChannelID, peerID)
ca, _, _, err := netann.CreateChanAnnouncement(
- chanInfo.AuthProof, chanInfo, e1, e2,
+ chanInfo, e1, e2,
)
if err != nil {
log.Errorf("unable to gen ann: %v",
@@ -3782,8 +3755,12 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context,
)
}
+ // Attach the proof to the channel info before creating the
+ // announcement.
+ chanInfo.AuthProof = dbProof
+
chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement(
- dbProof, chanInfo, e1, e2,
+ chanInfo, e1, e2,
)
if err != nil {
log.Error(err)
diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go
index 7e872c5..9f94770 100644
--- a/graph/db/models/channel_edge_info.go
+++ b/graph/db/models/channel_edge_info.go
@@ -199,3 +199,66 @@ func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) (
"this channel")
}
}
+
+// ToChannelAnnouncement converts the ChannelEdgeInfo to a
+// lnwire.ChannelAnnouncement1 message. Returns an error if AuthProof is nil
+// or if the version is not v1.
+func (c *ChannelEdgeInfo) ToChannelAnnouncement() (
+ *lnwire.ChannelAnnouncement1, error) {
+
+ // We currently only support v1 channel announcements.
+ if c.Version != lnwire.GossipVersion1 {
+ return nil, fmt.Errorf("unsupported channel version: %d",
+ c.Version)
+ }
+
+ // If there's no auth proof, we can't create a full channel
+ // announcement.
+ if c.AuthProof == nil {
+ return nil, fmt.Errorf("cannot create channel announcement " +
+ "without auth proof")
+ }
+
+ chanID := lnwire.NewShortChanIDFromInt(c.ChannelID)
+ chanAnn := &lnwire.ChannelAnnouncement1{
+ ShortChannelID: chanID,
+ NodeID1: c.NodeKey1Bytes,
+ NodeID2: c.NodeKey2Bytes,
+ ChainHash: c.ChainHash,
+ BitcoinKey1: c.BitcoinKey1Bytes,
+ BitcoinKey2: c.BitcoinKey2Bytes,
+ Features: c.Features.RawFeatureVector,
+ ExtraOpaqueData: c.ExtraOpaqueData,
+ }
+
+ var err error
+ chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature(
+ c.AuthProof.NodeSig1(),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature(
+ c.AuthProof.NodeSig2(),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature(
+ c.AuthProof.BitcoinSig1(),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature(
+ c.AuthProof.BitcoinSig2(),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return chanAnn, nil
+}
diff --git a/netann/channel_announcement.go b/netann/channel_announcement.go
index fee3c5a..14f65c3 100644
--- a/netann/channel_announcement.go
+++ b/netann/channel_announcement.go
@@ -34,48 +34,14 @@ const (
// function is used to transform out database structs into the corresponding wire
// structs for announcing new channels to other peers, or simply syncing up a
// peer's initial routing table upon connect.
-func CreateChanAnnouncement(chanProof *models.ChannelAuthProof,
- chanInfo *models.ChannelEdgeInfo,
+func CreateChanAnnouncement(chanInfo *models.ChannelEdgeInfo,
e1, e2 *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1,
*lnwire.ChannelUpdate1, *lnwire.ChannelUpdate1, error) {
// First, using the parameters of the channel, along with the channel
- // authentication chanProof, we'll create re-create the original
+ // authentication proof, we'll create re-create the original
// authenticated channel announcement.
- chanID := lnwire.NewShortChanIDFromInt(chanInfo.ChannelID)
- chanAnn := &lnwire.ChannelAnnouncement1{
- ShortChannelID: chanID,
- NodeID1: chanInfo.NodeKey1Bytes,
- NodeID2: chanInfo.NodeKey2Bytes,
- ChainHash: chanInfo.ChainHash,
- BitcoinKey1: chanInfo.BitcoinKey1Bytes,
- BitcoinKey2: chanInfo.BitcoinKey2Bytes,
- Features: chanInfo.Features.RawFeatureVector,
- ExtraOpaqueData: chanInfo.ExtraOpaqueData,
- }
-
- var err error
- chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature(
- chanProof.BitcoinSig1(),
- )
- if err != nil {
- return nil, nil, nil, err
- }
- chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature(
- chanProof.BitcoinSig2(),
- )
- if err != nil {
- return nil, nil, nil, err
- }
- chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature(
- chanProof.NodeSig1(),
- )
- if err != nil {
- return nil, nil, nil, err
- }
- chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature(
- chanProof.NodeSig2(),
- )
+ chanAnn, err := chanInfo.ToChannelAnnouncement()
if err != nil {
return nil, nil, nil, err
}
diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go
index c46b90a..11e72bb 100644
--- a/netann/channel_announcement_test.go
+++ b/netann/channel_announcement_test.go
@@ -59,9 +59,7 @@ func TestCreateChanAnnouncement(t *testing.T) {
models.WithFeatures(features),
)
require.NoError(t, err)
- chanAnn, _, _, err := CreateChanAnnouncement(
- chanProof, chanInfo, nil, nil,
- )
+ chanAnn, _, _, err := CreateChanAnnouncement(chanInfo, nil, nil)
require.NoError(t, err, "unable to create channel announcement")
assert.Equal(t, chanAnn, expChanAnn)
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.