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,192 @@
1
+ // ▄▄▄▄▄▄▄▄▄▄
2
+ // ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
3
+ // ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
4
+ // ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
5
+ // ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
6
+ // ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
7
+ // ███████▌ ▀██▀ ███
8
+ // ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
9
+ // ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
10
+ // ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
11
+ // ▀████▄ ▄██▄
12
+ // ▐████ ▐███ Author: xero (https://x-e.ro)
13
+ // ▄▄██████████ ▐███ ▄▄ License: MIT
14
+ // ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
15
+ // ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
16
+ // ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
17
+ // ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
18
+ // █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
19
+ // ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
20
+ // ▀█████▀▀
21
+ //
22
+ // src/ts/ecdsa/der.ts
23
+ //
24
+ // ECDSA signature DER ↔ raw r||s conversion utility, RFC 3279 §2.2.3,
25
+ // ECDSA Signature Algorithm.
26
+ //
27
+ // Ecdsa-Sig-Value ::= SEQUENCE {
28
+ // r INTEGER,
29
+ // s INTEGER
30
+ // }
31
+ //
32
+ // The library's WASM ABI consumes and produces raw 64-byte r || s
33
+ // signatures. DER is a side utility for callers who need X.509 / JWS /
34
+ // TLS interop. The encoder / decoder is hand-rolled against X.690 §8.3
35
+ // (INTEGER) and §8.9 (SEQUENCE); leviathan-crypto is zero-dependency
36
+ // so no external ASN.1 parser is used.
37
+ //
38
+ // Strict DER (not BER). The decoder rejects:
39
+ // - non-minimal length encodings (long-form length when short-form
40
+ // would suffice; X.690 §10.1, definite-length encoding)
41
+ // - excess leading-zero bytes inside INTEGER content (X.690 §8.3.2)
42
+ // - negative INTEGERs (sign bit set on the leading content octet
43
+ // without a 0x00 sign-pad; ECDSA r, s ∈ [1, n-1] are positive)
44
+ // - INTEGER content longer than 33 bytes (32-byte BE scalar plus an
45
+ // optional 0x00 sign-pad)
46
+ // - trailing bytes after the outer SEQUENCE
47
+ // - wrong tags (outer 0x30 SEQUENCE, inner 0x02 INTEGER)
48
+ //
49
+ // The from-DER path never throws on a semantic value problem
50
+ // (r = 0, s = 0, high-s, off-range); those are verify-time rejections
51
+ // in the WASM. Only DER syntax violations throw.
52
+ import { SigningError } from '../errors.js';
53
+ const SEQUENCE_TAG = 0x30;
54
+ const INTEGER_TAG = 0x02;
55
+ function reject(detail) {
56
+ throw new SigningError('sig-malformed-input', `leviathan-crypto: ecdsa-p256 DER signature ${detail}`);
57
+ }
58
+ /**
59
+ * Encode a positive 32-byte BE scalar (r or s component) as a DER
60
+ * INTEGER TLV. Strips leading zero bytes to the minimal encoding per
61
+ * X.690 §8.3.2, then prepends a single 0x00 if the high bit of the
62
+ * resulting first content byte is set, so the INTEGER remains positive.
63
+ *
64
+ * The component length is at most 33 bytes (32-byte scalar + optional
65
+ * sign-pad), so the DER length octet always fits in short-form.
66
+ */
67
+ function encodeInteger(scalarBE) {
68
+ let start = 0;
69
+ while (start < scalarBE.length - 1 && scalarBE[start] === 0)
70
+ start++;
71
+ const stripped = scalarBE.subarray(start);
72
+ const needsPad = (stripped[0] & 0x80) !== 0;
73
+ const contentLen = stripped.length + (needsPad ? 1 : 0);
74
+ const out = new Uint8Array(2 + contentLen);
75
+ out[0] = INTEGER_TAG;
76
+ out[1] = contentLen;
77
+ if (needsPad) {
78
+ out[2] = 0;
79
+ out.set(stripped, 3);
80
+ }
81
+ else {
82
+ out.set(stripped, 2);
83
+ }
84
+ return out;
85
+ }
86
+ /**
87
+ * Decode one DER INTEGER TLV starting at `start`. Returns the strict-DER
88
+ * minimal content bytes (with any leading 0x00 sign-pad stripped) and
89
+ * the offset immediately past the INTEGER. Throws SigningError on any
90
+ * DER syntax violation.
91
+ */
92
+ function decodeInteger(der, start) {
93
+ if (start + 2 > der.length)
94
+ reject(`has a truncated INTEGER header at offset ${start}`);
95
+ if (der[start] !== INTEGER_TAG)
96
+ reject(`INTEGER tag at offset ${start} is 0x${der[start].toString(16).padStart(2, '0')}, expected 0x02`);
97
+ const lenByte = der[start + 1];
98
+ // Strict DER: ECDSA-P256 INTEGER content is at most 33 bytes, so the
99
+ // length octet is always short-form (high bit clear, value 1..127).
100
+ if (lenByte & 0x80)
101
+ reject(`INTEGER at offset ${start} uses long-form length encoding (forbidden for content < 128 bytes)`);
102
+ // Zero-length INTEGER content has no representable value; ASN.1
103
+ // requires at least one content octet (X.690 §8.3.1).
104
+ if (lenByte === 0)
105
+ reject(`INTEGER at offset ${start} has zero-length content`);
106
+ const contentStart = start + 2;
107
+ const contentEnd = contentStart + lenByte;
108
+ if (contentEnd > der.length)
109
+ reject(`INTEGER at offset ${start} extends past the outer SEQUENCE end`);
110
+ const content = der.subarray(contentStart, contentEnd);
111
+ // Minimal encoding: a leading 0x00 octet is permitted only when the
112
+ // next byte's high bit is set (sign-pad). Otherwise the 0x00 is
113
+ // excess per X.690 §8.3.2.
114
+ if (content[0] === 0x00 && content.length > 1 && (content[1] & 0x80) === 0)
115
+ reject(`INTEGER at offset ${start} has excess leading zero byte (non-minimal DER)`);
116
+ // ECDSA r, s ∈ [1, n-1] are positive integers. A first content
117
+ // octet with the high bit set means the ASN.1 INTEGER decodes as a
118
+ // negative two's-complement value; reject.
119
+ if ((content[0] & 0x80) !== 0)
120
+ reject(`INTEGER at offset ${start} is negative (high bit set on first content byte); ECDSA r, s are positive`);
121
+ const value = (content[0] === 0x00) ? content.subarray(1) : content;
122
+ return { value, next: contentEnd };
123
+ }
124
+ /**
125
+ * Convert a 64-byte raw r || s signature to DER per RFC 3279 §2.2.3.
126
+ * Output length is variable: 8 bytes minimum (r = s = 1 byte each, no
127
+ * sign-pad), 72 bytes maximum (both components 32 bytes with high bit
128
+ * set, each picking up a 0x00 sign-pad).
129
+ *
130
+ * @throws TypeError if `sig` is not a Uint8Array
131
+ * @throws RangeError if `sig.length !== 64`
132
+ */
133
+ export function ecdsaSignatureToDer(sig) {
134
+ if (!(sig instanceof Uint8Array))
135
+ throw new TypeError('leviathan-crypto: ecdsa-p256 raw signature must be a Uint8Array');
136
+ if (sig.length !== 64)
137
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 raw signature must be 64 bytes r||s (got ${sig.length})`);
138
+ const r = encodeInteger(sig.subarray(0, 32));
139
+ const s = encodeInteger(sig.subarray(32, 64));
140
+ const contentLen = r.length + s.length;
141
+ // SEQUENCE content is at most 2 * 35 = 70 bytes, so the SEQUENCE
142
+ // length octet is always short-form.
143
+ const out = new Uint8Array(2 + contentLen);
144
+ out[0] = SEQUENCE_TAG;
145
+ out[1] = contentLen;
146
+ out.set(r, 2);
147
+ out.set(s, 2 + r.length);
148
+ return out;
149
+ }
150
+ /**
151
+ * Convert a DER ECDSA-P256 signature to 64-byte raw r || s. Rejects
152
+ * any DER syntax violation via SigningError('sig-malformed-input'):
153
+ * see the file-level header for the rejection rules.
154
+ *
155
+ * Semantic value rejections (r = 0, s = 0, high-s, off-range) are
156
+ * deferred to the WASM verify path; this function only enforces DER
157
+ * structure.
158
+ *
159
+ * @throws TypeError if `der` is not a Uint8Array
160
+ * @throws SigningError('sig-malformed-input') on any DER syntax error
161
+ */
162
+ export function ecdsaSignatureFromDer(der) {
163
+ if (!(der instanceof Uint8Array))
164
+ throw new TypeError('leviathan-crypto: ecdsa-p256 DER signature must be a Uint8Array');
165
+ // 8 bytes is the absolute minimum: SEQUENCE(2) + INTEGER(0x01 0x01)
166
+ // + INTEGER(0x01 0x01) = 8. Anything shorter cannot represent two
167
+ // non-empty INTEGER components.
168
+ if (der.length < 8)
169
+ reject(`is shorter than the 8-byte minimum (got ${der.length} bytes)`);
170
+ if (der[0] !== SEQUENCE_TAG)
171
+ reject(`outer tag is 0x${der[0].toString(16).padStart(2, '0')}, expected 0x30 (SEQUENCE)`);
172
+ const seqLen = der[1];
173
+ // ECDSA-P256 SEQUENCE content is at most 70 bytes; strict DER
174
+ // requires short-form length encoding when < 128.
175
+ if (seqLen & 0x80)
176
+ reject('uses long-form length encoding for the outer SEQUENCE (forbidden for content < 128 bytes)');
177
+ if (2 + seqLen !== der.length)
178
+ reject(`outer SEQUENCE length ${seqLen} does not match input size (${der.length} bytes total)`);
179
+ const { value: r, next: afterR } = decodeInteger(der, 2);
180
+ const { value: s, next: end } = decodeInteger(der, afterR);
181
+ if (end !== der.length)
182
+ reject('has trailing bytes after the second INTEGER');
183
+ // Each component must fit in 32 bytes BE (P-256 scalar size).
184
+ if (r.length > 32)
185
+ reject(`r component is ${r.length} bytes, exceeds the 32-byte scalar size`);
186
+ if (s.length > 32)
187
+ reject(`s component is ${s.length} bytes, exceeds the 32-byte scalar size`);
188
+ const out = new Uint8Array(64);
189
+ out.set(r, 32 - r.length);
190
+ out.set(s, 64 - s.length);
191
+ return out;
192
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Encode a 32-byte P-256 secret scalar as DER `ECPrivateKey` per
3
+ * RFC 5915 §3. Output length is exactly 51 bytes; version is 1, the
4
+ * named-curve OID for secp256r1 (`1.2.840.10045.3.1.7`,
5
+ * SP 800-186 §3.2.1.3) is included in `parameters [0]`, and the
6
+ * `publicKey [1]` field is omitted.
7
+ *
8
+ * Byte-stable: same input scalar produces byte-identical output. No
9
+ * canonicalisation pass needed because every field has a single legal
10
+ * DER encoding under the strict-DER rules of X.690 §10.
11
+ *
12
+ * @throws TypeError if `scalar` is not a Uint8Array
13
+ * @throws RangeError if `scalar.length !== 32`
14
+ */
15
+ export declare function encodeEcPrivateKey(scalar: Uint8Array): Uint8Array;
16
+ /**
17
+ * Decode a DER `ECPrivateKey` (RFC 5915 §3) and return the 32-byte
18
+ * raw P-256 secret scalar.
19
+ *
20
+ * Strict-DER per X.690 §10 (Restrictions on the BER). Rejects the
21
+ * cases enumerated in the file-level header. Accepts (and ignores)
22
+ * an optional `publicKey [1]` field per RFC 5915 §3 lenient input;
23
+ * the scalar is the only return value.
24
+ *
25
+ * Any curve OID inside `parameters [0]` other than secp256r1
26
+ * (`1.2.840.10045.3.1.7`) is rejected. Decoders that need other
27
+ * curves must use a different parser; this library is P-256 only.
28
+ *
29
+ * @throws TypeError if `der` is not a Uint8Array
30
+ * @throws Error on any DER syntax violation or unsupported parameter
31
+ */
32
+ export declare function decodeEcPrivateKey(der: Uint8Array): Uint8Array;
@@ -0,0 +1,230 @@
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/ecprivatekey-der.ts
23
+ //
24
+ // DER `ECPrivateKey` (RFC 5915 §3, Elliptic Curve Private Key Structure)
25
+ // codec for P-256. The library's WASM ABI stores secrets as 32-byte raw
26
+ // scalars; DER `ECPrivateKey` is the X.509 / PKCS#8-adjacent wire form
27
+ // some interop contexts require (PKCS#8 wraps an ECPrivateKey OCTET
28
+ // STRING inside its `privateKey` field; X.509 self-signed certs reference
29
+ // it through their algorithm OID).
30
+ //
31
+ // ECPrivateKey ::= SEQUENCE {
32
+ // version INTEGER (1),
33
+ // privateKey OCTET STRING,
34
+ // parameters [0] EXPLICIT ECParameters OPTIONAL,
35
+ // publicKey [1] EXPLICIT BIT STRING OPTIONAL
36
+ // }
37
+ //
38
+ // Encoder side: emits the exact 51-byte DER form with version 1, the
39
+ // 32-byte raw scalar in `privateKey`, and the named-curve OID
40
+ // `1.2.840.10045.3.1.7` (secp256r1, SP 800-186 §3.2.1.3) wrapped in
41
+ // `parameters [0]`. `publicKey` is omitted; consumers that need the
42
+ // public key alongside should re-derive via `EcdsaP256.keygenDerand` and
43
+ // the SEC 1 §2.3.4 uncompressed encoding (see `pointDecompress` in
44
+ // `./index.ts`). Byte-stable: same scalar in, same DER out.
45
+ //
46
+ // Decoder side: accepts any conforming RFC 5915 `ECPrivateKey` (not just
47
+ // leviathan's exact byte layout). Strict-DER posture per X.690 §10
48
+ // (Restrictions on the BER) rejects:
49
+ // - non-minimal length encodings (long-form when short-form suffices)
50
+ // - non-minimal INTEGER encodings on the `version` field
51
+ // - SEQUENCE / OCTET STRING / parameters [0] / publicKey [1] length
52
+ // prefixes that disagree with the on-the-wire content size
53
+ // - any curve OID inside `parameters [0]` other than secp256r1
54
+ // - trailing bytes after the outer SEQUENCE
55
+ // - wrong tags or missing required fields
56
+ //
57
+ // Lenient on `publicKey [1]`: some encoders include the derived pk
58
+ // alongside the scalar; the decoder skips past the field after a
59
+ // minimal-length check. The scalar is the only return value; callers
60
+ // who need the embedded pk should parse the DER themselves.
61
+ //
62
+ // No third-party ASN.1 library. Hand-rolled against X.690 §8.1 (TLV
63
+ // structure), §8.3 (INTEGER), §8.7 (OCTET STRING), §8.14 (tagged types),
64
+ // §10 (DER restrictions), mirroring the strict-parser hygiene of
65
+ // `./der.ts` (Ecdsa-Sig-Value codec).
66
+ const SEQUENCE_TAG = 0x30;
67
+ const INTEGER_TAG = 0x02;
68
+ const OCTET_STRING_TAG = 0x04;
69
+ const OID_TAG = 0x06;
70
+ const TAG_PARAMETERS_0 = 0xA0; // [0] EXPLICIT, constructed
71
+ const TAG_PUBLIC_KEY_1 = 0xA1; // [1] EXPLICIT, constructed
72
+ // DER OID for secp256r1: 1.2.840.10045.3.1.7. X.690 §8.19 collapses the
73
+ // first two arcs (1.2) into a single byte 0x2A (= 1 * 40 + 2); the
74
+ // remaining arcs (840, 10045, 3, 1, 7) base-128 encode as 86 48, CE 3D,
75
+ // 03, 01, 07. Wrapped in the OBJECT IDENTIFIER TLV (tag 0x06, len 0x08).
76
+ const SECP256R1_OID_DER = new Uint8Array([
77
+ OID_TAG, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
78
+ ]);
79
+ // Exact 51-byte DER shape for a P-256 ECPrivateKey with no embedded
80
+ // public key:
81
+ // 30 31 SEQUENCE, 49 bytes content
82
+ // 02 01 01 INTEGER, version = 1
83
+ // 04 20 <32 bytes scalar> OCTET STRING, 32 bytes
84
+ // A0 0A [0] EXPLICIT, 10 bytes content
85
+ // 06 08 2A 86 48 CE 3D 03 01 07 OBJECT IDENTIFIER, secp256r1
86
+ const ENCODED_LEN = 51;
87
+ const SEQUENCE_CONTENT_LEN = 49;
88
+ const SCALAR_LEN = 32;
89
+ // Absolute minimum input size that exposes every required field's TLV
90
+ // header without index-out-of-bounds on the parser side. SEQUENCE
91
+ // header (2) + version INTEGER TLV (3) + OCTET STRING tag + len byte
92
+ // (2) = 7. The decoder's field-level checks surface more specific
93
+ // rejections beyond this floor (wrong scalar length, missing required
94
+ // fields, etc).
95
+ const MIN_INPUT_LEN = 7;
96
+ function reject(detail) {
97
+ throw new Error(`leviathan-crypto: ecdsa-p256 ECPrivateKey DER ${detail}`);
98
+ }
99
+ /**
100
+ * Encode a 32-byte P-256 secret scalar as DER `ECPrivateKey` per
101
+ * RFC 5915 §3. Output length is exactly 51 bytes; version is 1, the
102
+ * named-curve OID for secp256r1 (`1.2.840.10045.3.1.7`,
103
+ * SP 800-186 §3.2.1.3) is included in `parameters [0]`, and the
104
+ * `publicKey [1]` field is omitted.
105
+ *
106
+ * Byte-stable: same input scalar produces byte-identical output. No
107
+ * canonicalisation pass needed because every field has a single legal
108
+ * DER encoding under the strict-DER rules of X.690 §10.
109
+ *
110
+ * @throws TypeError if `scalar` is not a Uint8Array
111
+ * @throws RangeError if `scalar.length !== 32`
112
+ */
113
+ export function encodeEcPrivateKey(scalar) {
114
+ if (!(scalar instanceof Uint8Array))
115
+ throw new TypeError('leviathan-crypto: ecdsa-p256 ECPrivateKey scalar must be a Uint8Array');
116
+ if (scalar.length !== SCALAR_LEN)
117
+ throw new RangeError(`leviathan-crypto: ecdsa-p256 ECPrivateKey scalar must be ${SCALAR_LEN} bytes (got ${scalar.length})`);
118
+ const out = new Uint8Array(ENCODED_LEN);
119
+ let p = 0;
120
+ // SEQUENCE header
121
+ out[p++] = SEQUENCE_TAG;
122
+ out[p++] = SEQUENCE_CONTENT_LEN;
123
+ // version INTEGER (1)
124
+ out[p++] = INTEGER_TAG;
125
+ out[p++] = 0x01;
126
+ out[p++] = 0x01;
127
+ // privateKey OCTET STRING (32 bytes)
128
+ out[p++] = OCTET_STRING_TAG;
129
+ out[p++] = SCALAR_LEN;
130
+ out.set(scalar, p);
131
+ p += SCALAR_LEN;
132
+ // parameters [0] EXPLICIT { secp256r1 OID }
133
+ out[p++] = TAG_PARAMETERS_0;
134
+ out[p++] = SECP256R1_OID_DER.length;
135
+ out.set(SECP256R1_OID_DER, p);
136
+ // p + SECP256R1_OID_DER.length now equals ENCODED_LEN by construction.
137
+ return out;
138
+ }
139
+ /**
140
+ * Decode a DER `ECPrivateKey` (RFC 5915 §3) and return the 32-byte
141
+ * raw P-256 secret scalar.
142
+ *
143
+ * Strict-DER per X.690 §10 (Restrictions on the BER). Rejects the
144
+ * cases enumerated in the file-level header. Accepts (and ignores)
145
+ * an optional `publicKey [1]` field per RFC 5915 §3 lenient input;
146
+ * the scalar is the only return value.
147
+ *
148
+ * Any curve OID inside `parameters [0]` other than secp256r1
149
+ * (`1.2.840.10045.3.1.7`) is rejected. Decoders that need other
150
+ * curves must use a different parser; this library is P-256 only.
151
+ *
152
+ * @throws TypeError if `der` is not a Uint8Array
153
+ * @throws Error on any DER syntax violation or unsupported parameter
154
+ */
155
+ export function decodeEcPrivateKey(der) {
156
+ if (!(der instanceof Uint8Array))
157
+ throw new TypeError('leviathan-crypto: ecdsa-p256 ECPrivateKey DER must be a Uint8Array');
158
+ // Floor at the bytes needed for the SEQUENCE header + version
159
+ // INTEGER TLV + privateKey OCTET STRING TLV header (no content
160
+ // required at the floor). Specific field-level checks below
161
+ // surface more accurate rejections beyond this.
162
+ if (der.length < MIN_INPUT_LEN)
163
+ reject(`is shorter than the ${MIN_INPUT_LEN}-byte minimum (got ${der.length} bytes)`);
164
+ if (der[0] !== SEQUENCE_TAG)
165
+ reject(`outer tag is 0x${der[0].toString(16).padStart(2, '0')}, expected 0x30 (SEQUENCE)`);
166
+ const seqLen = der[1];
167
+ // Strict DER short-form: outer SEQUENCE content for a P-256
168
+ // ECPrivateKey is at most 51 - 2 = 49 bytes with no public key, or
169
+ // roughly 51 + 67 - 2 = 116 with a publicKey [1] field; both fit
170
+ // in short-form. Long-form here would be a non-minimal encoding
171
+ // for content < 128 bytes.
172
+ if (seqLen & 0x80)
173
+ reject('uses long-form length encoding for the outer SEQUENCE (forbidden for content < 128 bytes)');
174
+ if (2 + seqLen !== der.length)
175
+ reject(`outer SEQUENCE length ${seqLen} does not match input size (${der.length} bytes total)`);
176
+ let off = 2;
177
+ // version INTEGER (1). Strict: exactly `02 01 01`. Any other
178
+ // length, leading-zero pad, or value rejects.
179
+ if (der[off] !== INTEGER_TAG)
180
+ reject(`version tag at offset ${off} is 0x${der[off].toString(16).padStart(2, '0')}, expected 0x02 (INTEGER)`);
181
+ if (der[off + 1] !== 0x01)
182
+ reject(`version length at offset ${off + 1} is ${der[off + 1]}, expected 1 (single-byte INTEGER)`);
183
+ if (der[off + 2] !== 0x01)
184
+ reject(`version value at offset ${off + 2} is ${der[off + 2]}, expected 1`);
185
+ off += 3;
186
+ // privateKey OCTET STRING (32 bytes for P-256).
187
+ if (der[off] !== OCTET_STRING_TAG)
188
+ reject(`privateKey tag at offset ${off} is 0x${der[off].toString(16).padStart(2, '0')}, expected 0x04 (OCTET STRING)`);
189
+ const skLen = der[off + 1];
190
+ if (skLen & 0x80)
191
+ reject(`privateKey OCTET STRING at offset ${off + 1} uses long-form length encoding (forbidden for content < 128 bytes)`);
192
+ if (skLen !== SCALAR_LEN)
193
+ reject(`privateKey OCTET STRING length at offset ${off + 1} is ${skLen}, expected ${SCALAR_LEN} (P-256 scalar)`);
194
+ if (off + 2 + skLen > der.length)
195
+ reject('privateKey OCTET STRING content extends past the outer SEQUENCE end');
196
+ const scalar = der.slice(off + 2, off + 2 + skLen);
197
+ off += 2 + skLen;
198
+ // Optional parameters [0] EXPLICIT. Per RFC 5915 §3 this field is
199
+ // OPTIONAL; when present it carries the curve OID. We require
200
+ // secp256r1 specifically; any other OID rejects.
201
+ if (off < der.length && der[off] === TAG_PARAMETERS_0) {
202
+ const paramLen = der[off + 1];
203
+ if (paramLen & 0x80)
204
+ reject(`parameters [0] at offset ${off + 1} uses long-form length encoding (forbidden for content < 128 bytes)`);
205
+ if (off + 2 + paramLen > der.length)
206
+ reject('parameters [0] content extends past the outer SEQUENCE end');
207
+ if (paramLen !== SECP256R1_OID_DER.length)
208
+ reject(`parameters [0] content is ${paramLen} bytes, expected ${SECP256R1_OID_DER.length} `
209
+ + '(secp256r1 OID TLV); other curves are not supported');
210
+ for (let i = 0; i < paramLen; i++) {
211
+ if (der[off + 2 + i] !== SECP256R1_OID_DER[i])
212
+ reject('parameters [0] OID does not match secp256r1 (1.2.840.10045.3.1.7); '
213
+ + 'other curves are not supported');
214
+ }
215
+ off += 2 + paramLen;
216
+ }
217
+ // RFC 5915 §3 OPTIONAL: skip past after minimal length check.
218
+ if (off < der.length && der[off] === TAG_PUBLIC_KEY_1) {
219
+ const pkLen = der[off + 1];
220
+ if (pkLen & 0x80)
221
+ reject(`publicKey [1] at offset ${off + 1} uses long-form length encoding (forbidden for content < 128 bytes)`);
222
+ if (off + 2 + pkLen > der.length)
223
+ reject('publicKey [1] content extends past the outer SEQUENCE end');
224
+ off += 2 + pkLen;
225
+ }
226
+ // No trailing bytes after the (optional) tail fields.
227
+ if (off !== der.length)
228
+ reject(`has ${der.length - off} trailing byte(s) after the optional fields`);
229
+ return scalar;
230
+ }
@@ -0,0 +1 @@
1
+ export { WASM_GZ_BASE64 as p256Wasm, WASM_GZ_BASE64 as ecdsaP256Wasm, } from '../embedded/p256.js';
@@ -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/ecdsa/embedded.ts
23
+ //
24
+ // `p256Wasm` canonical, `ecdsaP256Wasm` alias.
25
+ export { WASM_GZ_BASE64 as p256Wasm, WASM_GZ_BASE64 as ecdsaP256Wasm, } from '../embedded/p256.js';
@@ -0,0 +1,124 @@
1
+ import { isInitialized } from '../init.js';
2
+ import type { WasmSource } from '../wasm-source.js';
3
+ import type { EcdsaP256KeyPair } from './types.js';
4
+ /**
5
+ * Initialise the p256 WASM module. Loads the underlying binary
6
+ * (scalar, no SIMD) into the `p256` slot.
7
+ */
8
+ export declare function ecdsaP256Init(source: WasmSource): Promise<void>;
9
+ export type { WasmSource };
10
+ export type { EcdsaP256KeyPair, EcdsaP256Exports } from './types.js';
11
+ export { isInitialized };
12
+ export { encodeEcPrivateKey, decodeEcPrivateKey } from './ecprivatekey-der.js';
13
+ /**
14
+ * Decompress a 33-byte SEC 1 §2.3.3 compressed P-256 public key to the
15
+ * 65-byte SEC 1 §2.3.4 uncompressed encoding `0x04 || X || Y`.
16
+ *
17
+ * The compressed form encodes only the affine x coordinate plus a
18
+ * single parity bit (in the prefix byte: 0x02 even-y, 0x03 odd-y).
19
+ * Recovery of y solves the curve equation
20
+ * `y² = x³ - 3x + b mod p` (SP 800-186 §3.2.1.3, P-256 has a = -3)
21
+ * and selects the y root whose parity matches the prefix. The
22
+ * substrate runs the modular square root inside the p256 WASM
23
+ * (`feSqrt` via the p ≡ 3 (mod 4) shortcut, x^((p+1)/4)); rejecting
24
+ * invalid inputs that have no square root or whose recovered (x, y)
25
+ * lies off-curve.
26
+ *
27
+ * Rejection cases (all throw `SigningError('sig-malformed-input')`):
28
+ * - prefix byte not in {0x02, 0x03}
29
+ * - x coordinate is not the x of any on-curve point (no quadratic
30
+ * residue exists for `x³ - 3x + b mod p`)
31
+ *
32
+ * Length / shape rejections throw `TypeError` / `RangeError` per the
33
+ * usual leviathan-crypto contract-violation posture.
34
+ *
35
+ * Requires `init({ p256: ... })`. Uses the same p256 module singleton
36
+ * as `EcdsaP256`; concurrency-safe alongside non-stateful uses (the
37
+ * `_assertNotOwned` check fires if a stateful instance is holding
38
+ * the module).
39
+ *
40
+ * @param pk33 33-byte compressed pk per SEC 1 §2.3.3
41
+ * @returns 65-byte uncompressed pk per SEC 1 §2.3.4 (0x04 || X || Y)
42
+ */
43
+ export declare function pointDecompress(pk33: Uint8Array): Uint8Array;
44
+ export declare class EcdsaP256 {
45
+ constructor();
46
+ private get mx();
47
+ /**
48
+ * Deterministic ECDSA-P256 key generation from a 32-byte seed.
49
+ * d = seed mod n per FIPS 186-5 §A.4.2 (testing-candidates style,
50
+ * single candidate). pk = [d]G compressed to 33 bytes per SEC 1
51
+ * §2.3.3. The vanishingly rare seed mod n == 0 case traps in the
52
+ * WASM and surfaces as a SigningError here.
53
+ *
54
+ * @param seed 32-byte BE input
55
+ * @returns 33-byte compressed pk and a fresh 32-byte copy of the
56
+ * secret scalar d (sk === seed for this derivation, the
57
+ * caller may use either as the private value).
58
+ */
59
+ keygenDerand(seed: Uint8Array): EcdsaP256KeyPair;
60
+ /** Random ECDSA-P256 key generation, wraps `keygenDerand` with `randomBytes(32)`. */
61
+ keygen(): EcdsaP256KeyPair;
62
+ /**
63
+ * Key generation that returns the public key in the 65-byte SEC 1
64
+ * §2.3.4 uncompressed encoding `0x04 || X || Y`, rather than the
65
+ * 33-byte compressed form `keygen` / `keygenDerand` return. The
66
+ * secret-key half is the same 32-byte raw scalar `d`.
67
+ *
68
+ * Internally runs `keygen` (or `keygenDerand` if a seed is supplied)
69
+ * to obtain the compressed pk, then `pointDecompress` to expand it.
70
+ * The compressed intermediate is wiped before return.
71
+ *
72
+ * @param seed Optional 32-byte seed; passes through to `keygenDerand`
73
+ * when present, falls back to `keygen` (CSPRNG seed) when
74
+ * omitted.
75
+ */
76
+ keygenUncompressed(seed?: Uint8Array): EcdsaP256KeyPair;
77
+ /**
78
+ * Hedged-or-deterministic ECDSA-P256 sign per FIPS 186-5 §6.4 with
79
+ * RFC 6979 §3.5 low-S normalisation. The K nonce is derived per
80
+ * RFC 6979 §3.2 (deterministic) when `rnd` is all-zero, or per
81
+ * draft-irtf-cfrg-det-sigs-with-noise-05 (hedged) otherwise. The
82
+ * hedged path is the recommended default; pass `randomBytes(32)`.
83
+ *
84
+ * The WASM re-derives pk = [d]G internally and compares it against
85
+ * the caller-supplied `pk`. A mismatch traps via `unreachable` and
86
+ * is rethrown as `SigningError('sig-malformed-input')`. This
87
+ * defends against fault injection that would bias the per-signature
88
+ * randomness derivation by forcing the caller to also know pk.
89
+ *
90
+ * @param sk 32-byte secret scalar d
91
+ * @param pk 33-byte compressed or 65-byte uncompressed pk;
92
+ * cross-checked by WASM after derivation
93
+ * @param msgHash 32-byte SHA-256(M) digest (caller-computed)
94
+ * @param rnd 32-byte per-call entropy Z; all-zero selects
95
+ * deterministic RFC 6979 §3.2, non-zero selects
96
+ * the hedged path
97
+ * @returns 64-byte raw r || s signature, low-S normalised
98
+ * @throws SigningError('sig-malformed-input') on pk-mismatch
99
+ * (fault-injection trap)
100
+ */
101
+ sign(sk: Uint8Array, pk: Uint8Array, msgHash: Uint8Array, rnd: Uint8Array): Uint8Array;
102
+ /**
103
+ * Suite-only: hedged-or-deterministic sign that derives pk
104
+ * internally and skips the fault-injection cross-check. See
105
+ * AGENTS.md "SignatureSuite lifecycle". Underscore-prefixed,
106
+ * not part of the public API.
107
+ */
108
+ _signInternalPk(sk: Uint8Array, msgHash: Uint8Array, rnd: Uint8Array): Uint8Array;
109
+ /**
110
+ * Strict ECDSA-P256 verify per FIPS 186-5 §6.5 with low-S
111
+ * enforcement (RFC 6979 §3.5). Returns `true` on success, `false`
112
+ * on every signature failure mode: off-curve / identity pk, r or
113
+ * s out of [1, n-1], high-S, or the signature equation failing.
114
+ * Throws only on caller-side contract violations (wrong-length
115
+ * inputs).
116
+ *
117
+ * @param pk 33-byte compressed or 65-byte uncompressed pk
118
+ * @param msgHash 32-byte SHA-256(M) digest
119
+ * @param sig 64-byte raw r || s (use `ecdsaSignatureFromDer`
120
+ * to convert DER-encoded signatures first)
121
+ */
122
+ verify(pk: Uint8Array, msgHash: Uint8Array, sig: Uint8Array): boolean;
123
+ dispose(): void;
124
+ }