An Android / iOS signing app built on the bitwasm-signer parser and signer WASM modules. By default the app keeps no key: it loads the key for each signing and erases it when you are done.
Read the guide: how to set up and use the signer, from making a key to signing a transaction, with the browser demo running beside the text.
Try the testnet demo in your browser · Install over USB
On an iPhone, open the web app in Safari and add it to the Home Screen; it then runs offline. See the setup guide.
Warning: This is experimental software. It has not been audited and comes with no warranty. The author is not responsible for lost funds or any other damage from using it. Keep amounts small. What each setup protects against is on the first page of the guide.
Android (Android 10) and the iOS Simulator, on testnet with the public BIP39 test mnemonic.
Checked means it has been tried on an Android 10 phone (ARMv7). Implemented means the code is in place but has not been tried on a phone yet. iOS has only been run in the Simulator.
| Feature | Details | Android | iOS |
|---|---|---|---|
| Load a SeedQR | SeedQR / CompactSeedQR with an optional BIP39 passphrase, or a 64 byte seed. Keys are derived on the device | Checked | Implemented |
| Type the words | BIP39 English words (12 to 24) one at a time on an in-app keypad, with an optional passphrase. Letters that lead to no word cannot be pressed, and the last word only offers candidates that fit the checksum | Implemented | Implemented |
| Create a mnemonic | 12 or 24 words from SecureRandom or from exactly 50 / 99 dice rolls (hashed as SeedSigner and Krux do). Warns when the rolls look uneven or patterned, shows how the words came from the entropy, and shows them as a Standard / Compact SeedQR | Implemented | Implemented |
| Backup check | A new mnemonic can be loaded as the signing key once it passes a five-question, four-choice check | Implemented | Implemented |
| BIP85 | Derives child mnemonics | Implemented | Implemented |
| Review a PSBT | Receives an animated UR crypto-psbt with the camera, parses it with parser.wasm and shows signer.wasm's review on a native screen |
Checked | Implemented |
| Sign | Signs after explicit approval and shows the signed PSBT as an animated UR. A round trip with BlueWallet has been checked | Checked | Implemented |
| Export the account | Public keys as an animated UR crypto-account (BIP49 / BIP84 / BIP86 / BIP48), readable by Sparrow / Nunchuk / Keystone style wallets, and the account xpubs and descriptors as QR codes |
Implemented | Implemented |
| Multisig | Registers a P2WSH multisig (up to 3 keys) from a descriptor / BSMS / Coldcard QR (ur:bytes too) after showing the keys and the first receive address. Afterwards change is shown as change and inputs from other wallets are not signed. Exports as crypto-output, and in save mode it is restored with the seed |
Implemented | Implemented |
| Addresses and messages | Address ownership checks and BIP137 message signing | Implemented | Implemented |
| Erase the key | On Lock, or when the app goes to the background | Implemented | Implemented |
| Save on the device | With "Save on this device" in Settings, 12 / 24 word seeds are kept on the device (memory only by default). See below | Implemented | Implemented |
Mnemonics, SeedQRs, raw seeds and passphrases are never returned to the WebView.
Only the BIP39 entropy is stored (16 / 32 bytes, the same as a CompactSeedQR). The passphrase is not stored, and is entered at each unlock, so one saved seed opens with any passphrase. The fingerprint without a passphrase is stored to tell saved seeds apart. 64 byte raw seeds and 15 / 18 / 21 word seeds cannot be saved.
| Android | iOS | |
|---|---|---|
| Encryption | A Keystore AES-256-GCM key per seed (StrongBox when available; nothing is saved with a key outside the TEE or StrongBox; unusable while the screen is locked) | ECIES with a Secure Enclave P-256 key per seed (X9.63 SHA-256, AES-GCM) |
| Authentication | Biometrics or the screen lock on every use (15 seconds after authenticating on Android 10 and earlier) | Face ID / Touch ID or the passcode (.userPresence) |
| Location | noBackupFilesDir/vault.json (allowBackup="false") |
Keychain (WhenPasscodeSetThisDeviceOnly) |
Save mode cannot be chosen on a device without a screen lock (passcode). Deleting and erasing all also need device authentication. Save mode cannot be turned off while saved seeds remain. On iOS the app switcher snapshot and the screen during recording are covered in black; iOS itself cannot block screenshots. Input, review, showing secrets, key derivation and signing happen in the native layer. Public account data, signed PSBTs and message signatures are shown on native screens.
Keyboard learning and autofill are covered in docs/faq.md.
On Android, Signer Lockdown (lockdown/) makes a factory-reset phone a dedicated signer. Scan the QR code from a release on the welcome screen, or install it over USB from the installer page to keep the phone off every network. The same page can also install just the signer, without Lockdown. It installs the signer, locks the Wi-Fi and Bluetooth settings of a phone with no SIM card, blocks USB debugging, new apps and accounts, hides apps the signer does not need, and updates the signer from an SD card or a PC over USB. See docs/lockdown.md.
The WASM modules ship in src-tauri/plugins/signer/android/src/main/assets/ and their SHA-256 is checked when they load.
| Module | SHA-256 |
|---|---|
| parser.wasm | 9407abe3a65a56ebcfcab6da3b685f4e21ce9f6a60b3feddd299ddc0def89bab |
| signer.wasm | d27fe4f8050193cc7101cf7fab26b0740a1b39e33c867ebc66cd05d9f9003508 |
| checker.wasm | 579bf43d0652ed8152fae6d2ad2b0bf97afb9422e96b0d9e3658a3bd9f2a5912 |
checker.wasm is an extra consistency check that runs after signer.wasm's review passes. It can only stop a signature, never allow one, so signing does not depend on trusting it. Its sources are not public yet.
On Android, Rust's Wasmi runs through JNI. On iOS, the Swift plugin calls the Rust Wasmi host. The minimum Android API is 26.
Install Android Studio, the Android SDK / NDK, JDK 25, and the Rust aarch64-linux-android and armv7-linux-androideabi targets.
pnpm install
pnpm build
JAVA_HOME=/path/to/jdk ANDROID_HOME=/path/to/android-sdk NDK_HOME=/path/to/ndk pnpm tauri android build --debug --target aarch64 armv7 --apk --ciThe output is src-tauri/gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk.
A debug build compiles the Rust side that runs the WASM (Wasmi) without optimization, so it is extremely slow on a device. On an ARMv7 phone, loading a mnemonic (PBKDF2) took about 90 seconds in a debug build and about 3 seconds in a release build. Test with release builds.
If src-tauri/gen/android/keystore.properties exists (storeFile / storePassword / keyAlias / keyPassword; never commit it), release builds are signed with that key. Otherwise build an unsigned APK and sign it afterwards. The example below signs with the debug key, the same key as debug builds, so it installs over them and keeps the app's data. Do not use it for distribution.
JAVA_HOME=/path/to/jdk ANDROID_HOME=/path/to/android-sdk NDK_HOME=/path/to/ndk pnpm tauri android build --target armv7 --apk --ci
BT=$ANDROID_HOME/build-tools/<version>
$BT/zipalign -f -p 4 src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release-unsigned.apk aligned.apk
$BT/apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android --key-pass pass:android \
--ks-key-alias androiddebugkey --out bithanko-release.apk aligned.apk
adb install -r bithanko-release.apkMatch --target to the device's ABI (adb shell getprop ro.product.cpu.abilist; arm64-v8a means aarch64).
Install Xcode and the Rust aarch64-apple-ios target, and pass your own Apple team ID.
APPLE_DEVELOPMENT_TEAM=<your team ID> pnpm tauri ios build --debug --export-method debuggingpnpm build:pwa # writes to dist-web/appcargo test --manifest-path src-tauri/plugins/signer/Cargo.tomlPushing a v<version in tauri.conf.json> tag makes GitHub Actions attach a universal APK (aarch64 / armv7) signed with the release key, Signer Lockdown with that APK inside, its provisioning QR code and a SHA256SUMS file to a draft release. Register these repository secrets first.
| Secret | Contents |
|---|---|
ANDROID_KEYSTORE_BASE64 |
The release keystore in base64 |
ANDROID_KEYSTORE_PASSWORD |
The keystore password |
ANDROID_KEY_ALIAS |
The key alias |
ANDROID_KEY_PASSWORD |
The key password |
With a different release key the app cannot be installed over the old one, and uninstalling it also erases the seeds saved on the device. Back up the keystore outside the repository.
Try it at https://bitwasm-project.github.io/bit-hanko/ (deployed from main by GitHub Actions).
The same engine as iOS (wasmi_host.rs) is built for wasm32 and runs inside the page. It is the web app's code, testnet only, with a test key, address and transaction added from src/web/demo-inject.ts. Never enter a real seed: the page is online and has no offline check. The new wallet flow (walkthrough, dice, write-down and quiz) can be tried too; the words it makes are real BIP39 words made in a connected browser, so every screen showing them is stamped as a demo. It runs on a device that is online, so never use it with real keys or funds.
pnpm dev:web # open http://localhost:1420
pnpm build:web # writes to dist-webTo try signing, scan a QR from a wallet app with the camera, or use the test transaction from samples/psbt-ur-test-only.png. The camera only works on localhost or HTTPS pages.
The Markdown in docs/ (English) and docs/ja/ (Japanese) is published with VitePress at https://bitwasm-project.github.io/bit-hanko/docs/. A page with demo: in its frontmatter shows the browser demo in the right column; it maps section ids to the buttons to tap, so the phone moves to that screen when the section scrolls into view (see docs/index.md).
pnpm dev:docs # the demo comes from the published site
pnpm build:docs # writes to dist-web/docs, after pnpm build:webWhat has been checked on each platform is in the table under What it does on Android / iOS. Native hosts for Windows / macOS / Linux are not implemented.
Plans are in docs/roadmap.md.
MIT. See LICENSE.

