
Dotwave is the gateway to the Rostro ecosystem: a mobile app made for everyday people, not experts. Think of it as a WeChat-style super app, one simple interface with every Rostro product and service inside it.
- Your wallet — your Rostro account lives on your device, and only your device. No seed phrases shoved in your face, no jargon.
- Connect a wallet — already have a Solana wallet like Solflare? Connect it and it becomes your Rostro sign-in. Your recovery phrase stays in your wallet, we never see it. (in development)
- App launcher — access Rostro ecosystem apps, tools, and products from one place.
- Name registration — claim a simple, human-readable name instead of a long string of characters.
- Governance — have your say: view and vote on community proposals, right in the app.
- Token operations — send, receive, and eventually purchase Rostro tokens.
- Encrypted messaging — zero-metadata peer-to-peer messaging (in development).
- Flutter — cross-platform UI (Android first, iOS coming)
- Rust — all cryptography, RPC calls, and business logic via
rust_core - flutter_rust_bridge — bridge between Flutter and Rust
- Rostro/Substrate — a sovereign L1 (no parachains, no EVM). Multi-key extrinsic signatures: Sr25519 root, NIST P-256 StrongBox, ed25519 bring-your-own-wallet, and WebAuthn passkey. On-chain name resolution and governance.
Dotwave is built with a security-first approach. Key material never exists in plaintext in application memory longer than necessary.
- Create your keys, or bring your own.
- Android Keystore (StrongBox/TEE) for hardware-backed on-device key protection
- Hardware-signed extrinsics: a StrongBox NIST P-256 (
EcdsaP256) key with a per-signature biometric gate, enrolled beside the root - WebAuthn passkeys for seedless onboarding (the path for non-technical users)
- Bring-your-own-wallet is non-custodial: BYOW holds the seed and signs; Dotwave never sees the private key. Connecting a wallet provisions an account only. Personhood (proof-of-personhood) stays separate to signing keys, it is never implied by connecting or signing.
- Backup encryption: Argon2id + ChaCha20-Poly1305 with hardened parameters (256MB memory cost, 4 iterations)
- Biometric/PIN authentication gate for returning users
- Recovery: encrypted cloud backup + user passphrase — no Dotwave servers involved. Back up your keys!
- Passphrase entropy enforcement via zxcvbn with live strength meter
- Paste-friendly passphrase fields with autofill hint support for password managers
- Google Drive
- OneDrive
- WebDAV (Proton Drive, Nextcloud, iCloud, and any WebDAV-compatible service)
- Local file export (advanced option with explicit user warning). Move someplace safe.
dotwave/
├── lib/
│ ├── main.dart # App entry, all screens and navigation
│ ├── keystore.dart # Android Keystore platform channel
│ ├── cloud_backup.dart # CloudBackupProvider abstract interface
│ ├── backup_provider_screen.dart # Provider picker UI for backup
│ ├── restore_provider_screen.dart # Provider picker UI for restore
│ └── providers/
│ ├── google_drive_provider.dart
│ ├── onedrive_provider.dart
│ ├── webdav_provider.dart
│ └── local_backup_provider.dart
├── android/
│ └── app/src/main/kotlin/com/dotwave/dotwave/
│ └── MainActivity.kt # Android Keystore + biometric platform code
├── rust_core/
│ ├── src/
│ │ ├── lib.rs
│ │ └── core.rs # Keypair gen, encrypt, decrypt, entropy check
│ └── Cargo.toml
└── flutter_rust_bridge.yaml
- Ubuntu 22.04 (WSL2 supported)
- Flutter SDK (stable channel)
- Rust via rustup
- Android SDK + NDK 27.0.12077973
- Java 17
rustup target add aarch64-linux-android
rustup target add armv7-linux-androideabi
rustup target add x86_64-linux-android
cargo install cargo-ndkflutter pub get
flutter_rust_bridge_codegen generate
flutter build apk --debugThe Rust native lib is built for you. A Gradle hook (cargoNdkBuild in
android/app/build.gradle.kts) runs cargo ndk before packaging, compiling
librust_core.so fresh from rust_core on every build. The compiled .so is
not committed (jniLibs/**/*.so is gitignored), so it can never drift from
the generated bindings and no heavy binaries land in git. Because the .so is
always built from current source, the flutter_rust_bridge content hash it
carries always matches the Dart side. (A stale, hand-committed .so used to
hang the app at splash with RustLib.init() throwing a content-hash mismatch;
that failure mode is now designed out.)
The hook builds arm64-v8a by default (covers real devices). For a release APK
or emulator testing, pass the ABIs you need:
flutter build apk --release -Pdotwave.rustAbis=arm64-v8a,armeabi-v7a,x86_64It always builds Rust in --release; a release .so runs fine in a debug APK
and avoids cargo's large unstripped debug output. cargo and cargo-ndk must
be on PATH, and the NDK is resolved from sdk.dir / ANDROID_HOME.
New user: Generate account → view seed phrase (screenshot blocked) → set recovery passphrase (entropy enforced) → choose cloud backup provider → upload encrypted backup → home screen
Connect a wallet (BYOW): Open your Solana wallet (e.g. Solflare) → approve the connect → your wallet key roots a non-custodial Rostro account (SS58 derived from the wallet's ed25519 key) → home screen. The seed stays in the wallet; append convenience keys (StrongBox, passkey) to the account later.
Returning user: Biometric/PIN authentication → home screen
Account recovery: Enter passphrase → choose backup provider → download encrypted blob → decrypt → restore keypair → home screen
When Dotwave is first installed on Android, the system may prompt the user to configure device security (PIN, pattern, biometric, or none). If the user selects no screen lock, the StrongBox ceremony can still complete technically — StrongBox keypair generation does not require screen lock to be set. However the security objective is weakened:
- Without screen lock, physical access to an unlocked device is
sufficient to use the
cert_eckeypair for signing operations - The TOTP secret in the authenticator app is also accessible without screen lock
- The hardware guarantee (private key never leaves StrongBox) is preserved — screen lock does not affect key exportability
- What is lost is the user presence guarantee — the assumption that a signing operation requires a live human to authenticate
Dotwave SHOULD enforce that a screen lock is configured before
allowing the ZK-PKI genesis ceremony to proceed. The Android
KeyguardManager API can check whether a secure screen lock (PIN,
pattern, or biometric) is set:
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE)
asKeyguardManagerval isSecure = keyguardManager.isDeviceSecureIf isDeviceSecure returns false, Dotwave should block the ceremony
and present a clear explanation: "A PIN, pattern, or biometric lock
is required to use ZK-PKI. Please configure device security in your
Android settings before continuing."
This is a Dotwave client enforcement — not a pallet enforcement.
The pallet cannot detect screen lock status from the attestation
chain. Screen lock state is not attested in the Android Key
Attestation AuthorizationList for StrongBox keys unless the key
was created with userAuthenticationRequired = true. Dotwave should
create the cert_ec key with userAuthenticationRequired = true
and an appropriate userAuthenticationValidityDurationSeconds,
ensuring that the OS enforces authentication before each signing
operation.
Selecting "none" for screen lock at app install time is a user operational risk, not a protocol flaw. The hardware binding guarantee is intact. The user presence guarantee is weakened.
Your ZK-PKI certificate is still hardware-bound — your private key never leaves your device's secure hardware regardless of screen lock settings. However without a screen lock anyone with physical access to your unlocked phone could use your certificate to sign transactions. Dotwave requires a PIN, pattern, or biometric to be configured before the certificate is created. If you later remove your screen lock, your certificate remains valid but we strongly recommend re-enabling device security.
Name registration fees are currently hardcoded in rust_core/src/core.rs (get_name_price) using a tiered length-based table (e.g. 1-char = 1000 DOT, 6+ chars = 0.5 DOT). This is a temporary stand-in.
Before production: replace with a live query to the on-chain price_oracle pallet. Governance can update the price schedule at any time via referendum, so hardcoding will silently go stale. The correct approach is to call the oracle at registration time and display whatever the chain currently reports.
The home screen is wired to show an owned name (e.g. frank) instead of an address, but resolve_address_to_name currently returns None always. The RNS pallet does not store the label string in any on-chain structure accessible from the client — OwnerToPrimaryName returns a DomainHash (one-way hash), and neither NameRecord nor RegistrarInfo contain the original label bytes. Requires one of: a new DomainHash → Vec<u8> storage map, a new reverse_lookup(AccountId) runtime API, or an off-chain indexer.
The register_name function in Rust connects to the node and derives the keypair, but the actual RNS registration extrinsic submission is stubbed pending confirmation of the pallet call name and parameters.
Security foundation complete. Account lifecycle (create, backup, restore, authenticate) fully implemented. Multi-key signing landed: Sr25519 root, StrongBox EcdsaP256 (per-signature biometric), and WebAuthn passkey. Relay-free bring-your-own-wallet proven end to end on device: an external Solflare wallet signs a real Rostro extrinsic that lands on-chain, non-custodial, no relay or server. Name search, availability checking, and marketplace listing detection live. Transaction confirmation blade (reusable for all TX types) in place. Wallet-connect onboarding UI, name registration extrinsic, and price oracle integration pending.
Private. All rights reserved.