This commit is contained in:
Wei Tang 2018-05-19 14:43:19 +08:00
commit 3ccc825364
11 changed files with 479 additions and 197 deletions

View file

@ -1 +1 @@
1.8.8 1.8.9

View file

@ -111,9 +111,14 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
if err := requireUnpackKind(value, typ, kind, arguments); err != nil { if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
return err 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 kind == reflect.Struct {
if err := requireUniqueStructFieldNames(arguments); err != nil { var err error
abi2struct, err = mapAbiToStructFields(arguments, value)
if err != nil {
return err return err
} }
} }
@ -123,10 +128,11 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
switch kind { switch kind {
case reflect.Struct: case reflect.Struct:
err := unpackStruct(value, reflectValue, arg) if structField, ok := abi2struct[arg.Name]; ok {
if err != nil { if err := set(value.FieldByName(structField), reflectValue, arg); err != nil {
return err return err
} }
}
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
if value.Len() < i { if value.Len() < i {
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
@ -151,17 +157,22 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interf
if len(marshalledValues) != 1 { if len(marshalledValues) != 1 {
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues)) return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
} }
elem := reflect.ValueOf(v).Elem() elem := reflect.ValueOf(v).Elem()
kind := elem.Kind() kind := elem.Kind()
reflectValue := reflect.ValueOf(marshalledValues[0]) reflectValue := reflect.ValueOf(marshalledValues[0])
var abi2struct map[string]string
if kind == reflect.Struct { if kind == reflect.Struct {
//make sure names don't collide var err error
if err := requireUniqueStructFieldNames(arguments); err != nil { if abi2struct, err = mapAbiToStructFields(arguments, elem); err != nil {
return err return err
} }
arg := arguments.NonIndexed()[0]
return unpackStruct(elem, reflectValue, arguments[0]) if structField, ok := abi2struct[arg.Name]; ok {
return set(elem.FieldByName(structField), reflectValue, arg)
}
return nil
} }
return set(elem, reflectValue, arguments.NonIndexed()[0]) return set(elem, reflectValue, arguments.NonIndexed()[0])
@ -277,18 +288,3 @@ func capitalise(input string) string {
} }
return strings.ToUpper(input[:1]) + input[1:] 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
}

View file

@ -58,12 +58,28 @@ var jsonEventPledge = []byte(`{
"type": "event" "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 // 1000000
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240" var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd" // "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000" var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
// 1000000,2218516807680,1000001
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
func TestEventId(t *testing.T) { func TestEventId(t *testing.T) {
var table = []struct { var table = []struct {
definition string definition string
@ -121,6 +137,27 @@ func TestEventTupleUnpack(t *testing.T) {
Value *big.Int 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 { type EventPledge struct {
Who common.Address Who common.Address
Wad *big.Int Wad *big.Int
@ -133,9 +170,16 @@ func TestEventTupleUnpack(t *testing.T) {
Currency [3]byte 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) bigint := new(big.Int)
bigintExpected := big.NewInt(1000000) bigintExpected := big.NewInt(1000000)
bigintExpected2 := big.NewInt(2218516807680) bigintExpected2 := big.NewInt(2218516807680)
bigintExpected3 := big.NewInt(1000001)
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268") addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
var testCases = []struct { var testCases = []struct {
data string data string
@ -158,6 +202,34 @@ func TestEventTupleUnpack(t *testing.T) {
jsonEventTransfer, jsonEventTransfer,
"", "",
"Can unpack ERC20 Transfer event into slice", "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, pledgeData1,
&EventPledge{}, &EventPledge{},
@ -216,6 +288,13 @@ func TestEventTupleUnpack(t *testing.T) {
jsonEventPledge, jsonEventPledge,
"abi: cannot unmarshal tuple into map[string]interface {}", "abi: cannot unmarshal tuple into map[string]interface {}",
"Can not unpack Pledge event into map", "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 { for _, tc := range testCases {
@ -227,7 +306,7 @@ func TestEventTupleUnpack(t *testing.T) {
assert.Nil(err, "Should be able to unpack event data.") assert.Nil(err, "Should be able to unpack event data.")
assert.Equal(tc.expected, tc.dest, tc.name) assert.Equal(tc.expected, tc.dest, tc.name)
} else { } else {
assert.EqualError(err, tc.error) assert.EqualError(err, tc.error, tc.name)
} }
}) })
} }

View file

@ -19,6 +19,7 @@ package abi
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strings"
) )
// indirect recursively dereferences the value until it either gets the value // 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 return nil
} }
// requireUniqueStructFieldNames makes sure field names don't collide // mapAbiToStringField maps abi to struct fields.
func requireUniqueStructFieldNames(args Arguments) error { // first round: for each Exportable field that contains a `abi:""` tag
exists := make(map[string]bool) // 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
}
// skip fields that have no abi:"" tag.
var ok bool
var tagName string
if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok {
continue
}
// 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)
}
}
// second round ~~~
for _, arg := range args { for _, arg := range args {
field := capitalise(arg.Name)
if field == "" { abiFieldName := arg.Name
return fmt.Errorf("abi: purely underscored output cannot unpack to struct") structFieldName := capitalise(abiFieldName)
if structFieldName == "" {
return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
} }
if exists[field] {
return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field) // 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)
} }
exists[field] = true continue
} }
return nil
// 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
} }

View file

@ -18,8 +18,8 @@ environment:
install: install:
- git submodule update --init - git submodule update --init
- rmdir C:\go /s /q - rmdir C:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.1.windows-%GETH_ARCH%.zip - appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.2.windows-%GETH_ARCH%.zip
- 7z x go1.10.1.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - 7z x go1.10.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
- go version - go version
- gcc --version - gcc --version

View file

@ -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))
} }
func (r *Record) appendPairs(list []interface{}) []interface{} { // 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
}
// 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
} }

View file

@ -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)

View file

@ -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.
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
}
// 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
} }

114
p2p/enr/idscheme.go Normal file
View 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
View 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)
}
}

View file

@ -23,8 +23,8 @@ import (
const ( const (
VersionMajor = 1 // Major version component of the current release VersionMajor = 1 // Major version component of the current release
VersionMinor = 8 // Minor version component of the current release VersionMinor = 8 // Minor version component of the current release
VersionPatch = 8 // Patch version component of the current release VersionPatch = 9 // Patch version component of the current release
VersionMeta = "stable" // Version metadata to append to the version string VersionMeta = "unstable" // Version metadata to append to the version string
) )
// Version holds the textual version string. // Version holds the textual version string.