get rid of unsigned RoutingState struct, only expose SignedRoutingState

This commit is contained in:
Yusef Napora 2019-11-21 09:09:31 -06:00
parent a56dc2cb55
commit bf3693255b
5 changed files with 126 additions and 170 deletions

View File

@ -78,7 +78,7 @@ func OpenEnvelope(envelopeBytes []byte, domain string) (*SignedEnvelope, error)
// without validating its contents. Should not be used unless you have a very good reason // without validating its contents. Should not be used unless you have a very good reason
// (e.g. testing)! // (e.g. testing)!
func UnmarshalEnvelopeWithoutValidating(serializedEnvelope []byte) (*SignedEnvelope, error) { func UnmarshalEnvelopeWithoutValidating(serializedEnvelope []byte) (*SignedEnvelope, error) {
e := pb.SignedEnvelope{} var e pb.SignedEnvelope
if err := proto.Unmarshal(serializedEnvelope, &e); err != nil { if err := proto.Unmarshal(serializedEnvelope, &e); err != nil {
return nil, err return nil, err
} }

View File

@ -2,7 +2,6 @@ package host
import ( import (
"errors" "errors"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/routing" "github.com/libp2p/go-libp2p-core/routing"
) )
@ -15,19 +14,13 @@ func InfoFromHost(h Host) *peer.AddrInfo {
} }
} }
// RoutingStateFromHost returns a routing.RoutingState record that contains the Host's // SignedRoutingStateFromHost returns a SignedRoutingState record containing
// ID and all of its listen Addrs. // the Host's listen addresses, signed with the Host's private key.
func RoutingStateFromHost(h Host) *routing.RoutingState { func SignedRoutingStateFromHost(h Host) (*routing.SignedRoutingState, error) {
return routing.RoutingStateFromAddrInfo(InfoFromHost(h))
}
// SignedRoutingStateFromHost wraps a routing.RoutingState record in a
// SignedEnvelope, signed with the Host's private key.
func SignedRoutingStateFromHost(h Host) (*crypto.SignedEnvelope, error) {
privKey := h.Peerstore().PrivKey(h.ID()) privKey := h.Peerstore().PrivKey(h.ID())
if privKey == nil { if privKey == nil {
return nil, errors.New("unable to find host's private key in peerstore") return nil, errors.New("unable to find host's private key in peerstore")
} }
return RoutingStateFromHost(h).ToSignedEnvelope(privKey) return routing.MakeSignedRoutingState(privKey, h.Addrs())
} }

View File

@ -5,6 +5,7 @@ package peerstore
import ( import (
"context" "context"
"errors" "errors"
"github.com/libp2p/go-libp2p-core/routing"
"io" "io"
"math" "math"
"time" "time"
@ -96,9 +97,9 @@ type AddrBook interface {
// If the manager has a longer TTL, the operation is a no-op for that address // If the manager has a longer TTL, the operation is a no-op for that address
AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration)
// AddCertifiedAddrs adds addresses from a routing.RoutingState record // AddCertifiedAddrs adds addresses from a routing.SignedRoutingState record.
// contained in a serialized SignedEnvelope. // Certified addresses will be returned from both Addrs and CertifiedAddrs.
AddCertifiedAddrs(envelope []byte, ttl time.Duration) error AddCertifiedAddrs(s *routing.SignedRoutingState, ttl time.Duration) error
// SetAddr calls mgr.SetAddrs(p, addr, ttl) // SetAddr calls mgr.SetAddrs(p, addr, ttl)
SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration)
@ -111,12 +112,13 @@ type AddrBook interface {
// the given oldTTL to have the given newTTL. // the given oldTTL to have the given newTTL.
UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration) UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration)
// Addrs returns all known (and valid) addresses for a given peer // Addrs returns all known (and valid) addresses for a given peer, including
// both certified and uncertified addresses.
Addrs(p peer.ID) []ma.Multiaddr Addrs(p peer.ID) []ma.Multiaddr
// CertifiedAddrs returns all known addresses for a peer that have // CertifiedAddrs returns all known addresses for a peer that have
// been certified by that peer. CertifiedAddrs are contained in // been certified by that peer and added to the peerstore using
// a SignedEnvelope and added to the Peerstore using AddCertifiedAddrs. // AddCertifiedAddrs. Note that certified addrs are also returned.
CertifiedAddrs(p peer.ID) []ma.Multiaddr CertifiedAddrs(p peer.ID) []ma.Multiaddr
// AddrStream returns a channel that gets all addresses for a given // AddrStream returns a channel that gets all addresses for a given
@ -130,17 +132,16 @@ type AddrBook interface {
// PeersWithAddrs returns all of the peer IDs stored in the AddrBook // PeersWithAddrs returns all of the peer IDs stored in the AddrBook
PeersWithAddrs() peer.IDSlice PeersWithAddrs() peer.IDSlice
// SignedRoutingState returns a signed RoutingState record for the // SignedRoutingState returns a SignedRoutingState record for the
// given peer id, if one exists in the peerstore. The record is // given peer id, if one exists in the peerstore.
// returned as a byte slice containing a serialized SignedEnvelope.
// Returns nil if no routing state exists for the peer. // Returns nil if no routing state exists for the peer.
SignedRoutingState(p peer.ID) []byte SignedRoutingState(p peer.ID) *routing.SignedRoutingState
// SignedRoutingStates returns signed RoutingState records for each of // SignedRoutingStates returns SignedRoutingState records for each of
// the given peer ids, if one exists in the peerstore. // the given peer ids, if one exists in the peerstore.
// Returns a map of peer ids to serialized SignedEnvelope messages. If // Returns a map of peer ids to SignedRoutingState records. If
// no routing state exists for a peer, their map entry will be nil. // no routing state exists for a peer, their map entry will be nil.
SignedRoutingStates(peers ...peer.ID) map[peer.ID][]byte SignedRoutingStates(peers ...peer.ID) map[peer.ID]*routing.SignedRoutingState
} }
// KeyBook tracks the keys of Peers. // KeyBook tracks the keys of Peers.

View File

@ -18,54 +18,75 @@ const StateEnvelopeDomain = "libp2p-routing-state"
// TODO: register multicodec // TODO: register multicodec
var StateEnvelopePayloadType = []byte("/libp2p/routing-state-record") var StateEnvelopePayloadType = []byte("/libp2p/routing-state-record")
// AnnotatedAddr will extend the Multiaddr type with additional metadata, as type SignedRoutingState struct {
// extensions are added to the routing state record spec. It's defined now to
// make refactoring simpler in the future.
type AnnotatedAddr struct {
ma.Multiaddr
}
// RoutingState contains a snapshot of public, transient state (e.g. addresses, supported protocols)
// for a peer at a given point in time, where "time" is defined by the sequence counter
// field Seq. Greater Seq values are later in time than lesser values, but there are no
// guarantees about the wall-clock time between any two Seq values.
//
// Note that Seq values are peer-specific and can only be compared for records with equal PeerIDs.
type RoutingState struct {
// PeerID is the ID of the peer this record pertains to. // PeerID is the ID of the peer this record pertains to.
PeerID peer.ID peerID peer.ID
// Seq is an increment-only sequence counter used to order RoutingState records in time. // Seq is an increment-only sequence counter used to order RoutingState records in time.
Seq uint64 seq uint64
// Addresses contains the public addresses of the peer this record pertains to. // Addresses contains the public addresses of the peer this record pertains to.
Addresses []*AnnotatedAddr addresses []ma.Multiaddr
envelope *crypto.SignedEnvelope
} }
// RoutingStateWithMultiaddrs returns a RoutingState record for the given peer id // MakeSignedRoutingState returns a SignedRoutingState record containing the given multiaddrs,
// that contains the given multiaddrs. It generates a timestamp-based sequence number. // signed with the given private key.
func RoutingStateWithMultiaddrs(p peer.ID, addrs []ma.Multiaddr) *RoutingState {
annotated := make([]*AnnotatedAddr, len(addrs))
for i, a := range addrs {
annotated[i] = &AnnotatedAddr{Multiaddr: a}
}
return &RoutingState{
PeerID: p,
Seq: statelessSeqNo(),
Addresses: annotated,
}
}
// RoutingStateFromAddrInfo converts a peer.AddrInfo into a RoutingState record.
// It generates a timestamp-based sequence number. // It generates a timestamp-based sequence number.
func RoutingStateFromAddrInfo(info *peer.AddrInfo) *RoutingState { func MakeSignedRoutingState(privKey crypto.PrivKey, addrs []ma.Multiaddr) (*SignedRoutingState, error) {
return RoutingStateWithMultiaddrs(info.ID, info.Addrs) p, err := peer.IDFromPrivateKey(privKey)
if err != nil {
return nil, err
}
idBytes, err := p.MarshalBinary()
if err != nil {
return nil, err
}
seq := statelessSeqNo()
msg := pb.RoutingStateRecord{
PeerId: idBytes,
Seq: seq,
Addresses: addrsToProtobuf(addrs),
}
payload, err := proto.Marshal(&msg)
if err != nil {
return nil, err
}
envelope, err := crypto.MakeEnvelope(privKey, StateEnvelopeDomain, StateEnvelopePayloadType, payload)
if err != nil {
return nil, err
}
return &SignedRoutingState{
peerID: p,
seq: seq,
addresses: addrs,
envelope: envelope,
}, nil
} }
// UnmarshalRoutingState unpacks a peer RoutingState record from a serialized protobuf representation. // UnmarshalSignedRoutingState accepts a serialized SignedEnvelope message containing
func UnmarshalRoutingState(serialized []byte) (*RoutingState, error) { // a RoutingStateRecord protobuf and returns a SignedRoutingState record.
msg := pb.RoutingStateRecord{} // Fails if the signature is invalid, if the envelope has an unexpected payload type,
err := proto.Unmarshal(serialized, &msg) // if deserialization of the envelope or its inner payload fails.
func UnmarshalSignedRoutingState(envelopeBytes []byte) (*SignedRoutingState, error) {
envelope, err := crypto.OpenEnvelope(envelopeBytes, StateEnvelopeDomain)
if err != nil {
return nil, err
}
return SignedRoutingStateFromEnvelope(envelope)
}
// SignedRoutingStateFromEnvelope accepts a SignedEnvelope struct containing
// a RoutingStateRecord protobuf and returns a SignedRoutingState record.
// Fails if the signature is invalid, if the envelope has an unexpected payload type,
// or if deserialization of the envelope payload fails.
func SignedRoutingStateFromEnvelope(envelope *crypto.SignedEnvelope) (*SignedRoutingState, error) {
if bytes.Compare(envelope.PayloadType(), StateEnvelopePayloadType) != 0 {
return nil, errors.New("unexpected envelope payload type")
}
var msg pb.RoutingStateRecord
err := proto.Unmarshal(envelope.Payload(), &msg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -73,107 +94,79 @@ func UnmarshalRoutingState(serialized []byte) (*RoutingState, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &RoutingState{ if !id.MatchesPublicKey(envelope.PublicKey()) {
PeerID: id, return nil, errors.New("peer id in routing state record does not match signing key")
Seq: msg.Seq, }
Addresses: addrsFromProtobuf(msg.Addresses), return &SignedRoutingState{
peerID: id,
seq: msg.Seq,
addresses: addrsFromProtobuf(msg.Addresses),
envelope: envelope,
}, nil }, nil
} }
// RoutingStateFromEnvelope unwraps a peer RoutingState record from a serialized SignedEnvelope. // Marshal returns a byte slice containing the SignedRoutingState as a serialized SignedEnvelope
// This method will fail if the signature is invalid, or if the record // protobuf message.
// belongs to a peer other than the one that signed the envelope. func (s *SignedRoutingState) Marshal() ([]byte, error) {
func RoutingStateFromEnvelope(envelopeBytes []byte) (*RoutingState, error) { return s.envelope.Marshal()
envelope, err := crypto.OpenEnvelope(envelopeBytes, StateEnvelopeDomain)
if err != nil {
return nil, err
}
if bytes.Compare(envelope.PayloadType(), StateEnvelopePayloadType) != 0 {
return nil, errors.New("unexpected envelope payload type")
}
state, err := UnmarshalRoutingState(envelope.Payload())
if err != nil {
return nil, err
}
if !state.PeerID.MatchesPublicKey(envelope.PublicKey()) {
return nil, errors.New("peer id in routing state record does not match signing key")
}
return state, nil
} }
// ToSignedEnvelope wraps a Marshal'd RoutingState record in a SignedEnvelope using the // PeerID is the ID of the peer this record pertains to.
// given private signing key. func (s *SignedRoutingState) PeerID() peer.ID {
func (s *RoutingState) ToSignedEnvelope(key crypto.PrivKey) (*crypto.SignedEnvelope, error) { return s.peerID
payload, err := s.Marshal()
if err != nil {
return nil, err
}
return crypto.MakeEnvelope(key, StateEnvelopeDomain, StateEnvelopePayloadType, payload)
} }
// Marshal serializes a RoutingState record to protobuf and returns its byte representation. // Seq is an increment-only sequence counter used to order RoutingState records in time.
func (s *RoutingState) Marshal() ([]byte, error) { func (s *SignedRoutingState) Seq() uint64 {
id, err := s.PeerID.MarshalBinary() return s.seq
if err != nil {
return nil, err
}
msg := pb.RoutingStateRecord{
PeerId: id,
Seq: s.Seq,
Addresses: addrsToProtobuf(s.Addresses),
}
return proto.Marshal(&msg)
} }
// Multiaddrs returns the addresses from a RoutingState record without any metadata annotations. // Multiaddrs contains the public addresses of the peer this record pertains to.
func (s *RoutingState) Multiaddrs() []ma.Multiaddr { func (s *SignedRoutingState) Multiaddrs() []ma.Multiaddr {
out := make([]ma.Multiaddr, len(s.Addresses)) return s.addresses
for i, addr := range s.Addresses {
out[i] = addr.Multiaddr
}
return out
} }
func (s *RoutingState) Equal(other *RoutingState) bool { // Equal returns true if the other SignedRoutingState is identical to this one.
if s.Seq != other.Seq { func (s *SignedRoutingState) Equal(other *SignedRoutingState) bool {
if other == nil {
return false return false
} }
if s.PeerID != other.PeerID { if s.seq != other.seq {
return false return false
} }
if len(s.Addresses) != len(other.Addresses) { if s.peerID != other.peerID {
return false return false
} }
for i, _ := range s.Addresses { if len(s.addresses) != len(other.addresses) {
if !s.Addresses[i].Equal(other.Addresses[i]) { return false
}
for i, _ := range s.addresses {
if !s.addresses[i].Equal(other.addresses[i]) {
return false return false
} }
} }
return true return s.envelope.Equal(other.envelope)
}
func (a *AnnotatedAddr) Equal(other *AnnotatedAddr) bool {
return a.Multiaddr.Equal(other.Multiaddr)
} }
// statelessSeqNo is a helper to generate a timestamp-based sequence number.
func statelessSeqNo() uint64 { func statelessSeqNo() uint64 {
return uint64(time.Now().UnixNano()) return uint64(time.Now().UnixNano())
} }
func addrsFromProtobuf(addrs []*pb.RoutingStateRecord_AddressInfo) []*AnnotatedAddr { func addrsFromProtobuf(addrs []*pb.RoutingStateRecord_AddressInfo) []ma.Multiaddr {
out := make([]*AnnotatedAddr, 0) var out []ma.Multiaddr
for _, addr := range addrs { for _, addr := range addrs {
a, err := ma.NewMultiaddrBytes(addr.Multiaddr) a, err := ma.NewMultiaddrBytes(addr.Multiaddr)
if err != nil { if err != nil {
continue continue
} }
out = append(out, &AnnotatedAddr{Multiaddr: a}) out = append(out, a)
} }
return out return out
} }
func addrsToProtobuf(addrs []*AnnotatedAddr) []*pb.RoutingStateRecord_AddressInfo { func addrsToProtobuf(addrs []ma.Multiaddr) []*pb.RoutingStateRecord_AddressInfo {
out := make([]*pb.RoutingStateRecord_AddressInfo, 0) var out []*pb.RoutingStateRecord_AddressInfo
for _, addr := range addrs { for _, addr := range addrs {
out = append(out, &pb.RoutingStateRecord_AddressInfo{Multiaddr: addr.Bytes()}) out = append(out, &pb.RoutingStateRecord_AddressInfo{Multiaddr: addr.Bytes()})
} }

View File

@ -2,60 +2,29 @@ package routing
import ( import (
"github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/test" "github.com/libp2p/go-libp2p-core/test"
"testing" "testing"
) )
func TestRoutingStateFromAddrInfo(t *testing.T) { func TestSignedRoutingStateFromEnvelope(t *testing.T) {
id, _ := test.RandPeerID() priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)
addrs := test.GenerateTestAddrs(10)
info := peer.AddrInfo{
ID: id,
Addrs: addrs,
}
state := RoutingStateFromAddrInfo(&info)
if state.PeerID != info.ID {
t.Fatalf("expected routing state to have peer id %s, got %s", id.Pretty(), state.PeerID.Pretty())
}
test.AssertAddressesEqual(t, addrs, state.Multiaddrs())
}
func TestRoutingStateFromEnvelope(t *testing.T) {
priv, pub, err := test.RandTestKeyPair(crypto.Ed25519, 256)
test.AssertNilError(t, err)
id, err := peer.IDFromPublicKey(pub)
test.AssertNilError(t, err) test.AssertNilError(t, err)
addrs := test.GenerateTestAddrs(10) addrs := test.GenerateTestAddrs(10)
state := RoutingStateWithMultiaddrs(id, addrs) state, err := MakeSignedRoutingState(priv, addrs)
test.AssertNilError(t, err)
t.Run("can unwrap a RoutingState from a serialized envelope", func(t *testing.T) { t.Run("is unaltered after round-trip serde", func(t *testing.T) {
env, err := state.ToSignedEnvelope(priv) envBytes, err := state.Marshal()
test.AssertNilError(t, err) test.AssertNilError(t, err)
envBytes, err := env.Marshal() state2, err := UnmarshalSignedRoutingState(envBytes)
test.AssertNilError(t, err) test.AssertNilError(t, err)
state2, err := RoutingStateFromEnvelope(envBytes)
if !state.Equal(state2) { if !state.Equal(state2) {
t.Error("expected routing state to be unaltered after wrapping in signed envelope") t.Error("expected routing state to be unaltered after round-trip serde")
} }
}) })
t.Run("unwrapping from signed envelope fails if peer id does not match signing key", func(t *testing.T) {
priv2, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)
test.AssertNilError(t, err)
env, err := state.ToSignedEnvelope(priv2)
test.AssertNilError(t, err)
envBytes, err := env.Marshal()
test.AssertNilError(t, err)
_, err = RoutingStateFromEnvelope(envBytes)
test.ExpectError(t, err, "unwrapping RoutingState from envelope should fail if peer id does not match key used to sign envelope")
})
t.Run("unwrapping from signed envelope fails if envelope has wrong domain string", func(t *testing.T) { t.Run("unwrapping from signed envelope fails if envelope has wrong domain string", func(t *testing.T) {
stateBytes, err := state.Marshal() stateBytes, err := state.Marshal()
test.AssertNilError(t, err) test.AssertNilError(t, err)
@ -63,7 +32,7 @@ func TestRoutingStateFromEnvelope(t *testing.T) {
env, err := crypto.MakeEnvelope(priv, "wrong-domain", StateEnvelopePayloadType, stateBytes) env, err := crypto.MakeEnvelope(priv, "wrong-domain", StateEnvelopePayloadType, stateBytes)
test.AssertNilError(t, err) test.AssertNilError(t, err)
envBytes, err := env.Marshal() envBytes, err := env.Marshal()
_, err = RoutingStateFromEnvelope(envBytes) _, err = UnmarshalSignedRoutingState(envBytes)
test.ExpectError(t, err, "unwrapping RoutingState from envelope should fail if envelope was created with wrong domain string") test.ExpectError(t, err, "unwrapping RoutingState from envelope should fail if envelope was created with wrong domain string")
}) })
@ -74,7 +43,7 @@ func TestRoutingStateFromEnvelope(t *testing.T) {
env, err := crypto.MakeEnvelope(priv, StateEnvelopeDomain, payloadType, stateBytes) env, err := crypto.MakeEnvelope(priv, StateEnvelopeDomain, payloadType, stateBytes)
test.AssertNilError(t, err) test.AssertNilError(t, err)
envBytes, err := env.Marshal() envBytes, err := env.Marshal()
_, err = RoutingStateFromEnvelope(envBytes) _, err = UnmarshalSignedRoutingState(envBytes)
test.ExpectError(t, err, "unwrapping RoutingState from envelope should fail if envelope was created with wrong payload type") test.ExpectError(t, err, "unwrapping RoutingState from envelope should fail if envelope was created with wrong payload type")
}) })
} }