leviathan-crypto 2.1.0 → 3.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 +86 -443
- package/README.md +198 -65
- package/dist/aes/aes-cbc.d.ts +40 -0
- package/dist/aes/aes-cbc.js +158 -0
- package/dist/aes/aes-ctr.d.ts +50 -0
- package/dist/aes/aes-ctr.js +141 -0
- package/dist/aes/aes-gcm-siv.d.ts +67 -0
- package/dist/aes/aes-gcm-siv.js +217 -0
- package/dist/aes/aes-gcm.d.ts +61 -0
- package/dist/aes/aes-gcm.js +226 -0
- package/dist/aes/cipher-suite.d.ts +21 -0
- package/dist/aes/cipher-suite.js +179 -0
- package/dist/aes/embedded.d.ts +1 -0
- package/dist/aes/embedded.js +26 -0
- package/dist/aes/generator.d.ts +14 -0
- package/dist/aes/generator.js +103 -0
- package/dist/aes/index.d.ts +58 -0
- package/dist/aes/index.js +125 -0
- package/dist/aes/ops.d.ts +60 -0
- package/dist/aes/ops.js +164 -0
- package/dist/aes/pool-worker.d.ts +1 -0
- package/dist/aes/pool-worker.js +92 -0
- package/dist/aes/types.d.ts +1 -0
- package/dist/aes/types.js +23 -0
- package/dist/aes.wasm +0 -0
- package/dist/blake3/embedded.d.ts +1 -0
- package/dist/blake3/embedded.js +26 -0
- package/dist/blake3/index.d.ts +143 -0
- package/dist/blake3/index.js +620 -0
- package/dist/blake3/types.d.ts +102 -0
- package/dist/blake3/types.js +31 -0
- package/dist/blake3/validate.d.ts +29 -0
- package/dist/blake3/validate.js +80 -0
- package/dist/blake3.wasm +0 -0
- package/dist/chacha20/cipher-suite.js +47 -25
- package/dist/chacha20/generator.d.ts +2 -2
- package/dist/chacha20/generator.js +4 -4
- package/dist/chacha20/index.d.ts +16 -15
- package/dist/chacha20/index.js +52 -46
- package/dist/chacha20/ops.d.ts +7 -7
- package/dist/chacha20/ops.js +34 -34
- package/dist/chacha20/pool-worker.js +5 -3
- package/dist/cte-wasm.d.ts +1 -0
- package/dist/cte-wasm.js +3 -0
- package/dist/curve25519.wasm +0 -0
- package/dist/ecdsa/der.d.ts +23 -0
- package/dist/ecdsa/der.js +192 -0
- package/dist/ecdsa/ecprivatekey-der.d.ts +32 -0
- package/dist/ecdsa/ecprivatekey-der.js +230 -0
- package/dist/ecdsa/embedded.d.ts +1 -0
- package/dist/ecdsa/embedded.js +25 -0
- package/dist/ecdsa/index.d.ts +124 -0
- package/dist/ecdsa/index.js +366 -0
- package/dist/ecdsa/types.d.ts +31 -0
- package/dist/ecdsa/types.js +28 -0
- package/dist/ecdsa/validate.d.ts +18 -0
- package/dist/ecdsa/validate.js +92 -0
- package/dist/ed25519/embedded.d.ts +1 -0
- package/dist/ed25519/embedded.js +31 -0
- package/dist/ed25519/index.d.ts +70 -0
- package/dist/ed25519/index.js +308 -0
- package/dist/ed25519/types.d.ts +27 -0
- package/dist/ed25519/types.js +27 -0
- package/dist/ed25519/validate.d.ts +7 -0
- package/dist/ed25519/validate.js +77 -0
- package/dist/embedded/aes-pool-worker.d.ts +1 -0
- package/dist/embedded/aes-pool-worker.js +5 -0
- package/dist/embedded/aes.d.ts +1 -0
- package/dist/embedded/aes.js +3 -0
- package/dist/embedded/blake3.d.ts +1 -0
- package/dist/embedded/blake3.js +3 -0
- package/dist/embedded/chacha20-pool-worker.d.ts +1 -1
- package/dist/embedded/chacha20-pool-worker.js +2 -2
- package/dist/embedded/chacha20.d.ts +1 -1
- package/dist/embedded/chacha20.js +2 -2
- package/dist/embedded/curve25519.d.ts +1 -0
- package/dist/embedded/curve25519.js +3 -0
- package/dist/embedded/mldsa.d.ts +1 -0
- package/dist/embedded/mldsa.js +3 -0
- package/dist/embedded/mlkem.d.ts +1 -0
- package/dist/embedded/mlkem.js +3 -0
- package/dist/embedded/p256.d.ts +1 -0
- package/dist/embedded/p256.js +3 -0
- package/dist/embedded/serpent-pool-worker.d.ts +1 -1
- package/dist/embedded/serpent-pool-worker.js +2 -2
- package/dist/embedded/serpent.d.ts +1 -1
- package/dist/embedded/serpent.js +2 -2
- package/dist/embedded/sha2.d.ts +1 -1
- package/dist/embedded/sha2.js +2 -2
- package/dist/embedded/sha3.d.ts +1 -1
- package/dist/embedded/sha3.js +2 -2
- package/dist/embedded/slhdsa.d.ts +1 -0
- package/dist/embedded/slhdsa.js +3 -0
- package/dist/errors.d.ts +92 -1
- package/dist/errors.js +111 -1
- package/dist/fortuna.d.ts +5 -5
- package/dist/fortuna.js +37 -64
- package/dist/index.d.ts +38 -9
- package/dist/index.js +63 -19
- package/dist/init.d.ts +1 -1
- package/dist/init.js +11 -25
- package/dist/keccak/embedded.js +1 -1
- package/dist/keccak/index.d.ts +2 -0
- package/dist/keccak/index.js +4 -2
- package/dist/loader.d.ts +1 -24
- package/dist/loader.js +13 -16
- package/dist/merkle/blake3-tree.d.ts +35 -0
- package/dist/merkle/blake3-tree.js +187 -0
- package/dist/merkle/checkpoint.d.ts +58 -0
- package/dist/merkle/checkpoint.js +217 -0
- package/dist/merkle/index.d.ts +19 -0
- package/dist/merkle/index.js +37 -0
- package/dist/merkle/merkle-log.d.ts +130 -0
- package/dist/merkle/merkle-log.js +207 -0
- package/dist/merkle/merkle-verifier.d.ts +126 -0
- package/dist/merkle/merkle-verifier.js +296 -0
- package/dist/merkle/proof.d.ts +70 -0
- package/dist/merkle/proof.js +300 -0
- package/dist/merkle/sha256-tree.d.ts +33 -0
- package/dist/merkle/sha256-tree.js +145 -0
- package/dist/merkle/signed-log.d.ts +156 -0
- package/dist/merkle/signed-log.js +356 -0
- package/dist/merkle/signed-note.d.ts +309 -0
- package/dist/merkle/signed-note.js +648 -0
- package/dist/merkle/sth.d.ts +31 -0
- package/dist/merkle/sth.js +31 -0
- package/dist/merkle/storage.d.ts +40 -0
- package/dist/merkle/storage.js +71 -0
- package/dist/merkle/tree.d.ts +68 -0
- package/dist/merkle/tree.js +94 -0
- package/dist/mldsa/embedded.d.ts +1 -0
- package/dist/{kyber → mldsa}/embedded.js +5 -5
- package/dist/mldsa/expand.d.ts +53 -0
- package/dist/mldsa/expand.js +188 -0
- package/dist/mldsa/format.d.ts +16 -0
- package/dist/mldsa/format.js +68 -0
- package/dist/mldsa/hashvariant.d.ts +32 -0
- package/dist/mldsa/hashvariant.js +248 -0
- package/dist/mldsa/index.d.ts +142 -0
- package/dist/mldsa/index.js +463 -0
- package/dist/mldsa/keygen.d.ts +16 -0
- package/dist/mldsa/keygen.js +232 -0
- package/dist/mldsa/params.d.ts +21 -0
- package/dist/mldsa/params.js +55 -0
- package/dist/mldsa/sha3-helpers.d.ts +30 -0
- package/dist/mldsa/sha3-helpers.js +124 -0
- package/dist/mldsa/sign.d.ts +36 -0
- package/dist/mldsa/sign.js +380 -0
- package/dist/mldsa/types.d.ts +91 -0
- package/dist/mldsa/types.js +25 -0
- package/dist/mldsa/validate.d.ts +55 -0
- package/dist/mldsa/validate.js +125 -0
- package/dist/mldsa/verify.d.ts +29 -0
- package/dist/mldsa/verify.js +269 -0
- package/dist/mldsa.wasm +0 -0
- package/dist/mlkem/embedded.d.ts +1 -0
- package/dist/mlkem/embedded.js +27 -0
- package/dist/mlkem/indcpa.d.ts +49 -0
- package/dist/{kyber → mlkem}/indcpa.js +44 -44
- package/dist/mlkem/index.d.ts +37 -0
- package/dist/{kyber → mlkem}/index.js +24 -34
- package/dist/mlkem/kem.d.ts +21 -0
- package/dist/{kyber → mlkem}/kem.js +44 -64
- package/dist/{kyber → mlkem}/params.d.ts +4 -4
- package/dist/{kyber → mlkem}/params.js +2 -2
- package/dist/mlkem/suite.d.ts +12 -0
- package/dist/{kyber → mlkem}/suite.js +17 -12
- package/dist/{kyber → mlkem}/types.d.ts +3 -3
- package/dist/{kyber → mlkem}/types.js +1 -1
- package/dist/{kyber → mlkem}/validate.d.ts +7 -7
- package/dist/{kyber → mlkem}/validate.js +7 -7
- package/dist/{kyber.wasm → mlkem.wasm} +0 -0
- package/dist/p256.wasm +0 -0
- package/dist/ratchet/index.d.ts +2 -0
- package/dist/ratchet/index.js +1 -0
- package/dist/ratchet/kdf-chain.js +3 -3
- package/dist/ratchet/ratchet-keypair.js +2 -2
- package/dist/ratchet/root-kdf.js +7 -7
- package/dist/ratchet/skipped-key-store.js +4 -4
- package/dist/ratchet/types.d.ts +1 -1
- package/dist/serpent/cipher-suite.js +20 -17
- package/dist/serpent/generator.d.ts +1 -1
- package/dist/serpent/generator.js +2 -2
- package/dist/serpent/index.d.ts +8 -7
- package/dist/serpent/index.js +18 -27
- package/dist/serpent/pool-worker.js +7 -5
- package/dist/serpent/serpent-cbc.d.ts +4 -4
- package/dist/serpent/serpent-cbc.js +11 -8
- package/dist/serpent/shared-ops.d.ts +3 -23
- package/dist/serpent/shared-ops.js +50 -85
- package/dist/serpent.wasm +0 -0
- package/dist/sha2/hkdf.js +5 -5
- package/dist/sha2/index.d.ts +21 -1
- package/dist/sha2/index.js +65 -10
- package/dist/sha2/types.d.ts +41 -2
- package/dist/sha2.wasm +0 -0
- package/dist/sha3/index.d.ts +72 -3
- package/dist/sha3/index.js +240 -14
- package/dist/sha3/kmac.d.ts +121 -0
- package/dist/sha3/kmac.js +800 -0
- package/dist/sha3.wasm +0 -0
- package/dist/shared/pkcs7.d.ts +22 -0
- package/dist/shared/pkcs7.js +84 -0
- package/dist/sign/ctx.d.ts +41 -0
- package/dist/sign/ctx.js +102 -0
- package/dist/sign/envelope.d.ts +45 -0
- package/dist/sign/envelope.js +152 -0
- package/dist/sign/hasher.d.ts +9 -0
- package/dist/sign/hasher.js +132 -0
- package/dist/sign/index.d.ts +11 -0
- package/dist/sign/index.js +34 -0
- package/dist/sign/sign-stream.d.ts +25 -0
- package/dist/sign/sign-stream.js +112 -0
- package/dist/sign/suites/ecdsa-p256.d.ts +2 -0
- package/dist/sign/suites/ecdsa-p256.js +120 -0
- package/dist/sign/suites/ed25519.d.ts +3 -0
- package/dist/sign/suites/ed25519.js +165 -0
- package/dist/sign/suites/hybrid-classical.d.ts +23 -0
- package/dist/sign/suites/hybrid-classical.js +526 -0
- package/dist/sign/suites/hybrid-pq.d.ts +4 -0
- package/dist/sign/suites/hybrid-pq.js +234 -0
- package/dist/sign/suites/mldsa.d.ts +7 -0
- package/dist/sign/suites/mldsa.js +161 -0
- package/dist/sign/suites/slhdsa.d.ts +7 -0
- package/dist/sign/suites/slhdsa.js +176 -0
- package/dist/sign/types.d.ts +106 -0
- package/dist/sign/types.js +28 -0
- package/dist/sign/verify-stream.d.ts +30 -0
- package/dist/sign/verify-stream.js +227 -0
- package/dist/slhdsa/embedded.d.ts +1 -0
- package/dist/slhdsa/embedded.js +26 -0
- package/dist/slhdsa/index.d.ts +149 -0
- package/dist/slhdsa/index.js +493 -0
- package/dist/slhdsa/params.d.ts +26 -0
- package/dist/slhdsa/params.js +70 -0
- package/dist/slhdsa/prehash.d.ts +68 -0
- package/dist/slhdsa/prehash.js +307 -0
- package/dist/slhdsa/sign.d.ts +39 -0
- package/dist/slhdsa/sign.js +116 -0
- package/dist/slhdsa/types.d.ts +129 -0
- package/dist/slhdsa/types.js +27 -0
- package/dist/slhdsa/validate.d.ts +60 -0
- package/dist/slhdsa/validate.js +127 -0
- package/dist/slhdsa/verify.d.ts +32 -0
- package/dist/slhdsa/verify.js +107 -0
- package/dist/slhdsa.wasm +0 -0
- package/dist/stream/header.js +3 -3
- package/dist/stream/index.d.ts +1 -0
- package/dist/stream/index.js +1 -0
- package/dist/stream/open-stream.js +31 -10
- package/dist/stream/seal-stream-pool.d.ts +1 -0
- package/dist/stream/seal-stream-pool.js +63 -26
- package/dist/stream/seal-stream.d.ts +1 -1
- package/dist/stream/seal-stream.js +20 -9
- package/dist/stream/seal.js +6 -6
- package/dist/stream/types.d.ts +3 -1
- package/dist/stream/types.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +46 -54
- package/dist/wasm-source.d.ts +7 -7
- package/dist/wasm-source.js +1 -1
- package/dist/x25519/embedded.d.ts +1 -0
- package/dist/x25519/embedded.js +31 -0
- package/dist/x25519/index.d.ts +43 -0
- package/dist/x25519/index.js +159 -0
- package/dist/x25519/types.d.ts +25 -0
- package/dist/x25519/types.js +27 -0
- package/dist/x25519/validate.d.ts +2 -0
- package/dist/x25519/validate.js +39 -0
- package/package.json +70 -26
- package/SECURITY.md +0 -163
- package/dist/ct-wasm.d.ts +0 -1
- package/dist/ct-wasm.js +0 -3
- package/dist/docs/aead.md +0 -363
- package/dist/docs/architecture.md +0 -1011
- package/dist/docs/argon2id.md +0 -305
- package/dist/docs/chacha20.md +0 -781
- package/dist/docs/exports.md +0 -277
- package/dist/docs/fortuna.md +0 -530
- package/dist/docs/init.md +0 -301
- package/dist/docs/loader.md +0 -256
- package/dist/docs/serpent.md +0 -617
- package/dist/docs/sha2.md +0 -671
- package/dist/docs/sha3.md +0 -612
- package/dist/docs/types.md +0 -416
- package/dist/docs/utils.md +0 -457
- package/dist/embedded/kyber.d.ts +0 -1
- package/dist/embedded/kyber.js +0 -3
- package/dist/kyber/embedded.d.ts +0 -1
- package/dist/kyber/indcpa.d.ts +0 -49
- package/dist/kyber/index.d.ts +0 -38
- package/dist/kyber/kem.d.ts +0 -21
- package/dist/kyber/suite.d.ts +0 -12
- /package/dist/{ct.wasm → cte.wasm} +0 -0
package/dist/docs/serpent.md
DELETED
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
<img src="https://github.com/xero/leviathan-crypto/raw/main/docs/logo.svg" alt="logo" width="120" align="left" margin="10">
|
|
2
|
-
|
|
3
|
-
### Serpent-256 TypeScript API
|
|
4
|
-
|
|
5
|
-
See [Serpent implementation audit](./serpent_audit.md) for algorithm correctness verifications.
|
|
6
|
-
|
|
7
|
-
> ### Table of Contents
|
|
8
|
-
> - [Overview](#overview)
|
|
9
|
-
> - [Security Notes](#security-notes)
|
|
10
|
-
> - [Module Init](#module-init)
|
|
11
|
-
> - [API Reference](#api-reference)
|
|
12
|
-
> - [SerpentGenerator](#serpentgenerator)
|
|
13
|
-
> - [Usage Examples](#usage-examples)
|
|
14
|
-
> - [Error Conditions](#error-conditions)
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Overview
|
|
19
|
-
|
|
20
|
-
`SerpentCipher` is the primary API for authenticated Serpent-256 encryption. Pass it
|
|
21
|
-
to `Seal` for one-shot AEAD, or to `SealStream`/`OpenStream` for streaming. There is
|
|
22
|
-
no manual IV generation, no separate MAC step, and no room for misuse. Internally it
|
|
23
|
-
uses Encrypt-then-MAC (Serpent-CBC + HMAC-SHA-256) with HKDF key derivation.
|
|
24
|
-
|
|
25
|
-
For advanced use cases, three lower-level classes are available: `Serpent` (raw
|
|
26
|
-
16-byte block operations), `SerpentCtr` (counter mode streaming), and `SerpentCbc`
|
|
27
|
-
(cipher block chaining with PKCS7 padding). These are unauthenticated and require
|
|
28
|
-
explicit opt-in.
|
|
29
|
-
|
|
30
|
-
Serpent was an AES finalist. It uses 32 rounds versus AES's 10 to 14, yielding a
|
|
31
|
-
larger security margin at comparable speed in WASM.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## Security Notes
|
|
36
|
-
|
|
37
|
-
> [!IMPORTANT]
|
|
38
|
-
> Read this section carefully before using any Serpent class. These are not
|
|
39
|
-
> theoretical concerns. Ignoring them will render encryption useless.
|
|
40
|
-
|
|
41
|
-
### SerpentCbc and SerpentCtr are unauthenticated
|
|
42
|
-
|
|
43
|
-
This is the most dangerous mistake you can make with this module. An attacker who
|
|
44
|
-
can modify ciphertext encrypted with `SerpentCbc` or `SerpentCtr` will produce
|
|
45
|
-
corrupted plaintext on decryption. Decryption succeeds without any indication of
|
|
46
|
-
tampering. There is no integrity check. Your caller receives garbage and has no way
|
|
47
|
-
to distinguish it from the original message.
|
|
48
|
-
|
|
49
|
-
[`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher)
|
|
50
|
-
eliminates this problem. It computes an HMAC tag over the ciphertext and
|
|
51
|
-
verifies it before decryption. If anything has been modified, `Seal.decrypt()`
|
|
52
|
-
throws instead of returning corrupted data.
|
|
53
|
-
|
|
54
|
-
### Never reuse a nonce or IV with the same key
|
|
55
|
-
|
|
56
|
-
In CTR mode, reusing a nonce with the same key is catastrophic. It produces the
|
|
57
|
-
same keystream, which means an attacker can XOR two ciphertexts together and
|
|
58
|
-
recover both plaintexts. Always generate a fresh random nonce for each message.
|
|
59
|
-
In CBC mode, the IV must be random and unpredictable for each encryption. A predictable IV enables chosen-plaintext attacks.
|
|
60
|
-
|
|
61
|
-
Use `randomBytes(16)` to generate nonces and IVs. [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) handles IV generation internally.
|
|
62
|
-
|
|
63
|
-
### Always use 256-bit keys
|
|
64
|
-
|
|
65
|
-
Unless you have a specific reason to use a shorter key, pass a 32-byte key to
|
|
66
|
-
every Serpent operation. Shorter keys provide less security margin and there is no
|
|
67
|
-
meaningful performance benefit to using them. [`SerpentCipher`](./ciphersuite.md#serpentcipher) requires a 32-byte key; HKDF derives enc/mac/iv keys internally.
|
|
68
|
-
|
|
69
|
-
### Call dispose() when done
|
|
70
|
-
|
|
71
|
-
Every Serpent class holds key material in WebAssembly memory. When you are finished
|
|
72
|
-
with an instance, call `dispose()` to zero out all key material, intermediate
|
|
73
|
-
state, and buffers. Failing to call `dispose()` means sensitive data may persist
|
|
74
|
-
in memory longer than necessary.
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## Module Init
|
|
79
|
-
|
|
80
|
-
Each module subpath exports its own init function for consumers who want
|
|
81
|
-
tree-shakeable imports.
|
|
82
|
-
|
|
83
|
-
### `serpentInit(source)`
|
|
84
|
-
|
|
85
|
-
Initializes only the serpent WASM binary. Equivalent to calling the
|
|
86
|
-
root `init({ serpent: serpentWasm })` but without pulling the other three
|
|
87
|
-
modules into the bundle.
|
|
88
|
-
|
|
89
|
-
**Signature:**
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
async function serpentInit(source: WasmSource): Promise<void>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
**Usage:**
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
import { serpentInit, Serpent } from 'leviathan-crypto/serpent'
|
|
99
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
100
|
-
|
|
101
|
-
await serpentInit(serpentWasm)
|
|
102
|
-
const cipher = new Serpent()
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## API Reference
|
|
108
|
-
|
|
109
|
-
All classes require their WASM modules to be initialized before construction.
|
|
110
|
-
`SerpentCipher` (and therefore `Seal`, `SealStream`, `OpenStream`) requires both
|
|
111
|
-
`serpent` and `sha2`. `Serpent`, `SerpentCtr`, and `SerpentCbc` require `serpent` only.
|
|
112
|
-
|
|
113
|
-
### SerpentCipher
|
|
114
|
-
|
|
115
|
-
`CipherSuite` implementation for Serpent-256 CBC+HMAC-SHA-256. Pass to `Seal`,
|
|
116
|
-
`SealStream`, or `OpenStream`. Never instantiated directly.
|
|
117
|
-
|
|
118
|
-
Requires `init({ serpent: serpentWasm, sha2: sha2Wasm })`.
|
|
119
|
-
|
|
120
|
-
| Property | Value |
|
|
121
|
-
|----------|-------|
|
|
122
|
-
| `formatEnum` | `0x02` |
|
|
123
|
-
| `keySize` | `32` |
|
|
124
|
-
| `tagSize` | `32` (HMAC-SHA-256) |
|
|
125
|
-
| `padded` | `true` (PKCS7) |
|
|
126
|
-
| `wasmModules` | `['serpent', 'sha2']` |
|
|
127
|
-
|
|
128
|
-
#### `SerpentCipher.keygen(): Uint8Array`
|
|
129
|
-
|
|
130
|
-
Returns `randomBytes(32)`. Convenience method. Not on the [`CipherSuite`](./ciphersuite.md) interface.
|
|
131
|
-
|
|
132
|
-
#### Usage with `Seal`
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
import { init, Seal, SerpentCipher } from 'leviathan-crypto'
|
|
136
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
137
|
-
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
138
|
-
|
|
139
|
-
await init({ serpent: serpentWasm, sha2: sha2Wasm })
|
|
140
|
-
|
|
141
|
-
const key = SerpentCipher.keygen()
|
|
142
|
-
const blob = Seal.encrypt(SerpentCipher, key, plaintext)
|
|
143
|
-
const pt = Seal.decrypt(SerpentCipher, key, blob) // throws on tamper
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
#### Usage with `SealStream` / `OpenStream`
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
import { SealStream, OpenStream } from 'leviathan-crypto/stream'
|
|
150
|
-
import { SerpentCipher } from 'leviathan-crypto/serpent'
|
|
151
|
-
|
|
152
|
-
const sealer = new SealStream(SerpentCipher, key)
|
|
153
|
-
const preamble = sealer.preamble // 20 bytes, send before first chunk
|
|
154
|
-
const ct0 = sealer.push(chunk0)
|
|
155
|
-
const ctLast = sealer.finalize(lastChunk)
|
|
156
|
-
|
|
157
|
-
const opener = new OpenStream(SerpentCipher, key, preamble)
|
|
158
|
-
const pt0 = opener.pull(ct0)
|
|
159
|
-
const ptLast = opener.finalize(ctLast)
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
See [aead.md](./aead.md) for the full `Seal`, `SealStream`, and `OpenStream` API.
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
### Serpent
|
|
167
|
-
|
|
168
|
-
Raw Serpent block encryption and decryption. Operates on exactly 16-byte blocks.
|
|
169
|
-
This class is a low-level building block
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
class Serpent {
|
|
173
|
-
constructor()
|
|
174
|
-
loadKey(key: Uint8Array): void
|
|
175
|
-
encryptBlock(plaintext: Uint8Array): Uint8Array
|
|
176
|
-
decryptBlock(ciphertext: Uint8Array): Uint8Array
|
|
177
|
-
dispose(): void
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
#### `constructor()`
|
|
182
|
-
|
|
183
|
-
Creates a new Serpent instance. Throws if `init({ serpent: serpentWasm })` has not been called.
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
#### `loadKey(key: Uint8Array): void`
|
|
188
|
-
|
|
189
|
-
Loads and expands a key for subsequent block operations. Must be called before
|
|
190
|
-
`encryptBlock()` or `decryptBlock()`.
|
|
191
|
-
|
|
192
|
-
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
#### `encryptBlock(plaintext: Uint8Array): Uint8Array`
|
|
197
|
-
|
|
198
|
-
Encrypts a single 16-byte block and returns the 16-byte ciphertext.
|
|
199
|
-
|
|
200
|
-
- **plaintext**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
#### `decryptBlock(ciphertext: Uint8Array): Uint8Array`
|
|
205
|
-
|
|
206
|
-
Decrypts a single 16-byte block and returns the 16-byte plaintext.
|
|
207
|
-
|
|
208
|
-
- **ciphertext**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
#### `dispose(): void`
|
|
213
|
-
|
|
214
|
-
Wipes all key material and intermediate state from WASM memory. Always call this
|
|
215
|
-
when you are done with the instance.
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
### SerpentCtr
|
|
220
|
-
|
|
221
|
-
Serpent in Counter (CTR) mode. Encrypts and decrypts data of any length as a
|
|
222
|
-
stream of chunks.
|
|
223
|
-
|
|
224
|
-
> [!WARNING]
|
|
225
|
-
> CTR mode is unauthenticated. An attacker can modify ciphertext
|
|
226
|
-
> without detection. Use [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) for authenticated encryption, or pair
|
|
227
|
-
> with HMAC-SHA256 (Encrypt-then-MAC).
|
|
228
|
-
|
|
229
|
-
> [!CAUTION]
|
|
230
|
-
> `SerpentCtr` is stateful and holds exclusive access to the `serpent` WASM
|
|
231
|
-
> module for its entire lifetime. Constructing a second `SerpentCtr`/
|
|
232
|
-
> `SerpentCbc`, `SerpentCipher` usage (`Seal.encrypt(SerpentCipher, ...)`,
|
|
233
|
-
> `SealStream` with `SerpentCipher`), or any atomic serpent class
|
|
234
|
-
> (`Serpent` block) while this instance is live throws. Call `dispose()`
|
|
235
|
-
> when done. Pool workers are unaffected.
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
class SerpentCtr {
|
|
239
|
-
constructor(opts: { dangerUnauthenticated: true })
|
|
240
|
-
beginEncrypt(key: Uint8Array, nonce: Uint8Array): void
|
|
241
|
-
encryptChunk(chunk: Uint8Array): Uint8Array
|
|
242
|
-
beginDecrypt(key: Uint8Array, nonce: Uint8Array): void
|
|
243
|
-
decryptChunk(chunk: Uint8Array): Uint8Array
|
|
244
|
-
dispose(): void
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
#### `constructor(opts: { dangerUnauthenticated: true })`
|
|
249
|
-
|
|
250
|
-
Creates a new SerpentCtr instance. Throws if `init({ serpent: serpentWasm })` has not been
|
|
251
|
-
called. Throws if `{ dangerUnauthenticated: true }` is not passed:
|
|
252
|
-
|
|
253
|
-
```
|
|
254
|
-
leviathan-crypto: SerpentCtr is unauthenticated — use Seal with SerpentCipher instead.
|
|
255
|
-
To use SerpentCtr directly, pass { dangerUnauthenticated: true }.
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
---
|
|
259
|
-
|
|
260
|
-
#### `beginEncrypt(key: Uint8Array, nonce: Uint8Array): void`
|
|
261
|
-
|
|
262
|
-
Initializes the CTR state for encryption. Loads the key, sets the nonce, and
|
|
263
|
-
resets the internal counter to zero.
|
|
264
|
-
|
|
265
|
-
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
266
|
-
- **nonce**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
|
-
#### `encryptChunk(chunk: Uint8Array): Uint8Array`
|
|
271
|
-
|
|
272
|
-
Encrypts a chunk of plaintext and returns the same-length ciphertext. Call this
|
|
273
|
-
one or more times after `beginEncrypt()`. The internal counter advances
|
|
274
|
-
automatically.
|
|
275
|
-
|
|
276
|
-
- **chunk**: any length up to the module's internal chunk buffer size. Throws `RangeError` if the chunk exceeds the maximum size.
|
|
277
|
-
|
|
278
|
-
> [!NOTE]
|
|
279
|
-
> Always uses the 4-wide SIMD path (`encryptChunk_simd`). SIMD is required by the serpent module; `init()` throws on runtimes without WebAssembly SIMD support.
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
|
-
#### `beginDecrypt(key: Uint8Array, nonce: Uint8Array): void`
|
|
284
|
-
|
|
285
|
-
Initializes the CTR state for decryption. Functionally identical to `beginEncrypt()`. CTR mode uses the same operation in both directions.
|
|
286
|
-
|
|
287
|
-
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
288
|
-
- **nonce**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
#### `decryptChunk(chunk: Uint8Array): Uint8Array`
|
|
293
|
-
|
|
294
|
-
Decrypts a chunk of ciphertext and returns the same-length plaintext.
|
|
295
|
-
Functionally identical to `encryptChunk()`.
|
|
296
|
-
|
|
297
|
-
- **chunk**: any length up to the module's internal chunk buffer size. Throws `RangeError` if the chunk exceeds the maximum size.
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
#### `dispose(): void`
|
|
302
|
-
|
|
303
|
-
Wipes all key material and intermediate state from WASM memory.
|
|
304
|
-
|
|
305
|
-
After `dispose()`, all instance methods (`beginEncrypt`, `encryptChunk`,
|
|
306
|
-
`beginDecrypt`, `decryptChunk`) throw `Error: SerpentCtr: instance has been
|
|
307
|
-
disposed`. Disposal is permanent; construct a new instance if you need to
|
|
308
|
-
continue.
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
### SerpentCbc
|
|
313
|
-
|
|
314
|
-
Serpent in Cipher Block Chaining (CBC) mode with automatic PKCS7 padding.
|
|
315
|
-
Encrypts and decrypts entire messages in a single call.
|
|
316
|
-
|
|
317
|
-
> [!WARNING]
|
|
318
|
-
> CBC mode is unauthenticated. Always authenticate the output with
|
|
319
|
-
> HMAC-SHA256 (Encrypt-then-MAC) or use [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) instead.
|
|
320
|
-
|
|
321
|
-
> [!CAUTION]
|
|
322
|
-
> `SerpentCbc` is stateful and holds exclusive access to the `serpent` WASM
|
|
323
|
-
> module for its entire lifetime. Constructing a second `SerpentCbc`/
|
|
324
|
-
> `SerpentCtr`, `SerpentCipher` usage (which internally constructs a
|
|
325
|
-
> `SerpentCbc`), or any atomic serpent class (`Serpent` block) while this
|
|
326
|
-
> instance is live throws. Call `dispose()` when done.
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
class SerpentCbc {
|
|
330
|
-
constructor(opts: { dangerUnauthenticated: true })
|
|
331
|
-
encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array
|
|
332
|
-
decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array
|
|
333
|
-
dispose(): void
|
|
334
|
-
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
#### `constructor(opts: { dangerUnauthenticated: true })`
|
|
338
|
-
|
|
339
|
-
Creates a new SerpentCbc instance. Throws if `init({ serpent: serpentWasm })` has not been
|
|
340
|
-
called. Throws if `{ dangerUnauthenticated: true }` is not passed:
|
|
341
|
-
|
|
342
|
-
```
|
|
343
|
-
leviathan-crypto: SerpentCbc is unauthenticated — use Seal with SerpentCipher instead.
|
|
344
|
-
To use SerpentCbc directly, pass { dangerUnauthenticated: true }.
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
---
|
|
348
|
-
|
|
349
|
-
#### `encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array`
|
|
350
|
-
|
|
351
|
-
Encrypts plaintext with Serpent CBC and PKCS7 padding. The returned ciphertext is
|
|
352
|
-
always a multiple of 16 bytes and is at least 16 bytes longer than the input (due
|
|
353
|
-
to padding).
|
|
354
|
-
|
|
355
|
-
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
356
|
-
- **iv**: exactly 16 bytes. Must be random and unique per (key, message) pair. Throws `RangeError` if the length is not 16.
|
|
357
|
-
- **plaintext**: any length including zero. PKCS7 padding is applied automatically.
|
|
358
|
-
|
|
359
|
-
Returns the ciphertext as a new `Uint8Array`.
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
|
|
363
|
-
#### `decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array`
|
|
364
|
-
|
|
365
|
-
Decrypts Serpent CBC ciphertext and strips PKCS7 padding.
|
|
366
|
-
|
|
367
|
-
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
368
|
-
- **iv**: exactly 16 bytes. Must match the IV used for encryption. Throws `RangeError` if the length is not 16.
|
|
369
|
-
- **ciphertext**: must be a non-zero multiple of 16 bytes. Throws `RangeError` with the generic message `'invalid ciphertext'` on any failure — zero length, non-multiple-of-16 length, or invalid PKCS7 padding. The single message and branch-free padding check close the Vaudenay 2002 padding-oracle surface; a caller cannot distinguish failure modes by message or by timing.
|
|
370
|
-
|
|
371
|
-
Returns the decrypted plaintext as a new `Uint8Array`.
|
|
372
|
-
|
|
373
|
-
> [!NOTE]
|
|
374
|
-
> Decryption always uses the 4-wide SIMD path (`cbcDecryptChunk_simd`). SIMD is required by the serpent module; `init()` throws on runtimes without it. CBC encryption has no SIMD variant because each ciphertext block depends on the previous one.
|
|
375
|
-
|
|
376
|
-
---
|
|
377
|
-
|
|
378
|
-
#### `dispose(): void`
|
|
379
|
-
|
|
380
|
-
Wipes all key material and intermediate state from WASM memory.
|
|
381
|
-
|
|
382
|
-
After `dispose()`, `encrypt` and `decrypt` throw `Error: SerpentCbc: instance
|
|
383
|
-
has been disposed`. Disposal is permanent; construct a new instance if you
|
|
384
|
-
need to continue.
|
|
385
|
-
|
|
386
|
-
---
|
|
387
|
-
|
|
388
|
-
### Security — direct use of `SerpentCbc`
|
|
389
|
-
|
|
390
|
-
`SerpentCbc` is unauthenticated. If you use it directly via
|
|
391
|
-
`{ dangerUnauthenticated: true }`, you are responsible for:
|
|
392
|
-
|
|
393
|
-
1. Authenticating the ciphertext (HMAC-SHA256 in Encrypt-then-MAC order)
|
|
394
|
-
2. Verifying the HMAC **before** calling `decrypt()`
|
|
395
|
-
3. Using a unique, random IV per (key, message)
|
|
396
|
-
|
|
397
|
-
`SerpentCbc.decrypt()` throws a single generic `'invalid ciphertext'`
|
|
398
|
-
error for all padding failures and runs its validation in constant time over the
|
|
399
|
-
final 16 bytes. This mitigates padding-oracle attacks (Vaudenay 2002) on callers
|
|
400
|
-
that surface errors to remote parties. The authenticated composition
|
|
401
|
-
`SerpentCipher` always verifies HMAC before any PKCS7 processing and is the
|
|
402
|
-
recommended path.
|
|
403
|
-
|
|
404
|
-
---
|
|
405
|
-
|
|
406
|
-
## SerpentGenerator
|
|
407
|
-
|
|
408
|
-
Serpent-256 ECB counter-mode PRF for Fortuna's generator slot. Implements the
|
|
409
|
-
`Generator` interface (Practical Cryptography, Ferguson & Schneier 2003 §9.4).
|
|
410
|
-
This is a plain `const` object, not a class — no instantiation, no `dispose()`.
|
|
411
|
-
|
|
412
|
-
Requires `init({ serpent: serpentWasm })`. See [fortuna.md](./fortuna.md) for
|
|
413
|
-
full usage with `Fortuna.create()`.
|
|
414
|
-
|
|
415
|
-
| Property | Value |
|
|
416
|
-
|----------|-------|
|
|
417
|
-
| `keySize` | `32` |
|
|
418
|
-
| `blockSize` | `16` |
|
|
419
|
-
| `counterSize` | `16` |
|
|
420
|
-
| `wasmModules` | `['serpent']` |
|
|
421
|
-
|
|
422
|
-
### `SerpentGenerator.generate(key, counter, n): Uint8Array`
|
|
423
|
-
|
|
424
|
-
Produces `n` bytes of pseudorandom output from `(key, counter)`. Neither input
|
|
425
|
-
is mutated. Wipes WASM key/key-schedule/scratch and the JS-heap counter copy
|
|
426
|
-
before returning.
|
|
427
|
-
|
|
428
|
-
| Parameter | Type | Description |
|
|
429
|
-
|-----------|------|-------------|
|
|
430
|
-
| `key` | `Uint8Array` | 32 bytes (256-bit Serpent key) |
|
|
431
|
-
| `counter` | `Uint8Array` | 16 bytes, treated as a little-endian integer |
|
|
432
|
-
| `n` | `number` | Output byte count: 0 ≤ n ≤ 2³⁰ |
|
|
433
|
-
|
|
434
|
-
**Returns** a new `Uint8Array` of length `n`.
|
|
435
|
-
|
|
436
|
-
**Throws:**
|
|
437
|
-
- `RangeError('SerpentGenerator: key must be 32 bytes (got N)')` if key length ≠ 32
|
|
438
|
-
- `RangeError('SerpentGenerator: counter must be 16 bytes (got N)')` if counter length ≠ 16
|
|
439
|
-
- `RangeError('SerpentGenerator: n must be a non-negative safe integer <= 2^30 (got N)')` if n is out of range
|
|
440
|
-
- `Error` if another stateful instance currently owns the `serpent` WASM module
|
|
441
|
-
|
|
442
|
-
### Usage with `Fortuna`
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
import { init, Fortuna } from 'leviathan-crypto'
|
|
446
|
-
import { SerpentGenerator } from 'leviathan-crypto/serpent'
|
|
447
|
-
import { SHA256Hash } from 'leviathan-crypto/sha2'
|
|
448
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
449
|
-
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
450
|
-
|
|
451
|
-
await init({ serpent: serpentWasm, sha2: sha2Wasm })
|
|
452
|
-
const rng = await Fortuna.create({ generator: SerpentGenerator, hash: SHA256Hash })
|
|
453
|
-
const bytes = rng.get(32)
|
|
454
|
-
rng.stop()
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
---
|
|
458
|
-
|
|
459
|
-
## Usage Examples
|
|
460
|
-
|
|
461
|
-
### Example 1: Seal with SerpentCipher (authenticated encryption)
|
|
462
|
-
|
|
463
|
-
```typescript
|
|
464
|
-
import { init, Seal, SerpentCipher } from 'leviathan-crypto'
|
|
465
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
466
|
-
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
467
|
-
|
|
468
|
-
await init({ serpent: serpentWasm, sha2: sha2Wasm })
|
|
469
|
-
|
|
470
|
-
const key = SerpentCipher.keygen()
|
|
471
|
-
const plaintext = new TextEncoder().encode('Authenticated secret message.')
|
|
472
|
-
const blob = Seal.encrypt(SerpentCipher, key, plaintext)
|
|
473
|
-
const decrypted = Seal.decrypt(SerpentCipher, key, blob)
|
|
474
|
-
|
|
475
|
-
console.log(new TextDecoder().decode(decrypted))
|
|
476
|
-
// "Authenticated secret message."
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
### Example 2: CTR mode (advanced)
|
|
480
|
-
|
|
481
|
-
Use `SerpentCtr` to encrypt data of any length. CTR mode produces ciphertext
|
|
482
|
-
the same length as the plaintext with no padding overhead.
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
import { init, SerpentCtr, randomBytes } from 'leviathan-crypto';
|
|
486
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
|
|
487
|
-
|
|
488
|
-
await init({ serpent: serpentWasm });
|
|
489
|
-
|
|
490
|
-
const key = randomBytes(32); // 256-bit key
|
|
491
|
-
const nonce = randomBytes(16); // 16-byte nonce, NEVER reuse with the same key
|
|
492
|
-
|
|
493
|
-
const ctr = new SerpentCtr({ dangerUnauthenticated: true });
|
|
494
|
-
|
|
495
|
-
// Encrypt
|
|
496
|
-
ctr.beginEncrypt(key, nonce);
|
|
497
|
-
const ciphertext1 = ctr.encryptChunk(new TextEncoder().encode('Hello, '));
|
|
498
|
-
const ciphertext2 = ctr.encryptChunk(new TextEncoder().encode('world!'));
|
|
499
|
-
|
|
500
|
-
// Decrypt (same key and nonce)
|
|
501
|
-
ctr.beginDecrypt(key, nonce);
|
|
502
|
-
const plain1 = ctr.decryptChunk(ciphertext1);
|
|
503
|
-
const plain2 = ctr.decryptChunk(ciphertext2);
|
|
504
|
-
|
|
505
|
-
console.log(new TextDecoder().decode(plain1)); // "Hello, "
|
|
506
|
-
console.log(new TextDecoder().decode(plain2)); // "world!"
|
|
507
|
-
|
|
508
|
-
// Wipe key material
|
|
509
|
-
ctr.dispose();
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
> [!IMPORTANT]
|
|
513
|
-
> CTR mode is unauthenticated. An attacker can tamper with the
|
|
514
|
-
> ciphertext without detection. Use [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) for authenticated encryption.
|
|
515
|
-
|
|
516
|
-
### Example 3: CBC mode (advanced)
|
|
517
|
-
|
|
518
|
-
Use `SerpentCbc` for message-level encryption with automatic PKCS7 padding.
|
|
519
|
-
|
|
520
|
-
```typescript
|
|
521
|
-
import { init, SerpentCbc, randomBytes } from 'leviathan-crypto';
|
|
522
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
|
|
523
|
-
|
|
524
|
-
await init({ serpent: serpentWasm });
|
|
525
|
-
|
|
526
|
-
const key = randomBytes(32); // 256-bit key
|
|
527
|
-
const iv = randomBytes(16); // Random IV, must be unique per message
|
|
528
|
-
|
|
529
|
-
const cbc = new SerpentCbc({ dangerUnauthenticated: true });
|
|
530
|
-
|
|
531
|
-
// Encrypt
|
|
532
|
-
const plaintext = new TextEncoder().encode('This is a secret message.');
|
|
533
|
-
const ciphertext = cbc.encrypt(key, iv, plaintext);
|
|
534
|
-
|
|
535
|
-
// Decrypt
|
|
536
|
-
const decrypted = cbc.decrypt(key, iv, ciphertext);
|
|
537
|
-
console.log(new TextDecoder().decode(decrypted)); // "This is a secret message."
|
|
538
|
-
|
|
539
|
-
// Wipe key material
|
|
540
|
-
cbc.dispose();
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
> [!IMPORTANT]
|
|
544
|
-
> CBC mode is unauthenticated. Use [`Seal`](./aead.md#seal) with [`SerpentCipher`](./ciphersuite.md#serpentcipher) for authenticated encryption.
|
|
545
|
-
|
|
546
|
-
### Example 4: Raw block operations (low-level)
|
|
547
|
-
|
|
548
|
-
Use the `Serpent` class for single 16-byte block operations. This is the lowest-level
|
|
549
|
-
|
|
550
|
-
```typescript
|
|
551
|
-
import { init, Serpent } from 'leviathan-crypto';
|
|
552
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
|
|
553
|
-
|
|
554
|
-
await init({ serpent: serpentWasm });
|
|
555
|
-
|
|
556
|
-
const cipher = new Serpent();
|
|
557
|
-
|
|
558
|
-
// Load a 256-bit key (32 bytes)
|
|
559
|
-
const key = new Uint8Array(32);
|
|
560
|
-
crypto.getRandomValues(key);
|
|
561
|
-
cipher.loadKey(key);
|
|
562
|
-
|
|
563
|
-
// Encrypt a 16-byte block
|
|
564
|
-
const plaintext = new Uint8Array([
|
|
565
|
-
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
566
|
-
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
567
|
-
]);
|
|
568
|
-
const ciphertext = cipher.encryptBlock(plaintext);
|
|
569
|
-
|
|
570
|
-
// Decrypt it back
|
|
571
|
-
const decrypted = cipher.decryptBlock(ciphertext);
|
|
572
|
-
// decrypted is identical to plaintext
|
|
573
|
-
|
|
574
|
-
// Wipe key material from memory when done
|
|
575
|
-
cipher.dispose();
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
---
|
|
579
|
-
|
|
580
|
-
## Error Conditions
|
|
581
|
-
|
|
582
|
-
| Condition | Error type | Message |
|
|
583
|
-
|-----------|-----------|---------|
|
|
584
|
-
| `init({ serpent: ... })` not called before constructing `Serpent` | `Error` | `leviathan-crypto: call init({ serpent: ... }) before using this class` |
|
|
585
|
-
| `SerpentCbc` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCbc is unauthenticated — use Seal with SerpentCipher instead. To use SerpentCbc directly, pass { dangerUnauthenticated: true }.` |
|
|
586
|
-
| `SerpentCtr` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCtr is unauthenticated — use Seal with SerpentCipher instead. To use SerpentCtr directly, pass { dangerUnauthenticated: true }.` |
|
|
587
|
-
| Key is not 16, 24, or 32 bytes (`Serpent.loadKey`) | `RangeError` | `key must be 16, 24, or 32 bytes (got N)` |
|
|
588
|
-
| Key is not 16, 24, or 32 bytes (`SerpentCbc`) | `RangeError` | `Serpent key must be 16, 24, or 32 bytes (got N)` |
|
|
589
|
-
| Key is not 16, 24, or 32 bytes (`SerpentCtr`) | `RangeError` | `key must be 16, 24, or 32 bytes` |
|
|
590
|
-
| Block is not 16 bytes (`Serpent`) | `RangeError` | `block must be 16 bytes (got N)` |
|
|
591
|
-
| Nonce is not 16 bytes (`SerpentCtr`) | `RangeError` | `nonce must be 16 bytes (got N)` |
|
|
592
|
-
| Chunk exceeds buffer size (`SerpentCtr`) | `RangeError` | `chunk exceeds maximum size of N bytes — split into smaller chunks` |
|
|
593
|
-
| IV is not 16 bytes (`SerpentCbc`) | `RangeError` | `CBC IV must be 16 bytes (got N)` |
|
|
594
|
-
| Ciphertext length zero, not a multiple of 16, or PKCS7 padding invalid (`SerpentCbc.decrypt`) | `RangeError` | `invalid ciphertext` (same message for every failure mode — no numeric leak) |
|
|
595
|
-
| `SerpentGenerator.generate()` key ≠ 32 bytes | `RangeError` | `SerpentGenerator: key must be 32 bytes (got N)` |
|
|
596
|
-
| `SerpentGenerator.generate()` counter ≠ 16 bytes | `RangeError` | `SerpentGenerator: counter must be 16 bytes (got N)` |
|
|
597
|
-
| `SerpentGenerator.generate()` n out of range | `RangeError` | `SerpentGenerator: n must be a non-negative safe integer <= 2^30 (got N)` |
|
|
598
|
-
|
|
599
|
-
---
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
## Cross-References
|
|
603
|
-
|
|
604
|
-
| Document | Description |
|
|
605
|
-
| -------- | ----------- |
|
|
606
|
-
| [index](./README.md) | Project Documentation index |
|
|
607
|
-
| [lexicon](./lexicon.md) | Glossary of cryptographic terms |
|
|
608
|
-
| [architecture](./architecture.md) | architecture overview, module relationships, buffer layouts, and build pipeline |
|
|
609
|
-
| [asm_serpent](./asm_serpent.md) | WASM implementation details and buffer layout |
|
|
610
|
-
| [serpent_reference](./serpent_reference.md) | algorithm specification, S-boxes, linear transform, and known attacks |
|
|
611
|
-
| [serpent_audit](./serpent_audit.md) | security audit findings (correctness, side-channel analysis) |
|
|
612
|
-
| [authenticated encryption](./aead.md) | `Seal`, `SealStream`, `OpenStream`: use `SerpentCipher` as the suite argument |
|
|
613
|
-
| [chacha20](./chacha20.md) | `XChaCha20Cipher`: alternative `CipherSuite` for `Seal` and streaming |
|
|
614
|
-
| [sha2](./sha2.md) | HMAC-SHA256 and HKDF used internally by `SerpentCipher` |
|
|
615
|
-
| [types](./types.md) | `Blockcipher`, `Streamcipher`, and `AEAD` interfaces implemented by Serpent classes |
|
|
616
|
-
| [utils](./utils.md) | `constantTimeEqual`, `wipe`, `randomBytes` used by Serpent wrappers |
|
|
617
|
-
|