@f0rest8/metamorphic-crypto 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -76
- package/metamorphic_crypto.d.ts +2 -2
- package/metamorphic_crypto_bg.wasm +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,113 +1,166 @@
|
|
|
1
|
-
# metamorphic-crypto
|
|
1
|
+
# @f0rest8/metamorphic-crypto
|
|
2
2
|
|
|
3
|
-
Zero-knowledge end-to-end encryption
|
|
3
|
+
Zero-knowledge end-to-end encryption with post-quantum hybrid KEM (ML-KEM-768/1024 + X25519).
|
|
4
4
|
|
|
5
5
|
Built for [Metamorphic](https://metamorphic.app) and [Mosslet](https://mosslet.com) — privacy-first apps by [Moss Piglet Corporation](https://mosspiglet.dev) where all user data is encrypted client-side and the server only stores opaque ciphertext.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This package is a WebAssembly build of the [metamorphic-crypto](https://github.com/moss-piglet/metamorphic-crypto) Rust crate.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **Sealed box** (X25519) — anonymous public-key encryption (libsodium-compatible)
|
|
11
|
-
- **Hybrid PQ KEM** (ML-KEM-768 + X25519) — NIST Cat-3 post-quantum key encapsulation (default)
|
|
12
|
-
- **Hybrid PQ KEM** (ML-KEM-1024 + X25519) — NIST Cat-5 post-quantum key encapsulation (opt-in)
|
|
13
|
-
- **Argon2id KDF** — password-based key derivation (libsodium INTERACTIVE parameters)
|
|
14
|
-
- **WASM bindings** — browser-ready via `wasm-pack`
|
|
15
|
-
- **Recovery keys** — human-readable base32 encoding for key backup
|
|
9
|
+
## Install
|
|
16
10
|
|
|
17
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm install @f0rest8/metamorphic-crypto
|
|
13
|
+
```
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|-------|--------|---------------|------------|-------------|---------|
|
|
21
|
-
| Cat-3 | 768 | 3 | ~AES-192 | `0x02` | Yes |
|
|
22
|
-
| Cat-5 | 1024 | 5 | ~AES-256 | `0x03` | No |
|
|
15
|
+
## Quick start
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
```js
|
|
18
|
+
import init, {
|
|
19
|
+
generateKey,
|
|
20
|
+
encryptSecretboxString,
|
|
21
|
+
decryptSecretboxToString,
|
|
22
|
+
} from "@f0rest8/metamorphic-crypto";
|
|
23
|
+
|
|
24
|
+
// Initialize the WASM module (required once before calling any function)
|
|
25
|
+
await init();
|
|
26
|
+
|
|
27
|
+
// All functions are synchronous after init() and throw on error
|
|
28
|
+
const key = generateKey();
|
|
29
|
+
const ciphertext = encryptSecretboxString("sensitive data", key);
|
|
30
|
+
const plaintext = decryptSecretboxToString(ciphertext, key);
|
|
31
|
+
// plaintext === "sensitive data"
|
|
32
|
+
```
|
|
25
33
|
|
|
26
|
-
##
|
|
34
|
+
## API overview
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
- All secret key material zeroized after use
|
|
30
|
-
- Constant-time MAC comparison via RustCrypto
|
|
31
|
-
- OS CSPRNG via `getrandom` (no userspace PRNG)
|
|
32
|
-
- Hybrid construction: both ML-KEM AND X25519 must be broken to compromise a sealed key
|
|
36
|
+
All values are base64-encoded strings unless noted otherwise.
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
### Key derivation (Argon2id)
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
```js
|
|
41
|
+
import { deriveSessionKey, generateSalt, parseSaltFromKeyHash } from "@f0rest8/metamorphic-crypto";
|
|
37
42
|
|
|
43
|
+
const salt = generateSalt(); // random 16-byte salt (base64)
|
|
44
|
+
const sessionKey = deriveSessionKey(password, salt); // 32-byte derived key (base64)
|
|
45
|
+
const salt2 = parseSaltFromKeyHash("base64salt$argon2id..."); // extract salt from stored hash
|
|
38
46
|
```
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
|
|
48
|
+
### Symmetric encryption (XSalsa20-Poly1305)
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import {
|
|
52
|
+
generateKey,
|
|
53
|
+
encryptSecretboxString, decryptSecretboxToString,
|
|
54
|
+
encryptSecretbox, decryptSecretbox,
|
|
55
|
+
} from "@f0rest8/metamorphic-crypto";
|
|
56
|
+
|
|
57
|
+
const key = generateKey();
|
|
58
|
+
|
|
59
|
+
// String plaintext
|
|
60
|
+
const ct = encryptSecretboxString("hello", key);
|
|
61
|
+
const pt = decryptSecretboxToString(ct, key); // "hello"
|
|
62
|
+
|
|
63
|
+
// Binary plaintext (base64 in, base64 out)
|
|
64
|
+
const ctBin = encryptSecretbox(plaintextBase64, key);
|
|
65
|
+
const ptBin = decryptSecretbox(ctBin, key); // base64
|
|
41
66
|
```
|
|
42
67
|
|
|
43
|
-
###
|
|
68
|
+
### Public-key encryption (X25519 sealed box)
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { generateKeyPair, boxSeal, boxSealOpen } from "@f0rest8/metamorphic-crypto";
|
|
72
|
+
|
|
73
|
+
const kp = generateKeyPair(); // { publicKey, privateKey }
|
|
74
|
+
const sealed = boxSeal(plaintextBase64, kp.publicKey);
|
|
75
|
+
const opened = boxSealOpen(sealed, kp.publicKey, kp.privateKey); // base64
|
|
44
76
|
```
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
|
|
78
|
+
### Hybrid PQ seal/unseal (ML-KEM + X25519)
|
|
79
|
+
|
|
80
|
+
The recommended API for encrypting data to a user. Uses post-quantum hybrid encryption when a PQ public key is available, falls back to legacy X25519 sealed box otherwise. Decryption auto-detects the format.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import {
|
|
84
|
+
generateKeyPair, generateHybridKeyPair,
|
|
85
|
+
sealForUser, unsealFromUser,
|
|
86
|
+
} from "@f0rest8/metamorphic-crypto";
|
|
87
|
+
|
|
88
|
+
// Generate keys
|
|
89
|
+
const x25519 = generateKeyPair(); // { publicKey, privateKey }
|
|
90
|
+
const pq = generateHybridKeyPair(); // { publicKey, secretKey } (ML-KEM-768)
|
|
91
|
+
|
|
92
|
+
// Seal (encrypt) — pass both public keys
|
|
93
|
+
const sealed = sealForUser(plaintextBase64, x25519.publicKey, pq.publicKey);
|
|
94
|
+
|
|
95
|
+
// Unseal (decrypt) — pass both secret keys, auto-detects format
|
|
96
|
+
const opened = unsealFromUser(sealed, x25519.publicKey, x25519.privateKey, pq.secretKey);
|
|
47
97
|
```
|
|
48
98
|
|
|
49
99
|
### Cat-5 (ML-KEM-1024, opt-in)
|
|
50
|
-
```
|
|
51
|
-
Public key: ML-KEM-1024 ek (1568 B) || X25519 pk (32 B) = 1600 bytes
|
|
52
|
-
Ciphertext: 0x03 || ML-KEM-1024 ct (1568 B) || X25519 eph pk (32 B) || nonce (24 B) || secretbox ct
|
|
53
|
-
```
|
|
54
100
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| iOS | UniFFI (planned) | Native Swift apps |
|
|
62
|
-
| Android | UniFFI (planned) | Native Kotlin apps |
|
|
63
|
-
|
|
64
|
-
## Usage
|
|
65
|
-
|
|
66
|
-
```rust
|
|
67
|
-
use metamorphic_crypto::{generate_key, encrypt_secretbox_string, decrypt_secretbox_to_string};
|
|
68
|
-
use metamorphic_crypto::{generate_hybrid_keypair, hybrid_seal, hybrid_open};
|
|
69
|
-
use metamorphic_crypto::{generate_hybrid_keypair_1024, hybrid_seal_1024};
|
|
70
|
-
|
|
71
|
-
// Symmetric encryption
|
|
72
|
-
let key = generate_key();
|
|
73
|
-
let ciphertext = encrypt_secretbox_string("sensitive data", &key).unwrap();
|
|
74
|
-
let plaintext = decrypt_secretbox_to_string(&ciphertext, &key).unwrap();
|
|
75
|
-
assert_eq!(plaintext, "sensitive data");
|
|
76
|
-
|
|
77
|
-
// Hybrid PQ seal (Cat-3, default)
|
|
78
|
-
let kp = generate_hybrid_keypair();
|
|
79
|
-
let sealed = hybrid_seal(b"context_key_bytes", &kp.public_key).unwrap();
|
|
80
|
-
let opened = hybrid_open(&sealed, &kp.secret_key).unwrap();
|
|
81
|
-
|
|
82
|
-
// Hybrid PQ seal (Cat-5)
|
|
83
|
-
let kp5 = generate_hybrid_keypair_1024();
|
|
84
|
-
let sealed5 = hybrid_seal_1024(b"context_key_bytes", &kp5.public_key).unwrap();
|
|
85
|
-
let opened5 = hybrid_open(&sealed5, &kp5.secret_key).unwrap(); // auto-detects level
|
|
101
|
+
```js
|
|
102
|
+
import { generateHybridKeyPair1024, sealForUserWithLevel } from "@f0rest8/metamorphic-crypto";
|
|
103
|
+
|
|
104
|
+
const pq5 = generateHybridKeyPair1024(); // { publicKey, secretKey } (ML-KEM-1024)
|
|
105
|
+
const sealed = sealForUserWithLevel(plaintextBase64, x25519.publicKey, pq5.publicKey, "cat5");
|
|
106
|
+
// unsealFromUser auto-detects Cat-3 vs Cat-5, no level param needed
|
|
86
107
|
```
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
### Private key management
|
|
89
110
|
|
|
90
|
-
```
|
|
91
|
-
|
|
111
|
+
```js
|
|
112
|
+
import { encryptPrivateKey, decryptPrivateKey } from "@f0rest8/metamorphic-crypto";
|
|
113
|
+
|
|
114
|
+
const encrypted = encryptPrivateKey(privateKeyBase64, sessionKeyBase64);
|
|
115
|
+
const decrypted = decryptPrivateKey(encrypted, sessionKeyBase64);
|
|
92
116
|
```
|
|
93
117
|
|
|
118
|
+
### Recovery keys
|
|
119
|
+
|
|
94
120
|
```js
|
|
95
|
-
import
|
|
121
|
+
import {
|
|
122
|
+
generateRecoveryKey, recoveryKeyToSecret,
|
|
123
|
+
encryptPrivateKeyForRecovery, decryptPrivateKeyWithRecovery,
|
|
124
|
+
} from "@f0rest8/metamorphic-crypto";
|
|
125
|
+
|
|
126
|
+
const rk = generateRecoveryKey(); // { recoveryKey, recoverySecretBase64 }
|
|
127
|
+
// recoveryKey is a human-readable string like "ABCD-EFGH-..." for the user to write down
|
|
96
128
|
|
|
97
|
-
|
|
129
|
+
const backup = encryptPrivateKeyForRecovery(privateKeyBase64, rk.recoverySecretBase64);
|
|
130
|
+
const restored = decryptPrivateKeyWithRecovery(backup, rk.recoverySecretBase64);
|
|
98
131
|
|
|
99
|
-
|
|
100
|
-
const
|
|
132
|
+
// Or recover from the human-readable key:
|
|
133
|
+
const secret = recoveryKeyToSecret("ABCD-EFGH-...");
|
|
134
|
+
const restored2 = decryptPrivateKeyWithRecovery(backup, secret);
|
|
101
135
|
```
|
|
102
136
|
|
|
103
|
-
##
|
|
137
|
+
## Security levels
|
|
138
|
+
|
|
139
|
+
| Level | ML-KEM | NIST Category | Equivalent | Default |
|
|
140
|
+
|-------|--------|---------------|------------|---------|
|
|
141
|
+
| Cat-3 | 768 | 3 | ~AES-192 | Yes |
|
|
142
|
+
| Cat-5 | 1024 | 5 | ~AES-256 | No |
|
|
143
|
+
|
|
144
|
+
Decryption always auto-detects the level from the ciphertext version tag.
|
|
145
|
+
|
|
146
|
+
## Security properties
|
|
147
|
+
|
|
148
|
+
- `#![forbid(unsafe_code)]` — no unsafe anywhere in the Rust source
|
|
149
|
+
- All secret key material zeroized after use
|
|
150
|
+
- Constant-time MAC comparison via RustCrypto
|
|
151
|
+
- OS CSPRNG via `getrandom` (no userspace PRNG)
|
|
152
|
+
- Hybrid construction: both ML-KEM AND X25519 must be broken to compromise a sealed key
|
|
153
|
+
|
|
154
|
+
## Integrity verification
|
|
155
|
+
|
|
156
|
+
Every release includes SHA-512 checksums and [cosign](https://docs.sigstore.dev/cosign/overview/) signatures for supply chain verification:
|
|
104
157
|
|
|
105
158
|
```bash
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
159
|
+
cosign verify-blob \
|
|
160
|
+
--bundle metamorphic_crypto_bg.wasm.cosign.bundle \
|
|
161
|
+
metamorphic_crypto_bg.wasm
|
|
109
162
|
```
|
|
110
163
|
|
|
111
164
|
## License
|
|
112
165
|
|
|
113
|
-
Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) at your option.
|
|
166
|
+
Dual-licensed under [MIT](https://github.com/moss-piglet/metamorphic-crypto/blob/main/LICENSE-MIT) or [Apache-2.0](https://github.com/moss-piglet/metamorphic-crypto/blob/main/LICENSE-APACHE) at your option.
|
package/metamorphic_crypto.d.ts
CHANGED
|
@@ -144,10 +144,10 @@ export interface InitOutput {
|
|
|
144
144
|
readonly generateRecoveryKey: (a: number) => void;
|
|
145
145
|
readonly recoveryKeyToSecret: (a: number, b: number, c: number) => void;
|
|
146
146
|
readonly parseSaltFromKeyHash: (a: number, b: number, c: number) => void;
|
|
147
|
-
readonly
|
|
147
|
+
readonly decryptPrivateKeyWithRecovery: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
148
148
|
readonly decryptSecretboxToString: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
149
|
+
readonly encryptSecretboxString: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
149
150
|
readonly encryptPrivateKeyForRecovery: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
150
|
-
readonly decryptPrivateKeyWithRecovery: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
151
151
|
readonly __wbindgen_export: (a: number, b: number) => number;
|
|
152
152
|
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
153
153
|
readonly __wbindgen_export3: (a: number) => void;
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@f0rest8/metamorphic-crypto",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "Zero-knowledge end-to-end encryption with post-quantum hybrid KEM (ML-KEM-768/1024 + X25519)",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.5",
|
|
6
6
|
"license": "MIT OR Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|