Skip to content

V3#50

Open
aruokhai wants to merge 36 commits into
mainfrom
v3
Open

V3#50
aruokhai wants to merge 36 commits into
mainfrom
v3

Conversation

@aruokhai

@aruokhai aruokhai commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

No description provided.

aruokhai and others added 30 commits June 5, 2026 09:28
- Changed import paths from `crate::bindings::exports::component::threshold::types::*` to `crate::exports::component::threshold::types::*` in multiple files for consistency.
- Updated the WASM target path from `wasm32-wasip1` to `wasm32-wasip2` in all relevant end-to-end test files to reflect the new build configuration.
…content server

- Introduced new RPC methods for eVTXO programmable key generation in `mpc_wallet.proto`, including steps for key generation and resharing.
- Added a new Python script `warg_content_server.py` to serve as a minimal content-addressed store for Warg registry components, supporting PUT and GET requests for content management.
… creation

Make a contract-gated eVTXO a first-class Ark VTXO: it is minted by a normal
off-chain send to its Ark address, tracked by arkd, and spent through arkd's
SubmitTx/FinalizeTx so the ASP genuinely co-signs the cooperative
(ConditionMultisigClosure) leaf — no forged key. The 2-of-2 {wallet, cosigner}
V′ leg is gated by the cosigner, which releases its share only if the committed
WASM contract returns `allow`.

Contracts are now delivered to the cosigner once, at eVTXO creation
(EvtxoKeygenStep1Request.contract_wasm), validated, and stored in its own KV
(contract_wasm tree, keyed by contract_id); the gate resolves them from there.
The external Warg/local-dir registry is removed.

Ark / on-chain
- crates/ark: eVTXO taproot tree with an ark-lib-compatible exit leaf
  (CSVMultisigClosure: <bip68_seq> CSV DROP <owner> CHECKSIG) so arkd accepts the
  taptree; add bip68_sequence + evtxo_exit_script. Default VTXO scripts unchanged.
- ffi/ark/send.rs: per-input eVTXO branch — V′ signer key, the `condition`
  (contract_id preimage) and EVTXO/0x01 args PSBT fields, and gate_tx_bytes (the
  checkpoint PSBT) for the cosigner. Default sends unaffected.
- ffi/ark/evtxo_address.rs: derive the eVTXO's Ark address for minting.
- app-core: createEvtxoSpend/signEvtxoSpend sign the cooperative leaf with V′.
- cosigner-runtime/ark_send: carry the client's `condition` PSBT field through the
  checkpoint counter-sign — arkd drops it on re-serialize but re-evaluates the
  condition at FinalizeTx.

Contract delivery / validation
- proto: EvtxoKeygenStep1Request.contract_wasm.
- cosigner-runtime: KvRegistry (KV-backed, sha256-verified) replaces Warg/LocalDir;
  step3_finalize requires the wasm, checks sha256==contract_id, statically validates
  it (compiles to a component importing only the bitcoin:contract package — no WASI),
  and stores it. Correctness/termination stay fail-closed at evaluate time.

Tests / infra
- e2e: evtxo_arkd_e2e_test (full mint+spend through arkd: allow co-signed and
  confirmed, over-limit + bad-arg refused); evtxo_contract_e2e_test updated to the
  in-creation delivery (no Warg); contract_engine_test covers static validate.
- remove warg_content_server.py, the mpc_warg docker service, and Makefile warg-up.
- Adjusted MpcClient to support a 2-of-2 DKG process without a hardware signer.
- Updated doDkg method to handle the new DKG logic, ensuring both wallet and cosigner hold shares.
- Modified createEvtxoKey to register an eVTXO that reuses the main key without resharing.
- Enhanced REST API to return eVTXO scriptPubKey upon registration.
- Updated protobuf definitions to include evtxo_script_pubkey in responses.
- Added end-to-end tests for the new 2-of-2 DKG and eVTXO registration process.
…e imports

- Updated imports in various handler files to use `crate::cosigner::cosigner::CosignerInstance` instead of `crate::cosigner::wasm::CosignerInstance`.
- Removed the `wasm.rs` file as it is no longer needed.
- Introduced new helper functions for group authorization checks and persistence in `helpers.rs`.
- Updated `evtxo.rs` to reflect changes in participant handling for the DKG process.
- Added tests for the new 2-party reshare functionality in `frost_test.rs`.
…vice

- Deleted RefreshStep1, RefreshStep2, RefreshStep3, GetPolicyId, UpdatePolicy, and DeletePolicy RPC methods from the MPCWallet service.
- Removed corresponding request and response message definitions from the proto file.
- Updated generated Dart files to reflect the removal of these methods and messages.
- Implemented EvtxoOnboard RPC for onboarding participants to contract eVTXOs.
- Added EvtxoPendingShares RPC to allow participants to fetch contract shares held for them.
- Introduced EvtxoAckShare RPC for participants to acknowledge receipt of shares.
- Updated protocol buffers and generated Dart files to include new message types and service methods.
- Added DKG handlers in `handlers.rs` to manage the DKG ceremony steps (1, 2, and 3) directly in Rust, replacing the previous WASM-mediated approach.
- Introduced a new `DkgSession` struct in `session.rs` to maintain per-user DKG state, including round packages and pending responses.
- Created a `mod.rs` file to organize onboarding-related modules, including the new DKG coordinator and session management.
- Updated the REST API to utilize the new onboarding module for DKG coordination.
…n mpc_wallet.proto

- Updated RPC methods for contract creation, replacing the previous eVTXO key generation steps.
- Introduced new methods: ContractCreateStep1, ContractCreateStep2, ContractCreateStep3, and ContractCreateStep4.
- Removed legacy eVTXO methods and adjusted message structures to reflect the new contract creation process.
- Enhanced comments for clarity on the contract creation flow and the roles of user, cosigner, and service.
- Updated `new_nonce` to return a tuple of commitments JSON and the signing nonce.
- Modified `frost_sign` to accept a reference to the signing nonce instead of consuming it.
- Removed unused utility functions from `util_ops.rs` related to identifier derivation, polynomial evaluation, and random coefficient generation.
- Simplified the `world.wit` interface by removing unnecessary resource handles and consolidating session state management.
- Enhanced `refresh_to_receiver` to return a refreshed pairing structure containing the receiver's half, key package, and public key package.
- Updated DKG resharing logic to support key-preserving refresh operations for new participant identifiers.
- Added new fields to the `ContractContext` message in the protobuf definition to accommodate the cosigner group key.
…contract refresh-not-reshare (cosigner side)

Guest is now the long-lived, stateful, sole WASM component that owns all signing keys
and per-user state; the host does I/O + stores an opaque sealed snapshot.

- Fold cosigner-guest into the `cosigner` crate (one cosigner.wasm); delete the legacy
  in-WASM component; move pure-public-crypto (pubkey tweak, schnorr verify) host-side.
- Merge CosignerInstance into CosignerState (one actor Arc; dispatch is (state, shared, req)).
- All signing in the guest: FROST script-path + key-path tweak + contract spends behind the
  async contract-gate enforce.
- Plan A: SeedPolicy actor command (onboarding seeds keys into the guest + seals);
  OnboardingManager::with_registry; cold-spawn restore-first (skip plaintext InstallPolicy
  when the sealed snapshot restores).
- Contracts: replace the V->V' reshare with a key-preserving REFRESH of V onto
  {service,cosigner}; eVTXO coop leaf = V; ContractPolicy = gate allowlist + service share;
  4 ContractCreate steps -> 1; stateless ContractManager.
- Cleanup: delete handlers/sign.rs + contract/session.rs; move `ceremony` into onboarding.

Remaining work tracked in HANDOFF.md (Dart client contract change + e2e verify;
service-driven spend into guest; plaintext policy-store removal; Phase 6 enclave).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rk_tx for service-driven eVTXO spends; update contract_group_id description
…upport

- Introduced `BoardingSettleInFlight` struct to manage in-flight boarding settle state.
- Enhanced `GuestState` to include boarding signer and settle state.
- Added methods for handling boarding nonces and signing in the guest.
- Updated `SettleSession` to delegate tree-signing to the guest, managing public keys and nonces.
- Implemented new commands for generating nonces and signing tree transactions.
- Refactored event handling to accommodate the new boarding settle workflow.
- Removed Sled and Enclave persistence backends.
- Introduced RespStore for Redis-based key-value storage.
- Updated ServerConfig to use Redis URL for connection.
- Modified handlers and services to eliminate references to removed persistence methods.
- Updated Docker Compose to include Redis service with password authentication.
…up model

- Changed contract creation to use a peer model, removing the need for service-side assembly.
- Introduced EvtxoPendingShares and EvtxoAckShare for receiver-side inbox operations.
- Removed obsolete messages related to broadcasting transactions and fetching history.
- Updated comments for clarity on the new flow and responsibilities in the contract creation process.
The FROST wallet share is now gated by an Android passkey: the share is
persisted blinded (δ = share − b(PRF seed)) and reconstructed only at
signing time, one biometric gesture per operation burst. Request auth
moves off the share onto cosigner-minted session tokens.

Cosigner:
- Run a WebAuthn Relying Party in the runtime (webauthn-rs): register/
  assert ceremonies, credentials in the RESP KV, deterministic uuid-v5
  user handles. residentKey=required so Android routes to the platform
  passkey flow, plus an android:apk-key-hash allowed origin for
  Credential Manager assertions (WEBAUTH_ANDROID_ORIGIN).
- Mint 30-day Ed25519 session tokens (seeded by WEBAUTH_TOKEN_SECRET),
  verified centrally at the REST boundary before actor dispatch. An
  empty Schnorr signature now reports "missing or invalid session
  token" instead of a confusing length error.
- vtxo_received push is now a visible "Funds received" notification —
  a gated share can't silently sign a delegate from a background push.

App:
- Onboarding: welcome → server → DKG → passkey setup screen (replaces
  the create-PIN screen; PIN threading removed). Assert-first retry so
  a cancelled first attempt doesn't wedge on excludeCredentials;
  skippable with an un-gated Schnorr fallback.
- PasskeyAuthenticator: Credential Manager channel with PRF eval;
  single-flight assertions (concurrent requests share one gesture);
  2-minute seed cache so a settle's sequential FROST signs need one
  prompt; token persisted across restarts with a one-shot re-auth when
  the server rejects it.
- Ark tab: delegate banner + button when re-delegation would prompt;
  auto-delegate runs silently only when promptless, with a 5-minute
  failure cooldown (was: a biometric prompt every poll tick).

Ark:
- Per-input exit delays in delegate settle and off-chain send: a wallet
  legitimately holds mixed delays (a boarded VTXO keeps the boarding
  delay; received/refreshed ones use the unilateral delay), so spend
  info is rebuilt per distinct delay instead of one template for all
  inputs. Removes the "mixed exit_delays" rejection.
- Regtest arkd config: boarding 172032 / unilateral 86016 — arkd
  requires boarding to be the largest delay in the VTXO script.
Registration failed on the first attempt with Google's "Sign in another
way": enablePasskey asserted the just-created credential before Google
Password Manager had indexed it. Retry the post-register assertion with
backoff (2–5s) so the PRF seed and session token are still minted at
setup, and show a "Securing your wallet…" state on the setup screen
during the wait.

Supporting build + signing changes needed to run passkeys on-device:

- Toolchain: Gradle 7.6.3->8.12, AGP 7.3.0->8.7.3, Kotlin 1.9.0->2.3.20
  (Flutter 3.44.3 won't build under Gradle 7.x; androidx.credentials
  1.3.0 needs Kotlin >=1.9.0), plus kotlinOptions jvmTarget 1.8.
- Commit a shared debug keystore and sign debug builds with it, so every
  dev/CI shares one cert fingerprint for Digital Asset Links.
- cosigner: accept a comma-separated WEBAUTH_ANDROID_ORIGIN so debug and
  release builds validate against one server; wire the debug origin into
  the software-ark Make target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The wallet is now a pure 2-of-2 {wallet, cosigner}; the RP2350 TrustZone
hardware signer and the in-app hardware/recovery-signer abstraction are no
longer used. Real restore is handled server-side (restore-from-seal).

Removed:
- Firmware crates: hwsigner/, hwsigner-secure/, crates/embassy-rp-fork/
  (plus host tooling: scripts/test_hwsigner.py, 99-hwsigner.rules,
  docs/HW_SIGNER.md)
- Flutter/Android USB HID layer: app/lib/usb/, app-core/lib/usb/,
  UsbHidPlugin.kt, device_filter.xml + AndroidManifest USB entries
- Legacy signer layer: HardwareSignerInterface, TcpHardwareSigner,
  SoftwareSigner, MpcClient.doRestore(), the RecoveryPolicy cluster, and
  the e2e/signer-server test harness

Changed:
- MpcClient/MpcService collapsed to the software-only DKG path (doDkg is
  self-contained; no external signer attached)
- e2e tests/bins updated to drop the vestigial TcpHardwareSigner
- Makefile: dropped hw-* + signer-server targets, the duplicate flutter
  target, and the deprecated FFI aliases; hardware/hardware-ark now alias
  the software targets
- Docs reframed 2-of-3 -> 2-of-2; removed stray committed logs

Verified: flutter/dart analyze clean across app, app-core, e2e; the DKG /
sign / Ark path passes in e2e-ark. (Pre-existing Ark auto-settle/FCM test
failures are unrelated and tracked separately.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two pre-existing failures in the Ark e2e suite, unrelated to the
hardware-signer removal:

- Auto-settle tests (drives stored delegate, cold-spawn from restart,
  survives restart) never fired: the tick settles when
  `now >= expires_at - AUTO_SETTLE_SAFETY_MARGIN_SECS`, but arkd's
  ARKD_VTXO_TREE_EXPIRY (~4.3h) far exceeds the test's 3600s (1h) margin,
  so the threshold stayed hours in the future and the delegate never
  settled within the test window. Raise the margin to 9999999999 so the
  threshold clamps to 0 (always crossed) regardless of arkd expiry, in
  ark_e2e_test, evtxo_arkd_e2e_test, and dkg_2of2_test.

- FCM "push fires on VTXO receive" asserted a silent data-only push, but
  push_vtxo_received sends a user-visible notification ("Funds received" /
  "Tap to activate auto-settle protection") via send_notification. Update
  the payload-shape assertions to match the visible notification (notification
  block + apns alert, apns-priority=10) — the visible banner is the intended
  behavior.

make e2e-ark now passes 10/10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ojies and others added 6 commits July 8, 2026 13:00
…tlinks

First, build-safe stage of the com.example.ap -> com.vtxos.app rename:
- Move src/main/kotlin/com/example/ap -> com/vtxos/app and update the
  package declarations in MainActivity.kt and PasskeyPlugin.kt
- build.gradle: namespace -> com.vtxos.app
- assetlinks.json: package_name -> com.vtxos.app (template only)

applicationId stays com.example.ap for now so it keeps matching
google-services.json's package_name (the google-services gradle plugin
requires them equal). The applicationId flip + a fresh google-services.json +
firebase_options.dart appId are a console-gated trio to land together once the
com.vtxos.app app is registered under Firebase project vtxos-7afb3.

Do NOT deploy the updated assetlinks.json to vtxos.com/.well-known/ until the
applicationId also becomes com.vtxos.app, or passkeys for the current
com.example.ap build break.

Verified: flutter build apk --debug succeeds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the com.example.ap -> com.vtxos.app rename now that the
com.vtxos.app app is registered under Firebase project vtxos-7afb3:
- build.gradle: applicationId -> com.vtxos.app
- google-services.json: fresh download containing the new com.vtxos.app
  app (appId 1:575541915148:android:03d0cade16cd0393378829); the old
  com.example.ap entry is retained harmlessly. API key rotated to the
  value in the new download.
- firebase_options.dart: appId + apiKey updated to match the new app so
  Firebase.initializeApp targets the com.vtxos.app registration.

FCM server (cosigner-runtime) unchanged: same project + service account;
device tokens re-register against the new appId on next launch. WebAuthn
env unchanged (apk-key-hash is cert-derived, not package-derived).

ACTION REQUIRED (out of repo): deploy the updated app/assetlinks.json to
https://vtxos.com/.well-known/assetlinks.json so Credential Manager honors
passkeys for com.vtxos.app — ideally also adding the release/Play-signing
SHA-256 fingerprint (assetlinks currently lists only the debug cert).

Verified: flutter build apk --debug succeeds (google-services package
validation passes against the new applicationId).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sync the repo's Digital Asset Links template with what vtxos.com serves
(the MPCLanding Cloudflare worker): package_name com.vtxos.app, both the
debug (BB:5A:...:E2) and release (2D:FD:...:CB) SHA-256 fingerprints, and
both the handle_all_urls (legacy FIDO2) and get_login_creds (passkey)
relations. The authoritative copy is the worker's ASSET_LINKS constant;
this file is the reference source of truth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…app id

`make release` distributed to FIREBASE_APP_ID 1:575541915148:android:5fbaa581...
(the old com.example.ap Firebase app). After the com.vtxos.app rename, update
it to the new app's mobilesdk_app_id 1:575541915148:android:03d0cade16cd0393378829
so App Distribution uploads under the correct registration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
route_tick_auto_settle recorded the consolidated VTXO's exit_delay by
guessing it from the first input VTXO. The delegate self-refresh output is
always built with the ASP's unilateral_exit_delay, so when the first input
carried a different delay (e.g. a boarding VTXO with boarding_exit_delay),
the host stored the wrong exit_delay. A VTXO's scriptPubKey is re-derived
from exit_delay, so the client then built a spend against a wrong script and
arkd rejected it (INVALID_PSBT_INPUT) — a user who boarded funds and then
used auto-settle could not spend the consolidated VTXO.

Plumb the settle's real unilateral_exit_delay through
ActorResponse::SettleSubmitted (mirroring the boarding-settle and
submit_ark_send paths, which already record the correct value) and use it in
the host projection instead of guessing.

Add an e2e regression: ark_e2e_test ping-pongs Ark VTXOs between two boarded
wallets, spending a just-auto-settled VTXO every cycle (the exact failing
path) — fails pre-fix, passes post-fix. Configurable via
DELEGATE_STRESS_CYCLES (default 10).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The wallet now automatically falls back to on-chain Bitcoin only whenever the
Ark ASP is unreachable, and recovers automatically when it returns. Users can
also force offline mode from a new Settings screen.

MpcService gains a dynamic availability state machine: the existing VTXO poll
now detects an ASP outage (a failed refresh confirmed by a getArkInfo probe, so
a transient hiccup doesn't flap the flag), flips _arkAvailable false, and
re-probes every tick to auto-recover via initArk(). offlineMode =
offlineModeForced || !arkAvailable; the forced toggle is persisted (Hive
'offlineMode').

In offline mode the UI hides the Ark and Services tabs (both Ark-layer), shows
an "on-chain Bitcoin only" banner, blocks Ark (tark1) addresses on the send
screen, and a router redirect bounces any Ark/Services route back to Home.
On-chain receive/balance/send are unaffected (wallet-alone, no ASP).

- services/mpc_service.dart: offline state, persisted toggle, dynamic detection
- widgets/app_bottom_nav.dart (new): shared route-based nav, hides Ark/Services offline
- widgets/offline_banner.dart (new)
- screens/settings_screen.dart (new): offline toggle + live Ark status
- main.dart: /settings route + offline redirect guard (refreshListenable)
- screens/{home_screen,ark/ark_screen}.dart: shared nav; Home settings icon + banner
- screens/spending/send_screen.dart: offline gating of Ark addresses

Verified: flutter analyze clean; debug APK builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants