Cryptographic primitives
| Function | Algorithm | Parameters |
|---|---|---|
| KDF | Argon2id | OPSLIMIT_MODERATE, 256 MiB |
| AEAD | XChaCha20-Poly1305 | nonce 24B, tag 16B |
| HKDF | BLAKE2b-keyed | output 32B |
| Signatures | Ed25519 | RFC 8032 |
| Key Exchange | X25519 | RFC 7748 |
| Sealed Boxes | X25519 + XSalsa20-Poly1305 | derived nonce |
| Hash | BLAKE2b-256 | output 32B |
Key hierarchy
// Full derivation from the password
password
│ Argon2id(salt, opsLimit, memLimit)
▼
Master Key (MK)
├── BLAKE2b("vault.wrap") → K_vault_wrap
│ └── Unwrap → vault_key → file_key → chunks
├── BLAKE2b("login.sign") → seed → Ed25519 keypair
└── Unwrap → sk_exchange (X25519)
// Parallel branch: the recovery phrase (12 BIP39 words)
mnemonic
│ BLAKE2b(key="recovery.v1")
▼
recovery seed
├── Ed25519 → signs the recovery challenge
└── BLAKE2b("recovery.box.v1") → X25519 → opens the kit's seals
Account recovery (kit v2)
Your 12-word phrase (BIP39, 128 bits of entropy) doesn't just recover access: it also recovers your files. From it a X25519 pair is derived whose public key is registered on the server; with that public key, your browser seals (crypto_box_seal) each vault's key and your exchange key. Sealing only needs the public key — opening the seals needs only the private key, which exists solely if you have the phrase.
You forgot the password → nothing is lost
The phrase signs a challenge, the server hands over the seals, your browser opens them and re-encrypts the vault keys with your new password. Your files and everything shared with you stay with you.
The server only keeps sealed envelopes
The seals are ciphertext: neither Noctcom nor an attacker with the database can open them. The private key that opens them is derived from your phrase and never travels.
You lose phrase AND password → unrecoverable
There's no back door, no 'contact support'. It's the price of real zero-knowledge, and we say it with no fine print.
File encryption
4 MiB chunks
Each file is split into 4 MiB chunks, encrypted independently with XChaCha20-Poly1305. Each chunk uses a random 24-byte nonce.
Anti-reorder AAD
Each chunk includes its index as Additional Authenticated Data (AAD = 'chunk:N'). It prevents an attacker from reordering chunks.
Content hash
BLAKE2b-256 over all encrypted chunks. Verifies integrity on every download.
Zero-knowledge email
// The server never sees your email
email_hash = BLAKE2b(
message = normalize(email),
key = "noctcom.email.v1",
output = 32 bytes
)
External audits
No external audits have been carried out yet. Once they are completed, the full reports will be published here and in the repository.
In the meantime, the code is 100% auditable on GitHub.