diff --git a/VERSION b/VERSION
index 1790d35988..53ed4ba51e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.8.8
+1.8.9
diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go
index 512d8fdfa7..93b513c346 100644
--- a/accounts/abi/argument.go
+++ b/accounts/abi/argument.go
@@ -111,9 +111,14 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
return err
}
- // If the output interface is a struct, make sure names don't collide
+
+ // If the interface is a struct, get of abi->struct_field mapping
+
+ var abi2struct map[string]string
if kind == reflect.Struct {
- if err := requireUniqueStructFieldNames(arguments); err != nil {
+ var err error
+ abi2struct, err = mapAbiToStructFields(arguments, value)
+ if err != nil {
return err
}
}
@@ -123,9 +128,10 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
switch kind {
case reflect.Struct:
- err := unpackStruct(value, reflectValue, arg)
- if err != nil {
- return err
+ if structField, ok := abi2struct[arg.Name]; ok {
+ if err := set(value.FieldByName(structField), reflectValue, arg); err != nil {
+ return err
+ }
}
case reflect.Slice, reflect.Array:
if value.Len() < i {
@@ -151,17 +157,22 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interf
if len(marshalledValues) != 1 {
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
}
+
elem := reflect.ValueOf(v).Elem()
kind := elem.Kind()
reflectValue := reflect.ValueOf(marshalledValues[0])
+ var abi2struct map[string]string
if kind == reflect.Struct {
- //make sure names don't collide
- if err := requireUniqueStructFieldNames(arguments); err != nil {
+ var err error
+ if abi2struct, err = mapAbiToStructFields(arguments, elem); err != nil {
return err
}
-
- return unpackStruct(elem, reflectValue, arguments[0])
+ arg := arguments.NonIndexed()[0]
+ if structField, ok := abi2struct[arg.Name]; ok {
+ return set(elem.FieldByName(structField), reflectValue, arg)
+ }
+ return nil
}
return set(elem, reflectValue, arguments.NonIndexed()[0])
@@ -277,18 +288,3 @@ func capitalise(input string) string {
}
return strings.ToUpper(input[:1]) + input[1:]
}
-
-//unpackStruct extracts each argument into its corresponding struct field
-func unpackStruct(value, reflectValue reflect.Value, arg Argument) error {
- name := capitalise(arg.Name)
- typ := value.Type()
- for j := 0; j < typ.NumField(); j++ {
- // TODO read tags: `abi:"fieldName"`
- if typ.Field(j).Name == name {
- if err := set(value.Field(j), reflectValue, arg); err != nil {
- return err
- }
- }
- }
- return nil
-}
diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go
index cca61e433d..3bfdd7c0ab 100644
--- a/accounts/abi/event_test.go
+++ b/accounts/abi/event_test.go
@@ -58,12 +58,28 @@ var jsonEventPledge = []byte(`{
"type": "event"
}`)
+var jsonEventMixedCase = []byte(`{
+ "anonymous": false,
+ "inputs": [{
+ "indexed": false, "name": "value", "type": "uint256"
+ }, {
+ "indexed": false, "name": "_value", "type": "uint256"
+ }, {
+ "indexed": false, "name": "Value", "type": "uint256"
+ }],
+ "name": "MixedCase",
+ "type": "event"
+ }`)
+
// 1000000
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
+// 1000000,2218516807680,1000001
+var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
+
func TestEventId(t *testing.T) {
var table = []struct {
definition string
@@ -121,6 +137,27 @@ func TestEventTupleUnpack(t *testing.T) {
Value *big.Int
}
+ type EventTransferWithTag struct {
+ // this is valid because `value` is not exportable,
+ // so value is only unmarshalled into `Value1`.
+ value *big.Int
+ Value1 *big.Int `abi:"value"`
+ }
+
+ type BadEventTransferWithSameFieldAndTag struct {
+ Value *big.Int
+ Value1 *big.Int `abi:"value"`
+ }
+
+ type BadEventTransferWithDuplicatedTag struct {
+ Value1 *big.Int `abi:"value"`
+ Value2 *big.Int `abi:"value"`
+ }
+
+ type BadEventTransferWithEmptyTag struct {
+ Value *big.Int `abi:""`
+ }
+
type EventPledge struct {
Who common.Address
Wad *big.Int
@@ -133,9 +170,16 @@ func TestEventTupleUnpack(t *testing.T) {
Currency [3]byte
}
+ type EventMixedCase struct {
+ Value1 *big.Int `abi:"value"`
+ Value2 *big.Int `abi:"_value"`
+ Value3 *big.Int `abi:"Value"`
+ }
+
bigint := new(big.Int)
bigintExpected := big.NewInt(1000000)
bigintExpected2 := big.NewInt(2218516807680)
+ bigintExpected3 := big.NewInt(1000001)
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
var testCases = []struct {
data string
@@ -158,6 +202,34 @@ func TestEventTupleUnpack(t *testing.T) {
jsonEventTransfer,
"",
"Can unpack ERC20 Transfer event into slice",
+ }, {
+ transferData1,
+ &EventTransferWithTag{},
+ &EventTransferWithTag{Value1: bigintExpected},
+ jsonEventTransfer,
+ "",
+ "Can unpack ERC20 Transfer event into structure with abi: tag",
+ }, {
+ transferData1,
+ &BadEventTransferWithDuplicatedTag{},
+ &BadEventTransferWithDuplicatedTag{},
+ jsonEventTransfer,
+ "struct: abi tag in 'Value2' already mapped",
+ "Can not unpack ERC20 Transfer event with duplicated abi tag",
+ }, {
+ transferData1,
+ &BadEventTransferWithSameFieldAndTag{},
+ &BadEventTransferWithSameFieldAndTag{},
+ jsonEventTransfer,
+ "abi: multiple variables maps to the same abi field 'value'",
+ "Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable",
+ }, {
+ transferData1,
+ &BadEventTransferWithEmptyTag{},
+ &BadEventTransferWithEmptyTag{},
+ jsonEventTransfer,
+ "struct: abi tag in 'Value' is empty",
+ "Can not unpack ERC20 Transfer event with an empty tag",
}, {
pledgeData1,
&EventPledge{},
@@ -216,6 +288,13 @@ func TestEventTupleUnpack(t *testing.T) {
jsonEventPledge,
"abi: cannot unmarshal tuple into map[string]interface {}",
"Can not unpack Pledge event into map",
+ }, {
+ mixedCaseData1,
+ &EventMixedCase{},
+ &EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3},
+ jsonEventMixedCase,
+ "",
+ "Can unpack abi variables with mixed case",
}}
for _, tc := range testCases {
@@ -227,7 +306,7 @@ func TestEventTupleUnpack(t *testing.T) {
assert.Nil(err, "Should be able to unpack event data.")
assert.Equal(tc.expected, tc.dest, tc.name)
} else {
- assert.EqualError(err, tc.error)
+ assert.EqualError(err, tc.error, tc.name)
}
})
}
diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go
index 5620a70845..0193517a42 100644
--- a/accounts/abi/reflect.go
+++ b/accounts/abi/reflect.go
@@ -19,6 +19,7 @@ package abi
import (
"fmt"
"reflect"
+ "strings"
)
// indirect recursively dereferences the value until it either gets the value
@@ -111,18 +112,101 @@ func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
return nil
}
-// requireUniqueStructFieldNames makes sure field names don't collide
-func requireUniqueStructFieldNames(args Arguments) error {
- exists := make(map[string]bool)
- for _, arg := range args {
- field := capitalise(arg.Name)
- if field == "" {
- return fmt.Errorf("abi: purely underscored output cannot unpack to struct")
+// mapAbiToStringField maps abi to struct fields.
+// first round: for each Exportable field that contains a `abi:""` tag
+// and this field name exists in the arguments, pair them together.
+// second round: for each argument field that has not been already linked,
+// find what variable is expected to be mapped into, if it exists and has not been
+// used, pair them.
+func mapAbiToStructFields(args Arguments, value reflect.Value) (map[string]string, error) {
+
+ typ := value.Type()
+
+ abi2struct := make(map[string]string)
+ struct2abi := make(map[string]string)
+
+ // first round ~~~
+ for i := 0; i < typ.NumField(); i++ {
+ structFieldName := typ.Field(i).Name
+
+ // skip private struct fields.
+ if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) {
+ continue
}
- if exists[field] {
- return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field)
+
+ // skip fields that have no abi:"" tag.
+ var ok bool
+ var tagName string
+ if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok {
+ continue
}
- exists[field] = true
+
+ // check if tag is empty.
+ if tagName == "" {
+ return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName)
+ }
+
+ // check which argument field matches with the abi tag.
+ found := false
+ for _, abiField := range args.NonIndexed() {
+ if abiField.Name == tagName {
+ if abi2struct[abiField.Name] != "" {
+ return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName)
+ }
+ // pair them
+ abi2struct[abiField.Name] = structFieldName
+ struct2abi[structFieldName] = abiField.Name
+ found = true
+ }
+ }
+
+ // check if this tag has been mapped.
+ if !found {
+ return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName)
+ }
+
}
- return nil
+
+ // second round ~~~
+ for _, arg := range args {
+
+ abiFieldName := arg.Name
+ structFieldName := capitalise(abiFieldName)
+
+ if structFieldName == "" {
+ return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
+ }
+
+ // this abi has already been paired, skip it... unless there exists another, yet unassigned
+ // struct field with the same field name. If so, raise an error:
+ // abi: [ { "name": "value" } ]
+ // struct { Value *big.Int , Value1 *big.Int `abi:"value"`}
+ if abi2struct[abiFieldName] != "" {
+ if abi2struct[abiFieldName] != structFieldName &&
+ struct2abi[structFieldName] == "" &&
+ value.FieldByName(structFieldName).IsValid() {
+ return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", abiFieldName)
+ }
+ continue
+ }
+
+ // return an error if this struct field has already been paired.
+ if struct2abi[structFieldName] != "" {
+ return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName)
+ }
+
+ if value.FieldByName(structFieldName).IsValid() {
+ // pair them
+ abi2struct[abiFieldName] = structFieldName
+ struct2abi[structFieldName] = abiFieldName
+ } else {
+ // not paired, but annotate as used, to detect cases like
+ // abi : [ { "name": "value" }, { "name": "_value" } ]
+ // struct { Value *big.Int }
+ struct2abi[structFieldName] = abiFieldName
+ }
+
+ }
+
+ return abi2struct, nil
}
diff --git a/appveyor.yml b/appveyor.yml
index 976b30dd1a..6c4cd429e6 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -18,8 +18,8 @@ environment:
install:
- git submodule update --init
- rmdir C:\go /s /q
- - appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.1.windows-%GETH_ARCH%.zip
- - 7z x go1.10.1.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
+ - appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.2.windows-%GETH_ARCH%.zip
+ - 7z x go1.10.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
- go version
- gcc --version
diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go
index c018895cc0..48683471d2 100644
--- a/p2p/enr/enr.go
+++ b/p2p/enr/enr.go
@@ -29,21 +29,16 @@ package enr
import (
"bytes"
- "crypto/ecdsa"
"errors"
"fmt"
"io"
"sort"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp"
)
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")
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.
-// Calling SetSeq is usually not required because signing the redord increments the
-// sequence number.
+// Calling SetSeq is usually not required because setting any key in a signed record
+// increments the sequence number.
func (r *Record) SetSeq(s uint64) {
r.signature = nil
r.raw = nil
@@ -104,33 +99,42 @@ func (r *Record) Load(e Entry) error {
return &KeyError{Key: e.ENRKey(), Err: errNotFound}
}
-// Set adds or updates the given entry in the record.
-// It panics if the value can't be encoded.
+// Set adds or updates the given entry in the record. It panics if the value can't be
+// encoded. If the record is signed, Set increments the sequence number and invalidates
+// the sequence number.
func (r *Record) Set(e Entry) {
- r.signature = nil
- r.raw = nil
blob, err := rlp.EncodeToBytes(e)
if err != nil {
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() })
-
- if i < len(r.pairs) && r.pairs[i].k == e.ENRKey() {
+ pairs := make([]pair, len(r.pairs))
+ copy(pairs, r.pairs)
+ 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]
- r.pairs[i].v = blob
- return
- } else if i < len(r.pairs) {
+ pairs[i].v = blob
+ case i < len(r.pairs):
// insert pair before i-th elem
el := pair{e.ENRKey(), blob}
- r.pairs = append(r.pairs, pair{})
- copy(r.pairs[i+1:], r.pairs[i:])
- r.pairs[i] = el
- return
+ pairs = append(pairs, pair{})
+ copy(pairs[i+1:], pairs[i:])
+ pairs[i] = el
+ 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
- r.pairs = append(r.pairs, pair{e.ENRKey(), blob})
+func (r *Record) invalidate() {
+ if r.signature == nil {
+ r.seq++
+ }
+ r.signature = nil
+ r.raw = nil
}
// EncodeRLP implements rlp.Encoder. Encoding fails if
@@ -196,39 +200,55 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error {
return err
}
- // Verify signature.
- if err = dec.verifySignature(); err != nil {
+ _, scheme := dec.idScheme()
+ if scheme == nil {
+ return errNoID
+ }
+ if err := scheme.Verify(&dec, dec.signature); err != nil {
return err
}
*r = dec
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
-// unsigned.
+// unsigned or uses an unknown identity scheme.
func (r *Record) NodeAddr() []byte {
- var entry s256raw
- if r.Load(&entry) != nil {
+ _, scheme := r.idScheme()
+ if scheme == 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
-// 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 {
- r.seq = r.seq + 1
- r.Set(ID_SECP256k1_KECCAK)
- r.Set(Secp256k1(privkey.PublicKey))
- return r.signAndEncode(privkey)
+// 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
+ }
+ 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)
for _, p := range r.pairs {
list = append(list, p.k, p.v)
@@ -236,54 +256,23 @@ func (r *Record) appendPairs(list []interface{}) []interface{} {
return list
}
-func (r *Record) signAndEncode(privkey *ecdsa.PrivateKey) error {
- // Put record elements into a flat list. Leave room for the signature.
- list := make([]interface{}, 1, len(r.pairs)*2+2)
- list = r.appendPairs(list)
-
- // Sign the tail of the list.
- h := sha3.NewKeccak256()
- rlp.Encode(h, list[1:])
- sig, err := crypto.Sign(h.Sum(nil), privkey)
- if err != nil {
- return err
+func (r *Record) encode(sig []byte) (raw []byte, err error) {
+ list := make([]interface{}, 1, 2*len(r.pairs)+1)
+ list[0] = sig
+ list = r.AppendElements(list)
+ if raw, err = rlp.EncodeToBytes(list); err != nil {
+ return nil, 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(raw) > SizeLimit {
+ return nil, errTooBig
}
- if len(r.raw) > SizeLimit {
- return errTooBig
- }
- return nil
+ return raw, nil
}
-func (r *Record) verifySignature() error {
- // Get identity scheme, public key, signature.
+func (r *Record) idScheme() (string, IdentityScheme) {
var id ID
- var entry s256raw
if err := r.Load(&id); err != nil {
- return err
- } else if id != ID_SECP256k1_KECCAK {
- return errNoID
+ return "", nil
}
- if err := r.Load(&entry); err != nil {
- 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
+ return string(id), FindIdentityScheme(string(id))
}
diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go
index ce7767d105..d1d0887563 100644
--- a/p2p/enr/enr_test.go
+++ b/p2p/enr/enr_test.go
@@ -54,35 +54,35 @@ func TestGetSetID(t *testing.T) {
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) {
- ip := IP4{192, 168, 0, 3}
+ ip := IP{192, 168, 0, 3}
var r Record
r.Set(ip)
- var ip2 IP4
+ var ip2 IP
require.NoError(t, r.Load(&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) {
- 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
r.Set(ip)
- var ip2 IP6
+ var ip2 IP
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)
+func TestGetSetUDP(t *testing.T) {
+ port := UDP(30309)
var r Record
r.Set(port)
- var port2 DiscPort
+ var port2 UDP
require.NoError(t, r.Load(&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.
func TestGetSetSecp256k1(t *testing.T) {
var r Record
- if err := r.Sign(privkey); err != nil {
+ if err := SignV4(&r, privkey); err != nil {
t.Fatal(err)
}
@@ -101,16 +101,16 @@ func TestGetSetSecp256k1(t *testing.T) {
func TestLoadErrors(t *testing.T) {
var r Record
- ip4 := IP4{127, 0, 0, 1}
+ ip4 := IP{127, 0, 0, 1}
r.Set(ip4)
// Check error for missing keys.
- var ip6 IP6
- err := r.Load(&ip6)
+ var udp UDP
+ err := r.Load(&udp)
if !IsNotFound(err) {
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.
var list []uint
@@ -174,7 +174,7 @@ func TestDirty(t *testing.T) {
t.Errorf("expected errEncodeUnsigned, got %#v", err)
}
- require.NoError(t, r.Sign(privkey))
+ require.NoError(t, SignV4(&r, privkey))
if !r.Signed() {
t.Error("Signed return false for signed record")
}
@@ -194,13 +194,13 @@ func TestDirty(t *testing.T) {
func TestGetSetOverwrite(t *testing.T) {
var r Record
- ip := IP4{192, 168, 0, 3}
+ ip := IP{192, 168, 0, 3}
r.Set(ip)
- ip2 := IP4{192, 168, 0, 4}
+ ip2 := IP{192, 168, 0, 4}
r.Set(ip2)
- var ip3 IP4
+ var ip3 IP
require.NoError(t, r.Load(&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.
func TestSignEncodeAndDecode(t *testing.T) {
var r Record
- r.Set(DiscPort(30303))
- r.Set(IP4{127, 0, 0, 1})
- require.NoError(t, r.Sign(privkey))
+ r.Set(UDP(30303))
+ r.Set(IP{127, 0, 0, 1})
+ require.NoError(t, SignV4(&r, privkey))
blob, err := rlp.EncodeToBytes(r)
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)
}
- require.NoError(t, r.Sign(privkey))
- expected := "caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726"
+ require.NoError(t, SignV4(&r, privkey))
+ expected := "a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"
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
// implementation.
@@ -246,10 +246,10 @@ func TestPythonInterop(t *testing.T) {
}
var (
- wantAddr, _ = hex.DecodeString("caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726")
- wantSeq = uint64(1)
- wantIP = IP4{127, 0, 0, 1}
- wantDiscport = DiscPort(30303)
+ wantAddr, _ = hex.DecodeString("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
+ wantSeq = uint64(1)
+ wantIP = IP{127, 0, 0, 1}
+ wantUDP = UDP(30303)
)
if 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) {
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 {
desc := fmt.Sprintf("loading key %q", k.ENRKey())
if assert.NoError(t, r.Load(k), desc) {
@@ -272,14 +272,14 @@ func TestRecordTooBig(t *testing.T) {
key := randomString(10)
// set a big value for random key, expect error
- r.Set(WithEntry(key, randomString(300)))
- if err := r.Sign(privkey); err != errTooBig {
+ r.Set(WithEntry(key, randomString(SizeLimit)))
+ if err := SignV4(&r, privkey); err != errTooBig {
t.Fatalf("expected to get errTooBig, got %#v", err)
}
// set an acceptable value for random key, expect no error
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.
@@ -295,7 +295,7 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) {
r.Set(WithEntry(key, &value))
}
- require.NoError(t, r.Sign(privkey))
+ require.NoError(t, SignV4(&r, privkey))
_, err := rlp.EncodeToBytes(r)
require.NoError(t, err)
diff --git a/p2p/enr/entries.go b/p2p/enr/entries.go
index 7591e6effb..71c7653a28 100644
--- a/p2p/enr/entries.go
+++ b/p2p/enr/entries.go
@@ -57,59 +57,43 @@ func WithEntry(k string, v interface{}) Entry {
return &generic{key: k, value: v}
}
-// DiscPort is the "discv5" key, which holds the UDP port for discovery v5.
-type DiscPort uint16
+// TCP is the "tcp" key, which holds the TCP port of the node.
+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.
type ID string
+const IDv4 = ID("v4") // the default identity scheme
+
func (v ID) ENRKey() string { return "id" }
-// IP4 is the "ip4" key, which holds a 4-byte IPv4 address.
-type IP4 net.IP
+// IP is the "ip" key, which holds the IP address of the node.
+type IP net.IP
-func (v IP4) ENRKey() string { return "ip4" }
+func (v IP) ENRKey() string { return "ip" }
// EncodeRLP implements rlp.Encoder.
-func (v IP4) EncodeRLP(w io.Writer) error {
- ip4 := net.IP(v).To4()
- if ip4 == nil {
- return fmt.Errorf("invalid IPv4 address: %v", v)
+func (v IP) EncodeRLP(w io.Writer) error {
+ if ip4 := net.IP(v).To4(); ip4 != nil {
+ return rlp.Encode(w, ip4)
}
- return rlp.Encode(w, ip4)
+ return rlp.Encode(w, net.IP(v))
}
// 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 {
return err
}
- if len(*v) != 4 {
- return fmt.Errorf("invalid IPv4 address, want 4 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)
+ if len(*v) != 4 && len(*v) != 16 {
+ return fmt.Errorf("invalid IP address, want 4 or 16 bytes: %v", *v)
}
return nil
}
diff --git a/p2p/enr/idscheme.go b/p2p/enr/idscheme.go
new file mode 100644
index 0000000000..efaf68041d
--- /dev/null
+++ b/p2p/enr/idscheme.go
@@ -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 .
+
+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)
+}
diff --git a/p2p/enr/idscheme_test.go b/p2p/enr/idscheme_test.go
new file mode 100644
index 0000000000..d790e12f14
--- /dev/null
+++ b/p2p/enr/idscheme_test.go
@@ -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 .
+
+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)
+ }
+}
diff --git a/params/version.go b/params/version.go
index 5d0ab7c031..1e8c43bf81 100644
--- a/params/version.go
+++ b/params/version.go
@@ -21,10 +21,10 @@ import (
)
const (
- VersionMajor = 1 // Major version component of the current release
- VersionMinor = 8 // Minor version component of the current release
- VersionPatch = 8 // Patch version component of the current release
- VersionMeta = "stable" // Version metadata to append to the version string
+ VersionMajor = 1 // Major version component of the current release
+ VersionMinor = 8 // Minor version component of the current release
+ VersionPatch = 9 // Patch version component of the current release
+ VersionMeta = "unstable" // Version metadata to append to the version string
)
// Version holds the textual version string.