Skip to content

feat(libsignal): let a consumer provide the X25519 agreement - #1218

Merged
jlucaso1 merged 6 commits into
mainfrom
claude/x25519-crypto-provider-d6fs07
Aug 6, 2026
Merged

feat(libsignal): let a consumer provide the X25519 agreement#1218
jlucaso1 merged 6 commits into
mainfrom
claude/x25519-crypto-provider-d6fs07

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

SignalCryptoProvider is how a consumer swaps out our symmetric crypto: AES-256-CBC, AES-256-GCM, HMAC-SHA256 and the transport AEAD all route through it, but the X25519 agreement never did. PrivateKey::calculate_agreement called into curve25519 directly, so the implementation was fixed at compile time with no way around it from consumer code. That asymmetry costs anyone whose requirement is that every primitive run inside one approved cryptographic module (routing AES and HMAC but not the agreement is enough to sink the whole claim), and it puts a platform's faster X25519 out of reach while the same consumer can already reach its faster AES. The agreement is not a rare call either: RootKey::create_chain runs one per ratchet turn, which in an alternating conversation is per message. This adds x25519_agreement to the trait, with a default that is exactly the code the agreement ran before.

Changes

  • crypto/provider.rs — new x25519_agreement(&self, private_key: &[u8; 32], their_public_key: &[u8; 32]) -> Result<[u8; 32], CryptoProviderError> on SignalCryptoProvider. Raw bytes rather than PrivateKey/PublicKey so the contract external implementors write against doesn't carry our internal key representation, and curve-specific in name so a future curve gets its own method instead of every implementor having to handle a discriminator it may never see.
  • The default is the existing path, so nothing changes for a build without a custom provider and every trait implementation that exists today keeps compiling untouched. RustCryptoProvider deliberately does not override it: the default already is the pure path, and a second copy would be a second thing to keep correct.
  • The return is fallible so a backend that can refuse the operation says so, instead of panicking or answering with fabricated bytes that a session would build a root key from. The default cannot fail. The error rides the result path calculate_agreement already had: a new CurveError::AgreementFailed carrying the provider error, mapped to SignalProtocolError::KeyAgreementFailed, so "the backend refused" is distinguishable from "the peer key was malformed".
  • protocol/session_cipher.rs — a refused agreement now outranks the MAC-based verdicts in the decrypt candidate search. That search reports one verdict per message (duplicate, bad MAC, or "decryption failed"), and a backend failure landing in that pile came out as InvalidMessage, which would have the caller ask the peer to resend a message that was never corrupt. The refusal stays in the pile rather than returning on the spot, because the backend is asked with each candidate's own ratchet key and a sibling session with an open receiver chain agrees nothing at all — returning early there would lose a message that session could read. A replay still outranks both. Unreachable without a custom provider.
  • core/curve.rsPrivateKey::calculate_agreement now resolves through the active provider. Same match, same CurveError result type, same public signature; KeyPair::calculate_agreement delegates here, and KeyType has one variant, so this is the single interception point. The ratchet and X3DH call sites are untouched on purpose: one behavior, one place to keep in sync. The pure body moved to a pub(crate) fn x25519_agreement next to it, which is what the trait default calls.
  • crypto/mod.rspub fn x25519_agreement alongside the other delegating helpers, and the provider module doc now names the agreement in the list of primitives a provider owns.
  • No feature flag: the extension point holds always, the way it already does for AES and HMAC.
  • Signing and verification stay out. They lean on the Edwards cache and an encoding of their own, so folding them in here would mix two contracts with different shapes.

Checked while doing this:

  • from_bytes_without_cache is still what the pure path uses, so routing does not start building the Edwards cache. Pinned by a test that asserts the signing cache stays cold after an agreement.
  • Install ordering does move slightly: the agreement now counts as a crypto call, so the first provider() resolution can happen at the first DH instead of at the first symmetric operation. In the Noise handshake that is a few microseconds earlier in the same handshake (mix_shared_secret agrees, then mixes). A consumer that installs its provider before connecting is unaffected; one that installed it between the first DH and the first AES call would now get crypto provider already set. Worth a release note.
  • The client's NACK classification (src/message/receive.rs) still treats an unrecognized decrypt error as UnhandledError. Nothing regresses today, since no build without a custom provider can produce KeyAgreementFailed, but a consumer installing a fallible backend will want that path to treat it as transient. Left for a change in the client, alongside whatever retry policy that consumer wants.

Cost

Dispatch goes from a static, inlinable call to &'static dyn. Measured with the crate's divan bench, a temporary bench_x25519_agreement doing 10 agreements per iteration (added for the measurement, not part of this diff), 4 runs per variant, same machine back to back:

fastest (10 agreements) per agreement
before 572.5 / 576.2 / 573.7 / 573.0 µs ~57.3 µs
after (infallible) 573.0 / 573.2 / 572.9 / 575.1 µs ~57.3 µs
after (fallible) 572.6 / 573.5 / 573.3 / 573.4 µs ~57.3 µs

Best-of-runs spread across all three is under 1 µs on 572 µs, ~0.1%, well inside the run-to-run noise (medians on this box wander between 590 µs and 850 µs). Neither the virtual call nor the Result shows up against the scalar multiplication.

Validation

cargo fmt --all
cargo test -p wacore-libsignal          # lib + all integration binaries
cargo clippy -p wacore-libsignal -p wacore -p wacore-noise -p whatsapp-rust --all-targets -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc -p wacore-libsignal --no-deps

Tests added:

  • RFC 7748 §6.1 known-answer vector through PrivateKey::calculate_agreement with no provider installed, plus wrong-length key rejection: the default result is unchanged.
  • The same vector through a provider that implements only the previously-required methods, proving the default covers implementations written before this method existed.
  • tests/crypto_provider_x25519_agreement.rs (its own process because set_crypto_provider writes a global): a provider whose agreement is observably used, with the counter moving and the returned bytes being the provider's. Its answer is deliberately order-dependent, so the two sides of a session derive different roots and decryption fails as a typed SignalProtocolError rather than a panic. A third test drives that provider into a backend failure and asserts CurveError::AgreementFailed and SignalProtocolError::KeyAgreementFailed, never bytes.
  • tests/crypto_provider_agreement_failure.rs: a provider on the real primitive builds a live session, then refuses. Three shapes, all failing without the session_cipher.rs change or passing for the wrong reason with an early return: refusal mid-DH-ratchet on the current session, the same through the archived-session half of the candidate search (with the archived session still in place afterwards), and a refusal that must not end the search — a sibling session with an open receiver chain reads the message with no agreement at all.
  • The default-provider round trip with a mid-stream DH ratchet step is already covered by tests/session_divergence.rs (baseline_dm_ping_pong, dh_ratchet_step_preserves_decryption); both still pass.

Full matrix left to CI.

SignalCryptoProvider already lets a consumer supply AES-256-CBC,
AES-256-GCM, HMAC-SHA256 and the transport AEAD, but the X25519
agreement was wired straight to the in-crate implementation, so it
could not be routed anywhere. That gap is what blocks a consumer whose
requirement is that every primitive run inside one approved module, and
it also puts a platform's faster X25519 out of reach while the same
consumer can already reach its faster AES.

The trait gains x25519_agreement over raw 32-byte keys, with a default
that runs the same code the agreement ran before, so existing trait
implementations keep compiling and the default build produces the same
bytes. Routing happens at PrivateKey::calculate_agreement, the one
place the agreement is decided: keeping it there means the ratchet and
X3DH call sites stay untouched and there is a single point to keep in
sync with the provider.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added X25519 key agreement support through the active cryptographic provider.
    • Added provider-based routing for key agreement operations.
  • Bug Fixes

    • Improved handling of invalid key lengths and low-order inputs.
    • Session decryption now reports clear, typed failures when key agreement fails.
    • Provider failures are now surfaced as key-agreement errors without obscuring session state.
  • Tests

    • Added coverage for standard X25519 vectors, provider delegation, cache behavior, and error handling.

Walkthrough

The change routes X25519 agreement through the active crypto provider with a pure-Rust default. It adds typed provider-error propagation, preserves uncached signing behavior, and validates current and archived session decryption recovery.

Changes

X25519 Provider Agreement

Layer / File(s) Summary
Agreement API and default implementation
wacore/libsignal/src/core/curve.rs, wacore/libsignal/src/crypto/mod.rs, wacore/libsignal/src/crypto/provider.rs
The crate adds provider-backed x25519_agreement. The default provider uses the uncached curve implementation. Provider failures map to CurveError::AgreementFailed.
Curve and provider validation
wacore/libsignal/src/core/curve.rs, wacore/libsignal/src/crypto/provider.rs
Tests cover RFC 7748 vectors, malformed key lengths, signing-cache state, default-provider behavior, and low-order inputs.
Protocol error propagation and decryption handling
wacore/libsignal/src/protocol/error.rs, wacore/libsignal/src/protocol/session_cipher.rs
Provider failures map to SignalProtocolError::KeyAgreementFailed. Current and archived session decryption restore state before returning the error.
Substituted provider session coverage
wacore/libsignal/tests/crypto_provider_x25519_agreement.rs, wacore/libsignal/tests/crypto_provider_agreement_failure.rs
Integration tests verify provider routing, argument handling, typed failures, candidate-search behavior, state preservation, and recovery after the backend resumes.

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

Sequence Diagram(s)

sequenceDiagram
  participant SessionCipher
  participant x25519_agreement
  participant SignalCryptoProvider
  participant SignalProtocolError
  SessionCipher->>x25519_agreement: Request X25519 agreement
  x25519_agreement->>SignalCryptoProvider: Forward key material
  SignalCryptoProvider-->>x25519_agreement: Return shared secret or provider error
  x25519_agreement-->>SessionCipher: Return agreement result
  SessionCipher->>SignalProtocolError: Convert provider failure
  SignalProtocolError-->>SessionCipher: Return KeyAgreementFailed
Loading

Suggested labels: api-design

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly and concisely describes adding consumer-provided X25519 agreement support.
Description check ✅ Passed The description directly explains the X25519 provider integration, error propagation, decryption behavior, tests, and compatibility impact.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/x25519-crypto-provider-d6fs07

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 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes X25519 agreement replaceable through SignalCryptoProvider while retaining the existing implementation as the default. It also propagates backend agreement failures through typed curve and protocol errors and preserves those failures across session-candidate decryption.

  • Adds a fallible provider hook and routes PrivateKey::calculate_agreement through it.
  • Maps provider refusals to CurveError::AgreementFailed and SignalProtocolError::KeyAgreementFailed.
  • Updates decrypt-candidate error selection and adds provider-routing, compatibility, and recovery tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
wacore/libsignal/src/core/curve.rs Routes key agreement through the active provider while retaining the prior curve implementation as the default.
wacore/libsignal/src/crypto/provider.rs Extends the provider contract with a backward-compatible, fallible X25519 hook.
wacore/libsignal/src/protocol/error.rs Adds typed propagation of provider agreement failures into the protocol error surface.
wacore/libsignal/src/protocol/session_cipher.rs Preserves backend agreement failures after candidate-session search while retaining duplicate-message precedence.
wacore/libsignal/tests/crypto_provider_agreement_failure.rs Exercises refusal, recovery, archived-session restoration, and sibling-session fallback behavior.
wacore/libsignal/tests/crypto_provider_x25519_agreement.rs Verifies provider dispatch, typed failures, and the effect of an inconsistent custom agreement.

Sequence Diagram

sequenceDiagram
  participant Session as X3DH / Double Ratchet
  participant Key as PrivateKey
  participant Crypto as crypto::x25519_agreement
  participant Provider as SignalCryptoProvider
  Session->>Key: calculate_agreement(peer_public)
  Key->>Crypto: x25519_agreement(private, public)
  Crypto->>Provider: x25519_agreement(private, public)
  alt Agreement succeeds
    Provider-->>Session: shared secret
  else Backend refuses
    Provider-->>Key: CryptoProviderError
    Key-->>Session: CurveError::AgreementFailed
    Session-->>Session: SignalProtocolError::KeyAgreementFailed
  end
Loading

Reviews (6): Last reviewed commit: "fix(libsignal): let the candidate search..." | Re-trigger Greptile

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 6, 2026

@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/tests/crypto_provider_x25519_agreement.rs`:
- Line 241: Update the test fixture’s Peer::new and bundle() flow to retain the
provided device_id alongside ProtocolAddress and pass that stored value into
PreKeyBundle::new instead of the hardcoded 1u32. Preserve the existing device-id
behavior for all peers while ensuring bundle() reports the peer’s actual device
id.
🪄 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: 962515e3-00a5-4b7e-bbc0-5b82a96e377d

📥 Commits

Reviewing files that changed from the base of the PR and between 3237c72 and abdb0d2.

📒 Files selected for processing (4)
  • wacore/libsignal/src/core/curve.rs
  • wacore/libsignal/src/crypto/mod.rs
  • wacore/libsignal/src/crypto/provider.rs
  • wacore/libsignal/tests/crypto_provider_x25519_agreement.rs

Comment thread wacore/libsignal/tests/crypto_provider_x25519_agreement.rs
@greptile-apps
greptile-apps Bot dismissed their stale review August 6, 2026 20:55

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 6, 2026

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

ℹ️ 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 wacore/libsignal/src/crypto/provider.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.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: This adds a security-sensitive override point to the public SignalCryptoProvider trait and changes when the provider global is resolved. Approving the new crypto API and its operational tradeoffs should go to a human.

Re-trigger cubic

@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.

Review completed against the latest diff

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

Re-trigger cubic

Comment thread wacore/libsignal/src/crypto/provider.rs Outdated
Comment thread wacore/libsignal/tests/crypto_provider_x25519_agreement.rs
A substituted agreement can be backed by something that refuses the
operation, and an infallible return left such a provider two bad
options: panic, or answer with fabricated bytes that a session would
then build a root key from, surfacing much later as a MAC failure that
points nowhere near the cause.

x25519_agreement now returns Result. The trait default is still this
crate's own implementation and still cannot fail, so a build without a
custom provider is unchanged and existing implementations keep
compiling. The error travels the path calculate_agreement already had:
a new CurveError variant carrying the provider error, mapped onto a
SignalProtocolError variant of its own, so a caller can tell "the
backend refused" from "the peer key was malformed".
@greptile-apps
greptile-apps Bot dismissed their stale review August 6, 2026 22:44

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 6, 2026

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

ℹ️ 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 wacore/libsignal/src/core/curve.rs

@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.

0 issues found across 5 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Adds a public crypto-provider override for X25519 and moves provider resolution into the DH path, changing a security-sensitive API and install-ordering contract; a human should approve this architectural/security tradeoff.

Re-trigger cubic

The candidate-session search collects each state's failure and reports
one verdict for the message: duplicate, bad MAC, or "decryption
failed". A backend that refuses the agreement mid-ratchet was landing
in that pile and coming out as InvalidMessage, so the caller would ask
the peer to resend a message that was never corrupt, and the real cause
would be invisible.

A refused agreement is not evidence about the message: every sibling
state would ask the same backend and get the same answer. Return it as
itself, the way an already-consumed body already does. Unreachable
without a custom provider, since the default agreement cannot fail.
@greptile-apps
greptile-apps Bot dismissed their stale review August 6, 2026 22:56

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

@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/tests/crypto_provider_agreement_failure.rs`:
- Around line 384-403: Extend the agreement-failure test around the existing
ratcheted-message scenario to archive the live session before delivery, then
send and receive a message readable only by that archived session while REFUSING
is enabled. Assert the receive returns CryptoProviderError::BackendFailed and
verify record.previous_session_count() remains unchanged after the failure,
covering the archived-session restore_previous_session path.
🪄 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: e7456bd2-0f5b-4e1c-83f0-f136f979330f

📥 Commits

Reviewing files that changed from the base of the PR and between e69916d and 1240a05.

📒 Files selected for processing (2)
  • wacore/libsignal/src/protocol/session_cipher.rs
  • wacore/libsignal/tests/crypto_provider_agreement_failure.rs

Comment thread wacore/libsignal/tests/crypto_provider_agreement_failure.rs
The first half of the test returns from the current-state arm, so the
candidate loop over archived sessions never ran. Archiving the live
session leaves no current state, which makes that loop the only path,
and the message still decrypts afterwards: the session the loop
borrowed has to come back where it was.

@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.

0 issues found across 2 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Auto-approved: Adds a pluggable X25519 agreement to the crypto provider trait with a default that preserves existing behavior byte-for-byte, backed by RFC 7748 vectors, backward-compat tests, and a new error path that correctly separates provider failures from message corruption.

Re-trigger cubic

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

ℹ️ 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 wacore/libsignal/src/protocol/session_cipher.rs Outdated
Comment thread wacore/libsignal/src/protocol/session_cipher.rs Outdated
Returning the refusal on the spot was too eager. The backend is asked
with the candidate's own ratchet key, and a sibling session that
already has an open receiver chain agrees nothing at all, so a message
that session could read was being reported as a backend failure and
lost.

The refusal goes back in the pile and the search continues. It then
outranks the MAC-based verdicts when nothing could read the message, so
a caller still tells "this backend is down" from "this message is
corrupt", and a replay still wins over both.

@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.

0 issues found across 2 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Adds a new X25519 agreement method to the crypto provider trait and modifies session decryption error prioritization. These changes affect the security contract of the provider and introduce a new error flow, which require human review of architectural and security tradeoffs.

Re-trigger cubic

jlucaso1 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

CI note: Semver Checks (informational) is red, and it isn't from this diff. That job runs -p wacore -p wacore-binary -p waproto, so it never looks at wacore-libsignal, and every failure it lists is waproto structs and modules missing against the published 0.6.0 — regenerated protobufs, same on main. The job is continue-on-error: true and advisory by design.

Everything else that has finished is green: format, clippy, rustdoc, stable tests, no-simd tests, E2E, wasm32, cargo-deny, all four Miri jobs (including wacore-libsignal (address)), and the CodSpeed runs. Build & Test, all-features lint, the feature matrix and binary size are still running.


Generated by Claude Code

@codspeed-hq

codspeed-hq Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will improve performance by 11.13%

⚡ 2 improved benchmarks
✅ 234 untouched benchmarks
⏩ 2 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation bench_unmarshal_fanout 20 µs 18 µs +11.36%
Simulation bench_roundtrip_auto_small 12 µs 10.9 µs +10.89%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/x25519-crypto-provider-d6fs07 (892e4a4) with main (8756097)2

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.

  2. No successful run was found on main (3237c72) during the generation of this report, so 8756097 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 9.99 MiB 9.99 MiB -768 B (-0.01%) 🔽
bin .text 8.01 MiB 8.00 MiB -5.19 KiB (-0.06%) 🔽
bin allocated (text+data+bss) 9.99 MiB 9.99 MiB +188 B (+0.00%) 🔺
llvm-lines wacore 513,194 513,103 -91 (-0.02%) 🔽
llvm-lines wacore copies 16,746 16,751 +5 (+0.03%) 🔺
llvm-lines whatsapp-rust lib 736,311 736,307 -4 (-0.00%) 🔽
llvm-lines whatsapp-rust lib copies 23,177 23,173 -4 (-0.02%) 🔽
deps crates (Cargo.lock) 462 462 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.83 MiB 1.83 MiB -439 B (-0.02%) 🔽
.text wacore 689.31 KiB 685.66 KiB -3.65 KiB (-0.53%) 🔽
.text wacore_binary 91.30 KiB 91.67 KiB +370 B (+0.40%) 🔺
.text wacore_libsignal 170.71 KiB 173.44 KiB +2.72 KiB (+1.59%) ⚠️
.text wacore_appstate 22.35 KiB 22.35 KiB 0
.text wacore_noise 21.36 KiB 20.94 KiB -431 B (-1.97%) 🎉
.text waproto 1.74 MiB 1.74 MiB 0
.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.75 KiB 988.84 KiB +87 B (+0.01%) 🔺
.text other deps 1.90 MiB 1.90 MiB -3.77 KiB (-0.19%) 🔽
Top movers (cargo-bloat attribution)
Crate main PR Δ
wacore 689.31 KiB 685.66 KiB -3.65 KiB (-0.53%)
x25519_dalek 4.59 KiB 1.12 KiB -3.47 KiB (-75.58%)
wacore_libsignal 170.71 KiB 173.44 KiB +2.72 KiB (+1.59%)

Baseline: 8756097b0 (latest main run) · Head: 84ab2680c · Graphs

@jlucaso1
jlucaso1 merged commit 20076ce into main Aug 6, 2026
26 of 27 checks passed
@jlucaso1
jlucaso1 deleted the claude/x25519-crypto-provider-d6fs07 branch August 6, 2026 23:39
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.

2 participants