github.com/jimmyx0x/go-ethereum@v1.10.28/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 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 }