forked from cerc-io/plugeth
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.
This commit is contained in:
parent
f6bc65fc68
commit
6286c255f1
161
p2p/enr/enr.go
161
p2p/enr/enr.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
|
// element should be placed at the end of r.pairs
|
||||||
r.pairs = append(r.pairs, pair{e.ENRKey(), blob})
|
pairs = append(pairs, pair{e.ENRKey(), blob})
|
||||||
|
}
|
||||||
|
r.pairs = pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Record) invalidate() {
|
||||||
|
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
|
if len(raw) > SizeLimit {
|
||||||
|
return nil, errTooBig
|
||||||
// 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 {
|
return raw, nil
|
||||||
return errTooBig
|
|
||||||
}
|
|
||||||
return 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 fmt.Errorf("invalid IPv4 address: %v", v)
|
|
||||||
}
|
|
||||||
return rlp.Encode(w, ip4)
|
return rlp.Encode(w, ip4)
|
||||||
|
}
|
||||||
|
return rlp.Encode(w, net.IP(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeRLP implements rlp.Decoder.
|
// DecodeRLP implements rlp.Decoder.
|
||||||
func (v *IP4) 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) != 4 {
|
if len(*v) != 4 && len(*v) != 16 {
|
||||||
return fmt.Errorf("invalid IPv4 address, want 4 bytes: %v", *v)
|
return fmt.Errorf("invalid IP address, want 4 or 16 bytes: %v", *v)
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (v *IP6) DecodeRLP(s *rlp.Stream) error {
|
|
||||||
if err := s.Decode((*net.IP)(v)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(*v) != 16 {
|
|
||||||
return fmt.Errorf("invalid IPv6 address, want 16 bytes: %v", *v)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
114
p2p/enr/idscheme.go
Normal file
114
p2p/enr/idscheme.go
Normal file
@ -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)
|
||||||
|
}
|
36
p2p/enr/idscheme_test.go
Normal file
36
p2p/enr/idscheme_test.go
Normal file
@ -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…
Reference in New Issue
Block a user