github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/p2p/discover/v5wire/crypto.go (about)

     1  // Copyright 2021 The adkgo Authors
     2  // This file is part of the adkgo library (adapted for adkgo from go--ethereum v1.10.8).
     3  //
     4  // the adkgo 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 adkgo 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 adkgo 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/aidoskuneen/adk-node/common/math"
    29  	"github.com/aidoskuneen/adk-node/crypto"
    30  	"github.com/aidoskuneen/adk-node/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  	for i := range eph {
   133  		eph[i] = 0
   134  	}
   135  	return &sec
   136  }
   137  
   138  // ecdh creates a shared secret.
   139  func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
   140  	secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
   141  	if secX == nil {
   142  		return nil
   143  	}
   144  	sec := make([]byte, 33)
   145  	sec[0] = 0x02 | byte(secY.Bit(0))
   146  	math.ReadBits(secX, sec[1:])
   147  	return sec
   148  }
   149  
   150  // encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is
   151  // appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16
   152  // bytes longer than plaintext because it contains an authentication tag.
   153  func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) {
   154  	block, err := aes.NewCipher(key)
   155  	if err != nil {
   156  		panic(fmt.Errorf("can't create block cipher: %v", err))
   157  	}
   158  	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
   159  	if err != nil {
   160  		panic(fmt.Errorf("can't create GCM: %v", err))
   161  	}
   162  	return aesgcm.Seal(dest, nonce, plaintext, authData), nil
   163  }
   164  
   165  // decryptGCM decrypts ct using AES-GCM with the given key and nonce.
   166  func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
   167  	block, err := aes.NewCipher(key)
   168  	if err != nil {
   169  		return nil, fmt.Errorf("can't create block cipher: %v", err)
   170  	}
   171  	if len(nonce) != gcmNonceSize {
   172  		return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
   173  	}
   174  	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
   175  	if err != nil {
   176  		return nil, fmt.Errorf("can't create GCM: %v", err)
   177  	}
   178  	pt := make([]byte, 0, len(ct))
   179  	return aesgcm.Open(pt, nonce, ct, authData)
   180  }