lnwire: add ChannelAnnoucement,NodeAnnoucement,ChannelUpdateAnnoucement messages
This commit is contained in:
parent
d5423f007d
commit
b440005219
232
lnwire/channel_announcement.go
Normal file
232
lnwire/channel_announcement.go
Normal file
@ -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
|
||||
}
|
125
lnwire/channel_announcement_test.go
Normal file
125
lnwire/channel_announcement_test.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
190
lnwire/channel_update_announcement.go
Normal file
190
lnwire/channel_update_announcement.go
Normal file
@ -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
|
||||
}
|
38
lnwire/channel_update_announcement_test.go
Normal file
38
lnwire/channel_update_announcement_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
117
lnwire/lnwire.go
117
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)
|
||||
|
@ -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
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
|
||||
}
|
94
lnwire/node_announcement_test.go
Normal file
94
lnwire/node_announcement_test.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user