Skip to content

fix(pair,props): enforce ADV HMAC verify and round-trip <prop> children - #594

Merged
jlucaso1 merged 4 commits into
mainfrom
fix/pair-hmac-and-props-round-trip
Apr 26, 2026
Merged

fix(pair,props): enforce ADV HMAC verify and round-trip <prop> children#594
jlucaso1 merged 4 commits into
mainfrom
fix/pair-hmac-and-props-round-trip

Conversation

@jlucaso1

Copy link
Copy Markdown
Collaborator

Summary

Two correctness bugs in wacore, surfaced while wiring an in-process
"phone simulator" in mock-server (bartender) that produces <pair-success>
with real HMACs.

1. wacore/src/pair.rs — ADV HMAC verification was skipped

PairUtils::do_pair_crypto parsed the HMAC bytes into _hmac_bytes and
never called mac.verify_slice(...). The previous TODO claimed QR
pairing's adv_secret_key couldn't reach the primary, but in fact:

  • PairUtils::make_qr_data embeds the 32-byte adv_secret_key directly
    in the QR string (5th field), so any peer that scans the QR knows it.
  • pair_code::prepare_key_bundle HKDF-derives the same adv_secret
    from the DH bundle, so the pair-code flow shares it too.

Without verification, accountSignature (ed25519) alone is not a
backstop — its verification key comes from the same untrusted
AdvSignedDeviceIdentity blob, so an attacker could mint a fresh
keypair and self-sign. WA Web's WAWebHandlePairSuccess.js:88-97 does
the HMAC check and calls logoutAfterValidationFail() on mismatch;
WhatsApp Android (FHU.A01 in the apk decompile) likewise.

The fix is mac.verify_slice(hmac_bytes)? returning a typed
PairCryptoError { code: 401, text: "hmac-mismatch" }. Two regression
tests added: do_pair_crypto_accepts_matching_hmac and
do_pair_crypto_rejects_mismatched_hmac.

2. wacore/src/iq/props.rsPropsResponse::into_node dropped children

PropsResponse::into_node only emitted attrs (ab_key, hash, refresh,
refresh_id, delta_update) and never iterated experiment_props,
producing an empty <props/> tag.

Empirical wire format (captured prod log + WA Web JS):

<iq from="s.whatsapp.net" type="result" id="...">
  <props protocol="1" ab_key="..." hash="..." refresh="84289" refresh_id="95">
    <prop config_code="11261" config_value="1"/>
    <prop config_code="11262" config_value="1"/>     <!-- 769 children total -->
    ...
  </props>
</iq>

WA Web parser (WASmaxInAbPropsGetExperimentConfigResponseSuccess.js:55)
calls mapChildrenWithTag(props, "prop", 0, infinity, …) to read every
child via WASmaxInAbPropsExperimentConfigMixin (which expects
config_code int >=1, config_value string, optional config_expo_key).

The fix iterates experiment_props and emits one <prop config_code … config_value …/> per entry. config_expo_key is dropped on both sides
of the round-trip (intentional, matches the lightweight tuple type
introduced in #553); extending to preserve it is a separate change.

The existing test_props_response_protocol_node_round_trip claimed to
round-trip but omitted the experiment_props assertion — the omission
that hid the regression. The assertion is now added, and a new test
(test_props_response_into_node_emits_wa_web_compliant_prop_children)
checks the wire structure against the WA Web schema explicitly.

Impact on production whatsapp-rust

  • Pair flow: clients now reject forged/tampered <pair-success> containers.
    Real WA server traffic is unaffected (it has the secret and produces
    matching HMACs).
  • Props serialization: clients only consume PropsResponse, never
    serialize one back, so this is a no-op for the production client.
    Server-side reusers of wacore (mock-server, hypothetical proxies)
    now get the correct wire format.

Test plan

  • cargo test -p wacore --lib — 613 passed (was 611 before the 2 new tests)
  • cargo test -p wacore --lib props — 11 passed including the new round-trip assertion
  • cargo test -p wacore --lib pair::tests — 17 passed including new HMAC-pass and HMAC-fail tests
  • cargo clippy -p wacore --all-targets — clean
  • Downstream validation: cargo test -p whatsapp-mock-server --test group_privacy_token_e2e (with this branch path-patched) goes from 1/2 to 2/2 passing, confirming the props fix unblocks the AB-prop-gated add_participants privacy attachment.

Two correctness bugs surfaced while wiring an in-process "phone simulator"
in mock-server (bartender) that produces pair-success with real HMACs.

pair.rs: `do_pair_crypto` discarded `hmac_bytes` and skipped the verify
step. The TODO claimed QR pairing's adv_secret_key didn't reach the
primary, but make_qr_data embeds it directly into the QR string and
prepare_key_bundle HKDF-derives it for pair-code, so both flows do
share the secret. Without the HMAC check, account_signature alone is
not a backstop because its verification key comes from the same
unauthenticated container.

props.rs: PropsResponse::into_node only emitted attrs and silently
dropped experiment_props. WA Web's parser
(WASmaxInAbPropsExperimentConfigMixin) and captured prod traffic both
require <prop config_code config_value/> children. The existing
`test_props_response_protocol_node_round_trip` was named round-trip but
omitted the experiment_props assertion, hiding the regression.

Production whatsapp-rust clients are unaffected by the props change
since they only consume PropsResponse; the bug was visible only to
server-side reusers of wacore (mock-server).
@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown

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: 4f69370c-ba90-45fd-888f-cc312431f561

📥 Commits

Reviewing files that changed from the base of the PR and between b742f5c and f45cb15.

📒 Files selected for processing (1)
  • tests/e2e/src/lib.rs

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Protocol output now emits one configuration property element per experiment entry with correct attributes.
    • Device pairing now verifies message authenticity and rejects tampered or forged pairings.
  • New Features

    • End-to-end pairing flow supports an out-of-process QR responder to drive QR-based pairing.
  • Tests

    • Expanded coverage for properties round-trip, pairing verification (matching/mismatched), and e2e QR pairing.

Walkthrough

HMAC verification was added to pairing: PairUtils::do_pair_crypto now verifies AdvSignedDeviceIdentityHmac HMAC and aborts on mismatch with 401/hmac-mismatch. PropsResponse::into_node now emits one <prop> child per experiment_props tuple with config_code/config_value attributes; tests updated accordingly.

Changes

Cohort / File(s) Summary
Props Protocol Structure
wacore/src/iq/props.rs
PropsResponse::into_node now maps each experiment_props (code, value) to a <prop config_code="..." config_value="..."/> child under <props>. Unit tests updated to assert child cardinality and attribute correctness.
Pairing HMAC Verification
wacore/src/pair.rs
PairUtils::do_pair_crypto now extracts hmac_bytes, computes expected HMAC over details_bytes (handles hosted vs non-hosted prefix), and fails pairing with PairCryptoError{code:401, text:"hmac-mismatch"} on mismatch. Tests add payload builder and matching/mismatched secret cases for hosted and non-hosted flows.
E2E Pairing Flow Tests
tests/e2e/src/lib.rs
Client test harness spawns an out-of-process "phone" responder that awaits Event::PairingQrCode and posts QR bytes to bartender's admin QR-scan endpoint; wiring adds a dedicated QR event receiver and async responder task to enable pairing without mock-server auto-pairing.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PairUtils
    participant Crypto
    participant ErrorHandler

    Client->>PairUtils: Receive AdvSignedDeviceIdentityHmac payload
    PairUtils->>PairUtils: Extract details_bytes and hmac_bytes
    PairUtils->>Crypto: Compute expected HMAC(details_bytes, prefix)
    Crypto-->>PairUtils: computed_hmac
    PairUtils->>Crypto: verify_slice(computed_hmac, hmac_bytes)
    alt HMAC Match
        Crypto-->>PairUtils: success
        PairUtils-->>Client: continue pairing flow
    else HMAC Mismatch
        Crypto-->>PairUtils: failure
        PairUtils->>ErrorHandler: create PairCryptoError(code=401, text="hmac-mismatch")
        ErrorHandler-->>PairUtils: error object
        PairUtils-->>Client: reject pairing
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically summarizes the two main fixes: ADV HMAC verification enforcement and children round-tripping in props serialization.
Description check ✅ Passed The description thoroughly explains both bugs, the reasoning behind fixes, impact analysis, and test results—clearly related to the changeset and well-documented.
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 fix/pair-hmac-and-props-round-trip

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

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Apr 25, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 1m 31s —— View job


Code Review: fix(pair,props): enforce ADV HMAC verify and round-trip <prop> children

  • Read PR diff and changed files
  • Review wacore/src/pair.rs changes
  • Review wacore/src/iq/props.rs changes
  • Post final review

Verdict: Approve — both fixes are correct, well-motivated, and well-tested. Two minor observations below, neither blocking.


1. wacore/src/pair.rs — ADV HMAC verification

The core fix (pair.rs:170-174) is exactly right: mac.verify_slice(hmac_bytes)? activates the HMAC check that was previously stubbed out with a TODO. The error mapping to PairCryptoError { code: 401, text: "hmac-mismatch" } is consistent with the existing 401 / "invalid-key" and "invalid-signature" patterns in the same function.

The PR description's security rationale is sound — account_signature alone isn't a backstop since the verification key comes from the same untrusted AdvSignedDeviceIdentity blob. Both pairing paths (QR and pair-code) share the adv_secret_key with the primary, so the HMAC can and should be verified.

Tests (build_pair_success_payload + two tests at lines 654-670):

  • do_pair_crypto_accepts_matching_hmac exercises the happy path through the entire do_pair_crypto pipeline.
  • do_pair_crypto_rejects_mismatched_hmac confirms tampered payloads fail with the correct error code and text.
  • The helper synthesizes a real ed25519 signature so the test exercises HMAC verification specifically (not accidentally failing on a later step).

Minor observation (non-blocking):

The new tests only exercise the non-hosted (account_type: Some(0)) path. The hosted-account branch (pair.rs:162-164) prepends ADV_HOSTED_PREFIX_ACCOUNT_SIGNATURE before the details bytes in the MAC computation — if a hosted payload were tested, it would exercise that prefix logic too. Not a blocker since the HMAC verification itself is path-independent (mac.verify_slice doesn't care what was update'd), but a hosted-account variant of build_pair_success_payload would strengthen coverage for a future change.


2. wacore/src/iq/props.rsinto_node now emits <prop> children

The fix (props.rs:279-290) iterates experiment_props and emits <prop config_code="..." config_value="..."/> children, matching the WA Web wire format (WASmaxInAbPropsExperimentConfigMixin). The .collect()builder.children(prop_nodes).build() pattern is clean.

config_expo_key intentional drop: The comment at line 277-278 documents that config_expo_key is dropped on both sides of the round-trip. The parse side (try_from_node_ref, line 311-318) indeed only extracts config_code and config_value into the (u32, CompactString) tuple. This is internally consistent — the AbProp struct has config_expo_key: Option<u32> and its own into_node/try_from_node preserves it, but PropsResponse.experiment_props uses the lightweight tuple. Acknowledged as intentional per the PR body.

Tests:

  • The existing test_props_response_protocol_node_round_trip (line 529) now includes the experiment_props assertion at line 550 — this is the key line that was missing and would have caught the original bug.
  • The new test_props_response_into_node_emits_wa_web_compliant_prop_children (line 556) validates the wire structure by inspecting NodeContent::Nodes directly, checking tag names, attribute presence, and values. Thorough.

Minor observation (non-blocking):

The new WA-Web-compliance test checks attribute presence but doesn't assert config_value values (only codes are spot-checked). Since the round-trip test already covers value equality, this is fine — just noting the distinction.


Summary

Both changes are well-scoped, correctly implemented, and meaningfully tested. The security fix in pair.rs closes a real gap where a forged <pair-success> with a self-signed keypair could bypass pairing validation. The props.rs fix restores correct wire serialization that downstream consumers (mock-server, etc.) depend on. LGTM.

@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 the current code and only fix it if needed.

Inline comments:
In `@wacore/src/iq/props.rs`:
- Around line 277-290: The serializer currently emits prop nodes for entries in
experiment_props even when config_code == 0, violating the parser invariant
(try_from_node_ref rejects config_code == 0); fix by filtering out those entries
before building nodes so wire output matches the parser rules: apply
.filter(|(code, _)| *code != 0) (or equivalent) to self.experiment_props
iterator used to build the NodeBuilder::new("prop") nodes and then pass the
filtered vec into builder.children(...).build() so no prop with config_code == 0
is emitted.
- Around line 553-604: The test
test_props_response_into_node_emits_wa_web_compliant_prop_children currently
only checks that each child has config_code and config_value but not that the
code maps to the correct value; update the assertion after building children to
collect (config_code, config_value) pairs from each child (using
child.attrs.get("config_code") and child.attrs.get("config_value")), and assert
the resulting collection equals the expected set/vec of tuples [("11262","1"),
("11103","0")] (or equivalent map equality), so each code is paired with its
correct value when PropsResponse.into_node is used.

In `@wacore/src/pair.rs`:
- Around line 611-670: The tests only cover account_type 0, missing the
hosted-account (account_type 1) branch in do_pair_crypto; add two tests that
mirror do_pair_crypto_accepts_matching_hmac and
do_pair_crypto_rejects_mismatched_hmac but target hosted accounts: either extend
build_pair_success_payload to take an account_type parameter (or add a new
helper) and ensure the synthesized AdvDeviceIdentity / AdvSignedDeviceIdentity
uses account_type: Some(1) and uses the hosted MAC input path expected by
do_pair_crypto; then call PairUtils::do_pair_crypto(&state, &payload) once with
state.adv_secret_key (expect Ok) and once with a wrong_secret (expect Err with
code 401 and text "hmac-mismatch"). Ensure the new tests reference
build_pair_success_payload (or the new helper), do_pair_crypto, and
state.adv_secret_key for clarity.
🪄 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: 0dec839e-5648-417b-9c6e-d701ea0e0f6f

📥 Commits

Reviewing files that changed from the base of the PR and between 2d82f2c and cef4c7d.

📒 Files selected for processing (2)
  • wacore/src/iq/props.rs
  • wacore/src/pair.rs

Comment thread wacore/src/iq/props.rs
Comment on lines +277 to +290
// Round-trip with try_from_node_ref. config_expo_key is dropped on
// both sides; extend the tuple type before adding it back.
let prop_nodes: Vec<Node> = self
.experiment_props
.into_iter()
.map(|(code, value)| {
NodeBuilder::new("prop")
.attr("config_code", code)
.attr("config_value", &*value)
.build()
})
.collect();

builder.children(prop_nodes).build()

@coderabbitai coderabbitai Bot Apr 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Enforce config_code invariant on serialization path.

This needs to be locked down: parser rejects config_code == 0, but serializer can still emit it from experiment_props. Keep wire output consistent with the same protocol invariant.

Suggested fix
-        let prop_nodes: Vec<Node> = self
+        let prop_nodes: Vec<Node> = self
             .experiment_props
             .into_iter()
+            .filter(|(code, _)| *code > 0)
             .map(|(code, value)| {
                 NodeBuilder::new("prop")
                     .attr("config_code", code)
                     .attr("config_value", &*value)
                     .build()
             })
             .collect();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Round-trip with try_from_node_ref. config_expo_key is dropped on
// both sides; extend the tuple type before adding it back.
let prop_nodes: Vec<Node> = self
.experiment_props
.into_iter()
.map(|(code, value)| {
NodeBuilder::new("prop")
.attr("config_code", code)
.attr("config_value", &*value)
.build()
})
.collect();
builder.children(prop_nodes).build()
// Round-trip with try_from_node_ref. config_expo_key is dropped on
// both sides; extend the tuple type before adding it back.
let prop_nodes: Vec<Node> = self
.experiment_props
.into_iter()
.filter(|(code, _)| *code > 0)
.map(|(code, value)| {
NodeBuilder::new("prop")
.attr("config_code", code)
.attr("config_value", &*value)
.build()
})
.collect();
builder.children(prop_nodes).build()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wacore/src/iq/props.rs` around lines 277 - 290, The serializer currently
emits prop nodes for entries in experiment_props even when config_code == 0,
violating the parser invariant (try_from_node_ref rejects config_code == 0); fix
by filtering out those entries before building nodes so wire output matches the
parser rules: apply .filter(|(code, _)| *code != 0) (or equivalent) to
self.experiment_props iterator used to build the NodeBuilder::new("prop") nodes
and then pass the filtered vec into builder.children(...).build() so no prop
with config_code == 0 is emitted.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

not worth

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@jlucaso1, understood — acknowledged and moving on.

Comment thread wacore/src/iq/props.rs
Comment thread wacore/src/pair.rs
@github-actions

github-actions Bot commented Apr 25, 2026

Copy link
Copy Markdown

Benchmark Results

67 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 3,933 3,933 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 12,038 12,038 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 43,514 43,514 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 19,365 19,365 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 68,579 68,579 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 76,679 76,679 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 2,230 2,230 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 5,988 5,988 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 169,520 169,085 +0.3%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 190,838 190,838 +0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 874,473 874,468 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 966,112 966,157 -0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 1,452,489 1,453,180 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 2,575,090 2,574,917 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 9,341,081 9,341,186 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 44,454,403 44,455,410 -0.0%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 12,681,779 12,491,364 +1.5%
binary_benchmark::marshal_group::bench_marshal_allocating 71,247 71,247 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 71,300 71,300 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 98,367 98,367 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 78,801 78,801 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 71,347 71,347 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 7,518 7,518 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 7,561 7,561 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 9,273 9,273 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 530,504 530,504 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 530,072 530,072 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 531,427 531,427 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 8,506,160 8,506,160 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 8,450,412 8,450,412 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 19,677,947 19,677,947 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 2,468 2,468 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 33,558 33,558 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 787 787 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 526,732 526,732 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 4,986 4,986 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 5,315 5,315 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 61,874 61,874 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 5,347 5,347 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 61,942 61,942 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 6,734 6,734 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 85,564 85,564 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 477,570 477,570 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 11,563 11,563 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u32 396 396 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u32 120 120 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u64 439 439 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u64 153 153 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_i64 499 499 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_i64 162 162 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_loop_100_u64 44,624 44,624 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_loop_100_u64 16,424 16,424 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 17,261,596 17,407,522 -0.8%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 157,113 157,113 +0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 5,510,200 5,510,200 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 157,827 157,827 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 296,767 296,767 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 706,282 706,282 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 12,570,045 12,579,828 -0.1%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 27,368,765 27,595,983 -0.8%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 3,467,011 3,467,011 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 126,262,053 125,787,273 +0.4%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,830,452 2,830,452 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 46,003 46,003 +0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 5,072,844 5,072,844 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 316,083 316,083 +0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 14,255,917 14,255,917 +0.0%
No significant changes detected.

Address review feedback on PR #594:

- props: assert (config_code, config_value) pairs preserve their
  original mapping, not just presence of each code. Previous version
  would have passed even if into_node mismapped values.

- pair: cover the hosted-account branch (account_type=1) of
  do_pair_crypto. The HMAC and account-signature paths apply
  ADV_HOSTED_PREFIX_ACCOUNT_SIGNATURE under that branch; without
  these tests a regression in the prefix wiring would be silent.
  build_pair_success_payload gains an `is_hosted` parameter and
  mirrors the verifier's hosted/E2EE branching.

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

♻️ Duplicate comments (1)
wacore/src/iq/props.rs (1)

279-288: ⚠️ Potential issue | 🟠 Major

Filter out invalid config_code values before emitting <prop> nodes.

This still serializes config_code == 0, while try_from_node_ref() only accepts codes > 0. That makes the new round-trip lossy again and can put invalid props on the wire.

Suggested fix
         let prop_nodes: Vec<Node> = self
             .experiment_props
             .into_iter()
+            .filter(|(code, _)| *code > 0)
             .map(|(code, value)| {
                 NodeBuilder::new("prop")
                     .attr("config_code", code)
                     .attr("config_value", &*value)
                     .build()
             })
             .collect();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wacore/src/iq/props.rs` around lines 279 - 288, The code currently serializes
entries from self.experiment_props including invalid config_code values (e.g.,
0) even though try_from_node_ref() only accepts codes > 0; update the creation
of prop_nodes to filter out invalid codes before mapping (e.g., use
.into_iter().filter(|(code, _)| *code > 0) or equivalent) so only entries with
config_code > 0 are turned into NodeBuilder::new("prop")... nodes, preventing
invalid props from being emitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@wacore/src/iq/props.rs`:
- Around line 279-288: The code currently serializes entries from
self.experiment_props including invalid config_code values (e.g., 0) even though
try_from_node_ref() only accepts codes > 0; update the creation of prop_nodes to
filter out invalid codes before mapping (e.g., use .into_iter().filter(|(code,
_)| *code > 0) or equivalent) so only entries with config_code > 0 are turned
into NodeBuilder::new("prop")... nodes, preventing invalid props from being
emitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 64d8b976-dc4f-4bc4-869d-0a84310f13b8

📥 Commits

Reviewing files that changed from the base of the PR and between cef4c7d and b742f5c.

📒 Files selected for processing (2)
  • wacore/src/iq/props.rs
  • wacore/src/pair.rs

The mock server (post-MockPhone-migration) no longer auto-pairs — its
legacy timer is off by default. External e2e harnesses that only have
a WebSocket to bartender must POST scanned QR strings to the new
`/admin/mock-phone/scan-qr` endpoint, the out-of-process equivalent of
bartender's in-process `spawn_qr_autoresponder`.

`spawn_qr_autoresponder_http` listens for `Event::PairingQrCode` on a
dedicated `ChannelEventHandler` (sharing the test's event_rx would
steal events from `wait_for_event` since async_channel is MPMC),
derives the admin URL from `MOCK_SERVER_URL` (scheme + path swap),
and POSTs the QR via the existing UreqHttpClient (already configured
with `danger-skip-tls-verify` for the self-signed mock cert).

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

ℹ️ 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 tests/e2e/src/lib.rs
Comment on lines +57 to +67
Ok(resp) => {
eprintln!(
"qr-autoresponder: admin POST returned status {}: {}",
resp.status_code,
String::from_utf8_lossy(&resp.body)
);
return;
}
Err(e) => {
eprintln!("qr-autoresponder: admin POST transport error: {e}");
return;

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 Retry failed admin QR scan requests

The autoresponder exits permanently on the first non-2xx response or transport error, so any transient failure from /admin/mock-phone/scan-qr leaves the client unpaired and causes TestClient::connect* to time out even though later QR events could succeed. This makes the full e2e suite flaky under brief mock-server startup or network hiccups; the loop should keep listening/retrying until a successful POST (or channel close).

Useful? React with 👍 / 👎.

@jlucaso1
jlucaso1 merged commit 06809e1 into main Apr 26, 2026
10 checks passed
@jlucaso1
jlucaso1 deleted the fix/pair-hmac-and-props-round-trip branch April 26, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant