Skip to content

feat(secret-encrypted): decrypt poll-edit / poll-add-option / event-edit envelopes - #645

Merged
jlucaso1 merged 2 commits into
mainfrom
feat/secret-encrypted-message-types
May 27, 2026
Merged

feat(secret-encrypted): decrypt poll-edit / poll-add-option / event-edit envelopes#645
jlucaso1 merged 2 commits into
mainfrom
feat/secret-encrypted-message-types

Conversation

@jlucaso1

Copy link
Copy Markdown
Collaborator

Summary

secret_encrypted_message previously only decrypted MESSAGE_EDIT; incoming EVENT_EDIT, POLL_EDIT and POLL_ADD_OPTION envelopes were silently dropped as "unsupported". All four SecretEncType variants share the same shape — they decrypt to a full Message proto with an empty AAD — and differ only in the use-case secret fed into the HKDF. This generalizes the decrypt path to all of them.

Verified against the real WhatsApp Web module WAWebAddonEncryption (docs/captured-js/WAWeb/Addon/Encryption.js): its E() maps each kind to a use-case secret (Event Edit / Message Edit / Poll Edit / Poll Add Option), and its function g gives these an empty AAD (only PollVote/EventResponse bind stanza+sender). Mirrors whatsmeow's DecryptSecretEncryptedMessage dispatch (commit fcbcaad5).

Design

  • Consumer-driven (no change): decryption needs the parent message's messageContextInfo.messageSecret, which only the consumer's store has — so the library exposes extract + decrypt primitives, exactly like the existing edit/poll-vote paths.
  • MESSAGE_SCHEDULE / UNKNOWN are rejected — neither WA Web nor whatsmeow assigns them a use-case secret.
  • Encrypted reactions / comments / event-responses use separate proto structures (not secret_encrypted_message) and are out of scope here.

Changes

  • wacore/src/message_edit.rs: general decrypt_secret_encrypted (+ _with_fallback) over ModificationType; decrypt_message_edit delegates.
  • src/features/message_edit.rs: SecretEncKind, extract_secret_encrypted, decrypt_secret_encrypted (+fallback). The MESSAGE_EDIT-only extract_envelope now delegates to the general extractor (no duplication); shared sender-resolution factored into one helper.
  • src/features/mod.rs: re-export SecretEncKind, SecretEncrypted.

Test plan

  • cargo fmt --all
  • cargo clippy --all --tests (clean)
  • cargo test --workspace --exclude e2e-tests (0 failures)
  • wacore: non-edit (POLL_EDIT) roundtrip; wrong use-case fails
  • features: all 4 kinds recognised; MESSAGE_SCHEDULE/UNKNOWN rejected; extract_envelope still only matches MESSAGE_EDIT; full POLL_EDIT extract→resolve-sender→decrypt roundtrip

…dit envelopes

secret_encrypted_message previously only decrypted MESSAGE_EDIT; the other
SecretEncType envelope variants were dropped. They share the same shape (decrypt
to a Message with an empty AAD) and differ only in the HKDF use-case secret, so
this generalizes the path to all supported kinds.

- wacore: decrypt_secret_encrypted(+_with_fallback) over ModificationType;
  decrypt_message_edit now delegates.
- features: SecretEncKind + extract_secret_encrypted + decrypt_secret_encrypted
  (+fallback); the MESSAGE_EDIT-only extract_envelope delegates to the general
  extractor. MESSAGE_SCHEDULE/UNKNOWN are rejected (no use-case secret).

Verified against WA Web WAWebAddonEncryption (E() use-case map + empty-AAD
function g) and mirrors whatsmeow fcbcaad5. Kept consumer-driven (no auto
dispatch) since decryption needs the parent message's messageSecret. Encrypted
reactions/comments/event-responses use separate proto structures and are out of
scope.
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1693a7d5-bcb1-4507-bb7d-85ef4617c7b3

📥 Commits

Reviewing files that changed from the base of the PR and between 14bbd4d and 1341b78.

📒 Files selected for processing (2)
  • src/features/message_edit.rs
  • src/lib.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Broader support for decrypting multiple kinds of secret-encrypted edits so more encrypted edit types are recognized.
  • Refactor

    • Centralized envelope parsing, decryption and target-sender resolution to make encrypted edit handling more consistent and robust.
    • Malformed envelopes still warned and ignored as before.
  • Tests

    • Extended tests covering all supported encrypted kinds, IV validation, and roundtrip decryption.

Walkthrough

Adds a generic secret-encrypted envelope extraction and decryption path (feature + wacore), refactors MESSAGE_EDIT to use it, normalizes target sender resolution, broadens exports to expose the new API, and extends tests to cover IV validation, kind filtering, fallback deduping, and a PollEdit roundtrip.

Changes

Secret-encrypted envelope handling

Layer / File(s) Summary
Core generic decryption (wacore)
wacore/src/message_edit.rs
decrypt_secret_encrypted validates 12-byte IV, builds AddonContext with ModificationType, calls decrypt_addon, and decodes plaintext into waproto::whatsapp::Message. Adds decrypt_secret_encrypted_with_fallback and refactors decrypt_message_edit wrappers. Tests added for IV-length cases and a PollEdit encrypt/decrypt roundtrip.
Secret-encrypted extraction and public API
src/features/message_edit.rs
Adds SecretEncKind enum and SecretEncrypted<'a> wrapper, extract_secret_encrypted with kind/IV validation, resolve_target_sender for JID normalization, and feature-layer decrypt_secret_encrypted / decrypt_secret_encrypted_with_fallback that dedupe no-op fallback retries. Tests include secret_msg helper, extraction assertions across supported/unsupported kinds, and PollEdit decryption roundtrip.
MESSAGE_EDIT refactor and exports
src/features/message_edit.rs, src/features/mod.rs, src/lib.rs
extract_envelope now reuses extract_secret_encrypted and filters to MESSAGE_EDIT. Public re-exports updated to include SecretEncKind and SecretEncrypted. Minor reformatting in src/lib.rs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • oxidezap/whatsapp-rust#618: Generalizes and refactors MESSAGE_EDIT receiver logic that this change builds upon and extracts into reusable secret-encrypted infrastructure.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately reflects the main change: generalizing secret-encrypted decryption to support poll-edit, poll-add-option, and event-edit envelopes beyond the previous MESSAGE_EDIT-only support.
Description check ✅ Passed Description comprehensively explains the generalization of secret-encrypted envelope decryption, design rationale, specific changes across files, and test coverage—directly aligned with the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/secret-encrypted-message-types

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/message_edit.rs (1)

75-119: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider consolidating duplicate fallback dedup logic.

Look, I need my code to be efficient and maintainable at scale. The JID normalization and fallback deduplication logic at lines 93-110 is essentially duplicated at lines 355-369. decrypt_with_fallback could delegate to decrypt_secret_encrypted_with_fallback with SecretEncKind::MessageEdit instead of reimplementing the same pattern.

This isn't blocking — the code works correctly — but when we're connecting billions of people, we can't afford diverging implementations of the same logic.

♻️ Suggested consolidation
 pub fn decrypt_with_fallback(
     enc_payload: &[u8],
     enc_iv: &[u8],
     message_secret: &[u8],
     original_msg_id: &str,
     original_sender_jid: &Jid,
     editor_jid: &Jid,
     fallback_original_sender: Option<&Jid>,
     fallback_editor: Option<&Jid>,
 ) -> Result<wa::Message> {
-    let primary_orig = original_sender_jid.to_non_ad().to_string();
-    let primary_editor = editor_jid.to_non_ad().to_string();
-    let primary = MessageEditContext {
-        original_msg_id,
-        original_sender_jid: &primary_orig,
-        editor_jid: &primary_editor,
-    };
-
-    let fb_orig = fallback_original_sender.map(|j| j.to_non_ad().to_string());
-    let fb_editor = fallback_editor.map(|j| j.to_non_ad().to_string());
-    let fb_orig_resolved = fb_orig.as_deref().unwrap_or(primary.original_sender_jid);
-    let fb_editor_resolved = fb_editor.as_deref().unwrap_or(primary.editor_jid);
-    let fallback_ctx = if fb_orig_resolved == primary.original_sender_jid
-        && fb_editor_resolved == primary.editor_jid
-    {
-        None
-    } else {
-        Some(MessageEditContext {
-            original_msg_id,
-            original_sender_jid: fb_orig_resolved,
-            editor_jid: fb_editor_resolved,
-        })
-    };
-
-    message_edit::decrypt_message_edit_with_fallback(
+    decrypt_secret_encrypted_with_fallback(
         enc_payload,
         enc_iv,
         message_secret,
-        &primary,
-        fallback_ctx.as_ref(),
+        SecretEncKind::MessageEdit,
+        original_msg_id,
+        original_sender_jid,
+        editor_jid,
+        fallback_original_sender,
+        fallback_editor,
     )
 }

Also applies to: 333-379

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/message_edit.rs` around lines 75 - 119, The duplicate JID
normalization and fallback deduplication in decrypt_with_fallback should be
consolidated by delegating to the existing
decrypt_secret_encrypted_with_fallback path using SecretEncKind::MessageEdit
instead of reimplementing the same logic; replace the manual
normalization/compare/fallback_ctx construction in decrypt_with_fallback with a
call to decrypt_secret_encrypted_with_fallback(enc_payload, enc_iv,
message_secret, original_msg_id, original_sender_jid, editor_jid,
fallback_original_sender, fallback_editor, SecretEncKind::MessageEdit) (or a
thin wrapper that adapts those parameters) so the shared logic lives only in
decrypt_secret_encrypted_with_fallback and you avoid duplicated normalization
and dedup handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/mod.rs`:
- Line 41: The crate root is missing exports for symbols re-exported in
features: add EncryptedEdit, SecretEncKind, and SecretEncrypted to the public
re-export list in lib.rs so they are available from the crate root (i.e.,
include these identifiers alongside any existing pub use ... exports in the
top-level re-export section).

---

Outside diff comments:
In `@src/features/message_edit.rs`:
- Around line 75-119: The duplicate JID normalization and fallback deduplication
in decrypt_with_fallback should be consolidated by delegating to the existing
decrypt_secret_encrypted_with_fallback path using SecretEncKind::MessageEdit
instead of reimplementing the same logic; replace the manual
normalization/compare/fallback_ctx construction in decrypt_with_fallback with a
call to decrypt_secret_encrypted_with_fallback(enc_payload, enc_iv,
message_secret, original_msg_id, original_sender_jid, editor_jid,
fallback_original_sender, fallback_editor, SecretEncKind::MessageEdit) (or a
thin wrapper that adapts those parameters) so the shared logic lives only in
decrypt_secret_encrypted_with_fallback and you avoid duplicated normalization
and dedup handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 51dff6bf-6284-4956-a205-5fae8d0e7c31

📥 Commits

Reviewing files that changed from the base of the PR and between 8a3487c and 14bbd4d.

📒 Files selected for processing (3)
  • src/features/message_edit.rs
  • src/features/mod.rs
  • wacore/src/message_edit.rs

Comment thread src/features/mod.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14bbd4da0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/features/mod.rs
pub use media_reupload::{MediaRetryResult, MediaReupload, MediaReuploadRequest};

pub use message_edit::EncryptedEdit;
pub use message_edit::{EncryptedEdit, SecretEncKind, SecretEncrypted};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-export the new secret-encrypted types at crate root

This adds SecretEncKind/SecretEncrypted to features, but src/lib.rs still only re-exports EncryptedEdit, so consumers following the existing crate-level API cannot import whatsapp_rust::SecretEncKind or whatsapp_rust::SecretEncrypted. This also violates the repo instruction in AGENTS.md that new features be exposed via src/features/mod.rs and re-exported in src/lib.rs; please add these new public types to the root pub use features::{...} list as well.

Useful? React with 👍 / 👎.

- lib.rs: re-export SecretEncKind/SecretEncrypted at the crate root next to the
  already-exported EncryptedEdit.
- decrypt_with_fallback now delegates to decrypt_secret_encrypted_with_fallback
  (kind = MessageEdit) instead of duplicating the JID normalization + fallback
  dedup logic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant