The "Tear-off" Nightmare
If you’ve ever worked with NFC development, you know the "Tear-off" effect.
You are in the middle of writing a block of data to a MIFARE Classic card. The user’s hand shakes, the phone moves 1cm away from the tag, and boom—the RF field collapses. You just suffered a partial write. The data is corrupted, the checksums fail, and in the worst-case scenario, your application-level logic "bricks" the card because it's now in an inconsistent state.
In my project AECardTools, I decided that "good enough" wasn't enough. I wanted an NFC storage system that was physically impossible to brick, even if you pull the card away mid-write.
Here is how I built a Log-structured Copy-on-Write (LCOW) engine that fits inside a tiny 1KB NFC chip.
The Challenge: 1024 Bytes of Chaos
A MIFARE Classic 1K card is not a "hard drive." It's a collection of 16 sectors, each protected by keys. Normally, developers overwrite data in place. If the power cuts at byte 8 of 16, you are toast.
To solve this, I implemented three core concepts:
- Never Overwrite: We never modify the "current" valid data. We always write to a new location.
- Atomic Commits: Data is only considered "real" once a single, tiny pointer (the Anchor) is updated at the very end.
- Maximizing Payload (The 900-Byte Hack): Standard MIFARE 1K usually only gives you 752 bytes of user data because the "Sector Trailers" (which store Keys A and B) take up space.
The Trick: In AECardTools, I treat the card as a raw encrypted canvas. I store the Sector Keys locally in the app's encrypted database. This allows me to reclaim the Key A/B areas for data storage, pushing the usable capacity to roughly 900 bytes.
The Architecture: How LCOW Works
The engine (written in Python via Chaquopy for the heavy lifting, and Kotlin for the NFC I/O) treats the card as a series of versioned logs.
1. The Ping-Pong Anchors
I reserved the very last sector (Sector 15) as the "Command Center." It contains two "Anchors."
- Anchor A and Anchor B act like a toggle switch.
- Each anchor contains a Transaction Sequence Number (TSN) and a pointer to the current root block.
2. The Write Flow
When you save a new file to the card:
- Find Free Space: The engine looks for sectors not occupied by the current version.
- Write New Data: It writes the new encrypted payload to these "shadow" sectors. If the user pulls the card away now, the old data is still sitting safely in its original sectors.
- The Atomic Flip: Only after the data is 100% verified does the engine write a new TSN+1 to the other Anchor.
3. The Recovery
When the app scans a card, it looks at both Anchors.
- If Anchor A says
TSN: 10and Anchor B saysTSN: 11, the engine instantly knows that Version 11 is the truth. - If the write was interrupted, Version 11 would have a failed CRC or an older TSN, and the engine would automatically "roll back" to Version 10. Zero data loss. Zero bricking.
4. Architecture diagram
To bridge the gap between high-level Python logic and raw Android NFC hardware, I implemented a Neuromorphic FFI Bridge. Here is the high-level architecture:
┌─────────────────────────────────────────────────────────────────┐
│ AECardTools: Sovereign Architecture │
├─────────────────────────────────────────────────────────────────┤
│ [UI Layer] (Kotlin / Jetpack Compose) │
│ - HexCanvas & Registry Editor UI │
│ - Security Disclaimer & Transaction Monitoring │
└───────────────┬─────────────────────────────────────────────────┘
│ Reactive StateFlow
┌───────────────▼─────────────────────────────────────────────────┐
│ [ViewModel / Session Manager] │
│ - Global NFC Session tracking │
│ - Hardware I/O Orchestration │
└───────────────┬─────────────────────────────────────────────────┘
│ JNI / Chaquopy FFI Bridge
┌───────────────▼─────────────────────────────────────────────────┐
│ [Python Core Engine] (The "Brain") │
│ ┌────────────────────┐ ┌─────────────────────────────────┐ │
│ │ LCOW Engine │ │ Cryptography Module │ │
│ │ - Virtual Address │ │ - Argon2id Key Derivation │ │
│ │ - Transaction Logs │ │ - XChaCha20-Poly1305 (AEAD) │ │
│ │ - GC Controller │ │ - Merkle Tree Integrity Check │ │
│ └──────────┬─────────┘ └─────────────────────────────────┘ │
└─────────────┼───────────────────────────────────────────────────┘
│ Callbacks
┌─────────────▼───────────────────────────────────────────────────┐
│ [NFC Hardware Layer] (Kotlin / android.nfc.tech) │
│ - Universal Protocol Manager (IsoDep / NfcA / NfcB) │
│ - Sensitive Instruction Interceptor (Brick Protection) │
└──────────────────────────────┬──────────────────────────────────┘
│ RF Field (13.56MHz)
┌──────────▼──────────┐
│ MIFARE Classic 1K │
└─────────────────────┘
The real magic happens in the memory remapping. I treat the 16 sectors not as rigid silos, but as a virtualized block pool managed by the LCOW engine:
PHYSICAL STORAGE (1024B) LOGICAL VIEW (AEFS v5.5)
┌──────────────────────────┐ ┌──────────────────────────┐
│ Sector 0: Genesis Block │────────▶│ 0x000 - 0x01F: Metadata │
├──────────────────────────┤ ├──────────────────────────┤
│ Sectors 1-14: │ │ 0x020 - 0x2EF: │
│ │ │ │
│ [ LOG-STRUCTURED POOL ] │────────▶│ [ SEAMLESS PAYLOAD ] │
│ (42 Data Blocks) │ │ │
│ │ │ (Encrypted Canvas) │
├──────────────────────────┤ └──────────────────────────┘
│ Sector 15: Superblock │
│ [Block 0]: Anchor A (Ping)◀───┐ ATOMIC FLIP LOGIC:
│ [Block 1]: Anchor B (Pong)◀───┴──[ Active pointer toggles ]
│ [Block 2]: GC / Bitmap │ [ only after successful ]
│ [Block 3]: Keys & ACL │ [ verify-after-write ]
└──────────────────────────┘
Hardening the Security: Sovereign Keys
Since I'm using the Key A/B sectors for data to hit that 900-byte goal, the security model changes.
Most NFC apps rely on the card's hardware to check keys. But MIFARE Classic's "Crypto1" algorithm is famously broken. Instead, I implement Sovereign Encryption:
- The card is just a "dumb" encrypted block device.
- The encryption (XChaCha20-Poly1305) and key derivation (Argon2id) happen entirely inside the Android app.
- Even if someone cracks the RFID hardware layer, they just see a 900-byte blob of high-entropy noise.
The Result
By combining a Copy-on-Write strategy with a Local Key Management system, I turned a cheap $0.50 RFID tag into a robust, atomic, and encrypted mini-vault.
It’s a reminder that even when working with "ancient" hardware from the 90s, modern software patterns like LCOW can solve physical-layer reliability problems.
If you're building anything with NFC, you might find this useful 👇
👉 GitHub: AECardTools
Top comments (0)