github.com/status-im/status-go@v1.1.0/eth-node/crypto/crypto.go (about) 1 package crypto 2 3 import ( 4 "context" 5 "crypto/aes" 6 "crypto/cipher" 7 "crypto/ecdsa" 8 "crypto/rand" 9 "encoding/hex" 10 "errors" 11 "fmt" 12 13 "golang.org/x/crypto/sha3" 14 15 types "github.com/status-im/status-go/eth-node/types" 16 17 gethcrypto "github.com/ethereum/go-ethereum/crypto" 18 ) 19 20 const ( 21 aesNonceLength = 12 22 ) 23 24 // Sign calculates an ECDSA signature. 25 // 26 // This function is susceptible to chosen plaintext attacks that can leak 27 // information about the private key that is used for signing. Callers must 28 // be aware that the given digest cannot be chosen by an adversery. Common 29 // solution is to hash any input before calculating the signature. 30 // 31 // The produced signature is in the [R || S || V] format where V is 0 or 1. 32 func Sign(digestHash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { 33 return gethcrypto.Sign(digestHash, prv) 34 } 35 36 // SignBytes signs the hash of arbitrary data. 37 func SignBytes(data []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { 38 return Sign(Keccak256(data), prv) 39 } 40 41 // SignBytesAsHex signs the Keccak256 hash of arbitrary data and returns its hex representation. 42 func SignBytesAsHex(data []byte, identity *ecdsa.PrivateKey) (string, error) { 43 signature, err := SignBytes(data, identity) 44 if err != nil { 45 return "", err 46 } 47 return hex.EncodeToString(signature), nil 48 } 49 50 // SignStringAsHex signs the Keccak256 hash of arbitrary string and returns its hex representation. 51 func SignStringAsHex(data string, identity *ecdsa.PrivateKey) (string, error) { 52 return SignBytesAsHex([]byte(data), identity) 53 } 54 55 // VerifySignatures verifies tuples of signatures content/hash/public key 56 func VerifySignatures(signaturePairs [][3]string) error { 57 for _, signaturePair := range signaturePairs { 58 content := Keccak256([]byte(signaturePair[0])) 59 60 signature, err := hex.DecodeString(signaturePair[1]) 61 if err != nil { 62 return err 63 } 64 65 publicKeyBytes, err := hex.DecodeString(signaturePair[2]) 66 if err != nil { 67 return err 68 } 69 70 publicKey, err := UnmarshalPubkey(publicKeyBytes) 71 if err != nil { 72 return err 73 } 74 75 recoveredKey, err := SigToPub( 76 content, 77 signature, 78 ) 79 if err != nil { 80 return err 81 } 82 83 if PubkeyToAddress(*recoveredKey) != PubkeyToAddress(*publicKey) { 84 return errors.New("identity key and signature mismatch") 85 } 86 } 87 88 return nil 89 } 90 91 // ExtractSignatures extract from tuples of signatures content a public key 92 // DEPRECATED: use ExtractSignature 93 func ExtractSignatures(signaturePairs [][2]string) ([]string, error) { 94 response := make([]string, len(signaturePairs)) 95 for i, signaturePair := range signaturePairs { 96 content := Keccak256([]byte(signaturePair[0])) 97 98 signature, err := hex.DecodeString(signaturePair[1]) 99 if err != nil { 100 return nil, err 101 } 102 103 recoveredKey, err := SigToPub( 104 content, 105 signature, 106 ) 107 if err != nil { 108 return nil, err 109 } 110 111 response[i] = fmt.Sprintf("%x", FromECDSAPub(recoveredKey)) 112 } 113 114 return response, nil 115 } 116 117 // ExtractSignature returns a public key for a given data and signature. 118 func ExtractSignature(data, signature []byte) (*ecdsa.PublicKey, error) { 119 dataHash := Keccak256(data) 120 return SigToPub(dataHash, signature) 121 } 122 123 func EncryptSymmetric(key, plaintext []byte) ([]byte, error) { 124 block, err := aes.NewCipher(key) 125 if err != nil { 126 return nil, err 127 } 128 129 // Never use more than 2^32 random nonces with a given key because of the risk of a repeat. 130 salt, err := generateSecureRandomData(aesNonceLength) 131 if err != nil { 132 return nil, err 133 } 134 135 aesgcm, err := cipher.NewGCM(block) 136 if err != nil { 137 return nil, err 138 } 139 140 encrypted := aesgcm.Seal(nil, salt, plaintext, nil) 141 return append(encrypted, salt...), nil 142 } 143 144 func DecryptSymmetric(key []byte, cyphertext []byte) ([]byte, error) { 145 // symmetric messages are expected to contain the 12-byte nonce at the end of the payload 146 if len(cyphertext) < aesNonceLength { 147 return nil, errors.New("missing salt or invalid payload in symmetric message") 148 } 149 salt := cyphertext[len(cyphertext)-aesNonceLength:] 150 151 block, err := aes.NewCipher(key) 152 if err != nil { 153 return nil, err 154 } 155 aesgcm, err := cipher.NewGCM(block) 156 if err != nil { 157 return nil, err 158 } 159 decrypted, err := aesgcm.Open(nil, salt, cyphertext[:len(cyphertext)-aesNonceLength], nil) 160 if err != nil { 161 return nil, err 162 } 163 164 return decrypted, nil 165 } 166 167 func containsOnlyZeros(data []byte) bool { 168 for _, b := range data { 169 if b != 0 { 170 return false 171 } 172 } 173 return true 174 } 175 176 func validateDataIntegrity(k []byte, expectedSize int) bool { 177 if len(k) != expectedSize { 178 return false 179 } 180 if containsOnlyZeros(k) { 181 return false 182 } 183 return true 184 } 185 186 func generateSecureRandomData(length int) ([]byte, error) { 187 res := make([]byte, length) 188 189 _, err := rand.Read(res) 190 if err != nil { 191 return nil, err 192 } 193 194 if !validateDataIntegrity(res, length) { 195 return nil, errors.New("crypto/rand failed to generate secure random data") 196 } 197 198 return res, nil 199 } 200 201 // TextHash is a helper function that calculates a hash for the given message that can be 202 // safely used to calculate a signature from. 203 // 204 // The hash is calulcated as 205 // 206 // keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). 207 // 208 // This gives context to the signed message and prevents signing of transactions. 209 func TextHash(data []byte) []byte { 210 hash, _ := TextAndHash(data) 211 return hash 212 } 213 214 // TextAndHash is a helper function that calculates a hash for the given message that can be 215 // safely used to calculate a signature from. 216 // 217 // The hash is calulcated as 218 // 219 // keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). 220 // 221 // This gives context to the signed message and prevents signing of transactions. 222 func TextAndHash(data []byte) ([]byte, string) { 223 msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) 224 hasher := sha3.NewLegacyKeccak256() 225 _, _ = hasher.Write([]byte(msg)) 226 return hasher.Sum(nil), msg 227 } 228 229 func EcRecover(ctx context.Context, data types.HexBytes, sig types.HexBytes) (types.Address, error) { 230 // Returns the address for the Account that was used to create the signature. 231 // 232 // Note, this function is compatible with eth_sign and personal_sign. As such it recovers 233 // the address of: 234 // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") 235 // addr = ecrecover(hash, signature) 236 // 237 // Note, the signature must conform to the secp256k1 curve R, S and V values, where 238 // the V value must be be 27 or 28 for legacy reasons. 239 // 240 // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover 241 if len(sig) != 65 { 242 return types.Address{}, fmt.Errorf("signature must be 65 bytes long") 243 } 244 if sig[64] != 27 && sig[64] != 28 { 245 return types.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") 246 } 247 sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 248 hash := TextHash(data) 249 rpk, err := SigToPub(hash, sig) 250 if err != nil { 251 return types.Address{}, err 252 } 253 return PubkeyToAddress(*rpk), nil 254 }