Sanctum: Cryptographically Deniable Vault System with IPFS Storage
Source: Dev.to

đ The Problem: Encryption Isnât Enough
Traditional encrypted storage has a fatal flaw:
Attacker: "Give me the password or else."
You: "I don't have one."
Attacker: *checks encrypted file* "This is clearly encrypted. Try again."
You canât prove the absence of dataâuntil now.
⨠The Solution: Cryptographic Deniability
Sanctum creates three indistinguishable layers:
| Layer | Description |
|---|---|
| Decoy Layer | Innocent content (family photos, a small wallet with $200) |
| Hidden Layer | Real secrets (whistleblower docs, main crypto wallet) |
| Panic Layer | Shows âvault deletedâ under duress |
The magic? All three are cryptographically identical. An adversary cannot prove which layer is realâor even if hidden layers exist.
đď¸ Architecture: ZeroâTrust by Design
ClientâSide Encryption Flow
// 1. User creates vault with decoy + hidden content
const decoyBlob = encrypt(decoyContent, ''); // Empty passphrase
const hiddenBlob = encrypt(hiddenContent, deriveKey(passphrase));
// 2. XOR both layers (makes them indistinguishable)
const combined = xor(decoyBlob, hiddenBlob);
// 3. Upload to IPFS
const decoyCID = await ipfs.upload(decoyBlob);
const hiddenCID = await ipfs.upload(hiddenBlob);
// 4. Splitâkey architecture
const keyA = randomBytes(32); // Stays in URL
const keyB = randomBytes(32); // Encrypted in database
const vaultURL = `https://sanctumvault.online/unlock/${vaultId}#${encode(keyA)}`;
Tech Stack
- Frontend: Next.jsâŻ15 + React + Web Crypto API
- Cryptography: XChaCha20âPoly1305 + Argon2id (256âŻMB memory, 3 iterations)
- Storage: IPFS via Pinata / Filebase (free tiers)
- Database: Cloudflare D1 (splitâkey storage only)
- Hosting: Cloudflare Pages (static site)
Security Features
// RAMâonly storage (no disk persistence)
class SecureStorage {
private keys = new Map();
store(key: string, value: Uint8Array): void {
this.keys.set(key, value);
// Autoâclear after 5âŻminutes
setTimeout(() => this.wipe(key), 300_000);
}
wipe(key: string): void {
const data = this.keys.get(key);
if (data) {
data.fill(0); // Overwrite memory
this.keys.delete(key);
}
}
}
// Panic key: Doubleâpress Escape
let escapeCount = 0;
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
escapeCount++;
if (escapeCount === 2) {
wipeAllKeys();
window.location.href = '/';
}
setTimeout(() => (escapeCount = 0), 500);
}
});
đŻ RealâWorld Use Cases
-
Journalist Protecting Sources
Decoy: Published articles, public research notes Hidden: Confidential source documents, whistleblower communications Scenario: Device seized at border â reveal decoy, sources stay protected -
Crypto Holder Under Duress
Decoy: Small wallet with $200 ("this is all I have") Hidden: Main wallet with life savings Scenario: $5 wrench attack â hand over decoy wallet, real funds stay safe -
Activist in Authoritarian Regime
Decoy: Personal photos, innocuous social media content Hidden: Protest coordination plans, evidence of government abuse Scenario: Police raid â show decoy layer, cannot prove hidden content exists
đĄď¸ Attack Resistance
| Attack Vector | Defense |
|---|---|
| Physical Duress | Reveal decoy passphrase; hidden layer remains indistinguishable. |
| Disk Forensics | RAMâonly storage; keys never written to disk; autoâwiped on tab close. |
| Timing Analysis | Randomized 500â2000âŻms delay on all operations. |
| Blob Size Analysis | Padded to standard sizes (1âŻKB, 10âŻKB, 100âŻKB, 1âŻMB, 10âŻMB, 25âŻMB). |
| Brute Force | Argon2id with 256âŻMB memory makes bruteâforce computationally infeasible. |
đ Quick Start
For Users
Visit sanctumvault.online
- Configure Pinata or Filebase (free IPFS providers)
- Create a vault with optional decoy content
- Set a passphrase for the hidden layer
- Share the link â only you know the passphrase
For Developers
# Clone repository
git clone https://github.com/Teycir/Sanctum.git
cd Sanctum
# Install dependencies
npm install
# Run development server
npm run dev
đŹ Technical Deep Dive
Why XChaCha20âPoly1305?
// AESâGCM: 96âbit nonce (collision risk after 2^48 encryptions)
// XChaCha20: 192âbit nonce (collision risk after 2^96 encryptions)
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
export function encrypt(
plaintext: Uint8Array,
key: Uint8Array
): Uint8Array {
const nonce = randomBytes(24); // 192âbit nonce
const cipher = xchacha20poly1305(key, nonce);
return cipher.encrypt(plaintext);
}
XChaCha20âPoly1305 provides a vastly larger nonce space, eliminating nonceâreuse concerns and offering authenticated encryption with strong performance in the browser.
// Encryption function
function encryptData(
plaintext: Uint8Array,
key: Uint8Array
): EncryptionResult {
const nonce = randomBytes(24); // 192âbit nonce
const cipher = xchacha20poly1305(key, nonce);
const ciphertext = cipher.encrypt(plaintext);
return {
ciphertext,
nonce,
// Authenticated encryption tag (last 16âŻbytes)
authTag: ciphertext.slice(-16),
};
}
SplitâKey Architecture
// KeyA: Stays in URL fragment (never sent to server)
// KeyB: Encrypted in database with vaultâspecific key
const vaultKey = deriveKey(vaultId + salt);
const encryptedKeyB = encrypt(keyB, vaultKey);
// To decrypt IPFS CIDs:
const masterKey = xor(keyA, keyB);
const decoyCID = decrypt(encryptedDecoyCID, masterKey);
const hiddenCID = decrypt(encryptedHiddenCID, masterKey);
30âDay Grace Period
-- StageâŻ1: Soft delete (mark inactive)
UPDATE vaults
SET is_active = 0
WHERE expires_at < NOW();
-
â NOT received any:
- National Security Letters (NSLs)
- FISA court orders
- Gag orders
- Requests to implement backdoors
-
â Architecture guarantees:
- Zeroâknowledge: Cannot decrypt user vaults
- No user logs: No IP addresses or metadata
- No backdoors: All code is openâsource
- RAMâonly: No persistent storage of keys
đ Why IPFS?
Traditional cloud storage has single points of failure:
| Issue | Cloud Storage | IPFS |
|---|---|---|
| Centralized | Provider can be compelled to hand over data | Data replicated across many nodes |
| Censorable | Governments can block access | Contentâaddressed (CID), not locationâbased |
| Deletable | Provider can delete your data | Immutable â once uploaded, cannot be modified |
| Cost | Ongoing fees | Free tiers: Pinata (1âŻGB) + Filebase (5âŻGB) |
đŤ What Sanctum Is NOT
- â A password manager â Use KeePassXC / Bitwarden for that
- â A backup solution â IPFS data can be unpinned
- â A fileâsharing service â Links are permanent, no deletion
- â A VPN â Use Tor Browser for anonymity
đĄ Lessons Learned
1. RAMâOnly Storage Is Hard
// â WRONG: localStorage persists to disk
localStorage.setItem('key', encode(key));
// â
CORRECT: Inâmemory only
const keyStore = new Map();
2. Timing Attacks Are Real
// â WRONG: Instant response reveals wrong passphrase
if (passphrase !== correctPassphrase) {
return { error: 'Invalid passphrase' };
}
// â
CORRECT: Constantâtime comparison + random delay
const isValid = timingSafeEqual(hash(passphrase), hash(correctPassphrase));
await sleep(randomInt(500, 2000));
return isValid ? { data } : { error: 'Invalid passphrase' };
3. Browser History Is a Leak
// Vault URLs contain KeyA in fragment.
// Must clear from browser history.
if (window.history.replaceState) {
window.history.replaceState(null, '', '/unlock');
}
đŽ Future Roadmap
- Shamir Secret Sharing â Split vault access across multiple people
- Dead Manâs Switch â Autoârelease after inactivity
- Steganography â Hide vault in innocentâlooking images
- Hardware Key Support â YubiKey / Ledger integration
- Mobile Apps â iOS / Android with biometric unlock
đ Acknowledgments
- VeraCrypt â Inspiration for plausible deniability
- Cloudflare Pages â Free staticâsite hosting
- Pinata / Filebase â Free IPFS pinning services
- @noble/ciphers â Audited cryptography library
đ License
Business Source LicenseâŻ1.1 â Free for nonâproduction use. Production use requires a commercial license afterâŻ4âŻyears.
đ Links
- Live Demo:
- GitHub:
- Video Demo:
- Contact:
đŹ Discussion
What do you think? Would you use cryptographic deniability for your sensitive data? What other use cases can you imagine?
Drop a comment below or open an issue on GitHub!
Built with â¤ď¸ and đ by TeycirâŻBenâŻSoltane
Disclaimer: Sanctum is a tool for legitimate privacy needs. Users are responsible for complying with local laws. The developers do not condone illegal activities.