Skip to content

fix(libsignal): keep skipped message-key seeds projectable - #1210

Merged
jlucaso1 merged 1 commit into
mainfrom
fix/legacy-session-skipped-key-seed
Aug 5, 2026
Merged

fix(libsignal): keep skipped message-key seeds projectable#1210
jlucaso1 merged 1 commit into
mainfrom
fix/legacy-session-skipped-key-seed

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

SessionRecord::into_legacy_session_v1_operational() failed with ChainNotRepresentable { field: DerivedMessageKey } for any session holding a skipped message key. The v1 model expresses a message key as its 32-byte seed; our native format persisted only the derived cipher key / MAC key / IV, and that derivation is one-way. The round trip was lossy from the very first cycle: importing a v1 record with a skipped key and immediately exporting it back already failed. Skipped keys come from out-of-order delivery (counter > chain_index), so this hit exactly the sessions that had fallen behind.

The seed now rides along with the keys it derives, in a new field on SessionStructure.Chain.MessageKey. It is additive, not a replacement: MessageKeyGenerator::from_pb still requires cipher+mac+iv, so a build that ignores the seed loads the record and decrypts normally. Only the export path reads it. Three places had to change together, since the seed was dropped in into_pb, had nowhere to live in the schema, and was rebuilt as Derived on every read.

Changes

  • The field is declared in build.rs and spliced into the descriptor, not added to whatsapp.proto. That file and whatsapp.desc are regenerated wholesale from whatspec, so a field written there would be lost on the next sync and would conflict on every regeneration. LOCAL_FIELDS in build.rs holds it instead, apply_local_fields splices it into the decoded FileDescriptorSet, and codegen reads the spliced copy from OUT_DIR. The whatspec-owned files are untouched by this PR.
  • Field number 100, not 5. Upstream syncs append low numbers without warning — kyberPreKeyId = 4 and kyberCiphertext = 5 landed on SessionStructure.PendingPreKey exactly that way — and a collision would silently reinterpret the seed in every record already on disk. 100 follows the range local_field.rs already reserves. The splice now fails the build if a sync lands on the number or the name, rather than resolving the race quietly.
  • Written unconditionally, not behind legacy-session-interop. The feature decides what is exportable, not what is persisted; gating it would make the on-disk format vary with build flags, and a binary without the feature would erase seeds written by one with it.
  • MessageKeyGenerator::into_pb emits the seed, and the MessageKey literal stays exhaustive so a field removed by a future sync is a compile error rather than a silent drop. The four fields are now written out of one shared Bytes buffer, so the skipped-key path does two allocations where it used to do three (the buffer, plus the refcount block the first split_to promotes).
  • MessageKeyGenerator::Keys(MessageKeys) removed. It was never constructed anywhere in the workspace — every skipped key comes from step_with_message_keysnew_from_seedSeed. Keeping it would have left one variant able to produce a seedless key with nothing deciding what that means. Breaking for anyone matching on the enum; construct via new_from_seed.
  • SessionMessageKeyComponents::from_structure returns Seed when the field is present, Derived when it is not, and verifies that the seed actually derives the triple stored beside it. The seed supersedes that triple on export, so one that reproduces something else would hand a consumer a key decrypting nothing, with no other signal. Both are written from the same material, so disagreement means a corrupt record and the projection fails closed — the same policy the projection already applies to Derived material.
  • SessionMessageKeyMaterial carries fixed-width arrays ([u8; 32] / [u8; 16]) instead of Vec<u8>. Those lengths were already invariants checked at every conversion; in the type they cost no allocation and no runtime check, and into_structure stops being fallible. Breaking for consumers constructing or matching the enum.
  • SessionMessageKeyMaterial's contract changed. The doc-comment used to promise "Exported records always use Derived" and no longer holds. Breaking: a record whose skipped keys retained their seed now exports Seed. Migration: match both variants. Derived is still what comes back for keys persisted before this change.
  • project_chain_parts uses an exhaustive match. It decided with matches! and closed with unreachable!, so a new SessionMessageKeyMaterial variant would have compiled without anyone deciding its v1 form. The refusal for seedless material is unchanged.
  • RESERVED_SENDER_CHAIN_INDEX_FIELD's comment corrected. It claimed the upstream proto cannot carry local fields at all; that is now half true — they exist, just not in the checked-in .proto — so the comment says why that field is still written by the hand-rolled encoder.
  • Documented where the schema actually lives. whatsapp.proto no longer describes everything on disk, and it cannot say so itself since it is overwritten on every sync, so the pointer to LOCAL_FIELDS goes in the build.rs module doc and in the AGENTS.md gotchas.
  • CI: a test run with legacy-session-interop. The feature is off by default and no existing job enabled it for a test run, so every test in legacy_session.rs was compiled away and never executed — including the one this PR had to change. Slightly outside the stated scope, but the new tests protect nothing without it.

Release notes

  • Downgrading erases seeds. waproto/build.rs sets preserve_unknown_fields(false), so a previous binary that reads a record and writes it back drops the seed silently. The affected keys go back to being unexportable; nothing corrupts and decrypt is unaffected.
  • Sessions already on disk with a skipped key stay unexportable, permanently. The seed cannot be reconstructed from the derived keys. Those sessions start projecting again only once the affected keys are consumed or evicted.

Cost

A saturated receiver chain (MAX_MESSAGE_KEYS = 2000 skipped keys) serialized as a SessionRecord: 182211 → 252211 bytes, +70000 (+38%). Per skipped key that is +35 bytes — 32 of seed plus a 2-byte tag and a 1-byte length — against ~91 bytes before. The realistic case is single-digit skipped keys per chain, so a few hundred bytes; the saturated figure is the ceiling, and it only applies to a chain that hit the 2000-key cap.

In memory, CodSpeed measures the same trade in both benchmarks that retain skipped keys: fewer allocation calls, more bytes.

bench_message_key_eviction bench_out_of_order_decryption
base head base head
peak 446508 B 586204 B (+31%) 14273 B 16209 B (+14%)
total allocated 466596 B 608868 B (+30%) 98974 B 112750 B (+14%)
alloc calls 604 404 (-33%) 747 671 (-10%)

The bytes are the design, not a surprise. size_of::<MessageKey>() goes from 104 to 136 — the fourth Option<Bytes> costs 32 — and the seed itself is another 32 on the heap, so 64 bytes per retained skipped key. The eviction benchmark pre-fills 1999 keys and inserts 200, leaving ~2199 live: 2199 x 64 = 140736 predicted against 139696 measured, 0.7% off. The out-of-order benchmark holds ~30 skipped keys: 30 x 64 = 1920 against 1936 measured.

Alloc calls fell by exactly 200 in a benchmark that inserts exactly 200 keys — one fewer per key written. Two is the floor for four views over one buffer; independent Bytes would be four allocations for the same 112 bytes. from_structure now allocates nothing at all for key material, where it used to allocate per field.

That benchmark saturates MAX_MESSAGE_KEYS by construction, so +31% is the ceiling. At the realistic handful of skipped keys per chain it is a few hundred bytes, and the alternatives are the ones already ruled out above: storing only the seed breaks downgrade, and gating on the feature makes the on-disk format vary with a build flag.

Validation

cargo fmt --all
cargo test -p wacore-libsignal --features legacy-session-interop     # 259 tests
cargo check --workspace --exclude e2e-tests --all-targets
cargo clippy -p wacore-libsignal -p waproto --all-targets --features legacy-session-interop -- -D warnings
cargo doc -p wacore-libsignal --no-deps --features legacy-session-interop

New coverage:

  • a_skipped_key_survives_a_full_v1_projection_cycle (integration): establish a real session, cycle it through the v1 model, provoke a skipped key by delivering Bob's second message before his first over the real message_encrypt/message_decrypt APIs, cycle through v1 again, then decrypt the skipped message from the reimported record. Verified to fail with ChainNotRepresentable { chain: 2, field: DerivedMessageKey } when the seed write is reverted.
  • a_retained_skipped_key_projects_back_to_its_seed — named regression for the symptom.
  • operational_projection_fails_closed_for_derived_skipped_keys — the contract change called out above. Its old setup (import a v1 record with a seed) now succeeds, which is the whole point of the PR, so it builds a genuinely seedless record instead and still asserts the same typed error.
  • skipped_seed_uses_the_canonical_message_key_derivation — same reason: it asserted that import expands the seed into Derived. It now checks the persisted structure directly, so it still pins the derivation, and additionally asserts the seed survives.
  • seedless_persisted_message_keys_still_load_and_decrypt, reloaded_keys_come_from_the_persisted_derived_material, a_persisted_key_without_a_seed_projects_as_derived — read compatibility with records written before this change.
  • a_persisted_seed_that_derives_other_keys_is_rejected, a_malformed_persisted_seed_is_rejected, from_pb_still_rejects_a_key_without_derived_material — failure paths.
  • invalid_seed_length_is_rejected deleted: it constructed a 31-byte Seed to assert it was refused, which the fixed-width type no longer allows. The equivalent check on persisted input lives in a_malformed_persisted_seed_is_rejected.

The build-time collision guard was checked by pointing the local field at an occupied number and confirming the build fails with the conflicting upstream field named.

Full matrix left to CI.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jlucaso1, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 297ab475-9bcd-4cff-9e55-3f3686c93902

📥 Commits

Reviewing files that changed from the base of the PR and between 786c635 and d5feb45.

📒 Files selected for processing (8)
  • .github/workflows/main.yml
  • AGENTS.md
  • wacore/libsignal/src/protocol/legacy_session.rs
  • wacore/libsignal/src/protocol/ratchet/keys.rs
  • wacore/libsignal/src/protocol/record_components.rs
  • wacore/libsignal/src/protocol/state/session.rs
  • wacore/libsignal/tests/legacy_session_skipped_keys.rs
  • waproto/build.rs
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Improved compatibility when importing and exporting legacy session data.
    • Preserves skipped-message key information across session state round trips.
    • Supports restoring message keys from saved session records, including records without seed data.
  • Bug Fixes

    • Prevents inconsistent or malformed key records from being accepted.
    • Ensures regenerated keys match their persisted values.
    • Added coverage for out-of-order message delivery and legacy session interoperability.

Walkthrough

The change persists skipped-message key seeds, validates seed-derived material, supports seed-backed legacy projection, rejects derived-only projection, and adds feature-gated v1 round-trip coverage.

Changes

Skipped-key seed preservation

Layer / File(s) Summary
Persist and validate message-key seeds
waproto/build.rs, wacore/libsignal/src/protocol/ratchet/keys.rs, wacore/libsignal/src/protocol/record_components.rs, wacore/libsignal/src/protocol/state/session.rs, AGENTS.md
The local descriptor stores optional seeds. Ratchet serialization preserves seeds. Record deserialization validates seed length and derived keys. Seedless records retain derived key material.
Project seed-backed keys to legacy format
wacore/libsignal/src/protocol/legacy_session.rs
Legacy projection converts retained seeds and rejects derived-only skipped keys with ChainNotRepresentable.
Exercise v1 round-trip and feature-gated coverage
wacore/libsignal/tests/legacy_session_skipped_keys.rs, .github/workflows/main.yml
The integration test verifies skipped-key decryption after v1 export and import. CI runs the feature-specific test step.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Peer
  participant SessionStore
  participant LegacySession
  Peer->>SessionStore: establish session and store skipped key
  SessionStore->>LegacySession: export session to v1
  LegacySession->>SessionStore: import v1 session record
  Peer->>SessionStore: load skipped key and decrypt message
Loading

Possibly related PRs

Suggested labels: api-design, breaking-change

Suggested reviewers: greptile-apps

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes preserving skipped message-key seeds for legacy session projection.
Description check ✅ Passed The description directly explains the skipped message-key seed preservation, serialization changes, compatibility behavior, and test coverage.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/legacy-session-skipped-key-seed

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.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

This PR retains skipped message-key seeds alongside their derived key material so sessions affected by out-of-order delivery can round-trip through the legacy v1 representation.

  • Adds a collision-checked local protobuf field through the build-time descriptor pipeline.
  • Persists and validates skipped-key seeds while retaining compatibility with older seedless records.
  • Updates legacy projection and component representations, with focused unit and end-to-end regression coverage.
  • Enables the legacy-session interoperability test suite in CI.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
waproto/build.rs Adds collision-checked descriptor splicing for the local skipped-key seed field and feeds the modified descriptor into code generation.
wacore/libsignal/src/protocol/ratchet/keys.rs Persists a seed beside its derived message-key material while preserving seedless protobuf compatibility.
wacore/libsignal/src/protocol/record_components.rs Uses fixed-width key material and validates that a persisted seed reproduces the derived keys before exposing it for projection.
wacore/libsignal/src/protocol/legacy_session.rs Projects retained seeds into legacy skipped-message keys while continuing to reject genuinely seedless derived material.
wacore/libsignal/src/protocol/state/session.rs Adds compatibility coverage showing older seedless persisted skipped keys still deserialize and decrypt.
wacore/libsignal/tests/legacy_session_skipped_keys.rs Exercises out-of-order delivery, legacy projection and reimport, and eventual skipped-message decryption through public protocol APIs.
.github/workflows/main.yml Runs the feature-gated legacy-session interoperability tests in CI.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Out-of-order message delivery"] --> B["Derive skipped message key from seed"]
  B --> C["Persist seed + cipher key + MAC key + IV"]
  C --> D["Reload native session"]
  D --> E["Validate seed derives stored key material"]
  E --> F["Project seed into legacy v1 session"]
  F --> G["Reimport and decrypt skipped message"]
Loading

Reviews (5): Last reviewed commit: "fix(libsignal): keep skipped message-key..." | Re-trigger Greptile

@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

🤖 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 `@wacore/libsignal/src/protocol/record_components.rs`:
- Around line 337-350: Update from_structure in record_components.rs so the
SessionMessageKeyMaterial::Seed path does not accept any valid-length seed
blindly; derive the cipher key, MAC key, and IV from the seed plus index and
verify they match the persisted derived triple before returning. Keep the
existing exact_bytes and invalid index handling, and add a regression test
covering a valid-length seed whose derived values do not match the stored
fields.
🪄 Autofix

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 (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4bb86b50-45c7-47cd-82a4-110cf6ab3a26

📥 Commits

Reviewing files that changed from the base of the PR and between 786c635 and 332927f.

📒 Files selected for processing (10)
  • .github/workflows/main.yml
  • wacore/libsignal/src/protocol/legacy_session.rs
  • wacore/libsignal/src/protocol/ratchet/keys.rs
  • wacore/libsignal/src/protocol/record_components.rs
  • wacore/libsignal/src/protocol/state/session.rs
  • wacore/libsignal/tests/legacy_session_skipped_keys.rs
  • waproto/build.rs
  • waproto/src/whatsapp.desc
  • waproto/src/whatsapp.desc.sha256
  • waproto/src/whatsapp.proto

Comment thread wacore/libsignal/src/protocol/record_components.rs Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 10 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread wacore/libsignal/src/protocol/record_components.rs Outdated
@jlucaso1
jlucaso1 force-pushed the fix/legacy-session-skipped-key-seed branch from 332927f to fad650e Compare August 5, 2026 16:35
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@jlucaso1
jlucaso1 force-pushed the fix/legacy-session-skipped-key-seed branch from fad650e to e714a11 Compare August 5, 2026 16:39

@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: 3

🤖 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 `@wacore/libsignal/src/protocol/ratchet/keys.rs`:
- Around line 501-516: Update
reloaded_keys_come_from_the_persisted_derived_material so the persisted cipher,
MAC, and IV values differ from MessageKeys::derive_keys(&seed, None, 4), while
retaining a valid seedless serialized record. Keep the existing reload
assertions, ensuring they verify the stored derived material rather than values
that could also result from re-deriving the seed.

In `@wacore/libsignal/src/protocol/state/session.rs`:
- Around line 734-738: Correct the documentation comment at the serialized
RecordStructure field decision point: state that
SessionStructure.Chain.MessageKey.seed is spliced into the descriptor by
waproto/build.rs via LOCAL_FIELDS, not declared in the upstream whatspec proto.
Review the full sentence to preserve the existing explanation that the field is
hand-encoded and deliberately assigned a high number.

In `@wacore/libsignal/tests/legacy_session_skipped_keys.rs`:
- Line 144: Update the registration_id initialization in the legacy session test
to always generate a non-zero value within the valid 14-bit range, using an
inclusive range such as 1 through 0x3FFF or an equivalent mask-plus-one
approach. Keep the generated value compatible with both Bundle::bundle() calls.
🪄 Autofix

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 (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3d2600b7-0ece-4696-9ab0-21b35da92d0e

📥 Commits

Reviewing files that changed from the base of the PR and between 786c635 and fad650e.

📒 Files selected for processing (7)
  • .github/workflows/main.yml
  • wacore/libsignal/src/protocol/legacy_session.rs
  • wacore/libsignal/src/protocol/ratchet/keys.rs
  • wacore/libsignal/src/protocol/record_components.rs
  • wacore/libsignal/src/protocol/state/session.rs
  • wacore/libsignal/tests/legacy_session_skipped_keys.rs
  • waproto/build.rs

Comment thread wacore/libsignal/src/protocol/ratchet/keys.rs
Comment thread wacore/libsignal/src/protocol/state/session.rs Outdated
Comment thread wacore/libsignal/tests/legacy_session_skipped_keys.rs
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 9.99 MiB 9.99 MiB +2.38 KiB (+0.02%) 🔺
bin .text 8.01 MiB 8.01 MiB +2.06 KiB (+0.03%) 🔺
bin allocated (text+data+bss) 9.99 MiB 9.99 MiB -12 B (-0.00%) 🔽
llvm-lines wacore 513,151 513,151 0
llvm-lines wacore copies 16,747 16,747 0
llvm-lines whatsapp-rust lib 736,306 736,306 0
llvm-lines whatsapp-rust lib copies 23,179 23,179 0
deps crates (Cargo.lock) 462 462 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.83 MiB 1.83 MiB +673 B (+0.04%) 🔺
.text wacore 689.71 KiB 689.78 KiB +64 B (+0.01%) 🔺
.text wacore_binary 91.42 KiB 91.42 KiB 0
.text wacore_libsignal 170.64 KiB 170.63 KiB -12 B (-0.01%) 🔽
.text wacore_appstate 22.35 KiB 22.35 KiB 0
.text wacore_noise 21.79 KiB 21.36 KiB -444 B (-1.99%) 🎉
.text waproto 1.74 MiB 1.74 MiB +1.08 KiB (+0.06%) 🔺
.text whatsapp_rust_sqlite_storage 515.62 KiB 515.62 KiB 0
.text whatsapp_rust_tokio_transport 40.49 KiB 40.49 KiB 0
.text whatsapp_rust_ureq_http_client 11.83 KiB 11.83 KiB 0
.text std 988.07 KiB 988.75 KiB +699 B (+0.07%) 🔺
.text other deps 1.90 MiB 1.90 MiB 0
Top movers (cargo-bloat attribution)
Crate main PR Δ
waproto 1.74 MiB 1.74 MiB +1.08 KiB (+0.06%)

Baseline: 786c635f6 (latest main run) · Head: f0c484aa8 · Graphs

@codspeed-hq

codspeed-hq Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 18.1%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 2 regressed benchmarks
✅ 214 untouched benchmarks
⏩ 2 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory bench_message_key_eviction 436 KB 572.5 KB -23.83%
Memory bench_out_of_order_decryption 13.9 KB 15.8 KB -11.94%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix/legacy-session-skipped-key-seed (d5feb45) with main (786c635)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@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: e714a1163c

ℹ️ 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 waproto/build.rs Outdated
@jlucaso1
jlucaso1 force-pushed the fix/legacy-session-skipped-key-seed branch from e714a11 to 7c88cbb Compare August 5, 2026 16:56
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

A session that skipped a message key could not be exported to the v1
model: v1 expresses a message key as its 32-byte seed, the native format
persisted only the derived cipher/mac/iv, and that derivation has no
inverse. The round trip was lossy from the first cycle, so importing a v1
record and exporting it back already failed with ChainNotRepresentable.

The seed now rides along with the keys it derives, in a new local field
on SessionStructure.Chain.MessageKey. It is written unconditionally: the
persisted format must not vary with build flags, or a binary without
legacy-session-interop would erase what one with it wrote. The derived
triple stays, so a build that ignores the seed still loads the record and
decrypts; only the export path reads it.

The field is declared in build.rs and spliced into the descriptor rather
than added to whatsapp.proto, which is regenerated wholesale from
whatspec and would lose it on the next sync. Field number 100, because
upstream appends low numbers without warning - kyberPreKeyId = 4 and
kyberCiphertext = 5 landed on SessionStructure.PendingPreKey that way -
and a collision would silently reinterpret the seed in every record
already on disk. The splice now fails the build on such a collision
instead of resolving it.

SessionMessageKeyMaterial carries fixed-width arrays instead of Vec<u8>.
The lengths were already invariants enforced at every conversion; in the
type they cost no allocation and no runtime check.

Writing the four persisted fields out of one buffer keeps the
skipped-key path at a single allocation, down from three before this
change.
@jlucaso1
jlucaso1 force-pushed the fix/legacy-session-skipped-key-seed branch from 7c88cbb to d5feb45 Compare August 5, 2026 17:04
@jlucaso1
jlucaso1 merged commit 37ae410 into main Aug 5, 2026
25 of 26 checks passed
@jlucaso1
jlucaso1 deleted the fix/legacy-session-skipped-key-seed branch August 5, 2026 17:19
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