aboutsummaryrefslogtreecommitdiffstats
path: root/p2p/enr/enr.go
diff options
context:
space:
mode:
Diffstat (limited to 'p2p/enr/enr.go')
-rw-r--r--p2p/enr/enr.go168
1 files changed, 95 insertions, 73 deletions
diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go
index 48683471d..251caf458 100644
--- a/p2p/enr/enr.go
+++ b/p2p/enr/enr.go
@@ -15,14 +15,20 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package enr implements Ethereum Node Records as defined in EIP-778. A node record holds
-// arbitrary information about a node on the peer-to-peer network.
-//
-// Records contain named keys. To store and retrieve key/values in a record, use the Entry
+// arbitrary information about a node on the peer-to-peer network. Node information is
+// stored in key/value pairs. To store and retrieve key/values in a record, use the Entry
// interface.
//
-// Records must be signed before transmitting them to another node. Decoding a record verifies
-// its signature. When creating a record, set the entries you want, then call Sign to add the
-// signature. Modifying a record invalidates the signature.
+// Signature Handling
+//
+// Records must be signed before transmitting them to another node.
+//
+// Decoding a record doesn't check its signature. Code working with records from an
+// untrusted source must always verify two things: that the record uses an identity scheme
+// deemed secure, and that the signature is valid according to the declared scheme.
+//
+// When creating a record, set the entries you want and use a signing function provided by
+// the identity scheme to add the signature. Modifying a record invalidates the signature.
//
// Package enr supports the "secp256k1-keccak" identity scheme.
package enr
@@ -40,8 +46,7 @@ import (
const SizeLimit = 300 // maximum encoded size of a node record in bytes
var (
- errNoID = errors.New("unknown or unspecified identity scheme")
- errInvalidSig = errors.New("invalid signature")
+ ErrInvalidSig = errors.New("invalid signature on node record")
errNotSorted = errors.New("record key/value pairs are not sorted by key")
errDuplicateKey = errors.New("record contains duplicate key")
errIncompletePair = errors.New("record contains incomplete k/v pair")
@@ -50,6 +55,32 @@ var (
errNotFound = errors.New("no such key in record")
)
+// An IdentityScheme is capable of verifying record signatures and
+// deriving node addresses.
+type IdentityScheme interface {
+ Verify(r *Record, sig []byte) error
+ NodeAddr(r *Record) []byte
+}
+
+// SchemeMap is a registry of named identity schemes.
+type SchemeMap map[string]IdentityScheme
+
+func (m SchemeMap) Verify(r *Record, sig []byte) error {
+ s := m[r.IdentityScheme()]
+ if s == nil {
+ return ErrInvalidSig
+ }
+ return s.Verify(r, sig)
+}
+
+func (m SchemeMap) NodeAddr(r *Record) []byte {
+ s := m[r.IdentityScheme()]
+ if s == nil {
+ return nil
+ }
+ return s.NodeAddr(r)
+}
+
// Record represents a node record. The zero value is an empty record.
type Record struct {
seq uint64 // sequence number
@@ -64,11 +95,6 @@ type pair struct {
v rlp.RawValue
}
-// Signed reports whether the record has a valid signature.
-func (r *Record) Signed() bool {
- return r.signature != nil
-}
-
// Seq returns the sequence number.
func (r *Record) Seq() uint64 {
return r.seq
@@ -140,7 +166,7 @@ func (r *Record) invalidate() {
// EncodeRLP implements rlp.Encoder. Encoding fails if
// the record is unsigned.
func (r Record) EncodeRLP(w io.Writer) error {
- if !r.Signed() {
+ if r.signature == nil {
return errEncodeUnsigned
}
_, err := w.Write(r.raw)
@@ -149,25 +175,34 @@ func (r Record) EncodeRLP(w io.Writer) error {
// DecodeRLP implements rlp.Decoder. Decoding verifies the signature.
func (r *Record) DecodeRLP(s *rlp.Stream) error {
- raw, err := s.Raw()
+ dec, raw, err := decodeRecord(s)
if err != nil {
return err
}
+ *r = dec
+ r.raw = raw
+ return nil
+}
+
+func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) {
+ raw, err = s.Raw()
+ if err != nil {
+ return dec, raw, err
+ }
if len(raw) > SizeLimit {
- return errTooBig
+ return dec, raw, errTooBig
}
// Decode the RLP container.
- dec := Record{raw: raw}
s = rlp.NewStream(bytes.NewReader(raw), 0)
if _, err := s.List(); err != nil {
- return err
+ return dec, raw, err
}
if err = s.Decode(&dec.signature); err != nil {
- return err
+ return dec, raw, err
}
if err = s.Decode(&dec.seq); err != nil {
- return err
+ return dec, raw, err
}
// The rest of the record contains sorted k/v pairs.
var prevkey string
@@ -177,73 +212,68 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error {
if err == rlp.EOL {
break
}
- return err
+ return dec, raw, err
}
if err := s.Decode(&kv.v); err != nil {
if err == rlp.EOL {
- return errIncompletePair
+ return dec, raw, errIncompletePair
}
- return err
+ return dec, raw, err
}
if i > 0 {
if kv.k == prevkey {
- return errDuplicateKey
+ return dec, raw, errDuplicateKey
}
if kv.k < prevkey {
- return errNotSorted
+ return dec, raw, errNotSorted
}
}
dec.pairs = append(dec.pairs, kv)
prevkey = kv.k
}
- if err := s.ListEnd(); err != nil {
- return err
- }
+ return dec, raw, s.ListEnd()
+}
- _, scheme := dec.idScheme()
- if scheme == nil {
- return errNoID
- }
- if err := scheme.Verify(&dec, dec.signature); err != nil {
- return err
- }
- *r = dec
- return nil
+// IdentityScheme returns the name of the identity scheme in the record.
+func (r *Record) IdentityScheme() string {
+ var id ID
+ r.Load(&id)
+ return string(id)
}
-// NodeAddr returns the node address. The return value will be nil if the record is
-// unsigned or uses an unknown identity scheme.
-func (r *Record) NodeAddr() []byte {
- _, scheme := r.idScheme()
- if scheme == nil {
- return nil
- }
- return scheme.NodeAddr(r)
+// VerifySignature checks whether the record is signed using the given identity scheme.
+func (r *Record) VerifySignature(s IdentityScheme) error {
+ return s.Verify(r, r.signature)
}
// SetSig sets the record signature. It returns an error if the encoded record is larger
// than the size limit or if the signature is invalid according to the passed scheme.
-func (r *Record) SetSig(idscheme string, sig []byte) error {
- // Check that "id" is set and matches the given scheme. This panics because
- // inconsitencies here are always implementation bugs in the signing function calling
- // this method.
- id, s := r.idScheme()
- if s == nil {
- panic(errNoID)
- }
- if id != idscheme {
- panic(fmt.Errorf("identity scheme mismatch in Sign: record has %s, want %s", id, idscheme))
- }
-
- // Verify against the scheme.
- if err := s.Verify(r, sig); err != nil {
- return err
- }
- raw, err := r.encode(sig)
- if err != nil {
- return err
+//
+// You can also use SetSig to remove the signature explicitly by passing a nil scheme
+// and signature.
+//
+// SetSig panics when either the scheme or the signature (but not both) are nil.
+func (r *Record) SetSig(s IdentityScheme, sig []byte) error {
+ switch {
+ // Prevent storing invalid data.
+ case s == nil && sig != nil:
+ panic("enr: invalid call to SetSig with non-nil signature but nil scheme")
+ case s != nil && sig == nil:
+ panic("enr: invalid call to SetSig with nil signature but non-nil scheme")
+ // Verify if we have a scheme.
+ case s != nil:
+ if err := s.Verify(r, sig); err != nil {
+ return err
+ }
+ raw, err := r.encode(sig)
+ if err != nil {
+ return err
+ }
+ r.signature, r.raw = sig, raw
+ // Reset otherwise.
+ default:
+ r.signature, r.raw = nil, nil
}
- r.signature, r.raw = sig, raw
return nil
}
@@ -268,11 +298,3 @@ func (r *Record) encode(sig []byte) (raw []byte, err error) {
}
return raw, nil
}
-
-func (r *Record) idScheme() (string, IdentityScheme) {
- var id ID
- if err := r.Load(&id); err != nil {
- return "", nil
- }
- return string(id), FindIdentityScheme(string(id))
-}