mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
Introduce the canonical lnwire.BlindedPath / BlindedPaths codec with a sealed IntroductionNode sum-type covering both the BOLT 4 pubkey and sciddir variants. The codec gates every variable-length subfield against an io.LimitedReader. It fails closed on the encoder side so invalid input never hits the wire. This commit is a pure addition: no existing caller changes. Subsequent commits migrate OnionMessagePayload and the bolt12 message structs to consume the new codec.
181 lines
4.5 KiB
Go
181 lines
4.5 KiB
Go
package lnwire
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
)
|
|
|
|
// IntroductionNode is the sealed sum-type for a blinded path's introduction
|
|
// node. {0x02, 0x03} → PubkeyIntro; {0x00, 0x01} → SciddirIntro. The unexported
|
|
// method seals the variant set so foreign packages cannot satisfy the interface
|
|
// with an unrecognised wire form.
|
|
type IntroductionNode interface {
|
|
isIntroductionNode()
|
|
|
|
encodedLen() uint64
|
|
|
|
encode(w io.Writer) error
|
|
|
|
// validate checks that the discriminator byte is valid for the variant.
|
|
validate() error
|
|
|
|
// Bytes returns the wire-format encoding of the introduction node for
|
|
// callers that need it outside an io.Writer (RPC surfaces).
|
|
Bytes() []byte
|
|
}
|
|
|
|
// PubkeyIntro is the 33-byte compressed-pubkey variant. The SEC1 parity byte
|
|
// (0x02 or 0x03) doubles as the wire discriminator. Use the constructor to
|
|
// ensure the non-nil, on-curve invariant is upheld.
|
|
type PubkeyIntro struct {
|
|
Pubkey *btcec.PublicKey
|
|
}
|
|
|
|
// NewPubkeyIntro constructs the pubkey introduction-node variant, establishing
|
|
// the non-nil, on-curve invariant at construction time so callers receive an
|
|
// error up front rather than relying on every method to re-guard a nil key.
|
|
func NewPubkeyIntro(pubkey *btcec.PublicKey) (PubkeyIntro, error) {
|
|
p := PubkeyIntro{Pubkey: pubkey}
|
|
if err := p.validate(); err != nil {
|
|
return PubkeyIntro{}, err
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// SciddirIntro is the 9-byte sciddir variant. Direction is the wire
|
|
// discriminator; SCID is the 8-byte short channel ID. Use the constructor to
|
|
// ensure the direction is valid at construction time.
|
|
type SciddirIntro struct {
|
|
Direction byte
|
|
SCID [scidLen]byte
|
|
}
|
|
|
|
// NewSciddirIntro constructs the sciddir introduction-node variant, rejecting
|
|
// an invalid direction discriminator at construction time.
|
|
func NewSciddirIntro(direction byte, scid [scidLen]byte) (SciddirIntro, error) {
|
|
s := SciddirIntro{Direction: direction, SCID: scid}
|
|
if err := s.validate(); err != nil {
|
|
return SciddirIntro{}, err
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
var (
|
|
_ IntroductionNode = PubkeyIntro{}
|
|
_ IntroductionNode = SciddirIntro{}
|
|
)
|
|
|
|
// decodeIntroductionNode reads the discriminator byte and dispatches to the
|
|
// matching variant.
|
|
func decodeIntroductionNode(r io.Reader,
|
|
buf *[8]byte) (IntroductionNode, error) {
|
|
|
|
if _, err := io.ReadFull(r, buf[:1]); err != nil {
|
|
return nil, fmt.Errorf("read intro node type: %w", err)
|
|
}
|
|
|
|
disc := buf[0]
|
|
switch disc {
|
|
case 0x00, 0x01:
|
|
var scid [scidLen]byte
|
|
if _, err := io.ReadFull(r, scid[:]); err != nil {
|
|
return nil, fmt.Errorf("read sciddir: %w", err)
|
|
}
|
|
|
|
return NewSciddirIntro(disc, scid)
|
|
|
|
case 0x02, 0x03:
|
|
var b [pubKeyLen]byte
|
|
b[0] = disc
|
|
if _, err := io.ReadFull(r, b[1:]); err != nil {
|
|
return nil, fmt.Errorf("read intro pubkey: %w", err)
|
|
}
|
|
pub, err := btcec.ParsePubKey(b[:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: %w",
|
|
ErrInvalidIntroNode, err)
|
|
}
|
|
|
|
return NewPubkeyIntro(pub)
|
|
|
|
default:
|
|
return nil, fmt.Errorf("%w: 0x%02x", ErrInvalidIntroNode, disc)
|
|
}
|
|
}
|
|
|
|
func (PubkeyIntro) isIntroductionNode() {}
|
|
|
|
func (p PubkeyIntro) encodedLen() uint64 { return pubKeyLen }
|
|
|
|
func (p PubkeyIntro) encode(w io.Writer) error {
|
|
if p.Pubkey == nil {
|
|
return fmt.Errorf("nil intro pubkey")
|
|
}
|
|
_, err := w.Write(p.Pubkey.SerializeCompressed())
|
|
|
|
return err
|
|
}
|
|
|
|
func (p PubkeyIntro) validate() error {
|
|
if p.Pubkey == nil {
|
|
return fmt.Errorf("%w: nil pubkey", ErrInvalidIntroNode)
|
|
}
|
|
|
|
if !p.Pubkey.IsOnCurve() {
|
|
return fmt.Errorf("%w: pubkey not on curve",
|
|
ErrInvalidIntroNode)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Bytes returns the wire-format encoding of the pubkey variant.
|
|
func (p PubkeyIntro) Bytes() []byte {
|
|
var buf bytes.Buffer
|
|
buf.Grow(pubKeyLen)
|
|
|
|
// We ignore errors because we have validated that the pubkey is non nil
|
|
// at construction time.
|
|
_ = p.encode(&buf)
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func (SciddirIntro) isIntroductionNode() {}
|
|
|
|
func (s SciddirIntro) encodedLen() uint64 { return sciddirLen }
|
|
|
|
func (s SciddirIntro) encode(w io.Writer) error {
|
|
if _, err := w.Write([]byte{s.Direction}); err != nil {
|
|
return err
|
|
}
|
|
_, err := w.Write(s.SCID[:])
|
|
|
|
return err
|
|
}
|
|
|
|
func (s SciddirIntro) validate() error {
|
|
switch s.Direction {
|
|
case 0x00, 0x01:
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("%w: 0x%02x", ErrInvalidIntroNode, s.Direction)
|
|
}
|
|
|
|
// Bytes returns the wire-format encoding of the sciddir variant.
|
|
func (s SciddirIntro) Bytes() []byte {
|
|
var buf bytes.Buffer
|
|
buf.Grow(sciddirLen)
|
|
|
|
// We ignore the error because encode only writes the fixed-size
|
|
// direction and SCID to an in-memory buffer, which cannot fail.
|
|
_ = s.encode(&buf)
|
|
|
|
return buf.Bytes()
|
|
}
|