Files
node/network/peer/endpoint_signer.go
T
Hanzo AI 6de839a515 codec: rip github.com/luxfi/codec from leaf-misc (Wave 1D)
Wave 1D of the luxfi/codec final rip: leaf-misc tier under node/.
This batch removes the direct codec/wrappers, codec/linearcodec, and
codec.Manager imports from p2p network protocol, IP/port encoding,
indexer/keystore on-disk storage, warp socket IPC, x/archivedb +
merkledb internals, and the primary-network wallet UTXO loader.

Migration shape:

(a) codec/wrappers.Packer + length constants
    → node/utils/wrappers (same shape, already in tree as the local
      canonical home for binary IO helpers). 14 files: indexer/,
      network/, utils/ips/, utils/metric/, warp/, x/.

(b) codec.Manager + linearcodec for on-disk container storage
    → hand-rolled big-endian binary marshal/unmarshal. Hard cut;
      no codec-version prefix; payload size bounded explicitly:
      - indexer/codec.go: marshalContainer/unmarshalContainer
      - service/keystore/codec.go: marshalHash/unmarshalHash and
        marshalUser/unmarshalUser, 16 MiB blob cap retained.

(c) codec.Manager for cross-chain UTXO parsing (wallet/network/primary)
    → utxo.ParseUTXO via the ZAP wire dispatcher already registered
      by node/vms/components/lux. Drops the per-chain codec.Manager
      slot from FetchState; AddAllUTXOs no longer takes a codec.
      Underscore-import of vms/components/lux ensures the dispatcher
      is wired even for callers that don't already pull platformvm.

No snow/snowman/snowball/avalanche/avm/subnet residue introduced.
2026-06-05 11:57:14 -07:00

187 lines
5.2 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package peer
import (
"crypto"
"crypto/rand"
"errors"
"fmt"
"net/netip"
"time"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/crypto/hash"
"github.com/luxfi/net/endpoints"
"github.com/luxfi/node/staking"
"github.com/luxfi/node/utils/wrappers"
"github.com/luxfi/utils"
)
var (
errInvalidEndpointSignature = errors.New("invalid endpoint signature")
)
// UnsignedEndpoint is used for a validator to claim an endpoint (IP or hostname).
// The [Timestamp] ensures peers track the most updated endpoint claim.
type UnsignedEndpoint struct {
Endpoint endpoints.Endpoint
Timestamp uint64
}
// Sign this endpoint with the provided signers and return the signed endpoint.
func (e *UnsignedEndpoint) Sign(tlsSigner crypto.Signer, blsSigner bls.Signer) (*SignedEndpoint, error) {
endpointBytes := e.bytes()
tlsSignature, err := tlsSigner.Sign(
rand.Reader,
hash.ComputeHash256(endpointBytes),
crypto.SHA256,
)
if err != nil {
return nil, err
}
blsSignature, err := blsSigner.SignProofOfPossession(endpointBytes)
if err != nil {
return nil, err
}
return &SignedEndpoint{
UnsignedEndpoint: *e,
TLSSignature: tlsSignature,
BLSSignature: blsSignature,
BLSSignatureBytes: bls.SignatureToBytes(blsSignature),
}, nil
}
// bytes returns the canonical byte representation for signing.
func (e *UnsignedEndpoint) bytes() []byte {
endpointBytes := e.Endpoint.Bytes()
p := wrappers.Packer{
Bytes: make([]byte, len(endpointBytes)+wrappers.LongLen),
}
p.PackFixedBytes(endpointBytes)
p.PackLong(e.Timestamp)
return p.Bytes
}
// SignedEndpoint is a wrapper of UnsignedEndpoint with signatures.
type SignedEndpoint struct {
UnsignedEndpoint
TLSSignature []byte
BLSSignature *bls.Signature
BLSSignatureBytes []byte
}
// Verify verifies that:
// - [e.Timestamp] is within the allowed clock skew range.
// - [e.TLSSignature] is a valid signature over [e.UnsignedEndpoint] from [cert].
func (e *SignedEndpoint) Verify(
cert *staking.Certificate,
maxTimestamp time.Time,
) error {
maxUnixTimestamp := uint64(maxTimestamp.Unix())
if e.Timestamp > maxUnixTimestamp {
return fmt.Errorf("%w: timestamp %d > maxTimestamp %d",
errTimestampTooFarInFuture, e.Timestamp, maxUnixTimestamp)
}
// Prevent replay attacks
const reasonableClockSkewWindow = 10 * time.Minute
minTimestamp := maxTimestamp.Add(-reasonableClockSkewWindow)
minUnixTimestamp := uint64(minTimestamp.Unix())
if e.Timestamp < minUnixTimestamp {
return fmt.Errorf("%w: timestamp %d < minTimestamp %d",
errTimestampTooFarInPast, e.Timestamp, minUnixTimestamp)
}
if err := staking.CheckSignature(
cert,
e.UnsignedEndpoint.bytes(),
e.TLSSignature,
); err != nil {
return fmt.Errorf("%w: %w", errInvalidEndpointSignature, err)
}
return nil
}
// ToSignedIP converts to legacy SignedIP if this is an IP endpoint.
// Returns nil if this is a hostname endpoint.
func (e *SignedEndpoint) ToSignedIP() *SignedIP {
if !e.Endpoint.IsIP() {
return nil
}
return &SignedIP{
UnsignedIP: UnsignedIP{
AddrPort: e.Endpoint.AddrPort,
Timestamp: e.Timestamp,
},
TLSSignature: e.TLSSignature,
BLSSignature: e.BLSSignature,
BLSSignatureBytes: e.BLSSignatureBytes,
}
}
// EndpointSigner periodically signs the node's endpoint claim.
// Supports both IP and hostname endpoints.
type EndpointSigner struct {
endpoint *utils.Atomic[endpoints.Endpoint]
tlsSigner crypto.Signer
blsSigner bls.Signer
// For backward compatibility with IP-only peers
legacyIPSigner *IPSigner
}
// NewEndpointSigner returns an EndpointSigner that can sign endpoint claims.
// If the endpoint is IP-based, it also maintains a legacy IPSigner for compatibility.
func NewEndpointSigner(
endpoint *utils.Atomic[endpoints.Endpoint],
tlsSigner crypto.Signer,
blsSigner bls.Signer,
) *EndpointSigner {
es := &EndpointSigner{
endpoint: endpoint,
tlsSigner: tlsSigner,
blsSigner: blsSigner,
}
// If the endpoint is IP-based, create legacy signer for backward compatibility
currentEndpoint := endpoint.Get()
if currentEndpoint.IsIP() {
ipAtomic := utils.NewAtomic(currentEndpoint.AddrPort)
es.legacyIPSigner = NewIPSigner(ipAtomic, tlsSigner, blsSigner)
}
return es
}
// GetSignedEndpoint returns a signed endpoint claim with the current timestamp.
func (s *EndpointSigner) GetSignedEndpoint() (*SignedEndpoint, error) {
unsigned := &UnsignedEndpoint{
Endpoint: s.endpoint.Get(),
Timestamp: uint64(time.Now().Unix()),
}
return unsigned.Sign(s.tlsSigner, s.blsSigner)
}
// GetSignedIP returns a legacy signed IP for backward compatibility.
// Returns nil if the endpoint is hostname-based.
func (s *EndpointSigner) GetSignedIP() (*SignedIP, error) {
if s.legacyIPSigner == nil {
return nil, nil
}
return s.legacyIPSigner.GetSignedIP()
}
// SupportsHostname returns true if this signer is using a hostname endpoint.
func (s *EndpointSigner) SupportsHostname() bool {
return s.endpoint.Get().IsHostname()
}
// IPEndpointFromAddrPort creates an IP endpoint from netip.AddrPort for convenience.
func IPEndpointFromAddrPort(addr netip.AddrPort) endpoints.Endpoint {
return endpoints.NewIPEndpoint(addr)
}