Skip to content

Invitation links: migrate DIP-13 invitations from custom URL scheme to Universal/App Links on invitations.dashpay.io (post-Firebase) #4096

Description

@shumkov

Summary

The new Platform DashPay invitation feature (PR #4041) currently delivers invitation links via a custom URL scheme: dashpay://invite?data=<self-contained payload>. The payload is a one-time bearer voucher key (whoever holds the link can claim the funded identity).

Custom URL schemes are not exclusive on iOS/Android — any installed app can register dashpay:// and intercept the link when the user taps it (URL-scheme hijacking), potentially claiming the voucher first. This was raised in the PR security review.

The standard industry mitigation is Universal Links (iOS) / App Links (Android): domain-verified https:// links that the OS cryptographically binds to our app via a hosted association file, so no other app can claim them. We own dash.org / invitations.dashpay.io, so this is fully within reach — and the classic wallets already did it.

This issue is primarily an infrastructure/hosting task, with a small companion app change.

Prior art in the org (already used this pattern)

  • iOS (dashpay/dashwallet-ios): entitlement applinks:invitations.dashpay.io; DWInvitationLinkBuilder.m built invitation links on https://invitations.dashpay.io/link.
  • Android (dashpay/dash-wallet): AndroidManifest.xml has an autoVerify="true" App Link intent-filter (https://dashpay.onelink.me).

Why it's not just "turn it back on": the classic iOS builder used Firebase Dynamic Links (FIRDynamicLinkComponents, domainURIPrefix = https://invitations.dashpay.io/link), and Google shut Firebase Dynamic Links down (Aug 2025) — which is exactly why the new implementation ships a self-contained custom-scheme link instead.

Key point: the anti-hijack property comes from the apple-app-site-association (AASA) file on the domain + the app entitlement — not from Firebase. Firebase was only a shortener/redirect/preview layer on top. Since we control the domain, we can host our own static AASA / assetlinks.json and get the full protection with no third-party dependency.

Infra team asks

Host static association files on a domain we control (reuse invitations.dashpay.io, or pick a dash.org subdomain):

  1. iOS AASAhttps://invitations.dashpay.io/.well-known/apple-app-site-association (served as application/json, no redirects), listing the invitation app's appID (TeamID.BundleID) and the /invite path.
  2. Android Digital Asset Linkshttps://invitations.dashpay.io/.well-known/assetlinks.json, listing the app package name + release signing-cert SHA-256 fingerprint.
  3. Invitation URL endpoint — serve https://invitations.dashpay.io/invite?data=<payload>; ideally also a web fallback/preview page for recipients without the app installed (mirroring the old invite-preview) + an app-store redirect. The data payload is the existing self-contained envelope — no server-side state needed.

(Which team/host now controls invitations.dashpay.io post-Firebase needs to be identified — that path used to resolve through Firebase.)

App-side companion change (tracked separately / follow-up PR)

  • Add the applinks:invitations.dashpay.io associated-domains entitlement to the app.
  • Build invitation links as https://invitations.dashpay.io/invite?data=… instead of dashpay://invite?data=….
  • Handle inbound links via continueUserActivity (iOS) / the verified App Link intent (Android).
  • Keep the dashpay:// custom scheme as an offline/side-channel fallback (e.g. QR shown in-person).

Scope of the benefit (be precise)

  • Closes the on-device app-hijack vector (the specific review finding).
  • ⚠️ Does not remove the inherent bearer-link transfer exposure (the key is still plaintext in the link wherever it travels — messaging, clipboard, screenshots). That remains mitigated by design: the amount is capped (MAX_INVITATION_DUFFS, 0.01 DASH), the voucher is single-use (deterministic on-chain rejection of a second consume), there's a short advisory expiry, and the UI treats the link as sensitive. Fully eliminating the transfer exposure would require a custodial/server-escrow model, which conflicts with the self-custodial, no-account design of Platform invitations — out of scope.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions