How heyaibi/s4 works
Purpose and Runtime
S4 is a fully offline Android backup application for converting BIP-39 wallet entropy into threshold SLIP-39 paper mnemonic shares and restoring it later. It targets Android API 26–37 and uses Kotlin, Jetpack Compose, Material 3, Navigation Compose, NDK/CMake, and JNI. No backend or network service is part of the architecture.
Entry point: app/src/main/java/com/s4/MainActivity.kt. Android packaging and security constraints, including no network permission and backup restrictions, are in app/src/main/AndroidManifest.xml.
Application Navigation and Screen Boundary
MainActivity owns the Compose navigation graph and app-level secure-screen behavior. Routes separate the principal workflows: Split, Restore, results, and Recovery Guide.
• app/src/main/java/com/s4/navigation/Routes.kt
• app/src/main/java/com/s4/ui/SplitScreen.kt
• app/src/main/java/com/s4/ui/RestoreScreen.kt
UI state is memory-only: secret-bearing form values are not intended for persisted saved-state or disk storage. Result screens retain an in-memory session until completion, then discard it.
Split Workflow
The Split screen collects either BIP-39 mnemonic words or raw entropy, an optional BIP-39 passphrase, and threshold/count parameters. SplitViewModel validates and orchestrates the conversion pipeline:
1. parse or generate entropy;
2. derive/display a fingerprint;
3. call SLIP-39 native generation;
4. expose stable mnemonic shares and an optional recovery guide.
• app/src/main/java/com/s4/ui/SplitViewModel.kt
• app/src/main/java/com/s4/model/SplitParams.kt
• app/src/main/java/com/s4/ui/SplitResultScreen.kt
The app shards entropy, not the PBKDF2 wallet seed. The BIP-39 passphrase remains separate from shares by design.
Restore Workflow
Restore accepts one share per line, supports SLIP-39 word completion, parses and validates shares, then combines a qualifying threshold into entropy. That entropy is converted back to BIP-39 words and fingerprinted; the entered passphrase can be verified against the expected fingerprint.
• app/src/main/java/com/s4/ui/RestoreScreen.kt
• app/src/main/java/com/s4/crypto/ShareCodec.kt
• app/src/main/java/com/s4/ui/WordCompletion.kt
Errors at this boundary include malformed/checksum-invalid shares, insufficient shares, and incompatible share sets; these are surfaced through Kotlin exception types rather than leaking native details.
Kotlin Crypto Domain Layer
Kotlin provides the application-facing crypto API, input/result shaping, exception handling, and wordlist access. BIP-39 handles entropy-to-mnemonic conversion, seed derivation, and SHA-256 fingerprinting. SLIP-39 and Shamir wrappers bridge to the native implementation.
• app/src/main/java/com/s4/bip39/Bip39.kt
• app/src/main/java/com/s4/crypto/Slip39.kt
• app/src/main/java/com/s4/crypto/Shamir.kt
Static BIP-39 and SLIP-39 vocabularies are bundled locally in app/src/main/resources/wordlist.txt and app/src/main/resources/slip39-wordlist.txt.
Native Cryptography Boundary
Cryptographic splitting and combination execute in vendored C libraries compiled through CMake/Android NDK. Thin JNI facades define the Kotlin/native boundary. SLIP-39 uses a single configured group and native randomness is supplied from Android SecureRandom through callbacks.
• app/src/main/cpp/CMakeLists.txt
• app/src/main/cpp/slip39_jni.cpp
• app/src/main/cpp/shamir_jni.cpp
Vendored primitives and algorithms reside under app/src/main/cpp/bc-slip39, app/src/main/cpp/bc-shamir, and app/src/main/cpp/bc-crypto-base; this avoids OpenSSL and external crypto services.
Recovery and Device Provisioning
The Recovery Guide produces durable, plain-language recovery instructions based on the active split session, including the requirement to preserve the BIP-39 passphrase separately.
• app/src/main/java/com/s4/guide/RecoveryGuide.kt
• app/src/main/java/com/s4/ui/RecoveryGuideScreen.kt
Outside the app, one-shot ADB provisioning hardens the dedicated phone, installs specified APKs with checksum verification, disables radios/bloatware, and configures prerequisites for the anti-forensics deployment model.
• airgap/airgap.sh
• airgap/apps.json
• airgap/packages.txt
Main entry points: Routes.kt Routes · SplitViewModel.kt Split view model · ShareCodec.kt Share codec · slip39_jni.cpp JNI facades
Components 22
Android app
- Packaging & security · Blocks network access and restricts backups.
app/src/main/AndroidManifest.xml - Main activity · Owns navigation and secure-screen behavior.
app/src/main/java/com/s4/MainActivity.kt - Routes
app/src/main/java/com/s4/navigation/Routes.kt - Split workflow · Collects entropy or mnemonic, passphrase, and share parameters.
app/src/main/java/com/s4/ui/SplitScreen.kt - Split view model · Keeps secret-bearing state in memory only.
app/src/main/java/com/s4/ui/SplitViewModel.kt - Split parameters
app/src/main/java/com/s4/model/SplitParams.kt - Split results · Displays stable shares for the active session.
app/src/main/java/com/s4/ui/SplitResultScreen.kt - Restore workflow · Accepts one SLIP-39 share per line.
app/src/main/java/com/s4/ui/RestoreScreen.kt - Share word completion
app/src/main/java/com/s4/ui/WordCompletion.kt - Recovery guide · Preserves the requirement to store the BIP-39 passphrase separately.
app/src/main/java/com/s4/guide/RecoveryGuide.kt - Recovery guide screen
app/src/main/java/com/s4/ui/RecoveryGuideScreen.kt
Crypto domain
- Share codec · Validates, parses, and combines shares without exposing native errors.
app/src/main/java/com/s4/crypto/ShareCodec.kt - BIP-39 · Converts entropy and mnemonics, derives seeds, and fingerprints entropy.
app/src/main/java/com/s4/bip39/Bip39.kt - SLIP-39 wrapper
app/src/main/java/com/s4/crypto/Slip39.kt - Shamir wrapper
app/src/main/java/com/s4/crypto/Shamir.kt - Bundled wordlists · Static BIP-39 and SLIP-39 vocabularies.
app/src/main/resources/wordlist.txt
Native crypto
- JNI facades · Bridges Kotlin wrappers to native splitting and combination.
app/src/main/cpp/slip39_jni.cpp - Vendored crypto libraries · SLIP-39, Shamir, and base primitives; no OpenSSL.
app/src/main/cpp/bc-slip39 - NDK build
app/src/main/cpp/CMakeLists.txt
Device provisioning
- Air-gap provisioning · One-shot dedicated-device provisioning and APK verification.
airgap/airgap.sh - APK manifest · Specified APKs and checksums.
airgap/apps.json - Package policy · Radios and bloatware targets.
airgap/packages.txt
Connections 26
- Packaging & security → Main activity: packages and constrains
- Main activity → Routes: hosts
- Routes → Split workflow: Split route
- Routes → Restore workflow: Restore route
- Routes → Split results: results route
- Routes → Recovery guide screen: guide route
- Split workflow → Split view model: submits inputs
- Split view model → Split parameters: uses
- Split view model → BIP-39: parses entropy and fingerprints
- Split view model → SLIP-39 wrapper: generates shares
- Split view model → Split results: publishes session results
- Split results → Recovery guide: active split session
- Recovery guide → Recovery guide screen: renders
- Restore workflow → Share word completion: assists entry
- Restore workflow → Share codec: validates and combines
- Share codec → SLIP-39 wrapper: parses SLIP-39
- Share codec → Shamir wrapper: combines threshold shares
- Share codec → BIP-39: restores mnemonic and fingerprint
- BIP-39 → Bundled wordlists: loads BIP-39 words
- SLIP-39 wrapper → JNI facades: calls native generation
- Shamir wrapper → JNI facades: calls native combination
- JNI facades → Vendored crypto libraries: executes
- NDK build → JNI facades: builds
- NDK build → Vendored crypto libraries: compiles
- Air-gap provisioning → APK manifest: verifies and installs
- Air-gap provisioning → Package policy: disables/configures