diff --git a/lnwire/channel_announcement.go b/lnwire/channel_announcement.go new file mode 100644 index 00000000..2a8ec3be --- /dev/null +++ b/lnwire/channel_announcement.go @@ -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 +} diff --git a/lnwire/channel_announcement_test.go b/lnwire/channel_announcement_test.go new file mode 100644 index 00000000..a0009d07 --- /dev/null +++ b/lnwire/channel_announcement_test.go @@ -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") + } +} diff --git a/lnwire/channel_update_announcement.go b/lnwire/channel_update_announcement.go new file mode 100644 index 00000000..803e945d --- /dev/null +++ b/lnwire/channel_update_announcement.go @@ -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 +} diff --git a/lnwire/channel_update_announcement_test.go b/lnwire/channel_update_announcement_test.go new file mode 100644 index 00000000..4d578add --- /dev/null +++ b/lnwire/channel_update_announcement_test.go @@ -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) + } +} diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 3d82685b..bd102386 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -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) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 08cc01b0..facc7c21 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -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 { diff --git a/lnwire/message.go b/lnwire/message.go index baf2ff4d..266c7405 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -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: diff --git a/lnwire/node_announcement.go b/lnwire/node_announcement.go new file mode 100644 index 00000000..33829ec7 --- /dev/null +++ b/lnwire/node_announcement.go @@ -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 +} diff --git a/lnwire/node_announcement_test.go b/lnwire/node_announcement_test.go new file mode 100644 index 00000000..52446114 --- /dev/null +++ b/lnwire/node_announcement_test.go @@ -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") + } +}