p2p/enr: updates for discovery v4 compatibility (#16679)

This applies spec changes from ethereum/EIPs#1049 and adds support for
pluggable identity schemes.

Some care has been taken to make the "v4" scheme standalone. It uses
public APIs only and could be moved out of package enr at any time.

A couple of minor changes were needed to make identity schemes work:

- The sequence number is now updated in Set instead of when signing.
- Record is now copy-safe, i.e. calling Set on a shallow copy doesn't
  modify the record it was copied from.
pull/16753/head
Felix Lange 7 years ago committed by GitHub
parent f6bc65fc68
commit 6286c255f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 159
      p2p/enr/enr.go
  2. 66
      p2p/enr/enr_test.go
  3. 56
      p2p/enr/entries.go
  4. 114
      p2p/enr/idscheme.go
  5. 36
      p2p/enr/idscheme_test.go

@ -29,21 +29,16 @@ package enr
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"sort" "sort"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
const SizeLimit = 300 // maximum encoded size of a node record in bytes const SizeLimit = 300 // maximum encoded size of a node record in bytes
const ID_SECP256k1_KECCAK = ID("secp256k1-keccak") // the default identity scheme
var ( var (
errNoID = errors.New("unknown or unspecified identity scheme") errNoID = errors.New("unknown or unspecified identity scheme")
errInvalidSig = errors.New("invalid signature") errInvalidSig = errors.New("invalid signature")
@ -80,8 +75,8 @@ func (r *Record) Seq() uint64 {
} }
// SetSeq updates the record sequence number. This invalidates any signature on the record. // 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 // Calling SetSeq is usually not required because setting any key in a signed record
// sequence number. // increments the sequence number.
func (r *Record) SetSeq(s uint64) { func (r *Record) SetSeq(s uint64) {
r.signature = nil r.signature = nil
r.raw = nil r.raw = nil
@ -104,33 +99,42 @@ func (r *Record) Load(e Entry) error {
return &KeyError{Key: e.ENRKey(), Err: errNotFound} return &KeyError{Key: e.ENRKey(), Err: errNotFound}
} }
// Set adds or updates the given entry in the record. // Set adds or updates the given entry in the record. It panics if the value can't be
// It panics if the value can't be encoded. // encoded. If the record is signed, Set increments the sequence number and invalidates
// the sequence number.
func (r *Record) Set(e Entry) { func (r *Record) Set(e Entry) {
r.signature = nil
r.raw = nil
blob, err := rlp.EncodeToBytes(e) blob, err := rlp.EncodeToBytes(e)
if err != nil { if err != nil {
panic(fmt.Errorf("enr: can't encode %s: %v", e.ENRKey(), err)) panic(fmt.Errorf("enr: can't encode %s: %v", e.ENRKey(), err))
} }
r.invalidate()
i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= e.ENRKey() }) pairs := make([]pair, len(r.pairs))
copy(pairs, r.pairs)
if i < len(r.pairs) && r.pairs[i].k == e.ENRKey() { i := sort.Search(len(pairs), func(i int) bool { return pairs[i].k >= e.ENRKey() })
switch {
case i < len(pairs) && pairs[i].k == e.ENRKey():
// element is present at r.pairs[i] // element is present at r.pairs[i]
r.pairs[i].v = blob pairs[i].v = blob
return case i < len(r.pairs):
} else if i < len(r.pairs) {
// insert pair before i-th elem // insert pair before i-th elem
el := pair{e.ENRKey(), blob} el := pair{e.ENRKey(), blob}
r.pairs = append(r.pairs, pair{}) pairs = append(pairs, pair{})
copy(r.pairs[i+1:], r.pairs[i:]) copy(pairs[i+1:], pairs[i:])
r.pairs[i] = el pairs[i] = el
return default:
// element should be placed at the end of r.pairs
pairs = append(pairs, pair{e.ENRKey(), blob})
} }
r.pairs = pairs
}
// element should be placed at the end of r.pairs func (r *Record) invalidate() {
r.pairs = append(r.pairs, pair{e.ENRKey(), blob}) if r.signature == nil {
r.seq++
}
r.signature = nil
r.raw = nil
} }
// EncodeRLP implements rlp.Encoder. Encoding fails if // EncodeRLP implements rlp.Encoder. Encoding fails if
@ -196,39 +200,55 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error {
return err return err
} }
// Verify signature. _, scheme := dec.idScheme()
if err = dec.verifySignature(); err != nil { if scheme == nil {
return errNoID
}
if err := scheme.Verify(&dec, dec.signature); err != nil {
return err return err
} }
*r = dec *r = dec
return nil return nil
} }
type s256raw []byte
func (s256raw) ENRKey() string { return "secp256k1" }
// NodeAddr returns the node address. The return value will be nil if the record is // NodeAddr returns the node address. The return value will be nil if the record is
// unsigned. // unsigned or uses an unknown identity scheme.
func (r *Record) NodeAddr() []byte { func (r *Record) NodeAddr() []byte {
var entry s256raw _, scheme := r.idScheme()
if r.Load(&entry) != nil { if scheme == nil {
return nil return nil
} }
return crypto.Keccak256(entry) return scheme.NodeAddr(r)
} }
// Sign signs the record with the given private key. It updates the record's identity // SetSig sets the record signature. It returns an error if the encoded record is larger
// scheme, public key and increments the sequence number. Sign returns an error if the // than the size limit or if the signature is invalid according to the passed scheme.
// encoded record is larger than the size limit. func (r *Record) SetSig(idscheme string, sig []byte) error {
func (r *Record) Sign(privkey *ecdsa.PrivateKey) error { // Check that "id" is set and matches the given scheme. This panics because
r.seq = r.seq + 1 // inconsitencies here are always implementation bugs in the signing function calling
r.Set(ID_SECP256k1_KECCAK) // this method.
r.Set(Secp256k1(privkey.PublicKey)) id, s := r.idScheme()
return r.signAndEncode(privkey) 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
}
r.signature, r.raw = sig, raw
return nil
} }
func (r *Record) appendPairs(list []interface{}) []interface{} { // AppendElements appends the sequence number and entries to the given slice.
func (r *Record) AppendElements(list []interface{}) []interface{} {
list = append(list, r.seq) list = append(list, r.seq)
for _, p := range r.pairs { for _, p := range r.pairs {
list = append(list, p.k, p.v) list = append(list, p.k, p.v)
@ -236,54 +256,23 @@ func (r *Record) appendPairs(list []interface{}) []interface{} {
return list return list
} }
func (r *Record) signAndEncode(privkey *ecdsa.PrivateKey) error { func (r *Record) encode(sig []byte) (raw []byte, err error) {
// Put record elements into a flat list. Leave room for the signature. list := make([]interface{}, 1, 2*len(r.pairs)+1)
list := make([]interface{}, 1, len(r.pairs)*2+2) list[0] = sig
list = r.appendPairs(list) list = r.AppendElements(list)
if raw, err = rlp.EncodeToBytes(list); err != nil {
// Sign the tail of the list. return nil, err
h := sha3.NewKeccak256()
rlp.Encode(h, list[1:])
sig, err := crypto.Sign(h.Sum(nil), privkey)
if err != nil {
return err
}
sig = sig[:len(sig)-1] // remove v
// Put signature in front.
r.signature, list[0] = sig, sig
r.raw, err = rlp.EncodeToBytes(list)
if err != nil {
return err
} }
if len(r.raw) > SizeLimit { if len(raw) > SizeLimit {
return errTooBig return nil, errTooBig
} }
return nil return raw, nil
} }
func (r *Record) verifySignature() error { func (r *Record) idScheme() (string, IdentityScheme) {
// Get identity scheme, public key, signature.
var id ID var id ID
var entry s256raw
if err := r.Load(&id); err != nil { if err := r.Load(&id); err != nil {
return err return "", nil
} else if id != ID_SECP256k1_KECCAK {
return errNoID
} }
if err := r.Load(&entry); err != nil { return string(id), FindIdentityScheme(string(id))
return err
} else if len(entry) != 33 {
return fmt.Errorf("invalid public key")
}
// Verify the signature.
list := make([]interface{}, 0, len(r.pairs)*2+1)
list = r.appendPairs(list)
h := sha3.NewKeccak256()
rlp.Encode(h, list)
if !crypto.VerifySignature(entry, h.Sum(nil), r.signature) {
return errInvalidSig
}
return nil
} }

@ -54,35 +54,35 @@ func TestGetSetID(t *testing.T) {
assert.Equal(t, id, id2) assert.Equal(t, id, id2)
} }
// TestGetSetIP4 tests encoding/decoding and setting/getting of the IP4 key. // TestGetSetIP4 tests encoding/decoding and setting/getting of the IP key.
func TestGetSetIP4(t *testing.T) { func TestGetSetIP4(t *testing.T) {
ip := IP4{192, 168, 0, 3} ip := IP{192, 168, 0, 3}
var r Record var r Record
r.Set(ip) r.Set(ip)
var ip2 IP4 var ip2 IP
require.NoError(t, r.Load(&ip2)) require.NoError(t, r.Load(&ip2))
assert.Equal(t, ip, ip2) assert.Equal(t, ip, ip2)
} }
// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key. // TestGetSetIP6 tests encoding/decoding and setting/getting of the IP key.
func TestGetSetIP6(t *testing.T) { func TestGetSetIP6(t *testing.T) {
ip := IP6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68} ip := IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
var r Record var r Record
r.Set(ip) r.Set(ip)
var ip2 IP6 var ip2 IP
require.NoError(t, r.Load(&ip2)) require.NoError(t, r.Load(&ip2))
assert.Equal(t, ip, ip2) assert.Equal(t, ip, ip2)
} }
// TestGetSetDiscPort tests encoding/decoding and setting/getting of the DiscPort key. // TestGetSetDiscPort tests encoding/decoding and setting/getting of the DiscPort key.
func TestGetSetDiscPort(t *testing.T) { func TestGetSetUDP(t *testing.T) {
port := DiscPort(30309) port := UDP(30309)
var r Record var r Record
r.Set(port) r.Set(port)
var port2 DiscPort var port2 UDP
require.NoError(t, r.Load(&port2)) require.NoError(t, r.Load(&port2))
assert.Equal(t, port, port2) assert.Equal(t, port, port2)
} }
@ -90,7 +90,7 @@ func TestGetSetDiscPort(t *testing.T) {
// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the Secp256k1 key. // TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the Secp256k1 key.
func TestGetSetSecp256k1(t *testing.T) { func TestGetSetSecp256k1(t *testing.T) {
var r Record var r Record
if err := r.Sign(privkey); err != nil { if err := SignV4(&r, privkey); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -101,16 +101,16 @@ func TestGetSetSecp256k1(t *testing.T) {
func TestLoadErrors(t *testing.T) { func TestLoadErrors(t *testing.T) {
var r Record var r Record
ip4 := IP4{127, 0, 0, 1} ip4 := IP{127, 0, 0, 1}
r.Set(ip4) r.Set(ip4)
// Check error for missing keys. // Check error for missing keys.
var ip6 IP6 var udp UDP
err := r.Load(&ip6) err := r.Load(&udp)
if !IsNotFound(err) { if !IsNotFound(err) {
t.Error("IsNotFound should return true for missing key") t.Error("IsNotFound should return true for missing key")
} }
assert.Equal(t, &KeyError{Key: ip6.ENRKey(), Err: errNotFound}, err) assert.Equal(t, &KeyError{Key: udp.ENRKey(), Err: errNotFound}, err)
// Check error for invalid keys. // Check error for invalid keys.
var list []uint var list []uint
@ -174,7 +174,7 @@ func TestDirty(t *testing.T) {
t.Errorf("expected errEncodeUnsigned, got %#v", err) t.Errorf("expected errEncodeUnsigned, got %#v", err)
} }
require.NoError(t, r.Sign(privkey)) require.NoError(t, SignV4(&r, privkey))
if !r.Signed() { if !r.Signed() {
t.Error("Signed return false for signed record") t.Error("Signed return false for signed record")
} }
@ -194,13 +194,13 @@ func TestDirty(t *testing.T) {
func TestGetSetOverwrite(t *testing.T) { func TestGetSetOverwrite(t *testing.T) {
var r Record var r Record
ip := IP4{192, 168, 0, 3} ip := IP{192, 168, 0, 3}
r.Set(ip) r.Set(ip)
ip2 := IP4{192, 168, 0, 4} ip2 := IP{192, 168, 0, 4}
r.Set(ip2) r.Set(ip2)
var ip3 IP4 var ip3 IP
require.NoError(t, r.Load(&ip3)) require.NoError(t, r.Load(&ip3))
assert.Equal(t, ip2, ip3) assert.Equal(t, ip2, ip3)
} }
@ -208,9 +208,9 @@ func TestGetSetOverwrite(t *testing.T) {
// TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record. // TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record.
func TestSignEncodeAndDecode(t *testing.T) { func TestSignEncodeAndDecode(t *testing.T) {
var r Record var r Record
r.Set(DiscPort(30303)) r.Set(UDP(30303))
r.Set(IP4{127, 0, 0, 1}) r.Set(IP{127, 0, 0, 1})
require.NoError(t, r.Sign(privkey)) require.NoError(t, SignV4(&r, privkey))
blob, err := rlp.EncodeToBytes(r) blob, err := rlp.EncodeToBytes(r)
require.NoError(t, err) require.NoError(t, err)
@ -230,12 +230,12 @@ func TestNodeAddr(t *testing.T) {
t.Errorf("wrong address on empty record: got %v, want %v", addr, nil) t.Errorf("wrong address on empty record: got %v, want %v", addr, nil)
} }
require.NoError(t, r.Sign(privkey)) require.NoError(t, SignV4(&r, privkey))
expected := "caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726" expected := "a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"
assert.Equal(t, expected, hex.EncodeToString(r.NodeAddr())) assert.Equal(t, expected, hex.EncodeToString(r.NodeAddr()))
} }
var pyRecord, _ = hex.DecodeString("f896b840954dc36583c1f4b69ab59b1375f362f06ee99f3723cd77e64b6de6d211c27d7870642a79d4516997f94091325d2a7ca6215376971455fb221d34f35b277149a1018664697363763582765f82696490736563703235366b312d6b656363616b83697034847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138") var pyRecord, _ = hex.DecodeString("f884b8407098ad865b00a582051940cb9cf36836572411a47278783077011599ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1145ccb9c01826964827634826970847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31388375647082765f")
// TestPythonInterop checks that we can decode and verify a record produced by the Python // TestPythonInterop checks that we can decode and verify a record produced by the Python
// implementation. // implementation.
@ -246,10 +246,10 @@ func TestPythonInterop(t *testing.T) {
} }
var ( var (
wantAddr, _ = hex.DecodeString("caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726") wantAddr, _ = hex.DecodeString("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
wantSeq = uint64(1) wantSeq = uint64(1)
wantIP = IP4{127, 0, 0, 1} wantIP = IP{127, 0, 0, 1}
wantDiscport = DiscPort(30303) wantUDP = UDP(30303)
) )
if r.Seq() != wantSeq { if r.Seq() != wantSeq {
t.Errorf("wrong seq: got %d, want %d", r.Seq(), wantSeq) t.Errorf("wrong seq: got %d, want %d", r.Seq(), wantSeq)
@ -257,7 +257,7 @@ func TestPythonInterop(t *testing.T) {
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) t.Errorf("wrong addr: got %x, want %x", addr, wantAddr)
} }
want := map[Entry]interface{}{new(IP4): &wantIP, new(DiscPort): &wantDiscport} want := map[Entry]interface{}{new(IP): &wantIP, new(UDP): &wantUDP}
for k, v := range want { for k, v := range want {
desc := fmt.Sprintf("loading key %q", k.ENRKey()) desc := fmt.Sprintf("loading key %q", k.ENRKey())
if assert.NoError(t, r.Load(k), desc) { if assert.NoError(t, r.Load(k), desc) {
@ -272,14 +272,14 @@ func TestRecordTooBig(t *testing.T) {
key := randomString(10) key := randomString(10)
// set a big value for random key, expect error // set a big value for random key, expect error
r.Set(WithEntry(key, randomString(300))) r.Set(WithEntry(key, randomString(SizeLimit)))
if err := r.Sign(privkey); err != errTooBig { if err := SignV4(&r, privkey); err != errTooBig {
t.Fatalf("expected to get errTooBig, got %#v", err) t.Fatalf("expected to get errTooBig, got %#v", err)
} }
// set an acceptable value for random key, expect no error // set an acceptable value for random key, expect no error
r.Set(WithEntry(key, randomString(100))) r.Set(WithEntry(key, randomString(100)))
require.NoError(t, r.Sign(privkey)) require.NoError(t, SignV4(&r, privkey))
} }
// TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs. // TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs.
@ -295,7 +295,7 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) {
r.Set(WithEntry(key, &value)) r.Set(WithEntry(key, &value))
} }
require.NoError(t, r.Sign(privkey)) require.NoError(t, SignV4(&r, privkey))
_, err := rlp.EncodeToBytes(r) _, err := rlp.EncodeToBytes(r)
require.NoError(t, err) require.NoError(t, err)

@ -57,59 +57,43 @@ func WithEntry(k string, v interface{}) Entry {
return &generic{key: k, value: v} return &generic{key: k, value: v}
} }
// DiscPort is the "discv5" key, which holds the UDP port for discovery v5. // TCP is the "tcp" key, which holds the TCP port of the node.
type DiscPort uint16 type TCP uint16
func (v DiscPort) ENRKey() string { return "discv5" } func (v TCP) ENRKey() string { return "tcp" }
// UDP is the "udp" key, which holds the UDP port of the node.
type UDP uint16
func (v UDP) ENRKey() string { return "udp" }
// ID is the "id" key, which holds the name of the identity scheme. // ID is the "id" key, which holds the name of the identity scheme.
type ID string type ID string
const IDv4 = ID("v4") // the default identity scheme
func (v ID) ENRKey() string { return "id" } func (v ID) ENRKey() string { return "id" }
// IP4 is the "ip4" key, which holds a 4-byte IPv4 address. // IP is the "ip" key, which holds the IP address of the node.
type IP4 net.IP type IP net.IP
func (v IP4) ENRKey() string { return "ip4" } func (v IP) ENRKey() string { return "ip" }
// EncodeRLP implements rlp.Encoder. // EncodeRLP implements rlp.Encoder.
func (v IP4) EncodeRLP(w io.Writer) error { func (v IP) EncodeRLP(w io.Writer) error {
ip4 := net.IP(v).To4() if ip4 := net.IP(v).To4(); ip4 != nil {
if ip4 == nil { return rlp.Encode(w, ip4)
return fmt.Errorf("invalid IPv4 address: %v", v)
}
return rlp.Encode(w, ip4)
}
// DecodeRLP implements rlp.Decoder.
func (v *IP4) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode((*net.IP)(v)); err != nil {
return err
}
if len(*v) != 4 {
return fmt.Errorf("invalid IPv4 address, want 4 bytes: %v", *v)
} }
return nil return rlp.Encode(w, net.IP(v))
}
// IP6 is the "ip6" key, which holds a 16-byte IPv6 address.
type IP6 net.IP
func (v IP6) ENRKey() string { return "ip6" }
// EncodeRLP implements rlp.Encoder.
func (v IP6) EncodeRLP(w io.Writer) error {
ip6 := net.IP(v)
return rlp.Encode(w, ip6)
} }
// DecodeRLP implements rlp.Decoder. // DecodeRLP implements rlp.Decoder.
func (v *IP6) DecodeRLP(s *rlp.Stream) error { func (v *IP) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode((*net.IP)(v)); err != nil { if err := s.Decode((*net.IP)(v)); err != nil {
return err return err
} }
if len(*v) != 16 { if len(*v) != 4 && len(*v) != 16 {
return fmt.Errorf("invalid IPv6 address, want 16 bytes: %v", *v) return fmt.Errorf("invalid IP address, want 4 or 16 bytes: %v", *v)
} }
return nil return nil
} }

@ -0,0 +1,114 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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
import (
"crypto/ecdsa"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp"
)
// Registry of known identity schemes.
var schemes sync.Map
// 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
}
// RegisterIdentityScheme adds an identity scheme to the global registry.
func RegisterIdentityScheme(name string, scheme IdentityScheme) {
if _, loaded := schemes.LoadOrStore(name, scheme); loaded {
panic("identity scheme " + name + " already registered")
}
}
// FindIdentityScheme resolves name to an identity scheme in the global registry.
func FindIdentityScheme(name string) IdentityScheme {
s, ok := schemes.Load(name)
if !ok {
return nil
}
return s.(IdentityScheme)
}
// v4ID is the "v4" identity scheme.
type v4ID struct{}
func init() {
RegisterIdentityScheme("v4", v4ID{})
}
// SignV4 signs a record using the v4 scheme.
func SignV4(r *Record, privkey *ecdsa.PrivateKey) error {
// Copy r to avoid modifying it if signing fails.
cpy := *r
cpy.Set(ID("v4"))
cpy.Set(Secp256k1(privkey.PublicKey))
h := sha3.NewKeccak256()
rlp.Encode(h, cpy.AppendElements(nil))
sig, err := crypto.Sign(h.Sum(nil), privkey)
if err != nil {
return err
}
sig = sig[:len(sig)-1] // remove v
if err = cpy.SetSig("v4", sig); err == nil {
*r = cpy
}
return err
}
// s256raw is an unparsed secp256k1 public key entry.
type s256raw []byte
func (s256raw) ENRKey() string { return "secp256k1" }
func (v4ID) Verify(r *Record, sig []byte) error {
var entry s256raw
if err := r.Load(&entry); err != nil {
return err
} else if len(entry) != 33 {
return fmt.Errorf("invalid public key")
}
h := sha3.NewKeccak256()
rlp.Encode(h, r.AppendElements(nil))
if !crypto.VerifySignature(entry, h.Sum(nil), sig) {
return errInvalidSig
}
return nil
}
func (v4ID) NodeAddr(r *Record) []byte {
var pubkey Secp256k1
err := r.Load(&pubkey)
if err != nil {
return nil
}
buf := make([]byte, 64)
math.ReadBits(pubkey.X, buf[:32])
math.ReadBits(pubkey.Y, buf[32:])
return crypto.Keccak256(buf)
}

@ -0,0 +1,36 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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
import (
"crypto/ecdsa"
"math/big"
"testing"
)
// Checks that failure to sign leaves the record unmodified.
func TestSignError(t *testing.T) {
invalidKey := &ecdsa.PrivateKey{D: new(big.Int), PublicKey: *pubkey}
var r Record
if err := SignV4(&r, invalidKey); err == nil {
t.Fatal("expected error from SignV4")
}
if len(r.pairs) > 0 {
t.Fatal("expected empty record, have", r.pairs)
}
}
Loading…
Cancel
Save