@@ -62,7 +62,7 @@ def generate_and_send_pads(user_data, user_data_lock, contact_id: str, ui_queue)
6262 contact_mceliece_public_key = user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["contact_public_keys" ][CLASSIC_MCELIECE_8_F_NAME ]
6363 our_lt_private_key = user_data ["contacts" ][contact_id ]["lt_sign_keys" ]["our_keys" ]["private_key" ]
6464
65- our_strand_key = user_data ["contacts" ][contact_id ]["our_strand_key " ]
65+ our_next_strand_key = user_data ["contacts" ][contact_id ]["our_next_strand_key " ]
6666
6767 our_next_strand_nonce = user_data ["contacts" ][contact_id ]["our_next_strand_nonce" ]
6868
@@ -78,10 +78,13 @@ def generate_and_send_pads(user_data, user_data_lock, contact_id: str, ui_queue)
7878
7979 otp_batch_signature = create_signature (ML_DSA_87_NAME , kyber_ciphertext_blob + mceliece_ciphertext_blob , our_lt_private_key )
8080
81- our_new_strand_nonce = sha3_512 (secrets .token_bytes (XCHACHA20POLY1305_NONCE_LEN ))[:XCHACHA20POLY1305_NONCE_LEN ]
81+ # Here, the strandkey is actually just added to make messages structure uniform and easier to process in implementations
82+ # once contact receives this, he will save this new random key, then, process the batch, and save the new key derived from the batch.
83+
84+ new_strand_nonce = sha3_512 (secrets .token_bytes (XCHACHA20POLY1305_NONCE_LEN ))[:XCHACHA20POLY1305_NONCE_LEN ]
8285 _ , ciphertext_blob = encrypt_xchacha20poly1305 (
83- our_strand_key ,
84- MSG_TYPES ["MSG_BATCH" ] + our_new_strand_nonce + otp_batch_signature + kyber_ciphertext_blob + mceliece_ciphertext_blob + xchacha_shared_secrets ,
86+ our_next_strand_key ,
87+ sha3_512 ( secrets . token_bytes ( 32 ))[: 32 ] + new_strand_nonce + MSG_TYPES ["MSG_BATCH" ] + otp_batch_signature + kyber_ciphertext_blob + mceliece_ciphertext_blob + xchacha_shared_secrets ,
8588 nonce = our_next_strand_nonce
8689 )
8790
@@ -98,17 +101,19 @@ def generate_and_send_pads(user_data, user_data_lock, contact_id: str, ui_queue)
98101 ui_queue .put ({"type" : "showerror" , "title" : "Error" , "message" : "Failed to send our one-time-pads key batch to the server" })
99102 return False
100103
104+ # XOR shared secrets together for hybrid encryption
101105 pads , _ = one_time_pad (kyber_shared_secrets , mceliece_shared_secrets )
102106 pads , _ = one_time_pad (pads , xchacha_shared_secrets )
103107
104-
105- our_strand_key = pads [:32 ]
108+ # Derive key from pad + truncate it.
109+ new_strand_key = pads [:32 ]
110+ otp_pads = pads [32 :]
106111
107112 # We update & save only at the end, so if request fails, we do not desync our state.
108113 with user_data_lock :
109- user_data ["contacts" ][contact_id ]["our_next_strand_nonce" ] = our_new_strand_nonce
110- user_data ["contacts" ][contact_id ]["our_strand_key " ] = our_strand_key
111- user_data ["contacts" ][contact_id ]["our_pads" ] = pads [ 32 :]
114+ user_data ["contacts" ][contact_id ]["our_next_strand_nonce" ] = new_strand_nonce
115+ user_data ["contacts" ][contact_id ]["our_next_strand_key " ] = new_strand_key
116+ user_data ["contacts" ][contact_id ]["our_pads" ] = otp_pads
112117
113118
114119 save_account_data (user_data , user_data_lock )
@@ -139,6 +144,8 @@ def send_message_processor(user_data, user_data_lock, contact_id: str, message:
139144
140145 our_pads = user_data ["contacts" ][contact_id ]["our_pads" ]
141146
147+ if user_data ["contacts" ][contact_id ]["locked" ]:
148+ return
142149
143150
144151 if contact_kyber_public_key is None or contact_mceliece_public_key is None :
@@ -189,21 +196,23 @@ def send_message_processor(user_data, user_data_lock, contact_id: str, message:
189196 # because a malicious server could make our requests fail to force us to re-use the same pad for our next message
190197 # which would break all of our security
191198
192- our_new_strand_nonce = sha3_512 (secrets .token_bytes (XCHACHA20POLY1305_NONCE_LEN ))[:XCHACHA20POLY1305_NONCE_LEN ]
199+ new_strand_key = sha3_512 (secrets .token_bytes (32 ))[:32 ]
200+ new_strand_nonce = sha3_512 (secrets .token_bytes (XCHACHA20POLY1305_NONCE_LEN ))[:XCHACHA20POLY1305_NONCE_LEN ]
193201
194202 with user_data_lock :
195203 user_data ["contacts" ][contact_id ]["our_pads" ] = user_data ["contacts" ][contact_id ]["our_pads" ][len (message_encrypted ):]
196204
197- our_strand_key = user_data ["contacts" ][contact_id ]["our_strand_key" ]
205+ our_next_strand_key = user_data ["contacts" ][contact_id ]["our_next_strand_key" ]
206+ user_data ["contacts" ][contact_id ]["our_next_strand_key" ] = new_strand_key
198207
199208 our_next_strand_nonce = user_data ["contacts" ][contact_id ]["our_next_strand_nonce" ]
200- user_data ["contacts" ][contact_id ]["our_next_strand_nonce" ] = our_new_strand_nonce
209+ user_data ["contacts" ][contact_id ]["our_next_strand_nonce" ] = new_strand_nonce
201210
202211 save_account_data (user_data , user_data_lock )
203212
204213 _ , ciphertext_blob = encrypt_xchacha20poly1305 (
205- our_strand_key ,
206- MSG_TYPES ["MSG_NEW" ] + our_new_strand_nonce + message_encrypted ,
214+ our_next_strand_key ,
215+ new_strand_key + new_strand_nonce + MSG_TYPES ["MSG_NEW" ] + message_encrypted ,
207216 nonce = our_next_strand_nonce
208217 )
209218
@@ -251,7 +260,7 @@ def messages_data_handler(user_data: dict, user_data_lock, user_data_copied: dic
251260 logger .error ("Contact (%s) per-contact ML-DSA-87 public key is missing! Skipping message.." , contact_id )
252261 return
253262
254- if user_data_copied ["contacts" ][contact_id ]["contact_strand_key " ] is None :
263+ if user_data_copied ["contacts" ][contact_id ]["contact_next_strand_key " ] is None :
255264 logger .error ("Contact (%s) strand key key is missing! Skipping message..." , contact_id )
256265 return
257266
@@ -262,14 +271,14 @@ def messages_data_handler(user_data: dict, user_data_lock, user_data_copied: dic
262271
263272 # /32 because KEM shared_secret is 32 bytes, /64 because sha3_512 output is 64 bytes
264273
265- if len (msgs_plaintext ) != ( (ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN ) * (OTP_PAD_SIZE // 32 )) + (64 * (OTP_PAD_SIZE // 64 )) + ML_DSA_87_SIGN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1 :
274+ if len (msgs_plaintext ) != ( (ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN ) * (OTP_PAD_SIZE // 32 )) + (64 * (OTP_PAD_SIZE // 64 )) + ML_DSA_87_SIGN_LEN + 1 :
266275 logger .error ("Contact (%s) gave us a otp batch message request with malformed strand plaintext length (%d)" , contact_id , len (msgs_plaintext ))
267276 return
268277
269- otp_hashchain_signature = msgs_plaintext [1 + XCHACHA20POLY1305_NONCE_LEN : ML_DSA_87_SIGN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1 ]
270- otp_hashchain_ciphertext = msgs_plaintext [ML_DSA_87_SIGN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1 : ML_DSA_87_SIGN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1 + ((ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN ) * (OTP_PAD_SIZE // 32 ))]
278+ otp_hashchain_signature = msgs_plaintext [1 : ML_DSA_87_SIGN_LEN + 1 ]
279+ otp_hashchain_ciphertext = msgs_plaintext [ML_DSA_87_SIGN_LEN + 1 : ML_DSA_87_SIGN_LEN + 1 + ((ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN ) * (OTP_PAD_SIZE // 32 ))]
271280
272- xchacha_pads = msgs_plaintext [ML_DSA_87_SIGN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1 + ((ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN ) * (OTP_PAD_SIZE // 32 )):]
281+ xchacha_pads = msgs_plaintext [ML_DSA_87_SIGN_LEN + 1 + ((ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN ) * (OTP_PAD_SIZE // 32 )):]
273282
274283 try :
275284 valid_signature = verify_signature (ML_DSA_87_NAME , otp_hashchain_ciphertext , otp_hashchain_signature , contact_public_key )
@@ -298,14 +307,14 @@ def messages_data_handler(user_data: dict, user_data_lock, user_data_copied: dic
298307 contact_pads , _ = one_time_pad (contact_kyber_pads , contact_mceliece_pads )
299308 contact_pads , _ = one_time_pad (contact_pads , xchacha_pads )
300309
301- contact_strand_key = contact_pads [:32 ]
310+ contact_next_strand_key = contact_pads [:32 ]
302311 contact_pads = contact_pads [32 :]
303312
304313
305314 with user_data_lock :
306315 user_data ["contacts" ][contact_id ]["contact_pads" ] = contact_pads
307316
308- user_data ["contacts" ][contact_id ]["contact_strand_key " ] = contact_strand_key
317+ user_data ["contacts" ][contact_id ]["contact_next_strand_key " ] = contact_next_strand_key
309318
310319 user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["our_keys" ][CLASSIC_MCELIECE_8_F_NAME ]["rotation_counter" ] += 1
311320
@@ -331,12 +340,12 @@ def messages_data_handler(user_data: dict, user_data_lock, user_data_copied: dic
331340 elif bytes ([msgs_plaintext [0 ]]) == MSG_TYPES ["MSG_NEW" ]:
332341 logger .debug ("Received a new message from contact (%s)." , contact_id )
333342
334- if len (msgs_plaintext ) < OTP_MAX_BUCKET + XCHACHA20POLY1305_NONCE_LEN + 1 :
343+ if len (msgs_plaintext ) < OTP_MAX_BUCKET + 1 :
335344 logger .error ("Contact (%s) gave us a message request with malformed strand plaintext length (%d)" , contact_id , len (msgs_plaintext ))
336345 return
337346
338347
339- message_encrypted = msgs_plaintext [XCHACHA20POLY1305_NONCE_LEN + 1 :]
348+ message_encrypted = msgs_plaintext [1 :]
340349
341350
342351 with user_data_lock :
@@ -378,5 +387,3 @@ def messages_data_handler(user_data: dict, user_data_lock, user_data_copied: dic
378387 logger .error ("Received unknown message type (%d)" , msgs_plaintext [0 ])
379388 return
380389
381- with user_data_lock :
382- user_data ["contacts" ][contact_id ]["contact_next_strand_nonce" ] = msgs_plaintext [1 : XCHACHA20POLY1305_NONCE_LEN + 1 ]
0 commit comments