detz: baseline 1.46.1 pre-transpilation
This commit is contained in:
+69
-6
@@ -581,18 +581,23 @@ it `wa_contact` stays empty and names cannot be resolved.
|
||||
|
||||
| Piece | Files | What it does |
|
||||
|---|---|---|
|
||||
| Key receipt | `wa_receiver.*`, `wa_proto.*` (`parse_protocol_message`), `WaClient::handle_message` | Decrypts `<enc>` from our own account (pkmsg via X3DH responder, msg via session), unpads, stores each APP_STATE_SYNC_KEY_SHARE key in `wa_appstate_key`. Only the phone's own messages are decrypted; other senders are acked and ignored as before. |
|
||||
| Key request | `WaClient::request_app_state_keys`, `send_peer_message`, `encode_app_state_key_request` | Peer message to the primary phone (category `peer`), at most `kWaAppStateKeyIdsMax` ids, one request per key id per `kWaAppStateKeyRequestInterval` (24 h), stored in `wa_appstate_keyreq`. |
|
||||
| Key receipt | `wa_receiver.*`, `wa_proto.*` (`parse_protocol_message`), `WaClient::handle_message` | Decrypts `<enc>` from our own account (pkmsg via X3DH responder, msg via session), unpads, stores each APP_STATE_SYNC_KEY_SHARE key in `wa_appstate_key`. Own means our phone number or our LID (whatsmeow `IsFromMe`); other senders are acked and ignored as before. Every arrival is logged before the filter, so a trace shows dropped mail too. An empty share (keys without key data, openclaw/wacli PR 443) is reported and ends the wait without storing; a consumed peer message gets a `peer_msg` protocol receipt. |
|
||||
| Key request | `WaClient::request_app_state_keys`, `send_peer_message`, `encode_app_state_key_request` | Peer message to the primary phone (category `peer`, meta + enc, device-identity only on a prekey message), at most `kWaAppStateKeyIdsMax` ids, one request per key id per `kWaAppStateKeyRequestInterval` (24 h), stored in `wa_appstate_keyreq`. The send is verified (Signal session must exist, the server ack must carry no error); when it fails the 24 h marks are rolled back so the next sync asks again (whatsmeow PR 1268). |
|
||||
| Notifications, receipts | `WaClient::handle_notification`, `handle_receipt` | `server_sync` re-syncs the contact collection (other collections are named and skipped); `encrypt` with fewer than `kWaMinPrekeys` left refills prekeys; a retry receipt (the phone could not decrypt our message) is logged loudly. Notification syncs never nest inside a sync (guard + one more pass). |
|
||||
| Key expansion, MACs, values | `wa_appstate.*` (`WaAppStateKeys`, `WaAppStateCrypto`) | HKDF-SHA256 `WhatsApp Mutation Keys` to 160 bytes; value MAC HMAC-SHA512 truncated to 32; index, snapshot and patch MACs HMAC-SHA256; AES-256-CBC values. `WaCrypto::hmac_sha512` added. |
|
||||
| LTHash | `wa_appstate.*` (`WaLtHash`) | 128-byte state, HKDF-SHA256 info `WhatsApp Patch Integrity` (whatsmeow, not BLAKE2b; see research conclusion `appstate-lthash-hkdf-not-blake2b`). |
|
||||
| Patch fetch and apply | `WaAppStateSync::sync` | iq `w:sync:app:state`, `return_snapshot` at version 0, `version=N` otherwise; external blobs via `WaMedia::download` (`md-app-state`); snapshot MAC, patch MAC, index and value MACs checked before anything is stored. |
|
||||
| Contacts | `WaAppStateSync::apply_contact` | SET stores jid = index[1], name = fullName else firstName; empty name is counted and skipped; REMOVE deletes the row. |
|
||||
| Push-name learning | `WaClient::handle_message`, `push_name_candidate`, `WaStore::put_contact_if_absent` | Every incoming message carries the sender's self-reported name in `notify` (whatsmeow `message.go`); group senders come from `participant`, the PN alternate is preferred when present. Stored only when the jid has no name yet, so a saved, imported or aliased name always wins; truncated to `kWaPushNameMax` (128). No server query returns names for regular users (`GetUserInfo` usync has status, picture, devices, LID and business verified names only), so this plus VCF/alias/app-state is the complete name-resolution stack. |
|
||||
| Login hook | `WaClient::login` calls `sync_app_state` after prekeys | Never fails the login (errors are logged). If a key is missing, requests it, waits up to `kWaAppStateKeyWait` s for a key share, then syncs once more. |
|
||||
| Long sync | `detz --whatsapp-sync` (`WaWizard::sync`) | Logs in, re-syncs with a `kWaSyncOnlineWait` (300 s) key wait for a sleeping phone, reports the contact count. Exit 0 with contacts, 1 without, 2 when not linked or login failed. |
|
||||
| VCF fallback | `detz --whatsapp-import FILE` (`WaVcf`) | Imports a .vcf exported on the phone (FN else N, TEL lines, international digits) into the same contact search. Loud skips, capped at `kWaContactsMax`, file capped at `kWaVcfFileMax`. |
|
||||
|
||||
Caps (`wa_consts.h`): `kWaAppStatePatchMax` 50 patches per sync,
|
||||
`kWaAppStateMutationMax` 10000 mutations, `kWaAppStateTimeout` 30 s per iq,
|
||||
`kWaAppStateBlobMax` 16 MiB per external blob, `kWaAppStateKeyIdsMax` 16 key
|
||||
ids per request, `kWaAppStateKeyWait` 20 s. A cap stops the sync loudly
|
||||
ids per request, `kWaAppStateKeyWait` 20 s, `kWaSyncOnlineWait` 300 s,
|
||||
`kWaVcfFileMax` 8 MiB. A cap stops the sync loudly
|
||||
(`capped` in the report, a log line) and the rest waits for the next login.
|
||||
|
||||
Failure rule: a patch or snapshot that fails a MAC or hash check is not stored,
|
||||
@@ -616,7 +621,17 @@ Verification (offline, `build/whatsapp_test`, section `appstate`):
|
||||
- a missing key stores nothing and reports the key id;
|
||||
- the key share and key request bytes match whatsmeow's Message encoding;
|
||||
- a pkmsg built with `WaSession` decrypts through `WaReceiver` to the key share
|
||||
Message, and a corrupted pkmsg is refused.
|
||||
Message, and a corrupted pkmsg is refused;
|
||||
- the 24 h request mark can be cleared for an immediate retry;
|
||||
- own senders accept PN and LID, the peer node carries device-identity only on
|
||||
a prekey message, server_sync collections and retry receipts parse;
|
||||
- an empty key share parses with empty key data;
|
||||
- a 5-card .vcf imports 4 numbers (FN, N fallback, unfolding, 00 form) and the
|
||||
typo-tolerant search finds them;
|
||||
- push-name candidates parse for 1:1 (PN, LID, PN alternate), groups
|
||||
(participant, PN alternate), and reject own senders, `-`, missing notify,
|
||||
non-user senders and over-long names; fill-if-absent keeps the first name and
|
||||
never overwrites a saved one.
|
||||
|
||||
The oracle is `tests/whatsapp_data/appstate_vectors.json`, generated by
|
||||
`tests/whatsapp_data/appstate_oracle.go` (a Go program using whatsmeow's own
|
||||
@@ -624,8 +639,10 @@ The oracle is `tests/whatsapp_data/appstate_vectors.json`, generated by
|
||||
|
||||
Not verified: the live server's answers to the iq, key share timing, the media
|
||||
host for external blobs, and whether a REMOVE of an unknown index (whatsmeow
|
||||
only warns) causes trouble. Known gap: a SET with an empty name keeps any older
|
||||
name the row had; the spec says skip, not delete.
|
||||
only warns) causes trouble. Known gaps: a SET with an empty name keeps any
|
||||
older name the row had (the spec says skip, not delete); a retry receipt is
|
||||
logged, not answered with a resend (whatsmeow `retry.go`); history sync
|
||||
notifications are not downloaded.
|
||||
|
||||
|
||||
|
||||
@@ -646,3 +663,49 @@ Everything here was measured on 2026-10-27/28 against the live server and the us
|
||||
Gaps: the contact sync and the address-book key share are not verified against a phone yet; a newly linked device
|
||||
must be created with this build for the phone to send history. Set `DETZ_WA_TRACE=1` to log every node's tag and
|
||||
attributes when debugging a protocol exchange.
|
||||
|
||||
## 16. Incident WA-2026-10-28: the phone never sends the app-state sync key
|
||||
|
||||
Full record: `problem.md`. A newly linked companion (`31630045827:54`) logged
|
||||
in, uploaded prekeys and sent an image to its own number (visible in "Message
|
||||
yourself", participant hash matching the server's), but the phone never sent
|
||||
the app-state sync key: 100 to 120 s of `DETZ_WA_TRACE=1` showed only
|
||||
`<success>`, `<ib edge_routing>` and `<ib dirty>`, no `<message>` at all.
|
||||
|
||||
The request path was checked line by line against whatsmeow `send.go`,
|
||||
`message.go`, `appstate.go`, `notification.go`, `client.go`, `pair.go` and
|
||||
`store/clientpayload.go` (main, fetched 2026-10-27). Registration, login,
|
||||
DeviceProps (including `historySyncConfig`), the peer node shape, the key
|
||||
request bytes and the padding all match. whatsmeow answers the open questions
|
||||
this way:
|
||||
|
||||
- A peer message needs a Signal session with the phone first:
|
||||
`encryptMessageForDevice` with no bundle and no session returns
|
||||
`ErrNoSession` and the request never leaves the process (hence whatsmeow PR
|
||||
1268, which rolls back the 24 h mark on send failure, and Volt-Chat PR 5,
|
||||
which adds an explicit session setup).
|
||||
- Own senders are the PN user or the LID user (`parseMessageSource`); no
|
||||
`recipient` attribute is needed. The `<ib dirty type=account_sync>` node
|
||||
needs no answer (whatsmeow `handleIB` leaves it alone).
|
||||
- The `jurisdiction` and `encryption-metadata` nodes in `pair-success` are
|
||||
server-side account metadata; whatsmeow `handlePairSuccess` ignores every
|
||||
child it does not know, as we do. Not related to key sharing.
|
||||
|
||||
Fixes in this build (all local; the phone side is still unseen):
|
||||
|
||||
| Gap | Fix | Evidence |
|
||||
|---|---|---|
|
||||
| a failed key request still stamped the 24 h mark, blocking the retry | `clear_app_key_request_time`, rollback on send failure (whatsmeow PR 1268) | test `key request rollback` |
|
||||
| a peer send was trusted without checking the session or the ack | session verified after the prekey fetch, ack error attribute checked and logged (Volt-Chat PR 5) | code, log lines |
|
||||
| device-identity rode on every peer message, whatsmeow only on pkmsg | `build_peer_message_node` matches whatsmeow | test `peer message helpers` |
|
||||
| key shares from our LID were silently dropped (PN-only filter) | `is_own_sender` accepts PN or LID, every arrival is logged before the filter | test `peer message helpers` |
|
||||
| an empty key share looked like no answer | stored/empty counted separately, the wait ends, `peer_msg` receipt sent (openclaw/wacli PR 443) | test `empty key share` |
|
||||
| notifications and receipts were ignored | `server_sync` re-syncs, `encrypt` refills prekeys, retry receipts logged loudly | test `peer message helpers` |
|
||||
| 20 s online is short for a sleeping phone | `detz --whatsapp-sync` stays online 300 s (`kWaSyncOnlineWait`) | wizard source |
|
||||
| no fallback when the phone never answers | `detz --whatsapp-import FILE` imports a .vcf into the contact search | test `vcf import` |
|
||||
|
||||
What to run now: link fresh if needed, then `detz --whatsapp-sync` with
|
||||
WhatsApp open on the phone (no battery restriction). If no `<message>` arrives
|
||||
within the 300 s, the phone is gating on its side; import the .vcf and retry
|
||||
the sync another day. Research: `research/whatsapp-automation/`
|
||||
(conclusion `key-share-never-arrives.md`).
|
||||
|
||||
Reference in New Issue
Block a user