Skip to content

Commit 2adeed7

Browse files
committed
feat: add strandlock protocol support
1 parent cd8d2ce commit 2adeed7

File tree

3 files changed

+20
-9
lines changed

3 files changed

+20
-9
lines changed

logic/background_worker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def background_worker(user_data, user_data_lock, ui_queue, stop_flag):
160160
else:
161161
logger.error(
162162
"Skipping data with unknown data type (%d) from contact (%s)...",
163-
bytes([blob_plaintext[0]]),
163+
blob_plaintext[0],
164164
sender
165165
)
166166

logic/message.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,19 @@ def generate_and_send_pads(user_data, user_data_lock, contact_id: str, ui_queue)
7171
kyber_ciphertext_blob , kyber_shared_secrets = generate_shared_secrets(contact_kyber_public_key, ML_KEM_1024_NAME)
7272
mceliece_ciphertext_blob, mceliece_shared_secrets = generate_shared_secrets(contact_mceliece_public_key, CLASSIC_MCELIECE_8_F_NAME)
7373

74+
xchacha_shared_secrets = b''
75+
while len(xchacha_shared_secrets) < OTP_PAD_SIZE:
76+
xchacha_shared_secrets += sha3_512(secrets.token_bytes(64))
77+
78+
7479
otp_batch_signature = create_signature(ML_DSA_87_NAME, kyber_ciphertext_blob + mceliece_ciphertext_blob, our_lt_private_key)
7580

7681
hash_chain_seed = sha3_512(secrets.token_bytes(MESSAGE_HASH_CHAIN_LEN))
7782

7883
our_new_strand_nonce = sha3_512(secrets.token_bytes(XCHACHA20POLY1305_NONCE_LEN))[:XCHACHA20POLY1305_NONCE_LEN]
7984
_, ciphertext_blob = encrypt_xchacha20poly1305(
8085
our_strand_key,
81-
MSG_TYPE + b"\x00" + our_new_strand_nonce + hash_chain_seed + otp_batch_signature + kyber_ciphertext_blob + mceliece_ciphertext_blob,
86+
MSG_TYPE + b"\x00" + our_new_strand_nonce + hash_chain_seed + otp_batch_signature + kyber_ciphertext_blob + mceliece_ciphertext_blob + xchacha_shared_secrets,
8287
nonce = our_next_strand_nonce
8388
)
8489

@@ -95,6 +100,7 @@ def generate_and_send_pads(user_data, user_data_lock, contact_id: str, ui_queue)
95100
return False
96101

97102
pads, _ = one_time_pad(kyber_shared_secrets, mceliece_shared_secrets)
103+
pads, _ = one_time_pad(pads, xchacha_shared_secrets)
98104

99105

100106
our_strand_key = pads[:32]
@@ -265,12 +271,16 @@ def messages_data_handler(user_data: dict, user_data_lock, user_data_copied: dic
265271
if msgs_plaintext[0] == 0:
266272
logger.debug("Received a new OTP pads batch from contact (%s).", contact_id)
267273

268-
if len(msgs_plaintext) != ( (ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN) * (OTP_PAD_SIZE // 32)) + ML_DSA_87_SIGN_LEN + MESSAGE_HASH_CHAIN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1:
274+
# /32 because KEM shared_secret is 32 bytes, /64 because sha3_512 output is 64 bytes
275+
276+
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 + MESSAGE_HASH_CHAIN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1:
269277
logger.error("Contact (%s) gave us a otp batch message request with malformed strand plaintext length (%d)", contact_id, len(msgs_plaintext))
270278
return
271279

272280
otp_hashchain_signature = msgs_plaintext[1 + MESSAGE_HASH_CHAIN_LEN + XCHACHA20POLY1305_NONCE_LEN: MESSAGE_HASH_CHAIN_LEN + ML_DSA_87_SIGN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1]
273-
otp_hashchain_ciphertext = msgs_plaintext[ML_DSA_87_SIGN_LEN + MESSAGE_HASH_CHAIN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1:]
281+
otp_hashchain_ciphertext = msgs_plaintext[ML_DSA_87_SIGN_LEN + MESSAGE_HASH_CHAIN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1: ML_DSA_87_SIGN_LEN + MESSAGE_HASH_CHAIN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1 + ((ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN) * (OTP_PAD_SIZE // 32))]
282+
283+
xchacha_pads = msgs_plaintext[ML_DSA_87_SIGN_LEN + MESSAGE_HASH_CHAIN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1 + ((ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN) * (OTP_PAD_SIZE // 32)):]
274284

275285
contact_hash_chain = msgs_plaintext[1 + XCHACHA20POLY1305_NONCE_LEN: MESSAGE_HASH_CHAIN_LEN + XCHACHA20POLY1305_NONCE_LEN + 1]
276286

@@ -286,7 +296,6 @@ def messages_data_handler(user_data: dict, user_data_lock, user_data_copied: dic
286296
our_kyber_key = user_data_copied["contacts"][contact_id]["ephemeral_keys"]["our_keys"][ML_KEM_1024_NAME]["private_key"]
287297
our_mceliece_key = user_data_copied["contacts"][contact_id]["ephemeral_keys"]["our_keys"][CLASSIC_MCELIECE_8_F_NAME]["private_key"]
288298

289-
# / 32 because shared secret is 32 bytes
290299
try:
291300
contact_kyber_pads = decrypt_shared_secrets(otp_hashchain_ciphertext[:ML_KEM_1024_CT_LEN * (OTP_PAD_SIZE // 32)], our_kyber_key, ML_KEM_1024_NAME)
292301
except Exception as e:
@@ -300,6 +309,8 @@ def messages_data_handler(user_data: dict, user_data_lock, user_data_copied: dic
300309
return
301310

302311
contact_pads, _ = one_time_pad(contact_kyber_pads, contact_mceliece_pads)
312+
contact_pads, _ = one_time_pad(contact_pads, xchacha_pads)
313+
303314
contact_strand_key = contact_pads[:32]
304315
contact_pads = contact_pads[32:]
305316

logic/smp.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,26 +581,26 @@ def smp_data_handler(user_data, user_data_lock, user_data_copied, ui_queue, cont
581581

582582
elif smp_step == 3:
583583
if (not user_data_copied["contacts"][contact_id]["lt_sign_key_smp"]["pending_verification"]):
584-
logger.error("Contact (%s) is not pending verification, yet they sent us a SMP request. Ignoring it.", contact_id)
584+
logger.error("Contact (%s) is not pending verification, yet they sent us a SMP request with invalid step. Ignoring it.", contact_id)
585585
return
586586

587587
smp_step_3(user_data, user_data_lock, contact_id, message, ui_queue)
588588
elif smp_step == 4:
589589
if (not user_data_copied["contacts"][contact_id]["lt_sign_key_smp"]["pending_verification"]):
590-
logger.error("Contact (%s) is not pending verification, yet they sent us a SMP request. Ignoring it.", contact_id)
590+
logger.error("Contact (%s) is not pending verification, yet they sent us a SMP request with invalid step. Ignoring it.", contact_id)
591591
return
592592

593593
smp_step_4_request_answer(user_data, user_data_lock, contact_id, message, ui_queue)
594594

595595
elif smp_step == 5:
596596
if (not user_data_copied["contacts"][contact_id]["lt_sign_key_smp"]["pending_verification"]):
597-
logger.error("Contact (%s) is not pending verification, yet they sent us a SMP request. Ignoring it.", contact_id)
597+
logger.error("Contact (%s) is not pending verification, yet they sent us a SMP request with invalid step. Ignoring it.", contact_id)
598598
return
599599

600600
smp_step_5(user_data, user_data_lock, contact_id, message, ui_queue)
601601

602602
else:
603-
logger.error("This is an impossible condition, You may have discovered a bug in Coldwire. Skipping weird SMP step (%d)...", smp_step)
603+
logger.error("Skipping unknown SMP step (%d) from contact (%s)...", smp_step, contact_id)
604604
return
605605

606606
save_account_data(user_data, user_data_lock)

0 commit comments

Comments
 (0)