PKTap¶
PKTap is a privacy-first Android app for decentralized contact exchange. Tap two phones together over NFC, and each phone gets the other's encrypted contact info — with no accounts, no servers, and no cloud service ever involved.
Why I Built This¶
Sharing contact information on phones currently means trusting an intermediary. AirDrop requires an Apple account. Sharing via QR codes sends plaintext. Bluetooth contact exchange apps route data through servers. The pattern of "tap phones and share" should not require an account, a server, or a cloud service to be private.
The goal was a tool that uses the hardware already in both phones — NFC — to bootstrap a cryptographic key exchange, encrypts the contact fields locally, and uses the Mainline DHT as the only network touch point. No PKTap server is ever contacted. Records expire from the DHT naturally. The master key is non-extractable from the Android Keystore using StrongBox or Trusted Execution Environment hardware — it cannot leave the device.
The architecture splits cleanly: the Rust core (pktap-core) handles all cryptographic operations — key generation, ECDH, XChaCha20-Poly1305 encryption, PKARR DHT publish and resolve, BIP-39 mnemonic backup — and the Android app handles UI and NFC. No crypto runs in the JVM layer. That split was the design goal from the start, not an afterthought.
How It Works¶
The exchange happens in four steps. Two phones tap over NFC using Android Host Card Emulation, exchanging Ed25519 public keys. X25519 ECDH derives a shared secret; contact fields are encrypted with XChaCha20-Poly1305. The encrypted record is published to the Mainline DHT at a deterministic address — the SHA-256 hash of the sorted pair of public keys. The other device resolves that same address, decrypts, and displays the contact fields.
+------------------+ UniFFI +------------------+
| Android App | <================> | pktap-core |
| (Kotlin/ | (Kotlin FFI) | (Rust) |
| Compose) | | |
+------------------+ +------------------+
| Jetpack Compose | | Ed25519 keys |
| NFC HCE Service | | X25519 ECDH |
| Android Keystore | | XChaCha20-Poly1305|
| Room DB | | Pkarr/DHT client |
| Navigation | | BIP-39 mnemonic |
+------------------+ +------------------+
The Mainline DHT imposes a 1000-byte ceiling on PKARR records, which limits exchange to text fields — phone numbers, email addresses, a short bio. All secret material is zeroed after use: the Rust layer uses the zeroize crate; the Kotlin layer uses explicit ByteArray zeroing. The master key is stored in the Android Keystore under StrongBox or TEE and is non-extractable from the device.
The iOS limitation shaped the Android-only design from the start. iOS does not support NFC Host Card Emulation for write operations, only for payment use cases. Android HCE is the only practical path for symmetric NFC contact exchange on consumer hardware.
What I Learned¶
PKTap is at v1.0 with 5 of 7 phases complete. The Rust crypto core, PKARR DHT integration, UniFFI bridge, Android Keystore module, and NFC HCE module are all done. App Integration and Core UI (Phase 6) and QR Fallback and Public Mode (Phase 7) are still pending. The current state is a working cryptographic stack and NFC subsystem — not yet a complete user-facing Android app.
This section is intentionally thin because the project is mid-build. The concrete engineering lessons will land when Phase 6 and Phase 7 are complete — there is real ground to cover around Jetpack Compose state management, the Room database schema for contact history, and the QR fallback path for cross-platform exchange. John will expand this section once those phases ship.
What is clear now: the Rust/Kotlin split via UniFFI was the right call. Cross-compiling the Rust library to Android targets (aarch64, armv7, x86_64) and generating Kotlin bindings via UniFFI adds build complexity — the Gradle build invokes cargo-ndk to cross-compile and then runs uniffi-bindgen — but it means the cryptographic primitives are memory-safe by construction and shareable across any future platform targets. Writing the same crypto twice in JVM would have been a worse tradeoff.
Links¶
Tap to share — no accounts, no servers, no middleman.