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.
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.)
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:
23 https://github.com/openssl/openssl/blob/be9d82bb35812ac65cd92316d1ae7c7c75efe9cf/crypto/ec/ecx_meth.c#L81
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:
29 https://github.com/openssl/openssl/blob/master/crypto/poly1305/poly1305.c#L143
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:
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
46 "Curve25519:new Diffie-Hellman speed records"
48 International Workshop on Public Key Cryptography, 2006,
49 <https://cr.yp.to/ecdh/curve25519-20060209.pdf>
52 "Chacha20 and poly1305 for ietf protocols."
53 Adam Langley and Yoav Nir,
55 <https://tools.ietf.org/html/rfc8439>
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
72 _SYM_KEY_LENGTH: int = 256
75 _MAC_KEY_LENGTH: int = 256
78 _KEY_MATERIAL_LENGTH_BYTES: int = _SYM_KEY_LENGTH_BYTES + _MAC_KEY_LENGTH_BYTES
80 _TAG_LENGTH: int = 128
83 _SYM_NONCE_LENGTH: int = 128
87 _SYM_NONCE_VALUE: bytes = b
"\x00" * _SYM_NONCE_LENGTH_BYTES
90 _KDF_TAG: bytes = b
'ZethEnc'
93 EC_PRIVATE_KEY_LENGTH: int = 256
94 EC_PUBLIC_KEY_LENGTH: int = 256
96 ENCRYPTED_NOTE_LENGTH_BYTES: int = \
97 EC_PUBLIC_KEY_LENGTH_BYTES + NOTE_LENGTH_BYTES + _TAG_LENGTH_BYTES
100 InvalidSignature = cryptography_InvalidSignature
103 EncryptionSecretKey = NewType(
'EncryptionSecretKey', object)
111 return sk.private_bytes(
112 Encoding.Raw, PrivateFormat.Raw, NoEncryption())
117 X25519PrivateKey.from_private_bytes(sk_bytes))
126 X25519PrivateKey.from_private_bytes(bytes.fromhex(pk_str)))
130 EncryptionPublicKey = NewType(
'EncryptionPublicKey', object)
134 enc_secret: EncryptionSecretKey) -> EncryptionPublicKey:
135 return enc_secret.public_key()
139 return pk.public_bytes(Encoding.Raw, PublicFormat.Raw)
156 Key-pair for encrypting joinsplit notes.
158 def __init__(self, k_sk: EncryptionSecretKey, k_pk: EncryptionPublicKey):
159 self.k_pk: EncryptionPublicKey = k_pk
160 self.k_sk: EncryptionSecretKey = k_sk
168 def encrypt(message: bytes, pk_receiver: EncryptionPublicKey) -> bytes:
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
174 len(message) == NOTE_LENGTH_BYTES, \
175 f
"expected message length {NOTE_LENGTH_BYTES}, saw {len(message)}"
181 shared_key = _exchange(eph_keypair.k_sk, pk_receiver)
185 sym_key, mac_key = _kdf(pk_sender_bytes, shared_key)
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)
195 mac = poly1305.Poly1305(mac_key)
196 mac.update(sym_ciphertext)
200 return pk_sender_bytes+sym_ciphertext+tag
204 encrypted_message: bytes,
205 sk_receiver: EncryptionSecretKey) -> bytes:
207 Decrypts a NOTE_LENGTH-byte message by using valid ec25519 private key
208 objects. See: https://pynacl.readthedocs.io/en/stable/public/
211 len(encrypted_message) == ENCRYPTED_NOTE_LENGTH_BYTES, \
212 "encrypted_message byte-length must be: "+
str(ENCRYPTED_NOTE_LENGTH_BYTES)
214 assert(isinstance(sk_receiver, X25519PrivateKey)), \
215 f
"PrivateKey: {sk_receiver} ({type(sk_receiver)})"
218 pk_sender_bytes = encrypted_message[:EC_PUBLIC_KEY_LENGTH_BYTES]
220 shared_key = _exchange(sk_receiver, pk_sender)
223 sym_key, mac_key = _kdf(pk_sender_bytes, shared_key)
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]
234 mac = poly1305.Poly1305(mac_key)
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)
247 def _exchange(sk: EncryptionSecretKey, pk: EncryptionPublicKey) -> bytes:
248 return sk.exchange(pk)
251 def _kdf(eph_pk: bytes, shared_key: bytes) -> Tuple[bytes, bytes]:
253 Key derivation function
256 key_material = hashes.Hash(
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
265 key_material_bytes[:_SYM_KEY_LENGTH_BYTES], \
266 key_material_bytes[_SYM_KEY_LENGTH_BYTES:]