diff --git a/lnwire/fuzz_test.go b/lnwire/fuzz_test.go index a143ce6d9..6bbc86710 100644 --- a/lnwire/fuzz_test.go +++ b/lnwire/fuzz_test.go @@ -498,6 +498,12 @@ func FuzzCustomMessage(f *testing.F) { }) } +func FuzzOnionMessage(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + wireMsgHarness(t, data, MsgOnionMessage) + }) +} + // FuzzParseRawSignature tests that our DER-encoded signature parsing does not // panic for arbitrary inputs and that serializing and reparsing the signatures // does not mutate them. diff --git a/lnwire/message.go b/lnwire/message.go index c64b09b12..428f06b2f 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -66,6 +66,7 @@ const ( MsgChannelAnnouncement2 = 267 MsgNodeAnnouncement2 = 269 MsgChannelUpdate2 = 271 + MsgOnionMessage = 513 MsgKickoffSig = 777 // MsgEnd defines the end of the official message range of the protocol. @@ -198,6 +199,8 @@ func (t MessageType) String() string { return "NodeAnnouncement2" case MsgChannelUpdate2: return "ChannelUpdate2" + case MsgOnionMessage: + return "OnionMessage" default: return "" } @@ -362,6 +365,8 @@ func makeEmptyMessage(msgType MessageType) (Message, error) { msg = &NodeAnnouncement2{} case MsgChannelUpdate2: msg = &ChannelUpdate2{} + case MsgOnionMessage: + msg = &OnionMessage{} default: // If the message is not within our custom range and has not // specifically been overridden, return an unknown message. diff --git a/lnwire/onion_message.go b/lnwire/onion_message.go new file mode 100644 index 000000000..cabbc2141 --- /dev/null +++ b/lnwire/onion_message.go @@ -0,0 +1,81 @@ +package lnwire + +import ( + "bytes" + "io" + + "github.com/btcsuite/btcd/btcec/v2" +) + +// OnionMessage is a message that carries an onion-encrypted payload. +// This is used for BOLT12 messages. +type OnionMessage struct { + // PathKey is the route blinding ephemeral pubkey to be used for + // the onion message. + PathKey *btcec.PublicKey + + // OnionBlob contains the onion_message_packet, the raw serialized + // Sphinx onion packet (BOLT 4) containing the layered, per-hop + // encrypted payloads and routing instructions used to forward this + // message along its designated path. This blob should be handled in the + // same manner as onion_routing_packet used to route HTLCs, with the + // exception that it uses blinded routes by default. + OnionBlob []byte +} + +// NewOnionMessage creates a new OnionMessage. +func NewOnionMessage(pathKey *btcec.PublicKey, + onion []byte) *OnionMessage { + + return &OnionMessage{ + PathKey: pathKey, + OnionBlob: onion, + } +} + +// A compile-time check to ensure OnionMessage implements the Message interface. +var _ Message = (*OnionMessage)(nil) + +// Decode reads the bytes stream and converts it to the object. +func (o *OnionMessage) Decode(r io.Reader, _ uint32) error { + if err := ReadElement(r, &o.PathKey); err != nil { + return err + } + + var onionLen uint16 + if err := ReadElement(r, &onionLen); err != nil { + return err + } + + o.OnionBlob = make([]byte, onionLen) + if err := ReadElement(r, o.OnionBlob); err != nil { + return err + } + + return nil +} + +// Encode converts object to the bytes stream and write it into the +// write buffer. +func (o *OnionMessage) Encode(w *bytes.Buffer, _ uint32) error { + if err := WritePublicKey(w, o.PathKey); err != nil { + return err + } + + onionLen := len(o.OnionBlob) + if err := WriteUint16(w, uint16(onionLen)); err != nil { + return err + } + + if err := WriteBytes(w, o.OnionBlob); err != nil { + return err + } + + return nil +} + +// MsgType returns the integer uniquely identifying this message type on the +// wire. +func (o *OnionMessage) MsgType() MessageType { + return MsgOnionMessage +} diff --git a/lnwire/test_message.go b/lnwire/test_message.go index 4e62bee98..9aae7d273 100644 --- a/lnwire/test_message.go +++ b/lnwire/test_message.go @@ -822,6 +822,28 @@ func (c *Custom) RandTestMessage(t *rapid.T) Message { return msg } +// A compile time check to ensure OnionMessage implements the lnwire.TestMessage +// interface. +var _ TestMessage = (*OnionMessage)(nil) + +// RandTestMessage populates the message with random data suitable for testing. +// It uses the rapid testing framework to generate random values. +// +// This is part of the TestMessage interface. +func (o *OnionMessage) RandTestMessage(t *rapid.T) Message { + // Generate random compressed public key for node ID + pathKey := RandPubKey(t) + + dataLen := rapid.IntRange(0, 1000).Draw(t, "onionMessageDataLength") + data := rapid.SliceOfN(rapid.Byte(), dataLen, dataLen).Draw( + t, "onionMessageData", + ) + + msg := NewOnionMessage(pathKey, data) + + return msg +} + // A compile time check to ensure DynAck implements the lnwire.TestMessage // interface. var _ TestMessage = (*DynAck)(nil)