mirror of https://github.com/ethereum/go-ethereum
p2p/enr: updates for discovery v4 compatibility (#16679)
This applies spec changes from ethereum/EIPs#1049 and adds support for pluggable identity schemes. Some care has been taken to make the "v4" scheme standalone. It uses public APIs only and could be moved out of package enr at any time. A couple of minor changes were needed to make identity schemes work: - The sequence number is now updated in Set instead of when signing. - Record is now copy-safe, i.e. calling Set on a shallow copy doesn't modify the record it was copied from.pull/16753/head
parent
f6bc65fc68
commit
6286c255f1
@ -0,0 +1,114 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package enr |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/ecdsa" |
||||||
|
"fmt" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/math" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/crypto/sha3" |
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
) |
||||||
|
|
||||||
|
// Registry of known identity schemes.
|
||||||
|
var schemes sync.Map |
||||||
|
|
||||||
|
// An IdentityScheme is capable of verifying record signatures and
|
||||||
|
// deriving node addresses.
|
||||||
|
type IdentityScheme interface { |
||||||
|
Verify(r *Record, sig []byte) error |
||||||
|
NodeAddr(r *Record) []byte |
||||||
|
} |
||||||
|
|
||||||
|
// RegisterIdentityScheme adds an identity scheme to the global registry.
|
||||||
|
func RegisterIdentityScheme(name string, scheme IdentityScheme) { |
||||||
|
if _, loaded := schemes.LoadOrStore(name, scheme); loaded { |
||||||
|
panic("identity scheme " + name + " already registered") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// FindIdentityScheme resolves name to an identity scheme in the global registry.
|
||||||
|
func FindIdentityScheme(name string) IdentityScheme { |
||||||
|
s, ok := schemes.Load(name) |
||||||
|
if !ok { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return s.(IdentityScheme) |
||||||
|
} |
||||||
|
|
||||||
|
// v4ID is the "v4" identity scheme.
|
||||||
|
type v4ID struct{} |
||||||
|
|
||||||
|
func init() { |
||||||
|
RegisterIdentityScheme("v4", v4ID{}) |
||||||
|
} |
||||||
|
|
||||||
|
// SignV4 signs a record using the v4 scheme.
|
||||||
|
func SignV4(r *Record, privkey *ecdsa.PrivateKey) error { |
||||||
|
// Copy r to avoid modifying it if signing fails.
|
||||||
|
cpy := *r |
||||||
|
cpy.Set(ID("v4")) |
||||||
|
cpy.Set(Secp256k1(privkey.PublicKey)) |
||||||
|
|
||||||
|
h := sha3.NewKeccak256() |
||||||
|
rlp.Encode(h, cpy.AppendElements(nil)) |
||||||
|
sig, err := crypto.Sign(h.Sum(nil), privkey) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
sig = sig[:len(sig)-1] // remove v
|
||||||
|
if err = cpy.SetSig("v4", sig); err == nil { |
||||||
|
*r = cpy |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// s256raw is an unparsed secp256k1 public key entry.
|
||||||
|
type s256raw []byte |
||||||
|
|
||||||
|
func (s256raw) ENRKey() string { return "secp256k1" } |
||||||
|
|
||||||
|
func (v4ID) Verify(r *Record, sig []byte) error { |
||||||
|
var entry s256raw |
||||||
|
if err := r.Load(&entry); err != nil { |
||||||
|
return err |
||||||
|
} else if len(entry) != 33 { |
||||||
|
return fmt.Errorf("invalid public key") |
||||||
|
} |
||||||
|
|
||||||
|
h := sha3.NewKeccak256() |
||||||
|
rlp.Encode(h, r.AppendElements(nil)) |
||||||
|
if !crypto.VerifySignature(entry, h.Sum(nil), sig) { |
||||||
|
return errInvalidSig |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (v4ID) NodeAddr(r *Record) []byte { |
||||||
|
var pubkey Secp256k1 |
||||||
|
err := r.Load(&pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
buf := make([]byte, 64) |
||||||
|
math.ReadBits(pubkey.X, buf[:32]) |
||||||
|
math.ReadBits(pubkey.Y, buf[32:]) |
||||||
|
return crypto.Keccak256(buf) |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package enr |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/ecdsa" |
||||||
|
"math/big" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
// Checks that failure to sign leaves the record unmodified.
|
||||||
|
func TestSignError(t *testing.T) { |
||||||
|
invalidKey := &ecdsa.PrivateKey{D: new(big.Int), PublicKey: *pubkey} |
||||||
|
|
||||||
|
var r Record |
||||||
|
if err := SignV4(&r, invalidKey); err == nil { |
||||||
|
t.Fatal("expected error from SignV4") |
||||||
|
} |
||||||
|
if len(r.pairs) > 0 { |
||||||
|
t.Fatal("expected empty record, have", r.pairs) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue