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  }