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
@@ -0,0 +1,31 @@
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/merkle/sth.ts
23
+ //
24
+ // SignedTreeHead type: a c2sp.org/tlog-checkpoint body plus the
25
+ // signature lines that authenticate it. The body and signatures pair
26
+ // is the wire-format unit a SignedLog (built on top of MerkleTree in
27
+ // a later phase) produces and a verifier consumes. The byte-stable
28
+ // serialization is `emitSignedNote(serializeCheckpointBody(c), sigs)`;
29
+ // the parse side is `parseSignedNote` followed by `parseCheckpointBody`
30
+ // on the resulting body region.
31
+ export {};
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Minimum surface a backend exposes to drive a MerkleTree. Sync
3
+ * everywhere: the merkle layer is synchronous and consumers that need
4
+ * async IO wrap externally.
5
+ *
6
+ * Storage records only perfect aligned subtree hashes. Roots of
7
+ * partial right-edge subtrees are recomputed on demand by the tree.
8
+ */
9
+ export interface MerkleStorage {
10
+ /** Number of leaves appended so far. */
11
+ size(): number;
12
+ /** Record a leaf hash at the given index. The implementation must reject out-of-order indices. */
13
+ appendLeaf(leafIndex: number, leafHash: Uint8Array): void;
14
+ /** Read the leaf hash at index. Throws if absent. */
15
+ getLeaf(leafIndex: number): Uint8Array;
16
+ /** Record an internal node hash at the given (level, index). */
17
+ putNode(level: number, index: number, hash: Uint8Array): void;
18
+ /** Read an internal-node hash. Throws if absent. */
19
+ getNode(level: number, index: number): Uint8Array;
20
+ /** Probe an internal-node slot without throwing. */
21
+ hasNode(level: number, index: number): boolean;
22
+ }
23
+ /**
24
+ * In-process storage backed by a Map keyed on `${level}:${index}`. Sync.
25
+ * Suitable for tests, witnesses without persistent storage, and the
26
+ * MerkleVerifier short-lived flow. Production logs that need durability
27
+ * implement MerkleStorage over a file or DB and feed it to a
28
+ * MerkleTree the same way.
29
+ */
30
+ export declare class MemoryStorage implements MerkleStorage {
31
+ private leafCount;
32
+ private readonly nodes;
33
+ private static key;
34
+ size(): number;
35
+ appendLeaf(leafIndex: number, leafHash: Uint8Array): void;
36
+ getLeaf(leafIndex: number): Uint8Array;
37
+ putNode(level: number, index: number, hash: Uint8Array): void;
38
+ getNode(level: number, index: number): Uint8Array;
39
+ hasNode(level: number, index: number): boolean;
40
+ }
@@ -0,0 +1,71 @@
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/merkle/storage.ts
23
+ //
24
+ // MerkleStorage abstracts the per-node persistence layer a tree uses
25
+ // to materialise leaves and recomputed internal nodes. Two-axis key
26
+ // (level, index): level 0 is the leaf row, level >= 1 stores the hash
27
+ // of a perfect aligned subtree covering [index*2^level, (index+1)*2^level).
28
+ //
29
+ // MemoryStorage is the in-process implementation. File and database
30
+ // backends are extension surface and ship as consumer code.
31
+ /**
32
+ * In-process storage backed by a Map keyed on `${level}:${index}`. Sync.
33
+ * Suitable for tests, witnesses without persistent storage, and the
34
+ * MerkleVerifier short-lived flow. Production logs that need durability
35
+ * implement MerkleStorage over a file or DB and feed it to a
36
+ * MerkleTree the same way.
37
+ */
38
+ export class MemoryStorage {
39
+ leafCount = 0;
40
+ nodes = new Map();
41
+ static key(level, index) {
42
+ return `${level}:${index}`;
43
+ }
44
+ size() {
45
+ return this.leafCount;
46
+ }
47
+ appendLeaf(leafIndex, leafHash) {
48
+ if (leafIndex !== this.leafCount)
49
+ throw new RangeError(`MemoryStorage.appendLeaf: out-of-order index ${leafIndex}, expected ${this.leafCount}`);
50
+ this.nodes.set(MemoryStorage.key(0, leafIndex), leafHash);
51
+ this.leafCount++;
52
+ }
53
+ getLeaf(leafIndex) {
54
+ const v = this.nodes.get(MemoryStorage.key(0, leafIndex));
55
+ if (!v)
56
+ throw new RangeError(`MemoryStorage.getLeaf: no leaf at index ${leafIndex}`);
57
+ return v;
58
+ }
59
+ putNode(level, index, hash) {
60
+ this.nodes.set(MemoryStorage.key(level, index), hash);
61
+ }
62
+ getNode(level, index) {
63
+ const v = this.nodes.get(MemoryStorage.key(level, index));
64
+ if (!v)
65
+ throw new RangeError(`MemoryStorage.getNode: no node at (${level}, ${index})`);
66
+ return v;
67
+ }
68
+ hasNode(level, index) {
69
+ return this.nodes.has(MemoryStorage.key(level, index));
70
+ }
71
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Minimum surface a hash function must expose to drive RFC 9162
3
+ * (Certificate Transparency Version 2.0) §2.1.1, Merkle Hash Trees.
4
+ *
5
+ * Implementations are `const` objects (no instantiation); each call site
6
+ * acquires the underlying WASM module fresh, runs the operation, and
7
+ * disposes. There is no long-lived state on a Hasher; concurrent users
8
+ * are serialised by the per-module exclusivity guard at the WASM layer.
9
+ */
10
+ export interface Hasher {
11
+ /** Display name, used in error messages and the export catalog. */
12
+ readonly name: string;
13
+ /** Bytes per hash output. */
14
+ readonly outputSize: number;
15
+ /** WASM module ids this Hasher exercises during `init()`. */
16
+ readonly wasmModules: readonly string[];
17
+ /** RFC 9162 §2.1.1: MTH({}) = HASH(), the hash of the empty input. */
18
+ hashEmpty(): Uint8Array;
19
+ /** RFC 9162 §2.1.1: leaf domain separator `0x00` prefix. */
20
+ hashLeaf(leaf: Uint8Array): Uint8Array;
21
+ /** RFC 9162 §2.1.1: internal-node domain separator `0x01` prefix. */
22
+ hashInternal(left: Uint8Array, right: Uint8Array): Uint8Array;
23
+ }
24
+ /**
25
+ * Stateful Merkle tree with pluggable storage. Append a leaf, query
26
+ * size + root, build inclusion and consistency proofs. The tree owns
27
+ * the hash function via `hasher`; consumers do not pass it per call.
28
+ */
29
+ export interface MerkleTree {
30
+ readonly hasher: Hasher;
31
+ size(): number;
32
+ rootHash(): Uint8Array;
33
+ append(leafBytes: Uint8Array): {
34
+ leafIndex: number;
35
+ leafHash: Uint8Array;
36
+ };
37
+ getInclusionProof(leafIndex: number, treeSize?: number): Uint8Array[];
38
+ getConsistencyProof(oldSize: number, newSize: number): Uint8Array[];
39
+ }
40
+ /**
41
+ * RFC 9162 §2.1.4, Consistency Proof Verification: "k is the largest
42
+ * power of two smaller than n". The split point at which an n-leaf
43
+ * tree decomposes into a perfect left subtree of size k and a right
44
+ * subtree of size n - k. Defined for n >= 2.
45
+ *
46
+ * Invariant for n >= 2: k < n <= 2*k.
47
+ */
48
+ export declare function splitPoint(n: number): number;
49
+ /**
50
+ * `bits.Len64(x)` analogue: position of the most-significant set bit
51
+ * of x, with `bitLen(0) = 0`. Used by the §2.1.3 / §2.1.4 inclusion
52
+ * and consistency verifiers to split a proof into inner and border
53
+ * segments.
54
+ */
55
+ export declare function bitLen(x: number): number;
56
+ /**
57
+ * Popcount of a non-negative integer (`bits.OnesCount64` analogue).
58
+ * Used to compute the "border" length of an inclusion proof per the
59
+ * RFC 9162 §2.1.3 decomposition.
60
+ */
61
+ export declare function popcount(x: number): number;
62
+ /**
63
+ * Number of trailing zero bits in a positive integer
64
+ * (`bits.TrailingZeros64` analogue). Used by RFC 9162 §2.1.4 to step
65
+ * the consistency verifier past the levels covered by the size1
66
+ * subtree. Defined for x >= 1.
67
+ */
68
+ export declare function trailingZeros(x: number): number;
@@ -0,0 +1,94 @@
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/merkle/tree.ts
23
+ //
24
+ // MerkleTree + Hasher interfaces and the spec-anchored node-index math.
25
+ // Hash-agnostic by design: every hash-touching surface (the tree class,
26
+ // the free-function proof verifiers, the proof builders) is parameterised
27
+ // by a `Hasher`, so SHA-256 and BLAKE3 trees share the same algorithmic
28
+ // core and the same proof wire format.
29
+ /**
30
+ * RFC 9162 §2.1.4, Consistency Proof Verification: "k is the largest
31
+ * power of two smaller than n". The split point at which an n-leaf
32
+ * tree decomposes into a perfect left subtree of size k and a right
33
+ * subtree of size n - k. Defined for n >= 2.
34
+ *
35
+ * Invariant for n >= 2: k < n <= 2*k.
36
+ */
37
+ export function splitPoint(n) {
38
+ if (!Number.isInteger(n) || n < 2)
39
+ throw new RangeError(`splitPoint: n must be an integer >= 2, got ${n}`);
40
+ // Largest power of two strictly less than n: for n=2 -> 1, n=8 -> 4,
41
+ // n=2^k -> 2^(k-1). Equivalent to 1 << (bitLength(n - 1) - 1).
42
+ let k = 1;
43
+ while (k * 2 < n)
44
+ k *= 2;
45
+ return k;
46
+ }
47
+ /**
48
+ * `bits.Len64(x)` analogue: position of the most-significant set bit
49
+ * of x, with `bitLen(0) = 0`. Used by the §2.1.3 / §2.1.4 inclusion
50
+ * and consistency verifiers to split a proof into inner and border
51
+ * segments.
52
+ */
53
+ export function bitLen(x) {
54
+ if (!Number.isInteger(x) || x < 0)
55
+ throw new RangeError(`bitLen: x must be a non-negative integer, got ${x}`);
56
+ let n = 0;
57
+ while (x > 0) {
58
+ x = Math.floor(x / 2);
59
+ n++;
60
+ }
61
+ return n;
62
+ }
63
+ /**
64
+ * Popcount of a non-negative integer (`bits.OnesCount64` analogue).
65
+ * Used to compute the "border" length of an inclusion proof per the
66
+ * RFC 9162 §2.1.3 decomposition.
67
+ */
68
+ export function popcount(x) {
69
+ if (!Number.isInteger(x) || x < 0)
70
+ throw new RangeError(`popcount: x must be a non-negative integer, got ${x}`);
71
+ let n = 0;
72
+ while (x > 0) {
73
+ if (x & 1)
74
+ n++;
75
+ x = Math.floor(x / 2);
76
+ }
77
+ return n;
78
+ }
79
+ /**
80
+ * Number of trailing zero bits in a positive integer
81
+ * (`bits.TrailingZeros64` analogue). Used by RFC 9162 §2.1.4 to step
82
+ * the consistency verifier past the levels covered by the size1
83
+ * subtree. Defined for x >= 1.
84
+ */
85
+ export function trailingZeros(x) {
86
+ if (!Number.isInteger(x) || x < 1)
87
+ throw new RangeError(`trailingZeros: x must be a positive integer, got ${x}`);
88
+ let n = 0;
89
+ while ((x & 1) === 0) {
90
+ x = Math.floor(x / 2);
91
+ n++;
92
+ }
93
+ return n;
94
+ }
@@ -0,0 +1 @@
1
+ export { WASM_GZ_BASE64 as mldsaWasm } from '../embedded/mldsa.js';
@@ -19,9 +19,9 @@
19
19
  // ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
20
20
  // ▀█████▀▀
21
21
  //
22
- // src/ts/kyber/embedded.ts
22
+ // src/ts/mldsa/embedded.ts
23
23
  //
24
- // Exports the gzip+base64 kyber WASM blob for use as a WasmSource.
25
- // This is the only file in the kyber subpath that references the embedded blob.
26
- // Import via `leviathan-crypto/kyber/embedded`.
27
- export { WASM_GZ_BASE64 as kyberWasm } from '../embedded/kyber.js';
24
+ // Exports the gzip+base64 mldsa WASM blob for use as a WasmSource.
25
+ // This is the only file in the mldsa subpath that references the embedded blob.
26
+ // Import via `leviathan-crypto/mldsa/embedded`.
27
+ export { WASM_GZ_BASE64 as mldsaWasm } from '../embedded/mldsa.js';
@@ -0,0 +1,53 @@
1
+ import type { MlDsaExports, Sha3Exports } from './types.js';
2
+ import type { MlDsaParams } from './params.js';
3
+ /**
4
+ * ExpandA, FIPS 204 Algorithm 32.
5
+ *
6
+ * For (i, j) ∈ [0, k) × [0, ℓ):
7
+ * s ← ρ ‖ IntegerToBytes(j, 1) ‖ IntegerToBytes(i, 1)
8
+ * Â[i, j] ← RejNTTPoly(SHAKE128(s))
9
+ *
10
+ * Output is row-major: Â[i, j] sits at matrixOff + (i·ℓ + j) · 1024.
11
+ * This matches `polyvec_matrix_pointwise_montgomery`'s row-stride contract.
12
+ *
13
+ * ρ is the public seed; the rej_ntt_poly inner loop has data-dependent
14
+ * branching but only on ρ-derived bytes (public), so no CT concern.
15
+ */
16
+ export declare function expandA(mx: MlDsaExports, sx: Sha3Exports, params: MlDsaParams, rho: Uint8Array, matrixOff: number): void;
17
+ /**
18
+ * ExpandS, FIPS 204 Algorithm 33.
19
+ *
20
+ * For r ∈ [0, ℓ): s₁[r] ← RejBoundedPoly(SHAKE256(ρ' ‖ IntegerToBytes(r, 2)))
21
+ * For r ∈ [0, k): s₂[r] ← RejBoundedPoly(SHAKE256(ρ' ‖ IntegerToBytes(r+ℓ, 2)))
22
+ *
23
+ * Note the index is 2 bytes (little-endian per FIPS 204 §7.1 Alg 11), mlkem
24
+ * uses 1 byte because k ≤ 4, but ML-DSA's max index is k+ℓ-1 = 14 (still
25
+ * ≤ 255 in practice but the spec mandates 2 bytes).
26
+ *
27
+ * ρ' is secret. The local seed scratch is wiped on exit; the caller is
28
+ * responsible for the WASM-resident ρ' source buffer.
29
+ */
30
+ export declare function expandS(mx: MlDsaExports, sx: Sha3Exports, params: MlDsaParams, rhoPrime: Uint8Array, s1Off: number, s2Off: number): void;
31
+ /**
32
+ * ExpandMask, FIPS 204 Algorithm 34.
33
+ *
34
+ * For r ∈ [0, ℓ):
35
+ * v ← SHAKE256(ρ'' ‖ IntegerToBytes(κ + r, 2), 32·c)
36
+ * y[r] ← BitUnpack(v, γ₁ − 1, γ₁)
37
+ *
38
+ * where c = 1 + bitlen(γ₁ − 1) is the per-coefficient byte width
39
+ * (18 when γ₁ = 2¹⁷, 20 when γ₁ = 2¹⁹). Output coefficients land in
40
+ * [-(γ₁ − 1), γ₁]; bit_unpack(a=γ₁−1, b=γ₁) covers exactly that range.
41
+ *
42
+ * y is produced in time domain at `yPvOff`. Sign_internal applies
43
+ * polyvec_ntt to y before the matrix-vector product  · NTT(y).
44
+ *
45
+ * ρ'' is secret (derived from K ‖ rnd ‖ μ). This function uses one
46
+ * one-shot SHAKE256 per polynomial (caller's full input is ≤ 168 B,
47
+ * fits in a single shake256HashConcat call); the SHAKE state is reset
48
+ * each iteration via shake256Init inside the helper, so no state
49
+ * carries between r values. The squeeze output lands in a TS-side
50
+ * buffer and is set into the WASM XOF/PRF region only long enough for
51
+ * bit_unpack to consume it.
52
+ */
53
+ export declare function expandMask(mx: MlDsaExports, sx: Sha3Exports, params: MlDsaParams, rhoPrimePrime: Uint8Array, kappa: number, yPvOff: number): void;
@@ -0,0 +1,188 @@
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/expand.ts
23
+ //
24
+ // FIPS 204 Algorithms 32 (ExpandA), 33 (ExpandS), 34 (ExpandMask),
25
+ // pseudorandom expansion of seeds into the matrix  (public), the noise
26
+ // polyvecs s₁/s₂ (secret, time domain), and the masking polyvec y (per
27
+ // signing iteration, time domain).
28
+ //
29
+ // ExpandA samples  directly in NTT domain (RejNTTPoly produces NTT
30
+ // coefficients). ExpandS / ExpandMask sample in time domain; the
31
+ // orchestration layer applies polyvec_ntt where needed for the matrix-
32
+ // vector products in Sign_internal and Verify_internal.
33
+ import { wipe } from '../utils.js';
34
+ import { shake128Squeezer, shake256Squeezer, shake256HashConcat } from './sha3-helpers.js';
35
+ const POLY_BYTES = 1024; // 256 × i32
36
+ /**
37
+ * ExpandA, FIPS 204 Algorithm 32.
38
+ *
39
+ * For (i, j) ∈ [0, k) × [0, ℓ):
40
+ * s ← ρ ‖ IntegerToBytes(j, 1) ‖ IntegerToBytes(i, 1)
41
+ * Â[i, j] ← RejNTTPoly(SHAKE128(s))
42
+ *
43
+ * Output is row-major: Â[i, j] sits at matrixOff + (i·ℓ + j) · 1024.
44
+ * This matches `polyvec_matrix_pointwise_montgomery`'s row-stride contract.
45
+ *
46
+ * ρ is the public seed; the rej_ntt_poly inner loop has data-dependent
47
+ * branching but only on ρ-derived bytes (public), so no CT concern.
48
+ */
49
+ export function expandA(mx, sx, params, rho, matrixOff) {
50
+ const { k, l } = params;
51
+ const xofPrfOff = mx.getXofPrfOffset();
52
+ const mlMem = new Uint8Array(mx.memory.buffer);
53
+ // XOF input: ρ(32) ‖ jByte ‖ iByte (FIPS 204 §7.3 Alg 32 line 4)
54
+ const xofSeed = new Uint8Array(34);
55
+ xofSeed.set(rho, 0);
56
+ for (let i = 0; i < k; i++) {
57
+ for (let j = 0; j < l; j++) {
58
+ xofSeed[32] = j;
59
+ xofSeed[33] = i;
60
+ const sq = shake128Squeezer(sx, xofSeed);
61
+ const polyOff = matrixOff + (i * l + j) * POLY_BYTES;
62
+ let ctr = 0;
63
+ while (ctr < 256) {
64
+ const block = sq.squeeze();
65
+ mlMem.set(block, xofPrfOff);
66
+ ctr += mx.rej_ntt_poly(polyOff, ctr, xofPrfOff, sq.rate);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * ExpandS, FIPS 204 Algorithm 33.
73
+ *
74
+ * For r ∈ [0, ℓ): s₁[r] ← RejBoundedPoly(SHAKE256(ρ' ‖ IntegerToBytes(r, 2)))
75
+ * For r ∈ [0, k): s₂[r] ← RejBoundedPoly(SHAKE256(ρ' ‖ IntegerToBytes(r+ℓ, 2)))
76
+ *
77
+ * Note the index is 2 bytes (little-endian per FIPS 204 §7.1 Alg 11), mlkem
78
+ * uses 1 byte because k ≤ 4, but ML-DSA's max index is k+ℓ-1 = 14 (still
79
+ * ≤ 255 in practice but the spec mandates 2 bytes).
80
+ *
81
+ * ρ' is secret. The local seed scratch is wiped on exit; the caller is
82
+ * responsible for the WASM-resident ρ' source buffer.
83
+ */
84
+ export function expandS(mx, sx, params, rhoPrime, s1Off, s2Off) {
85
+ const { k, l, eta } = params;
86
+ const xofPrfOff = mx.getXofPrfOffset();
87
+ const mlMem = new Uint8Array(mx.memory.buffer);
88
+ // PRF input: ρ'(64) ‖ idx_lo ‖ idx_hi
89
+ const seed = new Uint8Array(66);
90
+ seed.set(rhoPrime, 0);
91
+ try {
92
+ // s₁: ℓ polynomials at indices 0..ℓ-1
93
+ for (let r = 0; r < l; r++) {
94
+ seed[64] = r & 0xFF;
95
+ seed[65] = (r >>> 8) & 0xFF;
96
+ const sq = shake256Squeezer(sx, seed);
97
+ const off = s1Off + r * POLY_BYTES;
98
+ let ctr = 0;
99
+ while (ctr < 256) {
100
+ const block = sq.squeeze();
101
+ mlMem.set(block, xofPrfOff);
102
+ ctr += mx.rej_bounded_poly(off, ctr, xofPrfOff, sq.rate, eta);
103
+ }
104
+ }
105
+ // s₂: k polynomials at indices ℓ..ℓ+k-1
106
+ for (let r = 0; r < k; r++) {
107
+ const idx = r + l;
108
+ seed[64] = idx & 0xFF;
109
+ seed[65] = (idx >>> 8) & 0xFF;
110
+ const sq = shake256Squeezer(sx, seed);
111
+ const off = s2Off + r * POLY_BYTES;
112
+ let ctr = 0;
113
+ while (ctr < 256) {
114
+ const block = sq.squeeze();
115
+ mlMem.set(block, xofPrfOff);
116
+ ctr += mx.rej_bounded_poly(off, ctr, xofPrfOff, sq.rate, eta);
117
+ }
118
+ }
119
+ }
120
+ finally {
121
+ // Local seed buffer carries ρ' for the duration of this call; wipe
122
+ // even on early throw so it never persists in TS heap.
123
+ wipe(seed);
124
+ }
125
+ }
126
+ // Bitlen helper, bitlen(n) for n > 0 is floor(log2(n)) + 1, used to size
127
+ // the BitUnpack output width inside ExpandMask. Identical to the helper in
128
+ // keygen.ts; keeping a private copy here avoids cross-importing the keygen
129
+ // module just for one tiny utility.
130
+ function bitlen(n) {
131
+ let b = 0;
132
+ let x = n;
133
+ while (x > 0) {
134
+ b++;
135
+ x >>>= 1;
136
+ }
137
+ return b;
138
+ }
139
+ /**
140
+ * ExpandMask, FIPS 204 Algorithm 34.
141
+ *
142
+ * For r ∈ [0, ℓ):
143
+ * v ← SHAKE256(ρ'' ‖ IntegerToBytes(κ + r, 2), 32·c)
144
+ * y[r] ← BitUnpack(v, γ₁ − 1, γ₁)
145
+ *
146
+ * where c = 1 + bitlen(γ₁ − 1) is the per-coefficient byte width
147
+ * (18 when γ₁ = 2¹⁷, 20 when γ₁ = 2¹⁹). Output coefficients land in
148
+ * [-(γ₁ − 1), γ₁]; bit_unpack(a=γ₁−1, b=γ₁) covers exactly that range.
149
+ *
150
+ * y is produced in time domain at `yPvOff`. Sign_internal applies
151
+ * polyvec_ntt to y before the matrix-vector product  · NTT(y).
152
+ *
153
+ * ρ'' is secret (derived from K ‖ rnd ‖ μ). This function uses one
154
+ * one-shot SHAKE256 per polynomial (caller's full input is ≤ 168 B,
155
+ * fits in a single shake256HashConcat call); the SHAKE state is reset
156
+ * each iteration via shake256Init inside the helper, so no state
157
+ * carries between r values. The squeeze output lands in a TS-side
158
+ * buffer and is set into the WASM XOF/PRF region only long enough for
159
+ * bit_unpack to consume it.
160
+ */
161
+ export function expandMask(mx, sx, params, rhoPrimePrime, kappa, yPvOff) {
162
+ const { l, gamma1 } = params;
163
+ const c = 1 + bitlen(gamma1 - 1); // 18 or 20
164
+ const polyBytesV = 32 * c; // 576 or 640
165
+ const xofPrfOff = mx.getXofPrfOffset();
166
+ const mlMem = new Uint8Array(mx.memory.buffer);
167
+ const idxBytes = new Uint8Array(2);
168
+ for (let r = 0; r < l; r++) {
169
+ const n = kappa + r;
170
+ idxBytes[0] = n & 0xFF;
171
+ idxBytes[1] = (n >>> 8) & 0xFF;
172
+ // SHAKE256(ρ'' ‖ idx, 32·c). For γ₁ ≤ 2¹⁹ the output never exceeds
173
+ // 640 bytes, well within the 8192-byte XOF region.
174
+ const v = shake256HashConcat(sx, [rhoPrimePrime, idxBytes], polyBytesV);
175
+ try {
176
+ mlMem.set(v, xofPrfOff);
177
+ // bit_unpack(a=γ₁-1, b=γ₁): bitlen(a+b) = bitlen(2γ₁-1) = c.
178
+ // Decodes to coefficients in [-(γ₁-1), γ₁], Alg 34 / Alg 19.
179
+ mx.bit_unpack(yPvOff + r * 1024, xofPrfOff, gamma1 - 1, gamma1);
180
+ }
181
+ finally {
182
+ // v held the SHAKE256(ρ'' ‖ idx) squeeze bytes, ρ''-derived,
183
+ // i.e. secret. These bytes become y for this iteration, so
184
+ // recovery would unmask the signing nonce on the rejected path.
185
+ wipe(v);
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Build M' = domSep ‖ |ctx| ‖ ctx ‖ M.
3
+ *
4
+ * domSep = 0x00 for pure ML-DSA, 0x01 for HashML-DSA.
5
+ * Caller has already validated ctx.length ≤ 255.
6
+ */
7
+ export declare function constructMPrime(domSep: number, ctx: Uint8Array, M: Uint8Array): Uint8Array;
8
+ /**
9
+ * Build the HashML-DSA M' = 0x01 ‖ |ctx| ‖ ctx ‖ OID ‖ PH_M.
10
+ *
11
+ * FIPS 204 §5.4 / Algorithm 4 line 23 (sign) and Algorithm 5 line 18 (verify).
12
+ * The leading byte is 0x01 (vs 0x00 for pure ML-DSA), domain separation
13
+ * across pure / pre-hash modes per FIPS 204 §3.6.4. Caller has already
14
+ * validated ctx.length ≤ 255.
15
+ */
16
+ export declare function constructMPrimeHash(ctx: Uint8Array, oid: Uint8Array, PH_M: Uint8Array): Uint8Array;
@@ -0,0 +1,68 @@
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/format.ts
23
+ //
24
+ // External-API M' construction. Lives in its own file because HashML-DSA
25
+ // reuses the same byte layout with a different domain separator (0x01)
26
+ // and a hash-of-message tail.
27
+ //
28
+ // FIPS 204 Algorithm 2 line 10 (Sign) and Algorithm 3 line 5 (Verify):
29
+ // M' ← BytesToBits(IntegerToBytes(domSep, 1)
30
+ // ‖ IntegerToBytes(|ctx|, 1)
31
+ // ‖ ctx
32
+ // ‖ <message-bytes>)
33
+ //
34
+ // In a byte-oriented SHAKE wrapper, BytesToBits is a no-op, the absorbed
35
+ // bytes are the same. So we hand the absorber a contiguous Uint8Array
36
+ // laid out exactly as the spec describes, plus the domain-separator byte
37
+ // up front.
38
+ /**
39
+ * Build M' = domSep ‖ |ctx| ‖ ctx ‖ M.
40
+ *
41
+ * domSep = 0x00 for pure ML-DSA, 0x01 for HashML-DSA.
42
+ * Caller has already validated ctx.length ≤ 255.
43
+ */
44
+ export function constructMPrime(domSep, ctx, M) {
45
+ const out = new Uint8Array(2 + ctx.length + M.length);
46
+ out[0] = domSep & 0xFF;
47
+ out[1] = ctx.length & 0xFF;
48
+ out.set(ctx, 2);
49
+ out.set(M, 2 + ctx.length);
50
+ return out;
51
+ }
52
+ /**
53
+ * Build the HashML-DSA M' = 0x01 ‖ |ctx| ‖ ctx ‖ OID ‖ PH_M.
54
+ *
55
+ * FIPS 204 §5.4 / Algorithm 4 line 23 (sign) and Algorithm 5 line 18 (verify).
56
+ * The leading byte is 0x01 (vs 0x00 for pure ML-DSA), domain separation
57
+ * across pure / pre-hash modes per FIPS 204 §3.6.4. Caller has already
58
+ * validated ctx.length ≤ 255.
59
+ */
60
+ export function constructMPrimeHash(ctx, oid, PH_M) {
61
+ const out = new Uint8Array(2 + ctx.length + oid.length + PH_M.length);
62
+ out[0] = 0x01;
63
+ out[1] = ctx.length & 0xFF;
64
+ out.set(ctx, 2);
65
+ out.set(oid, 2 + ctx.length);
66
+ out.set(PH_M, 2 + ctx.length + oid.length);
67
+ return out;
68
+ }