leviathan-crypto 2.0.1 → 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.
Files changed (312) hide show
  1. package/CLAUDE.md +88 -281
  2. package/LICENSE +4 -0
  3. package/README.md +275 -87
  4. package/dist/aes/aes-cbc.d.ts +40 -0
  5. package/dist/aes/aes-cbc.js +158 -0
  6. package/dist/aes/aes-ctr.d.ts +50 -0
  7. package/dist/aes/aes-ctr.js +141 -0
  8. package/dist/aes/aes-gcm-siv.d.ts +67 -0
  9. package/dist/aes/aes-gcm-siv.js +217 -0
  10. package/dist/aes/aes-gcm.d.ts +61 -0
  11. package/dist/aes/aes-gcm.js +226 -0
  12. package/dist/aes/cipher-suite.d.ts +21 -0
  13. package/dist/aes/cipher-suite.js +179 -0
  14. package/dist/aes/embedded.d.ts +1 -0
  15. package/dist/aes/embedded.js +26 -0
  16. package/dist/aes/generator.d.ts +14 -0
  17. package/dist/aes/generator.js +103 -0
  18. package/dist/aes/index.d.ts +58 -0
  19. package/dist/aes/index.js +125 -0
  20. package/dist/aes/ops.d.ts +60 -0
  21. package/dist/aes/ops.js +164 -0
  22. package/dist/aes/pool-worker.d.ts +1 -0
  23. package/dist/aes/pool-worker.js +92 -0
  24. package/dist/aes/types.d.ts +1 -0
  25. package/dist/aes/types.js +23 -0
  26. package/dist/aes.wasm +0 -0
  27. package/dist/blake3/embedded.d.ts +1 -0
  28. package/dist/blake3/embedded.js +26 -0
  29. package/dist/blake3/index.d.ts +143 -0
  30. package/dist/blake3/index.js +620 -0
  31. package/dist/blake3/types.d.ts +102 -0
  32. package/dist/blake3/types.js +31 -0
  33. package/dist/blake3/validate.d.ts +29 -0
  34. package/dist/blake3/validate.js +80 -0
  35. package/dist/blake3.wasm +0 -0
  36. package/dist/chacha20/cipher-suite.d.ts +10 -0
  37. package/dist/chacha20/cipher-suite.js +98 -13
  38. package/dist/chacha20/generator.d.ts +12 -0
  39. package/dist/chacha20/generator.js +91 -0
  40. package/dist/chacha20/index.d.ts +100 -3
  41. package/dist/chacha20/index.js +169 -35
  42. package/dist/chacha20/ops.d.ts +57 -6
  43. package/dist/chacha20/ops.js +107 -27
  44. package/dist/chacha20/pool-worker.js +14 -0
  45. package/dist/chacha20/types.d.ts +1 -32
  46. package/dist/cte-wasm.d.ts +1 -0
  47. package/dist/cte-wasm.js +3 -0
  48. package/dist/cte.wasm +0 -0
  49. package/dist/curve25519.wasm +0 -0
  50. package/dist/ecdsa/der.d.ts +23 -0
  51. package/dist/ecdsa/der.js +192 -0
  52. package/dist/ecdsa/ecprivatekey-der.d.ts +32 -0
  53. package/dist/ecdsa/ecprivatekey-der.js +230 -0
  54. package/dist/ecdsa/embedded.d.ts +1 -0
  55. package/dist/ecdsa/embedded.js +25 -0
  56. package/dist/ecdsa/index.d.ts +124 -0
  57. package/dist/ecdsa/index.js +366 -0
  58. package/dist/ecdsa/types.d.ts +31 -0
  59. package/dist/ecdsa/types.js +28 -0
  60. package/dist/ecdsa/validate.d.ts +18 -0
  61. package/dist/ecdsa/validate.js +92 -0
  62. package/dist/ed25519/embedded.d.ts +1 -0
  63. package/dist/ed25519/embedded.js +31 -0
  64. package/dist/ed25519/index.d.ts +70 -0
  65. package/dist/ed25519/index.js +308 -0
  66. package/dist/ed25519/types.d.ts +27 -0
  67. package/dist/ed25519/types.js +27 -0
  68. package/dist/ed25519/validate.d.ts +7 -0
  69. package/dist/ed25519/validate.js +77 -0
  70. package/dist/embedded/aes-pool-worker.d.ts +1 -0
  71. package/dist/embedded/aes-pool-worker.js +5 -0
  72. package/dist/embedded/aes.d.ts +1 -0
  73. package/dist/embedded/aes.js +3 -0
  74. package/dist/embedded/blake3.d.ts +1 -0
  75. package/dist/embedded/blake3.js +3 -0
  76. package/dist/embedded/chacha20-pool-worker.d.ts +1 -0
  77. package/dist/embedded/chacha20-pool-worker.js +5 -0
  78. package/dist/embedded/chacha20.d.ts +1 -1
  79. package/dist/embedded/chacha20.js +2 -2
  80. package/dist/embedded/curve25519.d.ts +1 -0
  81. package/dist/embedded/curve25519.js +3 -0
  82. package/dist/embedded/mldsa.d.ts +1 -0
  83. package/dist/embedded/mldsa.js +3 -0
  84. package/dist/embedded/mlkem.d.ts +1 -0
  85. package/dist/embedded/mlkem.js +3 -0
  86. package/dist/embedded/p256.d.ts +1 -0
  87. package/dist/embedded/p256.js +3 -0
  88. package/dist/embedded/serpent-pool-worker.d.ts +1 -0
  89. package/dist/embedded/serpent-pool-worker.js +5 -0
  90. package/dist/embedded/serpent.d.ts +1 -1
  91. package/dist/embedded/serpent.js +2 -2
  92. package/dist/embedded/sha2.d.ts +1 -1
  93. package/dist/embedded/sha2.js +2 -2
  94. package/dist/embedded/sha3.d.ts +1 -1
  95. package/dist/embedded/sha3.js +2 -2
  96. package/dist/embedded/slhdsa.d.ts +1 -0
  97. package/dist/embedded/slhdsa.js +3 -0
  98. package/dist/errors.d.ts +92 -1
  99. package/dist/errors.js +111 -1
  100. package/dist/fortuna.d.ts +18 -12
  101. package/dist/fortuna.js +166 -99
  102. package/dist/index.d.ts +42 -11
  103. package/dist/index.js +65 -20
  104. package/dist/init.d.ts +1 -3
  105. package/dist/init.js +73 -7
  106. package/dist/keccak/embedded.js +1 -1
  107. package/dist/keccak/index.d.ts +2 -0
  108. package/dist/keccak/index.js +4 -2
  109. package/dist/loader.d.ts +1 -19
  110. package/dist/loader.js +26 -32
  111. package/dist/merkle/blake3-tree.d.ts +35 -0
  112. package/dist/merkle/blake3-tree.js +187 -0
  113. package/dist/merkle/checkpoint.d.ts +58 -0
  114. package/dist/merkle/checkpoint.js +217 -0
  115. package/dist/merkle/index.d.ts +19 -0
  116. package/dist/merkle/index.js +37 -0
  117. package/dist/merkle/merkle-log.d.ts +130 -0
  118. package/dist/merkle/merkle-log.js +207 -0
  119. package/dist/merkle/merkle-verifier.d.ts +126 -0
  120. package/dist/merkle/merkle-verifier.js +296 -0
  121. package/dist/merkle/proof.d.ts +70 -0
  122. package/dist/merkle/proof.js +300 -0
  123. package/dist/merkle/sha256-tree.d.ts +33 -0
  124. package/dist/merkle/sha256-tree.js +145 -0
  125. package/dist/merkle/signed-log.d.ts +156 -0
  126. package/dist/merkle/signed-log.js +356 -0
  127. package/dist/merkle/signed-note.d.ts +309 -0
  128. package/dist/merkle/signed-note.js +648 -0
  129. package/dist/merkle/sth.d.ts +31 -0
  130. package/dist/merkle/sth.js +31 -0
  131. package/dist/merkle/storage.d.ts +40 -0
  132. package/dist/merkle/storage.js +71 -0
  133. package/dist/merkle/tree.d.ts +68 -0
  134. package/dist/merkle/tree.js +94 -0
  135. package/dist/mldsa/embedded.d.ts +1 -0
  136. package/dist/{kyber → mldsa}/embedded.js +5 -5
  137. package/dist/mldsa/expand.d.ts +53 -0
  138. package/dist/mldsa/expand.js +188 -0
  139. package/dist/mldsa/format.d.ts +16 -0
  140. package/dist/mldsa/format.js +68 -0
  141. package/dist/mldsa/hashvariant.d.ts +32 -0
  142. package/dist/mldsa/hashvariant.js +248 -0
  143. package/dist/mldsa/index.d.ts +142 -0
  144. package/dist/mldsa/index.js +463 -0
  145. package/dist/mldsa/keygen.d.ts +16 -0
  146. package/dist/mldsa/keygen.js +232 -0
  147. package/dist/mldsa/params.d.ts +21 -0
  148. package/dist/mldsa/params.js +55 -0
  149. package/dist/mldsa/sha3-helpers.d.ts +30 -0
  150. package/dist/mldsa/sha3-helpers.js +124 -0
  151. package/dist/mldsa/sign.d.ts +36 -0
  152. package/dist/mldsa/sign.js +380 -0
  153. package/dist/mldsa/types.d.ts +91 -0
  154. package/dist/mldsa/types.js +25 -0
  155. package/dist/mldsa/validate.d.ts +55 -0
  156. package/dist/mldsa/validate.js +125 -0
  157. package/dist/mldsa/verify.d.ts +29 -0
  158. package/dist/mldsa/verify.js +269 -0
  159. package/dist/mldsa.wasm +0 -0
  160. package/dist/mlkem/embedded.d.ts +1 -0
  161. package/dist/mlkem/embedded.js +27 -0
  162. package/dist/mlkem/indcpa.d.ts +49 -0
  163. package/dist/{kyber → mlkem}/indcpa.js +48 -48
  164. package/dist/mlkem/index.d.ts +37 -0
  165. package/dist/{kyber → mlkem}/index.js +41 -31
  166. package/dist/mlkem/kem.d.ts +21 -0
  167. package/dist/{kyber → mlkem}/kem.js +48 -13
  168. package/dist/{kyber → mlkem}/params.d.ts +4 -4
  169. package/dist/{kyber → mlkem}/params.js +2 -2
  170. package/dist/mlkem/suite.d.ts +12 -0
  171. package/dist/{kyber → mlkem}/suite.js +17 -12
  172. package/dist/{kyber → mlkem}/types.d.ts +4 -3
  173. package/dist/{kyber → mlkem}/types.js +1 -1
  174. package/dist/mlkem/validate.d.ts +23 -0
  175. package/dist/{kyber → mlkem}/validate.js +24 -20
  176. package/dist/{kyber.wasm → mlkem.wasm} +0 -0
  177. package/dist/p256.wasm +0 -0
  178. package/dist/ratchet/index.d.ts +8 -0
  179. package/dist/ratchet/index.js +38 -0
  180. package/dist/ratchet/kdf-chain.d.ts +13 -0
  181. package/dist/ratchet/kdf-chain.js +85 -0
  182. package/dist/ratchet/ratchet-keypair.d.ts +9 -0
  183. package/dist/ratchet/ratchet-keypair.js +61 -0
  184. package/dist/ratchet/root-kdf.d.ts +4 -0
  185. package/dist/ratchet/root-kdf.js +124 -0
  186. package/dist/ratchet/skipped-key-store.d.ts +14 -0
  187. package/dist/ratchet/skipped-key-store.js +154 -0
  188. package/dist/ratchet/types.d.ts +36 -0
  189. package/dist/ratchet/types.js +26 -0
  190. package/dist/serpent/cipher-suite.d.ts +10 -0
  191. package/dist/serpent/cipher-suite.js +144 -56
  192. package/dist/serpent/generator.d.ts +12 -0
  193. package/dist/serpent/generator.js +97 -0
  194. package/dist/serpent/index.d.ts +62 -1
  195. package/dist/serpent/index.js +97 -21
  196. package/dist/serpent/pool-worker.js +28 -102
  197. package/dist/serpent/serpent-cbc.d.ts +16 -6
  198. package/dist/serpent/serpent-cbc.js +58 -37
  199. package/dist/serpent/shared-ops.d.ts +63 -0
  200. package/dist/serpent/shared-ops.js +178 -0
  201. package/dist/serpent/types.d.ts +1 -5
  202. package/dist/serpent.wasm +0 -0
  203. package/dist/sha2/hash.d.ts +2 -0
  204. package/dist/sha2/hash.js +53 -0
  205. package/dist/sha2/hkdf.js +5 -5
  206. package/dist/sha2/index.d.ts +22 -1
  207. package/dist/sha2/index.js +80 -11
  208. package/dist/sha2/types.d.ts +41 -2
  209. package/dist/sha2.wasm +0 -0
  210. package/dist/sha3/hash.d.ts +2 -0
  211. package/dist/sha3/hash.js +53 -0
  212. package/dist/sha3/index.d.ts +87 -3
  213. package/dist/sha3/index.js +317 -19
  214. package/dist/sha3/kmac.d.ts +121 -0
  215. package/dist/sha3/kmac.js +800 -0
  216. package/dist/sha3.wasm +0 -0
  217. package/dist/shared/pkcs7.d.ts +22 -0
  218. package/dist/shared/pkcs7.js +84 -0
  219. package/dist/sign/ctx.d.ts +41 -0
  220. package/dist/sign/ctx.js +102 -0
  221. package/dist/sign/envelope.d.ts +45 -0
  222. package/dist/sign/envelope.js +152 -0
  223. package/dist/sign/hasher.d.ts +9 -0
  224. package/dist/sign/hasher.js +132 -0
  225. package/dist/sign/index.d.ts +11 -0
  226. package/dist/sign/index.js +34 -0
  227. package/dist/sign/sign-stream.d.ts +25 -0
  228. package/dist/sign/sign-stream.js +112 -0
  229. package/dist/sign/suites/ecdsa-p256.d.ts +2 -0
  230. package/dist/sign/suites/ecdsa-p256.js +120 -0
  231. package/dist/sign/suites/ed25519.d.ts +3 -0
  232. package/dist/sign/suites/ed25519.js +165 -0
  233. package/dist/sign/suites/hybrid-classical.d.ts +23 -0
  234. package/dist/sign/suites/hybrid-classical.js +526 -0
  235. package/dist/sign/suites/hybrid-pq.d.ts +4 -0
  236. package/dist/sign/suites/hybrid-pq.js +234 -0
  237. package/dist/sign/suites/mldsa.d.ts +7 -0
  238. package/dist/sign/suites/mldsa.js +161 -0
  239. package/dist/sign/suites/slhdsa.d.ts +7 -0
  240. package/dist/sign/suites/slhdsa.js +176 -0
  241. package/dist/sign/types.d.ts +106 -0
  242. package/dist/sign/types.js +28 -0
  243. package/dist/sign/verify-stream.d.ts +30 -0
  244. package/dist/sign/verify-stream.js +227 -0
  245. package/dist/slhdsa/embedded.d.ts +1 -0
  246. package/dist/slhdsa/embedded.js +26 -0
  247. package/dist/slhdsa/index.d.ts +149 -0
  248. package/dist/slhdsa/index.js +493 -0
  249. package/dist/slhdsa/params.d.ts +26 -0
  250. package/dist/slhdsa/params.js +70 -0
  251. package/dist/slhdsa/prehash.d.ts +68 -0
  252. package/dist/slhdsa/prehash.js +307 -0
  253. package/dist/slhdsa/sign.d.ts +39 -0
  254. package/dist/slhdsa/sign.js +116 -0
  255. package/dist/slhdsa/types.d.ts +129 -0
  256. package/dist/slhdsa/types.js +27 -0
  257. package/dist/slhdsa/validate.d.ts +60 -0
  258. package/dist/slhdsa/validate.js +127 -0
  259. package/dist/slhdsa/verify.d.ts +32 -0
  260. package/dist/slhdsa/verify.js +107 -0
  261. package/dist/slhdsa.wasm +0 -0
  262. package/dist/stream/header.js +8 -8
  263. package/dist/stream/index.d.ts +1 -0
  264. package/dist/stream/index.js +1 -0
  265. package/dist/stream/open-stream.js +65 -22
  266. package/dist/stream/seal-stream-pool.d.ts +2 -0
  267. package/dist/stream/seal-stream-pool.js +100 -33
  268. package/dist/stream/seal-stream.d.ts +1 -1
  269. package/dist/stream/seal-stream.js +48 -19
  270. package/dist/stream/seal.js +6 -6
  271. package/dist/stream/types.d.ts +3 -1
  272. package/dist/stream/types.js +1 -1
  273. package/dist/types.d.ts +22 -1
  274. package/dist/types.js +1 -1
  275. package/dist/utils.d.ts +9 -10
  276. package/dist/utils.js +84 -59
  277. package/dist/wasm-source.d.ts +9 -8
  278. package/dist/wasm-source.js +1 -1
  279. package/dist/x25519/embedded.d.ts +1 -0
  280. package/dist/x25519/embedded.js +31 -0
  281. package/dist/x25519/index.d.ts +43 -0
  282. package/dist/x25519/index.js +159 -0
  283. package/dist/x25519/types.d.ts +25 -0
  284. package/dist/x25519/types.js +27 -0
  285. package/dist/x25519/validate.d.ts +2 -0
  286. package/dist/x25519/validate.js +39 -0
  287. package/package.json +123 -64
  288. package/SECURITY.md +0 -276
  289. package/dist/ct-wasm.d.ts +0 -1
  290. package/dist/ct-wasm.js +0 -3
  291. package/dist/ct.wasm +0 -0
  292. package/dist/docs/aead.md +0 -323
  293. package/dist/docs/architecture.md +0 -932
  294. package/dist/docs/argon2id.md +0 -302
  295. package/dist/docs/chacha20.md +0 -674
  296. package/dist/docs/exports.md +0 -241
  297. package/dist/docs/fortuna.md +0 -313
  298. package/dist/docs/init.md +0 -302
  299. package/dist/docs/loader.md +0 -161
  300. package/dist/docs/serpent.md +0 -519
  301. package/dist/docs/sha2.md +0 -613
  302. package/dist/docs/sha3.md +0 -546
  303. package/dist/docs/types.md +0 -276
  304. package/dist/docs/utils.md +0 -367
  305. package/dist/embedded/kyber.d.ts +0 -1
  306. package/dist/embedded/kyber.js +0 -3
  307. package/dist/kyber/embedded.d.ts +0 -1
  308. package/dist/kyber/indcpa.d.ts +0 -49
  309. package/dist/kyber/index.d.ts +0 -38
  310. package/dist/kyber/kem.d.ts +0 -21
  311. package/dist/kyber/suite.d.ts +0 -13
  312. package/dist/kyber/validate.d.ts +0 -19
@@ -1,11 +1,18 @@
1
1
  // src/ts/chacha20/ops.ts
2
2
  //
3
- // Raw XChaCha20-Poly1305 operations standalone functions that take
3
+ // Raw XChaCha20-Poly1305 operations, standalone functions that take
4
4
  // ChaChaExports explicitly. Used by both the class wrappers (index.ts)
5
5
  // and the pool worker (pool.worker.ts), eliminating duplication.
6
- import { constantTimeEqual } from '../utils.js';
6
+ import { constantTimeEqual, wipe } from '../utils.js';
7
7
  import { AuthenticationError } from '../errors.js';
8
- // ── Module-private helpers ───────────────────────────────────────────────────
8
+ // ── Module-private helpers ──────────────────────────────────────────────────
9
+ /**
10
+ * Feed `data` into the active Poly1305 accumulator via the WASM message buffer.
11
+ * No-op when `data` is empty.
12
+ * @param x ChaCha20 WASM exports
13
+ * @param data Bytes to absorb
14
+ * @internal
15
+ */
9
16
  function polyFeed(x, data) {
10
17
  if (data.length === 0)
11
18
  return;
@@ -19,11 +26,19 @@ function polyFeed(x, data) {
19
26
  pos += chunk;
20
27
  }
21
28
  }
29
+ /**
30
+ * Build the 16-byte Poly1305 length footer from AAD and ciphertext lengths.
31
+ * Both lengths are encoded as 64-bit little-endian integers (RFC 8439 §2.8).
32
+ * @param aadLen AAD byte length
33
+ * @param ctLen Ciphertext byte length
34
+ * @returns 16-byte length block
35
+ * @internal
36
+ */
22
37
  function lenBlock(aadLen, ctLen) {
23
38
  const b = new Uint8Array(16);
24
39
  const dv = new DataView(b.buffer);
25
- // RFC 8439 §2.8 64-bit LE lengths.
26
- // JS numbers are f64 write low 32 bits directly, high bits via
40
+ // RFC 8439 §2.8, 64-bit LE lengths.
41
+ // JS numbers are f64, write low 32 bits directly, high bits via
27
42
  // Math.floor(n / 2^32). Safe for n ≤ Number.MAX_SAFE_INTEGER.
28
43
  dv.setUint32(0, aadLen >>> 0, true);
29
44
  dv.setUint32(4, Math.floor(aadLen / 0x100000000) >>> 0, true);
@@ -31,12 +46,20 @@ function lenBlock(aadLen, ctLen) {
31
46
  dv.setUint32(12, Math.floor(ctLen / 0x100000000) >>> 0, true);
32
47
  return b;
33
48
  }
34
- // ── Inner AEAD (12-byte nonce) ───────────────────────────────────────────────
35
- /** ChaCha20-Poly1305 AEAD encrypt (RFC 8439 §2.8). */
49
+ // ── Inner AEAD (12-byte nonce) ──────────────────────────────────────────────
50
+ /**
51
+ * ChaCha20-Poly1305 AEAD encrypt (RFC 8439 §2.8).
52
+ * @param x ChaCha20 WASM exports
53
+ * @param key 32-byte key
54
+ * @param nonce 12-byte nonce, must be unique per (key, message)
55
+ * @param plaintext Data to encrypt (must be ≤ WASM CHUNK_SIZE)
56
+ * @param aad Additional authenticated data
57
+ * @returns `{ ciphertext, tag }`, tag is 16 bytes
58
+ */
36
59
  export function aeadEncrypt(x, key, nonce, plaintext, aad) {
37
60
  const maxChunk = x.getChunkSize();
38
61
  if (plaintext.length > maxChunk)
39
- throw new RangeError(`plaintext exceeds ${maxChunk} bytes split into smaller chunks`);
62
+ throw new RangeError(`plaintext exceeds ${maxChunk} bytes, split into smaller chunks`);
40
63
  const mem = new Uint8Array(x.memory.buffer);
41
64
  // Step 1: Generate Poly1305 one-time key at counter=0 (RFC 8439 §2.6)
42
65
  mem.set(key, x.getKeyOffset());
@@ -50,9 +73,8 @@ export function aeadEncrypt(x, key, nonce, plaintext, aad) {
50
73
  const aadPad = (16 - aad.length % 16) % 16;
51
74
  if (aadPad > 0)
52
75
  polyFeed(x, new Uint8Array(aadPad));
53
- // Step 4: Re-init ChaCha20 at counter=1
76
+ // Step 4: counter=1 (state intact from chachaGenPolyKey; no reload needed).
54
77
  x.chachaSetCounter(1);
55
- x.chachaLoadKey();
56
78
  // Step 5: Encrypt
57
79
  mem.set(plaintext, x.getChunkPtOffset());
58
80
  x.chachaEncryptChunk_simd(plaintext.length);
@@ -71,11 +93,22 @@ export function aeadEncrypt(x, key, nonce, plaintext, aad) {
71
93
  const tag = new Uint8Array(x.memory.buffer).slice(tagOff, tagOff + 16);
72
94
  return { ciphertext, tag };
73
95
  }
74
- /** ChaCha20-Poly1305 AEAD decrypt (RFC 8439 §2.8). Constant-time tag comparison. */
96
+ /**
97
+ * ChaCha20-Poly1305 AEAD decrypt with constant-time tag comparison (RFC 8439 §2.8).
98
+ * Throws `AuthenticationError` on tag mismatch; never returns plaintext on failure.
99
+ * @param x ChaCha20 WASM exports
100
+ * @param key 32-byte key
101
+ * @param nonce 12-byte nonce, must match the value used to encrypt
102
+ * @param ciphertext Ciphertext bytes (must be ≤ WASM CHUNK_SIZE)
103
+ * @param tag 16-byte Poly1305 tag
104
+ * @param aad Additional authenticated data
105
+ * @param cipherName Error label for `AuthenticationError` (default 'chacha20-poly1305')
106
+ * @returns Plaintext
107
+ */
75
108
  export function aeadDecrypt(x, key, nonce, ciphertext, tag, aad, cipherName = 'chacha20-poly1305') {
76
109
  const maxChunk = x.getChunkSize();
77
110
  if (ciphertext.length > maxChunk)
78
- throw new RangeError(`ciphertext exceeds ${maxChunk} bytes split into smaller chunks`);
111
+ throw new RangeError(`ciphertext exceeds ${maxChunk} bytes, split into smaller chunks`);
79
112
  const mem = new Uint8Array(x.memory.buffer);
80
113
  // Compute expected tag
81
114
  mem.set(key, x.getKeyOffset());
@@ -97,9 +130,14 @@ export function aeadDecrypt(x, key, nonce, ciphertext, tag, aad, cipherName = 'c
97
130
  const tagOff = x.getPolyTagOffset();
98
131
  const expectedTag = new Uint8Array(x.memory.buffer).slice(tagOff, tagOff + 16);
99
132
  if (!constantTimeEqual(expectedTag, tag)) {
100
- // Wipe the full chunk output buffer defense-in-depth before throwing
133
+ // Defense-in-depth wipe on auth fail: chunk ct, chacha block (keystream),
134
+ // Poly1305 one-time subkey copy at POLY_KEY_OFFSET.
101
135
  const ctOff = x.getChunkCtOffset();
102
136
  mem.fill(0, ctOff, ctOff + maxChunk);
137
+ const blockOff = x.getChachaBlockOffset();
138
+ mem.fill(0, blockOff, blockOff + 64);
139
+ const polyKeyOff = x.getPolyKeyOffset();
140
+ mem.fill(0, polyKeyOff, polyKeyOff + 32);
103
141
  throw new AuthenticationError(cipherName);
104
142
  }
105
143
  // Decrypt only after authentication succeeds
@@ -110,8 +148,15 @@ export function aeadDecrypt(x, key, nonce, ciphertext, tag, aad, cipherName = 'c
110
148
  const ptOff = x.getChunkCtOffset();
111
149
  return new Uint8Array(x.memory.buffer).slice(ptOff, ptOff + ciphertext.length);
112
150
  }
113
- // ── XChaCha20 helpers ────────────────────────────────────────────────────────
114
- /** HChaCha20 subkey derivation — first 16 bytes of nonce. */
151
+ // ── XChaCha20 helpers ───────────────────────────────────────────────────────
152
+ /**
153
+ * Derive a 32-byte HChaCha20 subkey from `key` and the first 16 bytes of `nonce`.
154
+ * Used as the inner key for XChaCha20-Poly1305 (draft-irtf-cfrg-xchacha §2.3).
155
+ * @param x ChaCha20 WASM exports
156
+ * @param key 32-byte master key
157
+ * @param nonce 24-byte XChaCha20 nonce (only bytes 0-15 are used)
158
+ * @returns 32-byte HChaCha20 subkey
159
+ */
115
160
  export function deriveSubkey(x, key, nonce) {
116
161
  const mem = new Uint8Array(x.memory.buffer);
117
162
  mem.set(key, x.getKeyOffset());
@@ -120,28 +165,63 @@ export function deriveSubkey(x, key, nonce) {
120
165
  const off = x.getXChaChaSubkeyOffset();
121
166
  return new Uint8Array(x.memory.buffer).slice(off, off + 32);
122
167
  }
123
- /** Build inner 12-byte nonce from bytes 16–23 of XChaCha nonce. */
168
+ /**
169
+ * Build the inner 12-byte ChaCha20 nonce for XChaCha20 from bytes 16-23 of the
170
+ * 24-byte XChaCha nonce (draft-irtf-cfrg-xchacha §2.3).
171
+ * @param nonce 24-byte XChaCha20 nonce
172
+ * @returns 12-byte inner nonce (bytes 0-3 are zero, bytes 4-11 are nonce[16:24])
173
+ */
124
174
  export function innerNonce(nonce) {
125
175
  const n = new Uint8Array(12);
126
176
  n.set(nonce.subarray(16, 24), 4);
127
177
  return n;
128
178
  }
129
- // ── Full XChaCha20-Poly1305 ──────────────────────────────────────────────────
130
- /** XChaCha20-Poly1305 encrypt → ciphertext || tag. */
179
+ // ── Full XChaCha20-Poly1305 ─────────────────────────────────────────────────
180
+ /**
181
+ * XChaCha20-Poly1305 encrypt (draft-irtf-cfrg-xchacha).
182
+ * Derives HChaCha20 subkey from `key` + nonce[0:16], then runs
183
+ * ChaCha20-Poly1305 with a 12-byte inner nonce (nonce[16:24]).
184
+ * @param x ChaCha20 WASM exports
185
+ * @param key 32-byte key
186
+ * @param nonce 24-byte nonce
187
+ * @param plaintext Data to encrypt
188
+ * @param aad Additional authenticated data
189
+ * @returns Ciphertext || 16-byte Poly1305 tag
190
+ */
131
191
  export function xcEncrypt(x, key, nonce, plaintext, aad) {
132
192
  const subkey = deriveSubkey(x, key, nonce);
133
- const inner = innerNonce(nonce);
134
- const { ciphertext, tag } = aeadEncrypt(x, subkey, inner, plaintext, aad);
135
- const result = new Uint8Array(ciphertext.length + 16);
136
- result.set(ciphertext);
137
- result.set(tag, ciphertext.length);
138
- return result;
193
+ try {
194
+ const inner = innerNonce(nonce);
195
+ const { ciphertext, tag } = aeadEncrypt(x, subkey, inner, plaintext, aad);
196
+ const result = new Uint8Array(ciphertext.length + 16);
197
+ result.set(ciphertext);
198
+ result.set(tag, ciphertext.length);
199
+ return result;
200
+ }
201
+ finally {
202
+ wipe(subkey);
203
+ }
139
204
  }
140
- /** XChaCha20-Poly1305 decrypt → plaintext (throws on auth failure). */
205
+ /**
206
+ * XChaCha20-Poly1305 decrypt (draft-irtf-cfrg-xchacha).
207
+ * Derives HChaCha20 subkey, verifies the Poly1305 tag, then decrypts.
208
+ * Throws `AuthenticationError` on tag mismatch.
209
+ * @param x ChaCha20 WASM exports
210
+ * @param key 32-byte key
211
+ * @param nonce 24-byte nonce, must match the value used to encrypt
212
+ * @param ciphertext Ciphertext || 16-byte tag (combined format from `xcEncrypt`)
213
+ * @param aad Additional authenticated data
214
+ * @returns Plaintext
215
+ */
141
216
  export function xcDecrypt(x, key, nonce, ciphertext, aad) {
142
217
  const ct = ciphertext.subarray(0, ciphertext.length - 16);
143
218
  const tag = ciphertext.subarray(ciphertext.length - 16);
144
219
  const subkey = deriveSubkey(x, key, nonce);
145
- const inner = innerNonce(nonce);
146
- return aeadDecrypt(x, subkey, inner, ct, tag, aad, 'xchacha20-poly1305');
220
+ try {
221
+ const inner = innerNonce(nonce);
222
+ return aeadDecrypt(x, subkey, inner, ct, tag, aad, 'xchacha20-poly1305');
223
+ }
224
+ finally {
225
+ wipe(subkey);
226
+ }
147
227
  }
@@ -8,6 +8,17 @@ import { aeadEncrypt, aeadDecrypt } from './ops.js';
8
8
  import { AuthenticationError } from '../errors.js';
9
9
  let x;
10
10
  let subkey;
11
+ /**
12
+ * Message handler for the XChaCha20 pool worker.
13
+ *
14
+ * Accepts three message types:
15
+ * - `'init'` , instantiate the chacha20 WASM module and store the derived subkey
16
+ * - `'wipe'` , zero subkey and WASM buffers, then post `{ type: 'wiped' }`
17
+ * - `{ op: 'seal' | 'open', ... }`, encrypt or decrypt one chunk
18
+ *
19
+ * Replies with `{ type: 'result', id, data }` on success or
20
+ * `{ type: 'error', id, message, isAuthError }` on failure.
21
+ */
11
22
  self.onmessage = async (e) => {
12
23
  const msg = e.data;
13
24
  if (msg.type === 'init') {
@@ -23,6 +34,8 @@ self.onmessage = async (e) => {
23
34
  self.postMessage({ type: 'ready' });
24
35
  }
25
36
  catch (err) {
37
+ if (msg.derivedKeyBytes)
38
+ msg.derivedKeyBytes.fill(0);
26
39
  self.postMessage({ type: 'error', id: -1, message: err.message, isAuthError: false });
27
40
  }
28
41
  return;
@@ -34,6 +47,7 @@ self.onmessage = async (e) => {
34
47
  if (x)
35
48
  x.wipeBuffers();
36
49
  x = undefined;
50
+ self.postMessage({ type: 'wiped' });
37
51
  return;
38
52
  }
39
53
  if (!x || !subkey) {
@@ -1,32 +1 @@
1
- /** WASM exports for the chacha module */
2
- export interface ChaChaExports {
3
- memory: WebAssembly.Memory;
4
- getModuleId(): number;
5
- getKeyOffset(): number;
6
- getChachaNonceOffset(): number;
7
- getChachaCtrOffset(): number;
8
- getChachaBlockOffset(): number;
9
- getChachaStateOffset(): number;
10
- getChunkPtOffset(): number;
11
- getChunkCtOffset(): number;
12
- getChunkSize(): number;
13
- getPolyKeyOffset(): number;
14
- getPolyMsgOffset(): number;
15
- getPolyTagOffset(): number;
16
- getPolyBufLenOffset(): number;
17
- getXChaChaNonceOffset(): number;
18
- getXChaChaSubkeyOffset(): number;
19
- chachaLoadKey(): void;
20
- chachaSetCounter(n: number): void;
21
- chachaResetCounter(): void;
22
- chachaEncryptChunk(n: number): number;
23
- chachaDecryptChunk(n: number): number;
24
- chachaEncryptChunk_simd(n: number): number;
25
- chachaDecryptChunk_simd(n: number): number;
26
- chachaGenPolyKey(): void;
27
- hchacha20(): void;
28
- polyInit(): void;
29
- polyUpdate(n: number): void;
30
- polyFinal(): void;
31
- wipeBuffers(): void;
32
- }
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const CTE_WASM: Uint8Array<ArrayBuffer>;
@@ -0,0 +1,3 @@
1
+ // auto-generated, do not edit
2
+ // raw WASM bytes for constant-time equality comparison module
3
+ export const CTE_WASM = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 8, 1, 96, 3, 127, 127, 127, 1, 127, 3, 2, 1, 0, 5, 4, 1, 1, 1, 1, 7, 20, 2, 7, 99, 111, 109, 112, 97, 114, 101, 0, 0, 6, 109, 101, 109, 111, 114, 121, 2, 0, 10, 133, 1, 1, 130, 1, 3, 2, 127, 1, 126, 1, 123, 3, 64, 32, 3, 65, 16, 106, 34, 4, 32, 2, 76, 4, 64, 32, 6, 32, 0, 32, 3, 106, 253, 0, 4, 0, 32, 1, 32, 3, 106, 253, 0, 4, 0, 253, 81, 253, 80, 33, 6, 32, 4, 33, 3, 12, 1, 11, 11, 3, 64, 32, 2, 32, 3, 74, 4, 64, 32, 5, 32, 0, 32, 3, 106, 49, 0, 0, 32, 1, 32, 3, 106, 49, 0, 0, 133, 132, 33, 5, 32, 3, 65, 1, 106, 33, 3, 12, 1, 11, 11, 66, 0, 32, 5, 32, 6, 253, 29, 0, 32, 6, 253, 29, 1, 132, 132, 34, 5, 125, 32, 5, 132, 66, 63, 135, 66, 127, 133, 167, 65, 1, 113, 11]);
package/dist/cte.wasm ADDED
Binary file
Binary file
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Convert a 64-byte raw r || s signature to DER per RFC 3279 §2.2.3.
3
+ * Output length is variable: 8 bytes minimum (r = s = 1 byte each, no
4
+ * sign-pad), 72 bytes maximum (both components 32 bytes with high bit
5
+ * set, each picking up a 0x00 sign-pad).
6
+ *
7
+ * @throws TypeError if `sig` is not a Uint8Array
8
+ * @throws RangeError if `sig.length !== 64`
9
+ */
10
+ export declare function ecdsaSignatureToDer(sig: Uint8Array): Uint8Array;
11
+ /**
12
+ * Convert a DER ECDSA-P256 signature to 64-byte raw r || s. Rejects
13
+ * any DER syntax violation via SigningError('sig-malformed-input'):
14
+ * see the file-level header for the rejection rules.
15
+ *
16
+ * Semantic value rejections (r = 0, s = 0, high-s, off-range) are
17
+ * deferred to the WASM verify path; this function only enforces DER
18
+ * structure.
19
+ *
20
+ * @throws TypeError if `der` is not a Uint8Array
21
+ * @throws SigningError('sig-malformed-input') on any DER syntax error
22
+ */
23
+ export declare function ecdsaSignatureFromDer(der: Uint8Array): Uint8Array;
@@ -0,0 +1,192 @@
1
+ // ▄▄▄▄▄▄▄▄▄▄
2
+ // ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
3
+ // ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
4
+ // ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
5
+ // ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
6
+ // ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
7
+ // ███████▌ ▀██▀ ███
8
+ // ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
9
+ // ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
10
+ // ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
11
+ // ▀████▄ ▄██▄
12
+ // ▐████ ▐███ Author: xero (https://x-e.ro)
13
+ // ▄▄██████████ ▐███ ▄▄ License: MIT
14
+ // ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
15
+ // ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
16
+ // ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
17
+ // ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
18
+ // █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
19
+ // ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
20
+ // ▀█████▀▀
21
+ //
22
+ // src/ts/ecdsa/der.ts
23
+ //
24
+ // ECDSA signature DER ↔ raw r||s conversion utility, RFC 3279 §2.2.3,
25
+ // ECDSA Signature Algorithm.
26
+ //
27
+ // Ecdsa-Sig-Value ::= SEQUENCE {
28
+ // r INTEGER,
29
+ // s INTEGER
30
+ // }
31
+ //
32
+ // The library's WASM ABI consumes and produces raw 64-byte r || s
33
+ // signatures. DER is a side utility for callers who need X.509 / JWS /
34
+ // TLS interop. The encoder / decoder is hand-rolled against X.690 §8.3
35
+ // (INTEGER) and §8.9 (SEQUENCE); leviathan-crypto is zero-dependency
36
+ // so no external ASN.1 parser is used.
37
+ //
38
+ // Strict DER (not BER). The decoder rejects:
39
+ // - non-minimal length encodings (long-form length when short-form
40
+ // would suffice; X.690 §10.1, definite-length encoding)
41
+ // - excess leading-zero bytes inside INTEGER content (X.690 §8.3.2)
42
+ // - negative INTEGERs (sign bit set on the leading content octet
43
+ // without a 0x00 sign-pad; ECDSA r, s ∈ [1, n-1] are positive)
44
+ // - INTEGER content longer than 33 bytes (32-byte BE scalar plus an
45
+ // optional 0x00 sign-pad)
46
+ // - trailing bytes after the outer SEQUENCE
47
+ // - wrong tags (outer 0x30 SEQUENCE, inner 0x02 INTEGER)
48
+ //
49
+ // The from-DER path never throws on a semantic value problem
50
+ // (r = 0, s = 0, high-s, off-range); those are verify-time rejections
51
+ // in the WASM. Only DER syntax violations throw.
52
+ import { SigningError } from '../errors.js';
53
+ const SEQUENCE_TAG = 0x30;
54
+ const INTEGER_TAG = 0x02;
55
+ function reject(detail) {
56
+ throw new SigningError('sig-malformed-input', `leviathan-crypto: ecdsa-p256 DER signature ${detail}`);
57
+ }
58
+ /**
59
+ * Encode a positive 32-byte BE scalar (r or s component) as a DER
60
+ * INTEGER TLV. Strips leading zero bytes to the minimal encoding per
61
+ * X.690 §8.3.2, then prepends a single 0x00 if the high bit of the
62
+ * resulting first content byte is set, so the INTEGER remains positive.
63
+ *
64
+ * The component length is at most 33 bytes (32-byte scalar + optional
65
+ * sign-pad), so the DER length octet always fits in short-form.
66
+ */
67
+ function encodeInteger(scalarBE) {
68
+ let start = 0;
69
+ while (start < scalarBE.length - 1 && scalarBE[start] === 0)
70
+ start++;
71
+ const stripped = scalarBE.subarray(start);
72
+ const needsPad = (stripped[0] & 0x80) !== 0;
73
+ const contentLen = stripped.length + (needsPad ? 1 : 0);
74
+ const out = new Uint8Array(2 + contentLen);
75
+ out[0] = INTEGER_TAG;
76
+ out[1] = contentLen;
77
+ if (needsPad) {
78
+ out[2] = 0;
79
+ out.set(stripped, 3);
80
+ }
81
+ else {
82
+ out.set(stripped, 2);
83
+ }
84
+ return out;
85
+ }
86
+ /**
87
+ * Decode one DER INTEGER TLV starting at `start`. Returns the strict-DER
88
+ * minimal content bytes (with any leading 0x00 sign-pad stripped) and
89
+ * the offset immediately past the INTEGER. Throws SigningError on any
90
+ * DER syntax violation.
91
+ */
92
+ function decodeInteger(der, start) {
93
+ if (start + 2 > der.length)
94
+ reject(`has a truncated INTEGER header at offset ${start}`);
95
+ if (der[start] !== INTEGER_TAG)
96
+ reject(`INTEGER tag at offset ${start} is 0x${der[start].toString(16).padStart(2, '0')}, expected 0x02`);
97
+ const lenByte = der[start + 1];
98
+ // Strict DER: ECDSA-P256 INTEGER content is at most 33 bytes, so the
99
+ // length octet is always short-form (high bit clear, value 1..127).
100
+ if (lenByte & 0x80)
101
+ reject(`INTEGER at offset ${start} uses long-form length encoding (forbidden for content < 128 bytes)`);
102
+ // Zero-length INTEGER content has no representable value; ASN.1
103
+ // requires at least one content octet (X.690 §8.3.1).
104
+ if (lenByte === 0)
105
+ reject(`INTEGER at offset ${start} has zero-length content`);
106
+ const contentStart = start + 2;
107
+ const contentEnd = contentStart + lenByte;
108
+ if (contentEnd > der.length)
109
+ reject(`INTEGER at offset ${start} extends past the outer SEQUENCE end`);
110
+ const content = der.subarray(contentStart, contentEnd);
111
+ // Minimal encoding: a leading 0x00 octet is permitted only when the
112
+ // next byte's high bit is set (sign-pad). Otherwise the 0x00 is
113
+ // excess per X.690 §8.3.2.
114
+ if (content[0] === 0x00 && content.length > 1 && (content[1] & 0x80) === 0)
115
+ reject(`INTEGER at offset ${start} has excess leading zero byte (non-minimal DER)`);
116
+ // ECDSA r, s ∈ [1, n-1] are positive integers. A first content
117
+ // octet with the high bit set means the ASN.1 INTEGER decodes as a
118
+ // negative two's-complement value; reject.
119
+ if ((content[0] & 0x80) !== 0)
120
+ reject(`INTEGER at offset ${start} is negative (high bit set on first content byte); ECDSA r, s are positive`);
121
+ const value = (content[0] === 0x00) ? content.subarray(1) : content;
122
+ return { value, next: contentEnd };
123
+ }
124
+ /**
125
+ * Convert a 64-byte raw r || s signature to DER per RFC 3279 §2.2.3.
126
+ * Output length is variable: 8 bytes minimum (r = s = 1 byte each, no
127
+ * sign-pad), 72 bytes maximum (both components 32 bytes with high bit
128
+ * set, each picking up a 0x00 sign-pad).
129
+ *
130
+ * @throws TypeError if `sig` is not a Uint8Array
131
+ * @throws RangeError if `sig.length !== 64`
132
+ */
133
+ export function ecdsaSignatureToDer(sig) {
134
+ if (!(sig instanceof Uint8Array))
135
+ throw new TypeError('leviathan-crypto: ecdsa-p256 raw signature must be a Uint8Array');
136
+ if (sig.length !== 64)
137
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 raw signature must be 64 bytes r||s (got ${sig.length})`);
138
+ const r = encodeInteger(sig.subarray(0, 32));
139
+ const s = encodeInteger(sig.subarray(32, 64));
140
+ const contentLen = r.length + s.length;
141
+ // SEQUENCE content is at most 2 * 35 = 70 bytes, so the SEQUENCE
142
+ // length octet is always short-form.
143
+ const out = new Uint8Array(2 + contentLen);
144
+ out[0] = SEQUENCE_TAG;
145
+ out[1] = contentLen;
146
+ out.set(r, 2);
147
+ out.set(s, 2 + r.length);
148
+ return out;
149
+ }
150
+ /**
151
+ * Convert a DER ECDSA-P256 signature to 64-byte raw r || s. Rejects
152
+ * any DER syntax violation via SigningError('sig-malformed-input'):
153
+ * see the file-level header for the rejection rules.
154
+ *
155
+ * Semantic value rejections (r = 0, s = 0, high-s, off-range) are
156
+ * deferred to the WASM verify path; this function only enforces DER
157
+ * structure.
158
+ *
159
+ * @throws TypeError if `der` is not a Uint8Array
160
+ * @throws SigningError('sig-malformed-input') on any DER syntax error
161
+ */
162
+ export function ecdsaSignatureFromDer(der) {
163
+ if (!(der instanceof Uint8Array))
164
+ throw new TypeError('leviathan-crypto: ecdsa-p256 DER signature must be a Uint8Array');
165
+ // 8 bytes is the absolute minimum: SEQUENCE(2) + INTEGER(0x01 0x01)
166
+ // + INTEGER(0x01 0x01) = 8. Anything shorter cannot represent two
167
+ // non-empty INTEGER components.
168
+ if (der.length < 8)
169
+ reject(`is shorter than the 8-byte minimum (got ${der.length} bytes)`);
170
+ if (der[0] !== SEQUENCE_TAG)
171
+ reject(`outer tag is 0x${der[0].toString(16).padStart(2, '0')}, expected 0x30 (SEQUENCE)`);
172
+ const seqLen = der[1];
173
+ // ECDSA-P256 SEQUENCE content is at most 70 bytes; strict DER
174
+ // requires short-form length encoding when < 128.
175
+ if (seqLen & 0x80)
176
+ reject('uses long-form length encoding for the outer SEQUENCE (forbidden for content < 128 bytes)');
177
+ if (2 + seqLen !== der.length)
178
+ reject(`outer SEQUENCE length ${seqLen} does not match input size (${der.length} bytes total)`);
179
+ const { value: r, next: afterR } = decodeInteger(der, 2);
180
+ const { value: s, next: end } = decodeInteger(der, afterR);
181
+ if (end !== der.length)
182
+ reject('has trailing bytes after the second INTEGER');
183
+ // Each component must fit in 32 bytes BE (P-256 scalar size).
184
+ if (r.length > 32)
185
+ reject(`r component is ${r.length} bytes, exceeds the 32-byte scalar size`);
186
+ if (s.length > 32)
187
+ reject(`s component is ${s.length} bytes, exceeds the 32-byte scalar size`);
188
+ const out = new Uint8Array(64);
189
+ out.set(r, 32 - r.length);
190
+ out.set(s, 64 - s.length);
191
+ return out;
192
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Encode a 32-byte P-256 secret scalar as DER `ECPrivateKey` per
3
+ * RFC 5915 §3. Output length is exactly 51 bytes; version is 1, the
4
+ * named-curve OID for secp256r1 (`1.2.840.10045.3.1.7`,
5
+ * SP 800-186 §3.2.1.3) is included in `parameters [0]`, and the
6
+ * `publicKey [1]` field is omitted.
7
+ *
8
+ * Byte-stable: same input scalar produces byte-identical output. No
9
+ * canonicalisation pass needed because every field has a single legal
10
+ * DER encoding under the strict-DER rules of X.690 §10.
11
+ *
12
+ * @throws TypeError if `scalar` is not a Uint8Array
13
+ * @throws RangeError if `scalar.length !== 32`
14
+ */
15
+ export declare function encodeEcPrivateKey(scalar: Uint8Array): Uint8Array;
16
+ /**
17
+ * Decode a DER `ECPrivateKey` (RFC 5915 §3) and return the 32-byte
18
+ * raw P-256 secret scalar.
19
+ *
20
+ * Strict-DER per X.690 §10 (Restrictions on the BER). Rejects the
21
+ * cases enumerated in the file-level header. Accepts (and ignores)
22
+ * an optional `publicKey [1]` field per RFC 5915 §3 lenient input;
23
+ * the scalar is the only return value.
24
+ *
25
+ * Any curve OID inside `parameters [0]` other than secp256r1
26
+ * (`1.2.840.10045.3.1.7`) is rejected. Decoders that need other
27
+ * curves must use a different parser; this library is P-256 only.
28
+ *
29
+ * @throws TypeError if `der` is not a Uint8Array
30
+ * @throws Error on any DER syntax violation or unsupported parameter
31
+ */
32
+ export declare function decodeEcPrivateKey(der: Uint8Array): Uint8Array;