
Bug Tracker
Complete Documentation of All 71 Bugs
This document provides detailed documentation of all bugs discovered during SimpleGo development, including the incorrect code, correct code, and root cause analysis.
Summary
| Bug # | Component | Session | Status |
|---|---|---|---|
| 1 | E2E key length | 4 | FIXED |
| 2 | prevMsgHash length | 4 | FIXED |
| 3 | MsgHeader DH key | 4 | FIXED |
| 4 | ehBody length | 4 | FIXED |
| 5 | emHeader size | 4 | FIXED |
| 6 | Payload AAD size | 4 | FIXED |
| 7 | Root KDF output order | 4 | FIXED |
| 8 | Chain KDF IV order | 4 | FIXED |
| 9 | wolfSSL X448 byte order | 5 | FIXED |
| 10 | Port encoding | 6 | FIXED |
| 11 | smpQueues count | 6 | FIXED |
| 12 | queueMode Nothing | 6 | FIXED |
| 13 | Payload AAD length prefix | 8 | FIXED |
| 14 | chainKdf IV assignment | 8 | FIXED |
| 15 | Reply Queue HSalsa20 | 9 | FIXED |
| 16 | A_CRYPTO header AAD | 9 | FIXED |
| 17 | cmNonce instead of msgId | 10C | FIXED |
| 18 | Reply Queue E2E | 12-18 | FIXED |
| 19 | header_key_recv overwritten | 19-20 | FIXED |
| 20 | PrivHeader for HELLO | 21 | FIXED |
| 21 | AgentVersion for AgentMessage | 21 | FIXED |
| 22 | prevMsgHash encoding | 21 | FIXED |
| 23 | cbEncrypt padding | 21 | FIXED |
| 24 | DH Key for HELLO | 21 | FIXED |
| 25 | PubHeader Nothing encoding | 21 | FIXED |
| 26 | v2/v3 EncRatchetMessage format | 21 | FIXED |
| 27 | E2E Version Mismatch | 22 | FIXED |
| 28 | KEM Parser Crash | 22 | FIXED |
| 29 | Body Decrypt Pointer-Arithmetik | 22 | FIXED |
| 30 | HKs/NHKs Init + Promotion | 22 | FIXED |
| 31 | Phase 2a Try-Order | 22 | FIXED |
| 32 | Heap Overflow PQ Headers | 25 | FIXED |
| 33 | txCount Hardcoded | 25 | FIXED |
| 34 | Nonce Offset Wrong | 25 | FIXED |
| 35 | Ratchet State Copy | 25 | FIXED |
| 36 | Chain KDF Skip Relative | 25 | FIXED |
| 37 | Receipt count=Word16 | 25 | FIXED |
| 38 | Receipt rcptInfo=Word32 | 25 | FIXED |
| 39 | NULL contact Reply Queue | 25 | FIXED |
Total: 72 bugs documented, 69 FIXED, 1 identified (SPI3), 1 temp fix, 1 SHOWSTOPPER
Bug #1: E2E Key Length Prefix
Session: 4 Component: E2ERatchetParams encoding Impact: Critical - causes parsing failure
Incorrect Code
// Word16 BE length prefix (WRONG!)
buf[p++] = 0x00;
buf[p++] = 0x44; // 68 as Word16
memcpy(&buf[p], spki_key, 68);
Correct Code
// 1-byte length prefix (CORRECT!)
buf[p++] = 0x44; // 68 as single byte
memcpy(&buf[p], spki_key, 68);
Root Cause
E2ERatchetParams keys are encoded as ByteString (1-byte prefix), not Large (Word16 prefix).
Bug #2: prevMsgHash Length Prefix
Session: 4 Component: AgentMessage encoding Impact: Critical - causes parsing failure
Incorrect Code
// 1-byte length prefix (WRONG!)
buf[p++] = 0x00; // Empty hash
Correct Code
// Word16 BE length prefix (CORRECT!)
buf[p++] = 0x00;
buf[p++] = 0x00; // Empty hash as Word16
Root Cause
AgentMessage uses Large wrapper for prevMsgHash, requiring Word16 prefix.
Bug #3: MsgHeader DH Key Length
Session: 4 Component: MsgHeader encoding Impact: Critical - causes parsing failure
Incorrect Code
// Word16 BE length prefix (WRONG!)
buf[p++] = 0x00;
buf[p++] = 0x44;
memcpy(&buf[p], dh_key_spki, 68);
Correct Code
// 1-byte length prefix (CORRECT!)
buf[p++] = 0x44;
memcpy(&buf[p], dh_key_spki, 68);
Root Cause
MsgHeader msgDHRs is PublicKey, encoded as ByteString with 1-byte prefix.
Bug #4: ehBody Length Prefix
Session: 4 Component: EncMessageHeader encoding Impact: Critical - cascades to bugs #5 and #6
Incorrect Code
// Word16 BE length prefix (WRONG!)
em_header[hp++] = 0x00;
em_header[hp++] = 0x58; // 88 as Word16
Correct Code
// 1-byte length prefix (CORRECT!)
em_header[hp++] = 0x58; // 88 as single byte
Root Cause
ehBody is ByteString, not Large.
Bug #5: emHeader Size
Session: 4 Component: EncMessageHeader structure Impact: Critical - cascades to bug #6
Incorrect Code
#define EM_HEADER_SIZE 124
uint8_t em_header[124];
Correct Code
#define EM_HEADER_SIZE 123
uint8_t em_header[123];
Root Cause
Cascaded from Bug #4 - with 1-byte prefix, size is 123 not 124.
Bug #6: Payload AAD Size
Session: 4 Component: AES-GCM AAD Impact: Critical - auth tag mismatch
Incorrect Code
uint8_t payload_aad[236]; // WRONG!
aes_gcm_encrypt(..., payload_aad, 236, ...);
Correct Code
uint8_t payload_aad[235]; // CORRECT!
aes_gcm_encrypt(..., payload_aad, 235, ...);
Root Cause
Cascaded from Bug #5 - AAD = 112 + 123 = 235, not 236.
Bug #7: Root KDF Output Order
Session: 4 Component: Root KDF implementation Impact: Critical - all keys wrong
Incorrect Code
// Wrong order!
memcpy(chain_key, kdf_output, 32);
memcpy(new_root_key, kdf_output + 32, 32);
Correct Code
// Correct order per Haskell
memcpy(new_root_key, kdf_output, 32);
memcpy(chain_key, kdf_output + 32, 32);
memcpy(next_header_key, kdf_output + 64, 32);
Root Cause
Misread Haskell source - output order is root, chain, header.
Bug #8: Chain KDF IV Order
Session: 4 Component: Chain KDF implementation Impact: Critical - encryption uses wrong IVs
Incorrect Code
// Swapped! (WRONG!)
memcpy(msg_iv, kdf_output + 64, 16);
memcpy(header_iv, kdf_output + 80, 16);
Correct Code
// Correct order!
memcpy(header_iv, kdf_output + 64, 16); // iv1 = header
memcpy(msg_iv, kdf_output + 80, 16); // iv2 = message
Root Cause
iv1 (bytes 64-79) is header IV, iv2 (bytes 80-95) is message IV.
Bug #9: wolfSSL X448 Byte Order
Session: 5 Component: X448 cryptography Impact: Critical - all DH computations wrong
The Problem
wolfSSL X448 uses little-endian, SimpleX expects big-endian.
The Fix
static void reverse_bytes(const uint8_t *src, uint8_t *dst, size_t len) {
for (size_t i = 0; i < len; i++) {
dst[i] = src[len - 1 - i];
}
}
// After key generation:
reverse_bytes(pub_tmp, keypair->public_key, 56);
reverse_bytes(priv_tmp, keypair->private_key, 56);
// Before DH:
reverse_bytes(their_public, their_public_rev, 56);
reverse_bytes(my_private, my_private_rev, 56);
// After DH:
reverse_bytes(secret_tmp, shared_secret, 56);
Root Cause
wolfSSL defines EC448_LITTLE_ENDIAN internally.
Bug #10: Port Encoding
Session: 6 Component: SMPQueueInfo encoding Impact: Critical - parser fails
Incorrect Code
// Length prefix (WRONG!)
buf[p++] = (uint8_t)strlen(port_str);
memcpy(&buf[p], port_str, strlen(port_str));
Correct Code
// Space separator (CORRECT!)
buf[p++] = ' '; // 0x20
memcpy(&buf[p], port_str, strlen(port_str));
Root Cause
SMPServer encoding uses space separator, not length prefix.
Bug #11: smpQueues Count
Session: 6 Component: NonEmpty list encoding Impact: Critical - parser fails
Incorrect Code
// 1-byte count (WRONG!)
buf[p++] = 0x01;
Correct Code
// Word16 BE count (CORRECT!)
buf[p++] = 0x00;
buf[p++] = 0x01;
Root Cause
NonEmpty list uses Word16 for count.
Bug #12: queueMode Nothing
Session: 6 Component: SMPQueueInfo encoding Impact: Medium - parser might fail
Incorrect Code
// Send '0' byte (WRONG!)
buf[p++] = '0'; // 0x30
Correct Code
// Send NOTHING (CORRECT!)
// (no code - just don't write anything)
Root Cause
queueMode uses "maybe empty" not standard Maybe encoding.
Bug #13: Payload AAD Length Prefix (SESSION 8 BREAKTHROUGH!)
Session: 8 Component: Payload AAD construction Impact: Critical - AgentConfirmation rejected
The Discovery
Haskell largeP parser removes length prefix from parsed object:
largeP :: Parser a -> Parser a
largeP p = smpP >>= \len -> A.take (fromIntegral (len :: Word16)) >>= parseAll p
Incorrect Code
// AAD with length prefix (WRONG!)
uint8_t payload_aad[237]; // 2 + 112 + 123
payload_aad[0] = (total_len >> 8) & 0xFF; // Length prefix
payload_aad[1] = total_len & 0xFF;
memcpy(&payload_aad[2], header_aad, 112);
memcpy(&payload_aad[114], em_header, 123);
Correct Code
// AAD WITHOUT length prefix (CORRECT!)
uint8_t payload_aad[235]; // 112 + 123
memcpy(&payload_aad[0], header_aad, 112);
memcpy(&payload_aad[112], em_header, 123);
Root Cause
The length prefix is consumed by the parser, not included in AAD.
Bug #14: chainKdf IV Assignment (SESSION 8)
Session: 8 Component: Chain KDF IV handling Impact: Critical - wrong IVs used for encryption
The Discovery
Session 4 found the order but assignment was still swapped later.
Incorrect Code
// Assignments swapped (WRONG!)
uint8_t *header_iv = &chain_kdf_output[80]; // iv2
uint8_t *msg_iv = &chain_kdf_output[64]; // iv1
Correct Code
// Correct assignments!
uint8_t *header_iv = &chain_kdf_output[64]; // iv1 = header
uint8_t *msg_iv = &chain_kdf_output[80]; // iv2 = message
Root Cause
Chain KDF output layout:
[0:32] next_chain_key
[32:64] message_key
[64:80] iv1 = HEADER_IV
[80:96] iv2 = MESSAGE_IV
Bug #15: Reply Queue HSalsa20 (SESSION 9)
Session: 9 Component: Reply Queue E2E decryption Impact: Critical - Reply Queue decrypt fails
The Discovery
NaCl crypto_box includes HSalsa20 key derivation internally.
Incorrect Code
// crypto_scalarmult only does raw X25519 (WRONG!)
crypto_scalarmult(dh_secret, rcv_dh_private, srv_dh_public);
// dh_secret is RAW, not ready for XSalsa20-Poly1305!
Correct Code
// crypto_box_beforenm does X25519 + HSalsa20 (CORRECT!)
crypto_box_beforenm(dh_secret, srv_dh_public, rcv_dh_private);
// dh_secret is NOW ready for crypto_box_open_easy_afternm!
Root Cause
Must use same crypto primitive chain as sender.
Bug #16: A_CRYPTO Header AAD (SESSION 9)
Session: 9 Component: Header encryption AAD Impact: Critical - A_CRYPTO error in app
The Problem
Header encryption AAD format was incorrect.
Root Cause
Incorrect AAD construction for header encryption causing authentication failure.
Bug #17: cmNonce instead of msgId (SESSION 10C)
Session: 10C Component: Per-Queue E2E Decryption Impact: Critical - All Contact Queue messages fail decryption
The Discovery
Used msgId as nonce for per-queue E2E decryption, but the correct nonce is cmNonce from the ClientMsgEnvelope structure.
Incorrect Code
// WRONG - used msgId as nonce
memcpy(nonce, msg_id, msgIdLen); // msgId from MSG header
Correct Code
// CORRECT - extract cmNonce from ClientMsgEnvelope
int cm_nonce_offset = spki_offset + 44; // [60-83]
memcpy(cm_nonce, &server_plain[cm_nonce_offset], 24);
// Then decrypt with cmNonce
crypto_box_open_easy_afternm(plain, &data[cm_enc_body_offset],
enc_len, cm_nonce, dh_shared);
Bug #18: Reply Queue E2E Decryption -- SOLVED!
Sessions: 12, 13, 14, 15, 16, 17, 18 Component: Reply Queue Per-Queue E2E Layer 2 → envelope_len calculation Impact: Cannot decrypt Reply Queue messages Status: SOLVED in Session 18!
Root Cause & Fix
ROOT CAUSE:
envelope_len = plain_len - 2 = 16104 ← WRONG! Includes 102B SMP padding
envelope_len = raw_len_prefix = 16002 ← CORRECT! Exact content length
FIX -- ONE LINE:
envelope_len = raw_len_prefix;
RESULT:
Method 0 (decrypt_client_msg): SUCCESS!
Decrypted: 15904 bytes AgentConfirmation + EncRatchetMessage
See Session 18 documentation for full 7-session debugging history.
Bug #19: header_key_recv Gets Overwritten -- SOLVED!
Sessions: 19, 20 Component: Double Ratchet key management → debug self-decrypt test Impact: Medium - header decrypt fails without workaround Status: SOLVED in Session 20!
19.1 Symptom
header_key_recv after X3DH = 1c08e86e... (saved_nhk, correct)
header_key_recv at receipt = cf0c74d2... (wrong, overwritten)
19.2 Root Cause -- FOUND (Session 20)
smp_peer.c:347 -- Debug self-decrypt test calling ratchet_decrypt().
After encrypting the AgentConfirmation, a debug self-test called ratchet_decrypt()
on our own encrypted message. ratchet_decrypt() has side effects: it performs
a DH ratchet step when it detects a "new" DH key in the decrypted header.
Corrupted: header_key_recv, root_key, chain_key_recv, dh_peer, msg_num_recv.
19.3 Fix Applied (Session 20)
Removed the debug self-decrypt test from smp_peer.c:343-359.
Branch: claude/fix-header-key-recv-bug-DNYeF → merged to main.
Bug #20: PrivHeader for HELLO (SESSION 21)
Session: 21 Component: ClientMessage encoding for HELLO Impact: Critical - wrong message type indicator
Incorrect Code
// Used PHEmpty tag (WRONG!)
buf[p++] = '_'; // 0x5F = PHEmpty (Confirmation without key)
Correct Code
// No PrivHeader for regular messages (CORRECT!)
buf[p++] = 0x00; // No PrivHeader
Root Cause
PrivHeader encoding is NOT a standard Maybe:
'K'(0x4B) = PHConfirmation (with sender auth key)'_'(0x5F) = PHEmpty (confirmation without key)0x00= No PrivHeader (regular messages like HELLO)
HELLO is a regular AgentMessage, not a Confirmation.
Bug #21: AgentVersion for AgentMessage (SESSION 21)
Session: 21 Component: AgentMsgEnvelope encoding Impact: Critical - parser version mismatch
Incorrect Code
// Used Agent protocol version (WRONG!)
buf[p++] = 0x00;
buf[p++] = 0x02; // agentVersion = 2
Correct Code
// AgentMessage uses version 1 (CORRECT!)
buf[p++] = 0x00;
buf[p++] = 0x01; // agentVersion = 1
Root Cause
AgentConfirmation uses agentVersion=7 (protocol version), but AgentMessage (HELLO) uses agentVersion=1 (message format version). Different fields, different values.
Bug #22: prevMsgHash Encoding (SESSION 21)
Session: 21 Component: AgentMessage encoding Impact: Critical - parser fails on hash field
Incorrect Code
// Raw empty bytes or missing (WRONG!)
Correct Code
// smpEncode(ByteString) with Word16 prefix (CORRECT!)
buf[p++] = 0x00;
buf[p++] = 0x00; // Word16 BE length = 0 (empty hash)
Root Cause
prevMsgHash field uses Large encoding (Word16 prefix). For empty hash: [0x00][0x00].
Related to Bug #2 (same encoding pattern).
Bug #23: cbEncrypt Padding (SESSION 21)
Session: 21 Component: Server-level encryption (cbEncrypt) Impact: Critical - server rejects or app can't decrypt
Incorrect Code
// Encrypt raw plaintext (WRONG!)
cbEncrypt(key, nonce, raw_plaintext, raw_len, ...);
Correct Code
// Pad BEFORE encrypt (CORRECT!)
pad(raw_plaintext, raw_len, padded_buf, &padded_len);
cbEncrypt(key, nonce, padded_buf, padded_len, ...);
Root Cause
The pad function adds a 2-byte length prefix and 0x23 padding BEFORE encryption.
Receiver does: decrypt → unPad. Sender must: pad → encrypt.
Bug #24: DH Key for HELLO (SESSION 21)
Session: 21 Component: Per-queue E2E encryption key selection Impact: Critical - E2E layer fails
Incorrect Code
// Used receiver's DH key (WRONG!)
compute_e2e_secret(rcv_dh_public, our_private, ...);
Correct Code
// Use sender's DH key for HELLO (CORRECT!)
compute_e2e_secret(snd_dh_public, our_private, ...);
Root Cause
For Confirmation: use rcv_dh (receiver's DH key from the queue).
For HELLO: use snd_dh (sender's DH key for the reply queue).
Bug #25: PubHeader Nothing Encoding (SESSION 21)
Session: 21 Component: ClientMsgEnvelope PubHeader field Impact: Medium - parser may fail
Incorrect Code
// Field missing entirely (WRONG!)
Correct Code
// Maybe Nothing = '0' (CORRECT!)
buf[p++] = '0'; // 0x30 = Nothing