@hpke/hybridkem-x-wing 0.6.1 → 0.7.1

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/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  <h1 align="center">@hpke/hybridkem-x-wing</h1>
2
2
 
3
3
  <div align="center">
4
+ <a href="https://www.npmjs.com/package/@hpke/hybridkem-x-wing"><img src="https://img.shields.io/npm/v/@hpke/hybridkem-x-wing" alt="npm"/></a>
4
5
  <a href="https://jsr.io/@hpke/hybridkem-x-wing"><img src="https://jsr.io/badges/@hpke/hybridkem-x-wing" alt="JSR"/></a>
5
6
  </div>
6
7
 
@@ -74,6 +75,10 @@ try {
74
75
  }
75
76
  ```
76
77
 
78
+ Auth and AuthPSK require a KEM that supports AuthEncap/AuthDecap. ML-KEM, X-Wing
79
+ and X25519-Kyber768 support only Base and PSK modes; requesting authentication
80
+ with these KEMs throws `NotSupportedError` (RFC 9180 Section 7.1.5).
81
+
77
82
  ## Index
78
83
 
79
84
  - [Installation](#installation)
@@ -100,8 +105,8 @@ yarn add @hpke/hybridkem-x-wing
100
105
  pnpm install @hpke/hybridkem-x-wing
101
106
  # Using jsr:
102
107
  npx jsr add @hpke/hybridkem-x-wing
103
- yarn dlx jsr add @hpke/hybridkem-x-wing
104
- pnpm dlx jsr add @@hpke/hybridkem-x-wing
108
+ yarn add jsr:@hpke/hybridkem-x-wing
109
+ pnpm add jsr:@hpke/hybridkem-x-wing
105
110
  ```
106
111
 
107
112
  The above manner can be used with other JavaScript runtimes that support npm,
@@ -132,24 +137,20 @@ Using esm.sh:
132
137
  ```html
133
138
  <!-- use a specific version -->
134
139
  <script type="module">
135
- import {
136
- Aes256Gcm,
137
- CipherSuite,
138
- HkdfSha256,
139
- } from "https://esm.sh/@hpke/core@<SEMVER>";
140
- import { XWing } from "https://esm.sh/@hpke/hybridkem-x-wing@<SEMVER>";
141
- // ...
140
+ import {
141
+ Aes256Gcm,
142
+ CipherSuite,
143
+ HkdfSha256,
144
+ } from "https://esm.sh/@hpke/core@<SEMVER>";
145
+ import { XWing } from "https://esm.sh/@hpke/hybridkem-x-wing@<SEMVER>";
146
+ // ...
142
147
  </script>
143
148
 
144
149
  <!-- use the latest stable version -->
145
150
  <script type="module">
146
- import {
147
- Aes256Gcm,
148
- CipherSuite,
149
- HkdfSha256,
150
- } from "https://esm.sh/@hpke/core";
151
- import { XWing } from "https://esm.sh/@hpke/hybridkem-x-wing";
152
- // ...
151
+ import { Aes256Gcm, CipherSuite, HkdfSha256 } from "https://esm.sh/@hpke/core";
152
+ import { XWing } from "https://esm.sh/@hpke/hybridkem-x-wing";
153
+ // ...
153
154
  </script>
154
155
  ```
155
156
 
@@ -158,13 +159,13 @@ Using unpkg:
158
159
  ```html
159
160
  <!-- use a specific version -->
160
161
  <script type="module">
161
- import {
162
- Aes256Gcm,
163
- CipherSuite,
164
- HkdfSha256,
165
- } from "https://unpkg.com/@hpke/core@<SEMVER>/esm/mod.js";
166
- import { XWing } from "https://unpkg.com/@hpke/hybridkem-x-wing@<SEMVER>/esm/mod.js";
167
- // ...
162
+ import {
163
+ Aes256Gcm,
164
+ CipherSuite,
165
+ HkdfSha256,
166
+ } from "https://unpkg.com/@hpke/core@<SEMVER>/esm/mod.js";
167
+ import { XWing } from "https://unpkg.com/@hpke/hybridkem-x-wing@<SEMVER>/esm/mod.js";
168
+ // ...
168
169
  </script>
169
170
  ```
170
171
 
@@ -265,46 +266,42 @@ try {
265
266
  <head></head>
266
267
  <body>
267
268
  <script type="module">
268
- import {
269
- Aes256Gcm,
270
- CipherSuite,
271
- HkdfSha256,
272
- } from "https://esm.sh/@hpke/core";
273
- import { XWing } from "https://esm.sh/@hpke/hybridkem-x-wing";
274
-
275
- globalThis.doHpke = async () => {
276
- try {
277
- const suite = new CipherSuite({
278
- kem: new XWing(),
279
- kdf: new HkdfSha256(),
280
- aead: new Aes256Gcm(),
281
- });
282
-
283
- const rkp = await suite.kem.generateKeyPair();
284
-
285
- // Note that the `ct` resulting from X-Wing::Encapsulate() is set to `sender.enc`.
286
- const sender = await suite.createSenderContext({
287
- recipientPublicKey: rkp.publicKey,
288
- });
289
- // encrypt
290
- const encrypted = await sender.seal(
291
- new TextEncoder().encode("Hello world!"),
292
- );
293
-
294
- const recipient = await suite.createRecipientContext({
295
- recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable.
296
- enc: sender.enc, // == `ct` (ciphertext) in the context of X-Wing
297
- });
298
-
299
- // decrypt
300
- const pt = await recipient.open(encrypted);
301
-
302
- // Hello world!
303
- alert(new TextDecoder().decode(pt));
304
- } catch (err) {
305
- alert("failed:", err.message);
306
- }
307
- };
269
+ import { Aes256Gcm, CipherSuite, HkdfSha256 } from "https://esm.sh/@hpke/core";
270
+ import { XWing } from "https://esm.sh/@hpke/hybridkem-x-wing";
271
+
272
+ globalThis.doHpke = async () => {
273
+ try {
274
+ const suite = new CipherSuite({
275
+ kem: new XWing(),
276
+ kdf: new HkdfSha256(),
277
+ aead: new Aes256Gcm(),
278
+ });
279
+
280
+ const rkp = await suite.kem.generateKeyPair();
281
+
282
+ // Note that the `ct` resulting from X-Wing::Encapsulate() is set to `sender.enc`.
283
+ const sender = await suite.createSenderContext({
284
+ recipientPublicKey: rkp.publicKey,
285
+ });
286
+ // encrypt
287
+ const encrypted = await sender.seal(
288
+ new TextEncoder().encode("Hello world!"),
289
+ );
290
+
291
+ const recipient = await suite.createRecipientContext({
292
+ recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable.
293
+ enc: sender.enc, // == `ct` (ciphertext) in the context of X-Wing
294
+ });
295
+
296
+ // decrypt
297
+ const pt = await recipient.open(encrypted);
298
+
299
+ // Hello world!
300
+ alert(new TextDecoder().decode(pt));
301
+ } catch (err) {
302
+ alert("failed:", err.message);
303
+ }
304
+ };
308
305
  </script>
309
306
  <button type="button" onclick="doHpke()">do HPKE</button>
310
307
  </body>
package/esm/package.json CHANGED
@@ -1,3 +1,4 @@
1
1
  {
2
- "type": "module"
2
+ "type": "module",
3
+ "sideEffects": false
3
4
  }
@@ -1,7 +1,6 @@
1
1
  import { MlKem768 } from "mlkem";
2
2
  import type { JsonWebKeyExtended, KemInterface, RecipientContextParams, SenderContextParams } from "@hpke/common";
3
- import { KemId } from "@hpke/common";
4
- import { X25519 } from "@hpke/dhkem-x25519";
3
+ import type { KemId } from "@hpke/common";
5
4
  /**
6
5
  * The Hybrid Post-Quantum KEM (X25519, Kyber768).
7
6
  *
@@ -32,13 +31,12 @@ export declare class XWing implements KemInterface {
32
31
  readonly privateKeySize: number;
33
32
  readonly auth: boolean;
34
33
  protected _m: MlKem768;
35
- protected _x25519: X25519;
36
34
  private _api;
37
35
  constructor();
38
36
  serializePublicKey(key: CryptoKey): Promise<ArrayBuffer>;
39
- deserializePublicKey(key: ArrayBuffer): Promise<CryptoKey>;
37
+ deserializePublicKey(key: ArrayBufferLike | ArrayBufferView): Promise<CryptoKey>;
40
38
  serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer>;
41
- deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey>;
39
+ deserializePrivateKey(key: ArrayBufferLike | ArrayBufferView): Promise<CryptoKey>;
42
40
  /**
43
41
  * Generates a new key pair.
44
42
  *
@@ -61,7 +59,7 @@ export declare class XWing implements KemInterface {
61
59
  * @throws {DeriveKeyPairError} Thrown if the key pair cannot be derived.
62
60
  * @throws {InvalidParamError} Thrown if the length of the IKM is not 32 bytes.
63
61
  */
64
- deriveKeyPair(ikm: ArrayBuffer): Promise<CryptoKeyPair>;
62
+ deriveKeyPair(ikm: ArrayBufferLike | ArrayBufferView): Promise<CryptoKeyPair>;
65
63
  /**
66
64
  * Imports a key from the input.
67
65
  * @param format The format of the key. "raw" or "jwk" can be specified.
@@ -77,6 +75,7 @@ export declare class XWing implements KemInterface {
77
75
  * @returns {Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }>} A promise that resolves with the `ss` (shared secret) as `sharedSecret` and the `ct` (ciphertext) as `enc`.
78
76
  * @throws {InvalidParamError} Thrown if the length of the `ekm` is not 64 bytes.
79
77
  * @throws {EncapError} Thrown if the shared secret cannot be encapsulated.
78
+ * @throws {NotSupportedError} if `senderKey` is provided.
80
79
  */
81
80
  encap(params: SenderContextParams): Promise<{
82
81
  sharedSecret: ArrayBuffer;
@@ -90,6 +89,7 @@ export declare class XWing implements KemInterface {
90
89
  * @returns {Promise<ArrayBuffer>} A promise that resolves with the shared secret.
91
90
  * @throws {InvalidParamError} Thrown if the length of the `enc` is not 1120 bytes.
92
91
  * @throws {DecapError} Thrown if the shared secret cannot be decapsulated.
92
+ * @throws {NotSupportedError} if `senderPublicKey` is provided.
93
93
  */
94
94
  decap(params: RecipientContextParams): Promise<ArrayBuffer>;
95
95
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"xWing.d.ts","sourceRoot":"","sources":["../../src/src/xWing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAsB,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAUL,KAAK,EAKN,MAAM,cAAc,CAAC;AAItB,OAAO,EAAc,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAkCxD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,KAAM,YAAW,YAAY;IACxC,SAAgB,EAAE,EAAE,KAAK,CAAe;IACxC,SAAgB,IAAI,EAAE,MAAM,CAAY;IACxC,SAAgB,UAAU,EAAE,MAAM,CAAM;IACxC,SAAgB,OAAO,EAAE,MAAM,CAAQ;IACvC,SAAgB,aAAa,EAAE,MAAM,CAAQ;IAC7C,SAAgB,cAAc,EAAE,MAAM,CAAM;IAC5C,SAAgB,IAAI,EAAE,OAAO,CAAS;IACtC,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC;IACvB,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,IAAI,CAAiC;;IAOhC,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASxD,oBAAoB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;IAS1D,mBAAmB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASzD,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;IASxE;;;;OAIG;IACU,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC;IAkBtD;;;;;;OAMG;IACU,qBAAqB,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC;IAc1E;;;;;;;OAOG;IACU,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC;IAepE;;;;;;;OAOG;IACU,SAAS,CACpB,MAAM,EAAE,KAAK,GAAG,KAAK,EACrB,GAAG,EAAE,WAAW,GAAG,kBAAkB,EACrC,QAAQ,UAAO,GACd,OAAO,CAAC,SAAS,CAAC;IAmCrB;;;;;;OAMG;IACU,KAAK,CAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC;QAAE,YAAY,EAAE,WAAW,CAAC;QAAC,GAAG,EAAE,WAAW,CAAA;KAAE,CAAC;IA4C3D;;;;;;;;OAQG;IACU,KAAK,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAyBxE;;;;OAIG;YACW,MAAM;IAOpB;;;;OAIG;YACW,sBAAsB;IAOpC;;;;OAIG;YACW,uBAAuB;IAUrC,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,UAAU;CAmCnB"}
1
+ {"version":3,"file":"xWing.d.ts","sourceRoot":"","sources":["../../src/src/xWing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAsB,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAmBtB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA4B1C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,KAAM,YAAW,YAAY;IACxC,SAAgB,EAAE,EAAE,KAAK,CAAU;IACnC,SAAgB,IAAI,EAAE,MAAM,CAAY;IACxC,SAAgB,UAAU,EAAE,MAAM,CAAM;IACxC,SAAgB,OAAO,EAAE,MAAM,CAAQ;IACvC,SAAgB,aAAa,EAAE,MAAM,CAAQ;IAC7C,SAAgB,cAAc,EAAE,MAAM,CAAM;IAC5C,SAAgB,IAAI,EAAE,OAAO,CAAS;IACtC,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC;IACvB,OAAO,CAAC,IAAI,CAAiC;;IAMhC,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASxD,oBAAoB,CAC/B,GAAG,EAAE,eAAe,GAAG,eAAe,GACrC,OAAO,CAAC,SAAS,CAAC;IASR,mBAAmB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASzD,qBAAqB,CAChC,GAAG,EAAE,eAAe,GAAG,eAAe,GACrC,OAAO,CAAC,SAAS,CAAC;IASrB;;;;OAIG;IACU,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC;IAkBtD;;;;;;OAMG;IACU,qBAAqB,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC;IAc1E;;;;;;;OAOG;IACU,aAAa,CACxB,GAAG,EAAE,eAAe,GAAG,eAAe,GACrC,OAAO,CAAC,aAAa,CAAC;IAgBzB;;;;;;;OAOG;IACU,SAAS,CACpB,MAAM,EAAE,KAAK,GAAG,KAAK,EACrB,GAAG,EAAE,WAAW,GAAG,kBAAkB,EACrC,QAAQ,UAAO,GACd,OAAO,CAAC,SAAS,CAAC;IAmCrB;;;;;;;OAOG;IACU,KAAK,CAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC;QAAE,YAAY,EAAE,WAAW,CAAC;QAAC,GAAG,EAAE,WAAW,CAAA;KAAE,CAAC;IAkD3D;;;;;;;;;OASG;IACU,KAAK,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IA+BxE;;;;OAIG;YACW,MAAM;IAOpB;;;;OAIG;YACW,sBAAsB;IAOpC;;;;OAIG;YACW,uBAAuB;IAUrC,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,UAAU;CAmCnB"}
package/esm/src/xWing.js CHANGED
@@ -1,15 +1,7 @@
1
1
  // @ts-ignore: Unreachable code error
2
2
  import { MlKem768, sha3_256, shake256 } from "mlkem";
3
- import { base64UrlToBytes, concat, DecapError, DeriveKeyPairError, DeserializeError, EncapError, InvalidParamError, isCryptoKeyPair, KEM_USAGES, KemId, loadCrypto, NotSupportedError, SerializeError, XCryptoKey, } from "@hpke/common";
3
+ import { base64UrlToBytes, concat, DecapError, DeriveKeyPairError, DeserializeError, EncapError, InvalidParamError, isCryptoKeyPair, KEM_USAGES, loadCrypto, NotSupportedError, SerializeError, toArrayBuffer, x25519, XCryptoKey, } from "@hpke/common";
4
4
  const ALG_NAME = "X-Wing";
5
- import { HkdfSha256, X25519 } from "@hpke/dhkem-x25519";
6
- // deno-fmt-ignore
7
- const X25519_BASE = new Uint8Array([
8
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
9
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
10
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
11
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
12
- ]);
13
5
  // XWingLabel = concat(
14
6
  // "\./",
15
7
  // "/^\",
@@ -52,8 +44,8 @@ export class XWing {
52
44
  enumerable: true,
53
45
  configurable: true,
54
46
  writable: true,
55
- value: KemId.XWing
56
- });
47
+ value: 0x647a
48
+ }); // KemId.XWing
57
49
  Object.defineProperty(this, "name", {
58
50
  enumerable: true,
59
51
  configurable: true,
@@ -96,12 +88,6 @@ export class XWing {
96
88
  writable: true,
97
89
  value: void 0
98
90
  });
99
- Object.defineProperty(this, "_x25519", {
100
- enumerable: true,
101
- configurable: true,
102
- writable: true,
103
- value: void 0
104
- });
105
91
  Object.defineProperty(this, "_api", {
106
92
  enumerable: true,
107
93
  configurable: true,
@@ -109,7 +95,6 @@ export class XWing {
109
95
  value: undefined
110
96
  });
111
97
  this._m = new MlKem768();
112
- this._x25519 = new X25519(new HkdfSha256());
113
98
  }
114
99
  async serializePublicKey(key) {
115
100
  await this._setup();
@@ -123,7 +108,7 @@ export class XWing {
123
108
  async deserializePublicKey(key) {
124
109
  await this._setup();
125
110
  try {
126
- return await this._deserializePublicKey(key);
111
+ return await this._deserializePublicKey(toArrayBuffer(key));
127
112
  }
128
113
  catch (e) {
129
114
  throw new DeserializeError(e);
@@ -141,7 +126,7 @@ export class XWing {
141
126
  async deserializePrivateKey(key) {
142
127
  await this._setup();
143
128
  try {
144
- return await this._deserializePrivateKey(key);
129
+ return await this._deserializePrivateKey(toArrayBuffer(key));
145
130
  }
146
131
  catch (e) {
147
132
  throw new DeserializeError(e);
@@ -203,8 +188,9 @@ export class XWing {
203
188
  async deriveKeyPair(ikm) {
204
189
  await this._setup();
205
190
  try {
191
+ const rawIkm = toArrayBuffer(ikm);
206
192
  const sk = shake256.create({ dkLen: 32 })
207
- .update(new Uint8Array(ikm))
193
+ .update(new Uint8Array(rawIkm))
208
194
  .digest();
209
195
  const [_sk, pk] = await this._generateKeyPairDerand(sk);
210
196
  const dSk = await this.deserializePrivateKey(sk.buffer);
@@ -262,15 +248,20 @@ export class XWing {
262
248
  * @returns {Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }>} A promise that resolves with the `ss` (shared secret) as `sharedSecret` and the `ct` (ciphertext) as `enc`.
263
249
  * @throws {InvalidParamError} Thrown if the length of the `ekm` is not 64 bytes.
264
250
  * @throws {EncapError} Thrown if the shared secret cannot be encapsulated.
251
+ * @throws {NotSupportedError} if `senderKey` is provided.
265
252
  */
266
253
  async encap(params) {
254
+ if (params.senderKey !== undefined) {
255
+ throw new NotSupportedError("Auth and AuthPSK modes are not supported by this KEM");
256
+ }
267
257
  let ekm = undefined;
268
258
  if (params.ekm !== undefined && !isCryptoKeyPair(params.ekm)) {
269
- if (params.ekm.byteLength !== 64) {
259
+ ekm = toArrayBuffer(params.ekm);
260
+ if (ekm.byteLength !== 64) {
270
261
  throw new InvalidParamError("ekm must be 64 bytes in length");
271
262
  }
272
- ekm = params.ekm;
273
263
  }
264
+ await this._setup();
274
265
  let ekM = undefined;
275
266
  let ekX;
276
267
  if (ekm !== undefined) {
@@ -279,24 +270,24 @@ export class XWing {
279
270
  ekX = ek.subarray(32, 64);
280
271
  }
281
272
  else {
282
- ekX = new Uint8Array(32);
273
+ const randomEkX = new Uint8Array(32);
283
274
  try {
284
- this._api.getRandomValues(ekX);
275
+ this._api.getRandomValues(randomEkX);
285
276
  }
286
277
  catch (e) {
287
278
  throw new NotSupportedError(e);
288
279
  }
280
+ ekX = randomEkX;
289
281
  }
290
282
  const pk = new Uint8Array(await this.serializePublicKey(params.recipientPublicKey));
291
283
  if (pk.byteLength !== 1216) {
292
284
  throw new InvalidParamError("Invalid length of recipientPublicKey");
293
285
  }
294
- await this._setup();
295
286
  try {
296
287
  const pkM = pk.subarray(0, 1184);
297
288
  const pkX = pk.subarray(1184, 1216);
298
- const ctX = await this._x25519.derive(ekX, X25519_BASE);
299
- const ssX = await this._x25519.derive(ekX, pkX);
289
+ const ctX = x25519.getPublicKey(ekX);
290
+ const ssX = x25519.getSharedSecret(ekX, pkX);
300
291
  const [ctM, ssM] = await this._m.encap(pkM, ekM);
301
292
  return {
302
293
  sharedSecret: combiner(ssM, ssX, ctX, pkX).buffer,
@@ -315,12 +306,17 @@ export class XWing {
315
306
  * @returns {Promise<ArrayBuffer>} A promise that resolves with the shared secret.
316
307
  * @throws {InvalidParamError} Thrown if the length of the `enc` is not 1120 bytes.
317
308
  * @throws {DecapError} Thrown if the shared secret cannot be decapsulated.
309
+ * @throws {NotSupportedError} if `senderPublicKey` is provided.
318
310
  */
319
311
  async decap(params) {
312
+ if (params.senderPublicKey !== undefined) {
313
+ throw new NotSupportedError("Auth and AuthPSK modes are not supported by this KEM");
314
+ }
315
+ const enc = toArrayBuffer(params.enc);
320
316
  const rSk = isCryptoKeyPair(params.recipientKey)
321
317
  ? params.recipientKey.privateKey
322
318
  : params.recipientKey;
323
- if (params.enc.byteLength !== 1120) {
319
+ if (enc.byteLength !== 1120) {
324
320
  throw new InvalidParamError("Invalid length of enc");
325
321
  }
326
322
  const sk = new Uint8Array(await this.serializePrivateKey(rSk));
@@ -330,11 +326,11 @@ export class XWing {
330
326
  await this._setup();
331
327
  try {
332
328
  const [skM, skX, _pkM, pkX] = await this._expandDecapsulationKey(sk);
333
- const ct = new Uint8Array(params.enc);
329
+ const ct = new Uint8Array(enc);
334
330
  const ctM = ct.subarray(0, 1088);
335
331
  const ctX = ct.subarray(1088);
336
332
  const ssM = await this._m.decap(ctM, skM);
337
- const ssX = await this._x25519.derive(skX, ctX);
333
+ const ssX = x25519.getSharedSecret(skX, ctX);
338
334
  return combiner(ssM, ssX, ctX, pkX).buffer;
339
335
  }
340
336
  catch (e) {
@@ -370,7 +366,7 @@ export class XWing {
370
366
  const expanded = shake256.create({ dkLen: 96 }).update(sk).digest();
371
367
  const [pkM, skM] = await this._m.deriveKeyPair(expanded.subarray(0, 64));
372
368
  const skX = expanded.subarray(64, 96);
373
- const pkX = await this._x25519.derive(skX, X25519_BASE);
369
+ const pkX = x25519.getPublicKey(skX);
374
370
  return [skM, skX, pkM, pkX];
375
371
  }
376
372
  _serializePublicKey(k) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hpke/hybridkem-x-wing",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "A Hybrid Public Key Encryption (HPKE) module extension for X-Wing: general-purpose hybrid post-quantum KEM.",
5
5
  "keywords": [
6
6
  "hpke",
@@ -33,17 +33,17 @@
33
33
  },
34
34
  "./package.json": "./package.json"
35
35
  },
36
+ "funding": {
37
+ "type": "github",
38
+ "url": "https://github.com/sponsors/dajiaji"
39
+ },
36
40
  "sideEffects": false,
37
41
  "engines": {
38
42
  "node": ">=16.0.0"
39
43
  },
40
44
  "dependencies": {
41
- "@hpke/common": "^1.8.1",
42
- "@hpke/dhkem-x25519": "^1.6.4",
43
- "mlkem": "^2.5.0"
44
- },
45
- "devDependencies": {
46
- "@types/node": "^20.9.0"
45
+ "@hpke/common": "^1.13.0",
46
+ "mlkem": "^2.7.0"
47
47
  },
48
48
  "_generatedBy": "dnt@dev"
49
- }
49
+ }
@@ -1,7 +1,6 @@
1
1
  import { MlKem768 } from "mlkem";
2
2
  import type { JsonWebKeyExtended, KemInterface, RecipientContextParams, SenderContextParams } from "@hpke/common";
3
- import { KemId } from "@hpke/common";
4
- import { X25519 } from "@hpke/dhkem-x25519";
3
+ import type { KemId } from "@hpke/common";
5
4
  /**
6
5
  * The Hybrid Post-Quantum KEM (X25519, Kyber768).
7
6
  *
@@ -32,13 +31,12 @@ export declare class XWing implements KemInterface {
32
31
  readonly privateKeySize: number;
33
32
  readonly auth: boolean;
34
33
  protected _m: MlKem768;
35
- protected _x25519: X25519;
36
34
  private _api;
37
35
  constructor();
38
36
  serializePublicKey(key: CryptoKey): Promise<ArrayBuffer>;
39
- deserializePublicKey(key: ArrayBuffer): Promise<CryptoKey>;
37
+ deserializePublicKey(key: ArrayBufferLike | ArrayBufferView): Promise<CryptoKey>;
40
38
  serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer>;
41
- deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey>;
39
+ deserializePrivateKey(key: ArrayBufferLike | ArrayBufferView): Promise<CryptoKey>;
42
40
  /**
43
41
  * Generates a new key pair.
44
42
  *
@@ -61,7 +59,7 @@ export declare class XWing implements KemInterface {
61
59
  * @throws {DeriveKeyPairError} Thrown if the key pair cannot be derived.
62
60
  * @throws {InvalidParamError} Thrown if the length of the IKM is not 32 bytes.
63
61
  */
64
- deriveKeyPair(ikm: ArrayBuffer): Promise<CryptoKeyPair>;
62
+ deriveKeyPair(ikm: ArrayBufferLike | ArrayBufferView): Promise<CryptoKeyPair>;
65
63
  /**
66
64
  * Imports a key from the input.
67
65
  * @param format The format of the key. "raw" or "jwk" can be specified.
@@ -77,6 +75,7 @@ export declare class XWing implements KemInterface {
77
75
  * @returns {Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }>} A promise that resolves with the `ss` (shared secret) as `sharedSecret` and the `ct` (ciphertext) as `enc`.
78
76
  * @throws {InvalidParamError} Thrown if the length of the `ekm` is not 64 bytes.
79
77
  * @throws {EncapError} Thrown if the shared secret cannot be encapsulated.
78
+ * @throws {NotSupportedError} if `senderKey` is provided.
80
79
  */
81
80
  encap(params: SenderContextParams): Promise<{
82
81
  sharedSecret: ArrayBuffer;
@@ -90,6 +89,7 @@ export declare class XWing implements KemInterface {
90
89
  * @returns {Promise<ArrayBuffer>} A promise that resolves with the shared secret.
91
90
  * @throws {InvalidParamError} Thrown if the length of the `enc` is not 1120 bytes.
92
91
  * @throws {DecapError} Thrown if the shared secret cannot be decapsulated.
92
+ * @throws {NotSupportedError} if `senderPublicKey` is provided.
93
93
  */
94
94
  decap(params: RecipientContextParams): Promise<ArrayBuffer>;
95
95
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"xWing.d.ts","sourceRoot":"","sources":["../../src/src/xWing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAsB,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAUL,KAAK,EAKN,MAAM,cAAc,CAAC;AAItB,OAAO,EAAc,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAkCxD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,KAAM,YAAW,YAAY;IACxC,SAAgB,EAAE,EAAE,KAAK,CAAe;IACxC,SAAgB,IAAI,EAAE,MAAM,CAAY;IACxC,SAAgB,UAAU,EAAE,MAAM,CAAM;IACxC,SAAgB,OAAO,EAAE,MAAM,CAAQ;IACvC,SAAgB,aAAa,EAAE,MAAM,CAAQ;IAC7C,SAAgB,cAAc,EAAE,MAAM,CAAM;IAC5C,SAAgB,IAAI,EAAE,OAAO,CAAS;IACtC,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC;IACvB,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,IAAI,CAAiC;;IAOhC,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASxD,oBAAoB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;IAS1D,mBAAmB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASzD,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;IASxE;;;;OAIG;IACU,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC;IAkBtD;;;;;;OAMG;IACU,qBAAqB,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC;IAc1E;;;;;;;OAOG;IACU,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC;IAepE;;;;;;;OAOG;IACU,SAAS,CACpB,MAAM,EAAE,KAAK,GAAG,KAAK,EACrB,GAAG,EAAE,WAAW,GAAG,kBAAkB,EACrC,QAAQ,UAAO,GACd,OAAO,CAAC,SAAS,CAAC;IAmCrB;;;;;;OAMG;IACU,KAAK,CAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC;QAAE,YAAY,EAAE,WAAW,CAAC;QAAC,GAAG,EAAE,WAAW,CAAA;KAAE,CAAC;IA4C3D;;;;;;;;OAQG;IACU,KAAK,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAyBxE;;;;OAIG;YACW,MAAM;IAOpB;;;;OAIG;YACW,sBAAsB;IAOpC;;;;OAIG;YACW,uBAAuB;IAUrC,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,UAAU;CAmCnB"}
1
+ {"version":3,"file":"xWing.d.ts","sourceRoot":"","sources":["../../src/src/xWing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAsB,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAmBtB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA4B1C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,KAAM,YAAW,YAAY;IACxC,SAAgB,EAAE,EAAE,KAAK,CAAU;IACnC,SAAgB,IAAI,EAAE,MAAM,CAAY;IACxC,SAAgB,UAAU,EAAE,MAAM,CAAM;IACxC,SAAgB,OAAO,EAAE,MAAM,CAAQ;IACvC,SAAgB,aAAa,EAAE,MAAM,CAAQ;IAC7C,SAAgB,cAAc,EAAE,MAAM,CAAM;IAC5C,SAAgB,IAAI,EAAE,OAAO,CAAS;IACtC,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC;IACvB,OAAO,CAAC,IAAI,CAAiC;;IAMhC,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASxD,oBAAoB,CAC/B,GAAG,EAAE,eAAe,GAAG,eAAe,GACrC,OAAO,CAAC,SAAS,CAAC;IASR,mBAAmB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IASzD,qBAAqB,CAChC,GAAG,EAAE,eAAe,GAAG,eAAe,GACrC,OAAO,CAAC,SAAS,CAAC;IASrB;;;;OAIG;IACU,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC;IAkBtD;;;;;;OAMG;IACU,qBAAqB,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC;IAc1E;;;;;;;OAOG;IACU,aAAa,CACxB,GAAG,EAAE,eAAe,GAAG,eAAe,GACrC,OAAO,CAAC,aAAa,CAAC;IAgBzB;;;;;;;OAOG;IACU,SAAS,CACpB,MAAM,EAAE,KAAK,GAAG,KAAK,EACrB,GAAG,EAAE,WAAW,GAAG,kBAAkB,EACrC,QAAQ,UAAO,GACd,OAAO,CAAC,SAAS,CAAC;IAmCrB;;;;;;;OAOG;IACU,KAAK,CAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC;QAAE,YAAY,EAAE,WAAW,CAAC;QAAC,GAAG,EAAE,WAAW,CAAA;KAAE,CAAC;IAkD3D;;;;;;;;;OASG;IACU,KAAK,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IA+BxE;;;;OAIG;YACW,MAAM;IAOpB;;;;OAIG;YACW,sBAAsB;IAOpC;;;;OAIG;YACW,uBAAuB;IAUrC,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,UAAU;CAmCnB"}
@@ -4,7 +4,7 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "mlkem", "@hpke/common", "@hpke/dhkem-x25519"], factory);
7
+ define(["require", "exports", "mlkem", "@hpke/common"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
@@ -14,14 +14,6 @@
14
14
  const mlkem_1 = require("mlkem");
15
15
  const common_1 = require("@hpke/common");
16
16
  const ALG_NAME = "X-Wing";
17
- const dhkem_x25519_1 = require("@hpke/dhkem-x25519");
18
- // deno-fmt-ignore
19
- const X25519_BASE = new Uint8Array([
20
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
21
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
23
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24
- ]);
25
17
  // XWingLabel = concat(
26
18
  // "\./",
27
19
  // "/^\",
@@ -64,8 +56,8 @@
64
56
  enumerable: true,
65
57
  configurable: true,
66
58
  writable: true,
67
- value: common_1.KemId.XWing
68
- });
59
+ value: 0x647a
60
+ }); // KemId.XWing
69
61
  Object.defineProperty(this, "name", {
70
62
  enumerable: true,
71
63
  configurable: true,
@@ -108,12 +100,6 @@
108
100
  writable: true,
109
101
  value: void 0
110
102
  });
111
- Object.defineProperty(this, "_x25519", {
112
- enumerable: true,
113
- configurable: true,
114
- writable: true,
115
- value: void 0
116
- });
117
103
  Object.defineProperty(this, "_api", {
118
104
  enumerable: true,
119
105
  configurable: true,
@@ -121,7 +107,6 @@
121
107
  value: undefined
122
108
  });
123
109
  this._m = new mlkem_1.MlKem768();
124
- this._x25519 = new dhkem_x25519_1.X25519(new dhkem_x25519_1.HkdfSha256());
125
110
  }
126
111
  async serializePublicKey(key) {
127
112
  await this._setup();
@@ -135,7 +120,7 @@
135
120
  async deserializePublicKey(key) {
136
121
  await this._setup();
137
122
  try {
138
- return await this._deserializePublicKey(key);
123
+ return await this._deserializePublicKey((0, common_1.toArrayBuffer)(key));
139
124
  }
140
125
  catch (e) {
141
126
  throw new common_1.DeserializeError(e);
@@ -153,7 +138,7 @@
153
138
  async deserializePrivateKey(key) {
154
139
  await this._setup();
155
140
  try {
156
- return await this._deserializePrivateKey(key);
141
+ return await this._deserializePrivateKey((0, common_1.toArrayBuffer)(key));
157
142
  }
158
143
  catch (e) {
159
144
  throw new common_1.DeserializeError(e);
@@ -215,8 +200,9 @@
215
200
  async deriveKeyPair(ikm) {
216
201
  await this._setup();
217
202
  try {
203
+ const rawIkm = (0, common_1.toArrayBuffer)(ikm);
218
204
  const sk = mlkem_1.shake256.create({ dkLen: 32 })
219
- .update(new Uint8Array(ikm))
205
+ .update(new Uint8Array(rawIkm))
220
206
  .digest();
221
207
  const [_sk, pk] = await this._generateKeyPairDerand(sk);
222
208
  const dSk = await this.deserializePrivateKey(sk.buffer);
@@ -274,15 +260,20 @@
274
260
  * @returns {Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }>} A promise that resolves with the `ss` (shared secret) as `sharedSecret` and the `ct` (ciphertext) as `enc`.
275
261
  * @throws {InvalidParamError} Thrown if the length of the `ekm` is not 64 bytes.
276
262
  * @throws {EncapError} Thrown if the shared secret cannot be encapsulated.
263
+ * @throws {NotSupportedError} if `senderKey` is provided.
277
264
  */
278
265
  async encap(params) {
266
+ if (params.senderKey !== undefined) {
267
+ throw new common_1.NotSupportedError("Auth and AuthPSK modes are not supported by this KEM");
268
+ }
279
269
  let ekm = undefined;
280
270
  if (params.ekm !== undefined && !(0, common_1.isCryptoKeyPair)(params.ekm)) {
281
- if (params.ekm.byteLength !== 64) {
271
+ ekm = (0, common_1.toArrayBuffer)(params.ekm);
272
+ if (ekm.byteLength !== 64) {
282
273
  throw new common_1.InvalidParamError("ekm must be 64 bytes in length");
283
274
  }
284
- ekm = params.ekm;
285
275
  }
276
+ await this._setup();
286
277
  let ekM = undefined;
287
278
  let ekX;
288
279
  if (ekm !== undefined) {
@@ -291,24 +282,24 @@
291
282
  ekX = ek.subarray(32, 64);
292
283
  }
293
284
  else {
294
- ekX = new Uint8Array(32);
285
+ const randomEkX = new Uint8Array(32);
295
286
  try {
296
- this._api.getRandomValues(ekX);
287
+ this._api.getRandomValues(randomEkX);
297
288
  }
298
289
  catch (e) {
299
290
  throw new common_1.NotSupportedError(e);
300
291
  }
292
+ ekX = randomEkX;
301
293
  }
302
294
  const pk = new Uint8Array(await this.serializePublicKey(params.recipientPublicKey));
303
295
  if (pk.byteLength !== 1216) {
304
296
  throw new common_1.InvalidParamError("Invalid length of recipientPublicKey");
305
297
  }
306
- await this._setup();
307
298
  try {
308
299
  const pkM = pk.subarray(0, 1184);
309
300
  const pkX = pk.subarray(1184, 1216);
310
- const ctX = await this._x25519.derive(ekX, X25519_BASE);
311
- const ssX = await this._x25519.derive(ekX, pkX);
301
+ const ctX = common_1.x25519.getPublicKey(ekX);
302
+ const ssX = common_1.x25519.getSharedSecret(ekX, pkX);
312
303
  const [ctM, ssM] = await this._m.encap(pkM, ekM);
313
304
  return {
314
305
  sharedSecret: combiner(ssM, ssX, ctX, pkX).buffer,
@@ -327,12 +318,17 @@
327
318
  * @returns {Promise<ArrayBuffer>} A promise that resolves with the shared secret.
328
319
  * @throws {InvalidParamError} Thrown if the length of the `enc` is not 1120 bytes.
329
320
  * @throws {DecapError} Thrown if the shared secret cannot be decapsulated.
321
+ * @throws {NotSupportedError} if `senderPublicKey` is provided.
330
322
  */
331
323
  async decap(params) {
324
+ if (params.senderPublicKey !== undefined) {
325
+ throw new common_1.NotSupportedError("Auth and AuthPSK modes are not supported by this KEM");
326
+ }
327
+ const enc = (0, common_1.toArrayBuffer)(params.enc);
332
328
  const rSk = (0, common_1.isCryptoKeyPair)(params.recipientKey)
333
329
  ? params.recipientKey.privateKey
334
330
  : params.recipientKey;
335
- if (params.enc.byteLength !== 1120) {
331
+ if (enc.byteLength !== 1120) {
336
332
  throw new common_1.InvalidParamError("Invalid length of enc");
337
333
  }
338
334
  const sk = new Uint8Array(await this.serializePrivateKey(rSk));
@@ -342,11 +338,11 @@
342
338
  await this._setup();
343
339
  try {
344
340
  const [skM, skX, _pkM, pkX] = await this._expandDecapsulationKey(sk);
345
- const ct = new Uint8Array(params.enc);
341
+ const ct = new Uint8Array(enc);
346
342
  const ctM = ct.subarray(0, 1088);
347
343
  const ctX = ct.subarray(1088);
348
344
  const ssM = await this._m.decap(ctM, skM);
349
- const ssX = await this._x25519.derive(skX, ctX);
345
+ const ssX = common_1.x25519.getSharedSecret(skX, ctX);
350
346
  return combiner(ssM, ssX, ctX, pkX).buffer;
351
347
  }
352
348
  catch (e) {
@@ -382,7 +378,7 @@
382
378
  const expanded = mlkem_1.shake256.create({ dkLen: 96 }).update(sk).digest();
383
379
  const [pkM, skM] = await this._m.deriveKeyPair(expanded.subarray(0, 64));
384
380
  const skX = expanded.subarray(64, 96);
385
- const pkX = await this._x25519.derive(skX, X25519_BASE);
381
+ const pkX = common_1.x25519.getPublicKey(skX);
386
382
  return [skM, skX, pkM, pkX];
387
383
  }
388
384
  _serializePublicKey(k) {