leviathan-crypto 1.0.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.
- package/CLAUDE.md +265 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/SECURITY.md +174 -0
- package/dist/chacha.wasm +0 -0
- package/dist/chacha20/index.d.ts +49 -0
- package/dist/chacha20/index.js +177 -0
- package/dist/chacha20/ops.d.ts +16 -0
- package/dist/chacha20/ops.js +146 -0
- package/dist/chacha20/pool.d.ts +52 -0
- package/dist/chacha20/pool.js +188 -0
- package/dist/chacha20/pool.worker.d.ts +1 -0
- package/dist/chacha20/pool.worker.js +37 -0
- package/dist/chacha20/types.d.ts +30 -0
- package/dist/chacha20/types.js +1 -0
- package/dist/docs/architecture.md +795 -0
- package/dist/docs/argon2id.md +290 -0
- package/dist/docs/chacha20.md +602 -0
- package/dist/docs/chacha20_pool.md +306 -0
- package/dist/docs/fortuna.md +322 -0
- package/dist/docs/init.md +308 -0
- package/dist/docs/loader.md +206 -0
- package/dist/docs/serpent.md +914 -0
- package/dist/docs/sha2.md +620 -0
- package/dist/docs/sha3.md +509 -0
- package/dist/docs/types.md +198 -0
- package/dist/docs/utils.md +273 -0
- package/dist/docs/wasm.md +193 -0
- package/dist/embedded/chacha.d.ts +1 -0
- package/dist/embedded/chacha.js +2 -0
- package/dist/embedded/serpent.d.ts +1 -0
- package/dist/embedded/serpent.js +2 -0
- package/dist/embedded/sha2.d.ts +1 -0
- package/dist/embedded/sha2.js +2 -0
- package/dist/embedded/sha3.d.ts +1 -0
- package/dist/embedded/sha3.js +2 -0
- package/dist/fortuna.d.ts +72 -0
- package/dist/fortuna.js +445 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +44 -0
- package/dist/init.d.ts +11 -0
- package/dist/init.js +49 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.js +30 -0
- package/dist/serpent/index.d.ts +65 -0
- package/dist/serpent/index.js +242 -0
- package/dist/serpent/seal.d.ts +8 -0
- package/dist/serpent/seal.js +70 -0
- package/dist/serpent/stream-encoder.d.ts +20 -0
- package/dist/serpent/stream-encoder.js +167 -0
- package/dist/serpent/stream-pool.d.ts +48 -0
- package/dist/serpent/stream-pool.js +285 -0
- package/dist/serpent/stream-sealer.d.ts +34 -0
- package/dist/serpent/stream-sealer.js +223 -0
- package/dist/serpent/stream.d.ts +28 -0
- package/dist/serpent/stream.js +205 -0
- package/dist/serpent/stream.worker.d.ts +32 -0
- package/dist/serpent/stream.worker.js +117 -0
- package/dist/serpent/types.d.ts +5 -0
- package/dist/serpent/types.js +1 -0
- package/dist/serpent.wasm +0 -0
- package/dist/sha2/hkdf.d.ts +16 -0
- package/dist/sha2/hkdf.js +108 -0
- package/dist/sha2/index.d.ts +40 -0
- package/dist/sha2/index.js +190 -0
- package/dist/sha2/types.d.ts +5 -0
- package/dist/sha2/types.js +1 -0
- package/dist/sha2.wasm +0 -0
- package/dist/sha3/index.d.ts +55 -0
- package/dist/sha3/index.js +246 -0
- package/dist/sha3/types.d.ts +5 -0
- package/dist/sha3/types.js +1 -0
- package/dist/sha3.wasm +0 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +26 -0
- package/dist/utils.d.ts +26 -0
- package/dist/utils.js +169 -0
- package/package.json +90 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# SHA3 TypeScript API Reference
|
|
2
|
+
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> SHA-3 hash functions and SHAKE XOFs (TypeScript API)
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
The SHA-3 family provides six hash functions standardized in **FIPS 202**: four
|
|
9
|
+
fixed-output hash functions (SHA3-224, SHA3-256, SHA3-384, SHA3-512) and two
|
|
10
|
+
extendable-output functions, or XOFs (SHAKE128, SHAKE256). All six are built on
|
|
11
|
+
the **Keccak sponge construction** -- a fundamentally different design from the
|
|
12
|
+
Merkle-Damgard structure used by SHA-2.
|
|
13
|
+
|
|
14
|
+
SHA-3 is **not** a replacement for SHA-2. Both are considered secure, and both are
|
|
15
|
+
standardized by NIST. SHA-3 exists to provide **defense-in-depth**: if a flaw is
|
|
16
|
+
ever discovered in SHA-2, SHA-3 is completely unaffected because it uses a different
|
|
17
|
+
mathematical foundation. Think of it as insurance -- you may never need it, but if
|
|
18
|
+
you do, you will be very glad it is there.
|
|
19
|
+
|
|
20
|
+
The SHAKE XOFs are particularly flexible. Unlike SHA3-256, which always produces
|
|
21
|
+
exactly 32 bytes, SHAKE128 and SHAKE256 can produce variable-length output -- you
|
|
22
|
+
tell them how many bytes you want. This is useful for key derivation, generating
|
|
23
|
+
nonces, or any situation where you need more (or fewer) bytes than a standard hash
|
|
24
|
+
provides.
|
|
25
|
+
|
|
26
|
+
One key advantage of SHA-3 over SHA-2: **SHA-3 is immune to length extension
|
|
27
|
+
attacks.** With SHA-2, if you know `SHA256(secret + message)` but not the secret,
|
|
28
|
+
you can compute `SHA256(secret + message + padding + extra)` without knowing the
|
|
29
|
+
secret. SHA-3's sponge construction makes this impossible.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Security Notes
|
|
34
|
+
|
|
35
|
+
> [!IMPORTANT]
|
|
36
|
+
> Read these before using the API. Misusing hash functions is one of the most
|
|
37
|
+
> common sources of security vulnerabilities.
|
|
38
|
+
|
|
39
|
+
- **Length extension immunity.** Unlike SHA-2, the SHA-3 sponge construction does
|
|
40
|
+
not leak enough internal state for length extension attacks. Computing
|
|
41
|
+
`SHA3(secret + message)` does not let an attacker forge `SHA3(secret + message + extra)`.
|
|
42
|
+
That said, **HMAC is still the correct way to build a MAC** -- do not use raw
|
|
43
|
+
`SHA3(key + message)` as a MAC construction, even though it is not vulnerable to
|
|
44
|
+
length extension. HMAC provides a formally proven security reduction.
|
|
45
|
+
|
|
46
|
+
- **SHAKE output is unbounded.** SHAKE128 and SHAKE256 are full XOFs — output
|
|
47
|
+
length is unbounded. Request any number of bytes via `hash()`, or drive the
|
|
48
|
+
sponge directly with `absorb()` / `squeeze()`. The only constraint is
|
|
49
|
+
`outputLength >= 1`.
|
|
50
|
+
|
|
51
|
+
- **Not for password hashing.** SHA-3 is a fast hash — that is the opposite of
|
|
52
|
+
what you want for password storage. Passwords must be hashed with a slow,
|
|
53
|
+
memory-hardened algorithm like **Argon2id**. See [argon2id.md](./argon2id.md) for
|
|
54
|
+
usage patterns including passphrase-based encryption with leviathan primitives.
|
|
55
|
+
|
|
56
|
+
- **Call `dispose()` when finished.** Every SHA-3 class wraps a WASM module that
|
|
57
|
+
stores Keccak state in linear memory. Calling `dispose()` zeroes all internal
|
|
58
|
+
state (the 200-byte lane matrix, input buffer, output buffer, and metadata).
|
|
59
|
+
If you skip `dispose()`, key material or intermediate hash state may persist
|
|
60
|
+
in memory.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Module Init
|
|
65
|
+
|
|
66
|
+
Each module subpath exports its own init function for consumers who want
|
|
67
|
+
tree-shakeable imports.
|
|
68
|
+
|
|
69
|
+
### `sha3Init(mode?, opts?)`
|
|
70
|
+
|
|
71
|
+
Initializes only the sha3 WASM binary. Equivalent to calling the
|
|
72
|
+
root `init(['sha3'], mode, opts)` but without pulling the other three
|
|
73
|
+
modules into the bundle.
|
|
74
|
+
|
|
75
|
+
**Signature:**
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
async function sha3Init(mode?: Mode, opts?: InitOpts): Promise<void>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Usage:**
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { sha3Init, SHA3_256 } from 'leviathan-crypto/sha3'
|
|
85
|
+
|
|
86
|
+
await sha3Init()
|
|
87
|
+
const sha3 = new SHA3_256()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## API Reference
|
|
93
|
+
|
|
94
|
+
All SHA-3 classes require initialization before use. Either the root `init()`:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { init } from 'leviathan-crypto'
|
|
98
|
+
|
|
99
|
+
await init('sha3')
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Both `init('sha3')` and `init(['sha3'])` are valid — the root `init()` accepts
|
|
103
|
+
a single `Module` string or an array.
|
|
104
|
+
|
|
105
|
+
Or the subpath `sha3Init()`:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { sha3Init } from 'leviathan-crypto/sha3'
|
|
109
|
+
|
|
110
|
+
await sha3Init()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
If you use SHA-3 classes without calling `init()` first, the constructor
|
|
114
|
+
will throw an error.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### SHA3_224
|
|
119
|
+
|
|
120
|
+
Fixed-output hash function. Produces a **28-byte** (224-bit) digest.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
class SHA3_224 {
|
|
124
|
+
constructor()
|
|
125
|
+
hash(msg: Uint8Array): Uint8Array // returns 28 bytes
|
|
126
|
+
dispose(): void
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### SHA3_256
|
|
133
|
+
|
|
134
|
+
Fixed-output hash function. Produces a **32-byte** (256-bit) digest. This is the
|
|
135
|
+
most commonly used SHA-3 variant -- 256-bit security is suitable for most
|
|
136
|
+
applications.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
class SHA3_256 {
|
|
140
|
+
constructor()
|
|
141
|
+
hash(msg: Uint8Array): Uint8Array // returns 32 bytes
|
|
142
|
+
dispose(): void
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### SHA3_384
|
|
149
|
+
|
|
150
|
+
Fixed-output hash function. Produces a **48-byte** (384-bit) digest.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
class SHA3_384 {
|
|
154
|
+
constructor()
|
|
155
|
+
hash(msg: Uint8Array): Uint8Array // returns 48 bytes
|
|
156
|
+
dispose(): void
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### SHA3_512
|
|
163
|
+
|
|
164
|
+
Fixed-output hash function. Produces a **64-byte** (512-bit) digest. Use this when
|
|
165
|
+
you need the highest security margin.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
class SHA3_512 {
|
|
169
|
+
constructor()
|
|
170
|
+
hash(msg: Uint8Array): Uint8Array // returns 64 bytes
|
|
171
|
+
dispose(): void
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### SHAKE128
|
|
178
|
+
|
|
179
|
+
Extendable-output function (XOF). Produces **variable-length** output — any
|
|
180
|
+
number of bytes you request. 128-bit security level.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
class SHAKE128 {
|
|
184
|
+
constructor()
|
|
185
|
+
hash(msg: Uint8Array, outputLength: number): Uint8Array
|
|
186
|
+
absorb(msg: Uint8Array): this
|
|
187
|
+
squeeze(n: number): Uint8Array
|
|
188
|
+
reset(): this
|
|
189
|
+
dispose(): void
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Method | Description |
|
|
194
|
+
|--------|-------------|
|
|
195
|
+
| `hash(msg, outputLength)` | One-shot: reset, absorb, squeeze. Safe on a dirty instance. |
|
|
196
|
+
| `absorb(msg)` | Feed data into the sponge. Chainable. Throws if called after `squeeze()`. |
|
|
197
|
+
| `squeeze(n)` | Pull `n` bytes of XOF output. Output is contiguous — `squeeze(a)` followed by `squeeze(b)` yields bytes `[0, a)` and `[a, a+b)` of the XOF stream. |
|
|
198
|
+
| `reset()` | Return to a fresh, zeroed state. Chainable. Safe at any point. |
|
|
199
|
+
| `dispose()` | Zero all WASM state and the TS-side block buffer. |
|
|
200
|
+
|
|
201
|
+
**`outputLength`** / **`n`** must be `>= 1`. Values below 1 throw a `RangeError`.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### SHAKE256
|
|
206
|
+
|
|
207
|
+
Extendable-output function (XOF). Produces **variable-length** output — any
|
|
208
|
+
number of bytes you request. 256-bit security level.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
class SHAKE256 {
|
|
212
|
+
constructor()
|
|
213
|
+
hash(msg: Uint8Array, outputLength: number): Uint8Array
|
|
214
|
+
absorb(msg: Uint8Array): this
|
|
215
|
+
squeeze(n: number): Uint8Array
|
|
216
|
+
reset(): this
|
|
217
|
+
dispose(): void
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
| Method | Description |
|
|
222
|
+
|--------|-------------|
|
|
223
|
+
| `hash(msg, outputLength)` | One-shot: reset, absorb, squeeze. Safe on a dirty instance. |
|
|
224
|
+
| `absorb(msg)` | Feed data into the sponge. Chainable. Throws if called after `squeeze()`. |
|
|
225
|
+
| `squeeze(n)` | Pull `n` bytes of XOF output. Output is contiguous — `squeeze(a)` followed by `squeeze(b)` yields bytes `[0, a)` and `[a, a+b)` of the XOF stream. |
|
|
226
|
+
| `reset()` | Return to a fresh, zeroed state. Chainable. Safe at any point. |
|
|
227
|
+
| `dispose()` | Zero all WASM state and the TS-side block buffer. |
|
|
228
|
+
|
|
229
|
+
**`outputLength`** / **`n`** must be `>= 1`. Values below 1 throw a `RangeError`.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Incremental XOF API (`absorb` / `squeeze` / `reset`)
|
|
234
|
+
|
|
235
|
+
For use cases where you need to pull output in multiple steps — key derivation,
|
|
236
|
+
mask generation, protocol-specific domain separation — the SHAKE classes expose
|
|
237
|
+
a streaming interface alongside the one-shot `hash()`.
|
|
238
|
+
|
|
239
|
+
### State machine
|
|
240
|
+
|
|
241
|
+
| State | Valid calls |
|
|
242
|
+
|-------------|---------------------------------|
|
|
243
|
+
| fresh | `absorb()`, `hash()`, `reset()` |
|
|
244
|
+
| absorbing | `absorb()`, `squeeze()`, `hash()`, `reset()` |
|
|
245
|
+
| squeezing | `squeeze()`, `hash()`, `reset()` |
|
|
246
|
+
|
|
247
|
+
Calling `absorb()` while squeezing throws:
|
|
248
|
+
`"SHAKE128: cannot absorb after squeeze — call reset() first"`
|
|
249
|
+
|
|
250
|
+
`hash()` always resets before running — safe to call on a dirty instance.
|
|
251
|
+
|
|
252
|
+
### Example
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { init, SHAKE256 } from 'leviathan-crypto'
|
|
256
|
+
|
|
257
|
+
await init('sha3')
|
|
258
|
+
|
|
259
|
+
const xof = new SHAKE256()
|
|
260
|
+
xof.absorb(ikm) // input key material
|
|
261
|
+
xof.absorb(salt) // additional context
|
|
262
|
+
|
|
263
|
+
const encKey = xof.squeeze(32) // 256-bit encryption key
|
|
264
|
+
const macKey = xof.squeeze(32) // 256-bit MAC key
|
|
265
|
+
const nonce = xof.squeeze(12) // 96-bit nonce
|
|
266
|
+
|
|
267
|
+
xof.dispose()
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Usage Examples
|
|
273
|
+
|
|
274
|
+
### Example 1: Hash a string with SHA3-256
|
|
275
|
+
|
|
276
|
+
The most common use case -- hash some data and get a hex digest.
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { init, SHA3_256, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
|
|
280
|
+
|
|
281
|
+
// Initialize the SHA-3 WASM module (once, at startup)
|
|
282
|
+
await init('sha3')
|
|
283
|
+
|
|
284
|
+
// Create a hasher
|
|
285
|
+
const sha3 = new SHA3_256()
|
|
286
|
+
|
|
287
|
+
// Hash a UTF-8 string
|
|
288
|
+
const message = utf8ToBytes('Hello, world!')
|
|
289
|
+
const digest = sha3.hash(message)
|
|
290
|
+
|
|
291
|
+
console.log(bytesToHex(digest))
|
|
292
|
+
// 32 bytes (64 hex characters) of SHA3-256 output
|
|
293
|
+
|
|
294
|
+
// Clean up -- zeroes all WASM state
|
|
295
|
+
sha3.dispose()
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### Example 2: Hash binary data with SHA3-512
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { init, SHA3_512, bytesToHex } from 'leviathan-crypto'
|
|
304
|
+
|
|
305
|
+
await init('sha3')
|
|
306
|
+
|
|
307
|
+
const sha3 = new SHA3_512()
|
|
308
|
+
|
|
309
|
+
// Hash raw bytes (e.g., a file, a key, a nonce)
|
|
310
|
+
const data = new Uint8Array([0x01, 0x02, 0x03, 0x04])
|
|
311
|
+
const digest = sha3.hash(data)
|
|
312
|
+
|
|
313
|
+
console.log(bytesToHex(digest))
|
|
314
|
+
// 64 bytes (128 hex characters) of SHA3-512 output
|
|
315
|
+
|
|
316
|
+
sha3.dispose()
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### Example 3: Hash multiple messages
|
|
322
|
+
|
|
323
|
+
Each call to `hash()` is independent -- the internal state is reset automatically.
|
|
324
|
+
You can reuse the same class instance for multiple hashes.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { init, SHA3_256, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
|
|
328
|
+
|
|
329
|
+
await init('sha3')
|
|
330
|
+
|
|
331
|
+
const sha3 = new SHA3_256()
|
|
332
|
+
|
|
333
|
+
const hash1 = sha3.hash(utf8ToBytes('first message'))
|
|
334
|
+
const hash2 = sha3.hash(utf8ToBytes('second message'))
|
|
335
|
+
const hash3 = sha3.hash(utf8ToBytes('first message'))
|
|
336
|
+
|
|
337
|
+
// hash1 and hash3 are identical -- same input, same output
|
|
338
|
+
console.log(bytesToHex(hash1) === bytesToHex(hash3)) // true
|
|
339
|
+
|
|
340
|
+
// hash2 is different -- different input
|
|
341
|
+
console.log(bytesToHex(hash1) === bytesToHex(hash2)) // false
|
|
342
|
+
|
|
343
|
+
sha3.dispose()
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### Example 4: SHAKE128 variable-length output
|
|
349
|
+
|
|
350
|
+
SHAKE lets you choose exactly how many bytes of output you need. This is useful
|
|
351
|
+
for key derivation or generating fixed-size tokens.
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { init, SHAKE128, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
|
|
355
|
+
|
|
356
|
+
await init('sha3')
|
|
357
|
+
|
|
358
|
+
const shake = new SHAKE128()
|
|
359
|
+
|
|
360
|
+
const seed = utf8ToBytes('my-application-seed')
|
|
361
|
+
|
|
362
|
+
// Derive a 16-byte key (128 bits)
|
|
363
|
+
const key128 = shake.hash(seed, 16)
|
|
364
|
+
console.log('16-byte key:', bytesToHex(key128))
|
|
365
|
+
|
|
366
|
+
// Derive a 32-byte key (256 bits) from the same seed
|
|
367
|
+
const key256 = shake.hash(seed, 32)
|
|
368
|
+
console.log('32-byte key:', bytesToHex(key256))
|
|
369
|
+
|
|
370
|
+
// The 16-byte output is NOT a prefix of the 32-byte output --
|
|
371
|
+
// each call resets state, re-absorbs, and squeezes independently.
|
|
372
|
+
// However, for SHAKE, the first 16 bytes of the 32-byte output
|
|
373
|
+
// ARE identical to the 16-byte output (this is how XOFs work).
|
|
374
|
+
|
|
375
|
+
shake.dispose()
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### Example 5: SHAKE256 for key derivation
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import { init, SHAKE256, bytesToHex } from 'leviathan-crypto'
|
|
384
|
+
|
|
385
|
+
await init('sha3')
|
|
386
|
+
|
|
387
|
+
const shake = new SHAKE256()
|
|
388
|
+
|
|
389
|
+
// Derive a 48-byte key from raw entropy
|
|
390
|
+
const entropy = crypto.getRandomValues(new Uint8Array(32))
|
|
391
|
+
const derivedKey = shake.hash(entropy, 48)
|
|
392
|
+
|
|
393
|
+
console.log('Derived key:', bytesToHex(derivedKey))
|
|
394
|
+
// 48 bytes (96 hex characters) of SHAKE256 output
|
|
395
|
+
|
|
396
|
+
shake.dispose()
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
### Example 6: SHA-256 vs SHA3-256 -- different algorithms, different output
|
|
402
|
+
|
|
403
|
+
SHA-256 (from the SHA-2 family) and SHA3-256 are completely different algorithms.
|
|
404
|
+
They produce different output for the same input. Neither is "better" -- both
|
|
405
|
+
are secure. SHA3-256 adds defense-in-depth.
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { init, SHA256, SHA3_256, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
|
|
409
|
+
|
|
410
|
+
// Initialize both modules
|
|
411
|
+
await init(['sha2', 'sha3'])
|
|
412
|
+
|
|
413
|
+
const sha2 = new SHA256()
|
|
414
|
+
const sha3 = new SHA3_256()
|
|
415
|
+
|
|
416
|
+
const message = utf8ToBytes('abc')
|
|
417
|
+
|
|
418
|
+
const sha2Digest = sha2.hash(message)
|
|
419
|
+
const sha3Digest = sha3.hash(message)
|
|
420
|
+
|
|
421
|
+
console.log('SHA-256: ', bytesToHex(sha2Digest))
|
|
422
|
+
console.log('SHA3-256: ', bytesToHex(sha3Digest))
|
|
423
|
+
// These are completely different values -- different algorithms
|
|
424
|
+
|
|
425
|
+
sha2.dispose()
|
|
426
|
+
sha3.dispose()
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
### Example 7: Hashing empty input
|
|
432
|
+
|
|
433
|
+
All hash functions accept empty input. This is well-defined and produces a
|
|
434
|
+
deterministic output.
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
import { init, SHA3_256, bytesToHex } from 'leviathan-crypto'
|
|
438
|
+
|
|
439
|
+
await init('sha3')
|
|
440
|
+
|
|
441
|
+
const sha3 = new SHA3_256()
|
|
442
|
+
const digest = sha3.hash(new Uint8Array(0))
|
|
443
|
+
|
|
444
|
+
console.log(bytesToHex(digest))
|
|
445
|
+
// The SHA3-256 hash of empty input -- a fixed, known value
|
|
446
|
+
|
|
447
|
+
sha3.dispose()
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Error Conditions
|
|
453
|
+
|
|
454
|
+
### `init('sha3')` not called
|
|
455
|
+
|
|
456
|
+
If you construct a SHA-3 class before initializing the module, the constructor
|
|
457
|
+
throws immediately:
|
|
458
|
+
|
|
459
|
+
```
|
|
460
|
+
Error: leviathan-crypto: call init(['sha3']) before using this class
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Fix:** Call `await init('sha3')` once at application startup, before creating
|
|
464
|
+
any SHA-3 class instances.
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
### SHAKE output length out of range
|
|
469
|
+
|
|
470
|
+
SHAKE128 and SHAKE256 require `outputLength >= 1`. Passing 0 or a negative number
|
|
471
|
+
throws a `RangeError`:
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
RangeError: outputLength must be >= 1 (got 0)
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**Fix:** Request at least 1 byte.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
### SHAKE absorb after squeeze
|
|
482
|
+
|
|
483
|
+
Calling `absorb()` after `squeeze()` has been called throws an `Error`. The sponge
|
|
484
|
+
has been padded and finalized — further absorption is not meaningful.
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
Error: SHAKE128: cannot absorb after squeeze — call reset() first
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Fix:** Call `reset()` to return the instance to a fresh state before absorbing
|
|
491
|
+
new data.
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
### Empty input
|
|
496
|
+
|
|
497
|
+
Passing an empty `Uint8Array` (length 0) is **not** an error. All SHA-3 and SHAKE
|
|
498
|
+
functions produce valid, deterministic output for empty input. The sponge simply
|
|
499
|
+
absorbs zero bytes and then squeezes.
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Cross-References
|
|
504
|
+
|
|
505
|
+
- [README.md](./README.md): Project overview and quick-start guide
|
|
506
|
+
- [asm_sha3.md](./asm_sha3.md): WASM implementation details (buffer layout, Keccak internals, variant parameters)
|
|
507
|
+
- [sha2.md](./sha2.md): Alternative: SHA-2 family (SHA-256, SHA-384, SHA-512) and HMAC
|
|
508
|
+
- [utils.md](./utils.md): Encoding utilities: `bytesToHex`, `hexToBytes`, `utf8ToBytes`
|
|
509
|
+
- [architecture.md](./architecture.md): Library architecture and `init()` API
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Public TypeScript interfaces for cryptographic primitives
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This module defines the abstract interfaces that all leviathan-crypto cryptographic classes implement. These are **type-only exports** -- they contain no runtime code and generate no JavaScript output.
|
|
6
|
+
|
|
7
|
+
Use these interfaces when you need to write generic code that works with any hash function, any cipher, or any AEAD scheme without depending on a specific implementation. They are available immediately on import with no `init()` call required.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Security Notes
|
|
12
|
+
|
|
13
|
+
This module contains type definitions only. There are no security-sensitive operations.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## API Reference
|
|
18
|
+
|
|
19
|
+
### Hash
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
interface Hash {
|
|
23
|
+
hash(msg: Uint8Array): Uint8Array;
|
|
24
|
+
dispose(): void;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Interface for unkeyed hash functions (e.g., SHA-256, SHA-512, SHA-3).
|
|
29
|
+
|
|
30
|
+
| Method | Description |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `hash(msg)` | Hashes the entire message and returns the digest as a new `Uint8Array`. |
|
|
33
|
+
| `dispose()` | Releases WASM resources and wipes internal buffers. Call when done. |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### KeyedHash
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
interface KeyedHash {
|
|
41
|
+
hash(key: Uint8Array, msg: Uint8Array): Uint8Array;
|
|
42
|
+
dispose(): void;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Interface for keyed hash functions / MACs (e.g., HMAC-SHA256, HMAC-SHA512).
|
|
47
|
+
|
|
48
|
+
Note: `KeyedHash` does **not** extend `Hash`. Its `hash` method takes a `key` parameter in addition to the message.
|
|
49
|
+
|
|
50
|
+
| Method | Description |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `hash(key, msg)` | Computes the keyed hash / MAC over `msg` using `key`. Returns the tag as a new `Uint8Array`. |
|
|
53
|
+
| `dispose()` | Releases WASM resources and wipes internal buffers. Call when done. |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### Blockcipher
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
interface Blockcipher {
|
|
61
|
+
encrypt(block: Uint8Array): Uint8Array;
|
|
62
|
+
decrypt(block: Uint8Array): Uint8Array;
|
|
63
|
+
dispose(): void;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Interface for raw block ciphers (e.g., Serpent in ECB mode). Operates on single blocks.
|
|
68
|
+
|
|
69
|
+
| Method | Description |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `encrypt(block)` | Encrypts a single block and returns the ciphertext. |
|
|
72
|
+
| `decrypt(block)` | Decrypts a single block and returns the plaintext. |
|
|
73
|
+
| `dispose()` | Releases WASM resources and wipes internal buffers (including expanded key schedule). |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### Streamcipher
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
interface Streamcipher {
|
|
81
|
+
encrypt(msg: Uint8Array): Uint8Array;
|
|
82
|
+
decrypt(msg: Uint8Array): Uint8Array;
|
|
83
|
+
dispose(): void;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Interface for stream ciphers and block cipher streaming modes (e.g., Serpent-CTR, ChaCha20). Handles arbitrary-length messages.
|
|
88
|
+
|
|
89
|
+
| Method | Description |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `encrypt(msg)` | Encrypts an arbitrary-length message. Returns the ciphertext (same length as input). |
|
|
92
|
+
| `decrypt(msg)` | Decrypts an arbitrary-length ciphertext. Returns the plaintext (same length as input). |
|
|
93
|
+
| `dispose()` | Releases WASM resources and wipes internal buffers. |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### AEAD
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
interface AEAD {
|
|
101
|
+
encrypt(msg: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
102
|
+
decrypt(ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
103
|
+
dispose(): void;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Interface for authenticated encryption with associated data (e.g., XChaCha20-Poly1305). Provides both confidentiality and integrity.
|
|
108
|
+
|
|
109
|
+
| Method | Description |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `encrypt(msg, aad?)` | Encrypts `msg` and authenticates both `msg` and optional `aad`. Returns ciphertext with appended authentication tag. |
|
|
112
|
+
| `decrypt(ciphertext, aad?)` | Decrypts and verifies the authentication tag. Returns plaintext on success. Throws `Error` on authentication failure — never returns null. |
|
|
113
|
+
| `dispose()` | Releases WASM resources and wipes internal buffers. |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Usage Examples
|
|
118
|
+
|
|
119
|
+
### Type-constraining a function parameter
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import type { Hash } from 'leviathan-crypto'
|
|
123
|
+
|
|
124
|
+
function digestAndLog(hasher: Hash, data: Uint8Array): Uint8Array {
|
|
125
|
+
const digest = hasher.hash(data)
|
|
126
|
+
console.log('digest length:', digest.length)
|
|
127
|
+
return digest
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This function accepts any `Hash` implementation -- `SHA256`, `SHA512`, `SHA3_256`, etc. -- without importing any of them directly.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### Accepting any AEAD scheme
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import type { AEAD } from 'leviathan-crypto'
|
|
139
|
+
|
|
140
|
+
function sealMessage(aead: AEAD, plaintext: Uint8Array, metadata: Uint8Array): Uint8Array {
|
|
141
|
+
return aead.encrypt(plaintext, metadata)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function openMessage(aead: AEAD, ciphertext: Uint8Array, metadata: Uint8Array): Uint8Array {
|
|
145
|
+
// decrypt() throws on auth failure — no null check needed
|
|
146
|
+
return aead.decrypt(ciphertext, metadata)
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### Generic keyed-hash wrapper
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import type { KeyedHash } from 'leviathan-crypto'
|
|
156
|
+
|
|
157
|
+
function authenticate(mac: KeyedHash, key: Uint8Array, ...parts: Uint8Array[]): Uint8Array {
|
|
158
|
+
// Concatenate all message parts, then compute the tag
|
|
159
|
+
const total = parts.reduce((sum, p) => sum + p.length, 0)
|
|
160
|
+
const msg = new Uint8Array(total)
|
|
161
|
+
let offset = 0
|
|
162
|
+
for (const part of parts) {
|
|
163
|
+
msg.set(part, offset)
|
|
164
|
+
offset += part.length
|
|
165
|
+
}
|
|
166
|
+
return mac.hash(key, msg)
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### Storing a cipher with its interface type
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import type { Streamcipher, Blockcipher } from 'leviathan-crypto'
|
|
176
|
+
|
|
177
|
+
interface EncryptionContext {
|
|
178
|
+
cipher: Streamcipher | Blockcipher
|
|
179
|
+
mode: 'stream' | 'block'
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function cleanup(ctx: EncryptionContext): void {
|
|
183
|
+
ctx.cipher.dispose()
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Cross-References
|
|
190
|
+
|
|
191
|
+
- [README.md](./README.md) — library documentation index and exports table
|
|
192
|
+
- [architecture.md](./architecture.md) — module structure and correctness contracts
|
|
193
|
+
- [utils.md](./utils.md) — encoding utilities and `constantTimeEqual` for verifying MACs from `KeyedHash`
|
|
194
|
+
- [serpent.md](./serpent.md) — Serpent classes implement `Blockcipher`, `Streamcipher`, and `AEAD`
|
|
195
|
+
- [chacha20.md](./chacha20.md) — ChaCha20/Poly1305 classes implement `Streamcipher` and `AEAD`
|
|
196
|
+
- [sha2.md](./sha2.md) — SHA-2 classes implement `Hash`; HMAC classes implement `KeyedHash`
|
|
197
|
+
- [sha3.md](./sha3.md) — SHA-3 classes implement `Hash`; SHAKE classes extend with XOF API
|
|
198
|
+
- [test-suite.md](./test-suite.md) — test suite structure and vector corpus
|