p2p/enr: improve API and tests

API changes:

- Load no longer returns (bool, error), just error. Missing keys
  can be detected using IsNotFound.
- Load errors contain the key that the caller tried to load.
- NodeAddr doesn't return error anymore. I expect we'll be using this
  method often and it will never return an error for records decoded
  from RLP. Maybe it should panic if "secp256k1" doesn't exist, not sure
  about that yet.
- The Signed method can be used to check for a signature.
This commit is contained in:
Felix Lange 2017-12-08 11:16:07 +01:00
parent 67301a1c23
commit f9de82bdb0
3 changed files with 221 additions and 298 deletions

View file

@ -14,7 +14,17 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package enr implements the Ethereum Node Record as per https://github.com/ethereum/EIPs/pull/778
// 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 keys in a record, use the Key
// interface.
//
// Records must signed before transmitting them to another node. Decoding a record verifies
// its signature. When creating a record, set the keys you want, then call Sign to add the
// signature. Modifying a record invalidates the signature.
//
// Package enr supports the "secp256k1-keccak" identity scheme.
package enr
import (
@ -35,6 +45,8 @@ import (
const SizeLimit = 300 // maximum encoded size of a node record in bytes
const ID_SECP256k1_KECCAK = ID("secp256k1-keccak") // the default identity scheme
var (
errNoID = errors.New("unknown or unspecified identity scheme")
errInvalidSigsize = errors.New("invalid signature size")
@ -43,15 +55,16 @@ var (
errDuplicateKey = errors.New("record contains duplicate key")
errIncompletePair = errors.New("record contains incomplete k/v pair")
errTooBig = fmt.Errorf("record bigger than %d bytes", SizeLimit)
errEncodeUnsigned = errors.New("can't encode unsigned record")
errNotFound = errors.New("no such key in record")
)
// Key is implemented by known node record key types.
//
// To define a new key that is to be included in a node record,
// create a Go type that satisfies this interface. The type should
// also implement rlp.Decoder if additional checks are needed on the value.
type Key interface {
ENRKey() string
// Record represents a node record. The zero value is an empty record.
type Record struct {
seq uint32 // sequence number
signature []byte // the signature
raw []byte // RLP encoded record
pairs []pair // sorted list of all key/value pairs
}
// pair is a key/value pair in a record.
@ -60,36 +73,38 @@ type pair struct {
v rlp.RawValue
}
// Record represents Ethereum Node Record
type Record struct {
seq uint32 // sequence number
signature []byte // record's signature
raw []byte // RLP encoded record
pairs []pair // sorted list of all key/value pairs
// Signed reports whether the record has a valid signature.
func (r *Record) Signed() bool {
return r.signature != nil
}
// Seq return record's sequence number
func (r Record) Seq() uint32 {
// Seq returns the sequence number.
func (r *Record) Seq() uint32 {
return r.seq
}
// SetSeq update record's sequence number. Nodes should increase the number whenever the record changes.
// SetSeq updates the record sequence number. This invalidates any signature on the record.
// Calling SetSeq is usually not required because signing the redord increments the
// sequence number.
func (r *Record) SetSeq(s uint32) {
r.signature = nil
r.seq = s
}
// Load is loading a key/value pair based on provided key from the record.
// It returns false if such key cannot be found.
// It returns an error if there is a problem with RLP decoding of the pair.
func (r *Record) Load(k Key) (bool, error) {
// Load retrieves the valud of a key/value pair. The given Key must be a pointer and will
// be set to the value of the key in the record.
//
// Errors returned by Load are wrapped in KeyError. You can distinguish decoding errors
// from missing keys using the IsNotFound function.
func (r *Record) Load(k Key) error {
i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= k.ENRKey() })
if i < len(r.pairs) && r.pairs[i].k == k.ENRKey() {
return true, rlp.DecodeBytes(r.pairs[i].v, k)
if err := rlp.DecodeBytes(r.pairs[i].v, k); err != nil {
return &KeyError{Key: k.ENRKey(), Err: err}
}
return nil
}
return false, errors.New("record does not exist")
return &KeyError{Key: k.ENRKey(), Err: errNotFound}
}
// Set adds or updates the given key in the record.
@ -120,17 +135,17 @@ func (r *Record) Set(k Key) {
r.pairs = append(r.pairs, pair{k.ENRKey(), blob})
}
// EncodeRLP implements rlp.Encoder.
// Sign must be called prior to calling rlp.Encode
// EncodeRLP implements rlp.Encoder. Encoding fails if
// the record is unsigned.
func (r Record) EncodeRLP(w io.Writer) error {
if r.signature == nil {
return errors.New("record is not signed")
if !r.Signed() {
return errEncodeUnsigned
}
_, err := w.Write(r.raw)
return err
}
// DecodeRLP implements rlp.Decoder.
// DecodeRLP implements rlp.Decoder. Decoding verifies the signature.
func (r *Record) DecodeRLP(s *rlp.Stream) error {
raw, err := s.Raw()
if err != nil {
@ -191,29 +206,24 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error {
return nil
}
// NodeAddr returns node's address - keccak256 hash of the public key.
func (r *Record) NodeAddr() ([]byte, error) {
// NodeAddr returns the node address. The return value will be nil if the record is
// unsigned.
func (r *Record) NodeAddr() []byte {
var secp256k1 Secp256k1
_, err := r.Load(&secp256k1)
if err != nil {
return nil, err
if r.Load(&secp256k1) != nil {
return nil
}
pk := btcec.PublicKey(secp256k1)
digest := crypto.Keccak256Hash(pk.SerializeCompressed())
return digest.Bytes(), nil
return crypto.Keccak256(pk.SerializeCompressed())
}
// Sign signs the record with the provided private key.
// It updates record's identity scheme and public key.
// It returns an error if signed record is bigger than SizeLimit bytes.
// Sign signs the record with the given private key. It updates the record's identity
// scheme, public key and increments the sequence number. Sign returns an error if the
// encoded record is larger than the size limit.
func (r *Record) Sign(privkey *ecdsa.PrivateKey) error {
pk := (*btcec.PublicKey)(&privkey.PublicKey)
r.seq = r.seq + 1
r.Set(ID(ID_SECP256k1_KECCAK))
r.Set(ID_SECP256k1_KECCAK)
r.Set(Secp256k1(*pk))
return r.signAndEncode(privkey)
}
@ -246,11 +256,9 @@ func (r *Record) signAndEncode(privkey *ecdsa.PrivateKey) error {
if err != nil {
return err
}
if len(r.raw) > SizeLimit {
return errTooBig
}
return nil
}
@ -258,15 +266,13 @@ func (r *Record) verifySignature() error {
// Get identity scheme, public key, signature.
var id ID
var secp256k1 Secp256k1
if _, err := r.Load(&id); err != nil {
if err := r.Load(&id); err != nil {
return err
} else if id != ID_SECP256k1_KECCAK {
return errNoID
}
if ok, err := r.Load(&secp256k1); err != nil {
if err := r.Load(&secp256k1); err != nil {
return err
} else if !ok {
return fmt.Errorf("can't verify signature: missing %q key", secp256k1.ENRKey())
}
sig, err := parseCompactSignature(r.signature)
if err != nil {

View file

@ -19,60 +19,108 @@ package enr
import (
"bytes"
"encoding/hex"
"fmt"
"math/rand"
"net"
"reflect"
"testing"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
privkeyHex = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
var (
privkeyHex = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
privkey, _ = crypto.HexToECDSA(privkeyHex)
pubkeyBytes, _ = hex.DecodeString("03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
pubkey, _ = btcec.ParsePubKey(pubkeyBytes, btcec.S256())
)
var rnd *rand.Rand
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
func init() {
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
}
// TestGetSetID tests encoding/decoding and setting/getting of the enr.ID type
// TestGetSetID tests encoding/decoding and setting/getting of the ID key.
func TestGetSetID(t *testing.T) {
id := ID("someid")
var r Record
r.Set(id)
var id2 ID
_, err := r.Load(&id2)
if err != nil {
t.Fatal(err)
}
if id != id2 {
t.Fatalf("got %#v, expected %#v", id2, id)
}
require.NoError(t, r.Load(&id2))
assert.Equal(t, id, id2)
}
// TestGetSetIP4 tests encoding/decoding and setting/getting of the enr.IP4 type
// TestGetSetIP4 tests encoding/decoding and setting/getting of the IP4 key.
func TestGetSetIP4(t *testing.T) {
ip := IP4(net.IP{192, 168, 0, 3})
ip := IP4{192, 168, 0, 3}
var r Record
r.Set(ip)
var ip2 IP4
require.NoError(t, r.Load(&ip2))
assert.Equal(t, ip, ip2)
}
_, err := r.Load(&ip2)
if err != nil {
// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key.
func TestGetSetIP6(t *testing.T) {
ip := IP6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
var r Record
r.Set(ip)
var ip2 IP6
require.NoError(t, r.Load(&ip2))
assert.Equal(t, ip, ip2)
}
// TestGetSetDiscPort tests encoding/decoding and setting/getting of the DiscPort key.
func TestGetSetDiscPort(t *testing.T) {
port := DiscPort(30309)
var r Record
r.Set(port)
var port2 DiscPort
require.NoError(t, r.Load(&port2))
assert.Equal(t, port, port2)
}
// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the Secp256k1 key.
func TestGetSetSecp256k1(t *testing.T) {
var r Record
if err := r.Sign(privkey); err != nil {
t.Fatal(err)
}
if bytes.Compare(ip, ip2) != 0 {
t.Fatalf("got %#v, expected %#v", ip2, ip)
var pk Secp256k1
require.NoError(t, r.Load(&pk))
assert.EqualValues(t, pubkey, &pk)
}
func TestLoadErrors(t *testing.T) {
var r Record
ip4 := IP4{127, 0, 0, 1}
r.Set(ip4)
// Check error for missing keys.
var ip6 IP6
err := r.Load(&ip6)
if !IsNotFound(err) {
t.Error("IsNotFound should return true for missing key")
}
assert.Equal(t, &KeyError{Key: ip6.ENRKey(), Err: errNotFound}, err)
// Check error for invalid keys.
var list []uint
err = r.Load(WithKey(IP4{}.ENRKey(), &list))
kerr, ok := err.(*KeyError)
if !ok {
t.Fatal("expected KeyError, got %T", err)
}
assert.Equal(t, kerr.Key, ip4.ENRKey())
assert.Error(t, kerr.Err)
if IsNotFound(err) {
t.Error("IsNotFound should return false for decoding errors")
}
}
@ -107,103 +155,36 @@ func TestSortedGetAndSet(t *testing.T) {
for i, w := range tt.want {
// set got's key from r.pair[i], so that we preserve order of pairs
got := pair{k: r.pairs[i].k}
if ok, err := r.Load(WithKey(w.k, &got.v)); !ok || err != nil {
t.Fatal(err)
}
if got != w {
t.Fatalf("expected %#v, got %#v", w, got)
}
assert.NoError(t, r.Load(WithKey(w.k, &got.v)))
assert.Equal(t, w, got)
}
}
}
// TestGetSetIP6 tests encoding/decoding and setting/getting of the enr.IP6 type
func TestGetSetIP6(t *testing.T) {
ip := IP6(net.IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68})
var r Record
r.Set(ip)
var ip2 IP6
_, err := r.Load(&ip2)
if err != nil {
t.Fatal(err)
}
if bytes.Compare(ip, ip2) != 0 {
t.Fatalf("got %#v, expected %#v", ip2, ip)
}
}
// TestGetSetDiscPort tests encoding/decoding and setting/getting of the enr.DiscPort type
func TestGetSetDiscPort(t *testing.T) {
port := DiscPort(30309)
var r Record
r.Set(port)
var port2 DiscPort
_, err := r.Load(&port2)
if err != nil {
t.Fatal(err)
}
if port != port2 {
t.Fatalf("got %#v, expected %#v", port2, port)
}
}
// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the enr.Secp256k1 type
func TestGetSetSecp256k1(t *testing.T) {
privkey, err := crypto.HexToECDSA(privkeyHex)
if err != nil {
t.Fatal(err)
}
var r Record
err = r.Sign(privkey)
if err != nil {
t.Fatal(err)
}
var pk Secp256k1
_, err = r.Load(&pk)
if err != nil {
t.Fatal(err)
}
got := (*btcec.PublicKey)(&pk).SerializeCompressed()
expected := (*btcec.PublicKey)(&privkey.PublicKey).SerializeCompressed()
if bytes.Compare(got, expected) != 0 {
t.Fatalf("got %#v, expected %#v", got, expected)
}
}
// TestDirty tests record signature removal on setting of new key/value pair in record.
func TestDirty(t *testing.T) {
privkey, err := crypto.HexToECDSA(privkeyHex)
if err != nil {
t.Fatal(err)
}
var r Record
err = r.Sign(privkey)
if err != nil {
t.Fatal(err)
if r.Signed() {
t.Error("Signed returned true for zero record")
}
if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
t.Errorf("expected errEncodeUnsigned, got %#v", err)
}
if _, err := rlp.EncodeToBytes(r); err != nil {
t.Fatal(err)
require.NoError(t, r.Sign(privkey))
if !r.Signed() {
t.Error("Signed return false for signed record")
}
_, err := rlp.EncodeToBytes(r)
assert.NoError(t, err)
r.SetSeq(3)
if _, err := rlp.EncodeToBytes(r); err == nil {
t.Fatal("expected err, got nil")
if r.Signed() {
t.Error("Signed returned true for modified record")
}
if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
t.Errorf("expected errEncodeUnsigned, got %#v")
}
}
@ -218,88 +199,45 @@ func TestGetSetOverwrite(t *testing.T) {
r.Set(ip2)
var ip3 IP4
_, err := r.Load(&ip3)
if err != nil {
t.Fatal(err)
}
if bytes.Compare(ip2, ip3) != 0 {
t.Fatalf("got %#v, expected %#v", ip2, ip3)
}
require.NoError(t, r.Load(&ip3))
assert.Equal(t, ip2, ip3)
}
// TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record.
func TestSignEncodeAndDecode(t *testing.T) {
privkey, err := crypto.HexToECDSA(privkeyHex)
if err != nil {
t.Fatal(err)
}
var r Record
port := DiscPort(30303)
r.Set(port)
ipv4 := IP4(net.ParseIP("127.0.0.1"))
r.Set(ipv4)
err = r.Sign(privkey)
if err != nil {
t.Fatal(err)
}
r.Set(DiscPort(30303))
r.Set(IP4(net.ParseIP("127.0.0.1")))
require.NoError(t, r.Sign(privkey))
blob, err := rlp.EncodeToBytes(r)
if err != nil {
t.Fatal(err)
}
var r2 Record
err = rlp.DecodeBytes(blob, &r2)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(r, r2) {
t.Errorf("records not deep equal ; got\n%#v, expected\n%#v", r2, r)
}
require.NoError(t, rlp.DecodeBytes(blob, &r2))
assert.Equal(t, r, r2)
blob2, err := rlp.EncodeToBytes(r2)
if err != nil {
t.Fatal(err)
}
if bytes.Compare(blob, blob2) != 0 {
t.Errorf("serialised records not equal ; got\n%#v, expected\n%#v", blob2, blob)
}
assert.Equal(t, blob, blob2)
}
// TestNodeAddress tests that record returns correct node address - keccak256 hash of the public key.
func TestNodeAddress(t *testing.T) {
privkey, err := crypto.HexToECDSA(privkeyHex)
if err != nil {
t.Fatal(err)
}
func TestNodeAddr(t *testing.T) {
var r Record
err = r.Sign(privkey)
if err != nil {
t.Fatal(err)
}
addr, err := r.NodeAddr()
if err != nil {
t.Fatal(err)
if addr := r.NodeAddr(); addr != nil {
t.Errorf("wrong address on empty record: got %v, want %v", addr, nil)
}
require.NoError(t, r.Sign(privkey))
expected := "caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726"
got := hex.EncodeToString(addr)
if got != expected {
t.Errorf("got\n%#v, expected\n%#v", got, expected)
}
assert.Equal(t, expected, hex.EncodeToString(r.NodeAddr()))
}
// TestPythonInterop tests that Go implementation can successfully RLP decode a record produced by Python implementation.
// TestPythonInterop checks that we can decode and verify a record produced by the Python
// implementation.
func TestPythonInterop(t *testing.T) {
enc, _ := hex.DecodeString("f896b840638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454018664697363763582765f82696490736563703235366b312d6b656363616b83697034847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
var r Record
@ -316,95 +254,62 @@ func TestPythonInterop(t *testing.T) {
if r.Seq() != wantSeq {
t.Errorf("wrong seq: got %d, want %d", r.Seq(), wantSeq)
}
if addr, _ := r.NodeAddr(); !bytes.Equal(addr, wantAddr) {
if addr := r.NodeAddr(); !bytes.Equal(addr, wantAddr) {
t.Errorf("wrong addr: got %x, want %x", addr, wantAddr)
}
want := map[Key]interface{}{new(IP4): &wantIP, new(DiscPort): &wantDiscport}
for k, v := range want {
if _, err := r.Load(k); err != nil {
t.Errorf("can't load %q: %v", k.ENRKey(), err)
} else if !reflect.DeepEqual(k, v) {
t.Errorf("wrong %q: got %v, want %v", k.ENRKey(), k, v)
desc := fmt.Sprintf("loading key %q", k.ENRKey())
if assert.NoError(t, r.Load(k), desc) {
assert.Equal(t, k, v, desc)
}
}
}
// TestRecordTooBig tests that records bigger than SizeLimit bytes cannot be signed.
func TestRecordTooBig(t *testing.T) {
privkey, err := crypto.HexToECDSA(privkeyHex)
if err != nil {
t.Fatal(err)
}
var r Record
key := randomString(10)
// set a big value for random key, expect error
r.Set(WithKey(key, randomString(300)))
err = r.Sign(privkey)
if err != errTooBig {
if err := r.Sign(privkey); err != errTooBig {
t.Fatalf("expected to get errTooBig, got %#v", err)
}
// set an acceptable value for random key, expect no error
r.Set(WithKey(key, randomString(100)))
err = r.Sign(privkey)
if err != nil {
t.Fatal(err)
}
require.NoError(t, r.Sign(privkey))
}
// TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs.
func TestSignEncodeAndDecodeRandom(t *testing.T) {
privkey, err := crypto.HexToECDSA(privkeyHex)
if err != nil {
t.Fatal(err)
}
var r Record
// random key/value pairs for testing
pairs := map[string]uint32{}
for i := 0; i < 10; i++ {
key := randomString(7)
value := rnd.Uint32()
pair := WithKey(key, &value)
r.Set(pair)
pairs[key] = value
r.Set(WithKey(key, &value))
}
if r.Sign(privkey); err != nil {
t.Fatal(err)
}
_, err = rlp.EncodeToBytes(r)
if err != nil {
t.Fatal(err)
}
require.NoError(t, r.Sign(privkey))
_, err := rlp.EncodeToBytes(r)
require.NoError(t, err)
for k, v := range pairs {
desc := fmt.Sprintf("key %q", k)
var got uint32
buf := WithKey(k, &got)
if ok, err := r.Load(buf); !ok || err != nil {
t.Fatal(ok, err)
}
if got != v {
t.Fatalf("got %#v, expected %#v", got, v)
}
require.NoError(t, r.Load(buf), desc)
require.Equal(t, v, got, desc)
}
}
func randomString(strlen int) string {
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
result := make([]byte, strlen)
for i := range result {
result[i] = chars[rnd.Intn(len(chars))]
}
return string(result)
b := make([]byte, strlen)
rnd.Read(b)
return string(b)
}

View file

@ -26,14 +26,21 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)
// Key is implemented by known node record key types.
//
// To define a new key that is to be included in a node record,
// create a Go type that satisfies this interface. The type should
// also implement rlp.Decoder if additional checks are needed on the value.
type Key interface {
ENRKey() string
}
type generic struct {
key string
value interface{}
}
func (g generic) ENRKey() string {
return g.key
}
func (g generic) ENRKey() string { return g.key }
func (g generic) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, g.value)
@ -43,37 +50,27 @@ func (g *generic) DecodeRLP(s *rlp.Stream) error {
return s.Decode(g.value)
}
// WithKey returns a new Key that can be set in a Record.
// v must implement the rlp.Encoder and rlp.Decoder interface.
// WithKey wraps any value with a key name. It can be used to set and load arbitrary values
// in a record. The value v must be supported by rlp. To use WithKey with Load, the value
// must be a pointer.
func WithKey(k string, v interface{}) Key {
return &generic{key: k, value: v}
}
// DiscPort represents an UDP port for discovery v5.
// DiscPort is the "discv5" key, which holds the UDP port for discovery v5.
type DiscPort uint16
// ENRKey returns the node record key for an UDP port for discovery.
func (DiscPort) ENRKey() string {
return "discv5"
}
func (v DiscPort) ENRKey() string { return "discv5" }
const ID_SECP256k1_KECCAK = "secp256k1-keccak" // identity scheme identifier
// ID is the name of the identity scheme, e.g. "secp256k1-keccak".
// ID is the "id" key, which holds the name of the identity scheme.
type ID string
// ENRKey returns the node record key for its identity scheme.
func (ID) ENRKey() string {
return "id"
}
func (v ID) ENRKey() string { return "id" }
// IP4 represents an 4-byte IPv4 address in a node record.
// IP4 is the "ip4" key, which holds a 4-byte IPv4 address.
type IP4 net.IP
// ENRKey returns the node record key for an IPv4 address.
func (IP4) ENRKey() string {
return "ip4"
}
func (v IP4) ENRKey() string { return "ip4" }
// EncodeRLP implements rlp.Encoder.
func (v IP4) EncodeRLP(w io.Writer) error {
@ -95,13 +92,10 @@ func (v *IP4) DecodeRLP(s *rlp.Stream) error {
return nil
}
// IP6 represents an 16-byte IPv6 address in a node record.
// IP6 is the "ip6" key, which holds a 16-byte IPv6 address.
type IP6 net.IP
// ENRKey returns the node record key for an IPv6 address.
func (IP6) ENRKey() string {
return "ip6"
}
func (v IP6) ENRKey() string { return "ip6" }
// EncodeRLP implements rlp.Encoder.
func (v IP6) EncodeRLP(w io.Writer) error {
@ -120,13 +114,10 @@ func (v *IP6) DecodeRLP(s *rlp.Stream) error {
return nil
}
// Secp256k1 is compressed secp256k1 public key.
// Secp256k1 is the "secp256k1" key, which holds a public key.
type Secp256k1 ecdsa.PublicKey
// ENRKey returns the node record key for the secp256k1 public key.
func (Secp256k1) ENRKey() string {
return "secp256k1"
}
func (v Secp256k1) ENRKey() string { return "secp256k1" }
// EncodeRLP implements rlp.Encoder.
func (v Secp256k1) EncodeRLP(w io.Writer) error {
@ -151,3 +142,24 @@ func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error {
return nil
}
// KeyError is an error related to a key.
type KeyError struct {
Key string
Err error
}
// Error implements error.
func (err *KeyError) Error() string {
if err.Err == errNotFound {
return fmt.Sprintf("missing ENR key %q", err.Key)
}
return fmt.Sprintf("ENR key %q: %v", err.Key, err.Err)
}
// IsNotFound reports whether the given error means that a key/value pair is
// missing from a record.
func IsNotFound(err error) bool {
kerr, ok := err.(*KeyError)
return ok && kerr.Err == errNotFound
}