Conversation
The wrapper exposed Bluetooth as eight bridge methods and let the page run
the mdoc protocol over them. That put device engagement, session keys, the
transcript, chunking and response assembly in TypeScript, and it meant the
page never authenticated the reader: no certificate chain was extracted,
nothing was verified, nothing was asked about trust.
This replaces the byte pipe with a session. The page starts one and gets an
engagement URI back for its QR code; the SDK runs engagement, both BLE
roles, NFC static handover, session establishment, reader authentication
and device-response assembly, and calls back into the page for the three
things the page still owns: which credentials it will present, the user's
consent, and a signature from a key that never leaves it.
Two pieces:
JsCallHost calls from native into the page and waits. Correlation ids,
timeouts, cancellation, and failing everything outstanding
when a page commit replaces the handler registry. Payloads
are UTF-8 JSON in base64 both ways, so quoting, newlines and
U+2028/U+2029 stop being hazards and binary needs no second
encoding. The older resolve path, which interpolates into a
single-quoted string with no escaping, is left alone;
WebAuthn is its last user.
ProximityBridge wiring only. Turns the SDK's seven injected lambdas into
bridge calls and back.
Removed: bluetooth/ (client, server, characteristics, extensions), the two
debug GATT printers, the eight bluetooth* bridge methods and the two mode
accessors, and their shim wrappers. No NFC service is declared here because
the SDK's own library manifest contributes it.
Net 806 lines fewer, and one implementation instead of three.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The SDK is published to GitHub Packages, which is per-repository and authenticated, so reading it from this repo needs a token that can see another repository's packages. The default GITHUB_TOKEN cannot, which is why this run failed with the message the settings block now prints. Uses the same actor and secret the release workflow already uses. If the SDK moves to a channel with anonymous reads, both of these go away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The SDK is moving to Maven Central for 0.15.0, which needs no repository block and no token. Both places that exist only because the current channel is authenticated now say so and name the version, so bumping the catalog pin is enough to find them: the repository block in settings.gradle.kts and the actor/token pair in push.yml. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0.14.0 is the first SDK release on Maven Central, so the SDK itself now resolves from mavenCentral(). Publishing it there turned out to be necessary but not sufficient: its own native dependencies are not on Central, and a build without credentials fails on all five. org.siros:siros-wscd-manager org.siros:zk-cred-vega org.siros:zk-cred-longfellow org.siros:zk-cred-bbs org.siros:siros-dc-matcher GitHub Packages serves every package in the org through any repository-scoped URL the token can read, which is why one block covered these before and still does. So the repository block and the workflow token stay, but they now say what they are actually for and name the real removal condition: those five reaching Central, not the SDK. Verified: a build with GITHUB_ACTOR/GITHUB_TOKEN unset and gpr.* blanked fails on exactly those five and nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s not enough The README framed these as FaceTec-only and optional, and said CI needs no additional secrets. Both are now wrong. Every build needs them, because the SDK's native dependencies are on GitHub Packages, and the first CI run on this branch failed for exactly the reason the README said would not happen: the default GITHUB_TOKEN cannot read another repository's packages. Also records which kind of PAT works. A classic token with read:packages sees the whole organisation whatever repository URL Gradle asks for. A fine-grained token scopes by the repository that owns the package, so it needs all five crate repositories selected, and picking only siros-sdk-kotlin is the intuitive choice that does not work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tladesignz
self-requested a review
September 15, 2026 14:28
|
As regards to review question 1, this approach seems reasonable - me and @smncd were discussing if this "offline trust validation" (checking if a certificate is signed by a trusted CA) should be performed by the frontend, but considering that the SDK likely already wanna provide this functionality for other users and that this is primarily applicable to offline usecases, the SDK validation is likely the best until the day we pickup WebBLE again. |
tladesignz
reviewed
Sep 15, 2026
Collaborator
|
@smncd and @yimyitbarek should have a look at this too, I guess since it affects them very much, too, AFAIU. |
yimyitbarek
approved these changes
Sep 18, 2026
leifj
marked this pull request as ready for review
September 18, 2026 21:29
Author
|
@yimyitbarek told me he tested a build for this and it works. |
Member
|
I skimmed this and I have some thoughts about the bridge/interface. Will elaborate on Monday :) |
Both from review. The SDK's five native crates are on Maven Central now, so the repository block and the CI token pair come out. A build with GITHUB_ACTOR and GITHUB_TOKEN unset and gpr.* blanked now passes all four checks, which is the thing the workaround existed to avoid failing. The reader-trust comment claimed an offline session could not make a trust decision at all. That was wrong about the SDK: RICAL for readers and VICAL for issuers are both there, with the fail-closed-versus-fall-back distinction. What is true is narrower, and the comment now says it. The local evaluator is private to SirosWallet, which a flow-free composition does not construct; the remote half is reachable through BackendApiClient.evaluateTrust but wants a shaped AuthZEN body and a backend base URL the page owns the conversation with. A composition gap, one small SDK addition from being native. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDco921kM3P6Btpdc3HiGe
leifj
marked this pull request as draft
September 19, 2026 08:43
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What it does
The wrapper exposed Bluetooth as eight bridge methods and let the page drive the mdoc protocol over them. That put device engagement, session keys, the session transcript, chunking and device-response assembly in TypeScript, duplicated the BLE stack across this app, the iOS app and the page, and — the part worth pausing on — meant the page never authenticated the reader. It extracted no certificate chain, verified nothing, and asked nothing about trust. It presented to whatever connected.
This replaces the byte pipe with a session hosted by the SDK, whose proximity stack has been through real-reader interop.
The contract the page has to implement
Two methods to call, four handlers to register. Payloads are UTF-8 JSON in base64 in both directions.
proximity.credentialsreturns the SDK'sStoredCredentialshape, which is the same shape the encrypted container already holds, so the page is handing over what it already has rather than converting.Review points
Ordered by how much I would like an opinion.
proximity.readerTrustis still a callback, and the reason is narrower than this PR first claimed. The original comment said an offline session could not make a trust decision at all. Wrong about the SDK: RICAL for readers and VICAL for issuers are both there, with the fail-closed-versus-fall-back distinction. What is true is that the local evaluator is private toSirosWallet, which a flow-free composition does not construct, and the reachable remote half wants a backend base URL the page owns. A composition gap, one small SDK addition from closing. Corrected in the code comment.Which BLE role should be the default? The engagement offers both and this starts one, defaulting to peripheral server. The page historically drove central client. Peripheral server is the role the SDK has the most exposure on. Easy to change; I would rather it be a decision than a default.
Eligibility filtering stays in the page.
filterEligibleis the identity function here, because consumption policy and presentation history live in the page's wallet state. That means the page must not return an exhausted instance fromproximity.credentials. Reasonable, or should the policy move too?The old resolve path is left as it is. It interpolates its argument into a single-quoted JavaScript string with no escaping, which is why one caller emitted
\nby hand to survive the trip. WebAuthn is its last user and its fields are base64url already, so it works. New code uses base64 JSON. Worth a follow-up rather than widening this diff.The authenticated-channel cost.Resolved. All five native crates are on Maven Central now, so the repository block and the CI token pair are removed. Consuming this wrapper needs onlymavenCentral(), verified by a build with every credential removed.What was verified
ktlintChecktestDebugUnitTestassembleDebugassembleDebugAndroidTestCI going green is worth one note beyond the tick: it means the SDK resolves and the app builds against
keystore+credentialsalone, without the wallet facade and therefore without the engine. That composition was inferred from the module graph before this branch; now it is demonstrated in a real build.No hardware run against a reader, so the session itself is compile-and-lint verified only, and it cannot be exercised end to end until the page registers the handlers.
One thing worth noting from doing it: no NFC service is declared here, because the SDK's own library manifest already contributes
MdocHostApduService, its intent filter and its AID group. My first attempt declared it and the manifest merger rejected the duplicate. So NFC static handover arrives by adding the dependency.What comes out
bluetooth/— client, server, characteristics, extensionsdebug/— the two GATT printersbluetooth*bridge methods, two mode accessors, shim wrappersNet 806 lines fewer, and one implementation instead of three.
🤖 Generated with Claude Code