Zeth - Zerocash on Ethereum  0.8
Reference implementation of the Zeth protocol by Clearmatics
encryption.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # Copyright (c) 2015-2022 Clearmatics Technologies Ltd
4 #
5 # SPDX-License-Identifier: LGPL-3.0+
6 
7 """
8 Encryption operations for Zeth notes. Supports an `encrypt` operation using
9 receivers public key, and a `decrypt` operation using the corresponding private
10 key. `decrypt` fails (except with negligible probability) if the ciphertext was
11 encrypted with a different public key.
12 
13 This implementation makes use of the `cryptography` library with OpenSSL
14 backend. For the avoidance of doubt, the implementation adheres to the
15 appropriate standards as follows. (links refer to specific versions of external
16 libraries, to ensure that line numbers are correct, but the descriptions are
17 expected to hold for all versions.)
18 
19 As described in [Bernstein06], private keys may be generated as 32 random bytes
20 with bits 0, 1 and 2 of the first byte cleared, bit 7 of the last byte cleared,
21 and bit 6 of the last byte set. This happens at key generation time. See:
22 
23  https://github.com/openssl/openssl/blob/be9d82bb35812ac65cd92316d1ae7c7c75efe9cf/crypto/ec/ecx_meth.c#L81
24 
25 [LangleyN18] describes Poly1305, including the requirement that the "r" value of
26 the key (r, s) be "clamped". Note that this clamping is carried out by the
27 cryptography library when the key is generated. See:
28 
29  https://github.com/openssl/openssl/blob/master/crypto/poly1305/poly1305.c#L143
30 
31 The specification of the ChaCha20 stream cipher in [LangleyN18] (page 10)
32 describes the inputs to the encryption functions as a 256-bit key, a 32-bit
33 counter and a 96-bit nonce. This differs slightly from the signature of the
34 encryption function in the cryptography library, which accepts a 256-bit key and
35 128-bit nonce. That is, no counter is mentioned leaving ambiguity as to whether
36 this data is processed exactly as described in [LangleyN18]. Internally, the
37 cryptography library treats the first 32-bit word of the nonce as a counter and
38 increments this as necessary in accordance with [LangleyN18]. See:
39 
40  https://github.com/openssl/openssl/blob/be9d82bb35812ac65cd92316d1ae7c7c75efe9cf/crypto/chacha/chacha_enc.c#L128
41  https://github.com/openssl/openssl/blob/be9d82bb35812ac65cd92316d1ae7c7c75efe9cf/crypto/evp/e_chacha20_poly1305.c#L95
42 
43 References:
44 
45 \\[Bernstein06]
46  "Curve25519:new Diffie-Hellman speed records"
47  Daniel J. Bernstein,
48  International Workshop on Public Key Cryptography, 2006,
49  <https://cr.yp.to/ecdh/curve25519-20060209.pdf>
50 
51 \\[LangleyN18]
52  "Chacha20 and poly1305 for ietf protocols."
53  Adam Langley and Yoav Nir,
54  RFC 8439, 2018,
55  <https://tools.ietf.org/html/rfc8439>
56 """
57 
58 from zeth.core.constants import NOTE_LENGTH_BYTES, bit_length_to_byte_length
59 from cryptography.hazmat.primitives.asymmetric.x25519 \
60  import X25519PrivateKey, X25519PublicKey
61 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
62 from cryptography.hazmat.backends import default_backend
63 from cryptography.hazmat.primitives import hashes, poly1305
64 from cryptography.hazmat.primitives.serialization import \
65  Encoding, PrivateFormat, PublicFormat, NoEncryption
66 from cryptography.exceptions import InvalidSignature \
67  as cryptography_InvalidSignature
68 from typing import Tuple, NewType
69 
70 
71 # Internal sizes for the scheme
72 _SYM_KEY_LENGTH: int = 256
73 _SYM_KEY_LENGTH_BYTES: int = bit_length_to_byte_length(_SYM_KEY_LENGTH)
74 
75 _MAC_KEY_LENGTH: int = 256
76 _MAC_KEY_LENGTH_BYTES: int = bit_length_to_byte_length(_MAC_KEY_LENGTH)
77 
78 _KEY_MATERIAL_LENGTH_BYTES: int = _SYM_KEY_LENGTH_BYTES + _MAC_KEY_LENGTH_BYTES
79 
80 _TAG_LENGTH: int = 128
81 _TAG_LENGTH_BYTES = bit_length_to_byte_length(_TAG_LENGTH)
82 
83 _SYM_NONCE_LENGTH: int = 128
84 _SYM_NONCE_LENGTH_BYTES: int = bit_length_to_byte_length(_SYM_NONCE_LENGTH)
85 
86 # Nonce as 4 32-bit words [counter, nonce, nonce, nonce] (see above).
87 _SYM_NONCE_VALUE: bytes = b"\x00" * _SYM_NONCE_LENGTH_BYTES
88 
89 # Key Derivation Tag "ZethEnc" utf-8 encoding
90 _KDF_TAG: bytes = b'ZethEnc'
91 
92 # Public sizes
93 EC_PRIVATE_KEY_LENGTH: int = 256
94 EC_PUBLIC_KEY_LENGTH: int = 256
95 EC_PUBLIC_KEY_LENGTH_BYTES: int = bit_length_to_byte_length(EC_PUBLIC_KEY_LENGTH)
96 ENCRYPTED_NOTE_LENGTH_BYTES: int = \
97  EC_PUBLIC_KEY_LENGTH_BYTES + NOTE_LENGTH_BYTES + _TAG_LENGTH_BYTES
98 
99 # Expose the exception type
100 InvalidSignature = cryptography_InvalidSignature
101 
102 # Represents a secret key for encryption
103 EncryptionSecretKey = NewType('EncryptionSecretKey', object)
104 
105 
106 def generate_encryption_secret_key() -> EncryptionSecretKey:
107  return EncryptionSecretKey(X25519PrivateKey.generate()) # type: ignore
108 
109 
110 def encode_encryption_secret_key(sk: EncryptionSecretKey) -> bytes:
111  return sk.private_bytes( # type: ignore
112  Encoding.Raw, PrivateFormat.Raw, NoEncryption())
113 
114 
115 def decode_encryption_secret_key(sk_bytes: bytes) -> EncryptionSecretKey:
116  return EncryptionSecretKey(
117  X25519PrivateKey.from_private_bytes(sk_bytes))
118 
119 
120 def encryption_secret_key_as_hex(sk: EncryptionSecretKey) -> str:
121  return encode_encryption_secret_key(sk).hex() # type: ignore
122 
123 
124 def encryption_secret_key_from_hex(pk_str: str) -> EncryptionSecretKey:
125  return EncryptionSecretKey(
126  X25519PrivateKey.from_private_bytes(bytes.fromhex(pk_str)))
127 
128 
129 # Public key for decryption
130 EncryptionPublicKey = NewType('EncryptionPublicKey', object)
131 
132 
134  enc_secret: EncryptionSecretKey) -> EncryptionPublicKey:
135  return enc_secret.public_key() # type: ignore
136 
137 
138 def encode_encryption_public_key(pk: EncryptionPublicKey) -> bytes:
139  return pk.public_bytes(Encoding.Raw, PublicFormat.Raw) # type: ignore
140 
141 
142 def decode_encryption_public_key(pk_data: bytes) -> EncryptionPublicKey:
143  return EncryptionPublicKey(X25519PublicKey.from_public_bytes(pk_data))
144 
145 
146 def encryption_public_key_as_hex(pk: EncryptionPublicKey) -> str:
147  return encode_encryption_public_key(pk).hex()
148 
149 
150 def encryption_public_key_from_hex(pk_str: str) -> EncryptionPublicKey:
151  return decode_encryption_public_key(bytes.fromhex(pk_str))
152 
153 
155  """
156  Key-pair for encrypting joinsplit notes.
157  """
158  def __init__(self, k_sk: EncryptionSecretKey, k_pk: EncryptionPublicKey):
159  self.k_pk: EncryptionPublicKey = k_pk
160  self.k_sk: EncryptionSecretKey = k_sk
161 
162 
163 def generate_encryption_keypair() -> EncryptionKeyPair:
166 
167 
168 def encrypt(message: bytes, pk_receiver: EncryptionPublicKey) -> bytes:
169  """
170  Encrypts a string message under a ec25519 public key by using a custom
171  dhaes-based scheme. See: https://eprint.iacr.org/1999/007
172  """
173  assert \
174  len(message) == NOTE_LENGTH_BYTES, \
175  f"expected message length {NOTE_LENGTH_BYTES}, saw {len(message)}"
176 
177  # Generate ephemeral keypair
178  eph_keypair = generate_encryption_keypair()
179 
180  # Compute shared secret and eph key
181  shared_key = _exchange(eph_keypair.k_sk, pk_receiver)
182  pk_sender_bytes = encode_encryption_public_key(eph_keypair.k_pk)
183 
184  # Generate key material
185  sym_key, mac_key = _kdf(pk_sender_bytes, shared_key)
186 
187  # Generate symmetric ciphertext
188  # Chacha encryption
189  algorithm = algorithms.ChaCha20(sym_key, _SYM_NONCE_VALUE)
190  cipher = Cipher(algorithm, mode=None, backend=default_backend())
191  encryptor = cipher.encryptor()
192  sym_ciphertext = encryptor.update(message)
193 
194  # Generate mac
195  mac = poly1305.Poly1305(mac_key)
196  mac.update(sym_ciphertext)
197  tag = mac.finalize()
198 
199  # Arrange ciphertext
200  return pk_sender_bytes+sym_ciphertext+tag
201 
202 
204  encrypted_message: bytes,
205  sk_receiver: EncryptionSecretKey) -> bytes:
206  """
207  Decrypts a NOTE_LENGTH-byte message by using valid ec25519 private key
208  objects. See: https://pynacl.readthedocs.io/en/stable/public/
209  """
210  assert \
211  len(encrypted_message) == ENCRYPTED_NOTE_LENGTH_BYTES, \
212  "encrypted_message byte-length must be: "+str(ENCRYPTED_NOTE_LENGTH_BYTES)
213 
214  assert(isinstance(sk_receiver, X25519PrivateKey)), \
215  f"PrivateKey: {sk_receiver} ({type(sk_receiver)})"
216 
217  # Compute shared secret
218  pk_sender_bytes = encrypted_message[:EC_PUBLIC_KEY_LENGTH_BYTES]
219  pk_sender = decode_encryption_public_key(pk_sender_bytes)
220  shared_key = _exchange(sk_receiver, pk_sender)
221 
222  # Generate key material and recover keys
223  sym_key, mac_key = _kdf(pk_sender_bytes, shared_key)
224 
225  # ct_sym and mac
226  ct_sym = encrypted_message[
227  EC_PUBLIC_KEY_LENGTH_BYTES:
228  EC_PUBLIC_KEY_LENGTH_BYTES + NOTE_LENGTH_BYTES]
229  tag = encrypted_message[
230  EC_PUBLIC_KEY_LENGTH_BYTES + NOTE_LENGTH_BYTES:
231  EC_PUBLIC_KEY_LENGTH_BYTES + NOTE_LENGTH_BYTES + _TAG_LENGTH_BYTES]
232 
233  # Verify the mac
234  mac = poly1305.Poly1305(mac_key)
235  mac.update(ct_sym)
236  mac.verify(tag)
237 
238  # Decrypt sym ciphertext
239  algorithm = algorithms.ChaCha20(sym_key, _SYM_NONCE_VALUE)
240  cipher = Cipher(algorithm, mode=None, backend=default_backend())
241  decryptor = cipher.decryptor()
242  message = decryptor.update(ct_sym)
243 
244  return message
245 
246 
247 def _exchange(sk: EncryptionSecretKey, pk: EncryptionPublicKey) -> bytes:
248  return sk.exchange(pk) # type: ignore
249 
250 
251 def _kdf(eph_pk: bytes, shared_key: bytes) -> Tuple[bytes, bytes]:
252  """
253  Key derivation function
254  """
255  # Hashing
256  key_material = hashes.Hash(
257  hashes.BLAKE2b(64), # type: ignore
258  backend=default_backend())
259  key_material.update(_KDF_TAG)
260  key_material.update(eph_pk)
261  key_material.update(shared_key)
262  key_material_bytes = key_material.finalize()
263  assert len(key_material_bytes) == _KEY_MATERIAL_LENGTH_BYTES
264  return \
265  key_material_bytes[:_SYM_KEY_LENGTH_BYTES], \
266  key_material_bytes[_SYM_KEY_LENGTH_BYTES:]
zeth.core.encryption.encode_encryption_public_key
bytes encode_encryption_public_key(EncryptionPublicKey pk)
Definition: encryption.py:138
test_commands.mock.str
str
Definition: mock.py:18
zeth.core.encryption.decode_encryption_secret_key
EncryptionSecretKey decode_encryption_secret_key(bytes sk_bytes)
Definition: encryption.py:115
zeth.core.encryption.encryption_secret_key_from_hex
EncryptionSecretKey encryption_secret_key_from_hex(str pk_str)
Definition: encryption.py:124
zeth.core.constants
Definition: constants.py:1
zeth.core.encryption.encrypt
bytes encrypt(bytes message, EncryptionPublicKey pk_receiver)
Definition: encryption.py:168
zeth.core.encryption.generate_encryption_secret_key
EncryptionSecretKey generate_encryption_secret_key()
Definition: encryption.py:106
zeth.core.encryption.encode_encryption_secret_key
bytes encode_encryption_secret_key(EncryptionSecretKey sk)
Definition: encryption.py:110
zeth.core.encryption.encryption_public_key_as_hex
str encryption_public_key_as_hex(EncryptionPublicKey pk)
Definition: encryption.py:146
zeth.core.encryption.EncryptionPublicKey
EncryptionPublicKey
Definition: encryption.py:130
zeth.core.encryption.EncryptionSecretKey
EncryptionSecretKey
Definition: encryption.py:103
zeth.core.encryption.decode_encryption_public_key
EncryptionPublicKey decode_encryption_public_key(bytes pk_data)
Definition: encryption.py:142
zeth.core.encryption.get_encryption_public_key
EncryptionPublicKey get_encryption_public_key(EncryptionSecretKey enc_secret)
Definition: encryption.py:133
zeth.core.constants.bit_length_to_byte_length
int bit_length_to_byte_length(int bit_length)
Definition: constants.py:18
zeth.core.encryption.EncryptionKeyPair
Definition: encryption.py:154
zeth.core.encryption.decrypt
bytes decrypt(bytes encrypted_message, EncryptionSecretKey sk_receiver)
Definition: encryption.py:203
zeth.core.encryption.EncryptionKeyPair.__init__
def __init__(self, EncryptionSecretKey k_sk, EncryptionPublicKey k_pk)
Definition: encryption.py:158
zeth.core.encryption.generate_encryption_keypair
EncryptionKeyPair generate_encryption_keypair()
Definition: encryption.py:163
zeth.core.encryption.encryption_secret_key_as_hex
str encryption_secret_key_as_hex(EncryptionSecretKey sk)
Definition: encryption.py:120
zeth.core.encryption.encryption_public_key_from_hex
EncryptionPublicKey encryption_public_key_from_hex(str pk_str)
Definition: encryption.py:150