@vollcrypt/messages-wasm 0.2.0

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.
Files changed (4) hide show
  1. package/README.md +57 -0
  2. package/fast.d.ts +102 -0
  3. package/fast.js +301 -0
  4. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @vollcrypt/wasm
2
+
3
+ **Cross-platform, quantum-resistant cryptography engine - WebAssembly Binding**
4
+
5
+ [![npm](https://img.shields.io/npm/v/@vollcrypt/wasm.svg)](https://www.npmjs.com/package/@vollcrypt/wasm)
6
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/BeratVural/vollcrypt/blob/main/LICENSE)
7
+
8
+ This package provides the **WebAssembly (WASM) bindings** for the Vollcrypt cryptography engine. It is compiled directly from Rust using `wasm-pack`, bringing robust, post-quantum cryptography straight to the user's browser or frontend application (React, Next.js, Vue, etc.) without relying on slow JavaScript cryptography implementations.
9
+
10
+ ## Features
11
+
12
+ - **Universal:** Runs in any modern browser without native binary dependencies.
13
+ - **Quantum-Resistant:** Implements the NIST FIPS 203 (ML-KEM-768) standard combined with X25519 for hybrid key exchange.
14
+ - **Client-Side E2EE:** Perfect for End-to-End Encryption where keys never leave the user's device.
15
+ - **Secure Defaults:** Provides AES-256-GCM, Ed25519, HKDF-SHA256, and post-compromise security ratchets out of the box.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @vollcrypt/wasm
21
+ ```
22
+
23
+ *Note: Since this is a WebAssembly module, you may need to configure your bundler (Webpack, Vite, Rollup) to handle `.wasm` files depending on your frontend setup.*
24
+
25
+ ## Quick Start
26
+
27
+ ```javascript
28
+ import * as vollcrypt from '@vollcrypt/wasm';
29
+
30
+ // Generate an Ed25519 Identity Keypair
31
+ const identity = vollcrypt.generate_ed25519_keypair();
32
+ console.log("Public Key:", Buffer.from(identity.public_key).toString('hex'));
33
+
34
+ // Sign and Verify
35
+ const message = new TextEncoder().encode("Hello from Vollcrypt WASM!");
36
+ const signature = vollcrypt.sign_message(identity.private_key, message);
37
+ const isValid = vollcrypt.verify_signature(identity.public_key, message, signature);
38
+
39
+ console.log("Signature Valid:", isValid); // true
40
+
41
+ // Hybrid Key Exchange (X25519)
42
+ const alice = vollcrypt.generate_x25519_keypair();
43
+ const bob = vollcrypt.generate_x25519_keypair();
44
+ const sharedSecret = vollcrypt.ecdh_shared_secret(alice.private_key, bob.public_key);
45
+
46
+ console.log("Shared Secret Derived successfully.");
47
+ ```
48
+
49
+ ## Documentation
50
+
51
+ For full API documentation, architecture details, and the high-performance Native Node.js equivalent, please refer to the [Vollcrypt Main Repository](https://github.com/BeratVural/vollcrypt).
52
+
53
+ ## License
54
+
55
+ This project is licensed under the **GNU General Public License v3.0**.
56
+
57
+ For commercial use without the GPL copyleft requirement, please contact Berat Vural at [berat.vural.tr@gmail.com](mailto:berat.vural.tr@gmail.com).
package/fast.d.ts ADDED
@@ -0,0 +1,102 @@
1
+ export { default } from "./pkg/wasm"
2
+ export * from "./pkg/wasm"
3
+
4
+ export declare function encryptAesGcmWeb(
5
+ key: CryptoKey | Uint8Array,
6
+ plaintext: Uint8Array,
7
+ aad?: Uint8Array | null
8
+ ): Promise<Uint8Array>
9
+
10
+ export declare function decryptAesGcmWeb(
11
+ key: CryptoKey | Uint8Array,
12
+ ciphertext: Uint8Array,
13
+ aad?: Uint8Array | null
14
+ ): Promise<Uint8Array>
15
+
16
+ export declare function encryptAesGcmAuto(
17
+ key: CryptoKey | Uint8Array,
18
+ plaintext: Uint8Array,
19
+ aad?: Uint8Array | null
20
+ ): Promise<Uint8Array>
21
+
22
+ export declare function decryptAesGcmAuto(
23
+ key: CryptoKey | Uint8Array,
24
+ ciphertext: Uint8Array,
25
+ aad?: Uint8Array | null
26
+ ): Promise<Uint8Array>
27
+
28
+ export declare function encryptAesGcmPaddedWeb(
29
+ key: CryptoKey | Uint8Array,
30
+ plaintext: Uint8Array,
31
+ aad?: Uint8Array | null
32
+ ): Promise<Uint8Array>
33
+
34
+ export declare function decryptAesGcmPaddedWeb(
35
+ key: CryptoKey | Uint8Array,
36
+ ciphertext: Uint8Array,
37
+ aad?: Uint8Array | null
38
+ ): Promise<Uint8Array>
39
+
40
+ export declare function encryptAesGcmPaddedAuto(
41
+ key: CryptoKey | Uint8Array,
42
+ plaintext: Uint8Array,
43
+ aad?: Uint8Array | null
44
+ ): Promise<Uint8Array>
45
+
46
+ export declare function decryptAesGcmPaddedAuto(
47
+ key: CryptoKey | Uint8Array,
48
+ ciphertext: Uint8Array,
49
+ aad?: Uint8Array | null
50
+ ): Promise<Uint8Array>
51
+
52
+ export declare function encryptAesGcmChunkedWeb(
53
+ key: CryptoKey | Uint8Array,
54
+ plaintext: Uint8Array,
55
+ aad: Uint8Array | null | undefined,
56
+ chunkSize: number
57
+ ): Promise<Uint8Array>
58
+
59
+ export declare function decryptAesGcmChunkedWeb(
60
+ key: CryptoKey | Uint8Array,
61
+ ciphertext: Uint8Array,
62
+ aad: Uint8Array | null | undefined
63
+ ): Promise<Uint8Array>
64
+
65
+ export declare function encryptAesGcmChunkedPaddedWeb(
66
+ key: CryptoKey | Uint8Array,
67
+ plaintext: Uint8Array,
68
+ aad: Uint8Array | null | undefined,
69
+ chunkSize: number
70
+ ): Promise<Uint8Array>
71
+
72
+ export declare function decryptAesGcmChunkedPaddedWeb(
73
+ key: CryptoKey | Uint8Array,
74
+ ciphertext: Uint8Array,
75
+ aad: Uint8Array | null | undefined
76
+ ): Promise<Uint8Array>
77
+
78
+ export declare function encryptAesGcmChunkedAuto(
79
+ key: CryptoKey | Uint8Array,
80
+ plaintext: Uint8Array,
81
+ aad: Uint8Array | null | undefined,
82
+ chunkSize: number
83
+ ): Promise<Uint8Array>
84
+
85
+ export declare function decryptAesGcmChunkedAuto(
86
+ key: CryptoKey | Uint8Array,
87
+ ciphertext: Uint8Array,
88
+ aad: Uint8Array | null | undefined
89
+ ): Promise<Uint8Array>
90
+
91
+ export declare function encryptAesGcmChunkedPaddedAuto(
92
+ key: CryptoKey | Uint8Array,
93
+ plaintext: Uint8Array,
94
+ aad: Uint8Array | null | undefined,
95
+ chunkSize: number
96
+ ): Promise<Uint8Array>
97
+
98
+ export declare function decryptAesGcmChunkedPaddedAuto(
99
+ key: CryptoKey | Uint8Array,
100
+ ciphertext: Uint8Array,
101
+ aad: Uint8Array | null | undefined
102
+ ): Promise<Uint8Array>
package/fast.js ADDED
@@ -0,0 +1,301 @@
1
+ import init, * as wasm from "./pkg/wasm.js"
2
+
3
+ function getSubtle() {
4
+ const cryptoObj = globalThis?.crypto
5
+ return cryptoObj && cryptoObj.subtle ? cryptoObj.subtle : null
6
+ }
7
+
8
+ function normalizeU8(data) {
9
+ if (data == null) return null
10
+ return data instanceof Uint8Array ? data : new Uint8Array(data)
11
+ }
12
+
13
+ async function importAesKey(key, usages) {
14
+ if (typeof CryptoKey !== "undefined" && key instanceof CryptoKey) {
15
+ return key
16
+ }
17
+ const subtle = getSubtle()
18
+ if (!subtle) {
19
+ throw new Error("WebCrypto not available")
20
+ }
21
+ const raw = normalizeU8(key)
22
+ if (!raw) {
23
+ throw new Error("Invalid key")
24
+ }
25
+ return await subtle.importKey("raw", raw, { name: "AES-GCM" }, false, usages)
26
+ }
27
+
28
+ export async function encryptAesGcmWeb(key, plaintext, aad) {
29
+ const subtle = getSubtle()
30
+ if (!subtle) {
31
+ throw new Error("WebCrypto not available")
32
+ }
33
+ const pt = normalizeU8(plaintext)
34
+ if (!pt) {
35
+ throw new Error("Invalid plaintext")
36
+ }
37
+ const aadU8 = normalizeU8(aad)
38
+ const cryptoKey = await importAesKey(key, ["encrypt"])
39
+ const iv = new Uint8Array(12)
40
+ globalThis.crypto.getRandomValues(iv)
41
+ const algo = { name: "AES-GCM", iv }
42
+ if (aadU8 && aadU8.length > 0) {
43
+ algo.additionalData = aadU8
44
+ }
45
+ const ct = await subtle.encrypt(algo, cryptoKey, pt)
46
+ const out = new Uint8Array(iv.length + ct.byteLength)
47
+ out.set(iv, 0)
48
+ out.set(new Uint8Array(ct), iv.length)
49
+ return out
50
+ }
51
+
52
+ export async function decryptAesGcmWeb(key, ciphertext, aad) {
53
+ const subtle = getSubtle()
54
+ if (!subtle) {
55
+ throw new Error("WebCrypto not available")
56
+ }
57
+ const ct = normalizeU8(ciphertext)
58
+ if (!ct || ct.length < 12) {
59
+ throw new Error("Ciphertext too short")
60
+ }
61
+ const aadU8 = normalizeU8(aad)
62
+ const cryptoKey = await importAesKey(key, ["decrypt"])
63
+ const iv = ct.slice(0, 12)
64
+ const data = ct.slice(12)
65
+ const algo = { name: "AES-GCM", iv }
66
+ if (aadU8 && aadU8.length > 0) {
67
+ algo.additionalData = aadU8
68
+ }
69
+ const pt = await subtle.decrypt(algo, cryptoKey, data)
70
+ return new Uint8Array(pt)
71
+ }
72
+
73
+ export async function encryptAesGcmAuto(key, plaintext, aad) {
74
+ const subtle = getSubtle()
75
+ if (subtle) {
76
+ return await encryptAesGcmWeb(key, plaintext, aad)
77
+ }
78
+ return wasm.encryptAesGcm(key, plaintext, aad ?? null)
79
+ }
80
+
81
+ export async function decryptAesGcmAuto(key, ciphertext, aad) {
82
+ const subtle = getSubtle()
83
+ if (subtle) {
84
+ return await decryptAesGcmWeb(key, ciphertext, aad)
85
+ }
86
+ return wasm.decryptAesGcm(key, ciphertext, aad ?? null)
87
+ }
88
+
89
+ function calculatePaddingLen(contentLen) {
90
+ const sizes = [64, 128, 256, 512, 1024, 2048]
91
+ const minPadding = 2
92
+ const target = sizes.find((s) => s >= contentLen + minPadding) ?? (() => {
93
+ const remainder = (contentLen + minPadding) % 1024
94
+ return remainder === 0 ? contentLen + minPadding : contentLen + minPadding + (1024 - remainder)
95
+ })()
96
+ return target - contentLen
97
+ }
98
+
99
+ function padWithLen(content) {
100
+ const pt = normalizeU8(content)
101
+ if (!pt) {
102
+ throw new Error("Invalid plaintext")
103
+ }
104
+ if (pt.length > 0xffffffff) {
105
+ throw new Error("Message too large to pad")
106
+ }
107
+ const lenPrefix = new Uint8Array(4)
108
+ new DataView(lenPrefix.buffer).setUint32(0, pt.length, false)
109
+ const baseLen = 4 + pt.length
110
+ const padLen = calculatePaddingLen(baseLen)
111
+ const padding = new Uint8Array(padLen)
112
+ if (padLen > 0) {
113
+ globalThis.crypto.getRandomValues(padding)
114
+ }
115
+ const out = new Uint8Array(baseLen + padLen)
116
+ out.set(lenPrefix, 0)
117
+ out.set(pt, 4)
118
+ out.set(padding, 4 + pt.length)
119
+ return out
120
+ }
121
+
122
+ function unpadWithLen(padded) {
123
+ const buf = normalizeU8(padded)
124
+ if (!buf || buf.length < 4) {
125
+ throw new Error("Padded message too short")
126
+ }
127
+ const len = new DataView(buf.buffer, buf.byteOffset, buf.byteLength).getUint32(0, false)
128
+ if (len > buf.length - 4) {
129
+ throw new Error("Invalid padded message length")
130
+ }
131
+ return buf.subarray(4, 4 + len)
132
+ }
133
+
134
+ export async function encryptAesGcmPaddedWeb(key, plaintext, aad) {
135
+ const padded = padWithLen(plaintext)
136
+ return await encryptAesGcmWeb(key, padded, aad)
137
+ }
138
+
139
+ export async function decryptAesGcmPaddedWeb(key, ciphertext, aad) {
140
+ const padded = await decryptAesGcmWeb(key, ciphertext, aad)
141
+ return unpadWithLen(padded)
142
+ }
143
+
144
+ export async function encryptAesGcmPaddedAuto(key, plaintext, aad) {
145
+ const subtle = getSubtle()
146
+ if (subtle) {
147
+ return await encryptAesGcmPaddedWeb(key, plaintext, aad)
148
+ }
149
+ return wasm.encryptAesGcmPadded(key, plaintext, aad ?? null)
150
+ }
151
+
152
+ export async function decryptAesGcmPaddedAuto(key, ciphertext, aad) {
153
+ const subtle = getSubtle()
154
+ if (subtle) {
155
+ return await decryptAesGcmPaddedWeb(key, ciphertext, aad)
156
+ }
157
+ return wasm.decryptAesGcmPadded(key, ciphertext, aad ?? null)
158
+ }
159
+
160
+ function buildChunkAad(baseAad, chunkIndex) {
161
+ const aadPrefix = baseAad ? normalizeU8(baseAad) : null
162
+ const indexBuf = new Uint8Array(4)
163
+ const view = new DataView(indexBuf.buffer)
164
+ view.setUint32(0, chunkIndex, false)
165
+ if (!aadPrefix || aadPrefix.length === 0) {
166
+ return indexBuf
167
+ }
168
+ const out = new Uint8Array(aadPrefix.length + 4)
169
+ out.set(aadPrefix, 0)
170
+ out.set(indexBuf, aadPrefix.length)
171
+ return out
172
+ }
173
+
174
+ export async function encryptAesGcmChunkedWeb(key, plaintext, aad, chunkSize) {
175
+ if (!chunkSize || chunkSize <= 0) {
176
+ throw new Error("Invalid chunk size")
177
+ }
178
+ const pt = normalizeU8(plaintext)
179
+ if (!pt) {
180
+ throw new Error("Invalid plaintext")
181
+ }
182
+ const totalLen = pt.length
183
+ const chunkCount = totalLen === 0 ? 1 : Math.ceil(totalLen / chunkSize)
184
+ if (chunkCount > 0xffffffff) {
185
+ throw new Error("Chunk count exceeds supported maximum")
186
+ }
187
+ const parts = []
188
+ const header = new Uint8Array(4)
189
+ new DataView(header.buffer).setUint32(0, chunkCount, false)
190
+ parts.push(header)
191
+ for (let i = 0; i < chunkCount; i += 1) {
192
+ const start = i * chunkSize
193
+ const end = totalLen === 0 ? 0 : Math.min(start + chunkSize, totalLen)
194
+ const chunk = pt.subarray(start, end)
195
+ const aadChunk = buildChunkAad(aad, i)
196
+ const enc = await encryptAesGcmWeb(key, chunk, aadChunk)
197
+ const h = new Uint8Array(8)
198
+ const v = new DataView(h.buffer)
199
+ v.setUint32(0, i, false)
200
+ v.setUint32(4, enc.length, false)
201
+ parts.push(h, enc)
202
+ if (totalLen === 0) break
203
+ }
204
+ const total = parts.reduce((sum, p) => sum + p.length, 0)
205
+ const out = new Uint8Array(total)
206
+ let offset = 0
207
+ for (const p of parts) {
208
+ out.set(p, offset)
209
+ offset += p.length
210
+ }
211
+ return out
212
+ }
213
+
214
+ export async function encryptAesGcmChunkedPaddedWeb(key, plaintext, aad, chunkSize) {
215
+ const padded = padWithLen(plaintext)
216
+ return await encryptAesGcmChunkedWeb(key, padded, aad, chunkSize)
217
+ }
218
+
219
+ export async function decryptAesGcmChunkedWeb(key, ciphertext, aad) {
220
+ const ct = normalizeU8(ciphertext)
221
+ if (!ct || ct.length < 4) {
222
+ throw new Error("Encrypted data too short")
223
+ }
224
+ const view = new DataView(ct.buffer, ct.byteOffset, ct.byteLength)
225
+ const chunkCount = view.getUint32(0, false)
226
+ if (chunkCount === 0) {
227
+ throw new Error("Invalid chunk count")
228
+ }
229
+ let offset = 4
230
+ const parts = []
231
+ for (let expected = 0; expected < chunkCount; expected += 1) {
232
+ if (offset + 8 > ct.length) {
233
+ throw new Error("Encrypted data truncated")
234
+ }
235
+ const chunkIndex = view.getUint32(offset, false)
236
+ const chunkLen = view.getUint32(offset + 4, false)
237
+ offset += 8
238
+ if (chunkIndex !== expected) {
239
+ throw new Error("Chunk index mismatch")
240
+ }
241
+ if (chunkLen === 0 || offset + chunkLen > ct.length) {
242
+ throw new Error("Invalid chunk length")
243
+ }
244
+ const chunk = ct.subarray(offset, offset + chunkLen)
245
+ const aadChunk = buildChunkAad(aad, chunkIndex)
246
+ const dec = await decryptAesGcmWeb(key, chunk, aadChunk)
247
+ parts.push(dec)
248
+ offset += chunkLen
249
+ }
250
+ if (offset !== ct.length) {
251
+ throw new Error("Trailing data after chunks")
252
+ }
253
+ const total = parts.reduce((sum, p) => sum + p.length, 0)
254
+ const out = new Uint8Array(total)
255
+ let outOffset = 0
256
+ for (const p of parts) {
257
+ out.set(p, outOffset)
258
+ outOffset += p.length
259
+ }
260
+ return out
261
+ }
262
+
263
+ export async function decryptAesGcmChunkedPaddedWeb(key, ciphertext, aad) {
264
+ const padded = await decryptAesGcmChunkedWeb(key, ciphertext, aad)
265
+ return unpadWithLen(padded)
266
+ }
267
+
268
+ export async function encryptAesGcmChunkedAuto(key, plaintext, aad, chunkSize) {
269
+ const subtle = getSubtle()
270
+ if (subtle) {
271
+ return await encryptAesGcmChunkedWeb(key, plaintext, aad, chunkSize)
272
+ }
273
+ return wasm.encryptAesGcmChunked(key, plaintext, aad ?? null, chunkSize)
274
+ }
275
+
276
+ export async function encryptAesGcmChunkedPaddedAuto(key, plaintext, aad, chunkSize) {
277
+ const subtle = getSubtle()
278
+ if (subtle) {
279
+ return await encryptAesGcmChunkedPaddedWeb(key, plaintext, aad, chunkSize)
280
+ }
281
+ return wasm.encryptAesGcmChunkedPadded(key, plaintext, aad ?? null, chunkSize)
282
+ }
283
+
284
+ export async function decryptAesGcmChunkedAuto(key, ciphertext, aad) {
285
+ const subtle = getSubtle()
286
+ if (subtle) {
287
+ return await decryptAesGcmChunkedWeb(key, ciphertext, aad)
288
+ }
289
+ return wasm.decryptAesGcmChunked(key, ciphertext, aad ?? null)
290
+ }
291
+
292
+ export async function decryptAesGcmChunkedPaddedAuto(key, ciphertext, aad) {
293
+ const subtle = getSubtle()
294
+ if (subtle) {
295
+ return await decryptAesGcmChunkedPaddedWeb(key, ciphertext, aad)
296
+ }
297
+ return wasm.decryptAesGcmChunkedPadded(key, ciphertext, aad ?? null)
298
+ }
299
+
300
+ export { init }
301
+ export * from "./pkg/wasm.js"
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@vollcrypt/messages-wasm",
3
+ "version": "0.2.0",
4
+ "description": "Cross-platform, quantum-resistant cryptography engine - WebAssembly binding",
5
+ "type": "module",
6
+ "main": "pkg/wasm.js",
7
+ "types": "pkg/wasm.d.ts",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/BeratVural/vollcrypt.git",
14
+ "directory": "wasm"
15
+ },
16
+ "homepage": "https://github.com/BeratVural/vollcrypt#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/BeratVural/vollcrypt/issues"
19
+ },
20
+ "files": [
21
+ "pkg/",
22
+ "fast.js",
23
+ "fast.d.ts"
24
+ ],
25
+ "keywords": [
26
+ "vollcrypt",
27
+ "cryptography",
28
+ "encryption",
29
+ "wasm",
30
+ "webassembly",
31
+ "aes-gcm",
32
+ "ed25519",
33
+ "x25519",
34
+ "ml-kem",
35
+ "post-quantum",
36
+ "pqc",
37
+ "e2ee"
38
+ ],
39
+ "author": "Berat Vural <berat.vural.tr@gmail.com>",
40
+ "license": "GPL-3.0-only"
41
+ }