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\xfa@\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.
Congratulations @cseberino! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Vote for @Steemitboard as a witness to get one more award and increased upvotes!