NFC Backup Cards
How Numa stores an encrypted copy of your seed phrase on a physical NFC card — and how to recover it with or without the app.
What it is
Numa's signer mode can write a copy of your BIP39 seed phrase to an ordinary NFC card (NTAG215 or NTAG216), encrypted with a password you choose. Restoring is the reverse: tap the card, enter the password, and the words are filled in for you.
The format is deliberately open and fully documented on this page. If Numa disappeared tomorrow, anyone could still recover the seed using standard, widely available tools. Your backup never depends on us.
How the data is stored
The card holds a single NDEF record (the standard NFC data format readable by any phone) with the MIME type application/vnd.numa.wallet-backup. Its payload is a small JSON document:
{
"v": 1,
"t": "bip39",
"kdf": "argon2id",
"mem": 131072,
"it": 4,
"par": 1,
"s": "<base64: 16-byte random salt>",
"n": "<base64: 12-byte random nonce>",
"c": "<base64: ciphertext + 16-byte GCM tag>"
}
| Field | Meaning |
|---|---|
v | Format version (always 1) |
t | Payload type — a BIP39 mnemonic |
kdf | Key derivation function (Argon2id, version 1.3) |
mem | Argon2 memory cost in KiB (131072 = 128 MiB) |
it | Argon2 passes (4) |
par | Argon2 parallelism (1) |
s | Random salt for key derivation |
n | AES-GCM nonce |
c | The encrypted mnemonic, with the GCM authentication tag appended |
Anyone who taps the card can read this JSON — NFC has no access control. That is by design: all secrecy lives in the encryption, not in hiding the format. What an attacker gets is ciphertext they cannot open without your password.
The encryption
Your password is turned into a 256-bit key with Argon2id — the winner of the Password Hashing Competition and the OWASP first choice — configured at 128 MiB of memory and 4 passes. Every password guess costs an attacker 128 MiB of RAM and real CPU time, which cripples GPU cracking rigs: a 24 GB GPU fits ~190 parallel guesses instead of the millions it could run against a conventional hash.
The mnemonic is then encrypted with AES-256-GCM, an authenticated cipher. The GCM tag doubles as a correctness check: a wrong password fails authentication cleanly — decryption can never silently produce the wrong words.
- The password is used exactly as typed (UTF-8, case-sensitive, no normalization).
- Salt and nonce are freshly random for every card written — two cards with the same seed and password still carry different bytes.
- Your BIP39 passphrase (the "25th word"), if you use one, is never stored on the card. Card plus password alone cannot unlock a passphrase-protected wallet.
The password is the whole defense. Argon2id makes each guess expensive, but no algorithm rescues a guessable password — avoid names, dates, and anything you use elsewhere. Write the password down and store it away from the card. Without it, the backup is unrecoverable — by you or anyone else.
How to decrypt without the app
Read the card's NDEF record with any NFC tool (for example "NFC Tools" on iOS/Android) and copy the JSON payload. Then run the reference script below — it uses only standard, audited open-source libraries:
# pip install argon2-cffi cryptography
import base64, json
from argon2.low_level import hash_secret_raw, Type
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
card = json.loads(input("card JSON: "))
password = input("card password: ").strip()
key = hash_secret_raw(
password.encode("utf-8"),
base64.b64decode(card["s"]),
time_cost=card["it"],
memory_cost=card["mem"],
parallelism=card["par"],
hash_len=32,
type=Type.ID,
)
words = AESGCM(key).decrypt(
base64.b64decode(card["n"]),
base64.b64decode(card["c"]),
None,
).decode()
print(words)
Argon2id and AES-256-GCM are available in every major ecosystem — libsodium, the Argon2 reference implementation, hash-wasm for browsers, and countless others. Any implementation that follows the parameters above will produce the same key and decrypt the same card.
Test vector
Implementations can verify themselves against this fixed vector. Password: correct horse battery staple — it decrypts to the standard BIP39 test mnemonic (11× "abandon" + "about"):
{"v":1,"t":"bip39","kdf":"argon2id","mem":131072,"it":4,"par":1,
"s":"AAECAwQFBgcICQoLDA0ODw==","n":"oKGio6Slpqeoqaqr",
"c":"hf9UJ59swabLSp32UhLkLOUCweQlCcqoI9eh5q60V3us/Fni7lplS73pVrdUv672SapqLnaCzCuRVaXzJ5MgSYja5DeORsv4VrMGq+sAhjlvyykhCYCDd35Fr3p766wAKm7lxJsasZqNGoh9tg=="}
Good practices
- Test the restore right after writing a card: open the seed import screen, tap the card, and confirm the password works.
- Treat the card as one backup among others — never the only one. NFC cards can be lost, damaged, or demagnetized.
- Keep the card and its password in different places. Together they are the seed.
- Use NTAG215 or NTAG216 cards. Optionally, the app can permanently lock the card read-only after writing.
Format guarantee: version 1 is frozen. Future versions of Numa may add new formats, but every released build will always be able to decrypt v1 cards, and this page will remain the authoritative spec.
← Back to Numa Wallet