lnwire: add ChannelAnnoucement,NodeAnnoucement,ChannelUpdateAnnoucement messages

This commit is contained in:
Andrey Samokhvalov 2016-12-07 18:46:22 +03:00 committed by Olaoluwa Osuntokun
parent d5423f007d
commit b440005219
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
9 changed files with 1059 additions and 5 deletions

@ -0,0 +1,232 @@
package lnwire
import (
"bytes"
"fmt"
"github.com/go-errors/errors"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/wire"
"io"
)
// ChannelID represent the set of data which is needed to retrieve all
// necessary data to validate the channel existance.
type ChannelID struct {
// BlockHeight is the height of the block where funding
// transaction located.
// NOTE: This field is limited to 3 bytes.
BlockHeight uint32
// TxIndex is a position of funding transaction within a block.
// NOTE: This field is limited to 3 bytes.
TxIndex uint32
// TxPosition indicating transaction output which pays to the
// channel.
TxPosition uint16
}
func (c *ChannelID) String() string {
return fmt.Sprintf("BlockHeight:\t\t\t%v\n", c.BlockHeight) +
fmt.Sprintf("TxIndex:\t\t\t%v\n", c.TxIndex) +
fmt.Sprintf("TxPosition:\t\t\t%v\n", c.TxPosition)
}
// ChannelAnnouncement message is used to announce the existence of a channel
// between two peers in the overlay, which is propagated by the discovery
// service over broadcast handler.
type ChannelAnnouncement struct {
// This signatures are used by nodes in order to create cross
// references between node's channel and node. Requiring both nodes
// to sign indicates they are both willing to route other payments via
// this node.
FirstNodeSig *btcec.Signature
SecondNodeSig *btcec.Signature
// ChannelID is the unique description of the funding transaction.
ChannelID *ChannelID
// This signatures are used by nodes in order to create cross
// references between node's channel and node. Requiring the bitcoin
// signatures proves they control the channel.
FirstBitcoinSig *btcec.Signature
SecondBitcoinSig *btcec.Signature
// The public keys of the two nodes who are operating the channel, such
// that is FirstNodeID the numerically-lesser of the two DER encoded
// keys (ascending numerical order).
FirstNodeID *btcec.PublicKey
SecondNodeID *btcec.PublicKey
// Public keys which corresponds to the keys which was declared in
// multisig funding transaction output.
FirstBitcoinKey *btcec.PublicKey
SecondBitcoinKey *btcec.PublicKey
}
// A compile time check to ensure ChannelAnnouncement implements the
// lnwire.Message interface.
var _ Message = (*ChannelAnnouncement)(nil)
// Validate performs any necessary sanity checks to ensure all fields present
// on the ChannelAnnouncement are valid.
//
// This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement) Validate() error {
var sigHash []byte
sigHash = wire.DoubleSha256(a.FirstNodeID.SerializeCompressed())
if !a.FirstBitcoinSig.Verify(sigHash, a.FirstBitcoinKey) {
return errors.New("can't verify first bitcoin signature")
}
sigHash = wire.DoubleSha256(a.SecondNodeID.SerializeCompressed())
if !a.SecondBitcoinSig.Verify(sigHash, a.SecondBitcoinKey) {
return errors.New("can't verify second bitcoin signature")
}
data, err := a.DataToSign()
if err != nil {
return err
}
dataHash := wire.DoubleSha256(data)
if !a.FirstNodeSig.Verify(dataHash, a.FirstNodeID) {
return errors.New("can't verify data in first node signature")
}
if !a.SecondNodeSig.Verify(dataHash, a.SecondNodeID) {
return errors.New("can't verify data in second node signature")
}
return nil
}
// Decode deserializes a serialized ChannelAnnouncement stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelAnnouncement) Decode(r io.Reader, pver uint32) error {
err := readElements(r,
&c.FirstNodeSig,
&c.SecondNodeSig,
&c.ChannelID,
&c.FirstBitcoinSig,
&c.SecondBitcoinSig,
&c.FirstNodeID,
&c.SecondNodeID,
&c.FirstBitcoinKey,
&c.SecondBitcoinKey,
)
if err != nil {
return err
}
return nil
}
// Encode serializes the target ChannelAnnouncement into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ChannelAnnouncement) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.FirstNodeSig,
c.SecondNodeSig,
c.ChannelID,
c.FirstBitcoinSig,
c.SecondBitcoinSig,
c.FirstNodeID,
c.SecondNodeID,
c.FirstBitcoinKey,
c.SecondBitcoinKey,
)
if err != nil {
return err
}
return nil
}
// Command returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ChannelAnnouncement) Command() uint32 {
return CmdChannelAnnoucmentMessage
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelAnnouncement) MaxPayloadLength(pver uint32) uint32 {
var length uint32
// FirstNodeSig - 64 bytes
length += 64
// SecondNodeSig - 64 bytes
length += 64
// ChannelID - 8 bytes
length += 8
// FirstBitcoinSig - 64 bytes
length += 64
// SecondBitcoinSig - 64 bytes
length += 64
// FirstNodeID - 33 bytes
length += 33
// SecondNodeID - 33 bytes
length += 33
// FirstBitcoinKey - 33 bytes
length += 33
// SecondBitcoinKey - 33 bytes
length += 33
return length
}
// String returns the string representation of the target ChannelAnnouncement.
//
// This is part of the lnwire.Message interface.
func (c *ChannelAnnouncement) String() string {
return fmt.Sprintf("\n--- Begin ChannelAnnouncement ---\n") +
fmt.Sprintf("FirstNodeSig:\t\t%v\n", c.FirstNodeSig) +
fmt.Sprintf("SecondNodeSig:\t\t%v\n", c.SecondNodeSig) +
fmt.Sprintf("ChannelID:\t\t%v\n", c.ChannelID.String()) +
fmt.Sprintf("FirstBitcoinSig:\t\t%v\n", c.FirstBitcoinSig) +
fmt.Sprintf("SecondBitcoinSig:\t\t%v\n", c.SecondBitcoinSig) +
fmt.Sprintf("FirstNodeSig:\t\t%v\n", c.FirstNodeSig) +
fmt.Sprintf("SecondNodeID:\t\t%v\n", c.SecondNodeID) +
fmt.Sprintf("FirstBitcoinKey:\t\t%v\n", c.FirstBitcoinKey) +
fmt.Sprintf("SecondBitcoinKey:\t\t%v\n", c.SecondBitcoinKey) +
fmt.Sprintf("--- End ChannelAnnouncement ---\n")
}
// DataToSign is used to retrieve part of the announcement message which
// should be signed.
func (c *ChannelAnnouncement) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
var w bytes.Buffer
err := writeElements(&w,
c.ChannelID,
c.FirstBitcoinSig,
c.SecondBitcoinSig,
c.FirstNodeID,
c.SecondNodeID,
c.FirstBitcoinKey,
c.SecondBitcoinKey,
)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}

@ -0,0 +1,125 @@
package lnwire
import (
"bytes"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/wire"
"reflect"
"testing"
)
func TestChannelAnnoucementEncodeDecode(t *testing.T) {
ca := &ChannelAnnouncement{
FirstNodeSig: someSig,
SecondNodeSig: someSig,
ChannelID: someChannelID,
FirstBitcoinSig: someSig,
SecondBitcoinSig: someSig,
FirstNodeID: pubKey,
SecondNodeID: pubKey,
FirstBitcoinKey: pubKey,
SecondBitcoinKey: pubKey,
}
// Next encode the CA message into an empty bytes buffer.
var b bytes.Buffer
if err := ca.Encode(&b, 0); err != nil {
t.Fatalf("unable to encode ChannelAnnouncement: %v", err)
}
// Deserialize the encoded CA message into a new empty struct.
ca2 := &ChannelAnnouncement{}
if err := ca2.Decode(&b, 0); err != nil {
t.Fatalf("unable to decode ChannelAnnouncement: %v", err)
}
// Assert equality of the two instances.
if !reflect.DeepEqual(ca, ca2) {
t.Fatalf("encode/decode error messages don't match %#v vs %#v",
ca, ca2)
}
}
func TestChannelAnnoucementValidation(t *testing.T) {
getKeys := func(s string) (*btcec.PrivateKey, *btcec.PublicKey) {
return btcec.PrivKeyFromBytes(btcec.S256(), []byte(s))
}
firstNodePrivKey, firstNodePubKey := getKeys("node-id-1")
secondNodePrivKey, secondNodePubKey := getKeys("node-id-2")
firstBitcoinPrivKey, firstBitcoinPubKey := getKeys("bitcoin-key-1")
secondBitcoinPrivKey, secondBitcoinPubKey := getKeys("bitcoin-key-2")
var hash []byte
hash = wire.DoubleSha256(firstNodePubKey.SerializeCompressed())
firstBitcoinSig, _ := firstBitcoinPrivKey.Sign(hash)
hash = wire.DoubleSha256(secondNodePubKey.SerializeCompressed())
secondBitcoinSig, _ := secondBitcoinPrivKey.Sign(hash)
ca := &ChannelAnnouncement{
ChannelID: someChannelID,
FirstBitcoinSig: firstBitcoinSig,
SecondBitcoinSig: secondBitcoinSig,
FirstNodeID: firstNodePubKey,
SecondNodeID: secondNodePubKey,
FirstBitcoinKey: firstBitcoinPubKey,
SecondBitcoinKey: secondBitcoinPubKey,
}
dataToSign, _ := ca.DataToSign()
hash = wire.DoubleSha256(dataToSign)
firstNodeSign, _ := firstNodePrivKey.Sign(hash)
ca.FirstNodeSig = firstNodeSign
secondNodeSign, _ := secondNodePrivKey.Sign(hash)
ca.SecondNodeSig = secondNodeSign
if err := ca.Validate(); err != nil {
t.Fatal(err)
}
}
func TestChannelAnnoucementBadValidation(t *testing.T) {
getKeys := func(s string) (*btcec.PrivateKey, *btcec.PublicKey) {
return btcec.PrivKeyFromBytes(btcec.S256(), []byte(s))
}
firstNodePrivKey, firstNodePubKey := getKeys("node-id-1")
secondNodePrivKey, secondNodePubKey := getKeys("node-id-2")
firstBitcoinPrivKey, _ := getKeys("bitcoin-key-1")
secondBitcoinPrivKey, _ := getKeys("bitcoin-key-2")
var hash []byte
hash = wire.DoubleSha256(firstNodePubKey.SerializeCompressed())
firstBitcoinSig, _ := firstBitcoinPrivKey.Sign(hash)
hash = wire.DoubleSha256(secondNodePubKey.SerializeCompressed())
secondBitcoinSig, _ := secondBitcoinPrivKey.Sign(hash)
ca := &ChannelAnnouncement{
ChannelID: someChannelID,
FirstBitcoinSig: firstBitcoinSig,
SecondBitcoinSig: secondBitcoinSig,
FirstNodeID: pubKey, // wrong pubkey
SecondNodeID: pubKey, // wrong pubkey
FirstBitcoinKey: pubKey, // wrong pubkey
SecondBitcoinKey: pubKey, // wrong pubkey
}
dataToSign, _ := ca.DataToSign()
hash = wire.DoubleSha256(dataToSign)
firstNodeSign, _ := firstNodePrivKey.Sign(hash)
ca.FirstNodeSig = firstNodeSign
secondNodeSign, _ := secondNodePrivKey.Sign(hash)
ca.SecondNodeSig = secondNodeSign
if err := ca.Validate(); err == nil {
t.Fatal("error should be raised")
}
}

@ -0,0 +1,190 @@
package lnwire
import (
"bytes"
"errors"
"fmt"
"github.com/roasbeef/btcd/btcec"
"io"
)
// ChannelUpdateAnnouncement message is used after channel has been initially
// announced. Each side independently announces its fees and minimum expiry for
// HTLCs and other parameters. Also this message is used to redeclare initially
// setted channel parameters.
type ChannelUpdateAnnouncement struct {
// Signature is used to validate the announced data and prove the
// ownership of node id.
Signature *btcec.Signature
// ChannelID is the unique description of the funding transaction.
ChannelID *ChannelID
// Timestamp allows ordering in the case of multiple announcements.
// We should ignore the message if timestamp is not greater than
// the last-received.
Timestamp uint32
// Flags least-significant bit must be set to 0 if the creating node
// corresponds to the first node in previously sent channel
// announcement and 1 otherwise.
Flags uint16
// Expiry is the minimum number of blocks this node requires to be
// added to the expiry of HTLCs. This is a security parameter determined
// by the node operator.
Expiry uint16
// HtlcMinimumMstat is the minimum HTLC value which will be accepted.
HtlcMinimumMstat uint32
// FeeBaseMstat...
FeeBaseMstat uint32
// FeeProportionalMillionths...
FeeProportionalMillionths uint32
}
// A compile time check to ensure ChannelUpdateAnnouncement implements the
// lnwire.Message interface.
var _ Message = (*ChannelUpdateAnnouncement)(nil)
// Validate performs any necessary sanity checks to ensure all fields present
// on the ChannelUpdateAnnouncement are valid.
//
// This is part of the lnwire.Message interface.
func (a *ChannelUpdateAnnouncement) Validate() error {
// NOTE: As far as we don't have the node id (public key) in this
// message, we can't validate the signature on this stage, it should
// be validated latter - in discovery service handler.
if a.Expiry == 0 {
return errors.New("expiry should be greater then zero")
}
return nil
}
// Decode deserializes a serialized ChannelUpdateAnnouncement stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdateAnnouncement) Decode(r io.Reader, pver uint32) error {
err := readElements(r,
&c.Signature,
&c.ChannelID,
&c.Timestamp,
&c.Flags,
&c.Expiry,
&c.HtlcMinimumMstat,
&c.FeeBaseMstat,
&c.FeeProportionalMillionths,
)
if err != nil {
return err
}
return nil
}
// Encode serializes the target ChannelUpdateAnnouncement into the passed
// io.Writer observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdateAnnouncement) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.Signature,
c.ChannelID,
c.Timestamp,
c.Flags,
c.Expiry,
c.HtlcMinimumMstat,
c.FeeBaseMstat,
c.FeeProportionalMillionths,
)
if err != nil {
return err
}
return nil
}
// Command returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdateAnnouncement) Command() uint32 {
return CmdChannelUpdateAnnoucmentMessage
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdateAnnouncement) MaxPayloadLength(pver uint32) uint32 {
var length uint32
// Signature - 64 bytes
length += 64
// ChannelID - 8 bytes
length += 8
// Timestamp - 4 bytes
length += 4
// Flags - 2 bytes
length += 2
// Expiry - 2 bytes
length += 2
// HtlcMinimumMstat - 4 bytes
length += 4
// FeeBaseMstat - 4 bytes
length += 4
// FeeProportionalMillionths - 4 bytes
length += 4
return length
}
// String returns the string representation of the target ChannelUpdateAnnouncement.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdateAnnouncement) String() string {
return fmt.Sprintf("\n--- Begin ChannelUpdateAnnouncement ---\n") +
fmt.Sprintf("Signature:\t\t%v\n", c.Signature) +
fmt.Sprintf("ChannelID:\t\t%v\n", c.ChannelID.String()) +
fmt.Sprintf("Timestamp:\t\t%v\n", c.Timestamp) +
fmt.Sprintf("Flags:\t\t%v\n", c.Flags) +
fmt.Sprintf("Expiry:\t\t%v\n", c.Expiry) +
fmt.Sprintf("HtlcMinimumMstat:\t\t%v\n", c.HtlcMinimumMstat) +
fmt.Sprintf("FeeBaseMstat:\t\t%v\n", c.FeeBaseMstat) +
fmt.Sprintf("FeeProportionalMillionths:\t\t%v\n", c.FeeProportionalMillionths) +
fmt.Sprintf("--- End ChannelUpdateAnnouncement ---\n")
}
// DataToSign is used to retrieve part of the announcement message which
// should be signed.
func (c *ChannelUpdateAnnouncement) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
var w bytes.Buffer
err := writeElements(&w,
c.ChannelID,
c.Timestamp,
c.Flags,
c.Expiry,
c.HtlcMinimumMstat,
c.FeeBaseMstat,
c.FeeProportionalMillionths,
)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}

@ -0,0 +1,38 @@
package lnwire
import (
"bytes"
"reflect"
"testing"
)
func TestChannelUpdateAnnouncementEncodeDecode(t *testing.T) {
cua := &ChannelUpdateAnnouncement{
Signature: someSig,
ChannelID: someChannelID,
Timestamp: maxUint32,
Flags: maxUint16,
Expiry: maxUint16,
HtlcMinimumMstat: maxUint32,
FeeBaseMstat: maxUint32,
FeeProportionalMillionths: maxUint32,
}
// Next encode the CUA message into an empty bytes buffer.
var b bytes.Buffer
if err := cua.Encode(&b, 0); err != nil {
t.Fatalf("unable to encode ChannelUpdateAnnouncement: %v", err)
}
// Deserialize the encoded CUA message into a new empty struct.
cua2 := &ChannelUpdateAnnouncement{}
if err := cua2.Decode(&b, 0); err != nil {
t.Fatalf("unable to decode ChannelUpdateAnnouncement: %v", err)
}
// Assert equality of the two instances.
if !reflect.DeepEqual(cua, cua2) {
t.Fatalf("encode/decode error messages don't match %#v vs %#v",
cua, cua2)
}
}

@ -6,10 +6,12 @@ import (
"fmt"
"io"
"github.com/go-errors/errors"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"net"
)
// MaxSliceLength is the maximum allowed lenth for any opaque byte slices in
@ -324,6 +326,63 @@ func writeElement(w io.Writer, element interface{}) error {
if err != nil {
return err
}
case *ChannelID:
// Check that field fit in 3 bytes and write the blockHeight
if e.BlockHeight > ((1 << 24) - 1) {
return errors.New("block height should fit in 3 bytes")
}
var blockHeight [4]byte
binary.BigEndian.PutUint32(blockHeight[:], e.BlockHeight)
if _, err := w.Write(blockHeight[1:]); err != nil {
return err
}
// Check that field fit in 3 bytes and write the txIndex
if e.TxIndex > ((1 << 24) - 1) {
return errors.New("tx index should fit in 3 bytes")
}
var txIndex [4]byte
binary.BigEndian.PutUint32(txIndex[:], e.TxIndex)
if _, err := w.Write(txIndex[1:]); err != nil {
return err
}
// Write the txPosition
var txPosition [2]byte
binary.BigEndian.PutUint16(txPosition[:], e.TxPosition)
if _, err := w.Write(txPosition[:]); err != nil {
return err
}
case *net.TCPAddr:
var ip [16]byte
copy(ip[:], e.IP.To16())
if _, err := w.Write(ip[:]); err != nil {
return err
}
var port [4]byte
binary.BigEndian.PutUint32(port[:], uint32(e.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
case RGB:
err := writeElements(w,
e.red,
e.green,
e.blue,
)
if err != nil {
return err
}
case Alias:
if err := writeElements(w, ([32]byte)(e)); err != nil {
return err
}
default:
return fmt.Errorf("Unknown type in writeElement: %T", e)
}
@ -403,7 +462,7 @@ func readElement(r io.Reader, element interface{}) error {
}
*e = &b
case **btcec.PublicKey:
var b [33]byte
var b [btcec.PubKeyBytesLenCompressed]byte
if _, err = io.ReadFull(r, b[:]); err != nil {
return err
}
@ -503,11 +562,11 @@ func readElement(r io.Reader, element interface{}) error {
return err
}
case *[]byte:
bytes, err := wire.ReadVarBytes(r, 0, MaxSliceLength, "byte slice")
b, err := wire.ReadVarBytes(r, 0, MaxSliceLength, "byte slice")
if err != nil {
return err
}
*e = bytes
*e = b
case *PkScript:
pkScript, err := wire.ReadVarBytes(r, 0, 25, "pkscript")
if err != nil {
@ -610,6 +669,58 @@ func readElement(r io.Reader, element interface{}) error {
if err != nil {
return err
}
case **ChannelID:
var blockHeight [4]byte
if _, err = io.ReadFull(r, blockHeight[1:]); err != nil {
return err
}
var txIndex [4]byte
if _, err = io.ReadFull(r, txIndex[1:]); err != nil {
return err
}
var txPosition [2]byte
if _, err = io.ReadFull(r, txPosition[:]); err != nil {
return err
}
*e = &ChannelID{
BlockHeight: binary.BigEndian.Uint32(blockHeight[:]),
TxIndex: binary.BigEndian.Uint32(txIndex[:]),
TxPosition: binary.BigEndian.Uint16(txPosition[:]),
}
case **net.TCPAddr:
var ip [16]byte
if _, err = io.ReadFull(r, ip[:]); err != nil {
return err
}
var port [4]byte
if _, err = io.ReadFull(r, port[:]); err != nil {
return err
}
*e = &net.TCPAddr{
IP: (net.IP)(ip[:]),
Port: int(binary.BigEndian.Uint32(port[:])),
}
case *RGB:
err := readElements(r,
&e.red,
&e.green,
&e.blue,
)
if err != nil {
return err
}
case *Alias:
var a [32]byte
if err := readElements(r, &a); err != nil {
return err
}
*e = (Alias)(a)
default:
return fmt.Errorf("Unknown type in readElement: %T", e)

@ -10,6 +10,7 @@ import (
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"net"
)
// Common variables and functions for the message tests
@ -22,6 +23,10 @@ var (
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
maxUint32 uint32 = (1 << 32) - 1
maxUint24 uint32 = (1 << 24) - 1
maxUint16 uint16 = (1 << 16) - 1
// For debugging, writes to /dev/shm/
// Maybe in the future do it if you do "go test -v"
WRITE_FILE = false
@ -91,6 +96,24 @@ var (
// Reversed when displayed
txidBytes, _ = hex.DecodeString("fd95c6e5c9d5bcf9cfc7231b6a438e46c518c724d0b04b75cc8fddf84a254e3a")
_ = copy(txid[:], txidBytes)
someAlias, _ = NewAlias("012345678901234567890")
someSig, _ = btcec.ParseSignature(sigStr, btcec.S256())
someSigBytes = someSig.Serialize()
someAddress = &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}
someChannelID = &ChannelID{
BlockHeight: maxUint24,
TxIndex: maxUint24,
TxPosition: maxUint16,
}
someRGB = RGB{
red: 255,
green: 255,
blue: 255,
}
)
func SerializeTest(t *testing.T, message Message, expectedString string, filename string) *bytes.Buffer {

@ -55,9 +55,14 @@ const (
// Commands for reporting protocol errors.
CmdErrorGeneric = uint32(4000)
// Commands for discovery service.
CmdChannelAnnoucmentMessage = uint32(5000)
CmdChannelUpdateAnnoucmentMessage = uint32(5010)
CmdNodeAnnoucmentMessage = uint32(5020)
// Commands for connection keep-alive.
CmdPing = uint32(5000)
CmdPong = uint32(5010)
CmdPing = uint32(6000)
CmdPong = uint32(6010)
)
// Message is an interface that defines a lightning wire protocol message. The
@ -114,6 +119,12 @@ func makeEmptyMessage(command uint32) (Message, error) {
msg = &NeighborAckMessage{}
case CmdNeighborRstMessage:
msg = &NeighborRstMessage{}
case CmdChannelAnnoucmentMessage:
msg = &ChannelAnnouncement{}
case CmdChannelUpdateAnnoucmentMessage:
msg = &ChannelUpdateAnnouncement{}
case CmdNodeAnnoucmentMessage:
msg = &NodeAnnouncement{}
case CmdPing:
msg = &Ping{}
case CmdPong:

230
lnwire/node_announcement.go Normal file

@ -0,0 +1,230 @@
package lnwire
import (
"bytes"
"fmt"
"github.com/go-errors/errors"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/wire"
"io"
"net"
)
var (
startPort uint16 = 1024
endPort uint16 = 49151
aliasSpecLen = 21
)
// RGB is used to represent the color.
type RGB struct {
red uint8
green uint8
blue uint8
}
// Alias a hex encoded UTF-8 string that may be displayed
// as an alternative to the node's ID. Notice that aliases are not
// unique and may be freely chosen by the node operators.
type Alias [32]byte
// NewAlias create the alias from string and also checks spec requirements.
func NewAlias(s string) (Alias, error) {
var a Alias
data := []byte(s)
if len(data) > aliasSpecLen {
return a, errors.Errorf("alias too long the size "+
"must be less than %v", aliasSpecLen)
}
copy(a[:], data)
return a, nil
}
func (a *Alias) String() string {
return string(a[:])
}
// Validate check that alias data lenght is lower than spec size.
func (a *Alias) Validate() error {
nonzero := len(a)
for a[nonzero-1] == 0 && nonzero > 0 {
nonzero--
}
if nonzero > aliasSpecLen {
return errors.New("alias should be less then 21 bytes")
}
return nil
}
// NodeAnnouncement message is used to announce the presence of a lightning node
// and signal that the node is accepting incoming connections.
type NodeAnnouncement struct {
// Signature is used to prove the ownership of node id.
Signature *btcec.Signature
// Timestamp allows ordering in the case of multiple announcements.
Timestamp uint32
// Address includes two specification fields: 'ipv6' and 'port' on which
// the node is accepting incoming connections.
Address *net.TCPAddr
// NodeID is a public key which is used as node identificator.
NodeID *btcec.PublicKey
// RGBColor is used to customize their node's appearance in maps and graphs
RGBColor RGB
// pad is used to reserve to additional bytes for future usage.
pad uint16
// Alias is used to customize their node's appearance in maps and graphs
Alias Alias
}
// A compile time check to ensure NodeAnnouncement implements the
// lnwire.Message interface.
var _ Message = (*NodeAnnouncement)(nil)
// Validate performs any necessary sanity checks to ensure all fields present
// on the NodeAnnouncement are valid.
//
// This is part of the lnwire.Message interface.
func (a *NodeAnnouncement) Validate() error {
if err := a.Alias.Validate(); err != nil {
return err
}
data, err := a.DataToSign()
if err != nil {
return err
}
dataHash := wire.DoubleSha256(data)
if !a.Signature.Verify(dataHash, a.NodeID) {
return errors.New("can't check the node annoucement signature")
}
return nil
}
// Decode deserializes a serialized NodeAnnouncement stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *NodeAnnouncement) Decode(r io.Reader, pver uint32) error {
err := readElements(r,
&c.Signature,
&c.Timestamp,
&c.Address,
&c.NodeID,
&c.RGBColor,
&c.pad,
&c.Alias,
)
if err != nil {
return err
}
return nil
}
// Encode serializes the target NodeAnnouncement into the passed
// io.Writer observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *NodeAnnouncement) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.Signature,
c.Timestamp,
c.Address,
c.NodeID,
c.RGBColor,
c.pad,
c.Alias,
)
if err != nil {
return err
}
return nil
}
// Command returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *NodeAnnouncement) Command() uint32 {
return CmdNodeAnnoucmentMessage
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *NodeAnnouncement) MaxPayloadLength(pver uint32) uint32 {
var length uint32
// Signature - 64 bytes
length += 64
// Timestamp - 4 bytes
length += 4
// Ipv6 - 16 bytes
length += 16
// Port - 2 bytes
length += 2
// NodeID - 32 bytes
length += 32
// RGBColor - 3 bytes
length += 3
// pad - 2 bytes
length += 2
// Alias - 32 bytes
length += 32
return length
}
// String returns the string representation of the target NodeAnnouncement.
//
// This is part of the lnwire.Message interface.
func (c *NodeAnnouncement) String() string {
return fmt.Sprintf("\n--- Begin NodeAnnouncement ---\n") +
fmt.Sprintf("Signature:\t\t%v\n", c.Signature) +
fmt.Sprintf("Timestamp:\t\t%v\n", c.Timestamp) +
fmt.Sprintf("Address:\t\t%v\n", c.Address.String()) +
fmt.Sprintf("NodeID:\t\t%v\n", c.NodeID) +
fmt.Sprintf("RGBColor:\t\t%v\n", c.RGBColor) +
fmt.Sprintf("Alias:\t\t%v\n", c.Alias) +
fmt.Sprintf("--- End NodeAnnouncement ---\n")
}
// dataToSign...
func (c *NodeAnnouncement) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
var w bytes.Buffer
err := writeElements(&w,
c.Timestamp,
c.Address,
c.NodeID,
c.RGBColor,
c.pad,
c.Alias,
)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}

@ -0,0 +1,94 @@
package lnwire
import (
"bytes"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/wire"
"reflect"
"testing"
)
func TestNodeAnnouncementEncodeDecode(t *testing.T) {
cua := &NodeAnnouncement{
Signature: someSig,
Timestamp: maxUint32,
Address: someAddress,
NodeID: pubKey,
RGBColor: someRGB,
pad: maxUint16,
Alias: someAlias,
}
// Next encode the NA message into an empty bytes buffer.
var b bytes.Buffer
if err := cua.Encode(&b, 0); err != nil {
t.Fatalf("unable to encode NodeAnnouncement: %v", err)
}
// Deserialize the encoded NA message into a new empty struct.
cua2 := &NodeAnnouncement{}
if err := cua2.Decode(&b, 0); err != nil {
t.Fatalf("unable to decode NodeAnnouncement: %v", err)
}
// Assert equality of the two instances.
if !reflect.DeepEqual(cua, cua2) {
t.Fatalf("encode/decode error messages don't match %#v vs %#v",
cua, cua2)
}
}
func TestNodeAnnoucementValidation(t *testing.T) {
getKeys := func(s string) (*btcec.PrivateKey, *btcec.PublicKey) {
return btcec.PrivKeyFromBytes(btcec.S256(), []byte(s))
}
nodePrivKey, nodePubKey := getKeys("node-id-1")
var hash []byte
na := &NodeAnnouncement{
Timestamp: maxUint32,
Address: someAddress,
NodeID: nodePubKey,
RGBColor: someRGB,
pad: maxUint16,
Alias: someAlias,
}
dataToSign, _ := na.DataToSign()
hash = wire.DoubleSha256(dataToSign)
signature, _ := nodePrivKey.Sign(hash)
na.Signature = signature
if err := na.Validate(); err != nil {
t.Fatal(err)
}
}
func TestNodeAnnoucementBadValidation(t *testing.T) {
getKeys := func(s string) (*btcec.PrivateKey, *btcec.PublicKey) {
return btcec.PrivKeyFromBytes(btcec.S256(), []byte(s))
}
na := &NodeAnnouncement{
Timestamp: maxUint32,
Address: someAddress,
NodeID: pubKey, // wrong pubkey
RGBColor: someRGB,
pad: maxUint16,
Alias: someAlias,
}
nodePrivKey, _ := getKeys("node-id-1")
dataToSign, _ := na.DataToSign()
hash := wire.DoubleSha256(dataToSign)
signature, _ := nodePrivKey.Sign(hash)
na.Signature = signature
if err := na.Validate(); err == nil {
t.Fatal("error wasn't raised")
}
}