What changed, and why it matters
This commit adds a new network message type called OnionMessage to the Lightning Network Daemon (LND). It is a protocol-level feature for carrying encrypted BOLT12 messages, not a fix for a known security flaw. The change itself is straightforward wiring: it registers the message type, adds encode/decode logic, and includes tests. There is no direct evidence in the commit that this introduces a vulnerability, but any new message parser adds a small amount of attack surface.
Treat as a routine feature commit. Review the OnionMessage handler/consumer code (not shown here) to ensure the onion blob is passed safely into the Sphinx/BOLT4 parsing path and that resource limits are enforced before expensive decryption. Continue monitoring fuzz results for the new FuzzOnionMessage target.
Security signals we found
New wire message parser added (OnionMessage.Decode)
Variable-length byte field parsed from network input (uint16 length prefix)
Public key parsed from network input without explicit curve-point validation at this layer
Fuzz test added for the new parser
No bounds check beyond uint16 length, which caps the blob at 65,535 bytes
Evidence from the diff
The patch introduces MsgOnionMessage (type 513) and an OnionMessage struct with a public key (PathKey) and a variable-length onion blob. It wires the type into the message registry, string mapping, factory function, fuzz tests, and rapid test generators. The parser reads a compressed public key, a uint16 length, and then that many bytes. No validation of the onion payload contents is performed at this layer, which is expected because decryption happens elsewhere. The commit is purely additive and does not modify existing security-critical logic.
Changed components
lnwire/message.golnwire/onion_message.golnwire/fuzz_test.golnwire/test_message.goInspect captured patch +114 / −0
diff --git a/lnwire/fuzz_test.go b/lnwire/fuzz_test.go
index a143ce6..6bbc867 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 c64b09b..428f06b 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 "<unknown>"
}
@@ -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 0000000..cabbc21
--- /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 4e62bee..9aae7d2 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)
Why this scored 23/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.