Repository navigation
Conversation
…its application key Steps 10 and 11 of the shared network plan (ADR 0037, "Nothing in the clear"). With `ProtocolConfig.app_publisher_key` set, nothing a carrier shows outside a frame is derived from the application id any more: the Bluetooth LE App tag, the Android Wi-Fi Direct group name, passphrase and `app` record value, and the browser nearby room are all derived from `K_app`, which another application's developer cannot compute. With no publisher key every value is byte for byte what it was. One implementation, in the engine. `AppIdentifiers` (offline-protocol/src/protocol/app_identifiers.rs) derives each value from the same `K_app` the membership exchange uses, under a label of its own from `offline-protocol-sealed::membership`: - App tag: HMAC-SHA256(K_app, "offline-app-ble-tag-v1" || device_id)[..8], over the Device id exactly as served, so it is per instance and groups nothing; - Wi-Fi Direct: HKDF(K_app) under "offline-app-wifi-direct-record-v1", "-network-v1" and "-passphrase-v1", in the shapes the application-id values had; - nearby: a 32-byte secret under "offline-app-web-nearby-v1" replaces the application id at the front of the room's input. The bridges ask for it rather than mirroring an HMAC: two new UDL methods, `ble_app_tag(device_id)` and `wifi_direct_identifiers()`, both null without a publisher key, answered from a copy taken at construction so the Android peripheral's binder-thread App tag read never waits on the engine lock. The browser asks its engine (`bleAppTagIsKeyed`, `bleKeyedAppTag`, `nearbyRoom`); the page cannot configure a publisher key yet, so its keyed paths are exercised from Rust only. The central selection rule changes for a configured central only: with several instances it also reads Device id on each tagged instance and takes the first whose tag equals the keyed tag over that Device id, then rules 2 and 3 as before. A Device id read while choosing is routed to the choice, never the handshake, and one answered late from an unchosen instance is dropped. An unconfigured central reads no Device id before it chooses, as before. Vectors: an `identifiers` block in app-membership-v1.vectors.json, computed by tools/spec-vectors/generate.py over hashlib, checked by the sealed crate, the engine and the browser crate; the legacy nearby topic is pinned from an independent computation. Selection tests on both platforms and Robolectric tests of the Android probe; a source guard pins that every bridge takes the keyed values from the engine and that BleAppTag computes no HMAC. Every binding regenerated: Swift, Kotlin, Python, Ruby, Java, the C dispatcher, the C# generated sources and the Python local API table. The two methods are platform operations on the local API: carrier drivers read them, and the passphrase is a secret no client needs. Specs: ble-framing.md (the two App tag forms and the keyed selection rule), control-messages.md (the identifier derivations), web.md, local-api.md.
This was referenced Nov 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Steps 10 and 11 of the shared-network plan (ADR 0037). Stacked on #692.
A configured application (one with a publisher key) no longer shows its application id on the air. Every identifier a carrier exposes outside a frame is derived from
K_appunder its own label. Unconfigured applications produce exactly the same bytes as before: the legacy app-tag vectors and an independently computed legacy nearby topic still match.The derivations (
control-messages.md, "What the application shows on the air")Pinned by the
identifiersblock ofapp-membership-v1.vectors.json. The sealed crate, the engine and the browser crate all check it.Shape
AppIdentifierslives inprotocol/app_identifiers.rsand uses the sameK_appas the exchange. The bridges call it, so there are no new hand mirrors. The legacy SHA-256 tag mirrors stay, and run only without a publisher key.sequence<u8>? ble_app_tag(string device_id)andWifiDirectIdentifiers? wifi_direct_identifiers(). Both return null without a publisher key. Both read a copy taken at construction rather than going through the engine lock, because Android answers App tag reads on a binder thread. A test holds the engine lock and still gets an answer. Every binding is regenerated.BleServiceInstanceSelection.selectKeyed: a configured central reads Device id only on tagged instances while it chooses, and takes the first instance whose tag matches. Device id is unauthenticated, so a match only picks an instance.WifiDirectManagertakes all three values fromwifiDirectIdentifiers(), and falls back to the app-id derivations without a key.bluetooth.jsasks the engine for the keyed tag, andworker.jsopens nearby rooms throughengine.nearbyRoom.Guards
the_page_takes_keyed_identifiers_from_the_enginechecks the page side.Decisions to review
offline-app-ble-tag-v1), for domain separation from the hello HMAC. The ADR line readsHMAC(K_app, instance_address)and could use that word.appPublisherKeyandsetAppMembershipto the page is a follow-up.Checks
Rebased onto #692. The UDL, dispatch, local-API, vector and sealed-test conflicts were merged by keeping both sides, and every generated artefact (bindings, C dispatcher, C header, local API table, C#, vectors) was regenerated afterwards.
cargo clippy --workspace -- -D warningsandcargo test --workspace --libpass.cargo test -p offline-protocol-uniffi: 319 passed, guards included.cargo test -p offline-protocol-web: 92 of 93 pass. The one failure isno_shipped_file_names_a_relay, and its only offenders are under a stale local.claude/worktrees, so it does not apply to CI.swift testfor the iOS sources: 505 pass.Not run:
bindings/web/scripts/build.shandtest-browser.sh(the sandbox refused the bash scripts;node --checkpassed on the three edited JS files), and no device runs. CI runs only once the stack below merges into main.