@fgv/ts-extras 5.1.0-26 → 5.1.0-28

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 (149) hide show
  1. package/dist/index.browser.js +2 -2
  2. package/dist/index.browser.js.map +1 -1
  3. package/dist/packlets/ai-assist/apiClient.js +300 -213
  4. package/dist/packlets/ai-assist/apiClient.js.map +1 -1
  5. package/dist/packlets/ai-assist/chatRequestBuilders.js +6 -0
  6. package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  7. package/dist/packlets/ai-assist/imageOptionsResolver.js +212 -0
  8. package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  9. package/dist/packlets/ai-assist/index.js +1 -0
  10. package/dist/packlets/ai-assist/index.js.map +1 -1
  11. package/dist/packlets/ai-assist/model.js +1 -1
  12. package/dist/packlets/ai-assist/model.js.map +1 -1
  13. package/dist/packlets/ai-assist/registry.js +120 -22
  14. package/dist/packlets/ai-assist/registry.js.map +1 -1
  15. package/dist/packlets/ai-assist/sseParser.js +1 -0
  16. package/dist/packlets/ai-assist/sseParser.js.map +1 -1
  17. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +17 -12
  18. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  19. package/dist/packlets/ai-assist/streamingAdapters/common.js +2 -0
  20. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  21. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +17 -5
  22. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  23. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +19 -4
  24. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  25. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +20 -5
  26. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  27. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +9 -3
  28. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  29. package/dist/packlets/ai-assist/streamingClient.js +28 -6
  30. package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
  31. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +265 -0
  32. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  33. package/dist/packlets/conversion/converters.js +1 -0
  34. package/dist/packlets/conversion/converters.js.map +1 -1
  35. package/dist/packlets/crypto-utils/converters.js +24 -4
  36. package/dist/packlets/crypto-utils/converters.js.map +1 -1
  37. package/dist/packlets/crypto-utils/hpkeProvider.js +333 -0
  38. package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  39. package/dist/packlets/crypto-utils/index.browser.js +3 -0
  40. package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
  41. package/dist/packlets/crypto-utils/index.js +2 -0
  42. package/dist/packlets/crypto-utils/index.js.map +1 -1
  43. package/dist/packlets/crypto-utils/keystore/converters.js +2 -2
  44. package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -1
  45. package/dist/packlets/crypto-utils/keystore/keyStore.js +108 -2
  46. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  47. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -1
  48. package/dist/packlets/crypto-utils/model.js +21 -0
  49. package/dist/packlets/crypto-utils/model.js.map +1 -1
  50. package/dist/packlets/crypto-utils/nodeCryptoProvider.js +74 -0
  51. package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
  52. package/dist/ts-extras.d.ts +1029 -137
  53. package/dist/tsdoc-metadata.json +1 -1
  54. package/lib/index.browser.d.ts +2 -2
  55. package/lib/index.browser.d.ts.map +1 -1
  56. package/lib/index.browser.js +4 -3
  57. package/lib/index.browser.js.map +1 -1
  58. package/lib/packlets/ai-assist/apiClient.d.ts +29 -85
  59. package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
  60. package/lib/packlets/ai-assist/apiClient.js +300 -213
  61. package/lib/packlets/ai-assist/apiClient.js.map +1 -1
  62. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
  63. package/lib/packlets/ai-assist/chatRequestBuilders.js +6 -0
  64. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  65. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts +74 -0
  66. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts.map +1 -0
  67. package/lib/packlets/ai-assist/imageOptionsResolver.js +216 -0
  68. package/lib/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  69. package/lib/packlets/ai-assist/index.d.ts +2 -1
  70. package/lib/packlets/ai-assist/index.d.ts.map +1 -1
  71. package/lib/packlets/ai-assist/index.js +4 -1
  72. package/lib/packlets/ai-assist/index.js.map +1 -1
  73. package/lib/packlets/ai-assist/model.d.ts +410 -35
  74. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  75. package/lib/packlets/ai-assist/model.js +1 -1
  76. package/lib/packlets/ai-assist/model.js.map +1 -1
  77. package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
  78. package/lib/packlets/ai-assist/registry.js +120 -22
  79. package/lib/packlets/ai-assist/registry.js.map +1 -1
  80. package/lib/packlets/ai-assist/sseParser.d.ts.map +1 -1
  81. package/lib/packlets/ai-assist/sseParser.js +1 -0
  82. package/lib/packlets/ai-assist/sseParser.js.map +1 -1
  83. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +2 -1
  84. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
  85. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +17 -12
  86. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  87. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +5 -1
  88. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
  89. package/lib/packlets/ai-assist/streamingAdapters/common.js +2 -0
  90. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  91. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +2 -1
  92. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
  93. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +17 -5
  94. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  95. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +2 -1
  96. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
  97. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +19 -4
  98. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  99. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +2 -1
  100. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
  101. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +20 -5
  102. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  103. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
  104. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +9 -3
  105. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  106. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
  107. package/lib/packlets/ai-assist/streamingClient.js +28 -6
  108. package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
  109. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +71 -0
  110. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -0
  111. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +270 -0
  112. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  113. package/lib/packlets/conversion/converters.d.ts.map +1 -1
  114. package/lib/packlets/conversion/converters.js +1 -0
  115. package/lib/packlets/conversion/converters.js.map +1 -1
  116. package/lib/packlets/crypto-utils/converters.d.ts +12 -1
  117. package/lib/packlets/crypto-utils/converters.d.ts.map +1 -1
  118. package/lib/packlets/crypto-utils/converters.js +25 -5
  119. package/lib/packlets/crypto-utils/converters.js.map +1 -1
  120. package/lib/packlets/crypto-utils/hpkeProvider.d.ts +142 -0
  121. package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -0
  122. package/lib/packlets/crypto-utils/hpkeProvider.js +337 -0
  123. package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  124. package/lib/packlets/crypto-utils/index.browser.d.ts +1 -0
  125. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
  126. package/lib/packlets/crypto-utils/index.browser.js +5 -1
  127. package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
  128. package/lib/packlets/crypto-utils/index.d.ts +1 -0
  129. package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
  130. package/lib/packlets/crypto-utils/index.js +4 -1
  131. package/lib/packlets/crypto-utils/index.js.map +1 -1
  132. package/lib/packlets/crypto-utils/keystore/converters.js +1 -1
  133. package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -1
  134. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +32 -2
  135. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
  136. package/lib/packlets/crypto-utils/keystore/keyStore.js +116 -10
  137. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  138. package/lib/packlets/crypto-utils/keystore/model.d.ts +21 -3
  139. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -1
  140. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -1
  141. package/lib/packlets/crypto-utils/model.d.ts +165 -9
  142. package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
  143. package/lib/packlets/crypto-utils/model.js +22 -1
  144. package/lib/packlets/crypto-utils/model.js.map +1 -1
  145. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +39 -0
  146. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -1
  147. package/lib/packlets/crypto-utils/nodeCryptoProvider.js +74 -0
  148. package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
  149. package/package.json +13 -13
@@ -0,0 +1,333 @@
1
+ // Copyright (c) 2026 Erik Fortune
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ import { captureAsyncResult, captureResult, fail, succeed } from '@fgv/ts-utils';
21
+ // ---- Internal constants ----
22
+ // "HPKE-v1" (7 bytes) — RFC 9180 domain separator prefix
23
+ const _HPKE_VERSION = new Uint8Array([0x48, 0x50, 0x4b, 0x45, 0x2d, 0x76, 0x31]);
24
+ // suite_id = "HPKE" || I2OSP(KEM_ID=0x0020, 2) || I2OSP(KDF_ID=0x0001, 2) || I2OSP(AEAD_ID=0x0002, 2)
25
+ // Used in key schedule LabeledExtract/LabeledExpand calls.
26
+ const _SUITE_ID = new Uint8Array([
27
+ 0x48, 0x50, 0x4b, 0x45, 0x00, 0x20, 0x00, 0x01, 0x00, 0x02
28
+ ]);
29
+ // kem_suite_id = "KEM" || I2OSP(KEM_ID=0x0020, 2)
30
+ // Used in DHKEM-internal LabeledExtract/LabeledExpand calls.
31
+ const _KEM_SUITE_ID = new Uint8Array([0x4b, 0x45, 0x4d, 0x00, 0x20]);
32
+ const _N_SECRET = 32; // Nsecret: KEM shared-secret output length
33
+ const _N_PK = 32; // Npk: X25519 public key (raw) length — also the enc length
34
+ const _N_K = 32; // Nk: AES-256-GCM key length
35
+ const _N_N = 12; // Nn: AES-256-GCM nonce length
36
+ const _N_T = 16; // Nt: AES-256-GCM authentication tag length
37
+ const _MODE_BASE = 0x00; // RFC 9180 mode_base
38
+ // ---- Internal helpers ----
39
+ // These are NOT exported. Per design D7, only the five public operations are public surface.
40
+ // Copies any Uint8Array into a fresh Uint8Array<ArrayBuffer>.
41
+ // Required to satisfy TypeScript's strict BufferSource typing for Web Crypto API calls —
42
+ // subtle.* rejects Uint8Array<ArrayBufferLike> but accepts Uint8Array<ArrayBuffer>.
43
+ // Pattern follows browserCryptoProvider.ts toBufferView.
44
+ function _toBufferView(arr) {
45
+ const buffer = new ArrayBuffer(arr.byteLength);
46
+ const view = new Uint8Array(buffer);
47
+ view.set(arr);
48
+ return view;
49
+ }
50
+ function _concat(...arrays) {
51
+ const total = arrays.reduce((n, a) => n + a.length, 0);
52
+ const buffer = new ArrayBuffer(total);
53
+ const out = new Uint8Array(buffer);
54
+ let offset = 0;
55
+ for (const a of arrays) {
56
+ out.set(a, offset);
57
+ offset += a.length;
58
+ }
59
+ return out;
60
+ }
61
+ function _i2osp(value, length) {
62
+ const buffer = new ArrayBuffer(length);
63
+ const out = new Uint8Array(buffer);
64
+ for (let i = length - 1; i >= 0; i--) {
65
+ out[i] = value % 256;
66
+ value = Math.floor(value / 256);
67
+ }
68
+ return out;
69
+ }
70
+ // Decodes a base64url-encoded string to Uint8Array<ArrayBuffer>.
71
+ // Uses `atob` (global in Node 18+ and all modern browsers; no import needed).
72
+ function _base64UrlDecode(s) {
73
+ const base64 = s.replace(/-/g, '+').replace(/_/g, '/');
74
+ const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
75
+ const binary = atob(padded);
76
+ const buffer = new ArrayBuffer(binary.length);
77
+ const out = new Uint8Array(buffer);
78
+ for (let i = 0; i < binary.length; i++) {
79
+ out[i] = binary.charCodeAt(i);
80
+ }
81
+ return out;
82
+ }
83
+ // HMAC-SHA256(key, data) — the underlying primitive for HKDF.
84
+ async function _hmacSha256(subtle, key, data) {
85
+ const hmacKey = await subtle.importKey('raw', _toBufferView(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
86
+ return new Uint8Array(await subtle.sign('HMAC', hmacKey, _toBufferView(data)));
87
+ }
88
+ // RFC 5869 HKDF-Extract(salt, IKM) = HMAC-SHA256(key=salt, msg=IKM).
89
+ // If salt is empty (length 0), uses 32 zero bytes per RFC 5869 §2.2.
90
+ async function _hkdfExtract(subtle, salt, ikm) {
91
+ const effectiveSalt = salt.length === 0 ? new Uint8Array(32) : salt;
92
+ return _hmacSha256(subtle, effectiveSalt, ikm);
93
+ }
94
+ // RFC 5869 HKDF-Expand(PRK, info, L) — iterative HMAC expansion.
95
+ async function _hkdfExpand(subtle, prk, info, length) {
96
+ if (length > 255 * 32) {
97
+ throw new Error(`HKDF-Expand: requested length ${length} exceeds maximum 8160 bytes (255 * HashLen)`);
98
+ }
99
+ const n = Math.ceil(length / 32);
100
+ const buffer = new ArrayBuffer(length);
101
+ const okm = new Uint8Array(buffer);
102
+ let prev = new Uint8Array(new ArrayBuffer(0));
103
+ let offset = 0;
104
+ for (let i = 1; i <= n; i++) {
105
+ prev = await _hmacSha256(subtle, prk, _concat(prev, info, new Uint8Array([i])));
106
+ const toCopy = Math.min(32, length - offset);
107
+ okm.set(prev.subarray(0, toCopy), offset);
108
+ offset += toCopy;
109
+ }
110
+ return okm;
111
+ }
112
+ // RFC 9180 §4 LabeledExtract — HKDF-Extract with HPKE-v1 domain label.
113
+ // labeled_ikm = "HPKE-v1" || suite_id || label || ikm
114
+ // Extract(salt, labeled_ikm)
115
+ async function _labeledExtract(subtle, suiteId, salt, label, ikm) {
116
+ const labeledIkm = _concat(_HPKE_VERSION, suiteId, new TextEncoder().encode(label), ikm);
117
+ return _hkdfExtract(subtle, salt, labeledIkm);
118
+ }
119
+ // RFC 9180 §4 LabeledExpand — HKDF-Expand with HPKE-v1 domain label.
120
+ // labeled_info = I2OSP(L, 2) || "HPKE-v1" || suite_id || label || info
121
+ // Expand(prk, labeled_info, L)
122
+ async function _labeledExpand(subtle, suiteId, prk, label, info, length) {
123
+ const labeledInfo = _concat(_i2osp(length, 2), _HPKE_VERSION, suiteId, new TextEncoder().encode(label), info);
124
+ return _hkdfExpand(subtle, prk, labeledInfo, length);
125
+ }
126
+ // RFC 9180 §4.1 DHKEM Encap — generates ephemeral keypair, DH with recipient pubkey,
127
+ // derives shared_secret via ExtractAndExpand.
128
+ // NOTE: uses label "eae_prk" (not "dh") per RFC 9180 §4.1 ExtractAndExpand.
129
+ async function _kemEncap(subtle, recipientPublicKey) {
130
+ const ephemeral = (await subtle.generateKey({ name: 'X25519' }, true, ['deriveBits']));
131
+ const enc = new Uint8Array(await subtle.exportKey('raw', ephemeral.publicKey));
132
+ const dh = new Uint8Array(await subtle.deriveBits({ name: 'X25519', public: recipientPublicKey }, ephemeral.privateKey, 256));
133
+ const pkRm = new Uint8Array(await subtle.exportKey('raw', recipientPublicKey));
134
+ const kemContext = _concat(enc, pkRm);
135
+ const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);
136
+ const sharedSecret = await _labeledExpand(subtle, _KEM_SUITE_ID, eaePrk, 'shared_secret', kemContext, _N_SECRET);
137
+ return { sharedSecret, enc };
138
+ }
139
+ // RFC 9180 §4.1 DHKEM Decap — deserializes enc, DH with recipient privkey,
140
+ // derives same shared_secret via ExtractAndExpand.
141
+ // Requires recipientPrivateKey to be extractable (JWK export needed for pkRm).
142
+ async function _kemDecap(subtle, enc, recipientPrivateKey) {
143
+ const pkE = await subtle.importKey('raw', _toBufferView(enc), { name: 'X25519' }, true, []);
144
+ const dh = new Uint8Array(await subtle.deriveBits({ name: 'X25519', public: pkE }, recipientPrivateKey, 256));
145
+ // Recover recipient's own public key from JWK x field (base64url-encoded raw X25519 public key).
146
+ const jwk = (await subtle.exportKey('jwk', recipientPrivateKey));
147
+ /* c8 ignore next 3 - defensive: X25519 JWK always has an x field; unreachable via public API */
148
+ if (!jwk.x) {
149
+ throw new Error('HPKE Decap: failed to extract public key bytes from recipient private key JWK');
150
+ }
151
+ const pkRm = _base64UrlDecode(jwk.x);
152
+ const kemContext = _concat(enc, pkRm);
153
+ const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);
154
+ return _labeledExpand(subtle, _KEM_SUITE_ID, eaePrk, 'shared_secret', kemContext, _N_SECRET);
155
+ }
156
+ // RFC 9180 §5 KeySchedule (base mode, psk = b"", psk_id = b"").
157
+ async function _keyScheduleBase(subtle, sharedSecret, info) {
158
+ const empty = new Uint8Array(0);
159
+ const pskIdHash = await _labeledExtract(subtle, _SUITE_ID, empty, 'psk_id_hash', empty);
160
+ const infoHash = await _labeledExtract(subtle, _SUITE_ID, empty, 'info_hash', info);
161
+ const ksContext = _concat(new Uint8Array([_MODE_BASE]), pskIdHash, infoHash);
162
+ const prk = await _labeledExtract(subtle, _SUITE_ID, sharedSecret, 'secret', empty);
163
+ const key = await _labeledExpand(subtle, _SUITE_ID, prk, 'key', ksContext, _N_K);
164
+ const baseNonce = await _labeledExpand(subtle, _SUITE_ID, prk, 'base_nonce', ksContext, _N_N);
165
+ return { key, baseNonce };
166
+ }
167
+ // ---- Public class ----
168
+ /**
169
+ * HPKE base mode (RFC 9180) — `DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM`.
170
+ *
171
+ * Class-based provider that captures a `SubtleCrypto` instance at construction,
172
+ * matching the existing `NodeCryptoProvider` / `BrowserCryptoProvider` / `KeyStore`
173
+ * factory pattern used throughout `@fgv/ts-extras/crypto-utils`.
174
+ *
175
+ * **Node.js usage:**
176
+ * ```typescript
177
+ * import * as crypto from 'crypto';
178
+ * const hpke = HpkeProvider.create(crypto.webcrypto.subtle).orThrow();
179
+ * ```
180
+ *
181
+ * **Browser usage:**
182
+ * ```typescript
183
+ * const hpke = HpkeProvider.create(globalThis.crypto.subtle).orThrow();
184
+ * ```
185
+ *
186
+ * **Runtime requirements:** Node.js 20+ (X25519 in `crypto.webcrypto`);
187
+ * Chrome 113+, Safari 16.4+, Firefox 118+ (X25519 added to Web Crypto in 2023).
188
+ * @public
189
+ */
190
+ export class HpkeProvider {
191
+ constructor(subtle) {
192
+ this._subtle = subtle;
193
+ }
194
+ /**
195
+ * Creates an `HpkeProvider` bound to the given `SubtleCrypto` instance.
196
+ *
197
+ * @param subtle - Web Crypto SubtleCrypto instance.
198
+ * Node.js: `(await import('crypto')).webcrypto.subtle`.
199
+ * Browser: `globalThis.crypto.subtle`.
200
+ * @returns `Success` with the provider, or `Failure` if construction fails.
201
+ */
202
+ static create(subtle) {
203
+ return captureResult(() => new HpkeProvider(subtle));
204
+ }
205
+ /**
206
+ * HPKE base-mode seal (sender side). RFC 9180 §6.1.
207
+ *
208
+ * Generates a fresh ephemeral X25519 keypair, runs DHKEM Encap to produce a
209
+ * shared secret and `enc` (32-byte raw ephemeral public key), derives the AEAD
210
+ * key and nonce deterministically via the RFC 9180 key schedule, then encrypts
211
+ * `plaintext` with AES-256-GCM.
212
+ *
213
+ * @param recipientPublicKey - Recipient's X25519 public `CryptoKey`
214
+ * (`algorithm.name === 'X25519'`, `type === 'public'`, **`extractable: true`**).
215
+ * Must be extractable — DHKEM Encap calls `exportKey('raw', ...)` on this key to
216
+ * build the KEM shared-secret context. Keys imported with `extractable: false` will
217
+ * cause this method to return a `Failure`.
218
+ * @param info - Context-binding bytes. **Load-bearing — no default.**
219
+ * Binds this ciphertext to a specific application context, preventing replay
220
+ * across different contexts sharing the same recipient keypair.
221
+ * Use `new TextEncoder().encode('myapp/v1/use-case\x00' + contextId)` pattern.
222
+ * Never pass an empty array in production: empty `info` provides no context binding.
223
+ * @param aad - Additional authenticated data. Integrity-protected but not encrypted.
224
+ * `new Uint8Array(0)` is valid when no AAD is needed.
225
+ * @param plaintext - Bytes to encrypt. `new Uint8Array(0)` is valid.
226
+ * @returns `Success` with `{ enc, ciphertext }`, or `Failure` with error context.
227
+ */
228
+ async sealBase(recipientPublicKey, info, aad, plaintext) {
229
+ const result = await captureAsyncResult(async () => {
230
+ const { sharedSecret, enc } = await _kemEncap(this._subtle, recipientPublicKey);
231
+ const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);
232
+ const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['encrypt']);
233
+ const ct = await this._subtle.encrypt({ name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) }, aesKey, _toBufferView(plaintext));
234
+ return { enc, ciphertext: new Uint8Array(ct) };
235
+ });
236
+ return result.withErrorFormat((e) => `HPKE sealBase failed: ${e}`);
237
+ }
238
+ /**
239
+ * HPKE base-mode open (recipient side). RFC 9180 §6.1.
240
+ *
241
+ * Decapsulates `enc` using the recipient's X25519 private key, derives the same
242
+ * AEAD key and nonce from the shared secret and `info`, then authenticates and
243
+ * decrypts `ciphertext` with AES-256-GCM.
244
+ *
245
+ * Returns `Failure` on any of:
246
+ * - Wrong private key (different DH output → different key derivation)
247
+ * - Wrong `info` (different key schedule context → different AEAD key)
248
+ * - Wrong `aad` (AES-GCM authentication fails)
249
+ * - Tampered `ciphertext` or `enc` (authentication fails or DH fails)
250
+ * - `enc` not exactly 32 bytes
251
+ * - `ciphertext` shorter than 16 bytes (no room for authentication tag)
252
+ *
253
+ * @param recipientPrivateKey - Recipient's X25519 private `CryptoKey`
254
+ * (`algorithm.name === 'X25519'`, `type === 'private'`, `usages` includes `'deriveBits'`).
255
+ * **Must be extractable** (`extractable: true`) — the recipient's public key bytes
256
+ * are recovered from the JWK `x` field during Decap.
257
+ * @param info - Context-binding bytes. Must exactly match `info` from `sealBase`.
258
+ * @param aad - Must exactly match `aad` from `sealBase`.
259
+ * @param enc - The encapsulated key from `sealBase` — exactly 32 bytes.
260
+ * @param ciphertext - The ciphertext from `sealBase` — `plaintext.length + 16` bytes.
261
+ * @returns `Success` with decrypted plaintext bytes, or `Failure` with error context.
262
+ */
263
+ async openBase(recipientPrivateKey, info, aad, enc, ciphertext) {
264
+ if (enc.length !== _N_PK) {
265
+ return fail(`HPKE openBase: enc must be ${_N_PK} bytes, got ${enc.length}`);
266
+ }
267
+ if (ciphertext.length < _N_T) {
268
+ return fail(`HPKE openBase: ciphertext too short (minimum ${_N_T} bytes for auth tag, got ${ciphertext.length})`);
269
+ }
270
+ const result = await captureAsyncResult(async () => {
271
+ const sharedSecret = await _kemDecap(this._subtle, enc, recipientPrivateKey);
272
+ const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);
273
+ const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['decrypt']);
274
+ const pt = await this._subtle.decrypt({ name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) }, aesKey, _toBufferView(ciphertext));
275
+ return new Uint8Array(pt);
276
+ });
277
+ return result.withErrorFormat((e) => `HPKE openBase failed: ${e}`);
278
+ }
279
+ /**
280
+ * HKDF-SHA256 key derivation (RFC 5869). Extract-then-Expand using SHA-256.
281
+ *
282
+ * This is raw RFC 5869 HKDF — it does **not** use RFC 9180's labeled variants.
283
+ * The HPKE key schedule internally uses labeled HKDF; this method is the unlabeled
284
+ * version for callers that need standalone key derivation.
285
+ *
286
+ * @param secret - Input keying material (IKM). Any length.
287
+ * @param salt - Optional salt. Use `new Uint8Array(0)` if no salt is available
288
+ * (RFC 5869: 32 zero bytes are used internally when salt is empty).
289
+ * @param info - Context / application-binding bytes. Any length.
290
+ * @param length - Number of output bytes to derive. Maximum 8160 bytes (255 × 32).
291
+ * @returns `Success` with derived bytes, or `Failure` with error context.
292
+ */
293
+ async hkdf(secret, salt, info, length) {
294
+ const result = await captureAsyncResult(async () => {
295
+ const prk = await _hkdfExtract(this._subtle, salt, secret);
296
+ return _hkdfExpand(this._subtle, prk, info, length);
297
+ });
298
+ return result.withErrorFormat((e) => `HKDF failed: ${e}`);
299
+ }
300
+ /**
301
+ * Encodes an {@link IHpkeSealResult} as a single contiguous byte array for wire transport.
302
+ *
303
+ * Format: `enc` (32 bytes, fixed) || `ciphertext` (variable length).
304
+ * The 32-byte `enc` length is fixed for X25519; the split point is unambiguous.
305
+ *
306
+ * @param result - The output of {@link HpkeProvider.sealBase}.
307
+ * @returns Concatenated bytes: `enc || ciphertext`.
308
+ */
309
+ static encodeEnvelope(result) {
310
+ return _concat(result.enc, result.ciphertext);
311
+ }
312
+ /**
313
+ * Decodes an envelope produced by {@link HpkeProvider.encodeEnvelope}.
314
+ *
315
+ * Validates that the buffer is at least 48 bytes (32-byte enc + 16-byte minimum
316
+ * ciphertext containing the AES-GCM auth tag; zero-length plaintext is the minimum
317
+ * meaningful case).
318
+ *
319
+ * @param envelope - Envelope bytes from `encodeEnvelope`.
320
+ * @returns `Success` with `{ enc, ciphertext }`, or `Failure` if malformed.
321
+ */
322
+ static decodeEnvelope(envelope) {
323
+ const minLen = _N_PK + _N_T;
324
+ if (envelope.length < minLen) {
325
+ return fail(`HPKE decodeEnvelope: envelope too short (minimum ${minLen} bytes, got ${envelope.length})`);
326
+ }
327
+ return succeed({
328
+ enc: envelope.slice(0, _N_PK),
329
+ ciphertext: envelope.slice(_N_PK)
330
+ });
331
+ }
332
+ }
333
+ //# sourceMappingURL=hpkeProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hpkeProvider.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/hpkeProvider.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport { Result, captureAsyncResult, captureResult, fail, succeed } from '@fgv/ts-utils';\n\n// ---- Internal constants ----\n\n// \"HPKE-v1\" (7 bytes) — RFC 9180 domain separator prefix\nconst _HPKE_VERSION: Uint8Array<ArrayBuffer> = new Uint8Array([0x48, 0x50, 0x4b, 0x45, 0x2d, 0x76, 0x31]);\n\n// suite_id = \"HPKE\" || I2OSP(KEM_ID=0x0020, 2) || I2OSP(KDF_ID=0x0001, 2) || I2OSP(AEAD_ID=0x0002, 2)\n// Used in key schedule LabeledExtract/LabeledExpand calls.\nconst _SUITE_ID: Uint8Array<ArrayBuffer> = new Uint8Array([\n 0x48, 0x50, 0x4b, 0x45, 0x00, 0x20, 0x00, 0x01, 0x00, 0x02\n]);\n\n// kem_suite_id = \"KEM\" || I2OSP(KEM_ID=0x0020, 2)\n// Used in DHKEM-internal LabeledExtract/LabeledExpand calls.\nconst _KEM_SUITE_ID: Uint8Array<ArrayBuffer> = new Uint8Array([0x4b, 0x45, 0x4d, 0x00, 0x20]);\n\nconst _N_SECRET: number = 32; // Nsecret: KEM shared-secret output length\nconst _N_PK: number = 32; // Npk: X25519 public key (raw) length — also the enc length\nconst _N_K: number = 32; // Nk: AES-256-GCM key length\nconst _N_N: number = 12; // Nn: AES-256-GCM nonce length\nconst _N_T: number = 16; // Nt: AES-256-GCM authentication tag length\nconst _MODE_BASE: number = 0x00; // RFC 9180 mode_base\n\n// ---- Internal helpers ----\n// These are NOT exported. Per design D7, only the five public operations are public surface.\n\n// Copies any Uint8Array into a fresh Uint8Array<ArrayBuffer>.\n// Required to satisfy TypeScript's strict BufferSource typing for Web Crypto API calls —\n// subtle.* rejects Uint8Array<ArrayBufferLike> but accepts Uint8Array<ArrayBuffer>.\n// Pattern follows browserCryptoProvider.ts toBufferView.\nfunction _toBufferView(arr: Uint8Array): Uint8Array<ArrayBuffer> {\n const buffer = new ArrayBuffer(arr.byteLength);\n const view = new Uint8Array(buffer);\n view.set(arr);\n return view;\n}\n\nfunction _concat(...arrays: Uint8Array[]): Uint8Array<ArrayBuffer> {\n const total = arrays.reduce((n, a) => n + a.length, 0);\n const buffer = new ArrayBuffer(total);\n const out = new Uint8Array(buffer);\n let offset = 0;\n for (const a of arrays) {\n out.set(a, offset);\n offset += a.length;\n }\n return out;\n}\n\nfunction _i2osp(value: number, length: number): Uint8Array<ArrayBuffer> {\n const buffer = new ArrayBuffer(length);\n const out = new Uint8Array(buffer);\n for (let i = length - 1; i >= 0; i--) {\n out[i] = value % 256;\n value = Math.floor(value / 256);\n }\n return out;\n}\n\n// Decodes a base64url-encoded string to Uint8Array<ArrayBuffer>.\n// Uses `atob` (global in Node 18+ and all modern browsers; no import needed).\nfunction _base64UrlDecode(s: string): Uint8Array<ArrayBuffer> {\n const base64 = s.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n const binary = atob(padded);\n const buffer = new ArrayBuffer(binary.length);\n const out = new Uint8Array(buffer);\n for (let i = 0; i < binary.length; i++) {\n out[i] = binary.charCodeAt(i);\n }\n return out;\n}\n\n// HMAC-SHA256(key, data) — the underlying primitive for HKDF.\nasync function _hmacSha256(\n subtle: SubtleCrypto,\n key: Uint8Array,\n data: Uint8Array\n): Promise<Uint8Array<ArrayBuffer>> {\n const hmacKey = await subtle.importKey(\n 'raw',\n _toBufferView(key),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n return new Uint8Array(await subtle.sign('HMAC', hmacKey, _toBufferView(data)));\n}\n\n// RFC 5869 HKDF-Extract(salt, IKM) = HMAC-SHA256(key=salt, msg=IKM).\n// If salt is empty (length 0), uses 32 zero bytes per RFC 5869 §2.2.\nasync function _hkdfExtract(\n subtle: SubtleCrypto,\n salt: Uint8Array,\n ikm: Uint8Array\n): Promise<Uint8Array<ArrayBuffer>> {\n const effectiveSalt = salt.length === 0 ? new Uint8Array(32) : salt;\n return _hmacSha256(subtle, effectiveSalt, ikm);\n}\n\n// RFC 5869 HKDF-Expand(PRK, info, L) — iterative HMAC expansion.\nasync function _hkdfExpand(\n subtle: SubtleCrypto,\n prk: Uint8Array,\n info: Uint8Array,\n length: number\n): Promise<Uint8Array<ArrayBuffer>> {\n if (length > 255 * 32) {\n throw new Error(`HKDF-Expand: requested length ${length} exceeds maximum 8160 bytes (255 * HashLen)`);\n }\n const n = Math.ceil(length / 32);\n const buffer = new ArrayBuffer(length);\n const okm = new Uint8Array(buffer);\n let prev: Uint8Array<ArrayBuffer> = new Uint8Array(new ArrayBuffer(0));\n let offset = 0;\n for (let i = 1; i <= n; i++) {\n prev = await _hmacSha256(subtle, prk, _concat(prev, info, new Uint8Array([i])));\n const toCopy = Math.min(32, length - offset);\n okm.set(prev.subarray(0, toCopy), offset);\n offset += toCopy;\n }\n return okm;\n}\n\n// RFC 9180 §4 LabeledExtract — HKDF-Extract with HPKE-v1 domain label.\n// labeled_ikm = \"HPKE-v1\" || suite_id || label || ikm\n// Extract(salt, labeled_ikm)\nasync function _labeledExtract(\n subtle: SubtleCrypto,\n suiteId: Uint8Array,\n salt: Uint8Array,\n label: string,\n ikm: Uint8Array\n): Promise<Uint8Array<ArrayBuffer>> {\n const labeledIkm = _concat(_HPKE_VERSION, suiteId, new TextEncoder().encode(label), ikm);\n return _hkdfExtract(subtle, salt, labeledIkm);\n}\n\n// RFC 9180 §4 LabeledExpand — HKDF-Expand with HPKE-v1 domain label.\n// labeled_info = I2OSP(L, 2) || \"HPKE-v1\" || suite_id || label || info\n// Expand(prk, labeled_info, L)\nasync function _labeledExpand(\n subtle: SubtleCrypto,\n suiteId: Uint8Array,\n prk: Uint8Array,\n label: string,\n info: Uint8Array,\n length: number\n): Promise<Uint8Array<ArrayBuffer>> {\n const labeledInfo = _concat(\n _i2osp(length, 2),\n _HPKE_VERSION,\n suiteId,\n new TextEncoder().encode(label),\n info\n );\n return _hkdfExpand(subtle, prk, labeledInfo, length);\n}\n\n// RFC 9180 §4.1 DHKEM Encap — generates ephemeral keypair, DH with recipient pubkey,\n// derives shared_secret via ExtractAndExpand.\n// NOTE: uses label \"eae_prk\" (not \"dh\") per RFC 9180 §4.1 ExtractAndExpand.\nasync function _kemEncap(\n subtle: SubtleCrypto,\n recipientPublicKey: CryptoKey\n): Promise<{ sharedSecret: Uint8Array<ArrayBuffer>; enc: Uint8Array<ArrayBuffer> }> {\n const ephemeral = (await subtle.generateKey({ name: 'X25519' }, true, ['deriveBits'])) as CryptoKeyPair;\n const enc = new Uint8Array(await subtle.exportKey('raw', ephemeral.publicKey));\n const dh = new Uint8Array(\n await subtle.deriveBits({ name: 'X25519', public: recipientPublicKey }, ephemeral.privateKey, 256)\n );\n const pkRm = new Uint8Array(await subtle.exportKey('raw', recipientPublicKey));\n const kemContext = _concat(enc, pkRm);\n const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);\n const sharedSecret = await _labeledExpand(\n subtle,\n _KEM_SUITE_ID,\n eaePrk,\n 'shared_secret',\n kemContext,\n _N_SECRET\n );\n return { sharedSecret, enc };\n}\n\n// RFC 9180 §4.1 DHKEM Decap — deserializes enc, DH with recipient privkey,\n// derives same shared_secret via ExtractAndExpand.\n// Requires recipientPrivateKey to be extractable (JWK export needed for pkRm).\nasync function _kemDecap(\n subtle: SubtleCrypto,\n enc: Uint8Array,\n recipientPrivateKey: CryptoKey\n): Promise<Uint8Array<ArrayBuffer>> {\n const pkE = await subtle.importKey('raw', _toBufferView(enc), { name: 'X25519' }, true, []);\n const dh = new Uint8Array(\n await subtle.deriveBits({ name: 'X25519', public: pkE }, recipientPrivateKey, 256)\n );\n // Recover recipient's own public key from JWK x field (base64url-encoded raw X25519 public key).\n const jwk = (await subtle.exportKey('jwk', recipientPrivateKey)) as JsonWebKey;\n /* c8 ignore next 3 - defensive: X25519 JWK always has an x field; unreachable via public API */\n if (!jwk.x) {\n throw new Error('HPKE Decap: failed to extract public key bytes from recipient private key JWK');\n }\n const pkRm = _base64UrlDecode(jwk.x);\n const kemContext = _concat(enc, pkRm);\n const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);\n return _labeledExpand(subtle, _KEM_SUITE_ID, eaePrk, 'shared_secret', kemContext, _N_SECRET);\n}\n\n// RFC 9180 §5 KeySchedule (base mode, psk = b\"\", psk_id = b\"\").\nasync function _keyScheduleBase(\n subtle: SubtleCrypto,\n sharedSecret: Uint8Array,\n info: Uint8Array\n): Promise<{ key: Uint8Array<ArrayBuffer>; baseNonce: Uint8Array<ArrayBuffer> }> {\n const empty = new Uint8Array(0);\n const pskIdHash = await _labeledExtract(subtle, _SUITE_ID, empty, 'psk_id_hash', empty);\n const infoHash = await _labeledExtract(subtle, _SUITE_ID, empty, 'info_hash', info);\n const ksContext = _concat(new Uint8Array([_MODE_BASE]), pskIdHash, infoHash);\n const prk = await _labeledExtract(subtle, _SUITE_ID, sharedSecret, 'secret', empty);\n const key = await _labeledExpand(subtle, _SUITE_ID, prk, 'key', ksContext, _N_K);\n const baseNonce = await _labeledExpand(subtle, _SUITE_ID, prk, 'base_nonce', ksContext, _N_N);\n return { key, baseNonce };\n}\n\n// ---- Public types ----\n\n/**\n * Output of {@link HpkeProvider.sealBase}.\n *\n * The `ciphertext` field includes the 16-byte AES-256-GCM authentication tag\n * appended by Web Crypto's `encrypt()` operation: `length = plaintext.length + 16`.\n * @public\n */\nexport interface IHpkeSealResult {\n /**\n * Encapsulated key — 32-byte raw X25519 ephemeral public key (`enc` in RFC 9180).\n * Must be transmitted to the recipient alongside `ciphertext`.\n */\n readonly enc: Uint8Array;\n\n /**\n * AES-256-GCM ciphertext with the 16-byte authentication tag appended.\n * Length = `plaintext.length + 16`.\n */\n readonly ciphertext: Uint8Array;\n}\n\n// ---- Public class ----\n\n/**\n * HPKE base mode (RFC 9180) — `DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM`.\n *\n * Class-based provider that captures a `SubtleCrypto` instance at construction,\n * matching the existing `NodeCryptoProvider` / `BrowserCryptoProvider` / `KeyStore`\n * factory pattern used throughout `@fgv/ts-extras/crypto-utils`.\n *\n * **Node.js usage:**\n * ```typescript\n * import * as crypto from 'crypto';\n * const hpke = HpkeProvider.create(crypto.webcrypto.subtle).orThrow();\n * ```\n *\n * **Browser usage:**\n * ```typescript\n * const hpke = HpkeProvider.create(globalThis.crypto.subtle).orThrow();\n * ```\n *\n * **Runtime requirements:** Node.js 20+ (X25519 in `crypto.webcrypto`);\n * Chrome 113+, Safari 16.4+, Firefox 118+ (X25519 added to Web Crypto in 2023).\n * @public\n */\nexport class HpkeProvider {\n private readonly _subtle: SubtleCrypto;\n\n private constructor(subtle: SubtleCrypto) {\n this._subtle = subtle;\n }\n\n /**\n * Creates an `HpkeProvider` bound to the given `SubtleCrypto` instance.\n *\n * @param subtle - Web Crypto SubtleCrypto instance.\n * Node.js: `(await import('crypto')).webcrypto.subtle`.\n * Browser: `globalThis.crypto.subtle`.\n * @returns `Success` with the provider, or `Failure` if construction fails.\n */\n public static create(subtle: SubtleCrypto): Result<HpkeProvider> {\n return captureResult(() => new HpkeProvider(subtle));\n }\n\n /**\n * HPKE base-mode seal (sender side). RFC 9180 §6.1.\n *\n * Generates a fresh ephemeral X25519 keypair, runs DHKEM Encap to produce a\n * shared secret and `enc` (32-byte raw ephemeral public key), derives the AEAD\n * key and nonce deterministically via the RFC 9180 key schedule, then encrypts\n * `plaintext` with AES-256-GCM.\n *\n * @param recipientPublicKey - Recipient's X25519 public `CryptoKey`\n * (`algorithm.name === 'X25519'`, `type === 'public'`, **`extractable: true`**).\n * Must be extractable — DHKEM Encap calls `exportKey('raw', ...)` on this key to\n * build the KEM shared-secret context. Keys imported with `extractable: false` will\n * cause this method to return a `Failure`.\n * @param info - Context-binding bytes. **Load-bearing — no default.**\n * Binds this ciphertext to a specific application context, preventing replay\n * across different contexts sharing the same recipient keypair.\n * Use `new TextEncoder().encode('myapp/v1/use-case\\x00' + contextId)` pattern.\n * Never pass an empty array in production: empty `info` provides no context binding.\n * @param aad - Additional authenticated data. Integrity-protected but not encrypted.\n * `new Uint8Array(0)` is valid when no AAD is needed.\n * @param plaintext - Bytes to encrypt. `new Uint8Array(0)` is valid.\n * @returns `Success` with `{ enc, ciphertext }`, or `Failure` with error context.\n */\n public async sealBase(\n recipientPublicKey: CryptoKey,\n info: Uint8Array,\n aad: Uint8Array,\n plaintext: Uint8Array\n ): Promise<Result<IHpkeSealResult>> {\n const result = await captureAsyncResult(async () => {\n const { sharedSecret, enc } = await _kemEncap(this._subtle, recipientPublicKey);\n const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);\n const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['encrypt']);\n const ct = await this._subtle.encrypt(\n { name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) },\n aesKey,\n _toBufferView(plaintext)\n );\n return { enc, ciphertext: new Uint8Array(ct) };\n });\n return result.withErrorFormat((e: string) => `HPKE sealBase failed: ${e}`);\n }\n\n /**\n * HPKE base-mode open (recipient side). RFC 9180 §6.1.\n *\n * Decapsulates `enc` using the recipient's X25519 private key, derives the same\n * AEAD key and nonce from the shared secret and `info`, then authenticates and\n * decrypts `ciphertext` with AES-256-GCM.\n *\n * Returns `Failure` on any of:\n * - Wrong private key (different DH output → different key derivation)\n * - Wrong `info` (different key schedule context → different AEAD key)\n * - Wrong `aad` (AES-GCM authentication fails)\n * - Tampered `ciphertext` or `enc` (authentication fails or DH fails)\n * - `enc` not exactly 32 bytes\n * - `ciphertext` shorter than 16 bytes (no room for authentication tag)\n *\n * @param recipientPrivateKey - Recipient's X25519 private `CryptoKey`\n * (`algorithm.name === 'X25519'`, `type === 'private'`, `usages` includes `'deriveBits'`).\n * **Must be extractable** (`extractable: true`) — the recipient's public key bytes\n * are recovered from the JWK `x` field during Decap.\n * @param info - Context-binding bytes. Must exactly match `info` from `sealBase`.\n * @param aad - Must exactly match `aad` from `sealBase`.\n * @param enc - The encapsulated key from `sealBase` — exactly 32 bytes.\n * @param ciphertext - The ciphertext from `sealBase` — `plaintext.length + 16` bytes.\n * @returns `Success` with decrypted plaintext bytes, or `Failure` with error context.\n */\n public async openBase(\n recipientPrivateKey: CryptoKey,\n info: Uint8Array,\n aad: Uint8Array,\n enc: Uint8Array,\n ciphertext: Uint8Array\n ): Promise<Result<Uint8Array>> {\n if (enc.length !== _N_PK) {\n return fail(`HPKE openBase: enc must be ${_N_PK} bytes, got ${enc.length}`);\n }\n if (ciphertext.length < _N_T) {\n return fail(\n `HPKE openBase: ciphertext too short (minimum ${_N_T} bytes for auth tag, got ${ciphertext.length})`\n );\n }\n const result = await captureAsyncResult(async () => {\n const sharedSecret = await _kemDecap(this._subtle, enc, recipientPrivateKey);\n const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);\n const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['decrypt']);\n const pt = await this._subtle.decrypt(\n { name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) },\n aesKey,\n _toBufferView(ciphertext)\n );\n return new Uint8Array(pt);\n });\n return result.withErrorFormat((e: string) => `HPKE openBase failed: ${e}`);\n }\n\n /**\n * HKDF-SHA256 key derivation (RFC 5869). Extract-then-Expand using SHA-256.\n *\n * This is raw RFC 5869 HKDF — it does **not** use RFC 9180's labeled variants.\n * The HPKE key schedule internally uses labeled HKDF; this method is the unlabeled\n * version for callers that need standalone key derivation.\n *\n * @param secret - Input keying material (IKM). Any length.\n * @param salt - Optional salt. Use `new Uint8Array(0)` if no salt is available\n * (RFC 5869: 32 zero bytes are used internally when salt is empty).\n * @param info - Context / application-binding bytes. Any length.\n * @param length - Number of output bytes to derive. Maximum 8160 bytes (255 × 32).\n * @returns `Success` with derived bytes, or `Failure` with error context.\n */\n public async hkdf(\n secret: Uint8Array,\n salt: Uint8Array,\n info: Uint8Array,\n length: number\n ): Promise<Result<Uint8Array>> {\n const result = await captureAsyncResult(async () => {\n const prk = await _hkdfExtract(this._subtle, salt, secret);\n return _hkdfExpand(this._subtle, prk, info, length);\n });\n return result.withErrorFormat((e: string) => `HKDF failed: ${e}`);\n }\n\n /**\n * Encodes an {@link IHpkeSealResult} as a single contiguous byte array for wire transport.\n *\n * Format: `enc` (32 bytes, fixed) || `ciphertext` (variable length).\n * The 32-byte `enc` length is fixed for X25519; the split point is unambiguous.\n *\n * @param result - The output of {@link HpkeProvider.sealBase}.\n * @returns Concatenated bytes: `enc || ciphertext`.\n */\n public static encodeEnvelope(result: IHpkeSealResult): Uint8Array {\n return _concat(result.enc, result.ciphertext);\n }\n\n /**\n * Decodes an envelope produced by {@link HpkeProvider.encodeEnvelope}.\n *\n * Validates that the buffer is at least 48 bytes (32-byte enc + 16-byte minimum\n * ciphertext containing the AES-GCM auth tag; zero-length plaintext is the minimum\n * meaningful case).\n *\n * @param envelope - Envelope bytes from `encodeEnvelope`.\n * @returns `Success` with `{ enc, ciphertext }`, or `Failure` if malformed.\n */\n public static decodeEnvelope(envelope: Uint8Array): Result<IHpkeSealResult> {\n const minLen = _N_PK + _N_T;\n if (envelope.length < minLen) {\n return fail(\n `HPKE decodeEnvelope: envelope too short (minimum ${minLen} bytes, got ${envelope.length})`\n );\n }\n return succeed({\n enc: envelope.slice(0, _N_PK),\n ciphertext: envelope.slice(_N_PK)\n });\n }\n}\n"]}
@@ -42,4 +42,7 @@ export { keyPairAlgorithmParams } from './keyPairAlgorithmParams';
42
42
  export { createEncryptedFile, decryptFile, fromBase64, toBase64, tryDecryptFile } from './encryptedFile';
43
43
  // Multibase/SPKI helpers
44
44
  export { exportPublicKeyAsMultibaseSpki, importPublicKeyFromMultibaseSpki, multibaseBase64UrlDecode, multibaseBase64UrlEncode } from './spkiHelpers';
45
+ // HPKE base mode (RFC 9180) — DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM
46
+ // hpkeProvider.ts has no Node-specific imports and is safe in the browser entry point.
47
+ export { HpkeProvider } from './hpkeProvider';
45
48
  //# sourceMappingURL=index.browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/index.browser.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAEZ;;;;GAIG;AAEH,iCAAiC;AACjC,cAAc,SAAS,CAAC;AAExB,YAAY;AACZ,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACZ,MAAM,aAAa,CAAC;AAErB,qBAAqB;AACrB,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB,uBAAuB;AACvB,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,6BAA6B;AAC7B,OAAO,EAAE,wBAAwB,EAAmC,MAAM,4BAA4B,CAAC;AAEvG,8DAA8D;AAC9D,OAAO,EAA2B,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,8DAA8D;AAC9D,4DAA4D;AAE5D,yBAAyB;AACzB,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,UAAU,EAEV,QAAQ,EACR,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB,yBAAyB;AACzB,OAAO,EACL,8BAA8B,EAC9B,gCAAgC,EAChC,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,eAAe,CAAC","sourcesContent":["// Copyright (c) 2024 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Crypto utilities for encrypted file handling and key management (browser version).\n * Note: For browser crypto provider, use \\@fgv/ts-web-extras.\n * @packageDocumentation\n */\n\n// Re-export all types from model\nexport * from './model';\n\n// Constants\nexport {\n AES_256_KEY_SIZE,\n DEFAULT_ALGORITHM,\n ENCRYPTED_FILE_FORMAT,\n GCM_AUTH_TAG_SIZE,\n GCM_IV_SIZE\n} from './constants';\n\n// KeyStore namespace\nimport * as KeyStore from './keystore';\nexport { KeyStore };\n\n// Converters namespace\nimport * as Converters from './converters';\nexport { Converters };\n\n// Direct encryption provider\nexport { DirectEncryptionProvider, IDirectEncryptionProviderParams } from './directEncryptionProvider';\n\n// WebCrypto parameter table for asymmetric keypair algorithms\nexport { IKeyPairAlgorithmParams, keyPairAlgorithmParams } from './keyPairAlgorithmParams';\n\n// Note: NodeCryptoProvider is NOT exported in browser version\n// Use BrowserCryptoProvider from @fgv/ts-web-extras instead\n\n// Encrypted file helpers\nexport {\n createEncryptedFile,\n decryptFile,\n fromBase64,\n ICreateEncryptedFileParams,\n toBase64,\n tryDecryptFile\n} from './encryptedFile';\n\n// Multibase/SPKI helpers\nexport {\n exportPublicKeyAsMultibaseSpki,\n importPublicKeyFromMultibaseSpki,\n multibaseBase64UrlDecode,\n multibaseBase64UrlEncode\n} from './spkiHelpers';\n"]}
1
+ {"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/index.browser.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAEZ;;;;GAIG;AAEH,iCAAiC;AACjC,cAAc,SAAS,CAAC;AAExB,YAAY;AACZ,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACZ,MAAM,aAAa,CAAC;AAErB,qBAAqB;AACrB,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB,uBAAuB;AACvB,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,6BAA6B;AAC7B,OAAO,EAAE,wBAAwB,EAAmC,MAAM,4BAA4B,CAAC;AAEvG,8DAA8D;AAC9D,OAAO,EAA2B,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,8DAA8D;AAC9D,4DAA4D;AAE5D,yBAAyB;AACzB,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,UAAU,EAEV,QAAQ,EACR,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB,yBAAyB;AACzB,OAAO,EACL,8BAA8B,EAC9B,gCAAgC,EAChC,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,eAAe,CAAC;AAEvB,qFAAqF;AACrF,uFAAuF;AACvF,OAAO,EAAE,YAAY,EAAmB,MAAM,gBAAgB,CAAC","sourcesContent":["// Copyright (c) 2024 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Crypto utilities for encrypted file handling and key management (browser version).\n * Note: For browser crypto provider, use \\@fgv/ts-web-extras.\n * @packageDocumentation\n */\n\n// Re-export all types from model\nexport * from './model';\n\n// Constants\nexport {\n AES_256_KEY_SIZE,\n DEFAULT_ALGORITHM,\n ENCRYPTED_FILE_FORMAT,\n GCM_AUTH_TAG_SIZE,\n GCM_IV_SIZE\n} from './constants';\n\n// KeyStore namespace\nimport * as KeyStore from './keystore';\nexport { KeyStore };\n\n// Converters namespace\nimport * as Converters from './converters';\nexport { Converters };\n\n// Direct encryption provider\nexport { DirectEncryptionProvider, IDirectEncryptionProviderParams } from './directEncryptionProvider';\n\n// WebCrypto parameter table for asymmetric keypair algorithms\nexport { IKeyPairAlgorithmParams, keyPairAlgorithmParams } from './keyPairAlgorithmParams';\n\n// Note: NodeCryptoProvider is NOT exported in browser version\n// Use BrowserCryptoProvider from @fgv/ts-web-extras instead\n\n// Encrypted file helpers\nexport {\n createEncryptedFile,\n decryptFile,\n fromBase64,\n ICreateEncryptedFileParams,\n toBase64,\n tryDecryptFile\n} from './encryptedFile';\n\n// Multibase/SPKI helpers\nexport {\n exportPublicKeyAsMultibaseSpki,\n importPublicKeyFromMultibaseSpki,\n multibaseBase64UrlDecode,\n multibaseBase64UrlEncode\n} from './spkiHelpers';\n\n// HPKE base mode (RFC 9180) — DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM\n// hpkeProvider.ts has no Node-specific imports and is safe in the browser entry point.\nexport { HpkeProvider, IHpkeSealResult } from './hpkeProvider';\n"]}
@@ -42,4 +42,6 @@ export { NodeCryptoProvider, nodeCryptoProvider } from './nodeCryptoProvider';
42
42
  export { createEncryptedFile, decryptFile, fromBase64, toBase64, tryDecryptFile } from './encryptedFile';
43
43
  // Multibase/SPKI helpers
44
44
  export { exportPublicKeyAsMultibaseSpki, importPublicKeyFromMultibaseSpki, multibaseBase64UrlDecode, multibaseBase64UrlEncode } from './spkiHelpers';
45
+ // HPKE base mode (RFC 9180) — DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM
46
+ export { HpkeProvider } from './hpkeProvider';
45
47
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAEZ;;;GAGG;AAEH,iCAAiC;AACjC,cAAc,SAAS,CAAC;AAExB,YAAY;AACZ,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,qBAAqB;AACrB,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB,uBAAuB;AACvB,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,6BAA6B;AAC7B,OAAO,EAAE,wBAAwB,EAAmC,MAAM,4BAA4B,CAAC;AAEvG,8DAA8D;AAC9D,OAAO,EAA2B,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,qDAAqD;AACrD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE9E,yBAAyB;AACzB,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,UAAU,EAEV,QAAQ,EACR,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB,yBAAyB;AACzB,OAAO,EACL,8BAA8B,EAC9B,gCAAgC,EAChC,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,eAAe,CAAC","sourcesContent":["// Copyright (c) 2024 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Crypto utilities for encrypted file handling and key management.\n * @packageDocumentation\n */\n\n// Re-export all types from model\nexport * from './model';\n\n// Constants\nimport * as Constants from './constants';\nexport { Constants };\n\n// KeyStore namespace\nimport * as KeyStore from './keystore';\nexport { KeyStore };\n\n// Converters namespace\nimport * as Converters from './converters';\nexport { Converters };\n\n// Direct encryption provider\nexport { DirectEncryptionProvider, IDirectEncryptionProviderParams } from './directEncryptionProvider';\n\n// WebCrypto parameter table for asymmetric keypair algorithms\nexport { IKeyPairAlgorithmParams, keyPairAlgorithmParams } from './keyPairAlgorithmParams';\n\n// Node.js crypto provider (Node.js environment only)\nexport { NodeCryptoProvider, nodeCryptoProvider } from './nodeCryptoProvider';\n\n// Encrypted file helpers\nexport {\n createEncryptedFile,\n decryptFile,\n fromBase64,\n ICreateEncryptedFileParams,\n toBase64,\n tryDecryptFile\n} from './encryptedFile';\n\n// Multibase/SPKI helpers\nexport {\n exportPublicKeyAsMultibaseSpki,\n importPublicKeyFromMultibaseSpki,\n multibaseBase64UrlDecode,\n multibaseBase64UrlEncode\n} from './spkiHelpers';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAEZ;;;GAGG;AAEH,iCAAiC;AACjC,cAAc,SAAS,CAAC;AAExB,YAAY;AACZ,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,qBAAqB;AACrB,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB,uBAAuB;AACvB,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,6BAA6B;AAC7B,OAAO,EAAE,wBAAwB,EAAmC,MAAM,4BAA4B,CAAC;AAEvG,8DAA8D;AAC9D,OAAO,EAA2B,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,qDAAqD;AACrD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE9E,yBAAyB;AACzB,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,UAAU,EAEV,QAAQ,EACR,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB,yBAAyB;AACzB,OAAO,EACL,8BAA8B,EAC9B,gCAAgC,EAChC,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,eAAe,CAAC;AAEvB,qFAAqF;AACrF,OAAO,EAAE,YAAY,EAAmB,MAAM,gBAAgB,CAAC","sourcesContent":["// Copyright (c) 2024 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Crypto utilities for encrypted file handling and key management.\n * @packageDocumentation\n */\n\n// Re-export all types from model\nexport * from './model';\n\n// Constants\nimport * as Constants from './constants';\nexport { Constants };\n\n// KeyStore namespace\nimport * as KeyStore from './keystore';\nexport { KeyStore };\n\n// Converters namespace\nimport * as Converters from './converters';\nexport { Converters };\n\n// Direct encryption provider\nexport { DirectEncryptionProvider, IDirectEncryptionProviderParams } from './directEncryptionProvider';\n\n// WebCrypto parameter table for asymmetric keypair algorithms\nexport { IKeyPairAlgorithmParams, keyPairAlgorithmParams } from './keyPairAlgorithmParams';\n\n// Node.js crypto provider (Node.js environment only)\nexport { NodeCryptoProvider, nodeCryptoProvider } from './nodeCryptoProvider';\n\n// Encrypted file helpers\nexport {\n createEncryptedFile,\n decryptFile,\n fromBase64,\n ICreateEncryptedFileParams,\n toBase64,\n tryDecryptFile\n} from './encryptedFile';\n\n// Multibase/SPKI helpers\nexport {\n exportPublicKeyAsMultibaseSpki,\n importPublicKeyFromMultibaseSpki,\n multibaseBase64UrlDecode,\n multibaseBase64UrlEncode\n} from './spkiHelpers';\n\n// HPKE base mode (RFC 9180) — DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM\nexport { HpkeProvider, IHpkeSealResult } from './hpkeProvider';\n"]}
@@ -18,7 +18,7 @@
18
18
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  // SOFTWARE.
20
20
  import { Converters, succeed, Validators } from '@fgv/ts-utils';
21
- import { base64String, encryptionAlgorithm, keyDerivationParams } from '../converters';
21
+ import { base64String, encryptionAlgorithm, pbkdf2KeyDerivationParams } from '../converters';
22
22
  import { allKeyPairAlgorithms, allKeyStoreSecretTypes, allKeyStoreSymmetricSecretTypes, KEYSTORE_FORMAT, allKeyStoreAsymmetricSecretTypes } from './model';
23
23
  // ============================================================================
24
24
  // Key Store Format Converter
@@ -171,6 +171,6 @@ export const keystoreFile = Converters.object({
171
171
  iv: base64String,
172
172
  authTag: base64String,
173
173
  encryptedData: base64String,
174
- keyDerivation: keyDerivationParams
174
+ keyDerivation: pbkdf2KeyDerivationParams
175
175
  });
176
176
  //# sourceMappingURL=converters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"converters.js","sourceRoot":"","sources":["../../../../src/packlets/crypto-utils/keystore/converters.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport { Converter, Converters, succeed, Validation, Validator, Validators } from '@fgv/ts-utils';\nimport { base64String, encryptionAlgorithm, keyDerivationParams } from '../converters';\nimport {\n allKeyPairAlgorithms,\n allKeyStoreSecretTypes,\n allKeyStoreSymmetricSecretTypes,\n IKeyStoreAsymmetricEntryJson,\n IKeyStoreEntryJson,\n IKeyStoreFile,\n IKeyStoreSymmetricEntryJson,\n IKeyStoreVaultContents,\n KEYSTORE_FORMAT,\n KeyPairAlgorithm,\n KeyStoreAsymmetricSecretType,\n KeyStoreFormat,\n KeyStoreSecretType,\n KeyStoreSymmetricSecretType,\n allKeyStoreAsymmetricSecretTypes\n} from './model';\n\n// ============================================================================\n// Key Store Format Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyStoreFormat | key store format} version.\n * @public\n */\nexport const keystoreFormat: Converter<KeyStoreFormat> = Converters.enumeratedValue<KeyStoreFormat>([\n KEYSTORE_FORMAT\n]);\n\n// ============================================================================\n// Secret Type Converters\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyStoreSecretType | any key store secret type} discriminator.\n * Accepts both symmetric and asymmetric type values.\n * @public\n */\nexport const keystoreSecretType: Converter<KeyStoreSecretType> =\n Converters.enumeratedValue<KeyStoreSecretType>(allKeyStoreSecretTypes);\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyStoreSymmetricSecretType | symmetric secret type} discriminator.\n * Accepts only `'encryption-key'` and `'api-key'`.\n * @public\n */\nexport const keystoreSymmetricSecretType: Converter<KeyStoreSymmetricSecretType> =\n Converters.enumeratedValue<KeyStoreSymmetricSecretType>(allKeyStoreSymmetricSecretTypes);\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyStoreAsymmetricSecretType | asymmetric secret type} discriminator.\n * Accepts only `'asymmetric-keypair'`.\n * @public\n */\nexport const keystoreAsymmetricSecretType: Converter<KeyStoreAsymmetricSecretType> =\n Converters.enumeratedValue<KeyStoreAsymmetricSecretType>(allKeyStoreAsymmetricSecretTypes);\n\n// ============================================================================\n// Key Pair Algorithm Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyPairAlgorithm | key pair algorithm}.\n * @public\n */\nexport const keyPairAlgorithm: Converter<KeyPairAlgorithm> =\n Converters.enumeratedValue<KeyPairAlgorithm>(allKeyPairAlgorithms);\n\n// ============================================================================\n// JWK Shape Validator\n// ============================================================================\n\n/**\n * In-place shape check for a JSON Web Key. Asserts only that the input is a\n * non-array object whose `kty` discriminator is a string; every other JWK\n * field passes through untouched. This is intentionally **not** a true JWK\n * validator — per-algorithm correctness (RSA `n`/`e`, EC `crv`/`x`/`y`,\n * key-size constraints, etc.) is delegated to `crypto.subtle.importKey` at\n * first use, which is the authoritative checker. The \"shape\" suffix in the\n * name is the warning sign for readers expecting full validation.\n * @remarks\n * Built with `Validators.object` (in-place, non-strict) so unknown JWK fields\n * survive the round-trip; the cast to `FieldValidators<JsonWebKey>` is required\n * only because TypeScript's mapped type demands an entry for every key in\n * `JsonWebKey`. At runtime the `ObjectValidator` only inspects keys present in\n * the field-validators map.\n * @public\n */\nexport const jsonWebKeyShape: Validator<JsonWebKey> = Validators.object<JsonWebKey>({\n kty: Validators.string\n} as Validation.Classes.FieldValidators<JsonWebKey>);\n\n// ============================================================================\n// Symmetric Secret Entry Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntryJson | symmetric secret entry} in JSON form.\n *\n * @remarks\n * Backwards compatibility with vaults written before asymmetric-keypair\n * support: those entries may lack the `type` discriminator on the wire. To\n * keep the model type honest (`type` is required on\n * {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntryJson}, see its docs),\n * we declare `type` in `optionalFields` so the inner `Converters.object` will\n * accept input without it, then `.map()` injects the default\n * `'encryption-key'` when missing. The output therefore always carries the\n * discriminator and downstream code never sees the legacy missing-type form.\n *\n * @public\n */\nexport const keystoreSymmetricEntryJson: Converter<IKeyStoreSymmetricEntryJson> =\n Converters.object<IKeyStoreSymmetricEntryJson>(\n {\n name: Converters.string,\n type: keystoreSymmetricSecretType,\n key: base64String,\n description: Converters.string,\n createdAt: Converters.string\n },\n {\n // `type` is optional at the input layer for legacy-vault compatibility;\n // the .map() below normalizes by injecting the default.\n optionalFields: ['type', 'description']\n }\n ).map((entry) =>\n succeed<IKeyStoreSymmetricEntryJson>({\n ...entry,\n type: entry.type ?? 'encryption-key'\n })\n );\n\n// ============================================================================\n// Asymmetric Keypair Entry Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.IKeyStoreAsymmetricEntryJson | asymmetric keypair entry} in JSON form.\n * The `publicKeyJwk` field passes through {@link CryptoUtils.KeyStore.Converters.jsonWebKeyShape | jsonWebKeyShape}\n * (shape check only — see its docs); cryptographic correctness is enforced by\n * `crypto.subtle.importKey` at use.\n * @public\n */\nexport const keystoreAsymmetricEntryJson: Converter<IKeyStoreAsymmetricEntryJson> =\n Converters.object<IKeyStoreAsymmetricEntryJson>({\n name: Converters.string,\n type: keystoreAsymmetricSecretType,\n id: Converters.string,\n algorithm: keyPairAlgorithm,\n publicKeyJwk: jsonWebKeyShape,\n description: Converters.string.optional(),\n createdAt: Converters.string\n });\n\n// ============================================================================\n// Discriminated-Union Entry Converter\n// ============================================================================\n\n/**\n * Discriminated-union converter for any {@link CryptoUtils.KeyStore.IKeyStoreEntryJson | key store entry} in JSON form.\n * Routes by the `type` field: `'asymmetric-keypair'` is parsed by\n * {@link CryptoUtils.KeyStore.Converters.keystoreAsymmetricEntryJson | keystoreAsymmetricEntryJson},\n * anything else (including a missing `type` field for backwards compatibility) by\n * {@link CryptoUtils.KeyStore.Converters.keystoreSymmetricEntryJson | keystoreSymmetricEntryJson}.\n * @public\n */\nexport const keystoreSecretEntryJson: Converter<IKeyStoreEntryJson> = Converters.oneOf<IKeyStoreEntryJson>([\n keystoreAsymmetricEntryJson,\n keystoreSymmetricEntryJson\n]);\n\n// ============================================================================\n// Vault Contents Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.IKeyStoreVaultContents | key store vault contents} (decrypted state).\n * @public\n */\nexport const keystoreVaultContents: Converter<IKeyStoreVaultContents> =\n Converters.object<IKeyStoreVaultContents>({\n version: keystoreFormat,\n secrets: Converters.recordOf(keystoreSecretEntryJson)\n });\n\n// ============================================================================\n// Key Store File Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.IKeyStoreFile | encrypted key store file}.\n * @public\n */\nexport const keystoreFile: Converter<IKeyStoreFile> = Converters.object<IKeyStoreFile>({\n format: keystoreFormat,\n algorithm: encryptionAlgorithm,\n iv: base64String,\n authTag: base64String,\n encryptedData: base64String,\n keyDerivation: keyDerivationParams\n});\n"]}
1
+ {"version":3,"file":"converters.js","sourceRoot":"","sources":["../../../../src/packlets/crypto-utils/keystore/converters.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport { Converter, Converters, succeed, Validation, Validator, Validators } from '@fgv/ts-utils';\nimport { base64String, encryptionAlgorithm, pbkdf2KeyDerivationParams } from '../converters';\nimport {\n allKeyPairAlgorithms,\n allKeyStoreSecretTypes,\n allKeyStoreSymmetricSecretTypes,\n IKeyStoreAsymmetricEntryJson,\n IKeyStoreEntryJson,\n IKeyStoreFile,\n IKeyStoreSymmetricEntryJson,\n IKeyStoreVaultContents,\n KEYSTORE_FORMAT,\n KeyPairAlgorithm,\n KeyStoreAsymmetricSecretType,\n KeyStoreFormat,\n KeyStoreSecretType,\n KeyStoreSymmetricSecretType,\n allKeyStoreAsymmetricSecretTypes\n} from './model';\n\n// ============================================================================\n// Key Store Format Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyStoreFormat | key store format} version.\n * @public\n */\nexport const keystoreFormat: Converter<KeyStoreFormat> = Converters.enumeratedValue<KeyStoreFormat>([\n KEYSTORE_FORMAT\n]);\n\n// ============================================================================\n// Secret Type Converters\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyStoreSecretType | any key store secret type} discriminator.\n * Accepts both symmetric and asymmetric type values.\n * @public\n */\nexport const keystoreSecretType: Converter<KeyStoreSecretType> =\n Converters.enumeratedValue<KeyStoreSecretType>(allKeyStoreSecretTypes);\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyStoreSymmetricSecretType | symmetric secret type} discriminator.\n * Accepts only `'encryption-key'` and `'api-key'`.\n * @public\n */\nexport const keystoreSymmetricSecretType: Converter<KeyStoreSymmetricSecretType> =\n Converters.enumeratedValue<KeyStoreSymmetricSecretType>(allKeyStoreSymmetricSecretTypes);\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyStoreAsymmetricSecretType | asymmetric secret type} discriminator.\n * Accepts only `'asymmetric-keypair'`.\n * @public\n */\nexport const keystoreAsymmetricSecretType: Converter<KeyStoreAsymmetricSecretType> =\n Converters.enumeratedValue<KeyStoreAsymmetricSecretType>(allKeyStoreAsymmetricSecretTypes);\n\n// ============================================================================\n// Key Pair Algorithm Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.KeyPairAlgorithm | key pair algorithm}.\n * @public\n */\nexport const keyPairAlgorithm: Converter<KeyPairAlgorithm> =\n Converters.enumeratedValue<KeyPairAlgorithm>(allKeyPairAlgorithms);\n\n// ============================================================================\n// JWK Shape Validator\n// ============================================================================\n\n/**\n * In-place shape check for a JSON Web Key. Asserts only that the input is a\n * non-array object whose `kty` discriminator is a string; every other JWK\n * field passes through untouched. This is intentionally **not** a true JWK\n * validator — per-algorithm correctness (RSA `n`/`e`, EC `crv`/`x`/`y`,\n * key-size constraints, etc.) is delegated to `crypto.subtle.importKey` at\n * first use, which is the authoritative checker. The \"shape\" suffix in the\n * name is the warning sign for readers expecting full validation.\n * @remarks\n * Built with `Validators.object` (in-place, non-strict) so unknown JWK fields\n * survive the round-trip; the cast to `FieldValidators<JsonWebKey>` is required\n * only because TypeScript's mapped type demands an entry for every key in\n * `JsonWebKey`. At runtime the `ObjectValidator` only inspects keys present in\n * the field-validators map.\n * @public\n */\nexport const jsonWebKeyShape: Validator<JsonWebKey> = Validators.object<JsonWebKey>({\n kty: Validators.string\n} as Validation.Classes.FieldValidators<JsonWebKey>);\n\n// ============================================================================\n// Symmetric Secret Entry Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntryJson | symmetric secret entry} in JSON form.\n *\n * @remarks\n * Backwards compatibility with vaults written before asymmetric-keypair\n * support: those entries may lack the `type` discriminator on the wire. To\n * keep the model type honest (`type` is required on\n * {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntryJson}, see its docs),\n * we declare `type` in `optionalFields` so the inner `Converters.object` will\n * accept input without it, then `.map()` injects the default\n * `'encryption-key'` when missing. The output therefore always carries the\n * discriminator and downstream code never sees the legacy missing-type form.\n *\n * @public\n */\nexport const keystoreSymmetricEntryJson: Converter<IKeyStoreSymmetricEntryJson> =\n Converters.object<IKeyStoreSymmetricEntryJson>(\n {\n name: Converters.string,\n type: keystoreSymmetricSecretType,\n key: base64String,\n description: Converters.string,\n createdAt: Converters.string\n },\n {\n // `type` is optional at the input layer for legacy-vault compatibility;\n // the .map() below normalizes by injecting the default.\n optionalFields: ['type', 'description']\n }\n ).map((entry) =>\n succeed<IKeyStoreSymmetricEntryJson>({\n ...entry,\n type: entry.type ?? 'encryption-key'\n })\n );\n\n// ============================================================================\n// Asymmetric Keypair Entry Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.IKeyStoreAsymmetricEntryJson | asymmetric keypair entry} in JSON form.\n * The `publicKeyJwk` field passes through {@link CryptoUtils.KeyStore.Converters.jsonWebKeyShape | jsonWebKeyShape}\n * (shape check only — see its docs); cryptographic correctness is enforced by\n * `crypto.subtle.importKey` at use.\n * @public\n */\nexport const keystoreAsymmetricEntryJson: Converter<IKeyStoreAsymmetricEntryJson> =\n Converters.object<IKeyStoreAsymmetricEntryJson>({\n name: Converters.string,\n type: keystoreAsymmetricSecretType,\n id: Converters.string,\n algorithm: keyPairAlgorithm,\n publicKeyJwk: jsonWebKeyShape,\n description: Converters.string.optional(),\n createdAt: Converters.string\n });\n\n// ============================================================================\n// Discriminated-Union Entry Converter\n// ============================================================================\n\n/**\n * Discriminated-union converter for any {@link CryptoUtils.KeyStore.IKeyStoreEntryJson | key store entry} in JSON form.\n * Routes by the `type` field: `'asymmetric-keypair'` is parsed by\n * {@link CryptoUtils.KeyStore.Converters.keystoreAsymmetricEntryJson | keystoreAsymmetricEntryJson},\n * anything else (including a missing `type` field for backwards compatibility) by\n * {@link CryptoUtils.KeyStore.Converters.keystoreSymmetricEntryJson | keystoreSymmetricEntryJson}.\n * @public\n */\nexport const keystoreSecretEntryJson: Converter<IKeyStoreEntryJson> = Converters.oneOf<IKeyStoreEntryJson>([\n keystoreAsymmetricEntryJson,\n keystoreSymmetricEntryJson\n]);\n\n// ============================================================================\n// Vault Contents Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.IKeyStoreVaultContents | key store vault contents} (decrypted state).\n * @public\n */\nexport const keystoreVaultContents: Converter<IKeyStoreVaultContents> =\n Converters.object<IKeyStoreVaultContents>({\n version: keystoreFormat,\n secrets: Converters.recordOf(keystoreSecretEntryJson)\n });\n\n// ============================================================================\n// Key Store File Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.KeyStore.IKeyStoreFile | encrypted key store file}.\n * @public\n */\nexport const keystoreFile: Converter<IKeyStoreFile> = Converters.object<IKeyStoreFile>({\n format: keystoreFormat,\n algorithm: encryptionAlgorithm,\n iv: base64String,\n authTag: base64String,\n encryptedData: base64String,\n keyDerivation: pbkdf2KeyDerivationParams\n});\n"]}