131 lines
4.6 KiB
Markdown
131 lines
4.6 KiB
Markdown
# Crypto Comparison: @fileverse/crypto vs rSpace DocCrypto vs MIT Primitives
|
||
|
||
## Overview
|
||
|
||
Three options for the encryption layer:
|
||
|
||
1. **Keep DocCrypto** — rSpace's existing AES-256-GCM + HKDF implementation
|
||
2. **Adopt @fileverse/crypto** — Drop-in AGPL library with ECIES, NaCl, Argon2id
|
||
3. **Build from MIT primitives** — Same capabilities as Fileverse but without AGPL
|
||
|
||
## Primitive-Level Comparison
|
||
|
||
### Symmetric Encryption
|
||
|
||
| | DocCrypto | @fileverse/crypto | MIT Build |
|
||
|--|-----------|-------------------|-----------|
|
||
| Algorithm | AES-256-GCM | XSalsa20-Poly1305 (NaCl SecretBox) | Either |
|
||
| Implementation | Web Crypto API | TweetNaCl.js | Web Crypto or TweetNaCl |
|
||
| Key size | 256 bits | 256 bits | 256 bits |
|
||
| Nonce | 96 bits (random) | 192 bits (random) | Depends on choice |
|
||
| Auth tag | 128 bits | 128 bits (Poly1305) | Depends on choice |
|
||
| Bundle size | 0 (native) | ~7KB (tweetnacl) | 0–7KB |
|
||
| Performance | Fastest (hardware) | Fast (pure JS) | Fastest if Web Crypto |
|
||
|
||
**Analysis:** AES-256-GCM via Web Crypto is faster (hardware acceleration) and adds 0 bundle size. NaCl SecretBox is simpler to use correctly (larger nonce = less collision risk). For rStack, Web Crypto AES-256-GCM is the better choice — it's already battle-tested in DocCrypto.
|
||
|
||
### Asymmetric Encryption (Gap in DocCrypto)
|
||
|
||
| | DocCrypto | @fileverse/crypto | MIT Build |
|
||
|--|-----------|-------------------|-----------|
|
||
| ECIES | Not implemented | ✅ (secp256k1) | Noble curves |
|
||
| RSA | Not implemented | ✅ (envelope) | Web Crypto |
|
||
| Key exchange | Not implemented | ECDH shared secret | Noble/StableLib x25519 |
|
||
|
||
**Analysis:** This is the main gap. DocCrypto only does symmetric encryption — it can't share encryption keys between users without a trusted server. ECIES allows encrypting a document key so only a specific collaborator's private key can decrypt it.
|
||
|
||
### Key Derivation
|
||
|
||
| | DocCrypto | @fileverse/crypto | MIT Build |
|
||
|--|-----------|-------------------|-----------|
|
||
| HKDF | ✅ (Web Crypto) | ✅ (StableLib) | Web Crypto |
|
||
| Argon2id | Not implemented | ✅ (argon2-browser WASM) | argon2-browser |
|
||
| Key hierarchy | Master → Space → Doc | Flat | Custom |
|
||
|
||
**Analysis:** DocCrypto's key hierarchy is more sophisticated. Fileverse derives keys per-operation. For rStack, keep DocCrypto's hierarchy and add Argon2id for password-protected sharing.
|
||
|
||
## Recommendation
|
||
|
||
### Extend DocCrypto with MIT Primitives
|
||
|
||
```typescript
|
||
// Additions to rspace-online/shared/local-first/crypto.ts
|
||
|
||
import { x25519 } from '@noble/curves/ed25519'
|
||
import { hkdf } from '@noble/hashes/hkdf'
|
||
import { sha256 } from '@noble/hashes/sha256'
|
||
import argon2 from 'argon2-browser'
|
||
|
||
class DocCrypto {
|
||
// ... existing AES-256-GCM + HKDF methods ...
|
||
|
||
// NEW: Key exchange for multi-user collaboration
|
||
static async deriveSharedSecret(
|
||
myPrivateKey: Uint8Array,
|
||
theirPublicKey: Uint8Array
|
||
): Promise<CryptoKey> {
|
||
const shared = x25519.getSharedSecret(myPrivateKey, theirPublicKey)
|
||
return this.importKey(shared)
|
||
}
|
||
|
||
// NEW: Encrypt document key for a specific collaborator
|
||
static async encryptKeyForUser(
|
||
docKey: Uint8Array,
|
||
recipientPublicKey: Uint8Array,
|
||
senderPrivateKey: Uint8Array
|
||
): Promise<Uint8Array> {
|
||
const sharedSecret = x25519.getSharedSecret(senderPrivateKey, recipientPublicKey)
|
||
const wrappingKey = hkdf(sha256, sharedSecret, null, 'rstack-key-wrap', 32)
|
||
// Encrypt docKey with wrappingKey using existing AES-256-GCM
|
||
return this.encrypt(wrappingKey, docKey)
|
||
}
|
||
|
||
// NEW: Password-protected note sharing
|
||
static async deriveKeyFromPassword(
|
||
password: string,
|
||
salt: Uint8Array
|
||
): Promise<CryptoKey> {
|
||
const result = await argon2.hash({
|
||
pass: password,
|
||
salt: salt,
|
||
type: argon2.ArgonType.Argon2id,
|
||
hashLen: 32,
|
||
time: 3,
|
||
mem: 65536,
|
||
parallelism: 1,
|
||
})
|
||
return this.importKey(result.hash)
|
||
}
|
||
}
|
||
```
|
||
|
||
### Dependencies (All MIT)
|
||
|
||
```json
|
||
{
|
||
"@noble/curves": "^1.4.0",
|
||
"@noble/hashes": "^1.4.0",
|
||
"argon2-browser": "^1.18.0"
|
||
}
|
||
```
|
||
|
||
### Bundle Size Impact
|
||
|
||
| Package | Size (min+gzip) |
|
||
|---------|----------------|
|
||
| @noble/curves (x25519 only) | ~8KB |
|
||
| @noble/hashes (sha256+hkdf) | ~4KB |
|
||
| argon2-browser (WASM) | ~30KB |
|
||
| **Total** | **~42KB** |
|
||
|
||
vs `@fileverse/crypto` full package: ~45KB (similar, but with AGPL)
|
||
|
||
## Interoperability
|
||
|
||
If we build from the same MIT primitives Fileverse uses, we maintain the option to:
|
||
- Decrypt content encrypted by Fileverse apps
|
||
- Share encrypted documents with Fileverse users
|
||
- Participate in Fileverse collaboration rooms
|
||
|
||
The key is using compatible curve parameters (secp256k1 or x25519) and matching the encryption format (nonce || ciphertext || tag).
|