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,356 @@
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/signed-log.ts
23
+ //
24
+ // `SignedLog<S extends SignatureSuite>` ties a `MerkleTree` (Sha256Tree
25
+ // or Blake3Tree), a `SignatureSuite` registered in the C2SP cosignature
26
+ // algorithm-byte registry, and an origin string into one object that
27
+ // produces signed checkpoints, verifies received checkpoints, and
28
+ // exposes inclusion / consistency proofs.
29
+ //
30
+ // Wire format per c2sp.org/tlog-cosignature §Format and §"Ed25519
31
+ // signed message" / §"ML-DSA-44 signed message":
32
+ //
33
+ // envelope = emitSignedNote(body, [
34
+ // { name: origin,
35
+ // keyId: deriveKeyId(origin, algoByte, pubkey),
36
+ // signature: emitCosigSignaturePayload(timestamp, sig) },
37
+ // ])
38
+ //
39
+ // where `sig` is the result of `suite.sign(sk, signedMessage, EMPTY_CTX)`
40
+ // and `signedMessage` is dispatched on `algoEntry.messageConstruction`:
41
+ //
42
+ // 'cosig' → buildCosigSignedMessage(body, timestamp)
43
+ // "cosignature/v1\ntime <ts>\n<body>"
44
+ // (Ed25519, C2SP algo byte 0x04)
45
+ //
46
+ // 'cosigned-message' → buildCosignedMessage({...})
47
+ // TLS-Presentation `cosigned_message` struct
48
+ // (ML-DSA-44, C2SP algo byte 0x06)
49
+ //
50
+ // Suites without a registered entry (e.g. EcdsaP256Suite, every prehash
51
+ // variant, all SLH-DSA, every hybrid) cannot construct a `SignedLog`
52
+ // and throw `SigningError('sig-unsupported-suite')` at construction.
53
+ //
54
+ // C2SP commit pinned for this implementation:
55
+ // 3752ba5b3590dc3754e04fcc8369bd3612897c02 (github.com/C2SP/C2SP).
56
+ import { isInitialized } from '../init.js';
57
+ import { SigningError, MerkleCodecError } from '../errors.js';
58
+ import { constantTimeEqual, wipe } from '../utils.js';
59
+ import { emitSignedNote, parseSignedNote, deriveKeyId, lookupAlgoEntryByFormatEnum, buildCosigSignedMessage, buildCosignedMessage, emitCosigSignaturePayload, parseCosigSignaturePayload, } from './signed-note.js';
60
+ import { serializeCheckpointBody, parseCheckpointBody, } from './checkpoint.js';
61
+ // ── Module surface union ────────────────────────────────────────────────────
62
+ const SHA2_MODULE = 'sha2';
63
+ // Empty ctx passed to suite.sign / suite.verify. Domain separation
64
+ // for the signed message is built in to the cosignature/v1 header
65
+ // (Ed25519 case) or the cosigned_message label (ML-DSA-44 case);
66
+ // the suite-level ctx adds no additional binding.
67
+ const EMPTY_CTX = new Uint8Array(0);
68
+ function unionModules(...lists) {
69
+ const seen = new Set();
70
+ for (const list of lists)
71
+ for (const m of list)
72
+ seen.add(m);
73
+ return Object.freeze([...seen]);
74
+ }
75
+ function validateOriginAtConstruction(origin) {
76
+ if (origin.length === 0)
77
+ throw new RangeError('SignedLog: origin must be non-empty');
78
+ // c2sp.org/tlog-checkpoint §Note text MUSTs; fail at construction
79
+ // not serialize time.
80
+ if (/\s/.test(origin) || origin.includes('+'))
81
+ throw new RangeError('SignedLog: origin must not contain whitespace or plus characters');
82
+ }
83
+ /**
84
+ * Signed transparency log substrate. Combines a `MerkleTree` with a
85
+ * registered cosignature `SignatureSuite` and an origin string;
86
+ * exposes append, proof, and cosignature sign / verify operations.
87
+ *
88
+ * Per-call WASM lifecycle is enforced by the suite itself (see the
89
+ * SignatureSuite factories under `src/ts/sign/suites/`). `SignedLog`
90
+ * does not wrap additional try/finally around `suite.sign` /
91
+ * `suite.verify` because the suite already does. Internally the
92
+ * SignedLog owns a private copy of the signing key wiped by
93
+ * `dispose()`.
94
+ */
95
+ export class SignedLog {
96
+ tree;
97
+ suite;
98
+ origin;
99
+ pubkey;
100
+ wasmModules;
101
+ _algoEntry;
102
+ _keyId;
103
+ _signingKey;
104
+ _disposed = false;
105
+ constructor(opts) {
106
+ const { tree, suite, origin, signingKey, pubkey } = opts;
107
+ validateOriginAtConstruction(origin);
108
+ if (!(signingKey instanceof Uint8Array))
109
+ throw new TypeError('SignedLog: signingKey must be a Uint8Array');
110
+ if (!(pubkey instanceof Uint8Array))
111
+ throw new TypeError('SignedLog: pubkey must be a Uint8Array');
112
+ if (signingKey.length !== suite.skSize)
113
+ throw new RangeError(`SignedLog: signingKey length ${signingKey.length} != suite.skSize ${suite.skSize}`);
114
+ if (pubkey.length !== suite.pkSize)
115
+ throw new RangeError(`SignedLog: pubkey length ${pubkey.length} != suite.pkSize ${suite.pkSize}`);
116
+ const algoEntry = lookupAlgoEntryByFormatEnum(suite.formatEnum);
117
+ if (algoEntry === undefined)
118
+ throw new SigningError('sig-unsupported-suite', `SignedLog: suite formatEnum 0x${suite.formatEnum.toString(16).padStart(2, '0')} `
119
+ + `(${suite.formatName}) has no C2SP signed-note algorithm byte registered; `
120
+ + 'see c2sp.org/tlog-cosignature §Format for the supported algorithms');
121
+ const wasmModules = unionModules(tree.hasher.wasmModules, suite.wasmModules, [SHA2_MODULE]);
122
+ for (const mod of wasmModules) {
123
+ if (!isInitialized(mod))
124
+ throw new Error(`SignedLog: WASM module '${mod}' is not initialized; `
125
+ + 'call init() with the appropriate sources before constructing SignedLog');
126
+ }
127
+ this.tree = tree;
128
+ this.suite = suite;
129
+ this.origin = origin;
130
+ this.pubkey = pubkey.slice();
131
+ this.wasmModules = wasmModules;
132
+ this._signingKey = signingKey.slice();
133
+ this._algoEntry = algoEntry;
134
+ this._keyId = deriveKeyId(origin, algoEntry.algoByte, this.pubkey);
135
+ }
136
+ // ── Tree passthroughs ──────────────────────────────────────────────
137
+ /**
138
+ * Append a leaf to the underlying tree and return the new leaf's
139
+ * index, hash, and inclusion proof against the post-append tree size.
140
+ */
141
+ append(leafBytes) {
142
+ this._assertNotDisposed();
143
+ const { leafIndex, leafHash } = this.tree.append(leafBytes);
144
+ const inclusionProof = this.tree.getInclusionProof(leafIndex, this.tree.size());
145
+ return { leafIndex, leafHash, inclusionProof };
146
+ }
147
+ size() {
148
+ this._assertNotDisposed();
149
+ return this.tree.size();
150
+ }
151
+ rootHash() {
152
+ this._assertNotDisposed();
153
+ return this.tree.rootHash();
154
+ }
155
+ getInclusionProof(leafIndex, treeSize) {
156
+ this._assertNotDisposed();
157
+ return this.tree.getInclusionProof(leafIndex, treeSize);
158
+ }
159
+ getConsistencyProof(oldSize, newSize) {
160
+ this._assertNotDisposed();
161
+ return this.tree.getConsistencyProof(oldSize, newSize);
162
+ }
163
+ // ── Sign + verify ──────────────────────────────────────────────────
164
+ /**
165
+ * Issue a cosignature over the current checkpoint and emit the
166
+ * signed-note envelope per c2sp.org/signed-note §Format. The
167
+ * signature line carries the `timestamped_signature` payload
168
+ * from c2sp.org/tlog-cosignature §Format; the bytes the suite
169
+ * signs are dispatched on the algorithm's
170
+ * `messageConstruction`:
171
+ *
172
+ * - `'cosig'` → `buildCosigSignedMessage(body, ts)`
173
+ * (Ed25519, §"Ed25519 signed message")
174
+ * - `'cosigned-message'` → `buildCosignedMessage(...)`
175
+ * (ML-DSA-44, §"ML-DSA-44 signed message")
176
+ *
177
+ * `timestamp` defaults to current wall-clock POSIX seconds. The
178
+ * c2sp.org/tlog-witness `add-checkpoint` rule mandates a non-zero
179
+ * timestamp on production cosignatures; `0` is accepted by this
180
+ * function for test reproducibility but witness verifiers will
181
+ * reject envelopes that carry it. Tests and vector generators
182
+ * pass an explicit value to lock byte stability.
183
+ */
184
+ signCheckpoint(opts) {
185
+ this._assertNotDisposed();
186
+ const timestamp = opts?.timestamp ?? Math.floor(Date.now() / 1000);
187
+ const body = serializeCheckpointBody({
188
+ origin: this.origin,
189
+ treeSize: this.tree.size(),
190
+ rootHash: this.tree.rootHash(),
191
+ });
192
+ const signedMessage = this._buildSignedMessage(body, timestamp);
193
+ const sig = this.suite.sign(this._signingKey, signedMessage, EMPTY_CTX);
194
+ if (sig.length !== this._algoEntry.sigSize)
195
+ throw new SigningError('sig-malformed-input', `SignedLog.signCheckpoint: suite.sign returned ${sig.length} bytes, `
196
+ + `expected ${this._algoEntry.sigSize} per c2sp.org/tlog-cosignature §Format`);
197
+ const payload = emitCosigSignaturePayload(timestamp, sig);
198
+ const line = {
199
+ name: this.origin,
200
+ keyId: this._keyId,
201
+ signature: payload,
202
+ };
203
+ return emitSignedNote(body, [line]);
204
+ }
205
+ /**
206
+ * Parse a signed-note envelope into the structured `SignedTreeHead`
207
+ * form per c2sp.org/signed-note §Format. Surfaces the body's
208
+ * decoded `Checkpoint`, the signature lines that survived the
209
+ * permissive signed-note parse, and the primary log cosignature's
210
+ * POSIX-seconds timestamp (extracted via
211
+ * `parseCosigSignaturePayload` on the line whose keyId matches
212
+ * this log's pubkey-derived keyId).
213
+ *
214
+ * If no signature line matches, `timestamp` is reported as 0. The
215
+ * field is informational at parse time; cryptographic verification
216
+ * lives in `verifyCheckpoint`. Throws `RangeError` on whole-envelope
217
+ * structural failure (the parseSignedNote / parseCheckpointBody
218
+ * contract); does not throw on signature line content issues.
219
+ */
220
+ parseCheckpoint(bytes) {
221
+ this._assertNotDisposed();
222
+ const env = parseSignedNote(bytes);
223
+ const checkpoint = parseCheckpointBody(env.body, this.tree.hasher.outputSize);
224
+ let timestamp = 0;
225
+ const matching = env.signatures.find(s => s.keyId.length === this._keyId.length
226
+ && constantTimeEqual(s.keyId, this._keyId));
227
+ if (matching) {
228
+ try {
229
+ const parsed = parseCosigSignaturePayload(matching.signature, this._algoEntry.sigSize);
230
+ timestamp = parsed.timestamp;
231
+ }
232
+ catch (err) {
233
+ // Soft-fail timestamp to 0 on payload codec error; verifyCheckpoint surfaces hard fail.
234
+ if (!(err instanceof MerkleCodecError))
235
+ throw err;
236
+ }
237
+ }
238
+ return { checkpoint, signatures: env.signatures, timestamp };
239
+ }
240
+ /**
241
+ * Verify a signed-note envelope against this SignedLog's origin,
242
+ * pubkey, suite, and tree hasher. Returns `true` iff the envelope
243
+ * parses, carries a signature line whose keyId matches this log's
244
+ * pubkey-derived keyId, the `timestamped_signature` payload on
245
+ * that line decodes cleanly, and the signature verifies under
246
+ * `suite.verify` over the cosignature signed message reconstructed
247
+ * with the parsed timestamp.
248
+ *
249
+ * Returns `false` on every soft-fail mode: wrong origin, wrong
250
+ * root-hash length, no matching keyId line, malformed payload,
251
+ * signature failure. Throws only on this log's own disposed
252
+ * state; never on envelope content (envelope content is public,
253
+ * so timing distinctions on its content are not security-sensitive).
254
+ *
255
+ * The keyId comparison uses `constantTimeEqual` for hygiene around
256
+ * key-material-adjacent state; the origin and root-hash-length
257
+ * early returns are intentional non-constant-time exits since
258
+ * both fields are public per the spec.
259
+ */
260
+ verifyCheckpoint(bytes) {
261
+ this._assertNotDisposed();
262
+ let env;
263
+ try {
264
+ env = parseSignedNote(bytes);
265
+ }
266
+ catch {
267
+ return false;
268
+ }
269
+ let checkpoint;
270
+ try {
271
+ checkpoint = parseCheckpointBody(env.body, this.tree.hasher.outputSize);
272
+ }
273
+ catch {
274
+ return false;
275
+ }
276
+ if (checkpoint.origin !== this.origin)
277
+ return false;
278
+ if (checkpoint.rootHash.length !== this.tree.hasher.outputSize)
279
+ return false;
280
+ const matching = env.signatures.find(s => s.keyId.length === this._keyId.length
281
+ && constantTimeEqual(s.keyId, this._keyId));
282
+ if (!matching)
283
+ return false;
284
+ let payload;
285
+ try {
286
+ payload = parseCosigSignaturePayload(matching.signature, this._algoEntry.sigSize);
287
+ }
288
+ catch {
289
+ return false;
290
+ }
291
+ let signedMessage;
292
+ try {
293
+ signedMessage = this._buildSignedMessage(env.body, payload.timestamp);
294
+ }
295
+ catch {
296
+ // Reconstruction failed (e.g. timestamp out of range, or
297
+ // the cosigned_message branch's start/end/state contract
298
+ // violated by some attacker-influenced field). Treat as
299
+ // verify-false.
300
+ return false;
301
+ }
302
+ return this.suite.verify(this.pubkey, signedMessage, payload.signature, EMPTY_CTX);
303
+ }
304
+ // ── Lifecycle ──────────────────────────────────────────────────────
305
+ /**
306
+ * Zero the stored signing-key copy. Idempotent. Subsequent calls
307
+ * to any public method throw.
308
+ */
309
+ dispose() {
310
+ if (this._disposed)
311
+ return;
312
+ wipe(this._signingKey);
313
+ this._signingKey = new Uint8Array(0);
314
+ this._disposed = true;
315
+ }
316
+ _assertNotDisposed() {
317
+ if (this._disposed)
318
+ throw new Error('SignedLog: instance has been disposed');
319
+ }
320
+ // ── Internal: message construction dispatch ────────────────────────
321
+ /**
322
+ * Dispatch the cosignature signed-message construction on the
323
+ * algorithm-byte registry entry's `messageConstruction`. The
324
+ * `body` argument is the canonical checkpoint body from
325
+ * `serializeCheckpointBody`, ending in 0x0A.
326
+ *
327
+ * 'cosig' c2sp.org/tlog-cosignature §"Ed25519 signed
328
+ * message". The full envelope body is
329
+ * embedded verbatim after the
330
+ * cosignature/v1 + time prefix.
331
+ *
332
+ * 'cosigned-message' c2sp.org/tlog-cosignature §"ML-DSA-44
333
+ * signed message". The body is decomposed
334
+ * into origin, tree size, and root hash;
335
+ * cosigner_name == origin (Phase 7 logs sign
336
+ * their own checkpoints); start == 0; end ==
337
+ * tree size; hash == root hash.
338
+ */
339
+ _buildSignedMessage(body, timestamp) {
340
+ if (this._algoEntry.messageConstruction === 'cosig')
341
+ return buildCosigSignedMessage(body, timestamp);
342
+ // 'cosigned-message' branch: decompose the body to feed the
343
+ // TLS-Presentation struct. `parseCheckpointBody` is the
344
+ // authoritative source of truth for the three-line body
345
+ // layout.
346
+ const cp = parseCheckpointBody(body, this.tree.hasher.outputSize);
347
+ return buildCosignedMessage({
348
+ cosignerName: this.origin,
349
+ timestamp,
350
+ logOrigin: this.origin,
351
+ start: 0,
352
+ end: cp.treeSize,
353
+ hash: cp.rootHash,
354
+ });
355
+ }
356
+ }
@@ -0,0 +1,309 @@
1
+ /**
2
+ * c2sp.org/signed-note §Format signature type for plain Ed25519
3
+ * signatures over the raw note text per RFC 8032. Listed for spec
4
+ * completeness; no leviathan SignatureSuite currently routes here
5
+ * because Phase 7 cosignatures use the timestamped Ed25519 variant
6
+ * (`ALGO_BYTE_ED25519_COSIG`) per c2sp.org/tlog-cosignature §Format.
7
+ */
8
+ export declare const ALGO_BYTE_ED25519_NOTE = 1;
9
+ /**
10
+ * c2sp.org/tlog-cosignature §Format signature type for timestamped
11
+ * Ed25519 checkpoint cosignatures. The signature payload is
12
+ * `u64_be(timestamp) || ed25519_signature(64)` for a total of 72
13
+ * bytes, base64-encoded together with the 4-byte key ID on the
14
+ * signature line.
15
+ */
16
+ export declare const ALGO_BYTE_ED25519_COSIG = 4;
17
+ /**
18
+ * c2sp.org/tlog-cosignature §Format signature type for timestamped
19
+ * ML-DSA-44 (sub)tree cosignatures. The signature payload is
20
+ * `u64_be(timestamp) || ml_dsa_44_signature(2420)` for a total of
21
+ * 2428 bytes, base64-encoded together with the 4-byte key ID.
22
+ */
23
+ export declare const ALGO_BYTE_MLDSA44_COSIG = 6;
24
+ /**
25
+ * How the cosigner constructs the bytes it signs.
26
+ *
27
+ * 'cosig' c2sp.org/tlog-cosignature §"Ed25519 signed
28
+ * message". Produced by `buildCosigSignedMessage`.
29
+ * 'cosigned-message' c2sp.org/tlog-cosignature §"ML-DSA-44 signed
30
+ * message", `cosigned_message` struct.
31
+ */
32
+ export type MessageConstruction = 'cosig' | 'cosigned-message';
33
+ /**
34
+ * Per-signature payload encoding bundled with the 4-byte key ID.
35
+ *
36
+ * 'timestamped' c2sp.org/tlog-cosignature §Format
37
+ * `timestamped_signature` struct, shared by 0x04 and 0x06.
38
+ */
39
+ export type SignaturePayload = 'timestamped';
40
+ /**
41
+ * Per c2sp.org/tlog-cosignature §Format algorithm-byte registry. One
42
+ * entry per registered (leviathan suite, C2SP byte) pair; the entry
43
+ * carries the message-construction and payload-encoding rules a
44
+ * cosigner needs to sign and a verifier needs to parse.
45
+ */
46
+ export interface AlgoEntry {
47
+ /** Leviathan `SignatureSuite.formatEnum`. */
48
+ readonly formatEnum: number;
49
+ /** C2SP signed-note algorithm byte from §Format. */
50
+ readonly algoByte: number;
51
+ /** How the cosigner constructs the bytes it signs. */
52
+ readonly messageConstruction: MessageConstruction;
53
+ /** How the per-signature payload is encoded on the signature line. */
54
+ readonly signaturePayload: SignaturePayload;
55
+ /**
56
+ * Raw signature size in bytes from the underlying primitive. For
57
+ * Ed25519 this is 64 (RFC 8032 §5.1.6); for ML-DSA-44 this is
58
+ * 2420 (FIPS 204 Table 1). The `timestamped` payload encoding
59
+ * adds an 8-byte BE timestamp prefix per `timestamped_signature`,
60
+ * so the total payload length on the wire is `8 + sigSize`.
61
+ */
62
+ readonly sigSize: number;
63
+ }
64
+ /**
65
+ * Look up the algo-entry for a leviathan `SignatureSuite.formatEnum`.
66
+ * Returns `undefined` for suites not registered in the catalog;
67
+ * callers that need a hard guarantee should check the return value
68
+ * and raise an issue per AGENTS.md rather than locally mint a byte
69
+ * for a suite the C2SP spec has not registered.
70
+ */
71
+ export declare function lookupAlgoEntryByFormatEnum(formatEnum: number): AlgoEntry | undefined;
72
+ /**
73
+ * Look up the algo-entry for a wire-format C2SP algorithm byte. Used
74
+ * by verifiers that see an unknown signature line and need to decide
75
+ * how to reshape the payload (or whether to defer to
76
+ * `parseSignedNote`'s "unknown signatures MUST be ignored" rule).
77
+ */
78
+ export declare function lookupAlgoEntryByByte(algoByte: number): AlgoEntry | undefined;
79
+ /**
80
+ * Resolve a leviathan SignatureSuite formatEnum to its C2SP signed-note
81
+ * algorithm byte. Thin shim over `lookupAlgoEntryByFormatEnum`; kept for
82
+ * the call sites that only need the byte (e.g. `deriveKeyId` callers).
83
+ */
84
+ export declare function suiteFormatEnumToAlgoByte(formatEnum: number): number | undefined;
85
+ /**
86
+ * Per c2sp.org/signed-note §Signatures and c2sp.org/tlog-cosignature
87
+ * §Format, the recommended key ID is:
88
+ *
89
+ * key_id = SHA-256(utf8(name) || 0x0A || algo_byte || pubkey)[:4]
90
+ *
91
+ * The leading newline byte is U+000A (0x0A); `algo_byte` is the
92
+ * signature-type identifier from `c2sp.org/signed-note` §Signatures
93
+ * §Signature types. The key ID is intentionally short (4 bytes); it
94
+ * is an identifier, not a collision-resistant hash, and key ID
95
+ * collisions only produce verification failures, not forgeries (the
96
+ * verifier holds the authoritative public key).
97
+ *
98
+ * Acquires the sha2 module per call inside try / finally and disposes;
99
+ * does not hold long-lived state. The `name` argument must satisfy the
100
+ * signed-note key-name MUSTs (non-empty, no Unicode whitespace, no
101
+ * plus characters).
102
+ */
103
+ export declare function deriveKeyId(name: string, algoByte: number, pubkey: Uint8Array): Uint8Array;
104
+ /**
105
+ * Decoded signed-note signature line per c2sp.org/signed-note §Format.
106
+ * `name` is the verified UTF-8 key name from the line; `keyId` is the
107
+ * 4-byte prefix extracted from the base64 payload; `signature` is the
108
+ * remaining bytes after the prefix, opaque to the parser (the format
109
+ * is defined by whatever algorithm corresponds to this key, which the
110
+ * parser does not look up).
111
+ */
112
+ export interface SignatureLine {
113
+ readonly name: string;
114
+ readonly keyId: Uint8Array;
115
+ readonly signature: Uint8Array;
116
+ }
117
+ /**
118
+ * Decoded signed-note envelope. `body` includes the body's terminating
119
+ * U+000A but NOT the blank line that separates body from signatures;
120
+ * `signatures` contains every signature line that parsed structurally;
121
+ * `ignoredCount` is the number of signature lines that failed structural
122
+ * validation and were discarded per the signed-note §Signatures rule
123
+ * that unknown signatures MUST be ignored.
124
+ */
125
+ export interface SignedNote {
126
+ readonly body: Uint8Array;
127
+ readonly signatures: SignatureLine[];
128
+ readonly ignoredCount: number;
129
+ }
130
+ /**
131
+ * Emit a signed-note envelope per c2sp.org/signed-note §Format. The
132
+ * caller supplies the body bytes (which MUST end in U+000A; the
133
+ * checkpoint body codec already enforces this) and one or more
134
+ * signature lines. The wire layout is:
135
+ *
136
+ * body || '\n' || (— name b64(keyId||sig) '\n')+
137
+ *
138
+ * The blank line that separates body from signature lines is the
139
+ * extra newline between the body's own trailing newline and the
140
+ * first signature line; both `serializeCheckpointBody` and this
141
+ * function MUST agree on this convention.
142
+ *
143
+ * Throws RangeError on a body that does not end in U+000A, on an
144
+ * empty signatures array, or on any signature whose key name violates
145
+ * the signed-note key-name MUSTs.
146
+ */
147
+ export declare function emitSignedNote(body: Uint8Array, sigs: readonly SignatureLine[]): Uint8Array;
148
+ /**
149
+ * Parse a signed-note envelope per c2sp.org/signed-note §Format. The
150
+ * input must be valid UTF-8 and MUST NOT contain ASCII control
151
+ * characters below U+0020 other than newline. The body is everything
152
+ * up to and including the first blank line, MINUS the blank line
153
+ * itself, MINUS the newline that immediately precedes the blank line
154
+ * (no, including it; see body convention below).
155
+ *
156
+ * Per the body convention in `emitSignedNote`, the returned `body`
157
+ * field includes the body's terminating U+000A but excludes the
158
+ * blank-line separator.
159
+ *
160
+ * Signature-line parsing is permissive: a line that does not match
161
+ * `— <name> <base64>\n` exactly, or whose base64 payload decodes to
162
+ * fewer than 4 bytes (no room for a key ID), is counted in
163
+ * `ignoredCount` and discarded rather than throwing. The signed-note
164
+ * §Signatures rule is that unknown signatures MUST be ignored, and
165
+ * "unknown" subsumes any line a future spec extension might add in
166
+ * a format leviathan does not recognize.
167
+ *
168
+ * Whole-envelope structural errors (missing blank separator, body
169
+ * not ending in newline, ASCII control bytes, invalid UTF-8) throw
170
+ * RangeError. The behaviour of "throw on envelope, ignore on line"
171
+ * is what makes the codec forward-compatible with future cosignature
172
+ * algorithms without changing the byte-stable body region.
173
+ */
174
+ export declare function parseSignedNote(bytes: Uint8Array): SignedNote;
175
+ /**
176
+ * Build the bytes a cosigner signs when issuing a cosignature for a
177
+ * checkpoint, per c2sp.org/tlog-cosignature §"Ed25519 signed message".
178
+ *
179
+ * Layout (each `\n` is U+000A):
180
+ *
181
+ * cosignature/v1\n
182
+ * time <decimal_timestamp>\n
183
+ * <body>
184
+ *
185
+ * `body` is the canonical checkpoint body produced by
186
+ * `serializeCheckpointBody` and already terminates in `\n`; the
187
+ * function adds no separator between the timestamp line and the
188
+ * body. Decimal carries no leading zeroes per the §Format rule on
189
+ * the timestamp line (mirrored from checkpoint §Note text).
190
+ *
191
+ * Spec-correct only for Ed25519 cosignatures (C2SP algo byte 0x04).
192
+ * ML-DSA-44 cosignatures sign the separate `cosigned_message` struct
193
+ * defined in §"ML-DSA-44 signed message" (codec not in this patch);
194
+ * callers reaching for this function with an ML-DSA-44 suite are
195
+ * producing the wrong wire format and should branch on the
196
+ * `messageConstruction` field of the suite's `AlgoEntry`.
197
+ *
198
+ * Throws `MerkleCodecError('timestamp-out-of-range')` if `timestamp`
199
+ * is not a non-negative safe integer.
200
+ */
201
+ export declare function buildCosigSignedMessage(body: Uint8Array, timestamp: number): Uint8Array;
202
+ /**
203
+ * Encode the `timestamped_signature` struct payload per
204
+ * c2sp.org/tlog-cosignature §Format. Layout (per RFC 8446 §3.3,
205
+ * Presentation Language; integers in network byte order):
206
+ *
207
+ * u64_be(timestamp) || signature[N]
208
+ *
209
+ * The result is the opaque payload portion of a signed-note signature
210
+ * line: prefixed by the 4-byte key ID and then base64-encoded by
211
+ * `emitSignedNote`. `signature` length is suite-dependent (64 for
212
+ * Ed25519, 2420 for ML-DSA-44); the encoder does not validate length
213
+ * here because both registry-allowed sizes round-trip correctly.
214
+ *
215
+ * Throws `MerkleCodecError('timestamp-out-of-range')` if `timestamp`
216
+ * is not a non-negative safe integer.
217
+ */
218
+ export declare function emitCosigSignaturePayload(timestamp: number, signature: Uint8Array): Uint8Array;
219
+ /**
220
+ * Decode a `timestamped_signature` payload per c2sp.org/tlog-cosignature
221
+ * §Format. Inverse of `emitCosigSignaturePayload`; round-trips
222
+ * byte-for-byte.
223
+ *
224
+ * `sigSize` is suite-locked (64 for Ed25519, 2420 for ML-DSA-44); the
225
+ * caller supplies it via the suite's `AlgoEntry.sigSize`. The decoder
226
+ * asserts `payload.length === 8 + sigSize` and throws
227
+ * `MerkleCodecError('cosig-payload-length-mismatch')` otherwise so a
228
+ * wrong-length payload fails loudly rather than producing a silently
229
+ * truncated signature.
230
+ *
231
+ * The wire timestamp is u64-BE; values exceeding `Number.MAX_SAFE_INTEGER`
232
+ * cannot round-trip through JavaScript Number and throw
233
+ * `MerkleCodecError('timestamp-exceeds-safe-integer')`. The cutoff is
234
+ * `tsHi >= 0x200000` (i.e. `2^53 / 2^32`).
235
+ */
236
+ export declare function parseCosigSignaturePayload(payload: Uint8Array, sigSize: number): {
237
+ timestamp: number;
238
+ signature: Uint8Array;
239
+ };
240
+ /**
241
+ * Inputs to `buildCosignedMessage`, one named field per `cosigned_message`
242
+ * struct member from c2sp.org/tlog-cosignature §"ML-DSA-44 signed
243
+ * message". `start` and `end` are numbers in [0, Number.MAX_SAFE_INTEGER];
244
+ * they encode on the wire as big-endian u64. `hash` is exactly 32 bytes.
245
+ */
246
+ export interface CosignedMessageInput {
247
+ /**
248
+ * UTF-8 cosigner identity, 1-255 bytes after encoding. For a log's
249
+ * cosignature on its own checkpoint this matches `logOrigin`; for a
250
+ * witness cosignature it identifies the witness.
251
+ */
252
+ readonly cosignerName: string;
253
+ /**
254
+ * POSIX-seconds timestamp. Per c2sp.org/tlog-cosignature §"ML-DSA-44
255
+ * signed message", `timestamp` MUST be zero when `start` is not
256
+ * zero (subtree case); both MAY be zero (the cosigner is making no
257
+ * statement about being the largest observed tree).
258
+ */
259
+ readonly timestamp: number;
260
+ /**
261
+ * UTF-8 log identity, 1-255 bytes after encoding. Matches the
262
+ * checkpoint body's origin line (without the trailing newline).
263
+ */
264
+ readonly logOrigin: string;
265
+ /**
266
+ * Index of the first leaf included in the signed range. MUST be 0
267
+ * for a checkpoint cosignature; non-zero only for subtree
268
+ * cosignatures (out of Phase 7 scope but supported by the codec).
269
+ */
270
+ readonly start: number;
271
+ /**
272
+ * Exclusive upper bound of the leaf indexes in the signed range.
273
+ * For a checkpoint cosignature, equals the tree size.
274
+ */
275
+ readonly end: number;
276
+ /** 32-byte Merkle root hash of the signed range. */
277
+ readonly hash: Uint8Array;
278
+ }
279
+ /**
280
+ * Build the bytes a cosigner signs when issuing an ML-DSA-44
281
+ * cosignature, per c2sp.org/tlog-cosignature §"ML-DSA-44 signed
282
+ * message". Layout (TLS-Presentation per RFC 8446 §3.3, lengths in
283
+ * big-endian network order):
284
+ *
285
+ * uint8 label[12] = "subtree/v1\n\0"
286
+ * opaque cosigner_name<1..2^8-1>
287
+ * uint64 timestamp
288
+ * opaque log_origin<1..2^8-1>
289
+ * uint64 start
290
+ * uint64 end
291
+ * uint8 hash[32]
292
+ *
293
+ * Total length is `70 + utf8(cosignerName).length + utf8(logOrigin).length`.
294
+ *
295
+ * Spec-correct for both checkpoint (start=0) and subtree (start>0)
296
+ * ML-DSA-44 cosignatures. Phase 7 uses only the checkpoint case;
297
+ * subtree cosignatures land with the witness-protocol work. The
298
+ * codec is agnostic so future TASKs do not re-cut the surface.
299
+ *
300
+ * Throws `MerkleCodecError`:
301
+ * 'timestamp-out-of-range' timestamp / start / end not safe non-negative
302
+ * 'cosigner-name-length' UTF-8 cosignerName empty or > 255 bytes
303
+ * 'log-origin-length' UTF-8 logOrigin empty or > 255 bytes
304
+ * 'cosigned-message-state' start > 0 and timestamp != 0 (spec MUST)
305
+ *
306
+ * Throws `RangeError` on a `hash` whose length is not 32 (the
307
+ * `cosigned_message.hash` field is fixed-length per the struct).
308
+ */
309
+ export declare function buildCosignedMessage(input: CosignedMessageInput): Uint8Array;