@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 +60 -63
- package/esm/package.json +2 -1
- package/esm/src/xWing.d.ts +6 -6
- package/esm/src/xWing.d.ts.map +1 -1
- package/esm/src/xWing.js +28 -32
- package/package.json +8 -8
- package/script/src/xWing.d.ts +6 -6
- package/script/src/xWing.d.ts.map +1 -1
- package/script/src/xWing.js +28 -32
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
|
|
104
|
-
pnpm
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
package/esm/src/xWing.d.ts
CHANGED
|
@@ -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:
|
|
37
|
+
deserializePublicKey(key: ArrayBufferLike | ArrayBufferView): Promise<CryptoKey>;
|
|
40
38
|
serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer>;
|
|
41
|
-
deserializePrivateKey(key:
|
|
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:
|
|
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
|
/**
|
package/esm/src/xWing.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
273
|
+
const randomEkX = new Uint8Array(32);
|
|
283
274
|
try {
|
|
284
|
-
this._api.getRandomValues(
|
|
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 =
|
|
299
|
-
const ssX =
|
|
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 (
|
|
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(
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
42
|
-
"
|
|
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
|
+
}
|
package/script/src/xWing.d.ts
CHANGED
|
@@ -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:
|
|
37
|
+
deserializePublicKey(key: ArrayBufferLike | ArrayBufferView): Promise<CryptoKey>;
|
|
40
38
|
serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer>;
|
|
41
|
-
deserializePrivateKey(key:
|
|
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:
|
|
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;
|
|
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/script/src/xWing.js
CHANGED
|
@@ -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"
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
285
|
+
const randomEkX = new Uint8Array(32);
|
|
295
286
|
try {
|
|
296
|
-
this._api.getRandomValues(
|
|
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 =
|
|
311
|
-
const ssX =
|
|
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 (
|
|
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(
|
|
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 =
|
|
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 =
|
|
381
|
+
const pkX = common_1.x25519.getPublicKey(skX);
|
|
386
382
|
return [skM, skX, pkM, pkX];
|
|
387
383
|
}
|
|
388
384
|
_serializePublicKey(k) {
|