Ethereum Classic (ETC) uses a multistep protocol to send private messages between network nodes. This is referred to as the Elliptic Curve Integrated Encryption Scheme (ECIES).

Procedure

The first ETC ECIES step is to create an “ephemeral” private and public key pair. The second step is to create a shared secret by multiplying the ephemeral private key and the public key of the other node. See the Elliptic Curve Diffie Hellman algorithm for further details on this. The third step is to use this shared secret to derive an AES (Advanced Encryption Standard) key and an HMAC (hash based message authentication code) secret using the ETC key derivation function. The fourth step is to encrypt the message which involves creating a random “initialization vector”. The fifth step is to create an HMAC for the initialization vector and the encrypted message. The last step is to send the following components to the other node:

1. ephemeral public key 2. initialization vector 3. encrypted message 4. HMAC



ETC uses AES in “counter mode” with 128 bit keys. The initialization vector and its derivatives are encrypted then exclusive OR’d with the message to produce the encrypted version.

Implentation

An example Python ETC ECIES implementation is provided below which uses the Python Cryptography Toolkit (pycrypto) package:

#!/usr/bin/env python3 import Crypto.Cipher.AES import Crypto.Util.Counter import hashlib import random N = 115792089237316195423570985008687907852837564279074904382605163141518161494337 P = 115792089237316195423570985008687907853269984665640564039457584007908834671663 Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240 Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424 PUBLIC_KEY_SIZE = 64 SHA256_BLOCK_SIZE = 64 AES_KEY_SIZE = 16 AES_BLOCK_SIZE = 16 HMAC_SIZE = 32 NUM_BITS_PER_BYTE = 8 def invert(number, modulus): """ Finds the inverses of natural numbers. """ result = 1 power = number for e in bin(modulus - 2)[2:][::-1]: if int(e): result = (result * power) % modulus power = (power ** 2) % modulus return result def add(pair_1, pair_2): """ Finds the sums of two pairs of natural numbers. """ if pair_1 == [0, 0]: result = pair_2 elif pair_2 == [0, 0]: result = pair_1 else: if pair_1 == pair_2: temp = 3 * pair_1[0] ** 2 temp = (temp * invert(2 * pair_1[1], P)) % P else: temp = pair_2[1] - pair_1[1] temp = (temp * invert(pair_2[0] - pair_1[0], P)) % P result = (temp ** 2 - pair_1[0] - pair_2[0]) % P result = [result, (temp * (pair_1[0] - result) - pair_1[1]) % P] return result def multiply(number, pair): """ Finds the products of natural numbers and pairs of natural numbers. """ result = [0, 0] power = pair[:] for e in bin(number)[2:][::-1]: if int(e): result = add(result, power) power = add(power, power) return result def make_private_key(): """ Creates random private keys. """ return random.randint(1, N) def get_public_key(private_key): """ Calculates public keys from private keys. """ return multiply(private_key, (Gx, Gy)) def get_shared_secret(private_key, public_key): """ Calculates shared secrets from public and private keys. """ return multiply(private_key, public_key)[0] def kdf(secret): """ Implements the key derivation function. """ secret = secret.to_bytes(PUBLIC_KEY_SIZE // 2, "big") key = hashlib.sha256(b"\x00\x00\x00\x01" + secret).digest() return key def hmac(secret, message): """ Calculates hash based message authenticaion codes (HMACs). """ secret = hashlib.sha256(secret).digest() secret = secret.ljust(SHA256_BLOCK_SIZE, b"\x00") ipad = SHA256_BLOCK_SIZE * bytes.fromhex("36") opad = SHA256_BLOCK_SIZE * bytes.fromhex("5c") key_1 = bytes([x ^ y for x, y in zip(secret, ipad)]) key_2 = bytes([x ^ y for x, y in zip(secret, opad)]) hash_ = hashlib.sha256(key_1 + message).digest() hmac_ = hashlib.sha256(key_2 + hash_).digest() return hmac_ def aes_ctr_encrypt(message, key, iv): """ Encrypts messages using AES in counter mode. """ iv = int.from_bytes(iv, 'big') counter = Crypto.Util.Counter.new(AES_KEY_SIZE * NUM_BITS_PER_BYTE, initial_value = iv) aes_ctr = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CTR, counter = counter) return aes_ctr.encrypt(message) def ecies_send(message, receiv_pub_key): """ Creates the ECIES byte string to send a message. """ eph_priv_key = make_private_key() eph_pub_key = get_public_key(eph_priv_key) eph_pub_key[0] = eph_pub_key[0].to_bytes(PUBLIC_KEY_SIZE // 2, "big") eph_pub_key[1] = eph_pub_key[1].to_bytes(PUBLIC_KEY_SIZE // 2, "big") eph_pub_key = b"\x04" + eph_pub_key[0] + eph_pub_key[1] shared_secret = get_shared_secret(eph_priv_key, receiv_pub_key) kdf_key = kdf(shared_secret) aes_key = kdf_key[:AES_KEY_SIZE] hmac_secret = kdf_key[AES_KEY_SIZE:] iv = random.getrandbits(AES_BLOCK_SIZE * NUM_BITS_PER_BYTE) iv = iv.to_bytes(AES_BLOCK_SIZE, "big") ciphertext = aes_ctr_encrypt(message, aes_key, iv) hmac_ = hmac(hmac_secret, iv + ciphertext) return eph_pub_key + iv + ciphertext + hmac_ def ecies_receive(received, receiv_priv_key): """ Extracts the message from an ECIES byte string. """ eph_pub_key = received[:PUBLIC_KEY_SIZE + 1] eph_pub_key = [eph_pub_key[1:][:PUBLIC_KEY_SIZE // 2], eph_pub_key[1:][PUBLIC_KEY_SIZE // 2:]] eph_pub_key = [int.from_bytes(e, "big") for e in eph_pub_key] iv_index = PUBLIC_KEY_SIZE + 1 iv = received[iv_index:iv_index + AES_BLOCK_SIZE] ciphertext = received[iv_index + AES_BLOCK_SIZE:-HMAC_SIZE] hmac_ = received[-HMAC_SIZE:] shared_secret = get_shared_secret(receiv_priv_key, eph_pub_key) kdf_key = kdf(shared_secret) aes_key = kdf_key[:AES_KEY_SIZE] hmac_secret = kdf_key[AES_KEY_SIZE:] message = b"" if hmac(hmac_secret, iv + ciphertext) == hmac_: message += aes_ctr_encrypt(ciphertext, aes_key, iv) return message



Here is an example of its usage in a Python shell:

>>> receiv_priv_key = make_private_key() >>> receiv_priv_key 65220784268995169636487104126103071511455089901114970914953057647529653418334 >>> receiv_pub_key = get_public_key(receiv_priv_key) >>> receiv_pub_key [27571019357111177175074932706008560366518621617269680530116383769747086735803, 6491692355663560906178933703971523490656680637639444902444063317701241768362] >>> message = b"This is the message." >>> sent = ecies_send(message, receiv_pub_key) >>> sent b"\x04\xde\x02\x8a\x1b\x9er\x99s\xc8\xe5\x08m\xd3\[email protected]\xc6,\x04\xe6%\x9e\xd3Y\xc1k\xc1X\x9c\xef\xfb^\x86K\xbb\xef\xb6]\xbc5\xec\rC|\xc7\xc2\xf5\x16\xa4s%_x7\xd64W\xb2<\x18\x03#t\xc4S\x16Z\xeb>j\x1f=\xd8\xb5_\x91v\xf4'\xe9\x88\xb5\x0b\x83\xedr\xd5`\xd8\x9e\x06`\xfc\xa1\xd6\xf8[~\x01WwT[2\x08\xd3\x11\xf3v\xb2\\\xd2\xc7\x87\t\xa0J\xc7\x17\xef\xd5\xedt\xf4\xcave\x02\xf9\xed\xdf\x95\x82" >>> ecies_receive(sent, receiv_priv_key) b'This is the message.'

Feedback

Feel free to leave any comments or questions below. You can also contact me by email at [email protected] or by clicking any of these icons:

Acknowledgements

I would like to thank IOHK (Input Output Hong Kong) for funding this effort.

License

This work is licensed under the Creative Commons Attribution ShareAlike 4.0 International License.