- docs/lite-wallet-implementation-plan-v2-2026-06-04.md: vertical-slice plan that supersedes the v1 plan (now banner-marked); carries over the inherited artifact/ signing/phase-2 design docs for reference. - scripts/check-source-hygiene.sh: pre-commit/CI guard rejecting >80-char filenames and chained churn-token names, to stop the deleted "_plan"/"_batch" scaffolding from regrowing. - CLAUDE.md: repository guidance for future sessions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
33 KiB
HushChat Protocol Spec (2026-05-06)
Scope:
- Document the memo protocol observed in
external/SilentDragonXLitefor a future safe ImGui implementation. - Batch 1 implements header parsing/validation only.
- Batch 2 implements transaction-output grouping for validated header/payload memo pairs only.
- Batch 3 implements read-only transaction extraction as sanitized refresh metadata behind
DRAGONX_ENABLE_CHAT. - Batch 4 implements pre-decryption validation/error typing only.
- Batch 5 implements decrypt-input byte boundary preparation and fixture readiness only.
- Batch 6 implements non-sensitive cross-wallet fixture shape verification only.
- Batch 7 implements fixture-file loading for pending and ready compatibility vectors.
- Batch 8 implements a strict import checklist for replacing pending fixture placeholders only when all required categories are supplied.
- Batch 9 implements a no-plaintext SDXL seed/public-key projection verifier for ready fixture files.
- Batch 10 implements an inert corrupted-authentication-failure readiness scaffold for future secretstream auth failure checks.
- Batch 11 implements a strict ready-fixture replacement dry-run report for real non-sensitive SDXL vectors.
- Batch 12 implements a redacted capture-manifest validator for staged real-vector directories.
- These batches do not decrypt, store, render, or send chat messages.
Reference Sources
external/SilentDragonXLite/src/chatmodel.cppMainWindow::createHeaderMemo(...)MainWindow::createTxFromChatPage()MainWindow::createTxForSafeContactRequest()
external/SilentDragonXLite/src/controller.cppController::refreshTransactions()outgoing/incoming memo handling
external/SilentDragonXLite/src/DataStore/ChatDataStore.*external/SilentDragonXLite/src/Model/ChatItem.*external/SilentDragonXLite/src/Model/ContactItem.*external/SilentDragonXLite/src/Model/ContactRequest.*
Transaction Shape
SilentDragonXLite sends HushChat data as shielded transactions with paired memo outputs:
- Header memo: UTF-8 JSON object beginning with
{. - Payload memo: non-JSON string paired with the same transaction.
For encrypted chat messages, the payload memo is libsodium secretstream ciphertext encoded as hex. For contact requests, the payload memo is plaintext request text in the reference implementation.
The reference may also add unrelated dust/noise memo outputs around the chat pair, so a future parser must group by transaction, output order/position, and validated header metadata rather than assuming the transaction contains only two outputs.
Header JSON
createHeaderMemo(...) emits short field names to preserve memo space:
{
"h": 1,
"v": 0,
"z": "zs-reply-address",
"cid": "conversation-id",
"t": "Memo",
"e": "48 hex chars for message headers, empty for contact requests",
"p": "64 hex chars"
}
Fields:
h: integer header number, starting at1.v: integer HushChat version. Current reference uses0.z: sender reply z-address.cid: conversation/contact id.t: type string. Known values areMemoandCont.e: hex-encodedcrypto_secretstream_xchacha20poly1305_HEADERBYTESvalue. Present forMemo, empty forContin the reference.p: hex-encodedcrypto_kx_PUBLICKEYBYTESpublic key generated fromcrypto_kx_seed_keypair().
Type Semantics
Memo:
- Header includes non-empty
ewith 24 bytes encoded as 48 hex characters. - Payload memo is ciphertext encoded as hex.
- Reference derives a deterministic keypair from the wallet/chat password, derives a shared session key with the peer public key, and decrypts via
crypto_secretstream_xchacha20poly1305_pull().
Cont:
- Header includes empty
e. - Payload memo is the contact request text.
- Header
z,cid, andpare used to create/update a contact record.
SilentDragonXLite Decryption Audit
Reference initialization:
main.cppcallssodium_init()at startup and aborts if libsodium cannot initialize.
Reference chat key material:
- Wallet/chat passphrase input is wrapped with fixed strings (
DRGX+ user passphrase +SDXL). - The wrapped passphrase is hashed with
blake3_PW(...). crypto_pwhash(...)is then called withcrypto_pwhash_OPSLIMIT_SENSITIVE,crypto_pwhash_MEMLIMIT_SENSITIVE, andcrypto_pwhash_ALG_DEFAULT, producingcrypto_box_SEEDBYTESbytes.- The resulting bytes are hex-encoded and stored in
ChatDataStore::_passwordfor later HushChat operations.
Reference Memo send path:
ChatDataStore::getPassword()returns the stored hex chat key string.- The Qt code passes
passphraseHash.toUtf8()directly as the seed input tocrypto_kx_seed_keypair(pk, sk, seed). - The peer public key is loaded from contact/address-book metadata using
QByteArray::fromHex(...). - Sending uses
crypto_kx_server_session_keys(server_rx, server_tx, pk, sk, peer_pk)and encrypts withserver_tx. crypto_secretstream_xchacha20poly1305_init_push(&state, header, server_tx)creates the 24-byte stream header.crypto_secretstream_xchacha20poly1305_push(..., TAG_FINAL)encrypts the UTF-8 message, producingcrypto_secretstream_xchacha20poly1305_ABYTES + plaintext_lengthbytes.- The header memo stores
eas the stream header hex andpas the sender public key hex. - The payload memo stores ciphertext hex.
Reference outgoing-history decrypt path:
- Re-derives the local deterministic keypair from
ChatDataStore::getPassword(). - Looks up the peer public key by address.
- Uses
crypto_kx_server_session_keys(...)and decrypts withserver_tx. - Uses
crypto_secretstream_xchacha20poly1305_init_pull(&state, header, server_tx)andcrypto_secretstream_xchacha20poly1305_pull(...). - Stores decrypted text as
ChatIteminChatDataStore.
Reference incoming decrypt path:
- Parses header memo fields
cid,t,z,e, andpfrom transaction memo JSON. - Stores/caches
cid, reply z-address, secretstream header, and sender public key. - Re-derives the local deterministic keypair from
ChatDataStore::getPassword(). - Uses
crypto_kx_client_session_keys(client_rx, client_tx, pk, sk, peer_pk)and decrypts withclient_rx. - Uses
crypto_secretstream_xchacha20poly1305_init_pull(&state, header, client_rx)andcrypto_secretstream_xchacha20poly1305_pull(...). - Stores decrypted text/contact request text as
ChatIteminChatDataStore.
Open questions before real decryption:
- Confirm the
crypto_pwhash(...)salt input length and derivation semantics from the BLAKE3 output. The reference passes a pointer derived from a string without an explicit salt object. - Confirm outgoing-vs-incoming role selection (
server_txfor sends/outgoing history,client_rxfor received messages) against cross-wallet fixtures. - Decide whether new DragonX-originated messages must preserve SilentDragonXLite's UTF-8-hex seed behavior forever or can support a versioned decoded-byte seed later.
- Decide whether Batch 3 metadata fields
cidand reply z-address are safe to persist, hash, or keep transient only. - Decide how corrupted ciphertext/header failures should surface to users without logging memo contents or plaintext.
Batch 1 Parser Contract
Implemented parser: dragonx::chat::parseHushChatHeaderMemo().
The parser accepts only the clear, structural header format:
- memo must be non-empty JSON object text beginning with
{ - maximum memo size is 512 bytes
- required fields must exist and have expected JSON types
- only version
0is accepted - only
MemoandConttypes are accepted - public key must be 32 bytes encoded as 64 hex characters
Memomust include a 24 byte secretstream header encoded as 48 hex charactersContmust have an empty secretstream header
The parser intentionally does not:
- decrypt ciphertext
- derive keys
- parse contact request payload text
- write chat storage
- expose UI
- log memo contents
Batch 2 Transaction Grouping Contract
Implemented grouping helper: dragonx::chat::groupHushChatMemoOutputs().
Inputs:
- one transaction's memo outputs as
(position, memo)records - output
positionshould be the wallet/RPC output position where available - equal positions retain caller order as a stable fallback
Output:
- zero or more validated HushChat header/payload pairs
- grouping issues for malformed or incomplete HushChat-looking data
- ignored memo count for unrelated dust/noise payloads
Grouping rules:
- outputs are processed in ascending output position
- non-header memos before any valid header are ignored as dust/noise
- memos larger than 512 bytes produce
OversizedMemoand are skipped - memos beginning with
{must parse as valid HushChat headers or produceInvalidHeader - a valid header opens a pending pair
- a valid
Memoheader pairs with the next non-header memo that is non-empty, <=512 bytes, even-length hex - a valid
Contheader pairs with the next non-header memo that is non-empty and <=512 bytes - non-matching payload candidates after a pending
Memoheader are ignored so unrelated dust/noise can appear before ciphertext - a second valid header before a payload produces
DuplicateHeader; the previous header also producesMissingPayload, then the new header becomes pending - a valid header left pending at end-of-transaction produces
MissingPayload
The grouping helper intentionally does not:
- decrypt ciphertext
- inspect contact request payload text
- decide whether a pair belongs to the local wallet
- write storage
- trigger notifications/UI
- run from the transaction refresh pipeline while
DRAGONX_ENABLE_CHATis off
Batch 3 Transaction Extraction Contract
Implemented extractor: dragonx::chat::extractHushChatTransactionMetadata().
Inputs:
- one transaction id
- one transaction's memo outputs as
(position, memo)records - an explicit feature-enabled switch, defaulting to the build-time
DRAGONX_ENABLE_CHATvalue
Output when enabled:
- zero or more sanitized transaction metadata records
- sanitized grouping issues for HushChat-looking malformed data
- ignored memo count for unrelated dust/noise payloads
Sanitized metadata fields:
- transaction id
- HushChat type (
MemoorCont) - conversation id
- reply z-address
- header output position
- payload output position
- payload memo byte length only
The extractor intentionally does not expose:
- payload memo contents
- secretstream header hex
- peer public key hex
- decrypted plaintext
- derived keys
Refresh integration:
NetworkRefreshService::TransactionRefreshResultcarries transienthushChatMetadatarecords.- Received shielded memo outputs and outgoing
z_viewtransactionmemo outputs are eligible for extraction only whileDRAGONX_ENABLE_CHATis enabled. - The default build leaves
hushChatMetadataempty and does not change transaction history, caches, wallet state, UI, sends, or logs. applyTransactionRefreshResult()currently ignores chat metadata, so no Batch 3 data is persisted.
Batch 4 Pre-Decryption Safety Contract
Implemented preflight helper: dragonx::chat::validateHushChatMemoDecryptPreflight().
Inputs:
- parsed HushChat header metadata
- ciphertext payload memo hex
- an explicit feature-enabled switch, defaulting to the build-time
DRAGONX_ENABLE_CHATvalue
Output:
ok=trueonly when the input is structurally safe to hand to a future decrypt implementation- typed errors for feature-disabled, non-
Memoheader, invalid header number/version, missing reply address/conversation id, malformed secretstream header, malformed public key, empty ciphertext, oversized ciphertext, odd-length ciphertext, non-hex ciphertext, and truncated ciphertext - decoded ciphertext byte length only on success
Validation rules:
- no work is performed while
DRAGONX_ENABLE_CHATis off - only
Memoheaders are valid for decrypt preflight;Contremains plaintext/contact-request handling for later batches - secretstream header metadata must remain 24 bytes encoded as 48 hex characters
- peer public key metadata must remain 32 bytes encoded as 64 hex characters
- ciphertext memo must be non-empty, <=512 bytes as memo text, even-length hex, and decode to more than
crypto_secretstream_xchacha20poly1305_ABYTESbytes
The preflight helper intentionally does not:
- call libsodium
- derive keypairs or session keys
- decode or return ciphertext bytes
- decrypt plaintext
- store chat/contact data
- render UI
- send messages
- log memo contents or plaintext
Batch 5 Decrypt-Input Boundary Contract
Implemented helpers:
dragonx::chat::decodeHushChatHexBytes()dragonx::chat::prepareHushChatDecryptInput()dragonx::chat::inspectHushChatDecryptFixtureReadiness()dragonx::chat::hushChatSessionKeySelectionForDirection()
Inputs:
- stored chat key material as the 64-character hex string currently stored by SilentDragonXLite
ChatDataStore::getPassword() - parsed
Memoheader metadata - ciphertext payload memo hex
- decrypt direction (
IncomingorOutgoing) - an explicit feature-enabled switch, defaulting to the build-time
DRAGONX_ENABLE_CHATvalue
Prepared fields:
stored_chat_key_bytes: decoded 32 bytes from the stored chat key hex stringseed_bytes: SilentDragonXLite-compatiblecrypto_kx_seed_keypair()seed bytespeer_public_key_bytes: decoded 32 bytes from header fieldpstream_header_bytes: decoded 24 bytes from header fieldeciphertext_bytes: decoded ciphertext payload bytessession_key_selection:ClientRxfor incoming messages,ServerTxfor outgoing-history messagesplaintext_capacity: ciphertext byte length minuscrypto_secretstream_xchacha20poly1305_ABYTES
Seed compatibility decision:
- SilentDragonXLite stores a 32-byte
crypto_pwhash(...)output as 64 lowercase hex characters. - SilentDragonXLite then passes
ChatDataStore::getPassword().toUtf8()directly tocrypto_kx_seed_keypair(...). - Because
crypto_kx_seed_keypair(...)consumes 32 bytes, DragonX's compatibility contract preparesseed_bytesas the first 32 UTF-8 bytes of the stored 64-character hex string. - The decoded 32-byte stored key is kept separate as
stored_chat_key_bytesso future code cannot accidentally conflate the two interpretations.
Validation rules:
- no work is performed while
DRAGONX_ENABLE_CHATis off - stored chat key hex must decode to exactly 32 bytes
- Batch 4 preflight must pass before any payload decoding
- peer public key must decode to exactly 32 bytes
- stream header must decode to exactly 24 bytes
- ciphertext must decode to the preflight-reported byte length
- incoming direction selects
client_rx; outgoing-history direction selectsserver_tx - fixture readiness requires all prepared byte fields to have exact lengths and ciphertext to contain more than secretstream authentication overhead
The Batch 5 helpers intentionally do not:
- call libsodium
- derive keypairs or session keys
- decrypt plaintext
- store key material, ciphertext, chat, or contact data outside temporary result objects
- render UI
- send messages
- log key material, memo contents, ciphertext bytes, or plaintext
Required cross-wallet fixtures before real decryption:
- SilentDragonXLite outgoing
Memofixture with stored chat key hex, local public key, peer public key, header memo, ciphertext memo, direction, expected plaintext byte length, and a non-sensitive plaintext hash. - SilentDragonXLite incoming
Memofixture with the same metadata plus role expectation (client_rx). - Round-trip fixture between SilentDragonXLite and DragonX confirming the UTF-8-hex seed projection produces the observed public key.
- Corrupted fixture variants for bad peer public key, bad stream header, truncated ciphertext, and authentication failure.
- Contact-request fixture proving
Contremains outside encryptedMemodecrypt handling.
Batch 6 Cross-Wallet Fixture Contract
Implemented verifier: dragonx::chat::verifyHushChatCompatibilityFixture().
Static fixture fields:
- fixture id
- stored chat key hex
- local public key hex
- peer public key hex
- header memo
- ciphertext memo
- direction (
IncomingorOutgoing) - expected session key selection (
ClientRxorServerTx) - expected stored chat key byte length
- expected seed byte length
- expected local public key byte length
- expected peer public key byte length
- expected stream header byte length
- expected ciphertext byte length
- expected plaintext byte length
- optional expected plaintext hash hex
Verifier behavior:
- no work is performed while
DRAGONX_ENABLE_CHATis off - fixture id must be present so test/report output can identify the vector without logging memo contents
- local public key hex must decode to exactly 32 bytes
- peer public key hex must decode to exactly 32 bytes
- header memo is parsed through
parseHushChatHeaderMemo() (header memo, ciphertext memo)is grouped throughgroupHushChatMemoOutputs()- only
Memofixtures are accepted;Contfixtures are rejected as outside encrypted decrypt handling - for incoming fixtures, header field
pmust match the fixture peer public key - for outgoing-history fixtures, header field
pmust match the fixture local public key - decrypt input is prepared through
prepareHushChatDecryptInput()with the fixture peer public key kept separate from the header public key - readiness is checked through
inspectHushChatDecryptFixtureReadiness() - expected byte lengths and expected role must match the prepared/readiness output exactly
- optional plaintext hash must be hex text only; the verifier records its byte length but never evaluates plaintext
The Batch 6 verifier intentionally does not:
- derive a keypair
- derive session keys
- call libsodium
- decrypt ciphertext
- compare plaintext
- persist fixture data
- render UI
- send messages
- log key material, memo contents, ciphertext bytes, plaintext hashes, or plaintext
Remaining requirements before real decryption:
- collect real SilentDragonXLite incoming and outgoing
Memofixtures using non-sensitive plaintext and recorded hashes - verify that the observed local public key matches the UTF-8-hex seed projection in an isolated test vector
- add corrupted authentication-failure fixtures that pass structural validation but must fail future
crypto_secretstream_xchacha20poly1305_pull() - decide how fixture hashes will be generated and compared without retaining plaintext in the repository
- keep contact-request fixtures separate from encrypted
Memofixtures
Batch 7 Fixture File Import Contract
Checked-in fixture files live under tests/fixtures/hushchat/. The current files are intentionally pending placeholders for real, non-sensitive SilentDragonXLite vectors. They prove the import format and required categories without committing key material, memo contents, ciphertext bytes, plaintext, or made-up compatibility data.
The loader-supported format is JSON. The field names are intentionally TOML-friendly and the equivalent TOML shape is documented below for future fixture capture tooling.
Required top-level fields:
schema: must bedragonx.hushchat.compat-fixture.v1kind: one ofincoming_memo,outgoing_memo,seed_public_key_projection,corrupted_auth_failure, orcont_exclusionstatus:pendingorreadyid: required forpendingfilespending_reason: required forpendingfiles and must explain what real SDXL data is still missingfixture: required forreadyfiles
Ready JSON shape:
{
"schema": "dragonx.hushchat.compat-fixture.v1",
"status": "ready",
"kind": "incoming_memo",
"fixture": {
"id": "sdxl-incoming-memo-example",
"stored_chat_key_hex": "<64 hex chars from SDXL test wallet>",
"local_public_key_hex": "<64 hex chars>",
"peer_public_key_hex": "<64 hex chars>",
"header_memo": "<header memo JSON string>",
"ciphertext_memo": "<payload memo hex string>",
"direction": "Incoming",
"expected": {
"session_key_selection": "ClientRx",
"stored_chat_key_bytes": 32,
"seed_bytes": 32,
"local_public_key_bytes": 32,
"peer_public_key_bytes": 32,
"stream_header_bytes": 24,
"ciphertext_bytes": 21,
"plaintext_bytes": 4,
"plaintext_hash_hex": "<optional 64 hex chars>"
}
}
}
Equivalent TOML shape for future tooling:
schema = "dragonx.hushchat.compat-fixture.v1"
status = "ready"
kind = "incoming_memo"
[fixture]
id = "sdxl-incoming-memo-example"
stored_chat_key_hex = "<64 hex chars from SDXL test wallet>"
local_public_key_hex = "<64 hex chars>"
peer_public_key_hex = "<64 hex chars>"
header_memo = "<header memo JSON string>"
ciphertext_memo = "<payload memo hex string>"
direction = "Incoming"
[fixture.expected]
session_key_selection = "ClientRx"
stored_chat_key_bytes = 32
seed_bytes = 32
local_public_key_bytes = 32
peer_public_key_bytes = 32
stream_header_bytes = 24
ciphertext_bytes = 21
plaintext_bytes = 4
plaintext_hash_hex = "<optional 64 hex chars>"
Ready incoming_memo, outgoing_memo, seed_public_key_projection, and corrupted_auth_failure fixture files are accepted only after the Batch 6 verifier succeeds. That means the file is parsed through the Batch 1 header parser, Batch 2 memo grouper, Batch 4 preflight checks, Batch 5 decrypt-input preparation, and Batch 6 readiness/byte-length/role checks. cont_exclusion is accepted only when the verifier rejects the fixture as NonMemoHeader, proving contact requests stay outside encrypted Memo decrypt handling.
The fixture loader does not derive keys, call libsodium, decrypt, compare plaintext, persist chat data, render UI, send messages, or log key material/memo contents/plaintext/ciphertext bytes. It reports pending for missing real vectors and verified only for structurally ready vectors.
Exact data still needed from SilentDragonXLite before changing pending files to ready:
- a non-sensitive incoming
Memovector with stored chat key hex, local public key hex, peer public key hex, header memo, ciphertext memo, expectedClientRxrole, expected byte lengths, and optional plaintext hash - a non-sensitive outgoing-history
Memovector with the same structural fields and expectedServerTxrole - a seed/public-key projection vector proving the SDXL UTF-8-hex seed input produces the recorded local public key, without committing passphrase or plaintext
- a corrupted authentication-failure vector that passes structural validation but must fail future secretstream pull authentication
- a
Contvector proving contact requests remain excluded from encrypted Memo decrypt preparation - the exact capture command/procedure from SDXL so future vectors are reproducible without exposing sensitive wallet data
Batch 8 Fixture Import Checklist Contract
Implemented import gate:
dragonx::chat::hushChatRequiredCompatibilityFixtureKinds()dragonx::chat::inspectHushChatCompatibilityFixtureImportChecklist()- developer tool target
HushChatFixtureCheck
The import checklist is the conservative replacement path for the pending JSON placeholders. It accepts a caller-supplied candidate file for each expected category and loads every file through loadHushChatCompatibilityFixtureFile(). It reports replacement-ready only when:
- all five required categories are supplied exactly once
- no required category is missing
- no duplicate category candidate is supplied
- no supplied file is malformed or unreadable
- no file remains
pending - each non-
Contready file passes the Batch 7 loader and Batch 6 structural verifier - each non-
Contready file passes the Batch 9 seed/public-key projection verifier - the
corrupted_auth_failureready file is marked structurally ready for a future secretstream authentication-failure check, without decrypting or authenticating it now - the
cont_exclusionready file is accepted only by being excluded from encrypted Memo handling
The current checked-in placeholder directory is expected to pass only with pending allowed:
./build/bin/HushChatFixtureCheck --allow-pending tests/fixtures/hushchat
Before replacing pending placeholders with real vectors, the strict check must pass:
./build/bin/HushChatFixtureCheck tests/fixtures/hushchat
The tool intentionally prints only category names, file basenames, status/error names, and aggregate counts. It does not print key material, memo contents, plaintext, ciphertext bytes, plaintext hashes, or derived bytes. The helper/tool does not derive session keys, decrypt, persist chat data, render UI, send messages, or enable DRAGONX_ENABLE_CHAT in the app; Batch 9 uses libsodium only for the local seed/public-key projection check.
Capture checklist for real SDXL fixtures is tracked in tests/fixtures/hushchat/IMPORT_CHECKLIST.md. Pending files may be replaced only with non-sensitive, disposable-wallet vectors that pass the strict checklist. If any category is pending, missing, malformed, mismatched, duplicated, or not verified, the replacement gate remains closed.
Batch 9 Seed/Public-Key Projection Contract
Implemented verifier:
dragonx::chat::verifyHushChatSeedPublicKeyProjection()
The projection verifier is a no-plaintext compatibility check for ready non-Cont fixture files. It accepts the existing fixture fields only:
- fixture id
- stored SDXL chat key hex string
- declared local public key hex
- expected stored chat key byte length
- expected seed byte length
- expected local public key byte length
Verification rules:
- no work is performed while
DRAGONX_ENABLE_CHATis off unless a caller explicitly supplies the test/import feature switch - fixture id must be present
- stored chat key hex must decode to the expected 32-byte stored key length
- seed bytes are exactly the first 32 UTF-8 bytes of the stored 64-character SDXL chat key hex string
- seed byte length must match the fixture's declared expected seed length
- local public key hex must decode to the expected 32-byte local public key length
- libsodium is initialized only for this local public-key projection check
crypto_kx_seed_keypair()is called with the SDXL-compatible seed bytes- only the projected public key is compared to the declared local public key
- the derived secret key, temporary seed bytes, and temporary projected public key buffer are zeroed before returning
The verifier intentionally does not:
- derive session keys
- decrypt ciphertext
- compare plaintext
- store chat/contact data
- render UI
- send messages
- log or return key material, memo contents, plaintext, ciphertext bytes, plaintext hashes, seed bytes, public key bytes, or secret key bytes
Batch 9 integration:
inspectHushChatCompatibilityFixtureImportChecklist()now requires all four non-Contready fixtures to pass the seed/public-key projection verifier.- The developer checker reports only the aggregate
seed_projectedcount. - Pending files still block strict replacement until real SilentDragonXLite vectors are supplied.
Batch 10 Corrupted Authentication-Failure Scaffold
Implemented scaffold:
dragonx::chat::inspectHushChatCorruptedAuthFailureReadiness()
The corrupted-auth scaffold is an inert marker for ready corrupted_auth_failure fixture files. It exists so a real non-sensitive SilentDragonXLite corrupted vector can be carried through the current structural pipeline and reserved for a future crypto_secretstream_xchacha20poly1305_pull() authentication-failure assertion.
Readiness rules:
- no work is performed while
DRAGONX_ENABLE_CHATis off unless a caller explicitly supplies the test/import feature switch - pending fixtures are rejected as not ready
- only
corrupted_auth_failurefixture files are accepted - the fixture file must already be parsed and verified through the Batch 7 loader and Batch 6 fixture verifier
- the fixture must already pass the Batch 9 seed/public-key projection verifier
- success sets
structurally_ready_for_future_auth_check=true - success sets
requires_future_secretstream_auth_failure=true - success keeps
decrypted=falseandauthenticated=false
The scaffold intentionally does not:
- derive session keys
- initialize a secretstream pull state
- decrypt ciphertext
- authenticate ciphertext
- compare plaintext
- store chat/contact data
- render UI
- send messages
- log or return key material, memo contents, plaintext, ciphertext bytes, plaintext hashes, seed bytes, public key bytes, or secret key bytes
Batch 10 integration:
inspectHushChatCompatibilityFixtureImportChecklist()now requires the corrupted fixture to be structurally ready for future auth-failure validation before replacement can be ready.- The developer checker reports aggregate
future_auth_requiredandauth_structural_readycounts, and per-file labels only for those states. - These counts do not mean the fixture has decrypted or authenticated; they mean it is ready for a future auth-failure assertion once decrypt support exists.
Batch 11 Strict Replacement Dry-Run Report
Implemented dry-run workflow:
dragonx::chat::inspectHushChatCompatibilityFixtureReplacementDryRun()./build/bin/HushChatFixtureCheck --replacement-dry-run <fixture-file-or-directory>...
The dry-run workflow wraps the Batch 10 import checklist and reports whether a supplied ready-vector directory would be eligible to replace the checked-in pending placeholders. It is strict by design: the CLI refuses --replacement-dry-run when --allow-pending is also supplied, and the dry-run result only sets would_replace=true when the underlying checklist is fully replacement_ready.
Replacement is refused if any required vector is:
- missing
- duplicated
- unreadable or malformed
- supplied under the wrong expected kind
- still marked
pending - not verified through the fixture loader and structural contracts
- unable to pass the SDXL seed/public-key projection check
- a
corrupted_auth_failurevector that is not structurally ready for a future auth-failure check - a
cont_exclusionvector that is not excluded from encryptedMemohandling
The replacement report is intentionally redacted. It may print only category names, file basenames, status/error names, boolean decision flags, and aggregate counts. It must not print fixture ids, stored chat key material, memo contents, secretstream headers, public key bytes, plaintext hashes, plaintext, ciphertext bytes, seed bytes, derived secret keys, or derived session keys.
The dry-run workflow intentionally does not:
- copy or overwrite fixture files
- derive session keys
- initialize a secretstream pull state
- decrypt ciphertext
- authenticate ciphertext
- compare plaintext
- store chat/contact data
- render UI
- send messages
- enable
DRAGONX_ENABLE_CHATin the app
Batch 12 Capture Manifest Workflow
Implemented manifest validator:
dragonx::chat::validateHushChatCaptureManifest()dragonx::chat::loadHushChatCaptureManifestFile()./build/bin/HushChatFixtureCheck --validate-capture-manifest <manifest-file-or-directory>
The capture manifest is a redacted provenance and handling record for a staged directory of real disposable SilentDragonXLite fixture files. It does not validate fixture contents and does not replace the Batch 11 strict replacement dry run. Instead, it verifies that the staged directory has the required metadata before strict fixture validation is used.
The checked-in template is tests/fixtures/hushchat/templates/capture-manifest.template.json. The template is intentionally marked template; a staged copy must be named capture-manifest.json, set top-level status to staged, and set each required category status to ready before the validator accepts it.
Required manifest fields:
- schema
dragonx.hushchat.capture-manifest.v1 - top-level status
staged - redacted manifest id
- staged fixture directory name
- dry-run command containing
HushChatFixtureCheck --replacement-dry-run - provenance object with source client
SilentDragonXLite, source client version or commit, capture date, network, and capture method - handling object with all required safety flags set to
true - exactly one category entry for each required fixture kind, each with a staged filename and status
ready
Rejected manifest conditions:
- unreadable, non-object, or malformed JSON
- wrong schema
- template or unknown status
- missing provenance, handling, dry-run, fixture-directory, or category metadata
- missing, duplicate, or unknown required category entries
- any known prohibited fixture-data field such as
fixture,stored_chat_key_hex,header_memo,ciphertext_memo,plaintext,passphrase, private-key fields, wallet-file fields, derived-key fields, or session-key fields
The manifest validator prints only redacted status/error names, counts, category names, basenames, and boolean safety flags. It intentionally does not inspect or print fixture contents, key material, memo contents, plaintext, ciphertext bytes, plaintext hashes, derived keys, session keys, or wallet files. It also does not copy files, decrypt, authenticate, derive session keys, store chat/contact data, render UI, send messages, or enable DRAGONX_ENABLE_CHAT in the app.
The staged-vector workflow is:
./build/bin/HushChatFixtureCheck --validate-capture-manifest /path/to/staged/hushchat-fixtures
./build/bin/HushChatFixtureCheck --replacement-dry-run /path/to/staged/hushchat-fixtures
Both commands must succeed before replacing the checked-in pending placeholders.
Remaining Implementation Batches
- Capture real non-sensitive SilentDragonXLite fixture files and replace pending placeholders only after the Batch 12 manifest validator and Batch 11 strict dry-run report both succeed.
- Add the real secretstream auth-failure assertion for ready corrupted fixtures after decrypt support exists.
- Implement encrypted message decryption only after fixture confirmation.
- Add wallet-scoped chat/contact storage.
- Add read-only ImGui Chat UI.
- Add contact request actions.
- Add send/composer flow after receive-side validation.