How File Encryption Works

What the core actually does under the hood when a user encrypts their wallet — and, just as importantly, what it does not do.

⚠️ This encryption is not quantum-safe, and that is a deliberate choice

AES-256 is believed to remain usable against quantum attack (Grover's algorithm roughly halves the effective key strength, leaving ~128 bits), but this design has never been evaluated as a post-quantum system and should not be described as one.

The purpose of file encryption here is narrower than it may appear. It protects coin files at rest on a disk that someone else might get hold of — a stolen laptop, a lost USB stick, a recycled drive. That is the threat it was built for. It is not a long-term confidentiality guarantee against a well-funded future adversary who records your ciphertext today and breaks it in twenty years.

Encryption is also off by default, and the wallet works normally without it. Treat it as a useful lock on a filing cabinet, not as a vault.

1. Why only part of the file is encrypted

A Format 10 file is 612 bytes, of which exactly 400 are encrypted: the 25 authenticity numbers. Everything else — format byte, denomination, serial number, POWN status, RAIDA identifiers, salt, nonce, checksum — is plaintext.

This surprises people, so here is the reasoning:

  • The authenticity numbers are the only secret. They are the money: anyone who learns 13 of the 25 can take the coin. Denomination and serial number are not secrets — the RAIDA network knows them, and they appear in the filename.
  • The wallet must work while locked. Showing a balance, counting coins, deciding whether a password prompt is needed, and reporting status all need metadata. If the whole file were encrypted, none of that would work until the user typed their password, and every folder scan would require an expensive key derivation.
  • Encrypting metadata would buy almost nothing. An attacker holding your file already knows it is a CloudCoin file of a certain size. Hiding the denomination does not protect the coin; protecting the ANs does.

The layout is arranged so the ciphertext starts at offset 208 and each encrypted AN begins on a 16-byte boundary — convenient in a hex viewer and aligned for the cipher.

2. What happens to the user's password

Short version: it is never written to disk, never sent over the network, and never stored in any recoverable form.

  1. The password arrives as raw UTF-8 bytes via POST /api/system/load-password. No normalization, no case folding, no trimming, no padding, no terminating NUL — two clients must send byte-identical input to derive the same key.
  2. It is fed to Argon2id along with a 16-byte random salt read from the coin file. Argon2id is memory-hard: the portable profile uses 64 MiB of RAM and three passes. This is what makes brute-forcing expensive — an attacker cannot test billions of candidates per second on a GPU the way they could against a plain hash.
  3. The 32-byte result becomes the AES-256 key. It is held in process memory only, protected by a mutex, and wiped with a non-elidable secure-clear routine on logout, on shutdown, and on every error path.
  4. The password itself is retained in memory only when it is needed for a pending first-encryption, and is wiped the moment that completes or fails.
There is no password file, and no recovery

Earlier CloudCoin software kept a verifier.bin containing a salted hash of the password so it could answer "wrong password" instantly. That file is gone. It was itself an offline attack surface: anyone who copied it could test passwords locally at full speed. Removing it means a lost password is unrecoverable — the coins are cryptographically inaccessible. This trade-off is intentional, and users must be told plainly before they encrypt.

3. When files are encrypted and decrypted

Conversion is explicit and user-driven. The core never silently encrypts.

Turning encryption on

  1. User picks Encrypt coins and enters a password (the interface should ask twice — see pitfalls).
  2. POST /api/system/load-password holds the password in memory.
  3. POST /api/system/encrypt_existing_files starts a background job. One random 16-byte salt is generated for the whole wallet, and every plaintext single-coin file is rewritten as Format 10. The job returns a task id; poll /api/system/tasks for counts.

Day-to-day use

Coins are decrypted in memory, one at a time, only when needed — to spend, heal, or inspect a coin. Plaintext authenticity numbers are never written back to disk while encryption is on. Every rewrite of a coin produces a completely fresh encrypted file with a new random nonce.

Turning encryption off

POST /api/system/decrypt_existing_files converts every Format 10 file back to plaintext Format 9. Coins then stay decrypted until the user chooses to encrypt again, and the next startup asks for no password.

Exporting

Coins sent to another person are always written as plaintext Format 9, even while your wallet is encrypted. The recipient cannot derive your password-based key, so an encrypted export would be worthless to them.

4. The API surface

EndpointMethodPurpose
/api/system/encryption-statusGETIs the wallet encrypted, decrypted, or mixed? Are files present, is a key loaded, is a password needed? Call this before prompting.
/api/system/load-passwordPOSTLoad the decryption password into RAM. POST only — a password in a URL would be written to server and proxy logs.
/api/system/encrypt_existing_filesPOSTConvert plaintext coin files to encrypted. Async; returns a task id.
/api/system/decrypt_existing_filesPOSTConvert encrypted coin files back to plaintext. Async; returns a task id.
/api/system/shutdownPOSTDelete the password from RAM and shut the core down. This is the log-out action.
Logging out means shutting down

There is no "clear the key but keep running" operation. Decrypted coin data can linger in process memory beyond the key itself — caches, buffers, request state — so exiting the process is the only way to be confident it is gone. The interface should call /api/system/shutdown and then exit itself.

A client can also determine wallet state without any API call, simply by looking at file sizes in the wallet's Bank or Fracked folder: 612 bytes = encrypted, 439 bytes = plaintext. Anything else should be ignored, since new formats may appear later.

5. Security design, and how each piece earns its place

MechanismWhat it does
Argon2id, 64 MiB, 3 passesMakes each password guess cost real memory and time, so offline brute force is slow and GPU/ASIC attacks lose most of their advantage.
AES-256-CTREncrypts the 400 AN bytes. Counter mode adds no length overhead and needs no padding, so the file stays a fixed 612 bytes.
Fresh 12-byte nonce per writeEvery rewrite gets a new random nonce, so the same coin encrypted twice never produces the same ciphertext. Reusing a nonce with the same key would leak plaintext.
No authentication tag — on purposeAn AEAD tag would let anyone holding the file test passwords offline: decrypt, check tag, repeat. Its absence is what makes the next row possible.
No password check of any kindThe file contains nothing that reveals whether a candidate password is right. Every password produces 400 plausible-looking bytes.
Verification via RAIDABecause nothing local can confirm a password, the core decrypts a coin and asks the RAIDA network whether its ANs are authentic. 13 of 25 agreeing confirms it.
Fail-closed write gateIf the wallet is encrypted and no confirmed key is loaded, coin writes are refused. The core will never fall back to writing plaintext into an encrypted wallet.
CRC-32C checksumCatches accidental corruption — bad sectors, truncated copies. It is public and recomputable, so it is not a security control.

The unusual property here is the deliberate absence of a password check. Most encryption tells you immediately when a password is wrong. This one cannot, by design — because anything that can tell you can also tell a thief holding your stolen drive.

6. An honest assessment of how strong this is

What it genuinely protects against

  • A stolen or lost drive, one snapshot, strong password. This is the case the design targets and handles well. An attacker gets 400 bytes indistinguishable from random and no way to check a guess offline. With a strong password, Argon2id makes brute force impractical.

What it does not protect against — plainly stated

  • A weak password. Argon2id raises the cost per guess; it does not save a password found in a wordlist. Encryption is only as strong as what the user chose.
  • Multiple backups over time. If an attacker holds two snapshots of the same coin taken at different times and any authenticity number did not change between them, they can test passwords by looking for that repeated value across candidate decryptions. Fresh salts and nonces do not prevent this. Keeping many historical backups weakens the main guarantee.
  • An attacker who already knows one authenticity number — from an old export, a packet capture, a previous owner. They can test candidate passwords by decrypting and checking for that known value.
  • Anything running on an unlocked machine. Malware, keyloggers, or memory inspection while the wallet is unlocked defeat file encryption entirely. The key is in RAM by necessity.
  • Deliberate tampering. There is no authentication tag, so an attacker can modify ciphertext or POWN and the file will still parse. They cannot produce ANs that the RAIDA will accept, but they can corrupt or destroy your coins. Encryption protects confidentiality here, not integrity.
  • Rollback. Nothing stops someone from replacing your file with an older complete copy.
  • Long-term recorded ciphertext. As stated at the top, this is not a post-quantum design.
  • Secure erasure. On SSDs, USB flash, and snapshotting filesystems, the plaintext that existed before encryption may still be physically recoverable.

Summary judgement: good protection for a lost device, with a strong password and few retained backups. Not a guarantee against a determined, well-resourced adversary, and not a substitute for keeping your machine clean.

7. Pitfalls for developers

  • Never write plaintext coin files into an encrypted wallet. Route every wallet write through the standard coin-file writers, which consult the gate. If a write returns RESULT_LOCKED, the correct response is to stop — not to retry in plaintext.
  • A refused write must never be followed by a file move. Several sweeps used to relocate a coin whose write failed. If the write was refused because the wallet is locked, moving the file relocates a perfectly good coin on the basis of a failure that never happened. Check the result before moving anything.
  • Do not treat "locked" as "corrupt". An encrypted file with no key loaded returns RESULT_LOCKED, not a parse error. Code that lumps every non-success into "corrupt → move to Trash" will quarantine a healthy encrypted wallet the moment a user starts the program without logging in.
  • Never report "wrong password" from local decryption alone. It cannot be known locally. Decryption always "succeeds" and yields 400 bytes. Only RAIDA verification can distinguish a wrong password, and even then the honest wording is that the password may be wrong or the coins may be counterfeit.
  • A wallet can be in a mixed state. If an encrypt or decrypt job is interrupted, some files are converted and some are not. Check for mixed in the status response and offer to finish the job; both directions are safely resumable.
  • Never assume a semantic no-op means no work. Writers skip rewriting when the coin's contents already match what is on disk — but a file that needs to change format must still be rewritten. This bug has appeared more than once: an encrypt job reporting success while every file stayed plaintext.
  • Ask for the password twice on first encryption. There is no verifier and nothing to check against, so a typo at that moment encrypts the entire wallet under a password the user does not know. It is unrecoverable and undetectable. Double entry is the only defence.
  • Do not log authenticity numbers or password material. Debug mode changes verbosity, not behaviour, and must never expose key material.
  • Backups of encrypted files must be byte-for-byte copies. Never decrypt to back up. A copy of ciphertext is still protected; a decrypted backup is not.
  • Format 10 always holds exactly one coin. There is no coin-count field and no way to fit a second. Multi-coin files are plaintext Format 9, export-only.

8. Related pages