mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
Tor stopped serving v2 onion services in October 2021; lnd should not produce v2 addresses anymore, but it must still verify signatures on and re-broadcast peer NodeAnnouncement messages that carry v2 entries. Stop accepting v2 as configuration input (lncfg), strip the legacy `--tor.v2` flag from the sample config, and remove the `tor.OnionHostToFakeIP` helper. Operator entry points (`--externalip`, `--listen`, `lncli connect`, `lncli wtclient towers add`) fail fast on a v2 `.onion` string, so upgrading nodes must remove any v2 entry from `lnd.conf` before lnd will start. Filter persisted v2 state before use without rewriting on-disk records: the self-announcement builder strips any v2 entry inherited from the stored self-node; the watchtower client drops v2 entries from each persisted tower's address list (skipping the tower entirely if no non-v2 address remains); the autopilot connector, graph bootstrapper, and static-channel backup restore paths skip v2 entries before attempting outbound dials. Restrict the Tor controller's ADD_ONION path to v3 keys, including the encrypted on-disk legacy-key fallback. For inbound announcements, keep the wire codec wire-faithful: `lnwire.WriteOnionAddr`, `graph/db.encodeOnionAddr`, and the matching decoders round-trip v2 bytes so `DataToSign` reproduces the bytes the remote peer signed, signature validation succeeds, and the announcement is persisted to the graph DB and re-broadcast across restarts byte-for- byte. RPC surfaces continue to expose the full address set so external tools can independently reproduce and verify the signed bytes. Add a netann regression test that signs a [v3, v2, ipv4] announcement, round-trips it through Encode/Decode, verifies the signature, and confirms the resulting models.Node preserves the v2 entry. Add a graph bootstrapper test asserting v2 entries are skipped while v3 and plain TCP entries on the same node still surface as bootstrap candidates.
253 lines
8.1 KiB
Go
253 lines
8.1 KiB
Go
package models
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/lightningnetwork/lnd/fn/v2"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
)
|
|
|
|
// Node represents an individual vertex/node within the channel graph.
|
|
// A node is connected to other nodes by one or more channel edges emanating
|
|
// from it. As the graph is directed, a node will also have an incoming edge
|
|
// attached to it for each outgoing edge.
|
|
type Node struct {
|
|
// Version is the gossip version that this node was advertised on.
|
|
Version lnwire.GossipVersion
|
|
|
|
// PubKeyBytes is the raw bytes of the public key of the target node.
|
|
PubKeyBytes [33]byte
|
|
|
|
// LastUpdate is the last time the vertex information for this node has
|
|
// been updated.
|
|
LastUpdate time.Time
|
|
|
|
// LastBlockHeight is the block height that timestamps the last update
|
|
// we received for this node. This is only used if this is a V2 node
|
|
// announcement.
|
|
LastBlockHeight uint32
|
|
|
|
// Address is the TCP address this node is reachable over.
|
|
Addresses []net.Addr
|
|
|
|
// Color is the selected color for the node.
|
|
Color fn.Option[color.RGBA]
|
|
|
|
// Alias is a nick-name for the node. The alias can be used to confirm
|
|
// a node's identity or to serve as a short ID for an address book.
|
|
Alias fn.Option[string]
|
|
|
|
// AuthSigBytes is the raw signature under the advertised public key
|
|
// which serves to authenticate the attributes announced by this node.
|
|
AuthSigBytes []byte
|
|
|
|
// Features is the list of protocol features supported by this node.
|
|
Features *lnwire.FeatureVector
|
|
|
|
// ExtraOpaqueData is the set of data that was appended to this
|
|
// message, some of which we may not actually know how to iterate or
|
|
// parse. By holding onto this data, we ensure that we're able to
|
|
// properly validate the set of signatures that cover these new fields,
|
|
// and ensure we're able to make upgrades to the network in a forwards
|
|
// compatible manner. This is only used for V1 node announcements.
|
|
ExtraOpaqueData []byte
|
|
|
|
// ExtraSignedFields is a map of extra fields that are covered by the
|
|
// node announcement's signature that we have not explicitly parsed.
|
|
// This is only used for version 2 node announcements and beyond.
|
|
ExtraSignedFields map[uint64][]byte
|
|
}
|
|
|
|
// NodeV1Fields houses the fields that are specific to a version 1 node
|
|
// announcement.
|
|
type NodeV1Fields struct {
|
|
// Address is the TCP address this node is reachable over.
|
|
Addresses []net.Addr
|
|
|
|
// AuthSigBytes is the raw signature under the advertised public key
|
|
// which serves to authenticate the attributes announced by this node.
|
|
AuthSigBytes []byte
|
|
|
|
// Features is the list of protocol features supported by this node.
|
|
Features *lnwire.RawFeatureVector
|
|
|
|
// Color is the selected color for the node.
|
|
Color color.RGBA
|
|
|
|
// Alias is a nick-name for the node. The alias can be used to confirm
|
|
// a node's identity or to serve as a short ID for an address book.
|
|
Alias string
|
|
|
|
// LastUpdate is the last time the vertex information for this node has
|
|
// been updated.
|
|
LastUpdate time.Time
|
|
|
|
// ExtraOpaqueData is the set of data that was appended to this
|
|
// message, some of which we may not actually know how to iterate or
|
|
// parse. By holding onto this data, we ensure that we're able to
|
|
// properly validate the set of signatures that cover these new fields,
|
|
// and ensure we're able to make upgrades to the network in a forwards
|
|
// compatible manner.
|
|
ExtraOpaqueData []byte
|
|
}
|
|
|
|
// NewV1Node creates a new version 1 node from the passed fields.
|
|
func NewV1Node(pub route.Vertex, n *NodeV1Fields) *Node {
|
|
return &Node{
|
|
Version: lnwire.GossipVersion1,
|
|
PubKeyBytes: pub,
|
|
Addresses: n.Addresses,
|
|
AuthSigBytes: n.AuthSigBytes,
|
|
Features: lnwire.NewFeatureVector(
|
|
n.Features, lnwire.Features,
|
|
),
|
|
Color: fn.Some(n.Color),
|
|
Alias: fn.Some(n.Alias),
|
|
LastUpdate: n.LastUpdate,
|
|
ExtraOpaqueData: n.ExtraOpaqueData,
|
|
}
|
|
}
|
|
|
|
// NodeV2Fields houses the fields that are specific to a version 2 node
|
|
// announcement.
|
|
type NodeV2Fields struct {
|
|
// LastBlockHeight is the block height that timestamps the last update
|
|
// we received for this node.
|
|
LastBlockHeight uint32
|
|
|
|
// Address is the TCP address this node is reachable over.
|
|
Addresses []net.Addr
|
|
|
|
// Color is the selected color for the node.
|
|
Color fn.Option[color.RGBA]
|
|
|
|
// Alias is a nick-name for the node. The alias can be used to confirm
|
|
// a node's identity or to serve as a short ID for an address book.
|
|
Alias fn.Option[string]
|
|
|
|
// Signature is the schnorr signature under the advertised public key
|
|
// which serves to authenticate the attributes announced by this node.
|
|
Signature []byte
|
|
|
|
// Features is the list of protocol features supported by this node.
|
|
Features *lnwire.RawFeatureVector
|
|
|
|
// ExtraSignedFields is a map of extra fields that are covered by the
|
|
// node announcement's signature that we have not explicitly parsed.
|
|
ExtraSignedFields map[uint64][]byte
|
|
}
|
|
|
|
// NewV2Node creates a new version 2 node from the passed fields.
|
|
func NewV2Node(pub route.Vertex, n *NodeV2Fields) *Node {
|
|
return &Node{
|
|
Version: lnwire.GossipVersion2,
|
|
PubKeyBytes: pub,
|
|
Addresses: n.Addresses,
|
|
AuthSigBytes: n.Signature,
|
|
Features: lnwire.NewFeatureVector(
|
|
n.Features, lnwire.Features,
|
|
),
|
|
LastBlockHeight: n.LastBlockHeight,
|
|
Color: n.Color,
|
|
Alias: n.Alias,
|
|
LastUpdate: time.Unix(0, 0),
|
|
ExtraSignedFields: n.ExtraSignedFields,
|
|
}
|
|
}
|
|
|
|
// NewV1ShellNode creates a new shell version 1 node.
|
|
func NewV1ShellNode(pubKey route.Vertex) *Node {
|
|
return NewShellNode(lnwire.GossipVersion1, pubKey)
|
|
}
|
|
|
|
// NewShellNode creates a new shell node with the given gossip version and
|
|
// public key.
|
|
func NewShellNode(v lnwire.GossipVersion, pubKey route.Vertex) *Node {
|
|
return &Node{
|
|
Version: v,
|
|
PubKeyBytes: pubKey,
|
|
Features: lnwire.EmptyFeatureVector(),
|
|
LastUpdate: time.Unix(0, 0),
|
|
}
|
|
}
|
|
|
|
// HaveAnnouncement returns true if we have received a node announcement for
|
|
// this node. We determine this by checking if we have a signature for the
|
|
// announcement.
|
|
func (n *Node) HaveAnnouncement() bool {
|
|
return len(n.AuthSigBytes) > 0
|
|
}
|
|
|
|
// PubKey is the node's long-term identity public key. This key will be used to
|
|
// authenticated any advertisements/updates sent by the node.
|
|
func (n *Node) PubKey() (*btcec.PublicKey, error) {
|
|
return btcec.ParsePubKey(n.PubKeyBytes[:])
|
|
}
|
|
|
|
// NodeAnnouncement retrieves the latest node announcement of the node.
|
|
func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1,
|
|
error) {
|
|
|
|
// Error out if we request the signed announcement, but we don't have
|
|
// a signature for this announcement.
|
|
if !n.HaveAnnouncement() && signed {
|
|
return nil, fmt.Errorf("node does not have node announcement")
|
|
}
|
|
|
|
alias, err := lnwire.NewNodeAlias(n.Alias.UnwrapOr(""))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodeAnn := &lnwire.NodeAnnouncement1{
|
|
Features: n.Features.RawFeatureVector,
|
|
NodeID: n.PubKeyBytes,
|
|
RGBColor: n.Color.UnwrapOr(color.RGBA{}),
|
|
Alias: alias,
|
|
Addresses: n.Addresses,
|
|
Timestamp: uint32(n.LastUpdate.Unix()),
|
|
ExtraOpaqueData: n.ExtraOpaqueData,
|
|
}
|
|
|
|
if !signed {
|
|
return nodeAnn, nil
|
|
}
|
|
|
|
sig, err := lnwire.NewSigFromECDSARawSignature(n.AuthSigBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodeAnn.Signature = sig
|
|
|
|
return nodeAnn, nil
|
|
}
|
|
|
|
// NodeFromWireAnnouncement creates a Node instance from an
|
|
// lnwire.NodeAnnouncement1 message. The address list from msg.Addresses
|
|
// is copied verbatim, including legacy entries such as tor v2 onion
|
|
// addresses that lnd no longer produces itself. This is required so
|
|
// that Node.NodeAnnouncement can later reconstruct the exact byte
|
|
// sequence the remote peer signed, allowing signature verification and
|
|
// re-broadcast to succeed across restarts.
|
|
func NodeFromWireAnnouncement(msg *lnwire.NodeAnnouncement1) *Node {
|
|
timestamp := time.Unix(int64(msg.Timestamp), 0)
|
|
|
|
return NewV1Node(
|
|
msg.NodeID,
|
|
&NodeV1Fields{
|
|
LastUpdate: timestamp,
|
|
Addresses: msg.Addresses,
|
|
Alias: msg.Alias.String(),
|
|
AuthSigBytes: msg.Signature.ToSignatureBytes(),
|
|
Features: msg.Features,
|
|
Color: msg.RGBColor,
|
|
ExtraOpaqueData: msg.ExtraOpaqueData,
|
|
},
|
|
)
|
|
}
|