github.com/status-im/status-go@v1.1.0/eth-node/crypto/ethereum_crypto.go (about)

     1  package crypto
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/aes"
     6  	"crypto/cipher"
     7  	"crypto/hmac"
     8  	"crypto/sha256"
     9  	"fmt"
    10  	"io"
    11  
    12  	dr "github.com/status-im/doubleratchet"
    13  	"golang.org/x/crypto/hkdf"
    14  
    15  	"github.com/status-im/status-go/eth-node/crypto/ecies"
    16  )
    17  
    18  // EthereumCrypto is an implementation of Crypto with cryptographic primitives recommended
    19  // by the Double Ratchet Algorithm specification. However, some details are different,
    20  // see function comments for details.
    21  type EthereumCrypto struct{}
    22  
    23  // See the Crypto interface.
    24  func (c EthereumCrypto) GenerateDH() (dr.DHPair, error) {
    25  	keys, err := GenerateKey()
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	return DHPair{
    31  		PubKey: CompressPubkey(&keys.PublicKey),
    32  		PrvKey: FromECDSA(keys),
    33  	}, nil
    34  
    35  }
    36  
    37  // See the Crypto interface.
    38  func (c EthereumCrypto) DH(dhPair dr.DHPair, dhPub dr.Key) (dr.Key, error) {
    39  	tmpKey := dhPair.PrivateKey()
    40  	privateKey, err := ToECDSA(tmpKey)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	eciesPrivate := ecies.ImportECDSA(privateKey)
    46  
    47  	publicKey, err := DecompressPubkey(dhPub)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	eciesPublic := ecies.ImportECDSAPublic(publicKey)
    52  
    53  	key, err := eciesPrivate.GenerateShared(
    54  		eciesPublic,
    55  		16,
    56  		16,
    57  	)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	return key, nil
    63  }
    64  
    65  // See the Crypto interface.
    66  func (c EthereumCrypto) KdfRK(rk, dhOut dr.Key) (dr.Key, dr.Key, dr.Key) {
    67  	var (
    68  		// We can use a non-secret constant as the last argument
    69  		r   = hkdf.New(sha256.New, dhOut, rk, []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL"))
    70  		buf = make([]byte, 96)
    71  	)
    72  
    73  	rootKey := make(dr.Key, 32)
    74  	chainKey := make(dr.Key, 32)
    75  	headerKey := make(dr.Key, 32)
    76  
    77  	// The only error here is an entropy limit which won't be reached for such a short buffer.
    78  	_, _ = io.ReadFull(r, buf)
    79  
    80  	copy(rootKey, buf[:32])
    81  	copy(chainKey, buf[32:64])
    82  	copy(headerKey, buf[64:96])
    83  	return rootKey, chainKey, headerKey
    84  }
    85  
    86  // See the Crypto interface.
    87  func (c EthereumCrypto) KdfCK(ck dr.Key) (dr.Key, dr.Key) {
    88  	const (
    89  		ckInput = 15
    90  		mkInput = 16
    91  	)
    92  
    93  	chainKey := make(dr.Key, 32)
    94  	msgKey := make(dr.Key, 32)
    95  
    96  	h := hmac.New(sha256.New, ck)
    97  
    98  	_, _ = h.Write([]byte{ckInput})
    99  	copy(chainKey, h.Sum(nil))
   100  	h.Reset()
   101  
   102  	_, _ = h.Write([]byte{mkInput})
   103  	copy(msgKey, h.Sum(nil))
   104  
   105  	return chainKey, msgKey
   106  }
   107  
   108  // Encrypt uses a slightly different approach than in the algorithm specification:
   109  // it uses AES-256-CTR instead of AES-256-CBC for security, ciphertext length and implementation
   110  // complexity considerations.
   111  func (c EthereumCrypto) Encrypt(mk dr.Key, plaintext, ad []byte) ([]byte, error) {
   112  	encKey, authKey, iv := c.deriveEncKeys(mk)
   113  
   114  	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
   115  	copy(ciphertext, iv[:])
   116  
   117  	block, err := aes.NewCipher(encKey)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	stream := cipher.NewCTR(block, iv[:])
   123  	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
   124  
   125  	return append(ciphertext, c.computeSignature(authKey, ciphertext, ad)...), nil
   126  }
   127  
   128  // See the Crypto interface.
   129  func (c EthereumCrypto) Decrypt(mk dr.Key, authCiphertext, ad []byte) ([]byte, error) {
   130  	var (
   131  		l          = len(authCiphertext)
   132  		ciphertext = authCiphertext[:l-sha256.Size]
   133  		signature  = authCiphertext[l-sha256.Size:]
   134  	)
   135  
   136  	// Check the signature.
   137  	encKey, authKey, _ := c.deriveEncKeys(mk)
   138  
   139  	if s := c.computeSignature(authKey, ciphertext, ad); !bytes.Equal(s, signature) {
   140  		return nil, fmt.Errorf("invalid signature")
   141  	}
   142  
   143  	// Decrypt.
   144  	block, err := aes.NewCipher(encKey)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize])
   150  	plaintext := make([]byte, len(ciphertext[aes.BlockSize:]))
   151  
   152  	stream.XORKeyStream(plaintext, ciphertext[aes.BlockSize:])
   153  
   154  	return plaintext, nil
   155  }
   156  
   157  // deriveEncKeys derive keys for message encryption and decryption. Returns (encKey, authKey, iv, err).
   158  func (c EthereumCrypto) deriveEncKeys(mk dr.Key) (dr.Key, dr.Key, [16]byte) {
   159  	// First, derive encryption and authentication key out of mk.
   160  	salt := make([]byte, 32)
   161  	var (
   162  		r   = hkdf.New(sha256.New, mk, salt, []byte("pcwSByyx2CRdryCffXJwy7xgVZWtW5Sh"))
   163  		buf = make([]byte, 80)
   164  	)
   165  
   166  	encKey := make(dr.Key, 32)
   167  	authKey := make(dr.Key, 32)
   168  	var iv [16]byte
   169  
   170  	// The only error here is an entropy limit which won't be reached for such a short buffer.
   171  	_, _ = io.ReadFull(r, buf)
   172  
   173  	copy(encKey, buf[0:32])
   174  	copy(authKey, buf[32:64])
   175  	copy(iv[:], buf[64:80])
   176  	return encKey, authKey, iv
   177  }
   178  
   179  func (c EthereumCrypto) computeSignature(authKey, ciphertext, associatedData []byte) []byte {
   180  	h := hmac.New(sha256.New, authKey)
   181  	_, _ = h.Write(associatedData)
   182  	_, _ = h.Write(ciphertext)
   183  	return h.Sum(nil)
   184  }
   185  
   186  type DHPair struct {
   187  	PrvKey dr.Key
   188  	PubKey dr.Key
   189  }
   190  
   191  func (p DHPair) PrivateKey() dr.Key {
   192  	return p.PrvKey
   193  }
   194  
   195  func (p DHPair) PublicKey() dr.Key {
   196  	return p.PubKey
   197  }