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.
Files changed (296) hide show
  1. package/CLAUDE.md +86 -443
  2. package/README.md +198 -65
  3. package/dist/aes/aes-cbc.d.ts +40 -0
  4. package/dist/aes/aes-cbc.js +158 -0
  5. package/dist/aes/aes-ctr.d.ts +50 -0
  6. package/dist/aes/aes-ctr.js +141 -0
  7. package/dist/aes/aes-gcm-siv.d.ts +67 -0
  8. package/dist/aes/aes-gcm-siv.js +217 -0
  9. package/dist/aes/aes-gcm.d.ts +61 -0
  10. package/dist/aes/aes-gcm.js +226 -0
  11. package/dist/aes/cipher-suite.d.ts +21 -0
  12. package/dist/aes/cipher-suite.js +179 -0
  13. package/dist/aes/embedded.d.ts +1 -0
  14. package/dist/aes/embedded.js +26 -0
  15. package/dist/aes/generator.d.ts +14 -0
  16. package/dist/aes/generator.js +103 -0
  17. package/dist/aes/index.d.ts +58 -0
  18. package/dist/aes/index.js +125 -0
  19. package/dist/aes/ops.d.ts +60 -0
  20. package/dist/aes/ops.js +164 -0
  21. package/dist/aes/pool-worker.d.ts +1 -0
  22. package/dist/aes/pool-worker.js +92 -0
  23. package/dist/aes/types.d.ts +1 -0
  24. package/dist/aes/types.js +23 -0
  25. package/dist/aes.wasm +0 -0
  26. package/dist/blake3/embedded.d.ts +1 -0
  27. package/dist/blake3/embedded.js +26 -0
  28. package/dist/blake3/index.d.ts +143 -0
  29. package/dist/blake3/index.js +620 -0
  30. package/dist/blake3/types.d.ts +102 -0
  31. package/dist/blake3/types.js +31 -0
  32. package/dist/blake3/validate.d.ts +29 -0
  33. package/dist/blake3/validate.js +80 -0
  34. package/dist/blake3.wasm +0 -0
  35. package/dist/chacha20/cipher-suite.js +47 -25
  36. package/dist/chacha20/generator.d.ts +2 -2
  37. package/dist/chacha20/generator.js +4 -4
  38. package/dist/chacha20/index.d.ts +16 -15
  39. package/dist/chacha20/index.js +52 -46
  40. package/dist/chacha20/ops.d.ts +7 -7
  41. package/dist/chacha20/ops.js +34 -34
  42. package/dist/chacha20/pool-worker.js +5 -3
  43. package/dist/cte-wasm.d.ts +1 -0
  44. package/dist/cte-wasm.js +3 -0
  45. package/dist/curve25519.wasm +0 -0
  46. package/dist/ecdsa/der.d.ts +23 -0
  47. package/dist/ecdsa/der.js +192 -0
  48. package/dist/ecdsa/ecprivatekey-der.d.ts +32 -0
  49. package/dist/ecdsa/ecprivatekey-der.js +230 -0
  50. package/dist/ecdsa/embedded.d.ts +1 -0
  51. package/dist/ecdsa/embedded.js +25 -0
  52. package/dist/ecdsa/index.d.ts +124 -0
  53. package/dist/ecdsa/index.js +366 -0
  54. package/dist/ecdsa/types.d.ts +31 -0
  55. package/dist/ecdsa/types.js +28 -0
  56. package/dist/ecdsa/validate.d.ts +18 -0
  57. package/dist/ecdsa/validate.js +92 -0
  58. package/dist/ed25519/embedded.d.ts +1 -0
  59. package/dist/ed25519/embedded.js +31 -0
  60. package/dist/ed25519/index.d.ts +70 -0
  61. package/dist/ed25519/index.js +308 -0
  62. package/dist/ed25519/types.d.ts +27 -0
  63. package/dist/ed25519/types.js +27 -0
  64. package/dist/ed25519/validate.d.ts +7 -0
  65. package/dist/ed25519/validate.js +77 -0
  66. package/dist/embedded/aes-pool-worker.d.ts +1 -0
  67. package/dist/embedded/aes-pool-worker.js +5 -0
  68. package/dist/embedded/aes.d.ts +1 -0
  69. package/dist/embedded/aes.js +3 -0
  70. package/dist/embedded/blake3.d.ts +1 -0
  71. package/dist/embedded/blake3.js +3 -0
  72. package/dist/embedded/chacha20-pool-worker.d.ts +1 -1
  73. package/dist/embedded/chacha20-pool-worker.js +2 -2
  74. package/dist/embedded/chacha20.d.ts +1 -1
  75. package/dist/embedded/chacha20.js +2 -2
  76. package/dist/embedded/curve25519.d.ts +1 -0
  77. package/dist/embedded/curve25519.js +3 -0
  78. package/dist/embedded/mldsa.d.ts +1 -0
  79. package/dist/embedded/mldsa.js +3 -0
  80. package/dist/embedded/mlkem.d.ts +1 -0
  81. package/dist/embedded/mlkem.js +3 -0
  82. package/dist/embedded/p256.d.ts +1 -0
  83. package/dist/embedded/p256.js +3 -0
  84. package/dist/embedded/serpent-pool-worker.d.ts +1 -1
  85. package/dist/embedded/serpent-pool-worker.js +2 -2
  86. package/dist/embedded/serpent.d.ts +1 -1
  87. package/dist/embedded/serpent.js +2 -2
  88. package/dist/embedded/sha2.d.ts +1 -1
  89. package/dist/embedded/sha2.js +2 -2
  90. package/dist/embedded/sha3.d.ts +1 -1
  91. package/dist/embedded/sha3.js +2 -2
  92. package/dist/embedded/slhdsa.d.ts +1 -0
  93. package/dist/embedded/slhdsa.js +3 -0
  94. package/dist/errors.d.ts +92 -1
  95. package/dist/errors.js +111 -1
  96. package/dist/fortuna.d.ts +5 -5
  97. package/dist/fortuna.js +37 -64
  98. package/dist/index.d.ts +38 -9
  99. package/dist/index.js +63 -19
  100. package/dist/init.d.ts +1 -1
  101. package/dist/init.js +11 -25
  102. package/dist/keccak/embedded.js +1 -1
  103. package/dist/keccak/index.d.ts +2 -0
  104. package/dist/keccak/index.js +4 -2
  105. package/dist/loader.d.ts +1 -24
  106. package/dist/loader.js +13 -16
  107. package/dist/merkle/blake3-tree.d.ts +35 -0
  108. package/dist/merkle/blake3-tree.js +187 -0
  109. package/dist/merkle/checkpoint.d.ts +58 -0
  110. package/dist/merkle/checkpoint.js +217 -0
  111. package/dist/merkle/index.d.ts +19 -0
  112. package/dist/merkle/index.js +37 -0
  113. package/dist/merkle/merkle-log.d.ts +130 -0
  114. package/dist/merkle/merkle-log.js +207 -0
  115. package/dist/merkle/merkle-verifier.d.ts +126 -0
  116. package/dist/merkle/merkle-verifier.js +296 -0
  117. package/dist/merkle/proof.d.ts +70 -0
  118. package/dist/merkle/proof.js +300 -0
  119. package/dist/merkle/sha256-tree.d.ts +33 -0
  120. package/dist/merkle/sha256-tree.js +145 -0
  121. package/dist/merkle/signed-log.d.ts +156 -0
  122. package/dist/merkle/signed-log.js +356 -0
  123. package/dist/merkle/signed-note.d.ts +309 -0
  124. package/dist/merkle/signed-note.js +648 -0
  125. package/dist/merkle/sth.d.ts +31 -0
  126. package/dist/merkle/sth.js +31 -0
  127. package/dist/merkle/storage.d.ts +40 -0
  128. package/dist/merkle/storage.js +71 -0
  129. package/dist/merkle/tree.d.ts +68 -0
  130. package/dist/merkle/tree.js +94 -0
  131. package/dist/mldsa/embedded.d.ts +1 -0
  132. package/dist/{kyber → mldsa}/embedded.js +5 -5
  133. package/dist/mldsa/expand.d.ts +53 -0
  134. package/dist/mldsa/expand.js +188 -0
  135. package/dist/mldsa/format.d.ts +16 -0
  136. package/dist/mldsa/format.js +68 -0
  137. package/dist/mldsa/hashvariant.d.ts +32 -0
  138. package/dist/mldsa/hashvariant.js +248 -0
  139. package/dist/mldsa/index.d.ts +142 -0
  140. package/dist/mldsa/index.js +463 -0
  141. package/dist/mldsa/keygen.d.ts +16 -0
  142. package/dist/mldsa/keygen.js +232 -0
  143. package/dist/mldsa/params.d.ts +21 -0
  144. package/dist/mldsa/params.js +55 -0
  145. package/dist/mldsa/sha3-helpers.d.ts +30 -0
  146. package/dist/mldsa/sha3-helpers.js +124 -0
  147. package/dist/mldsa/sign.d.ts +36 -0
  148. package/dist/mldsa/sign.js +380 -0
  149. package/dist/mldsa/types.d.ts +91 -0
  150. package/dist/mldsa/types.js +25 -0
  151. package/dist/mldsa/validate.d.ts +55 -0
  152. package/dist/mldsa/validate.js +125 -0
  153. package/dist/mldsa/verify.d.ts +29 -0
  154. package/dist/mldsa/verify.js +269 -0
  155. package/dist/mldsa.wasm +0 -0
  156. package/dist/mlkem/embedded.d.ts +1 -0
  157. package/dist/mlkem/embedded.js +27 -0
  158. package/dist/mlkem/indcpa.d.ts +49 -0
  159. package/dist/{kyber → mlkem}/indcpa.js +44 -44
  160. package/dist/mlkem/index.d.ts +37 -0
  161. package/dist/{kyber → mlkem}/index.js +24 -34
  162. package/dist/mlkem/kem.d.ts +21 -0
  163. package/dist/{kyber → mlkem}/kem.js +44 -64
  164. package/dist/{kyber → mlkem}/params.d.ts +4 -4
  165. package/dist/{kyber → mlkem}/params.js +2 -2
  166. package/dist/mlkem/suite.d.ts +12 -0
  167. package/dist/{kyber → mlkem}/suite.js +17 -12
  168. package/dist/{kyber → mlkem}/types.d.ts +3 -3
  169. package/dist/{kyber → mlkem}/types.js +1 -1
  170. package/dist/{kyber → mlkem}/validate.d.ts +7 -7
  171. package/dist/{kyber → mlkem}/validate.js +7 -7
  172. package/dist/{kyber.wasm → mlkem.wasm} +0 -0
  173. package/dist/p256.wasm +0 -0
  174. package/dist/ratchet/index.d.ts +2 -0
  175. package/dist/ratchet/index.js +1 -0
  176. package/dist/ratchet/kdf-chain.js +3 -3
  177. package/dist/ratchet/ratchet-keypair.js +2 -2
  178. package/dist/ratchet/root-kdf.js +7 -7
  179. package/dist/ratchet/skipped-key-store.js +4 -4
  180. package/dist/ratchet/types.d.ts +1 -1
  181. package/dist/serpent/cipher-suite.js +20 -17
  182. package/dist/serpent/generator.d.ts +1 -1
  183. package/dist/serpent/generator.js +2 -2
  184. package/dist/serpent/index.d.ts +8 -7
  185. package/dist/serpent/index.js +18 -27
  186. package/dist/serpent/pool-worker.js +7 -5
  187. package/dist/serpent/serpent-cbc.d.ts +4 -4
  188. package/dist/serpent/serpent-cbc.js +11 -8
  189. package/dist/serpent/shared-ops.d.ts +3 -23
  190. package/dist/serpent/shared-ops.js +50 -85
  191. package/dist/serpent.wasm +0 -0
  192. package/dist/sha2/hkdf.js +5 -5
  193. package/dist/sha2/index.d.ts +21 -1
  194. package/dist/sha2/index.js +65 -10
  195. package/dist/sha2/types.d.ts +41 -2
  196. package/dist/sha2.wasm +0 -0
  197. package/dist/sha3/index.d.ts +72 -3
  198. package/dist/sha3/index.js +240 -14
  199. package/dist/sha3/kmac.d.ts +121 -0
  200. package/dist/sha3/kmac.js +800 -0
  201. package/dist/sha3.wasm +0 -0
  202. package/dist/shared/pkcs7.d.ts +22 -0
  203. package/dist/shared/pkcs7.js +84 -0
  204. package/dist/sign/ctx.d.ts +41 -0
  205. package/dist/sign/ctx.js +102 -0
  206. package/dist/sign/envelope.d.ts +45 -0
  207. package/dist/sign/envelope.js +152 -0
  208. package/dist/sign/hasher.d.ts +9 -0
  209. package/dist/sign/hasher.js +132 -0
  210. package/dist/sign/index.d.ts +11 -0
  211. package/dist/sign/index.js +34 -0
  212. package/dist/sign/sign-stream.d.ts +25 -0
  213. package/dist/sign/sign-stream.js +112 -0
  214. package/dist/sign/suites/ecdsa-p256.d.ts +2 -0
  215. package/dist/sign/suites/ecdsa-p256.js +120 -0
  216. package/dist/sign/suites/ed25519.d.ts +3 -0
  217. package/dist/sign/suites/ed25519.js +165 -0
  218. package/dist/sign/suites/hybrid-classical.d.ts +23 -0
  219. package/dist/sign/suites/hybrid-classical.js +526 -0
  220. package/dist/sign/suites/hybrid-pq.d.ts +4 -0
  221. package/dist/sign/suites/hybrid-pq.js +234 -0
  222. package/dist/sign/suites/mldsa.d.ts +7 -0
  223. package/dist/sign/suites/mldsa.js +161 -0
  224. package/dist/sign/suites/slhdsa.d.ts +7 -0
  225. package/dist/sign/suites/slhdsa.js +176 -0
  226. package/dist/sign/types.d.ts +106 -0
  227. package/dist/sign/types.js +28 -0
  228. package/dist/sign/verify-stream.d.ts +30 -0
  229. package/dist/sign/verify-stream.js +227 -0
  230. package/dist/slhdsa/embedded.d.ts +1 -0
  231. package/dist/slhdsa/embedded.js +26 -0
  232. package/dist/slhdsa/index.d.ts +149 -0
  233. package/dist/slhdsa/index.js +493 -0
  234. package/dist/slhdsa/params.d.ts +26 -0
  235. package/dist/slhdsa/params.js +70 -0
  236. package/dist/slhdsa/prehash.d.ts +68 -0
  237. package/dist/slhdsa/prehash.js +307 -0
  238. package/dist/slhdsa/sign.d.ts +39 -0
  239. package/dist/slhdsa/sign.js +116 -0
  240. package/dist/slhdsa/types.d.ts +129 -0
  241. package/dist/slhdsa/types.js +27 -0
  242. package/dist/slhdsa/validate.d.ts +60 -0
  243. package/dist/slhdsa/validate.js +127 -0
  244. package/dist/slhdsa/verify.d.ts +32 -0
  245. package/dist/slhdsa/verify.js +107 -0
  246. package/dist/slhdsa.wasm +0 -0
  247. package/dist/stream/header.js +3 -3
  248. package/dist/stream/index.d.ts +1 -0
  249. package/dist/stream/index.js +1 -0
  250. package/dist/stream/open-stream.js +31 -10
  251. package/dist/stream/seal-stream-pool.d.ts +1 -0
  252. package/dist/stream/seal-stream-pool.js +63 -26
  253. package/dist/stream/seal-stream.d.ts +1 -1
  254. package/dist/stream/seal-stream.js +20 -9
  255. package/dist/stream/seal.js +6 -6
  256. package/dist/stream/types.d.ts +3 -1
  257. package/dist/stream/types.js +1 -1
  258. package/dist/types.d.ts +1 -1
  259. package/dist/types.js +1 -1
  260. package/dist/utils.d.ts +3 -3
  261. package/dist/utils.js +46 -54
  262. package/dist/wasm-source.d.ts +7 -7
  263. package/dist/wasm-source.js +1 -1
  264. package/dist/x25519/embedded.d.ts +1 -0
  265. package/dist/x25519/embedded.js +31 -0
  266. package/dist/x25519/index.d.ts +43 -0
  267. package/dist/x25519/index.js +159 -0
  268. package/dist/x25519/types.d.ts +25 -0
  269. package/dist/x25519/types.js +27 -0
  270. package/dist/x25519/validate.d.ts +2 -0
  271. package/dist/x25519/validate.js +39 -0
  272. package/package.json +70 -26
  273. package/SECURITY.md +0 -163
  274. package/dist/ct-wasm.d.ts +0 -1
  275. package/dist/ct-wasm.js +0 -3
  276. package/dist/docs/aead.md +0 -363
  277. package/dist/docs/architecture.md +0 -1011
  278. package/dist/docs/argon2id.md +0 -305
  279. package/dist/docs/chacha20.md +0 -781
  280. package/dist/docs/exports.md +0 -277
  281. package/dist/docs/fortuna.md +0 -530
  282. package/dist/docs/init.md +0 -301
  283. package/dist/docs/loader.md +0 -256
  284. package/dist/docs/serpent.md +0 -617
  285. package/dist/docs/sha2.md +0 -671
  286. package/dist/docs/sha3.md +0 -612
  287. package/dist/docs/types.md +0 -416
  288. package/dist/docs/utils.md +0 -457
  289. package/dist/embedded/kyber.d.ts +0 -1
  290. package/dist/embedded/kyber.js +0 -3
  291. package/dist/kyber/embedded.d.ts +0 -1
  292. package/dist/kyber/indcpa.d.ts +0 -49
  293. package/dist/kyber/index.d.ts +0 -38
  294. package/dist/kyber/kem.d.ts +0 -21
  295. package/dist/kyber/suite.d.ts +0 -12
  296. /package/dist/{ct.wasm → cte.wasm} +0 -0
@@ -0,0 +1,380 @@
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/mldsa/sign.ts
23
+ //
24
+ // FIPS 204 §6.2 Algorithm 7, ML-DSA.Sign_internal. Drives the
25
+ // rejection-sampling loop; wipe contract per §3.6.3 (see
26
+ // docs/mldsa.md#wipe-discipline).
27
+ //
28
+ // Slot allocation through one iteration body:
29
+ // POLYVEC_SLOT_0 ŝ₁ (NTT, persistent through loop)
30
+ // POLYVEC_SLOT_1 ŝ₂ (NTT, persistent)
31
+ // POLYVEC_SLOT_2 t̂₀ (NTT, persistent)
32
+ // POLYVEC_SLOT_3 y_time → ⟨cs₂⟩ → r₀ → ⟨ct₀⟩ → h
33
+ // POLYVEC_SLOT_4 y_ntt → w₁ → ⟨cs₁⟩ → z
34
+ // POLYVEC_SLOT_5 w_ntt → w_time → (w − ⟨cs₂⟩)
35
+ // POLY_SLOT_0 signs (sample_in_ball signsOff)
36
+ // POLY_SLOT_1 c, then ĉ (NTT-domain)
37
+ // POLY_SLOT_7 polyvec_pointwise_acc_montgomery scratch
38
+ //
39
+ // XOF/PRF carries ExpandMask, w1Encode, and SampleInBall bytes; each
40
+ // single-use per iteration.
41
+ import { wipe } from '../utils.js';
42
+ import { sha3Absorb, shake256HashConcat } from './sha3-helpers.js';
43
+ import { expandA, expandMask } from './expand.js';
44
+ import { getOid } from './hashvariant.js';
45
+ import { constructMPrimeHash } from './format.js';
46
+ const POLY_BYTES = 1024;
47
+ const D = 13;
48
+ const Q = 8380417;
49
+ const SHAKE256_RATE = 136;
50
+ // FIPS 204 Appendix C: expected 4-7 iterations, spec minimum bound 814.
51
+ // 1000 is a liveness bound, not a probability tail-cut.
52
+ const MAX_SIGN_ITERATIONS = 1000;
53
+ function bitlen(n) {
54
+ let b = 0;
55
+ let x = n;
56
+ while (x > 0) {
57
+ b++;
58
+ x >>>= 1;
59
+ }
60
+ return b;
61
+ }
62
+ /**
63
+ * ML-DSA.Sign_internal, FIPS 204 Algorithm 7.
64
+ *
65
+ * Inputs:
66
+ * sk , encoded signing key (skBytes per parameter set)
67
+ * MPrime, domain-separated message bytes (caller-built via constructMPrime)
68
+ * rnd , 32-byte randomness (random for hedged, all-zero for deterministic,
69
+ * caller-supplied for derand/CAVP)
70
+ *
71
+ * Output: σ (sigBytes), encoded per Algorithm 26 (sigEncode).
72
+ *
73
+ * Wipe contract: every WASM region that held a secret or secret-derived
74
+ * intermediate is zeroed before return. TS-side scratch (μ, ρ'', c̃, the
75
+ * w1 byte slice) wipes via try/finally even on early throw.
76
+ */
77
+ export function mldsaSignInternal(mx, sx, params, sk, MPrime, rnd) {
78
+ const { k, l, eta, tau, lambda, gamma1, gamma2, beta, omega, sigBytes } = params;
79
+ const lambdaOver4 = lambda >>> 2; // 32 / 48 / 64
80
+ const etaBitlen = bitlen(2 * eta); // 3 (η=2) or 4 (η=4)
81
+ const etaPolyBytes = (256 * etaBitlen) >> 3; // 96 or 128
82
+ const t0PolyBytes = (256 * D) >> 3; // 416
83
+ const t0LowEdge = (1 << (D - 1)) - 1; // 4095
84
+ const t0HighEdge = (1 << (D - 1)); // 4096
85
+ const c = 1 + bitlen(gamma1 - 1); // 18 or 20
86
+ const zPolyBytes = 32 * c; // 576 or 640
87
+ // w₁ field: m = (q − 1) / (2γ₂); width = bitlen(m − 1).
88
+ // γ₂=(q-1)/88 ⇒ m=44 ⇒ width=6; γ₂=(q-1)/32 ⇒ m=16 ⇒ width=4.
89
+ const w1Bitlen = bitlen(((Q - 1) / (2 * gamma2)) - 1);
90
+ const w1PolyBytes = (256 * w1Bitlen) >> 3;
91
+ const w1TotalBytes = k * w1PolyBytes;
92
+ // ── WASM offsets ─────────────────────────────────────────────────────
93
+ const matOff = mx.getMatrixSlot();
94
+ const slot0 = mx.getPolyvecSlot0(); // ŝ₁
95
+ const slot1 = mx.getPolyvecSlot1(); // ŝ₂
96
+ const slot2 = mx.getPolyvecSlot2(); // t̂₀
97
+ const slot3 = mx.getPolyvecSlot3(); // y_time / ⟨cs₂⟩ / r₀ / ⟨ct₀⟩ / h
98
+ const slot4 = mx.getPolyvecSlot4(); // y_ntt / w₁ / ⟨cs₁⟩ / z
99
+ const slot5 = mx.getPolyvecSlot5(); // w / w − ⟨cs₂⟩
100
+ const polySlotBase = mx.getPolySlotBase();
101
+ const polySlot0 = mx.getPolySlot0(); // signs (8 bytes)
102
+ const polySlot1 = mx.getPolySlot1(); // c → ĉ
103
+ const sigOff = mx.getSigOffset();
104
+ const xofOff = mx.getXofPrfOffset();
105
+ const seedOff = mx.getSeedOffset();
106
+ const trOff = mx.getTrOffset();
107
+ const skOff = mx.getSkOffset();
108
+ const cTildeOff = mx.getCTildeOffset();
109
+ const msgRepOff = mx.getMsgRepOffset();
110
+ const mlMem = new Uint8Array(mx.memory.buffer);
111
+ const sha3Mem = new Uint8Array(sx.memory.buffer);
112
+ const sha3OutOff = sx.getOutOffset();
113
+ // ── TS-side sensitive buffers, wiped in finally ─────────────────────
114
+ let mu;
115
+ let rhoPP;
116
+ let cTilde;
117
+ let w1Bytes;
118
+ try {
119
+ // ── skDecode, FIPS 204 §7.2 Algorithm 25 ────────────────────────
120
+ // sk = ρ ‖ K ‖ tr ‖ s₁ ‖ s₂ ‖ t₀ (offsets per Algorithm 24).
121
+ const rho = sk.subarray(0, 32);
122
+ const K = sk.subarray(32, 64);
123
+ const tr = sk.subarray(64, 128);
124
+ let off = 128;
125
+ // s₁, ℓ polynomials, BitUnpack(η, η) per Alg 25 line 4
126
+ for (let r = 0; r < l; r++) {
127
+ mlMem.set(sk.subarray(off, off + etaPolyBytes), xofOff);
128
+ mx.bit_unpack(slot0 + r * POLY_BYTES, xofOff, eta, eta);
129
+ off += etaPolyBytes;
130
+ }
131
+ // FIPS 204 §7.2 / Alg 25 line 5: s₁ coefficients must lie in [-η, η].
132
+ // bit_unpack at width bitlen(2η) overshoots that range for η ∈ {2,4}
133
+ // (η=2 yields [-5, 2]; η=4 yields [-11, 4]) when sk bytes are tampered
134
+ // or corrupted, reject before producing a wrong signature.
135
+ if (mx.polyvec_chknorm(slot0, eta + 1, l) !== 0)
136
+ throw new RangeError('leviathan-crypto: signing key s₁ coefficient out of [-η, η]');
137
+ // s₂, k polynomials, BitUnpack(η, η)
138
+ for (let r = 0; r < k; r++) {
139
+ mlMem.set(sk.subarray(off, off + etaPolyBytes), xofOff);
140
+ mx.bit_unpack(slot1 + r * POLY_BYTES, xofOff, eta, eta);
141
+ off += etaPolyBytes;
142
+ }
143
+ if (mx.polyvec_chknorm(slot1, eta + 1, k) !== 0)
144
+ throw new RangeError('leviathan-crypto: signing key s₂ coefficient out of [-η, η]');
145
+ // t₀, k polynomials, BitUnpack(2^(d-1)-1, 2^(d-1)), Alg 25 line 6
146
+ // bit_unpack(2^(d-1)-1, 2^(d-1)) decodes exactly to [-(2^(d-1)-1), 2^(d-1)]
147
+ // which matches the spec range, no caddq-style range check needed.
148
+ for (let r = 0; r < k; r++) {
149
+ mlMem.set(sk.subarray(off, off + t0PolyBytes), xofOff);
150
+ mx.bit_unpack(slot2 + r * POLY_BYTES, xofOff, t0LowEdge, t0HighEdge);
151
+ off += t0PolyBytes;
152
+ }
153
+ // ── FIPS 204 Alg 7 lines 2-5: ŝ₁ ← NTT(s₁); ŝ₂ ← NTT(s₂);
154
+ // t̂₀ ← NTT(t₀); Â ← ExpandA(ρ) ────
155
+ // Each NTT'd polyvec is then tomont'd so subsequent
156
+ // poly_pointwise_montgomery products with a regular-form ĉ yield
157
+ // regular (non-Montgomery) results, matches the keygen tomont
158
+ // pattern (src/ts/mldsa/keygen.ts step 4 (c)). Without tomont the
159
+ // post-invNTT values carry an extra R⁻¹ factor and ACVP fails.
160
+ mx.polyvec_ntt(slot0, l);
161
+ mx.polyvec_tomont(slot0, l);
162
+ mx.polyvec_ntt(slot1, k);
163
+ mx.polyvec_tomont(slot1, k);
164
+ mx.polyvec_ntt(slot2, k);
165
+ mx.polyvec_tomont(slot2, k);
166
+ expandA(mx, sx, params, rho, matOff);
167
+ // ── Line 6: μ ← H(BytesToBits(tr) ‖ M', 64) ───────────────────────
168
+ // Byte-oriented SHAKE wrapper: BytesToBits is a no-op (we absorb tr
169
+ // directly). ACVP gates byte-equality of μ implicitly through σ
170
+ // equality on the deterministic-rnd vectors.
171
+ mu = shake256HashConcat(sx, [tr, MPrime], 64);
172
+ // ── Line 7: ρ'' ← H(K ‖ rnd ‖ μ, 64) ──────────────────────────────
173
+ rhoPP = shake256HashConcat(sx, [K, rnd, mu], 64);
174
+ // ── Line 8: κ ← 0 ────────────────────────────────────────────────
175
+ let kappa = 0;
176
+ cTilde = new Uint8Array(lambdaOver4);
177
+ let success = false;
178
+ // ── Line 10-31: rejection-sampling loop ──────────────────────────
179
+ for (let iter = 0; iter < MAX_SIGN_ITERATIONS; iter++) {
180
+ // Line 11: y ← ExpandMask(ρ'', κ) at slot3 (time domain) ──────
181
+ expandMask(mx, sx, params, rhoPP, kappa, slot3);
182
+ // Line 12: w ← NTT⁻¹(Â · NTT(y))
183
+ // Copy y_time → slot4, NTT in place, matrix product → slot5,
184
+ // inverse NTT → time domain, caddq → canonical for HighBits.
185
+ mlMem.copyWithin(slot4, slot3, slot3 + l * POLY_BYTES);
186
+ mx.polyvec_ntt(slot4, l); // ŷ at slot4 (regular)
187
+ mx.polyvec_tomont(slot4, l); // ŷ ← ŷ·R so the matrix kernel's R⁻¹ leaves regular result
188
+ mx.polyvec_matrix_pointwise_montgomery(slot5, matOff, slot4, k, l);
189
+ mx.polyvec_invntt(slot5, k);
190
+ mx.polyvec_caddq(slot5, k); // w canonical at slot5
191
+ // Line 13: w₁ ← HighBits(w) at slot4 (overwrites ŷ, no longer needed)
192
+ mx.polyvec_highbits(slot4, slot5, k, gamma2);
193
+ // Line 14: c̃ ← H(μ ‖ w₁Encode(w₁), λ/4) ─────────────────────
194
+ // w₁Encode = Σ SimpleBitPack(w₁[i], (q-1)/(2γ₂) - 1), width per param set.
195
+ for (let r = 0; r < k; r++) {
196
+ mx.simple_bit_pack(xofOff + r * w1PolyBytes, slot4 + r * POLY_BYTES, w1Bitlen);
197
+ }
198
+ // w₁ from a rejected iteration is secret-derived (HighBits
199
+ // of NTT⁻¹(Â · NTT(y)); y came from ρ''). Wipe the prior
200
+ // slice before reslicing fresh bytes for this iteration.
201
+ if (w1Bytes)
202
+ wipe(w1Bytes);
203
+ w1Bytes = mlMem.slice(xofOff, xofOff + w1TotalBytes);
204
+ const cTildeNew = shake256HashConcat(sx, [mu, w1Bytes], lambdaOver4);
205
+ cTilde.set(cTildeNew);
206
+ // cTildeNew is public-derivable (c̃ ships inside σ) but TS-side
207
+ // hygiene matches the rest of the per-iteration scratch.
208
+ wipe(cTildeNew);
209
+ // Line 15: c ← SampleInBall(c̃) at polySlot1 (time domain) ──
210
+ // FIPS 204 Algorithm 29: SHAKE256(c̃) drives sign-bits (first
211
+ // 8 squeeze bytes) and position selection (subsequent bytes).
212
+ // Resumable kernel, squeeze further blocks until all τ samples
213
+ // land. polySlot0 is signs scratch; xofOff is position bytes.
214
+ mlMem.fill(0, polySlot1, polySlot1 + POLY_BYTES);
215
+ sx.shake256Init();
216
+ sha3Absorb(sx, cTilde);
217
+ sx.shakePad();
218
+ sx.shakeSqueezeBlock();
219
+ mlMem.set(sha3Mem.subarray(sha3OutOff, sha3OutOff + 8), polySlot0);
220
+ mlMem.set(sha3Mem.subarray(sha3OutOff + 8, sha3OutOff + SHAKE256_RATE), xofOff);
221
+ let sampleI = mx.sample_in_ball(polySlot1, polySlot0, xofOff, SHAKE256_RATE - 8, tau, 256 - tau);
222
+ while (sampleI < 256) {
223
+ sx.shakeSqueezeBlock();
224
+ mlMem.set(sha3Mem.subarray(sha3OutOff, sha3OutOff + SHAKE256_RATE), xofOff);
225
+ sampleI = mx.sample_in_ball(polySlot1, polySlot0, xofOff, SHAKE256_RATE, tau, sampleI);
226
+ }
227
+ // Line 16: ĉ ← NTT(c), single polynomial in place ─────────
228
+ mx.ntt(polySlot1);
229
+ // Line 17: ⟨cs₁⟩ ← NTT⁻¹(ĉ ∘ ŝ₁) at slot4 (overwrites w₁) ──
230
+ // The WASM polynomial layer exposes poly_pointwise_montgomery
231
+ // (per-poly) and polyvec_pointwise_montgomery (per-vec,
232
+ // expects same-length input). For c·s₁ where c is one
233
+ // polynomial and s₁ is a length-ℓ vec, drive a TS-side loop
234
+ // over the poly variant.
235
+ for (let r = 0; r < l; r++) {
236
+ mx.poly_pointwise_montgomery(slot4 + r * POLY_BYTES, polySlot1, slot0 + r * POLY_BYTES);
237
+ }
238
+ mx.polyvec_invntt(slot4, l);
239
+ // Line 19: z ← y + ⟨cs₁⟩ at slot4 ──────────────────────────
240
+ mx.polyvec_add(slot4, slot3, slot4, l);
241
+ // ‖z‖∞ check needs centered residues. polyvec_reduce produces
242
+ // (-q/2, q/2]; chknorm is correct on that form per the
243
+ // WASM polynomial-layer contract.
244
+ mx.polyvec_reduce(slot4, l);
245
+ // Line 21 (first half): ‖z‖∞ ≥ γ₁ − β ⇒ reject ─────────────
246
+ if (mx.polyvec_chknorm(slot4, gamma1 - beta, l) !== 0) {
247
+ kappa += l;
248
+ continue;
249
+ }
250
+ // Line 18: ⟨cs₂⟩ ← NTT⁻¹(ĉ ∘ ŝ₂) at slot3 (overwrites y_time) ─
251
+ for (let r = 0; r < k; r++) {
252
+ mx.poly_pointwise_montgomery(slot3 + r * POLY_BYTES, polySlot1, slot1 + r * POLY_BYTES);
253
+ }
254
+ mx.polyvec_invntt(slot3, k);
255
+ mx.polyvec_reduce(slot3, k);
256
+ mx.polyvec_caddq(slot3, k); // canonical for sub→reduce→caddq round-trip
257
+ // Line 20: r₀ ← LowBits(w − ⟨cs₂⟩). Compute (w − ⟨cs₂⟩) at
258
+ // slot5 (overwrites w, we no longer need w independently;
259
+ // downstream both r₀ and h derive from w − ⟨cs₂⟩).
260
+ mx.polyvec_sub(slot5, slot5, slot3, k);
261
+ mx.polyvec_reduce(slot5, k);
262
+ mx.polyvec_caddq(slot5, k); // canonical for lowbits + make_hint
263
+ // r₀ at slot3 (overwrites ⟨cs₂⟩, no longer needed)
264
+ mx.polyvec_lowbits(slot3, slot5, k, gamma2);
265
+ // Line 21 (second half): ‖r₀‖∞ ≥ γ₂ − β ⇒ reject ──────────
266
+ if (mx.polyvec_chknorm(slot3, gamma2 - beta, k) !== 0) {
267
+ kappa += l;
268
+ continue;
269
+ }
270
+ // Line 24: ⟨ct₀⟩ ← NTT⁻¹(ĉ ∘ t̂₀) at slot3 (overwrites r₀) ─
271
+ for (let r = 0; r < k; r++) {
272
+ mx.poly_pointwise_montgomery(slot3 + r * POLY_BYTES, polySlot1, slot2 + r * POLY_BYTES);
273
+ }
274
+ mx.polyvec_invntt(slot3, k);
275
+ mx.polyvec_reduce(slot3, k); // centered for chknorm
276
+ // Line 26 (first half): ‖⟨ct₀⟩‖∞ ≥ γ₂ ⇒ reject. Note the
277
+ // bound is γ₂ (NOT γ₂ − β), the missing β subtract is a
278
+ // common copy/paste error.
279
+ if (mx.polyvec_chknorm(slot3, gamma2, k) !== 0) {
280
+ kappa += l;
281
+ continue;
282
+ }
283
+ mx.polyvec_caddq(slot3, k); // canonical for make_hint z input
284
+ // Line 25: h ← MakeHint(−⟨ct₀⟩, w − ⟨cs₂⟩ + ⟨ct₀⟩).
285
+ // MakeHint(z, r) = [HighBits(r) ≠ HighBits(r + z)] is symmetric
286
+ // in (r, r+z), so passing z = ⟨ct₀⟩ and r = w − ⟨cs₂⟩ produces
287
+ // h[i] = [HighBits(w − ⟨cs₂⟩) ≠ HighBits(w − ⟨cs₂⟩ + ⟨ct₀⟩)],
288
+ // the same predicate. Avoids the explicit −⟨ct₀⟩ canonicalise.
289
+ // polyvec_make_hint aliases-safe with z; h overwrites slot3.
290
+ const popcount = mx.polyvec_make_hint(slot3, slot3, slot5, k, gamma2);
291
+ // Line 26 (second half): popcount > ω ⇒ reject ─────────────
292
+ if (popcount > omega) {
293
+ kappa += l;
294
+ continue;
295
+ }
296
+ // All four hard checks passed, encode signature.
297
+ // Line 32: σ ← sigEncode(c̃, z mod± q, h), Algorithm 26.
298
+ mlMem.set(cTilde, sigOff); // c̃ (λ/4 bytes)
299
+ const sigZOff = sigOff + lambdaOver4;
300
+ for (let r = 0; r < l; r++) {
301
+ // z is in centered residues post polyvec_reduce; bit_pack(γ₁-1, γ₁)
302
+ // expects [-(γ₁-1), γ₁]. ‖z‖∞ < γ₁ - β guarantees range.
303
+ mx.bit_pack(sigZOff + r * zPolyBytes, slot4 + r * POLY_BYTES, gamma1 - 1, gamma1);
304
+ }
305
+ mx.hint_bit_pack(sigZOff + l * zPolyBytes, slot3, k, omega);
306
+ success = true;
307
+ break;
308
+ }
309
+ if (!success) {
310
+ throw new Error(`leviathan-crypto: ML-DSA signing exceeded ${MAX_SIGN_ITERATIONS} rejection-sample iterations`
311
+ + ' (FIPS 204 Appendix C suggests min 814; bound here gives ~20% headroom)');
312
+ }
313
+ // Slice σ before any wipes, slice copies, so wipes after this
314
+ // don't touch the returned Uint8Array.
315
+ const sig = mlMem.slice(sigOff, sigOff + sigBytes);
316
+ return sig;
317
+ }
318
+ finally {
319
+ // Wipe scratch per FIPS 204 §3.6.3. Runs in finally so success
320
+ // and early-throw paths both clear. See
321
+ // docs/mldsa.md#wipe-discipline.
322
+ mlMem.fill(0, mx.getPolyvecSlotBase(), mx.getPolyvecSlotBase() + 6 * mx.getPolyvecSlotSize());
323
+ // Poly slots: signs (POLY_SLOT_0, public, c̃ derived), c (POLY_SLOT_1,
324
+ // public, derivable from c̃), and POLY_SLOT_7 holding the last
325
+ // matrix-vector product partial (secret-derived via y_ntt). Wipe all
326
+ // 8 contiguous slots in one fill, cheap and avoids residue gaps.
327
+ mlMem.fill(0, polySlotBase, polySlotBase + 8 * POLY_BYTES);
328
+ // XOF/PRF region last held SampleInBall position bytes (public, c̃-derived)
329
+ // or ExpandMask outputs (secret, ρ''-derived) on the rejected path.
330
+ mlMem.fill(0, xofOff, xofOff + 8192);
331
+ // Public-derivable but cheap to wipe for hygiene:
332
+ mlMem.fill(0, cTildeOff, cTildeOff + 64);
333
+ mlMem.fill(0, msgRepOff, msgRepOff + 64);
334
+ // Defensive: SEED, TR, SK regions, Sign_internal does not write
335
+ // to these (we extract sk components via TS subarrays), but a
336
+ // prior op may have left residue. Cheap wipe closes that window.
337
+ mlMem.fill(0, seedOff, seedOff + 128);
338
+ mlMem.fill(0, trOff, trOff + 64);
339
+ mlMem.fill(0, skOff, skOff + params.skBytes);
340
+ // SHA3 module: STATE / INPUT / OUT all carried K, μ, ρ'', c̃, and
341
+ // the SampleInBall stream. Wipe before returning.
342
+ sx.wipeBuffers();
343
+ // TS-side scratch, wipe last so any wipe()-throws don't skip
344
+ // the WASM cleanup above.
345
+ if (mu)
346
+ wipe(mu);
347
+ if (rhoPP)
348
+ wipe(rhoPP);
349
+ if (cTilde)
350
+ wipe(cTilde);
351
+ if (w1Bytes)
352
+ wipe(w1Bytes);
353
+ }
354
+ }
355
+ /**
356
+ * HashML-DSA sign, post-prehash. FIPS 204 §5.4 Algorithm 4 lines 22-24.
357
+ * Builds M' = 0x01 ‖ |ctx| ‖ ctx ‖ OID(algo) ‖ prehash and drives
358
+ * Sign_internal with the caller-supplied rnd.
359
+ *
360
+ * The caller owns `prehash` (it may be a slice of WASM memory, a
361
+ * caller-controlled digest, or an internally-computed PH_M); this helper
362
+ * never wipes it. The caller also owns `rnd` (hedged: caller wipes a
363
+ * freshly-generated value; deterministic: rnd is zeros, no wipe; derand:
364
+ * caller-supplied per FIPS 204 §3.4 contract).
365
+ *
366
+ * The 12 approved pre-hash functions (`algo`) and their OIDs are FIPS 204
367
+ * §5.4.1's full catalog. Domain-sep byte 0x01 inside the M' construction
368
+ * separates HashML-DSA signatures from pure-ML-DSA signatures on the same
369
+ * key per §3.6.4.
370
+ */
371
+ export function signWithPrehash(mx, sx, params, sk, prehash, algo, ctx, rnd) {
372
+ const oid = getOid(algo);
373
+ const MPrime = constructMPrimeHash(ctx, oid, prehash);
374
+ try {
375
+ return mldsaSignInternal(mx, sx, params, sk, MPrime, rnd);
376
+ }
377
+ finally {
378
+ wipe(MPrime);
379
+ }
380
+ }
@@ -0,0 +1,91 @@
1
+ export interface MlDsaExports {
2
+ memory: WebAssembly.Memory;
3
+ getModuleId: () => number;
4
+ getMemoryPages: () => number;
5
+ getPolySlotBase: () => number;
6
+ getPolySlotSize: () => number;
7
+ getPolySlot0: () => number;
8
+ getPolySlot1: () => number;
9
+ getPolySlot2: () => number;
10
+ getPolySlot3: () => number;
11
+ getPolySlot4: () => number;
12
+ getPolySlot5: () => number;
13
+ getPolySlot6: () => number;
14
+ getPolySlot7: () => number;
15
+ getMatrixSlot: () => number;
16
+ getMatrixSlotSize: () => number;
17
+ getPolyvecSlotBase: () => number;
18
+ getPolyvecSlotSize: () => number;
19
+ getPolyvecSlot0: () => number;
20
+ getPolyvecSlot1: () => number;
21
+ getPolyvecSlot2: () => number;
22
+ getPolyvecSlot3: () => number;
23
+ getPolyvecSlot4: () => number;
24
+ getPolyvecSlot5: () => number;
25
+ getPolyvecSlot6: () => number;
26
+ getPolyvecSlot7: () => number;
27
+ getSeedOffset: () => number;
28
+ getTrOffset: () => number;
29
+ getMsgRepOffset: () => number;
30
+ getCTildeOffset: () => number;
31
+ getPkOffset: () => number;
32
+ getSkOffset: () => number;
33
+ getSigOffset: () => number;
34
+ getXofPrfOffset: () => number;
35
+ wipeBuffers: () => void;
36
+ montgomery_reduce: (a: bigint) => number;
37
+ barrett_reduce: (a: number) => number;
38
+ fqmul: (a: number, b: number) => number;
39
+ getZetasOffset: () => number;
40
+ getZeta: (i: number) => number;
41
+ BitRev8: (m: number) => number;
42
+ ntt: (polyOff: number) => void;
43
+ invntt: (polyOff: number) => void;
44
+ poly_add: (rOff: number, aOff: number, bOff: number) => void;
45
+ poly_sub: (rOff: number, aOff: number, bOff: number) => void;
46
+ poly_reduce: (polyOff: number) => void;
47
+ poly_caddq: (polyOff: number) => void;
48
+ poly_pointwise_montgomery: (rOff: number, aOff: number, bOff: number) => void;
49
+ poly_freeze: (polyOff: number) => void;
50
+ poly_chknorm: (polyOff: number, bound: number) => number;
51
+ poly_tomont: (polyOff: number) => void;
52
+ simple_bit_pack: (rByteOff: number, polyOff: number, bitlen: number) => void;
53
+ bit_pack: (rByteOff: number, polyOff: number, a: number, b: number) => void;
54
+ simple_bit_unpack: (polyOff: number, vByteOff: number, bitlen: number) => void;
55
+ bit_unpack: (polyOff: number, vByteOff: number, a: number, b: number) => void;
56
+ hint_bit_pack: (rByteOff: number, hPvOff: number, k: number, omega: number) => void;
57
+ hint_bit_unpack: (hPvOff: number, vByteOff: number, k: number, omega: number) => number;
58
+ power2round: (r1Off: number, r0Off: number, aOff: number) => void;
59
+ decompose: (r1Off: number, r0Off: number, aOff: number, gamma2: number) => void;
60
+ highbits: (rOff: number, aOff: number, gamma2: number) => void;
61
+ lowbits: (rOff: number, aOff: number, gamma2: number) => void;
62
+ make_hint: (hOff: number, zOff: number, rOff: number, gamma2: number) => void;
63
+ use_hint: (rOff: number, hOff: number, aOff: number, gamma2: number) => void;
64
+ polyvec_add: (rOff: number, aOff: number, bOff: number, len: number) => void;
65
+ polyvec_sub: (rOff: number, aOff: number, bOff: number, len: number) => void;
66
+ polyvec_reduce: (pvOff: number, len: number) => void;
67
+ polyvec_caddq: (pvOff: number, len: number) => void;
68
+ polyvec_freeze: (pvOff: number, len: number) => void;
69
+ polyvec_tomont: (pvOff: number, len: number) => void;
70
+ polyvec_ntt: (pvOff: number, len: number) => void;
71
+ polyvec_invntt: (pvOff: number, len: number) => void;
72
+ polyvec_pointwise_montgomery: (rOff: number, aOff: number, bOff: number, len: number) => void;
73
+ polyvec_pointwise_acc_montgomery: (rPolyOff: number, aPvOff: number, bPvOff: number, len: number) => void;
74
+ polyvec_matrix_pointwise_montgomery: (rPvOff: number, matOff: number, vPvOff: number, k: number, l: number) => void;
75
+ polyvec_chknorm: (pvOff: number, bound: number, len: number) => number;
76
+ polyvec_power2round: (r1pvOff: number, r0pvOff: number, aPvOff: number, len: number) => void;
77
+ polyvec_decompose: (r1pvOff: number, r0pvOff: number, aPvOff: number, len: number, gamma2: number) => void;
78
+ polyvec_highbits: (rPvOff: number, aPvOff: number, len: number, gamma2: number) => void;
79
+ polyvec_lowbits: (rPvOff: number, aPvOff: number, len: number, gamma2: number) => void;
80
+ polyvec_make_hint: (hPvOff: number, zPvOff: number, rPvOff: number, len: number, gamma2: number) => number;
81
+ polyvec_use_hint: (rPvOff: number, hPvOff: number, aPvOff: number, len: number, gamma2: number) => void;
82
+ rej_ntt_poly: (polyOff: number, ctrStart: number, bufOff: number, bufLen: number) => number;
83
+ rej_bounded_poly: (polyOff: number, ctrStart: number, bufOff: number, bufLen: number, eta: number) => number;
84
+ sample_in_ball: (polyOff: number, signsOff: number, posBytesOff: number, posBytesLen: number, tau: number, startI: number) => number;
85
+ }
86
+ export type { Sha3Exports } from '../mlkem/types.js';
87
+ /** ML-DSA key pair returned by keygen / keygenDerand. */
88
+ export interface MlDsaKeyPair {
89
+ verificationKey: Uint8Array;
90
+ signingKey: Uint8Array;
91
+ }
@@ -0,0 +1,25 @@
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/mldsa/types.ts
23
+ //
24
+ // ML-DSA type definitions: WASM export interfaces and signature API types.
25
+ export {};
@@ -0,0 +1,55 @@
1
+ import type { MlDsaParams } from './params.js';
2
+ import { type PreHashAlgorithm } from './hashvariant.js';
3
+ /**
4
+ * FIPS 204 §5.2 / §5.3 line 1, ctx must be ≤ 255 bytes (the byte that
5
+ * follows the domain separator in M' is ctx.length, so longer values
6
+ * cannot be encoded).
7
+ */
8
+ export declare function validateContext(ctx: Uint8Array): void;
9
+ /**
10
+ * FIPS 204 §3.6.2, verification key must be exactly pkBytes long for
11
+ * its parameter set. Throws here; the public verify() method catches
12
+ * the throw and returns false so wrong-length pk reads as "not a valid
13
+ * signature" rather than a caller error.
14
+ */
15
+ export declare function validateVerificationKey(vk: Uint8Array, params: MlDsaParams): void;
16
+ /**
17
+ * Signing key must be exactly skBytes long for its parameter set. Wrong
18
+ * length is a caller error (the caller produced this sk via keygen* or
19
+ * loaded it from storage they own); throw RangeError unconditionally.
20
+ */
21
+ export declare function validateSigningKey(sk: Uint8Array, params: MlDsaParams): void;
22
+ /**
23
+ * FIPS 204 §3.6.2, signature must be exactly sigBytes long for its
24
+ * parameter set. Throws here; the public verify() method catches and
25
+ * returns false (same protocol shape as wrong-length pk).
26
+ */
27
+ export declare function validateSignature(sig: Uint8Array, params: MlDsaParams): void;
28
+ /**
29
+ * FIPS 204 Algorithm 7 line 1, rnd must be 32 bytes. Used by signDerand
30
+ * (the testing/CAVP API). Hedged sign supplies rnd internally; deterministic
31
+ * sign uses zeros; only signDerand exposes rnd to the caller.
32
+ */
33
+ export declare function validateRnd(rnd: Uint8Array): void;
34
+ /**
35
+ * Confirms M is a Uint8Array. FIPS 204 places no length restriction on
36
+ * the message, M is absorbed into a SHAKE256 sponge (μ derivation,
37
+ * §6.2/§6.3) so any byte length is admissible. The bit-vs-byte
38
+ * distinction visible in §5.2/§5.3 (BytesToBits in M' construction)
39
+ * collapses at the byte boundary inside our SHAKE wrapper.
40
+ */
41
+ export declare function validateMessage(M: Uint8Array): void;
42
+ /**
43
+ * FIPS 204 §5.4.1, the prehash PH_M passed to HashML-DSA Sign_internal /
44
+ * Verify_internal must be exactly the digest size of `algo`. Used by the
45
+ * `*Prehashed` family where the caller computes PH externally; the
46
+ * non-prehashed family produces PH internally so this check is implicit.
47
+ *
48
+ * Throws `SigningError('sig-malformed-input')` on mismatch. The verify
49
+ * surface intercepts this to return false (a wrong-size digest is a
50
+ * structural mismatch, indistinguishable from a wrong signature), while
51
+ * the sign surface lets the throw propagate (the caller supplied bad
52
+ * input, that is a contract violation per the FIPS 204 §5.4 input
53
+ * contract).
54
+ */
55
+ export declare function validateDigest(digest: Uint8Array, algo: PreHashAlgorithm): void;