simple-hpke 0.0.9 → 0.1.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.
package/dist/index.d.ts CHANGED
@@ -1,67 +1,34 @@
1
- import { type SupportedEncodings } from 'uint8arrays';
2
- export type Uint8ArrayEncodings = SupportedEncodings;
1
+ import { type RecipientKey, encryptToString, encryptBytes } from './util';
3
2
  /**
4
- * A recipient's X25519 public key, in any of four forms:
5
- * - `CryptoKey`: an X25519 public key.
6
- * - `CryptoKeyPair`: its `.publicKey` is used (encryption never needs the
7
- * private half).
8
- * - `Uint8Array`: 32 raw X25519 public-key bytes.
9
- * - `{ publicKey, encoding? }`: the public key as an encoded string;
10
- * `encoding` defaults to `base64url`.
11
- */
12
- export type RecipientKey = CryptoKey | CryptoKeyPair | Uint8Array | {
13
- publicKey: string;
14
- encoding?: Uint8ArrayEncodings;
15
- };
16
- /**
17
- * Wrap an AES key to a recipient's public key.
3
+ * Create a new AES key for the given public key.
18
4
  *
19
- * @param recipient The recipient's X25519 public key, as a `CryptoKey`,
20
- * `CryptoKeyPair` (its `.publicKey` is used), 32 raw bytes (`Uint8Array`),
21
- * or `{ publicKey:string, encoding? }` (encoding defaults to `base64url`).
22
- * @param aesKey Optional key to seal, as either an AES-GCM `CryptoKey` or its
23
- * raw bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh
24
- * extractable key of `opts.keysize` bits. A supplied `CryptoKey` MUST be
25
- * extractable (its raw bytes are sealed).
26
- * @param opts `keysize` (128/256, default 256; ignored when `aesKey` is
27
- * supplied) and `info` (bound into the HPKE key schedule; default empty).
28
- * @returns { wrapped:Uint8Array, key:CryptoKey } The envelope bytes and a
29
- * usable AES-GCM `CryptoKey`.
5
+ * @param recipient Public key for decryptor
6
+ * @param opts `size` and `info`
7
+ * @returns {{ enc, key }} The wrapped envelope bytes and the generated
8
+ * AES-GCM key.
30
9
  */
31
- export declare function seal(recipient: RecipientKey, aesKey?: CryptoKey | Uint8Array | null, opts?: {
32
- keysize?: 128 | 256;
10
+ export declare function create(recipient: RecipientKey, opts?: {
11
+ size?: 128 | 256;
33
12
  info?: Uint8Array | string;
34
13
  }): Promise<{
35
- wrapped: Uint8Array;
14
+ enc: Uint8Array<ArrayBufferLike>;
36
15
  key: CryptoKey;
37
16
  }>;
38
17
  /**
39
- * Recover an AES key that was wrapped with `seal`, using your private key.
18
+ * Encrypt the given AES key to the given public key.
40
19
  *
41
- * @param keypair The same X25519 `CryptoKeyPair` used to seal.
42
- * @param wrapped The envelope returned by `seal` (`enc ‖ ciphertext`).
43
- * @param opts `info` — must match the value passed to `seal`.
44
- * @returns The recovered AES-GCM `CryptoKey` (extractable).
20
+ * @returns {{ enc, key }} The wrapped envelope bytes and a usable AES-GCM
21
+ * key with the same raw bytes as `aesKey`.
45
22
  */
46
- declare function openBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
47
- info: Uint8Array | string;
48
- }): Promise<CryptoKey>;
49
- /**
50
- * Like `open`, but returns the recovered key as raw bytes instead of
51
- * importing it as an AES-GCM `CryptoKey`. Exposed as `open.raw`.
52
- *
53
- * @param keypair The same X25519 `CryptoKeyPair` used to seal.
54
- * @param wrapped The envelope returned by `seal` (`enc ‖ ciphertext`).
55
- * @param opts `info` — must match the value passed to `seal`.
56
- * @returns The recovered key bytes (16 or 32 bytes, matching whatever
57
- * was sealed).
58
- */
59
- declare function openRawBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
60
- info: Uint8Array | string;
61
- }): Promise<Uint8Array>;
23
+ export declare function seal(recipient: RecipientKey, aesKey: CryptoKey | Uint8Array | null, opts?: {
24
+ info?: Uint8Array | string;
25
+ }): Promise<{
26
+ enc: Uint8Array<ArrayBufferLike>;
27
+ key: CryptoKey;
28
+ }>;
62
29
  /**
63
- * Recover the AES key wrapped by `seal`. Call `open(...)` for a usable
64
- * AES-GCM `CryptoKey`, or `open.raw(...)` for the raw key bytes.
30
+ * Recover the AES key wrapped by `create` or `seal`. Call `open(...)` for a
31
+ * usable AES-GCM `CryptoKey`, or `open.raw(...)` for the raw key bytes.
65
32
  */
66
33
  export declare const open: typeof openBytes & {
67
34
  raw: typeof openRawBytes;
@@ -132,63 +99,28 @@ declare function decryptFromString(keypair: CryptoKeyPair, message: string, opts
132
99
  buffer: true;
133
100
  }): Promise<Uint8Array>;
134
101
  /**
135
- * Seal a fresh (or supplied) AES key to `recipient`, then AES-GCM encrypt a
136
- * message under it. The wrapped key, IV, and ciphertext are concatenated
137
- * into a single self-describing envelope.
138
- *
139
- * Wire format: `wrappedLen(2, big-endian) ‖ wrapped ‖ iv(12) ‖ ciphertext`.
140
- * The length prefix lets `decrypt` slice the segments apart for either
141
- * 128- or 256-bit wrapped keys.
142
- *
143
- * @param recipient The recipient's X25519 public key, as a `CryptoKey`,
144
- * `CryptoKeyPair` (its `.publicKey` is used), 32 raw bytes (`Uint8Array`),
145
- * or `{ publicKey:string, encoding? }` (encoding defaults to `base64url`).
146
- * @param message Plaintext to encrypt. A `string` is UTF-8 encoded.
147
- * @param aesKey Optional key, as either an AES-GCM `CryptoKey` or its raw
148
- * bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh key of
149
- * `opts.keysize` bits. A supplied `CryptoKey` MUST be extractable. Each
150
- * call picks a fresh random 96-bit IV for the message ciphertext, so
151
- * reusing the same `aesKey` across many calls carries the standard
152
- * birthday bound for random nonces (collision risk becomes
153
- * non-negligible around 2^32 messages under one key, NIST SP 800-38D) --
154
- * prefer a fresh key per call (the default) over reusing one at scale.
155
- * @param opts `keysize` (128/256, default 256; ignored when `aesKey` is
156
- * supplied) and `info` (bound into the HPKE key schedule).
157
- * @returns The concatenated envelope bytes.
102
+ * Like `open`, but returns the recovered key as raw bytes instead of
103
+ * importing it as an AES-GCM `CryptoKey`. Exposed as `open.raw`.
158
104
  *
159
- * `encrypt.asString(...)` returns the same envelope as an encoded string.
105
+ * @param keypair The same X25519 `CryptoKeyPair` used to create or seal.
106
+ * @param wrapped The `enc` bytes returned by `create` or `seal`.
107
+ * @param opts `info` — must match the value passed to `create` or `seal`.
108
+ * @returns The recovered key bytes (16 or 32 bytes, matching whatever
109
+ * was wrapped).
160
110
  */
161
- declare function encryptBytes(recipient: RecipientKey, message: Uint8Array | string, aesKey?: CryptoKey | Uint8Array | null, opts?: {
162
- keysize?: 128 | 256;
163
- info?: Uint8Array | string;
111
+ declare function openRawBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
112
+ info: Uint8Array | string;
164
113
  }): Promise<Uint8Array>;
165
114
  /**
166
- * Like `encrypt`, but encodes the envelope bytes to a string — handy for
167
- * transports that carry text (JSON, URLs, headers). Decode with
168
- * `fromString(...)` (or any matching decoder) and pass the bytes to
169
- * `decrypt` / `decrypt.asString`. Exposed as `encrypt.asString`.
115
+ * Recover an AES key that was wrapped with `create` or `seal`.
170
116
  *
171
- * @param recipient The recipient's X25519 public key, as a `CryptoKey`,
172
- * `CryptoKeyPair` (its `.publicKey` is used), 32 raw bytes (`Uint8Array`),
173
- * or `{ publicKey:string, encoding? }` (encoding defaults to `base64url`).
174
- * @param message Plaintext to encrypt. A `string` is UTF-8 encoded.
175
- * @param aesKey Optional key, as either an AES-GCM `CryptoKey` or its raw
176
- * bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh key of
177
- * `opts.keysize` bits. A supplied `CryptoKey` MUST be extractable. Each
178
- * call picks a fresh random 96-bit IV for the message ciphertext, so
179
- * reusing the same `aesKey` across many calls carries the standard
180
- * birthday bound for random nonces (collision risk becomes
181
- * non-negligible around 2^32 messages under one key, NIST SP 800-38D) --
182
- * prefer a fresh key per call (the default) over reusing one at scale.
183
- * @param opts `keysize` (128/256, default 256; ignored when `aesKey` is
184
- * supplied), `info` (bound into the HPKE key schedule), and `encoding`
185
- * (the string encoding of the returned envelope; default `base64url`).
186
- * @returns The encoded envelope string.
117
+ * @param keypair The same X25519 `CryptoKeyPair` used to create or seal.
118
+ * @param wrapped The `enc` bytes returned by `create` or `seal`.
119
+ * @param opts `info` — must match the value passed to `create` or `seal`.
120
+ * @returns The recovered AES-GCM `CryptoKey` (extractable).
187
121
  */
188
- declare function encryptToString(recipient: RecipientKey, message: Uint8Array | string, aesKey?: CryptoKey | Uint8Array | null, opts?: {
189
- keysize?: 128 | 256;
190
- info?: Uint8Array | string;
191
- encoding?: SupportedEncodings;
192
- }): Promise<string>;
122
+ declare function openBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
123
+ info: Uint8Array | string;
124
+ }): Promise<CryptoKey>;
193
125
  export {};
194
126
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAM3E,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,CAAA;AAEpD;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAClB,SAAS,GACT,aAAa,GACb,UAAU,GACV;IAAE,SAAS,EAAC,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAC,mBAAmB,CAAA;CAAE,CAAA;AAqCzD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,IAAI,CACtB,SAAS,EAAC,YAAY,EACtB,MAAM,CAAC,EAAC,SAAS,GAAC,UAAU,GAAC,IAAI,EACjC,IAAI,CAAC,EAAC;IAEF,OAAO,CAAC,EAAC,GAAG,GAAC,GAAG,CAAA;IAEhB,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAC1B,GACH,OAAO,CAAC;IAAE,OAAO,EAAC,UAAU,CAAC;IAAC,GAAG,EAAC,SAAS,CAAA;CAAE,CAAC,CA0B/C;AAED;;;;;;;GAOG;AACH,iBAAe,SAAS,CACpB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GAClC,OAAO,CAAC,SAAS,CAAC,CAGnB;AAED;;;;;;;;;GASG;AACH,iBAAe,YAAY,CACvB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GAClC,OAAO,CAAC,UAAU,CAAC,CAYpB;AAED;;;GAGG;AACH,eAAO,MAAM,IAAI;;CAEf,CAAA;AAOF;;;;GAIG;AACH,eAAO,MAAM,OAAO;;CAElB,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;;;CAGlB,CAAA;AAEF;;;;;;;;GAQG;AACH,iBAAe,YAAY,CACvB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,UAAU,CAAC,CA6BpB;AAED;;;;;;;;GAQG;AACH,iBAAe,eAAe,CAC1B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,MAAM,CAAC,CAGhB;AAED;;;;;;;;;;;;;GAaG;AACH,iBAAe,iBAAiB,CAC5B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,MAAM,EACd,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAC;IAAC,MAAM,CAAC,EAAC,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,MAAM,CAAC,CAAA;AAEjB,iBAAe,iBAAiB,CAC5B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,MAAM,EACd,IAAI,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAC;IAAC,MAAM,EAAC,IAAI,CAAA;CAAE,GAC/C,OAAO,CAAC,UAAU,CAAC,CAAA;AAmbrB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,iBAAe,YAAY,CACvB,SAAS,EAAC,YAAY,EACtB,OAAO,EAAC,UAAU,GAAC,MAAM,EACzB,MAAM,CAAC,EAAC,SAAS,GAAC,UAAU,GAAC,IAAI,EACjC,IAAI,CAAC,EAAC;IACF,OAAO,CAAC,EAAC,GAAG,GAAC,GAAG,CAAA;IAChB,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAC1B,GACH,OAAO,CAAC,UAAU,CAAC,CAcpB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,iBAAe,eAAe,CAC1B,SAAS,EAAC,YAAY,EACtB,OAAO,EAAC,UAAU,GAAC,MAAM,EACzB,MAAM,CAAC,EAAC,SAAS,GAAC,UAAU,GAAC,IAAI,EACjC,IAAI,CAAC,EAAC;IACF,OAAO,CAAC,EAAC,GAAG,GAAC,GAAG,CAAA;IAChB,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAC,kBAAkB,CAAA;CAC/B,GACH,OAAO,CAAC,MAAM,CAAC,CAGhB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,KAAK,YAAY,EAGjB,eAAe,EACf,YAAY,EAOf,MAAM,QAAQ,CAAA;AAoBf;;;;;;;GAOG;AACH,wBAAsB,MAAM,CACxB,SAAS,EAAC,YAAY,EACtB,IAAI,CAAC,EAAC;IAEF,IAAI,CAAC,EAAC,GAAG,GAAC,GAAG,CAAA;IAEb,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAC1B,GACH,OAAO,CAAC;IAAE,GAAG,EAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IAAC,GAAG,EAAC,SAAS,CAAA;CAAE,CAAC,CAG5D;AAED;;;;;GAKG;AACH,wBAAsB,IAAI,CACtB,SAAS,EAAC,YAAY,EACtB,MAAM,EAAC,SAAS,GAAC,UAAU,GAAC,IAAI,EAChC,IAAI,CAAC,EAAC;IAEF,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAC1B,GACH,OAAO,CAAC;IAAE,GAAG,EAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IAAC,GAAG,EAAC,SAAS,CAAA;CAAE,CAAC,CAG5D;AAED;;;GAGG;AACH,eAAO,MAAM,IAAI;;CAEf,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,OAAO;;CAElB,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;;;CAGlB,CAAA;AAEF;;;;;;;;GAQG;AACH,iBAAe,YAAY,CACvB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,UAAU,CAAC,CA6BpB;AAED;;;;;;;;GAQG;AACH,iBAAe,eAAe,CAC1B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,MAAM,CAAC,CAGhB;AAED;;;;;;;;;;;;;GAaG;AACH,iBAAe,iBAAiB,CAC5B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,MAAM,EACd,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAC;IAAC,MAAM,CAAC,EAAC,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,MAAM,CAAC,CAAA;AAEjB,iBAAe,iBAAiB,CAC5B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,MAAM,EACd,IAAI,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAC;IAAC,MAAM,EAAC,IAAI,CAAA;CAAE,GAC/C,OAAO,CAAC,UAAU,CAAC,CAAA;AA0HrB;;;;;;;;;GASG;AACH,iBAAe,YAAY,CACvB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GAClC,OAAO,CAAC,UAAU,CAAC,CAYpB;AAED;;;;;;;GAOG;AACH,iBAAe,SAAS,CACpB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GAClC,OAAO,CAAC,SAAS,CAAC,CAGnB"}
package/dist/index.js CHANGED
@@ -1283,8 +1283,7 @@ function toString2(array, encoding = "utf8") {
1283
1283
  }
1284
1284
  __name(toString2, "toString");
1285
1285
 
1286
- // src/index.ts
1287
- var subtle = globalThis.crypto.subtle;
1286
+ // src/constants.ts
1288
1287
  var KEM_ID = 32;
1289
1288
  var KDF_ID = 1;
1290
1289
  var AEAD_ID = 2;
@@ -1295,6 +1294,7 @@ var ENC_LENGTH = 32;
1295
1294
  var AEAD_TAG_LENGTH = 16;
1296
1295
  var MODE_BASE = 0;
1297
1296
  var HPKE_V1 = new TextEncoder().encode("HPKE-v1");
1297
+ var WRAPPED_LEN_PREFIX = 2;
1298
1298
  var KEM_SUITE_ID = concat2(
1299
1299
  new TextEncoder().encode("KEM"),
1300
1300
  i2osp(KEM_ID, 2)
@@ -1305,174 +1305,68 @@ var HPKE_SUITE_ID = concat2(
1305
1305
  i2osp(KDF_ID, 2),
1306
1306
  i2osp(AEAD_ID, 2)
1307
1307
  );
1308
- async function seal(recipient, aesKey, opts) {
1309
- const info = normalizeInfo(opts?.info);
1310
- let keyBytes;
1311
- if (aesKey instanceof Uint8Array) {
1312
- validateRawKeyBytes(aesKey);
1313
- keyBytes = aesKey;
1314
- } else if (aesKey) {
1315
- keyBytes = await exportAesKeyBytes(aesKey);
1316
- validateRawKeyBytes(keyBytes);
1317
- } else {
1318
- const keysize = opts?.keysize ?? 256;
1319
- validateKeysize(keysize);
1320
- keyBytes = globalThis.crypto.getRandomValues(
1321
- new Uint8Array(keysize / 8)
1322
- );
1323
- }
1324
- const publicKey = await resolveRecipientPublicKey(recipient);
1325
- const { sharedSecret, enc } = await encap(publicKey);
1326
- const { key, baseNonce } = await keySchedule(sharedSecret, info);
1327
- const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
1328
- const wrapped = concat2(enc, ciphertext);
1329
- const aesGcmKey = await importAesKey(keyBytes);
1330
- return { wrapped, key: aesGcmKey };
1331
- }
1332
- __name(seal, "seal");
1333
- async function openBytes(keypair, wrapped, opts) {
1334
- const keyBytes = await openRawBytes(keypair, wrapped, opts);
1335
- return importAesKey(keyBytes);
1336
- }
1337
- __name(openBytes, "openBytes");
1338
- async function openRawBytes(keypair, wrapped, opts) {
1339
- if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
1340
- throw new Error("malformed envelope: too short");
1341
- }
1342
- const info = normalizeInfo(opts?.info);
1343
- const enc = wrapped.slice(0, ENC_LENGTH);
1344
- const ciphertext = wrapped.slice(ENC_LENGTH);
1345
- const sharedSecret = await decap(enc, keypair);
1346
- const { key, baseNonce } = await keySchedule(sharedSecret, info);
1347
- return aeadOpen(key, baseNonce, ciphertext);
1348
- }
1349
- __name(openRawBytes, "openRawBytes");
1350
- var open = Object.assign(openBytes, {
1351
- raw: openRawBytes
1352
- });
1353
- var WRAPPED_LEN_PREFIX = 2;
1354
- var encrypt = Object.assign(encryptBytes, {
1355
- asString: encryptToString
1356
- });
1357
- var decrypt = Object.assign(decryptBytes, {
1358
- asString: decryptToString,
1359
- fromString: decryptFromString
1360
- });
1361
- async function decryptBytes(keypair, message, opts) {
1362
- if (message.byteLength < WRAPPED_LEN_PREFIX) {
1363
- throw new Error("malformed message: too short");
1364
- }
1365
- const wrappedLen = message[0] << 8 | message[1];
1366
- const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
1367
- const ctStart = ivStart + NN;
1368
- if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
1369
- throw new Error("malformed message: too short");
1370
- }
1371
- const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
1372
- const iv = message.slice(ivStart, ctStart);
1373
- const ciphertext = message.slice(ctStart);
1374
- const keyBytes = await openRawBytes(
1375
- keypair,
1376
- wrapped,
1377
- opts?.info !== void 0 ? { info: opts.info } : void 0
1378
- );
1379
- const key = await importAesKeyDecryptOnly(keyBytes);
1380
- const pt = await subtle.decrypt(
1381
- { name: "AES-GCM", iv },
1382
- key,
1383
- ciphertext
1384
- );
1385
- return new Uint8Array(pt);
1386
- }
1387
- __name(decryptBytes, "decryptBytes");
1388
- async function decryptToString(keypair, message, opts) {
1389
- const bytes = await decryptBytes(keypair, message, opts);
1390
- return new TextDecoder().decode(bytes);
1391
- }
1392
- __name(decryptToString, "decryptToString");
1393
- async function decryptFromString(keypair, message, opts) {
1394
- const bytes = fromString2(message, "base64url");
1395
- const plaintext = await decryptBytes(
1396
- keypair,
1397
- bytes,
1398
- opts?.info !== void 0 ? { info: opts.info } : void 0
1399
- );
1400
- return opts?.buffer ? plaintext : new TextDecoder().decode(plaintext);
1401
- }
1402
- __name(decryptFromString, "decryptFromString");
1403
- function validateKeysize(keysize) {
1404
- if (keysize !== 128 && keysize !== 256) {
1405
- throw new Error(
1406
- `invalid keysize: ${keysize} (expected 128 or 256)`
1407
- );
1408
- }
1409
- }
1410
- __name(validateKeysize, "validateKeysize");
1411
- function validateRawKeyBytes(raw) {
1412
- if (raw.length !== 16 && raw.length !== 32) {
1413
- throw new Error(
1414
- `invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
1415
- );
1416
- }
1417
- }
1418
- __name(validateRawKeyBytes, "validateRawKeyBytes");
1419
- function normalizeInfo(info) {
1420
- if (info === void 0 || info === null) return new Uint8Array(0);
1421
- if (typeof info === "string") return new TextEncoder().encode(info);
1422
- return info;
1423
- }
1424
- __name(normalizeInfo, "normalizeInfo");
1425
- async function exportAesKeyBytes(key) {
1426
- if (!key.extractable) {
1427
- throw new Error(
1428
- "aesKey must be extractable: its raw bytes are what get sealed"
1429
- );
1430
- }
1431
- return new Uint8Array(await subtle.exportKey("raw", key));
1432
- }
1433
- __name(exportAesKeyBytes, "exportAesKeyBytes");
1434
- async function importAesKey(raw) {
1435
- return subtle.importKey(
1308
+
1309
+ // src/util.ts
1310
+ var subtle = globalThis.crypto.subtle;
1311
+ async function aeadSeal(key, nonce, plaintext) {
1312
+ const k = await subtle.importKey(
1436
1313
  "raw",
1437
- raw,
1314
+ key,
1438
1315
  { name: "AES-GCM" },
1439
- true,
1440
- ["encrypt", "decrypt"]
1316
+ false,
1317
+ ["encrypt"]
1318
+ );
1319
+ const ct = await subtle.encrypt(
1320
+ { name: "AES-GCM", iv: nonce },
1321
+ k,
1322
+ plaintext
1441
1323
  );
1324
+ return new Uint8Array(ct);
1442
1325
  }
1443
- __name(importAesKey, "importAesKey");
1444
- async function importAesKeyDecryptOnly(raw) {
1445
- return subtle.importKey(
1326
+ __name(aeadSeal, "aeadSeal");
1327
+ async function aeadOpen(key, nonce, ciphertext) {
1328
+ const k = await subtle.importKey(
1446
1329
  "raw",
1447
- raw,
1330
+ key,
1448
1331
  { name: "AES-GCM" },
1449
1332
  false,
1450
1333
  ["decrypt"]
1451
1334
  );
1335
+ const pt = await subtle.decrypt(
1336
+ { name: "AES-GCM", iv: nonce },
1337
+ k,
1338
+ ciphertext
1339
+ );
1340
+ return new Uint8Array(pt);
1452
1341
  }
1453
- __name(importAesKeyDecryptOnly, "importAesKeyDecryptOnly");
1454
- function i2osp(n, len) {
1455
- const out = new Uint8Array(len);
1456
- let v = n;
1457
- for (let i = len - 1; i >= 0; i--) {
1458
- out[i] = v & 255;
1459
- v = Math.floor(v / 256);
1342
+ __name(aeadOpen, "aeadOpen");
1343
+ async function encryptBytes(recipient, message, aesKey, opts) {
1344
+ const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
1345
+ let enc;
1346
+ let key;
1347
+ if (!aesKey) {
1348
+ const keys = await create2(recipient, opts);
1349
+ enc = keys.enc;
1350
+ key = keys.key;
1351
+ } else {
1352
+ const keys = await seal(recipient, aesKey, opts);
1353
+ enc = keys.enc;
1354
+ key = keys.key;
1460
1355
  }
1461
- return out;
1356
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
1357
+ const ct = new Uint8Array(await subtle.encrypt(
1358
+ { name: "AES-GCM", iv },
1359
+ key,
1360
+ plaintext
1361
+ ));
1362
+ return concat2(i2osp(enc.length, WRAPPED_LEN_PREFIX), enc, iv, ct);
1462
1363
  }
1463
- __name(i2osp, "i2osp");
1464
- function concat2(...arrays) {
1465
- let total = 0;
1466
- for (const a of arrays) total += a.length;
1467
- const out = new Uint8Array(total);
1468
- let offset = 0;
1469
- for (const a of arrays) {
1470
- out.set(a, offset);
1471
- offset += a.length;
1472
- }
1473
- return out;
1364
+ __name(encryptBytes, "encryptBytes");
1365
+ async function encryptToString(recipient, message, aesKey, opts) {
1366
+ const envelope = await encryptBytes(recipient, message, aesKey, opts);
1367
+ return toString2(envelope, opts?.encoding ?? "base64url");
1474
1368
  }
1475
- __name(concat2, "concat");
1369
+ __name(encryptToString, "encryptToString");
1476
1370
  async function hmac(key, data) {
1477
1371
  const k = await subtle.importKey(
1478
1372
  "raw",
@@ -1641,6 +1535,144 @@ async function decap(enc, keypair) {
1641
1535
  return extractAndExpand(dhBytes, kemContext);
1642
1536
  }
1643
1537
  __name(decap, "decap");
1538
+ function i2osp(n, len) {
1539
+ const out = new Uint8Array(len);
1540
+ let v = n;
1541
+ for (let i = len - 1; i >= 0; i--) {
1542
+ out[i] = v & 255;
1543
+ v = Math.floor(v / 256);
1544
+ }
1545
+ return out;
1546
+ }
1547
+ __name(i2osp, "i2osp");
1548
+ function concat2(...arrays) {
1549
+ let total = 0;
1550
+ for (const a of arrays) total += a.length;
1551
+ const out = new Uint8Array(total);
1552
+ let offset = 0;
1553
+ for (const a of arrays) {
1554
+ out.set(a, offset);
1555
+ offset += a.length;
1556
+ }
1557
+ return out;
1558
+ }
1559
+ __name(concat2, "concat");
1560
+
1561
+ // src/index.ts
1562
+ var subtle2 = globalThis.crypto.subtle;
1563
+ async function create2(recipient, opts) {
1564
+ const { wrapped, key } = await encryptKey(recipient, null, opts);
1565
+ return { enc: wrapped, key };
1566
+ }
1567
+ __name(create2, "create");
1568
+ async function seal(recipient, aesKey, opts) {
1569
+ const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
1570
+ return { enc: wrapped, key };
1571
+ }
1572
+ __name(seal, "seal");
1573
+ var open = Object.assign(openBytes, {
1574
+ raw: openRawBytes
1575
+ });
1576
+ var encrypt = Object.assign(encryptBytes, {
1577
+ asString: encryptToString
1578
+ });
1579
+ var decrypt = Object.assign(decryptBytes, {
1580
+ asString: decryptToString,
1581
+ fromString: decryptFromString
1582
+ });
1583
+ async function decryptBytes(keypair, message, opts) {
1584
+ if (message.byteLength < WRAPPED_LEN_PREFIX) {
1585
+ throw new Error("malformed message: too short");
1586
+ }
1587
+ const wrappedLen = message[0] << 8 | message[1];
1588
+ const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
1589
+ const ctStart = ivStart + NN;
1590
+ if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
1591
+ throw new Error("malformed message: too short");
1592
+ }
1593
+ const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
1594
+ const iv = message.slice(ivStart, ctStart);
1595
+ const ciphertext = message.slice(ctStart);
1596
+ const keyBytes = await openRawBytes(
1597
+ keypair,
1598
+ wrapped,
1599
+ opts?.info !== void 0 ? { info: opts.info } : void 0
1600
+ );
1601
+ const key = await importAesKeyDecryptOnly(keyBytes);
1602
+ const pt = await subtle2.decrypt(
1603
+ { name: "AES-GCM", iv },
1604
+ key,
1605
+ ciphertext
1606
+ );
1607
+ return new Uint8Array(pt);
1608
+ }
1609
+ __name(decryptBytes, "decryptBytes");
1610
+ async function decryptToString(keypair, message, opts) {
1611
+ const bytes = await decryptBytes(keypair, message, opts);
1612
+ return new TextDecoder().decode(bytes);
1613
+ }
1614
+ __name(decryptToString, "decryptToString");
1615
+ async function decryptFromString(keypair, message, opts) {
1616
+ const bytes = fromString2(message, "base64url");
1617
+ const plaintext = await decryptBytes(
1618
+ keypair,
1619
+ bytes,
1620
+ opts?.info !== void 0 ? { info: opts.info } : void 0
1621
+ );
1622
+ return opts?.buffer ? plaintext : new TextDecoder().decode(plaintext);
1623
+ }
1624
+ __name(decryptFromString, "decryptFromString");
1625
+ function validateKeysize(keysize) {
1626
+ if (keysize !== 128 && keysize !== 256) {
1627
+ throw new Error(
1628
+ `invalid keysize: ${keysize} (expected 128 or 256)`
1629
+ );
1630
+ }
1631
+ }
1632
+ __name(validateKeysize, "validateKeysize");
1633
+ function validateRawKeyBytes(raw) {
1634
+ if (raw.length !== 16 && raw.length !== 32) {
1635
+ throw new Error(
1636
+ `invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
1637
+ );
1638
+ }
1639
+ }
1640
+ __name(validateRawKeyBytes, "validateRawKeyBytes");
1641
+ function normalizeInfo(info) {
1642
+ if (info === void 0 || info === null) return new Uint8Array(0);
1643
+ if (typeof info === "string") return new TextEncoder().encode(info);
1644
+ return info;
1645
+ }
1646
+ __name(normalizeInfo, "normalizeInfo");
1647
+ async function exportAesKeyBytes(key) {
1648
+ if (!key.extractable) {
1649
+ throw new Error(
1650
+ "aesKey must be extractable: its raw bytes are what get sealed"
1651
+ );
1652
+ }
1653
+ return new Uint8Array(await subtle2.exportKey("raw", key));
1654
+ }
1655
+ __name(exportAesKeyBytes, "exportAesKeyBytes");
1656
+ async function importAesKey(raw) {
1657
+ return subtle2.importKey(
1658
+ "raw",
1659
+ raw,
1660
+ { name: "AES-GCM" },
1661
+ true,
1662
+ ["encrypt", "decrypt"]
1663
+ );
1664
+ }
1665
+ __name(importAesKey, "importAesKey");
1666
+ async function importAesKeyDecryptOnly(raw) {
1667
+ return subtle2.importKey(
1668
+ "raw",
1669
+ raw,
1670
+ { name: "AES-GCM" },
1671
+ false,
1672
+ ["decrypt"]
1673
+ );
1674
+ }
1675
+ __name(importAesKeyDecryptOnly, "importAesKeyDecryptOnly");
1644
1676
  async function keySchedule(sharedSecret, info) {
1645
1677
  const empty2 = new Uint8Array(0);
1646
1678
  const pskIdHash = await labeledExtract(
@@ -1679,56 +1711,50 @@ async function keySchedule(sharedSecret, info) {
1679
1711
  return { key, baseNonce };
1680
1712
  }
1681
1713
  __name(keySchedule, "keySchedule");
1682
- async function aeadSeal(key, nonce, plaintext) {
1683
- const k = await subtle.importKey(
1684
- "raw",
1685
- key,
1686
- { name: "AES-GCM" },
1687
- false,
1688
- ["encrypt"]
1689
- );
1690
- const ct = await subtle.encrypt(
1691
- { name: "AES-GCM", iv: nonce },
1692
- k,
1693
- plaintext
1694
- );
1695
- return new Uint8Array(ct);
1696
- }
1697
- __name(aeadSeal, "aeadSeal");
1698
- async function aeadOpen(key, nonce, ciphertext) {
1699
- const k = await subtle.importKey(
1700
- "raw",
1701
- key,
1702
- { name: "AES-GCM" },
1703
- false,
1704
- ["decrypt"]
1705
- );
1706
- const pt = await subtle.decrypt(
1707
- { name: "AES-GCM", iv: nonce },
1708
- k,
1709
- ciphertext
1710
- );
1711
- return new Uint8Array(pt);
1714
+ async function openRawBytes(keypair, wrapped, opts) {
1715
+ if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
1716
+ throw new Error("malformed envelope: too short");
1717
+ }
1718
+ const info = normalizeInfo(opts?.info);
1719
+ const enc = wrapped.slice(0, ENC_LENGTH);
1720
+ const ciphertext = wrapped.slice(ENC_LENGTH);
1721
+ const sharedSecret = await decap(enc, keypair);
1722
+ const { key, baseNonce } = await keySchedule(sharedSecret, info);
1723
+ return aeadOpen(key, baseNonce, ciphertext);
1712
1724
  }
1713
- __name(aeadOpen, "aeadOpen");
1714
- async function encryptBytes(recipient, message, aesKey, opts) {
1715
- const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
1716
- const { wrapped, key } = await seal(recipient, aesKey, opts);
1717
- const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
1718
- const ct = new Uint8Array(await subtle.encrypt(
1719
- { name: "AES-GCM", iv },
1720
- key,
1721
- plaintext
1722
- ));
1723
- return concat2(i2osp(wrapped.length, WRAPPED_LEN_PREFIX), wrapped, iv, ct);
1725
+ __name(openRawBytes, "openRawBytes");
1726
+ async function openBytes(keypair, wrapped, opts) {
1727
+ const keyBytes = await openRawBytes(keypair, wrapped, opts);
1728
+ return importAesKey(keyBytes);
1724
1729
  }
1725
- __name(encryptBytes, "encryptBytes");
1726
- async function encryptToString(recipient, message, aesKey, opts) {
1727
- const envelope = await encryptBytes(recipient, message, aesKey, opts);
1728
- return toString2(envelope, opts?.encoding ?? "base64url");
1730
+ __name(openBytes, "openBytes");
1731
+ async function encryptKey(recipient, aesKey, opts) {
1732
+ const info = normalizeInfo(opts?.info);
1733
+ let keyBytes;
1734
+ if (aesKey instanceof Uint8Array) {
1735
+ validateRawKeyBytes(aesKey);
1736
+ keyBytes = aesKey;
1737
+ } else if (aesKey) {
1738
+ keyBytes = await exportAesKeyBytes(aesKey);
1739
+ validateRawKeyBytes(keyBytes);
1740
+ } else {
1741
+ const keysize = opts?.size ?? 256;
1742
+ validateKeysize(keysize);
1743
+ keyBytes = globalThis.crypto.getRandomValues(
1744
+ new Uint8Array(keysize / 8)
1745
+ );
1746
+ }
1747
+ const publicKey = await resolveRecipientPublicKey(recipient);
1748
+ const { sharedSecret, enc } = await encap(publicKey);
1749
+ const { key, baseNonce } = await keySchedule(sharedSecret, info);
1750
+ const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
1751
+ const wrapped = concat2(enc, ciphertext);
1752
+ const aesGcmKey = await importAesKey(keyBytes);
1753
+ return { wrapped, key: aesGcmKey };
1729
1754
  }
1730
- __name(encryptToString, "encryptToString");
1755
+ __name(encryptKey, "encryptKey");
1731
1756
  export {
1757
+ create2 as create,
1732
1758
  decrypt,
1733
1759
  encrypt,
1734
1760
  open,