Repository navigation
Every e2e from an Android emulator, with real Credential Manager passkeys, the enclave and the platform - #62
Merged
Merged
Conversation
testpasskey is a passkey provider in software, set as the emulator's provider over adb (credential_service, credential_service_primary), so PasskeyPlugin's calls take the real path: Credential Manager, a provider, and back. It signs as the runtime's SoftwareAuthenticator does, claims the origin Android would (android:apk-key-hash of the calling APK's certificate), and answers WebAuthn's PRF extension. integration_test/passkey_test.dart drives PlatformPasskey through it: create, then two approvals that must give the same 32-byte seed, with the debug key's origin in clientDataJSON and no PRF output in what is returned. scripts/approve-passkeys.sh taps the sheet's Continue, which androidx.credentials 1.3 cannot skip for a passkey sign-in. Passes on the API 35 Play image in 15 s, three sheets approved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ens in debug Eight styles asked for RobotoMono, which the app does not bundle, so it was fetched from Google at runtime, and not found at all where runtime fetching is off, as in the integration tests. They use FiraCode, the monospace font that is bundled; the one bold label is Medium, the heaviest FiraCode bundled. The Exit tab's "full path on-chain" tile sits in a coloured card, which hides a ListTile's ink, and Flutter asserts so in a debug build: opening the tab with a signed exit threw. The tile has its own transparent Material now. Found by running integration_test/app_test.dart on an emulator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With scrcpy mirroring the emulator, the sheet's window was never reported as focused, so nothing was approved. The button is looked for in Play services' sheet and in Android's own Credential Manager UI, for an image without Play. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After the Exit tab, KSh 1,500 to an M-PESA number from Send: the platform's quote, one approval that mints the payout's escrow, pairs the platform into it, seals and funds the deal, then the fake Grid's payout and the platform's repayment out of the escrow. The test app provides PayoutService as main.dart does, without which the send screens do not load. Passes on the emulator against make up's enclave and platform, with scripts/approve-passkeys.sh approving each sheet: 5:55, five approvals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…form scripts/e2e-app.sh (make e2e-app) brings up MerlinPlatform, boots the dev enclave image's app bundle (ENCLAVE_BUNDLE=app: rp id vtxos.com and the app's origins) with the cosigner set as make up sets it, makes testpasskey the emulator's passkey provider, and runs integration_test/app_test.dart with approve-passkeys.sh approving each sheet. The app-e2e job runs it on an API 35 Play emulator on the runner's KVM. Needs the dev enclave image with the app bundle, from vtxos/infra#5; docker-compose.enclave.yml pins it once that is published. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…provider gradlew is Flutter's to generate and not checked in, so a fresh checkout has none: flutter build apk --config-only writes it, with the local.properties Gradle reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The suite and the app's flow ran as two jobs, each booting the dev enclave. scripts/e2e-all.sh (make e2e-all) boots it once: the image's app bundle, whose relying party vtxos.com allows https://vtxos.com, the origin the suite's software passkeys claim, as well as the app's android:apk-key-hash ones; and the suite's own settings, which already name MerlinPlatform (e2e/bin/guest_env.dart prints them). The suite runs attached to it, then the app's flow; both run whatever the first does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a send, the delegate and the exits are renewed over what is held, once the indexer reflects the send. That waited only for the inputs to be gone: in between that and the change being listed, the held set is empty or short of the change, counted as settled, and the renewal went over it. The change then had no exit, and after a last send the wallet had no delegate at all: the e2e's "every renewal signs an exit for every VTXO it covers" and "three sends Alice to Bob" failed on it, on a busy machine every time. A send with change now also waits for it to arrive: arkTxid:1, as the cosigner orders an ark transaction's outputs (recipient, change, anchor). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 01465f0)
org.gradle.java.home named /home/joshua/dev/jdk-17, which exists on one machine: anywhere else, CI included, Gradle refused to start. Where the JDK is belongs to each machine (JAVA_HOME, flutter config --jdk-dir, or ~/.gradle/gradle.properties). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
integration_test/enclave_test.dart is the host's e2e suite on the emulator: every wallet is the app's own client, and every passkey the platform's, made and asserted through Credential Manager. Attestation and its refusals, DKG and tenants, addresses, exits (one that bitcoind mines), sends, nothing secret at rest, a mixed exit-delay set, a client rebuilt from storage, recovery on a new phone and what it must refuse, and an unknown service refused. The served component's hash comes from the host (E2E_GUEST_SHA256), which the device cannot read. app_test.dart goes on past its payout: the app restarted, opening from its own storage, then a new phone, wiped and restored with "I already have a wallet", which pays again. testpasskey answers a request for any passkey with its newest: a restore, on an emulator many tests have made passkeys on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make e2e is e2e-all now: on one boot of the image's app bundle, with MerlinPlatform, app_test.dart and enclave_test.dart run on the emulator. The host's suite (e2e/test/enclave_ark_test.dart) is what enclave_test became, and goes with its fake escrow service: the service's own protocol is proven in the cosigner's tests, and end to end by the payout. The e2e package keeps the walkthroughs and their helpers. e2e-all.sh takes the enclave down by the compose file's full path (from app/, the relative one named nothing and the enclave stayed up, run after run, until its pool disk was full), and serves COSIGNER_WASM as the host's suite did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unpinned, the jobs took whatever stable was that day: 3.47.7, which refuses this project's Gradle 8.12 outright, so the e2e could not build the app. Moving Flutter on comes with the Gradle (8.14) and AGP upgrade it asks for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A run whose app never reported in sat 72 minutes with nothing printed, until cancelled: flutter test waits for the app with no limit, and nothing said why. The emulator's logcat is now written for the whole run and uploaded when the job fails, and the tests are bounded at 60 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
infra#5's image: the bundle any software passkey can use, and the app's, whose relying party is vtxos.com and which allows the app's origins. scripts/e2e-all.sh boots the app's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its Wi-Fi is netsim's, which on the runner came up with no route even to the host at 10.0.2.2: the app started, and its first call failed with "Network is unreachable". With Wi-Fi off the emulator uses mobile data, its own network, which reaches the host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e way Twice the app started and nothing more was heard: the emulator's log stopped three seconds in, as if adb had gone. The run now writes memory, load and adb's view of the emulator every half minute, keeps the kernel's log when it fails, and builds without a Gradle daemon holding 4 GB of a 16 GB runner. The tests are bounded at 40 minutes in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the runner, 19 of 21 passed. The app's flow could not reach the exit address's Save button on the default small device, and two passkeys with two ceremonies took longer than package:test's 30 seconds. The emulator is a Pixel 6, as the tests run on locally, and enclave_test's tests default to five minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rove more lightly On the runner, the app's flow waited for a deposit it could not see: on regtest the app watches electrs at 127.0.0.1:50001, which a phone reaches only through adb reverse. make up sets that up; e2e-all.sh did not, and local runs passed on a reverse left over from it. Twice more a file's app installed and nothing followed: flutter test reads the device's log for the app's VM service, from one logcat it never restarts, and that stream ended at launch with the machine idle and adb still up. Each file now runs on its own, and one that prints nothing for ten minutes is stopped and run once more; a failure is not retried. approve-passkeys.sh dumped the screen every second, a JVM started on the device each time. It dumps only when the sheet's activity is in front now, and every tenth time regardless. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Looking for the sheet's activity first, through dumpsys, approved nothing on the runner in half an hour: the check's grep -m1 broke its pipe, which pipefail counts as no match, and the dumpsys left behind appear to have slowed the emulator past the every-tenth-time dump too. The version that approved every sheet there is back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Every e2e now runs from an Android emulator, the way the app runs. Every passkey goes through Android's real Credential Manager, against the dev enclave and MerlinPlatform. The host's Dart suite is replaced by its on-device port.
What runs (
make e2e=scripts/e2e-all.sh)It uses one boot of the dev enclave image's app bundle (
ENCLAVE_BUNDLE=app: rp idvtxos.com, the app's origins), with MerlinPlatform, and runs two files on the emulator:app/integration_test/app_test.dart, the app's full flow through its screens:app/integration_test/enclave_test.dart, the wallet against the cosigner. This is the host suite's tests, ported with every wallet the app's own client:Not ported:
app_test.dartproves it through the app.How the passkey is tested
app/android/testpasskeyis a passkey provider in software, set as the emulator's over adb.PasskeyPlugin's calls take the real path: Credential Manager, a provider, and back.SoftwareAuthenticatordoes.android:apk-key-hashof the calling APK's certificate.scripts/approve-passkeys.shtaps each sheet's Continue.CI
e2ejob runsmake e2e-allon an API 35 Play emulator on the runner's KVM, next to the enclave.rust,dartandflutterare unchanged.Fixes this found
Material.gradle.properties: named/home/joshua/dev/jdk-17, so Gradle couldn't start on any other machine.Blocked on
vtxos/infra#5, the dev enclave image with the app bundle. Until it is published and its digest pinned in
docker-compose.enclave.yml, the e2e job boots the old image, whose relying party isenclave.test, and the app's passkey is refused.What was run
make e2eon a KVM workstation: an API 35 Play emulator, against infra#5's image built locally. Both files pass, in 14:22, with 93 passkey sheets approved, and the enclave is taken down after.🤖 Generated with Claude Code