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,366 @@
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/index.ts
23
+ //
24
+ // ECDSA-P256 public API. FIPS 186-5 §6 over the NIST P-256 curve
25
+ // (SP 800-186 §3.2.1.3). Hedged-or-deterministic K per RFC 6979 §3.2
26
+ // and draft-irtf-cfrg-det-sigs-with-noise-05. Strict-S verify (low-S
27
+ // enforced).
28
+ //
29
+ // Uncompressed pk per SEC 1 §2.3.4 is available via
30
+ // `pointDecompress` and `EcdsaP256.keygenUncompressed`. The composite
31
+ // ML-DSA + ECDSA suites need the 65-byte `0x04 || X || Y` form.
32
+ import { getInstance, initModule, isInitialized, _assertNotOwned } from '../init.js';
33
+ import { randomBytes, wipe } from '../utils.js';
34
+ import { SigningError } from '../errors.js';
35
+ import { validateSeed, validateSecretKey, validatePublicKey, validateMessageHash, validateSignature, validateEntropy, } from './validate.js';
36
+ /**
37
+ * Initialise the p256 WASM module. Loads the underlying binary
38
+ * (scalar, no SIMD) into the `p256` slot.
39
+ */
40
+ export async function ecdsaP256Init(source) {
41
+ return initModule('p256', source);
42
+ }
43
+ export { isInitialized };
44
+ export { encodeEcPrivateKey, decodeEcPrivateKey } from './ecprivatekey-der.js';
45
+ // ── I/O staging layout ─────────────────────────────────────────────────────
46
+ //
47
+ // The p256 module's mutable buffer region ends at BUFFER_END = 7054
48
+ // (see src/asm/p256/buffers.ts). The TS layer stages caller-supplied
49
+ // inputs and reads outputs from a fixed region above that. The module
50
+ // is allocated 3 pages (196608 bytes); the I/O region stretches from
51
+ // 8192 to the end of linear memory.
52
+ //
53
+ // Each slot is sized to its maximum useful content and rounded to a
54
+ // 32-byte boundary for layout clarity. The WASM exports read every
55
+ // input once at the start of execution, so co-locating per-call
56
+ // inputs in this region is safe.
57
+ const IO_BASE = 8192;
58
+ const SEED_STAGE = IO_BASE; // 32 bytes (seed for keygen, sk for sign)
59
+ const PK_STAGE = IO_BASE + 64; // 33 bytes (compressed pk, SEC 1 §2.3.3)
60
+ const SIG_STAGE = IO_BASE + 128; // 64 bytes (raw r||s)
61
+ const MSG_HASH_STAGE = IO_BASE + 192; // 32 bytes (SHA-256 digest)
62
+ const RND_STAGE = IO_BASE + 224; // 32 bytes (per-call entropy Z)
63
+ const POINT_STAGE = IO_BASE + 256; // 96 bytes (projective X:Y:Z, decompression output)
64
+ const PK_XY_STAGE = IO_BASE + 352; // 64 bytes (BE X || Y, staging for SEC 1 §2.3.4 emit)
65
+ function ioWipe(mx) {
66
+ // Zero the entire TS-managed staging region. wipeBuffers covers
67
+ // MUTABLE_START..BUFFER_END only; the I/O region above lives outside
68
+ // that range and must be scrubbed by the wrapper.
69
+ new Uint8Array(mx.memory.buffer).fill(0, IO_BASE, mx.memory.buffer.byteLength);
70
+ }
71
+ function rethrowTrap(err, discriminator, message) {
72
+ // The WASM module's fault-injection check (sign's pk-mismatch path)
73
+ // terminates with `unreachable`, which surfaces as
74
+ // WebAssembly.RuntimeError. Re-throw any such trap as a typed
75
+ // SigningError so callers can branch on it.
76
+ if (err instanceof WebAssembly.RuntimeError)
77
+ throw new SigningError(discriminator, message);
78
+ throw err;
79
+ }
80
+ /**
81
+ * Normalise a caller-supplied public key to the 33-byte compressed
82
+ * SEC 1 §2.3.3 form required by the WASM ABI. 33-byte inputs are
83
+ * returned as-is; 65-byte inputs (uncompressed, SEC 1 §2.3.4,
84
+ * 0x04 || x || y) are converted by dropping y and setting the prefix
85
+ * to 0x02 or 0x03 based on the parity of the y coordinate (LSB of
86
+ * y[31] in big-endian). Constant-time is not required: pk is public.
87
+ *
88
+ * Validation of the prefix byte happens at the WASM layer
89
+ * (`pointDecompress` rejects anything that is not 0x02 / 0x03 with an
90
+ * on-curve x), so this helper does not branch on whether the input
91
+ * was a strict uncompressed (0x04) or hybrid (0x06 / 0x07, SEC 1
92
+ * §2.3.5) encoding.
93
+ */
94
+ function normalizePublicKey(pk) {
95
+ if (pk.length === 33)
96
+ return pk;
97
+ // pk has already passed validatePublicKey, so length is 33 or 65.
98
+ const out = new Uint8Array(33);
99
+ out[0] = 0x02 | (pk[64] & 0x01);
100
+ out.set(pk.subarray(1, 33), 1);
101
+ return out;
102
+ }
103
+ /**
104
+ * Decompress a 33-byte SEC 1 §2.3.3 compressed P-256 public key to the
105
+ * 65-byte SEC 1 §2.3.4 uncompressed encoding `0x04 || X || Y`.
106
+ *
107
+ * The compressed form encodes only the affine x coordinate plus a
108
+ * single parity bit (in the prefix byte: 0x02 even-y, 0x03 odd-y).
109
+ * Recovery of y solves the curve equation
110
+ * `y² = x³ - 3x + b mod p` (SP 800-186 §3.2.1.3, P-256 has a = -3)
111
+ * and selects the y root whose parity matches the prefix. The
112
+ * substrate runs the modular square root inside the p256 WASM
113
+ * (`feSqrt` via the p ≡ 3 (mod 4) shortcut, x^((p+1)/4)); rejecting
114
+ * invalid inputs that have no square root or whose recovered (x, y)
115
+ * lies off-curve.
116
+ *
117
+ * Rejection cases (all throw `SigningError('sig-malformed-input')`):
118
+ * - prefix byte not in {0x02, 0x03}
119
+ * - x coordinate is not the x of any on-curve point (no quadratic
120
+ * residue exists for `x³ - 3x + b mod p`)
121
+ *
122
+ * Length / shape rejections throw `TypeError` / `RangeError` per the
123
+ * usual leviathan-crypto contract-violation posture.
124
+ *
125
+ * Requires `init({ p256: ... })`. Uses the same p256 module singleton
126
+ * as `EcdsaP256`; concurrency-safe alongside non-stateful uses (the
127
+ * `_assertNotOwned` check fires if a stateful instance is holding
128
+ * the module).
129
+ *
130
+ * @param pk33 33-byte compressed pk per SEC 1 §2.3.3
131
+ * @returns 65-byte uncompressed pk per SEC 1 §2.3.4 (0x04 || X || Y)
132
+ */
133
+ export function pointDecompress(pk33) {
134
+ if (!isInitialized('p256'))
135
+ throw new Error('leviathan-crypto: call init({ p256: ... }) before using pointDecompress');
136
+ if (!(pk33 instanceof Uint8Array))
137
+ throw new TypeError('leviathan-crypto: ecdsa-p256 compressed public key must be a Uint8Array');
138
+ if (pk33.length !== 33)
139
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 compressed public key must be 33 bytes (got ${pk33.length})`);
140
+ if (pk33[0] !== 0x02 && pk33[0] !== 0x03)
141
+ throw new SigningError('sig-malformed-input', 'leviathan-crypto: ecdsa-p256 compressed public key prefix must be 0x02 or 0x03 per SEC 1 §2.3.3 '
142
+ + `(got 0x${pk33[0].toString(16).padStart(2, '0')})`);
143
+ _assertNotOwned('p256');
144
+ const mx = getInstance('p256').exports;
145
+ const mem = new Uint8Array(mx.memory.buffer);
146
+ mem.set(pk33, PK_STAGE);
147
+ try {
148
+ const ok = mx.pointDecompress(POINT_STAGE, PK_STAGE);
149
+ if (ok !== 1)
150
+ throw new SigningError('sig-malformed-input', 'leviathan-crypto: ecdsa-p256 compressed public key x coordinate has no on-curve y '
151
+ + '(point decompression failed per SEC 1 §2.3.4)');
152
+ // pointDecompress writes (X : Y : Z = 1) in the FE limb form
153
+ // at POINT_STAGE..+96. feToBytes converts each FE to 32-byte BE
154
+ // per SP 800-186 §3.2.1.3 coordinate encoding.
155
+ mx.feToBytes(PK_XY_STAGE, POINT_STAGE); // X (32 BE)
156
+ mx.feToBytes(PK_XY_STAGE + 32, POINT_STAGE + 32); // Y (32 BE)
157
+ const out = new Uint8Array(65);
158
+ out[0] = 0x04;
159
+ out.set(mem.subarray(PK_XY_STAGE, PK_XY_STAGE + 64), 1);
160
+ return out;
161
+ }
162
+ finally {
163
+ ioWipe(mx);
164
+ mx.wipeBuffers();
165
+ }
166
+ }
167
+ export class EcdsaP256 {
168
+ constructor() {
169
+ if (!isInitialized('p256'))
170
+ throw new Error('leviathan-crypto: call init({ p256: ... }) before using EcdsaP256');
171
+ }
172
+ get mx() {
173
+ return getInstance('p256').exports;
174
+ }
175
+ /**
176
+ * Deterministic ECDSA-P256 key generation from a 32-byte seed.
177
+ * d = seed mod n per FIPS 186-5 §A.4.2 (testing-candidates style,
178
+ * single candidate). pk = [d]G compressed to 33 bytes per SEC 1
179
+ * §2.3.3. The vanishingly rare seed mod n == 0 case traps in the
180
+ * WASM and surfaces as a SigningError here.
181
+ *
182
+ * @param seed 32-byte BE input
183
+ * @returns 33-byte compressed pk and a fresh 32-byte copy of the
184
+ * secret scalar d (sk === seed for this derivation, the
185
+ * caller may use either as the private value).
186
+ */
187
+ keygenDerand(seed) {
188
+ _assertNotOwned('p256');
189
+ validateSeed(seed);
190
+ const mx = this.mx;
191
+ const mem = new Uint8Array(mx.memory.buffer);
192
+ mem.set(seed, SEED_STAGE);
193
+ try {
194
+ try {
195
+ mx.ecdsaKeygen(SEED_STAGE, PK_STAGE);
196
+ }
197
+ catch (err) {
198
+ rethrowTrap(err, 'sig-malformed-input', 'leviathan-crypto: ecdsa-p256 keygen aborted, seed mod n is zero '
199
+ + '(2^-256 probability event; supply a different seed)');
200
+ }
201
+ const publicKey = mem.slice(PK_STAGE, PK_STAGE + 33);
202
+ const secretKey = new Uint8Array(32);
203
+ secretKey.set(seed);
204
+ return { publicKey, secretKey };
205
+ }
206
+ finally {
207
+ ioWipe(mx);
208
+ mx.wipeBuffers();
209
+ }
210
+ }
211
+ /** Random ECDSA-P256 key generation, wraps `keygenDerand` with `randomBytes(32)`. */
212
+ keygen() {
213
+ const seed = randomBytes(32);
214
+ try {
215
+ return this.keygenDerand(seed);
216
+ }
217
+ finally {
218
+ wipe(seed);
219
+ }
220
+ }
221
+ /**
222
+ * Key generation that returns the public key in the 65-byte SEC 1
223
+ * §2.3.4 uncompressed encoding `0x04 || X || Y`, rather than the
224
+ * 33-byte compressed form `keygen` / `keygenDerand` return. The
225
+ * secret-key half is the same 32-byte raw scalar `d`.
226
+ *
227
+ * Internally runs `keygen` (or `keygenDerand` if a seed is supplied)
228
+ * to obtain the compressed pk, then `pointDecompress` to expand it.
229
+ * The compressed intermediate is wiped before return.
230
+ *
231
+ * @param seed Optional 32-byte seed; passes through to `keygenDerand`
232
+ * when present, falls back to `keygen` (CSPRNG seed) when
233
+ * omitted.
234
+ */
235
+ keygenUncompressed(seed) {
236
+ const kp = seed === undefined ? this.keygen() : this.keygenDerand(seed);
237
+ try {
238
+ const publicKey = pointDecompress(kp.publicKey);
239
+ return { publicKey, secretKey: kp.secretKey };
240
+ }
241
+ catch (err) {
242
+ wipe(kp.secretKey);
243
+ throw err;
244
+ }
245
+ finally {
246
+ wipe(kp.publicKey);
247
+ }
248
+ }
249
+ /**
250
+ * Hedged-or-deterministic ECDSA-P256 sign per FIPS 186-5 §6.4 with
251
+ * RFC 6979 §3.5 low-S normalisation. The K nonce is derived per
252
+ * RFC 6979 §3.2 (deterministic) when `rnd` is all-zero, or per
253
+ * draft-irtf-cfrg-det-sigs-with-noise-05 (hedged) otherwise. The
254
+ * hedged path is the recommended default; pass `randomBytes(32)`.
255
+ *
256
+ * The WASM re-derives pk = [d]G internally and compares it against
257
+ * the caller-supplied `pk`. A mismatch traps via `unreachable` and
258
+ * is rethrown as `SigningError('sig-malformed-input')`. This
259
+ * defends against fault injection that would bias the per-signature
260
+ * randomness derivation by forcing the caller to also know pk.
261
+ *
262
+ * @param sk 32-byte secret scalar d
263
+ * @param pk 33-byte compressed or 65-byte uncompressed pk;
264
+ * cross-checked by WASM after derivation
265
+ * @param msgHash 32-byte SHA-256(M) digest (caller-computed)
266
+ * @param rnd 32-byte per-call entropy Z; all-zero selects
267
+ * deterministic RFC 6979 §3.2, non-zero selects
268
+ * the hedged path
269
+ * @returns 64-byte raw r || s signature, low-S normalised
270
+ * @throws SigningError('sig-malformed-input') on pk-mismatch
271
+ * (fault-injection trap)
272
+ */
273
+ sign(sk, pk, msgHash, rnd) {
274
+ _assertNotOwned('p256');
275
+ validateSecretKey(sk);
276
+ validatePublicKey(pk);
277
+ validateMessageHash(msgHash);
278
+ validateEntropy(rnd);
279
+ const pkC = normalizePublicKey(pk);
280
+ const mx = this.mx;
281
+ const mem = new Uint8Array(mx.memory.buffer);
282
+ mem.set(sk, SEED_STAGE);
283
+ mem.set(pkC, PK_STAGE);
284
+ mem.set(msgHash, MSG_HASH_STAGE);
285
+ mem.set(rnd, RND_STAGE);
286
+ try {
287
+ try {
288
+ mx.ecdsaSign(SEED_STAGE, PK_STAGE, MSG_HASH_STAGE, RND_STAGE, SIG_STAGE);
289
+ }
290
+ catch (err) {
291
+ rethrowTrap(err, 'sig-malformed-input', 'leviathan-crypto: ecdsa-p256 sign aborted, pk does not match the pk derived from sk '
292
+ + '(likely fault injection or caller misuse)');
293
+ }
294
+ return mem.slice(SIG_STAGE, SIG_STAGE + 64);
295
+ }
296
+ finally {
297
+ ioWipe(mx);
298
+ mx.wipeBuffers();
299
+ }
300
+ }
301
+ /**
302
+ * Suite-only: hedged-or-deterministic sign that derives pk
303
+ * internally and skips the fault-injection cross-check. See
304
+ * AGENTS.md "SignatureSuite lifecycle". Underscore-prefixed,
305
+ * not part of the public API.
306
+ */
307
+ _signInternalPk(sk, msgHash, rnd) {
308
+ _assertNotOwned('p256');
309
+ validateSecretKey(sk);
310
+ validateMessageHash(msgHash);
311
+ validateEntropy(rnd);
312
+ const mx = this.mx;
313
+ const mem = new Uint8Array(mx.memory.buffer);
314
+ mem.set(sk, SEED_STAGE);
315
+ mem.set(msgHash, MSG_HASH_STAGE);
316
+ mem.set(rnd, RND_STAGE);
317
+ try {
318
+ mx.ecdsaSignInternalPk(SEED_STAGE, MSG_HASH_STAGE, RND_STAGE, SIG_STAGE);
319
+ return mem.slice(SIG_STAGE, SIG_STAGE + 64);
320
+ }
321
+ finally {
322
+ ioWipe(mx);
323
+ mx.wipeBuffers();
324
+ }
325
+ }
326
+ /**
327
+ * Strict ECDSA-P256 verify per FIPS 186-5 §6.5 with low-S
328
+ * enforcement (RFC 6979 §3.5). Returns `true` on success, `false`
329
+ * on every signature failure mode: off-curve / identity pk, r or
330
+ * s out of [1, n-1], high-S, or the signature equation failing.
331
+ * Throws only on caller-side contract violations (wrong-length
332
+ * inputs).
333
+ *
334
+ * @param pk 33-byte compressed or 65-byte uncompressed pk
335
+ * @param msgHash 32-byte SHA-256(M) digest
336
+ * @param sig 64-byte raw r || s (use `ecdsaSignatureFromDer`
337
+ * to convert DER-encoded signatures first)
338
+ */
339
+ verify(pk, msgHash, sig) {
340
+ _assertNotOwned('p256');
341
+ validatePublicKey(pk);
342
+ validateMessageHash(msgHash);
343
+ validateSignature(sig);
344
+ const pkC = normalizePublicKey(pk);
345
+ const mx = this.mx;
346
+ const mem = new Uint8Array(mx.memory.buffer);
347
+ mem.set(pkC, PK_STAGE);
348
+ mem.set(msgHash, MSG_HASH_STAGE);
349
+ mem.set(sig, SIG_STAGE);
350
+ try {
351
+ return mx.ecdsaVerify(PK_STAGE, MSG_HASH_STAGE, SIG_STAGE) === 1;
352
+ }
353
+ finally {
354
+ ioWipe(mx);
355
+ mx.wipeBuffers();
356
+ }
357
+ }
358
+ dispose() {
359
+ // Idempotent; per-method wipe runs anyway.
360
+ try {
361
+ this.mx.wipeBuffers();
362
+ ioWipe(this.mx);
363
+ }
364
+ catch { /* idempotent */ }
365
+ }
366
+ }
@@ -0,0 +1,31 @@
1
+ export interface EcdsaP256KeyPair {
2
+ /** 33-byte compressed public key per SEC 1 §2.3.3, 0x02 / 0x03 || x. */
3
+ publicKey: Uint8Array;
4
+ /** 32-byte secret scalar d ∈ [1, n-1] per FIPS 186-5 §6.2.1, private-key generation. */
5
+ secretKey: Uint8Array;
6
+ }
7
+ /**
8
+ * The ECDSA-P256-relevant subset of the p256 WASM exports.
9
+ *
10
+ * The p256 module hosts the full elliptic-curve substrate (field,
11
+ * scalar, point, RFC 6979 K derivation, embedded SHA-256 + HMAC) and
12
+ * the four high-level ECDSA entry points (keygen, sign, signInternalPk,
13
+ * verify). The wrapper additionally drives `pointDecompress` + `feToBytes`
14
+ * for the SEC 1 §2.3.4 uncompressed-pk emission path; those two
15
+ * substrate exports are public because no high-level ABI covers
16
+ * "given a compressed pk, return its uncompressed form" without
17
+ * round-tripping through verify. Substrate test hooks live outside
18
+ * the consumer-facing ABI.
19
+ */
20
+ export interface EcdsaP256Exports {
21
+ memory: WebAssembly.Memory;
22
+ getModuleId: () => number;
23
+ getMemoryPages: () => number;
24
+ feToBytes: (outOff: number, feOff: number) => void;
25
+ pointDecompress: (outOff: number, srcOff: number) => number;
26
+ ecdsaKeygen: (seedOff: number, pkOff: number) => void;
27
+ ecdsaSign: (skOff: number, pkOff: number, msgHashOff: number, rndOff: number, sigOff: number) => void;
28
+ ecdsaSignInternalPk: (skOff: number, msgHashOff: number, rndOff: number, sigOff: number) => void;
29
+ ecdsaVerify: (pkOff: number, msgHashOff: number, sigOff: number) => number;
30
+ wipeBuffers: () => void;
31
+ }
@@ -0,0 +1,28 @@
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/types.ts
23
+ //
24
+ // ECDSA-P256 type surface: the WASM export interface for the p256
25
+ // module and the public key-pair shape returned by keygen /
26
+ // keygenDerand. FIPS 186-5 §6, ECDSA and SP 800-186 §3.2.1.3, P-256
27
+ // parameters.
28
+ export {};
@@ -0,0 +1,18 @@
1
+ export declare function validateSeed(seed: Uint8Array): void;
2
+ export declare function validateSecretKey(sk: Uint8Array): void;
3
+ /**
4
+ * Accepts both 33-byte compressed (SEC 1 §2.3.3, 0x02 / 0x03 || x) and
5
+ * 65-byte uncompressed (SEC 1 §2.3.4, 0x04 || x || y) inputs. The WASM
6
+ * ABI consumes only the 33-byte compressed form; the wrapper layer
7
+ * normalises 65-byte inputs to compressed before staging into WASM
8
+ * memory (see `normalizePublicKey` in `./index.ts`). Constant-time is
9
+ * not required at pk import since pk is public material.
10
+ *
11
+ * Length-only at this layer; on-curve / canonical-x checks happen in
12
+ * `pointDecompress` (verify) or via the fault-injection pk-mismatch
13
+ * trap (sign).
14
+ */
15
+ export declare function validatePublicKey(pk: Uint8Array): void;
16
+ export declare function validateMessageHash(h: Uint8Array): void;
17
+ export declare function validateSignature(sig: Uint8Array): void;
18
+ export declare function validateEntropy(rnd: Uint8Array): void;
@@ -0,0 +1,92 @@
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/validate.ts
23
+ //
24
+ // ECDSA-P256 caller-side input validation. Pure length / type checks;
25
+ // curve membership, canonical-encoding rejection, scalar-range checks
26
+ // ([1, n-1] for d, r, s), low-S enforcement, and the fault-injection
27
+ // pk-mismatch trap all live inside the WASM layer.
28
+ //
29
+ // TypeError for non-Uint8Array, RangeError for wrong-length. Matches
30
+ // the ed25519 / mldsa / mlkem validation conventions.
31
+ export function validateSeed(seed) {
32
+ if (!(seed instanceof Uint8Array))
33
+ throw new TypeError('leviathan-crypto: ecdsa-p256 seed must be a Uint8Array');
34
+ if (seed.length !== 32)
35
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 seed must be 32 bytes (got ${seed.length})`);
36
+ }
37
+ export function validateSecretKey(sk) {
38
+ // 32-byte big-endian scalar d per FIPS 186-5 §6.2.1, private-key
39
+ // generation. The d ∈ [1, n-1] range check lives in WASM
40
+ // (scalarIsZero rejection plus scalar arithmetic mod n).
41
+ if (!(sk instanceof Uint8Array))
42
+ throw new TypeError('leviathan-crypto: ecdsa-p256 secret key must be a Uint8Array');
43
+ if (sk.length !== 32)
44
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 secret key must be 32 bytes (got ${sk.length})`);
45
+ }
46
+ /**
47
+ * Accepts both 33-byte compressed (SEC 1 §2.3.3, 0x02 / 0x03 || x) and
48
+ * 65-byte uncompressed (SEC 1 §2.3.4, 0x04 || x || y) inputs. The WASM
49
+ * ABI consumes only the 33-byte compressed form; the wrapper layer
50
+ * normalises 65-byte inputs to compressed before staging into WASM
51
+ * memory (see `normalizePublicKey` in `./index.ts`). Constant-time is
52
+ * not required at pk import since pk is public material.
53
+ *
54
+ * Length-only at this layer; on-curve / canonical-x checks happen in
55
+ * `pointDecompress` (verify) or via the fault-injection pk-mismatch
56
+ * trap (sign).
57
+ */
58
+ export function validatePublicKey(pk) {
59
+ if (!(pk instanceof Uint8Array))
60
+ throw new TypeError('leviathan-crypto: ecdsa-p256 public key must be a Uint8Array');
61
+ if (pk.length !== 33 && pk.length !== 65)
62
+ throw new RangeError('leviathan-crypto: ecdsa-p256 public key must be 33 bytes (compressed, SEC 1 §2.3.3) '
63
+ + `or 65 bytes (uncompressed, SEC 1 §2.3.4) (got ${pk.length})`);
64
+ }
65
+ export function validateMessageHash(h) {
66
+ // ECDSA-P256 takes a 32-byte SHA-256 digest; the suite layer drives
67
+ // the actual hashing. FIPS 186-5 §6.4.1 requires hlen = qlen = 256
68
+ // for P-256 + SHA-256 so the bits2int truncation is a no-op.
69
+ if (!(h instanceof Uint8Array))
70
+ throw new TypeError('leviathan-crypto: ecdsa-p256 message hash must be a Uint8Array');
71
+ if (h.length !== 32)
72
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 message hash must be 32 bytes (got ${h.length})`);
73
+ }
74
+ export function validateSignature(sig) {
75
+ // Raw r || s wire form, 64 bytes total. DER-wrapped signatures pass
76
+ // through `ecdsaSignatureFromDer` first; that helper produces a
77
+ // 64-byte output. The r, s ∈ [1, n-1] and low-S checks happen in
78
+ // WASM during ecdsaVerify.
79
+ if (!(sig instanceof Uint8Array))
80
+ throw new TypeError('leviathan-crypto: ecdsa-p256 signature must be a Uint8Array');
81
+ if (sig.length !== 64)
82
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 signature must be 64 bytes raw r||s (got ${sig.length})`);
83
+ }
84
+ export function validateEntropy(rnd) {
85
+ // Per-call entropy Z, 32 bytes. All-zero selects the deterministic
86
+ // RFC 6979 §3.2 K-derivation path; non-zero selects the hedged
87
+ // path per draft-irtf-cfrg-det-sigs-with-noise-05.
88
+ if (!(rnd instanceof Uint8Array))
89
+ throw new TypeError('leviathan-crypto: ecdsa-p256 entropy must be a Uint8Array');
90
+ if (rnd.length !== 32)
91
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 entropy must be 32 bytes (got ${rnd.length})`);
92
+ }
@@ -0,0 +1 @@
1
+ export { WASM_GZ_BASE64 as curve25519Wasm, WASM_GZ_BASE64 as ed25519Wasm, } from '../embedded/curve25519.js';
@@ -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/ed25519/embedded.ts
23
+ //
24
+ // Exports the gzip+base64 curve25519 WASM blob for use as a WasmSource.
25
+ // Ed25519 and X25519 share the same curve25519 WASM binary; both the
26
+ // `leviathan-crypto/ed25519/embedded` and `leviathan-crypto/x25519/embedded`
27
+ // subpaths re-export the same blob under three names: `curve25519Wasm`
28
+ // (canonical), `ed25519Wasm`, and `x25519Wasm` (aliases that read more
29
+ // naturally in the matching subpath context). All three resolve to the
30
+ // identical underlying string; tree-shaking is unaffected.
31
+ export { WASM_GZ_BASE64 as curve25519Wasm, WASM_GZ_BASE64 as ed25519Wasm, } from '../embedded/curve25519.js';
@@ -0,0 +1,70 @@
1
+ import { isInitialized } from '../init.js';
2
+ import type { WasmSource } from '../wasm-source.js';
3
+ import type { Ed25519KeyPair } from './types.js';
4
+ /**
5
+ * Initialise the curve25519 WASM module under the `ed25519` alias.
6
+ * Equivalent to `x25519Init(source)`; both target the same WASM module
7
+ * and the init layer de-dupes when given identical sources.
8
+ */
9
+ export declare function ed25519Init(source: WasmSource): Promise<void>;
10
+ export type { WasmSource };
11
+ export type { Ed25519KeyPair, Ed25519Exports } from './types.js';
12
+ export { isInitialized };
13
+ export declare class Ed25519 {
14
+ constructor();
15
+ private get mx();
16
+ /**
17
+ * Deterministic Ed25519 key generation, RFC 8032 §5.1.5.
18
+ * @param seed 32-byte secret seed
19
+ * @returns 32-byte verifying key and a fresh 32-byte secret-key copy
20
+ * of the supplied seed (the spec defines sk = seed).
21
+ */
22
+ keygenDerand(seed: Uint8Array): Ed25519KeyPair;
23
+ /** Random Ed25519 key generation, wraps `keygenDerand` with `randomBytes(32)`. */
24
+ keygen(): Ed25519KeyPair;
25
+ /**
26
+ * Pure Ed25519 sign, RFC 8032 §5.1.6.
27
+ *
28
+ * The WASM re-derives pk from `sk` internally and compares it against
29
+ * the caller-supplied `pk`; a mismatch traps via `unreachable` and is
30
+ * rethrown as `SigningError('sig-ed25519-pk-mismatch')`. This defends
31
+ * against fault injection that bias the per-signature randomness
32
+ * derivation by forcing the caller to also know pk.
33
+ */
34
+ sign(sk: Uint8Array, pk: Uint8Array, M: Uint8Array): Uint8Array;
35
+ /**
36
+ * Ed25519ph sign, RFC 8032 §5.1.7 (prehash, dom2 phflag=1).
37
+ *
38
+ * Caller supplies the 64-byte SHA-512(M) digest; the library does not
39
+ * compute it. Same pk-mismatch fault-injection trap as `sign`.
40
+ */
41
+ signPrehashed(sk: Uint8Array, pk: Uint8Array, digest: Uint8Array, ctx: Uint8Array): Uint8Array;
42
+ /**
43
+ * Suite-only: pure Ed25519 sign that derives pk internally and
44
+ * skips the fault-injection cross-check. See AGENTS.md
45
+ * "SignatureSuite lifecycle". Underscore-prefixed, not part of
46
+ * the public API.
47
+ */
48
+ _signInternalPk(sk: Uint8Array, M: Uint8Array): Uint8Array;
49
+ /**
50
+ * Suite-only: Ed25519ph mirror of `_signInternalPk`. See
51
+ * AGENTS.md "SignatureSuite lifecycle".
52
+ */
53
+ _signPrehashedInternalPk(sk: Uint8Array, digest: Uint8Array, ctx: Uint8Array): Uint8Array;
54
+ /**
55
+ * Strict pure Ed25519 verify, RFC 8032 §5.1.7 / FIPS 186-5 §7.6.4.
56
+ *
57
+ * Returns `true` on success, `false` on every signature failure mode:
58
+ * off-curve pk, non-canonical R, non-canonical S (>= L), small-order
59
+ * pk, or signature equation inequality. Throws only on caller-side
60
+ * contract violations (wrong-length pk / M / sig).
61
+ */
62
+ verify(pk: Uint8Array, M: Uint8Array, sig: Uint8Array): boolean;
63
+ /**
64
+ * Strict Ed25519ph verify, RFC 8032 §5.1.7 prehash. Same rejection
65
+ * conditions as {@link verify} plus the dom2(F=1, ctx) prefix on the
66
+ * per-spec SHA-512 inputs (handled inside the WASM).
67
+ */
68
+ verifyPrehashed(pk: Uint8Array, digest: Uint8Array, ctx: Uint8Array, sig: Uint8Array): boolean;
69
+ dispose(): void;
70
+ }