github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/p2p/discover/v5wire/crypto.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package v5wire
    18  
    19  import (
    20  	"crypto/aes"
    21  	"crypto/cipher"
    22  	"crypto/ecdsa"
    23  	"crypto/elliptic"
    24  	"errors"
    25  	"fmt"
    26  	"hash"
    27  
    28  	"github.com/ethereum/go-ethereum/common/math"
    29  	"github.com/ethereum/go-ethereum/crypto"
    30  	"github.com/ethereum/go-ethereum/p2p/enode"
    31  	"golang.org/x/crypto/hkdf"
    32  )
    33  
    34  const (
    35  	// Encryption/authentication parameters.
    36  	aesKeySize   = 16
    37  	gcmNonceSize = 12
    38  )
    39  
    40  // Nonce represents a nonce used for AES/GCM.
    41  type Nonce [gcmNonceSize]byte
    42  
    43  // EncodePubkey encodes a public key.
    44  func EncodePubkey(key *ecdsa.PublicKey) []byte {
    45  	switch key.Curve {
    46  	case crypto.S256():
    47  		return crypto.CompressPubkey(key)
    48  	default:
    49  		panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey")
    50  	}
    51  }
    52  
    53  // DecodePubkey decodes a public key in compressed format.
    54  func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
    55  	switch curve {
    56  	case crypto.S256():
    57  		if len(e) != 33 {
    58  			return nil, errors.New("wrong size public key data")
    59  		}
    60  		return crypto.DecompressPubkey(e)
    61  	default:
    62  		return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name)
    63  	}
    64  }
    65  
    66  // idNonceHash computes the ID signature hash used in the handshake.
    67  func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte {
    68  	h.Reset()
    69  	h.Write([]byte("discovery v5 identity proof"))
    70  	h.Write(challenge)
    71  	h.Write(ephkey)
    72  	h.Write(destID[:])
    73  	return h.Sum(nil)
    74  }
    75  
    76  // makeIDSignature creates the ID nonce signature.
    77  func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) {
    78  	input := idNonceHash(hash, challenge, ephkey, destID)
    79  	switch key.Curve {
    80  	case crypto.S256():
    81  		idsig, err := crypto.Sign(input, key)
    82  		if err != nil {
    83  			return nil, err
    84  		}
    85  		return idsig[:len(idsig)-1], nil // remove recovery ID
    86  	default:
    87  		return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name)
    88  	}
    89  }
    90  
    91  // s256raw is an unparsed secp256k1 public key ENR entry.
    92  type s256raw []byte
    93  
    94  func (s256raw) ENRKey() string { return "secp256k1" }
    95  
    96  // verifyIDSignature checks that signature over idnonce was made by the given node.
    97  func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error {
    98  	switch idscheme := n.Record().IdentityScheme(); idscheme {
    99  	case "v4":
   100  		var pubkey s256raw
   101  		if n.Load(&pubkey) != nil {
   102  			return errors.New("no secp256k1 public key in record")
   103  		}
   104  		input := idNonceHash(hash, challenge, ephkey, destID)
   105  		if !crypto.VerifySignature(pubkey, input, sig) {
   106  			return errInvalidNonceSig
   107  		}
   108  		return nil
   109  	default:
   110  		return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme)
   111  	}
   112  }
   113  
   114  type hashFn func() hash.Hash
   115  
   116  // deriveKeys creates the session keys.
   117  func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session {
   118  	const text = "discovery v5 key agreement"
   119  	var info = make([]byte, 0, len(text)+len(n1)+len(n2))
   120  	info = append(info, text...)
   121  	info = append(info, n1[:]...)
   122  	info = append(info, n2[:]...)
   123  
   124  	eph := ecdh(priv, pub)
   125  	if eph == nil {
   126  		return nil
   127  	}
   128  	kdf := hkdf.New(hash, eph, challenge, info)
   129  	sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)}
   130  	kdf.Read(sec.writeKey)
   131  	kdf.Read(sec.readKey)
   132  	clear(eph)
   133  	return &sec
   134  }
   135  
   136  // ecdh creates a shared secret.
   137  func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
   138  	secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
   139  	if secX == nil {
   140  		return nil
   141  	}
   142  	sec := make([]byte, 33)
   143  	sec[0] = 0x02 | byte(secY.Bit(0))
   144  	math.ReadBits(secX, sec[1:])
   145  	return sec
   146  }
   147  
   148  // encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is
   149  // appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16
   150  // bytes longer than plaintext because it contains an authentication tag.
   151  func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) {
   152  	block, err := aes.NewCipher(key)
   153  	if err != nil {
   154  		panic(fmt.Errorf("can't create block cipher: %v", err))
   155  	}
   156  	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
   157  	if err != nil {
   158  		panic(fmt.Errorf("can't create GCM: %v", err))
   159  	}
   160  	return aesgcm.Seal(dest, nonce, plaintext, authData), nil
   161  }
   162  
   163  // decryptGCM decrypts ct using AES-GCM with the given key and nonce.
   164  func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
   165  	block, err := aes.NewCipher(key)
   166  	if err != nil {
   167  		return nil, fmt.Errorf("can't create block cipher: %v", err)
   168  	}
   169  	if len(nonce) != gcmNonceSize {
   170  		return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
   171  	}
   172  	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
   173  	if err != nil {
   174  		return nil, fmt.Errorf("can't create GCM: %v", err)
   175  	}
   176  	pt := make([]byte, 0, len(ct))
   177  	return aesgcm.Open(pt, nonce, ct, authData)
   178  }