From eabde840772e3b05b2d0a80e61807f0d43ffb769 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Mon, 14 Aug 2023 09:23:32 +0900 Subject: [PATCH 01/14] Import from mod.ts. --- test/sample.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/sample.test.ts b/test/sample.test.ts index f1d46b8f7..7a9014708 100644 --- a/test/sample.test.ts +++ b/test/sample.test.ts @@ -1,9 +1,8 @@ import { assertEquals, assertRejects } from "testing/asserts.ts"; import { describe, it } from "testing/bdd.ts"; +import { AeadId, CipherSuite, KdfId, KemId } from "../mod.ts"; import { Aes128Gcm } from "../src/aeads/aesGcm.ts"; -import { AeadId, KdfId, KemId } from "../src/identifiers.ts"; -import { CipherSuite } from "../src/cipherSuite.ts"; import * as errors from "../src/errors.ts"; import { DhkemP256HkdfSha256 } from "../src/kems/dhkemP256.ts"; import { DhkemP384HkdfSha384 } from "../src/kems/dhkemP384.ts"; From b09723993539da5e99c6dff589d763376fa54a55 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Mon, 14 Aug 2023 09:24:52 +0900 Subject: [PATCH 02/14] Specify version on deno sample. --- samples/deno/app.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/deno/app.ts b/samples/deno/app.ts index 48954537a..a7af08c4d 100644 --- a/samples/deno/app.ts +++ b/samples/deno/app.ts @@ -1,16 +1,16 @@ // import { // Aes128Gcm, CipherSuite, DhkemP256HkdfSha256, HkdfSha256, -// } from "https://deno.land/x/hpke/core/mod.ts"; -// import { DhkemX25519HkdfSha256 } from "https://deno.land/x/hpke/x/dhkem-x25519/mod.ts"; +// } from "https://deno.land/x/hpke@1.2.0/core/mod.ts"; +// import { DhkemX25519HkdfSha256 } from "https://deno.land/x/hpke@1.2.0/x/dhkem-x25519/mod.ts"; import { AeadId, CipherSuite, KdfId, KemId, -} from "https://deno.land/x/hpke/mod.ts"; +} from "https://deno.land/x/hpke@1.2.0/mod.ts"; async function doHpke() { - const suite: CipherSuite = new CipherSuite({ + const suite = new CipherSuite({ kem: KemId.DhkemX25519HkdfSha256, kdf: KdfId.HkdfSha256, aead: AeadId.Chacha20Poly1305, From ce46914f7ed2065bae03f6c10a2b83eed569cb57 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Thu, 17 Aug 2023 21:18:31 +0900 Subject: [PATCH 03/14] Add support for X25519Kyber768Draft00. --- dnt.ts | 5 +- src/identifiers.ts | 1 + src/interfaces/dhkemInterface.ts | 9 + src/interfaces/kemInterface.ts | 3 + src/kems/dhkem.ts | 12 +- src/kems/hybridkem.ts | 238 +++++ src/kems/hybridkemX25519Kyber768.ts | 65 ++ src/kems/kemKyber768.ts | 192 ++++ src/kems/pqkemPrimitives/kyber768.ts | 893 ++++++++++++++++++ src/utils/misc.ts | 10 + x/hybridkem-x25519-kyber768/README.md | 241 +++++ x/hybridkem-x25519-kyber768/deno.json | 36 + x/hybridkem-x25519-kyber768/dnt.ts | 63 ++ x/hybridkem-x25519-kyber768/mod.ts | 1 + .../test/hybridkemX25519Kyber768.test.ts | 173 ++++ 15 files changed, 1930 insertions(+), 12 deletions(-) create mode 100644 src/interfaces/dhkemInterface.ts create mode 100644 src/kems/hybridkem.ts create mode 100644 src/kems/hybridkemX25519Kyber768.ts create mode 100644 src/kems/kemKyber768.ts create mode 100644 src/kems/pqkemPrimitives/kyber768.ts create mode 100644 x/hybridkem-x25519-kyber768/README.md create mode 100644 x/hybridkem-x25519-kyber768/deno.json create mode 100644 x/hybridkem-x25519-kyber768/dnt.ts create mode 100644 x/hybridkem-x25519-kyber768/mod.ts create mode 100644 x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts diff --git a/dnt.ts b/dnt.ts index 0e5a58f00..4ff5ff38d 100644 --- a/dnt.ts +++ b/dnt.ts @@ -6,6 +6,7 @@ await emptyDir("./x/chacha20poly1305/npm"); await emptyDir("./x/dhkem-secp256k1/npm"); await emptyDir("./x/dhkem-x25519/npm"); await emptyDir("./x/dhkem-x448/npm"); +await emptyDir("./x/hybridkem-x25519-kyber768/npm"); await build({ entryPoints: ["./mod.ts"], @@ -22,10 +23,10 @@ await build({ deno: "dev", }, package: { - name: "hpke-js", + name: "@hpke/hybridkem-x25519-kyber768", version: Deno.args[0], description: - "A Hybrid Public Key Encryption (HPKE) module for various JavaScript runtimes", + "A Hybrid Public Key Encryption (HPKE) module extension for a hybrid post-quantum KEM, X25519Kyber768", repository: { type: "git", url: "git+https://github.com/dajiaji/hpke-js.git", diff --git a/src/identifiers.ts b/src/identifiers.ts index 3f350c4de..7097497a0 100644 --- a/src/identifiers.ts +++ b/src/identifiers.ts @@ -26,6 +26,7 @@ export const Kem = { DhkemSecp256k1HkdfSha256: 0x0013, DhkemX25519HkdfSha256: 0x0020, DhkemX448HkdfSha512: 0x0021, + HybridkemX25519Kyber768: 0x0030, } as const; /** diff --git a/src/interfaces/dhkemInterface.ts b/src/interfaces/dhkemInterface.ts new file mode 100644 index 000000000..5de9ce850 --- /dev/null +++ b/src/interfaces/dhkemInterface.ts @@ -0,0 +1,9 @@ +import type { KdfInterface } from "./kdfInterface.ts"; +import type { KemInterface } from "./kemInterface.ts"; + +/** + * The DHKEM interface. + */ +export interface DhkemInterface extends KemInterface { + readonly kdf: KdfInterface; +} diff --git a/src/interfaces/kemInterface.ts b/src/interfaces/kemInterface.ts index 233c81a9c..9eb4b811f 100644 --- a/src/interfaces/kemInterface.ts +++ b/src/interfaces/kemInterface.ts @@ -3,6 +3,9 @@ import type { SenderContextParams } from "./senderContextParams.ts"; import { KemId } from "../identifiers.ts"; +// b"KEM" +export const SUITE_ID_HEADER_KEM = new Uint8Array([75, 69, 77, 0, 0]); + /** * The KEM interface. */ diff --git a/src/kems/dhkem.ts b/src/kems/dhkem.ts index b01678f54..ff7eab389 100644 --- a/src/kems/dhkem.ts +++ b/src/kems/dhkem.ts @@ -7,10 +7,9 @@ import type { RecipientContextParams } from "../interfaces/recipientContextParam import { EMPTY, INPUT_LENGTH_LIMIT } from "../consts.ts"; import { DecapError, EncapError, InvalidParamError } from "../errors.ts"; import { KemId } from "../identifiers.ts"; -import { i2Osp, isCryptoKeyPair } from "../utils/misc.ts"; +import { SUITE_ID_HEADER_KEM } from "../interfaces/kemInterface.ts"; +import { concat, i2Osp, isCryptoKeyPair } from "../utils/misc.ts"; -// b"KEM" -const SUITE_ID_HEADER_KEM = new Uint8Array([75, 69, 77, 0, 0]); // b"eae_prk" const LABEL_EAE_PRK = new Uint8Array([101, 97, 101, 95, 112, 114, 107]); // b"shared_secret" @@ -20,13 +19,6 @@ const LABEL_SHARED_SECRET = new Uint8Array([ 114, 101, 116, ]); -function concat(a: Uint8Array, b: Uint8Array): Uint8Array { - const ret = new Uint8Array(a.length + b.length); - ret.set(a, 0); - ret.set(b, a.length); - return ret; -} - function concat3( a: Uint8Array, b: Uint8Array, diff --git a/src/kems/hybridkem.ts b/src/kems/hybridkem.ts new file mode 100644 index 000000000..988273250 --- /dev/null +++ b/src/kems/hybridkem.ts @@ -0,0 +1,238 @@ +import type { DhkemInterface } from "../interfaces/dhkemInterface.ts"; +import type { KdfInterface } from "../interfaces/kdfInterface.ts"; +import type { KemInterface } from "../interfaces/kemInterface.ts"; +import type { RecipientContextParams } from "../interfaces/recipientContextParams.ts"; +import type { SenderContextParams } from "../interfaces/senderContextParams.ts"; + +import { EMPTY } from "../consts.ts"; +import { + DeserializeError, + InvalidParamError, + NotSupportedError, + SerializeError, +} from "../errors.ts"; +import { KemId } from "../identifiers.ts"; +import { LABEL_DKP_PRK, LABEL_SK } from "../interfaces/dhkemPrimitives.ts"; +import { SUITE_ID_HEADER_KEM } from "../interfaces/kemInterface.ts"; +import { concat, i2Osp, isCryptoKeyPair } from "../utils/misc.ts"; +import { XCryptoKey } from "../xCryptoKey.ts"; + +export class Hybridkem implements KemInterface { + public readonly id: KemId = KemId.NotAssigned; + public readonly name: string = ""; + public readonly secretSize: number = 0; + public readonly encSize: number = 0; + public readonly publicKeySize: number = 0; + public readonly privateKeySize: number = 0; + protected _a: DhkemInterface; + protected _b: KemInterface; + protected _kdf: KdfInterface; + + constructor( + id: KemId, + a: DhkemInterface, + b: KemInterface, + kdf: KdfInterface, + ) { + this.id = id; + this._a = a; + this._b = b; + this._kdf = kdf; + const suiteId = new Uint8Array(SUITE_ID_HEADER_KEM); + suiteId.set(i2Osp(this.id, 2), 3); + this._kdf.init(suiteId); + } + + public async serializePublicKey(key: CryptoKey): Promise { + try { + return await this._serializePublicKey(key as XCryptoKey); + } catch (e: unknown) { + throw new SerializeError(e); + } + } + + public async deserializePublicKey(key: ArrayBuffer): Promise { + try { + return await this._deserializePublicKey(key); + } catch (e: unknown) { + throw new DeserializeError(e); + } + } + + public async serializePrivateKey(key: CryptoKey): Promise { + try { + return await this._serializePrivateKey(key as XCryptoKey); + } catch (e: unknown) { + throw new SerializeError(e); + } + } + + public async deserializePrivateKey(key: ArrayBuffer): Promise { + try { + return await this._deserializePrivateKey(key); + } catch (e: unknown) { + throw new DeserializeError(e); + } + } + + public async generateKeyPair(): Promise { + const kpA = await this._a.generateKeyPair(); + const kpB = await this._b.generateKeyPair(); + const pkA = await this._a.serializePublicKey(kpA.publicKey); + const skA = await this._a.serializePrivateKey(kpA.privateKey); + const pkB = await this._b.serializePublicKey(kpB.publicKey); + const skB = await this._b.serializePrivateKey(kpB.privateKey); + return { + publicKey: await this.deserializePublicKey( + concat(new Uint8Array(pkA), new Uint8Array(pkB)), + ), + privateKey: await this.deserializePrivateKey( + concat(new Uint8Array(skA), new Uint8Array(skB)), + ), + }; + } + + public async deriveKeyPair(ikm: ArrayBuffer): Promise { + const dkpPrk = await this._kdf.labeledExtract( + EMPTY, + LABEL_DKP_PRK, + new Uint8Array(ikm), + ); + const seed = new Uint8Array( + await this._kdf.labeledExpand( + dkpPrk, + LABEL_SK, + EMPTY, + 32 + 64, + ), + ); + const seed1 = seed.slice(0, 32); + const seed2 = seed.slice(32, 96); + const kpA = await this._a.deriveKeyPair(seed1); + const kpB = await this._b.deriveKeyPair(seed2); + const pkA = await this._a.serializePublicKey(kpA.publicKey); + const skA = await this._a.serializePrivateKey(kpA.privateKey); + const pkB = await this._b.serializePublicKey(kpB.publicKey); + const skB = await this._b.serializePrivateKey(kpB.privateKey); + return { + publicKey: await this.deserializePublicKey( + concat(new Uint8Array(pkA), new Uint8Array(pkB)), + ), + privateKey: await this.deserializePrivateKey( + concat(new Uint8Array(skA), new Uint8Array(skB)), + ), + }; + } + + public async importKey( + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, + isPublic = true, + ): Promise { + if (format !== "raw") { + throw new NotSupportedError("'jwk' is not supported"); + } + if (!(key instanceof ArrayBuffer)) { + throw new InvalidParamError("Invalid type of key"); + } + if (isPublic) { + return await this.deserializePublicKey(key as ArrayBuffer); + } + return await this.deserializePrivateKey(key as ArrayBuffer); + } + + public async encap( + params: SenderContextParams, + ): Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }> { + const pkR = new Uint8Array( + await this.serializePublicKey(params.recipientPublicKey), + ); + const pkRA = await this._a.deserializePublicKey( + pkR.slice(0, this._a.publicKeySize), + ); + const pkRB = await this._b.deserializePublicKey( + pkR.slice(this._a.publicKeySize), + ); + const resA = await this._a.encap({ recipientPublicKey: pkRA }); + const resB = await this._b.encap({ recipientPublicKey: pkRB }); + return { + sharedSecret: concat( + new Uint8Array(resA.sharedSecret), + new Uint8Array(resB.sharedSecret), + ), + enc: concat(new Uint8Array(resA.enc), new Uint8Array(resB.enc)), + }; + } + + public async decap(params: RecipientContextParams): Promise { + const sk = isCryptoKeyPair(params.recipientKey) + ? params.recipientKey.privateKey + : params.recipientKey; + const skR = new Uint8Array(await this.serializePrivateKey(sk)); + const skRA = await this._a.deserializePrivateKey( + skR.slice(0, this._a.privateKeySize), + ); + const skRB = await this._b.deserializePrivateKey( + skR.slice(this._a.privateKeySize), + ); + const ssA = await this._a.decap({ + recipientKey: skRA, + enc: params.enc.slice(0, this._a.encSize), + }); + const ssB = await this._b.decap({ + recipientKey: skRB, + enc: params.enc.slice(this._a.encSize), + }); + return concat(new Uint8Array(ssA), new Uint8Array(ssB)); + } + + private _serializePublicKey(k: XCryptoKey): Promise { + return new Promise((resolve, reject) => { + if (k.type !== "public") { + reject(new Error("Not public key")); + } + if (k.algorithm.name !== this.name) { + reject(new Error(`Invalid algorithm name: ${k.algorithm.name}`)); + } + if (k.key.byteLength !== this.publicKeySize) { + reject(new Error(`Invalid key length: ${k.key.byteLength}`)); + } + resolve(k.key.buffer); + }); + } + + private _deserializePublicKey(k: ArrayBuffer): Promise { + return new Promise((resolve, reject) => { + if (k.byteLength !== this.publicKeySize) { + reject(new Error(`Invalid key length: ${k.byteLength}`)); + } + resolve(new XCryptoKey(this.name, new Uint8Array(k), "public")); + }); + } + + private _serializePrivateKey(k: XCryptoKey): Promise { + return new Promise((resolve, reject) => { + if (k.type !== "private") { + reject(new Error("Not private key")); + } + if (k.algorithm.name !== this.name) { + reject(new Error(`Invalid algorithm name: ${k.algorithm.name}`)); + } + if (k.key.byteLength !== this.privateKeySize) { + reject(new Error(`Invalid key length: ${k.key.byteLength}`)); + } + resolve(k.key.buffer); + }); + } + + private _deserializePrivateKey(k: ArrayBuffer): Promise { + return new Promise((resolve, reject) => { + if (k.byteLength !== this.privateKeySize) { + reject(new Error(`Invalid key length: ${k.byteLength}`)); + } + resolve( + new XCryptoKey(this.name, new Uint8Array(k), "private", ["deriveBits"]), + ); + }); + } +} diff --git a/src/kems/hybridkemX25519Kyber768.ts b/src/kems/hybridkemX25519Kyber768.ts new file mode 100644 index 000000000..fe575cf64 --- /dev/null +++ b/src/kems/hybridkemX25519Kyber768.ts @@ -0,0 +1,65 @@ +import type { DhkemInterface } from "../interfaces/dhkemInterface.ts"; + +import { KemId } from "../identifiers.ts"; +import { HkdfSha256 } from "../kdfs/hkdfSha256.ts"; + +import { Dhkem } from "./dhkem.ts"; +import { X25519 } from "./dhkemPrimitives/x25519.ts"; +import { Hybridkem } from "./hybridkem.ts"; +import { KemKyber768 } from "./kemKyber768.ts"; + +class DhkemX25519HkdfSha256 extends Dhkem implements DhkemInterface { + public readonly id: KemId = KemId.DhkemX25519HkdfSha256; + public readonly secretSize: number = 32; + public readonly encSize: number = 32; + public readonly publicKeySize: number = 32; + public readonly privateKeySize: number = 32; + + constructor() { + const kdf = new HkdfSha256(); + super(KemId.DhkemX25519HkdfSha256, new X25519(kdf), kdf); + } + + public get kdf() { + return this._kdf; + } +} + +/** + * The Hybrid Post-Quantum KEM (X25519, Kyber768). + * + * This class is implemented using + * {@link https://github.com/Argyle-Software/kyber | pqc-kyber }. + * + * The instance of this class can be specified to the + * {@link https://deno.land/x/hpke/core/mod.ts?s=CipherSuiteParams | CipherSuiteParams} as follows: + * + * @example + * ```ts + * import { Aes128Gcm, CipherSuite, HkdfSha256 } from "http://deno.land/x/hpke/core/mod.ts"; + * import { HybridkemX25519Kyber768 } from "https://deno.land/x/hpke/x/hybridkem-x25519-kyber768/mod.ts"; + * const suite = new CipherSuite({ + * kem: new HybridkemX25519Kyber768(), + * kdf: new HkdfSha256(), + * aead: new Aes128Gcm(), + * }); + * ``` + */ +export class HybridkemX25519Kyber768 extends Hybridkem { + public readonly id: KemId = KemId.HybridkemX25519Kyber768; + public readonly name: string = "X25519Kyber25519"; + public readonly secretSize: number = 64; + public readonly encSize: number = 1120; + public readonly publicKeySize: number = 1216; + public readonly privateKeySize: number = 2432; + public readonly auth: boolean = false; + + constructor() { + super( + KemId.HybridkemX25519Kyber768, + new DhkemX25519HkdfSha256(), + new KemKyber768(), + new HkdfSha256(), + ); + } +} diff --git a/src/kems/kemKyber768.ts b/src/kems/kemKyber768.ts new file mode 100644 index 000000000..0d85b98eb --- /dev/null +++ b/src/kems/kemKyber768.ts @@ -0,0 +1,192 @@ +// @ts-ignore: for "npm:" +// import * as kyber from "npm:crystals-kyber@5.1.0"; + +import type { KemInterface } from "../interfaces/kemInterface.ts"; +import type { RecipientContextParams } from "../interfaces/recipientContextParams.ts"; +import type { SenderContextParams } from "../interfaces/senderContextParams.ts"; + +import { INPUT_LENGTH_LIMIT } from "../consts.ts"; +import { + DecapError, + DeriveKeyPairError, + DeserializeError, + EncapError, + InvalidParamError, + NotSupportedError, + SerializeError, +} from "../errors.ts"; +import { KemId } from "../identifiers.ts"; +import { isCryptoKeyPair } from "../utils/misc.ts"; +import { XCryptoKey } from "../xCryptoKey.ts"; + +import { Kyber768 } from "./pqkemPrimitives/kyber768.ts"; + +// import { INPUT_LENGTH_LIMIT } from "../consts.ts"; + +const ALG_NAME = "Keyber768"; + +export class KemKyber768 implements KemInterface { + public readonly id: KemId = KemId.NotAssigned; + public readonly secretSize: number = 32; + public readonly encSize: number = 1088; + public readonly publicKeySize: number = 1184; + public readonly privateKeySize: number = 2400; + private _prim: Kyber768; + + constructor() { + this._prim = new Kyber768(); + } + + public async serializePublicKey(key: CryptoKey): Promise { + try { + return await this._serializePublicKey(key as XCryptoKey); + } catch (e: unknown) { + throw new SerializeError(e); + } + } + + public async deserializePublicKey(key: ArrayBuffer): Promise { + try { + return await this._deserializePublicKey(key); + } catch (e: unknown) { + throw new DeserializeError(e); + } + } + + public async serializePrivateKey(key: CryptoKey): Promise { + try { + return await this._serializePrivateKey(key as XCryptoKey); + } catch (e: unknown) { + throw new SerializeError(e); + } + } + + public async deserializePrivateKey(key: ArrayBuffer): Promise { + try { + return await this._deserializePrivateKey(key); + } catch (e: unknown) { + throw new DeserializeError(e); + } + } + + public async generateKeyPair(): Promise { + // const keys = kyber.KeyGen768(); + // const sk = await this.deserializePrivateKey(new Uint8Array(keys[1])); + // const pk = await this.deserializePublicKey(new Uint8Array(keys[0])); + const keys = this._prim.generateKeyPair(); + const sk = await this.deserializePrivateKey(keys[1]); + const pk = await this.deserializePublicKey(keys[0]); + return { publicKey: pk, privateKey: sk }; + } + + public async deriveKeyPair(ikm: ArrayBuffer): Promise { + if (ikm.byteLength > INPUT_LENGTH_LIMIT) { + throw new InvalidParamError("Too long ikm"); + } + try { + const keys = await this._prim.deriveKeyPair(new Uint8Array(ikm)); + const sk = await this.deserializePrivateKey(keys[1]); + const pk = await this.deserializePublicKey(keys[0]); + return { publicKey: pk, privateKey: sk }; + } catch (e: unknown) { + throw new DeriveKeyPairError(e); + } + } + + public async importKey( + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, + isPublic = true, + ): Promise { + if (format !== "raw") { + throw new NotSupportedError("'jwk' is not supported"); + } + if (isPublic) { + return await this.deserializePublicKey(key as ArrayBuffer); + } + return await this.deserializePrivateKey(key as ArrayBuffer); + } + + public async encap( + params: SenderContextParams, + ): Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }> { + const pkR = new Uint8Array( + await this.serializePublicKey(params.recipientPublicKey), + ); + try { + // const res = kyber.Encrypt768(pkR); + // return { + // sharedSecret: new Uint8Array(res[1]), + // enc: new Uint8Array(res[0]), + // }; + const res = this._prim.encap(pkR); + return { sharedSecret: res[1], enc: res[0] }; + } catch (e: unknown) { + throw new EncapError(e); + } + } + + public async decap(params: RecipientContextParams): Promise { + const skR = isCryptoKeyPair(params.recipientKey) + ? params.recipientKey.privateKey + : params.recipientKey; + const serializedSkR = new Uint8Array(await this.serializePrivateKey(skR)); + try { + // const res = kyber.Decrypt768(new Uint8Array(params.enc), serializedSkR); + // return new Uint8Array(res); + return this._prim.decap(new Uint8Array(params.enc), serializedSkR); + } catch (e: unknown) { + throw new DecapError(e); + } + } + + private _serializePublicKey(k: XCryptoKey): Promise { + return new Promise((resolve, reject) => { + if (k.type !== "public") { + reject(new Error("Not public key")); + } + if (k.algorithm.name !== ALG_NAME) { + reject(new Error(`Invalid algorithm name: ${k.algorithm.name}`)); + } + if (k.key.byteLength !== this.publicKeySize) { + reject(new Error(`Invalid key length: ${k.key.byteLength}`)); + } + resolve(k.key.buffer); + }); + } + + private _deserializePublicKey(k: ArrayBuffer): Promise { + return new Promise((resolve, reject) => { + if (k.byteLength !== this.publicKeySize) { + reject(new Error(`Invalid key length: ${k.byteLength}`)); + } + resolve(new XCryptoKey(ALG_NAME, new Uint8Array(k), "public")); + }); + } + + private _serializePrivateKey(k: XCryptoKey): Promise { + return new Promise((resolve, reject) => { + if (k.type !== "private") { + reject(new Error("Not private key")); + } + if (k.algorithm.name !== ALG_NAME) { + reject(new Error(`Invalid algorithm name: ${k.algorithm.name}`)); + } + if (k.key.byteLength !== this.privateKeySize) { + reject(new Error(`Invalid key length: ${k.key.byteLength}`)); + } + resolve(k.key.buffer); + }); + } + + private _deserializePrivateKey(k: ArrayBuffer): Promise { + return new Promise((resolve, reject) => { + if (k.byteLength !== this.privateKeySize) { + reject(new Error(`Invalid key length: ${k.byteLength}`)); + } + resolve( + new XCryptoKey(ALG_NAME, new Uint8Array(k), "private", ["deriveBits"]), + ); + }); + } +} diff --git a/src/kems/pqkemPrimitives/kyber768.ts b/src/kems/pqkemPrimitives/kyber768.ts new file mode 100644 index 000000000..80efd0304 --- /dev/null +++ b/src/kems/pqkemPrimitives/kyber768.ts @@ -0,0 +1,893 @@ +// @ts-ignore: for "npm:" +import { + sha3_256, + sha3_512, + shake128, + shake256, +} from "npm:@noble/hashes@1.3.1/sha3"; + +import { concat } from "../../utils/misc.ts"; + +// deno-fmt-ignore +const nttZetas = [ + 2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962, + 2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017, + 732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047, + 1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830, + 107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226, + 430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574, + 1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349, + 418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193, + 1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459, + 478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628, +]; + +// deno-fmt-ignore +const nttZetasInv = [ + 1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535, + 1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465, + 1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685, + 1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235, + 3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652, + 1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853, + 1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552, + 2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871, + 829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171, + 3127, 3042, 1907, 1836, 1517, 359, 758, 1441, +]; + +const paramsK = 3; +const paramsN = 256; +const paramsQ = 3329; +const paramsQinv = 62209; +const paramsETA = 2; + +export class Kyber768 { + private _api; + + constructor() { + this._api = globalThis.crypto; + } + + public generateKeyPair(): [Uint8Array, Uint8Array] { + const rnd = new Uint8Array(64); + this._api.getRandomValues(rnd); + return this._deriveKeyPair(rnd); + } + + public deriveKeyPair(ikm: Uint8Array): [Uint8Array, Uint8Array] { + if (ikm.byteLength !== 64) { + throw new Error("ikm must be 64 bytes in length"); + } + return this._deriveKeyPair(ikm); + } + + public encap(pk: Uint8Array, ikm?: Uint8Array): [Uint8Array, Uint8Array] { + // random 32 bytes m + let m: Uint8Array; + if (ikm === undefined) { + m = new Uint8Array(32); + this._api.getRandomValues(m); + } else { + if (ikm.byteLength !== 32) { + throw new Error("ikm must be 32 bytes in length"); + } + m = ikm; + } + const mh = sha3_256.create().update(m).digest(); + const pkh = sha3_256.create().update(pk).digest(); + const kr = sha3_512.create().update(mh).update(pkh).digest(); + const kr1 = kr.subarray(0, 32); + const kr2 = kr.subarray(32, 64); + const c = this._encap(pk, mh, kr2); + const ch = sha3_256.create().update(c).digest(); + const ss = shake256.create({}).update(kr1).update(ch).digest(); + return [c, ss]; + } + + public decap(c: Uint8Array, privateKey: Uint8Array): Uint8Array { + // extract sk, pk, pkh and z + const sk = privateKey.subarray(0, 1152); + const pk = privateKey.subarray(1152, 2336); + const pkh = privateKey.subarray(2336, 2368); + const z = privateKey.subarray(2368, 2400); + + // IND-CPA decrypt + const m = this._decap(c, sk); + const kr = sha3_512.create().update(m).update(pkh).digest(); + const kr1 = kr.subarray(0, 32); + const kr2 = kr.subarray(32, 64); + + // IND-CPA encrypt + const cmp = this._encap(pk, m, kr2); + + // compare c and cmp + const validated = compareArray(c, cmp); + + // hash c with SHA3-256 + const ch = sha3_256.create().update(c).digest(); + + let ss: Uint8Array; + if (validated) { + ss = shake256.create({}).update(kr1).update(ch).digest(); + } else { + ss = shake256.create({}).update(z).update(ch).digest(); + } + return ss; + } + + private _deriveKeyPair(ikm: Uint8Array): [Uint8Array, Uint8Array] { + const cpaSeed = ikm.subarray(0, 32); + const z = ikm.subarray(32, 64); + + // IND-CPA keypair + const cpaKeys = this._deriveCpaKeyPair(cpaSeed); + + const pk = cpaKeys[0]; + const pkh = sha3_256.create().update(pk).digest(); + const sk = new Uint8Array(2400); + sk.set(cpaKeys[1], 0); + sk.set(pk, 1152); + sk.set(pkh, 1152 + 1184); + sk.set(z, 1152 + 1184 + 32); + return [pk, sk]; + } + + // indcpaKeyGen generates public and private keys for the CPA-secure + // public-key encryption scheme underlying Kyber. + private _deriveCpaKeyPair(cpaSeed: Uint8Array): [Uint8Array, Uint8Array] { + const seed = sha3_512.create().update(cpaSeed).digest(); + const publicSeed = seed.subarray(0, 32); + const noiseSeed = seed.subarray(32, 64); + + // generate public matrix A (already in NTT form) + const a = generateMatrixA(publicSeed, false); + + // sample secret s + const s = new Array>(paramsK); + let nonce = 0; + for (let i = 0; i < paramsK; i++) { + s[i] = sample(noiseSeed, nonce); + nonce++; + } + + // sample noise e + const e = new Array>(paramsK); + for (let i = 0; i < paramsK; i++) { + e[i] = sample(noiseSeed, nonce); + nonce++; + } + + // perform number theoretic transform on secret s + for (let i = 0; i < paramsK; i++) { + s[i] = ntt(s[i]); + } + + // perform number theoretic transform on error/noise e + for (let i = 0; i < paramsK; i++) { + e[i] = ntt(e[i]); + } + + // barrett reduction + for (let i = 0; i < paramsK; i++) { + s[i] = reduce(s[i]); + } + + // KEY COMPUTATION + // A.s + e = pk + + // calculate A.s + const pk = new Array>(paramsK); + for (let i = 0; i < paramsK; i++) { + // montgomery reduction + pk[i] = polyToMont(multiply(a[i], s)); + } + + // calculate addition of e + for (let i = 0; i < paramsK; i++) { + pk[i] = add(pk[i], e[i]); + } + + // barrett reduction + for (let i = 0; i < paramsK; i++) { + pk[i] = reduce(pk[i]); + } + + // PUBLIC KEY + // turn polynomials into byte arrays + const pubKey = new Uint8Array(1184); + for (let i = 0; i < paramsK; i++) { + pubKey.set(polyToBytes(pk[i]), i * 384); + } + // append public seed + pubKey.set(publicSeed, 1152); + + // PRIVATE KEY + // turn polynomials into byte arrays + const privKey = new Uint8Array(1152); + for (let i = 0; i < paramsK; i++) { + privKey.set(polyToBytes(s[i]), i * 384); + } + return [pubKey, privKey]; + } + + // _encap is the encapsulation function of the CPA-secure + // public-key encryption scheme underlying Kyber. + private _encap( + pk1: Uint8Array, + msg: Uint8Array, + coins: Uint8Array, + ): Uint8Array { + const pk = new Array>(paramsK); + for (let i = 0; i < paramsK; i++) { + pk[i] = polyFromBytes(pk1.subarray(i * 384, (i + 1) * 384)); + } + const seed = pk1.subarray(1152, 1184); + + // generate transpose of public matrix A + const at = generateMatrixA(seed, true); + + // sample random vector r + const r = new Array>(paramsK); + let nonce = 0; + for (let i = 0; i < paramsK; i++) { + r[i] = sample(coins, nonce); + nonce++; + } + + // sample error vector e1 + const e1 = new Array>(paramsK); + for (let i = 0; i < paramsK; i++) { + e1[i] = sample(coins, nonce); + nonce++; + } + + // sample e2 + const e2 = sample(coins, nonce); + + // perform number theoretic transform on random vector r + for (let i = 0; i < paramsK; i++) { + r[i] = ntt(r[i]); + } + + // barrett reduction + for (let i = 0; i < paramsK; i++) { + r[i] = reduce(r[i]); + } + + // ENCRYPT COMPUTATION + // A.r + e1 = u + // pk.r + e2 + m = v + + // calculate A.r + const u = new Array>(paramsK); + for (let i = 0; i < paramsK; i++) { + u[i] = multiply(at[i], r); + } + + // perform inverse number theoretic transform on A.r + for (let i = 0; i < paramsK; i++) { + u[i] = nttInverse(u[i]); + } + + // calculate addition of e1 + for (let i = 0; i < paramsK; i++) { + u[i] = add(u[i], e1[i]); + } + + // decode message m + const m = polyFromMsg(msg); + + // calculate pk.r + let v = multiply(pk, r); + + // perform inverse number theoretic transform on pk.r + v = nttInverse(v); + + // calculate addition of e2 + v = add(v, e2); + + // calculate addition of m + v = add(v, m); + + // barrett reduction + for (let i = 0; i < paramsK; i++) { + u[i] = reduce(u[i]); + } + + // barrett reduction + v = reduce(v); + + // compress + const c1 = compress1(u); + const c2 = compress2(v); + + // return c1 || c2 + return concat(c1, c2); + } + + // indcpaDecrypt is the decryption function of the CPA-secure + // public-key encryption scheme underlying Kyber. + private _decap(c: Uint8Array, privateKey: Uint8Array): Uint8Array { + // extract ciphertext + const u = decompress1(c.subarray(0, 960)); + const v = decompress2(c.subarray(960, 1088)); + + const privateKeyPolyvec = polyvecFromBytes(privateKey); + + for (let i = 0; i < paramsK; i++) { + u[i] = ntt(u[i]); + } + + // ??? + let mp = multiply(privateKeyPolyvec, u); + mp = nttInverse(mp); + mp = subtract(v, mp); + mp = reduce(mp); + return polyToMsg(mp); + } +} + +// polyvecFromBytes deserializes a vector of polynomials. +function polyvecFromBytes(a: Uint8Array): Array> { + const r = new Array>(paramsK); + for (let i = 0; i < paramsK; i++) { + r[i] = new Array(384); + } + for (let i = 0; i < paramsK; i++) { + r[i] = polyFromBytes(a.subarray(i * 384, (i + 1) * 384)); + } + return r; +} + +// polyToBytes serializes a polynomial into an array of bytes. +function polyToBytes(a: Array): Uint8Array { + let t0 = 0; + let t1 = 0; + const r = new Uint8Array(384); + const a2 = subtract_q(a); // Returns: a - q if a >= q, else a (each coefficient of the polynomial) + // for 0-127 + for (let i = 0; i < paramsN / 2; i++) { + // get two coefficient entries in the polynomial + t0 = uint16(a2[2 * i]); + t1 = uint16(a2[2 * i + 1]); + + // convert the 2 coefficient into 3 bytes + r[3 * i + 0] = byte(t0 >> 0); // byte() does mod 256 of the input (output value 0-255) + r[3 * i + 1] = byte(t0 >> 8) | byte(t1 << 4); + r[3 * i + 2] = byte(t1 >> 4); + } + return r; +} + +// polyFromBytes de-serialises an array of bytes into a polynomial, +// and represents the inverse of polyToBytes. +function polyFromBytes(a: Uint8Array): Array { + const r = new Array(384).fill(0); + for (let i = 0; i < paramsN / 2; i++) { + r[2 * i] = int16( + ((uint16(a[3 * i + 0]) >> 0) | (uint16(a[3 * i + 1]) << 8)) & 0xFFF, + ); + r[2 * i + 1] = int16( + ((uint16(a[3 * i + 1]) >> 4) | (uint16(a[3 * i + 2]) << 4)) & 0xFFF, + ); + } + return r; +} + +// polyToMsg converts a polynomial to a 32-byte message +// and represents the inverse of polyFromMsg. +function polyToMsg(a: Array): Uint8Array { + const msg = new Uint8Array(32); + let t; + const a2 = subtract_q(a); + for (let i = 0; i < paramsN / 8; i++) { + msg[i] = 0; + for (let j = 0; j < 8; j++) { + t = (((uint16(a2[8 * i + j]) << 1) + uint16(paramsQ / 2)) / + uint16(paramsQ)) & 1; + msg[i] |= byte(t << j); + } + } + return msg; +} + +// polyFromMsg converts a 32-byte message to a polynomial. +function polyFromMsg(msg: Uint8Array): Array { + const r = new Array(384).fill(0); // each element is int16 (0-65535) + let mask; // int16 + for (let i = 0; i < paramsN / 8; i++) { + for (let j = 0; j < 8; j++) { + mask = -1 * int16((msg[i] >> j) & 1); + r[8 * i + j] = mask & int16((paramsQ + 1) / 2); + } + } + return r; +} + +// generateMatrixA deterministically generates a matrix `A` (or the transpose of `A`) +// from a seed. Entries of the matrix are polynomials that look uniformly random. +// Performs rejection sampling on the output of an extendable-output function (XOF). +function generateMatrixA( + seed: Uint8Array, + transposed: boolean, +): Array>> { + const a = new Array>>(3); + let ctr = 0; + const transpose = new Uint8Array(2); + const outputlen = 3 * 168; // 504 + + for (let i = 0; i < paramsK; i++) { + a[i] = new Array>(paramsK); + + for (let j = 0; j < paramsK; j++) { + // set if transposed matrix or not + if (transposed) { + transpose[0] = i; + transpose[1] = j; + } else { + transpose[0] = j; + transpose[1] = i; + } + const xof = shake128.create({ dkLen: 672 }); + // const output = xof.update(seed).update(Uint8Array.from(transpose)).digest(); + const output = xof.update(seed).update(transpose).digest(); + + // run rejection sampling on the output from above + const result = indcpaRejUniform( + output.subarray(0, 504), + outputlen, + paramsN, + ); + a[i][j] = result[0]; // the result here is an NTT-representation + ctr = result[1]; // keeps track of index of output array from sampling function + + while (ctr < paramsN) { // if the polynomial hasnt been filled yet with mod q entries + const outputn = output.subarray(504, 672); // take last 168 bytes of byte array from xof + const result1 = indcpaRejUniform(outputn, 168, paramsN - ctr); // run sampling function again + const missing = result1[0]; // here is additional mod q polynomial coefficients + const ctrn = result1[1]; // how many coefficients were accepted and are in the output + // starting at last position of output array from first sampling function until 256 is reached + for (let k = ctr; k < paramsN; k++) { + a[i][j][k] = missing[k - ctr]; // fill rest of array with the additional coefficients until full + } + ctr = ctr + ctrn; // update index + } + } + } + return a; +} + +// indcpaRejUniform runs rejection sampling on uniform random bytes +// to generate uniform random integers modulo `Q`. +function indcpaRejUniform( + buf: Uint8Array, + bufl: number, + len: number, +): [Array, number] { + const r = new Array(384).fill(0); + let val0, val1; // d1, d2 in kyber documentation + let pos = 0; // i + let ctr = 0; // j + + while (ctr < len && pos + 3 <= bufl) { + // compute d1 and d2 + val0 = (uint16((buf[pos]) >> 0) | (uint16(buf[pos + 1]) << 8)) & 0xFFF; + val1 = (uint16((buf[pos + 1]) >> 4) | (uint16(buf[pos + 2]) << 4)) & 0xFFF; + + // increment input buffer index by 3 + pos = pos + 3; + + // if d1 is less than 3329 + if (val0 < paramsQ) { + // assign to d1 + r[ctr] = val0; + // increment position of output array + ctr = ctr + 1; + } + if (ctr < len && val1 < paramsQ) { + r[ctr] = val1; + ctr = ctr + 1; + } + } + return [r, ctr]; +} + +// sample samples a polynomial deterministically from a seed +// and nonce, with the output polynomial being close to a centered +// binomial distribution with parameter paramsETA = 2. +function sample(seed: Uint8Array, nonce: number): Array { + const l = paramsETA * paramsN / 4; + const p = prf(l, seed, nonce); + return byteopsCbd(p); +} + +// prf provides a pseudo-random function (PRF) which returns +// a byte array of length `l`, using the provided key and nonce +// to instantiate the PRF's underlying hash function. +function prf(l: number, key: Uint8Array, nonce: number): Uint8Array { + const nonce_arr = new Uint8Array(1); + nonce_arr[0] = nonce; + // const hash = new SHAKE(256); + // hash.reset(); + // const buffer1 = Buffer.from(key); + // const buffer2 = Buffer.from(nonce_arr); + // hash.update(buffer1).update(buffer2); + // let buf = hash.digest({ buffer: Buffer.alloc(l)}); // 128 long byte array + // return buf; + return shake256.create({ dkLen: l }).update(key).update(nonce_arr).digest(); +} + +// byteopsCbd computes a polynomial with coefficients distributed +// according to a centered binomial distribution with parameter paramsETA, +// given an array of uniformly random bytes. +function byteopsCbd(buf: Uint8Array): Array { + let t, d; + let a, b; + const r = new Array(384).fill(0); + for (let i = 0; i < paramsN / 8; i++) { + t = byteopsLoad32(buf.subarray(4 * i, buf.length)) >>> 0; + d = (t & 0x55555555) >>> 0; + d = d + ((((t >> 1) >>> 0) & 0x55555555) >>> 0) >>> 0; + for (let j = 0; j < 8; j++) { + a = int16((((d >> (4 * j + 0)) >>> 0) & 0x3) >>> 0); + b = int16((((d >> (4 * j + paramsETA)) >>> 0) & 0x3) >>> 0); + r[8 * i + j] = a - b; + } + } + return r; +} + +// byteopsLoad32 returns a 32-bit unsigned integer loaded from byte x. +function byteopsLoad32(x: Uint8Array): number { + let r; + r = uint32(x[0]); + r = ((r | (uint32(x[1]) << 8)) >>> 0) >>> 0; + r = ((r | (uint32(x[2]) << 16)) >>> 0) >>> 0; + r = ((r | (uint32(x[3]) << 24)) >>> 0) >>> 0; + return uint32(r); +} + +// ntt performs an inplace number-theoretic transform (NTT) in `Rq`. +// The input is in standard order, the output is in bit-reversed order. +function ntt(r: Array): Array { + let j = 0; + let k = 1; + let zeta; + let t; + // 128, 64, 32, 16, 8, 4, 2 + for (let l = 128; l >= 2; l >>= 1) { + // 0, + for (let start = 0; start < 256; start = j + l) { + zeta = nttZetas[k]; + k = k + 1; + // for each element in the subsections (128, 64, 32, 16, 8, 4, 2) starting at an offset + for (j = start; j < start + l; j++) { + // compute the modular multiplication of the zeta and each element in the subsection + t = nttFqMul(zeta, r[j + l]); // t is mod q + // overwrite each element in the subsection as the opposite subsection element minus t + r[j + l] = r[j] - t; + // add t back again to the opposite subsection + r[j] = r[j] + t; + } + } + } + return r; +} + +// nttFqMul performs multiplication followed by Montgomery reduction +// and returns a 16-bit integer congruent to `a*b*R^{-1} mod Q`. +function nttFqMul(a: number, b: number): number { + return byteopsMontgomeryReduce(a * b); +} + +// reduce applies Barrett reduction to all coefficients of a polynomial. +function reduce(r: Array): Array { + for (let i = 0; i < paramsN; i++) { + r[i] = barrett(r[i]); + } + return r; +} + +// barrett computes a Barrett reduction; given +// a integer `a`, returns a integer congruent to +// `a mod Q` in {0,...,Q}. +function barrett(a: number): number { + const v = ((1 << 24) + paramsQ / 2) / paramsQ; + let t = v * a >> 24; + t = t * paramsQ; + return a - t; +} + +// byteopsMontgomeryReduce computes a Montgomery reduction; given +// a 32-bit integer `a`, returns `a * R^-1 mod Q` where `R=2^16`. +function byteopsMontgomeryReduce(a: number): number { + const u = int16(int32(a) * paramsQinv); + let t = u * paramsQ; + t = a - t; + t >>= 16; + return int16(t); +} + +// polyToMont performs the in-place conversion of all coefficients +// of a polynomial from the normal domain to the Montgomery domain. +function polyToMont(r: Array): Array { + // let f = int16(((uint64(1) << 32) >>> 0) % uint64(paramsQ)); + const f = 1353; // if paramsQ changes then this needs to be updated + for (let i = 0; i < paramsN; i++) { + r[i] = byteopsMontgomeryReduce(int32(r[i]) * int32(f)); + } + return r; +} + +// pointwise-multiplies elements of polynomial-vectors +// `a` and `b`, accumulates the results into `r`, and then multiplies by `2^-16`. +function multiply( + a: Array>, + b: Array>, +): Array { + let r = polyBaseMulMontgomery(a[0], b[0]); + let t; + for (let i = 1; i < paramsK; i++) { + t = polyBaseMulMontgomery(a[i], b[i]); + r = add(r, t); + } + return reduce(r); +} + +// polyBaseMulMontgomery performs the multiplication of two polynomials +// in the number-theoretic transform (NTT) domain. +function polyBaseMulMontgomery( + a: Array, + b: Array, +): Array { + let rx, ry; + for (let i = 0; i < paramsN / 4; i++) { + rx = nttBaseMul( + a[4 * i + 0], + a[4 * i + 1], + b[4 * i + 0], + b[4 * i + 1], + nttZetas[64 + i], + ); + ry = nttBaseMul( + a[4 * i + 2], + a[4 * i + 3], + b[4 * i + 2], + b[4 * i + 3], + -nttZetas[64 + i], + ); + a[4 * i + 0] = rx[0]; + a[4 * i + 1] = rx[1]; + a[4 * i + 2] = ry[0]; + a[4 * i + 3] = ry[1]; + } + return a; +} + +// nttBaseMul performs the multiplication of polynomials +// in `Zq[X]/(X^2-zeta)`. Used for multiplication of elements +// in `Rq` in the number-theoretic transformation domain. +function nttBaseMul( + a0: number, + a1: number, + b0: number, + b1: number, + zeta: number, +): Array { + const r = new Array(2); + r[0] = nttFqMul(a1, b1); + r[0] = nttFqMul(r[0], zeta); + r[0] = r[0] + nttFqMul(a0, b0); + r[1] = nttFqMul(a0, b1); + r[1] = r[1] + nttFqMul(a1, b0); + return r; +} + +// adds two polynomials. +function add(a: Array, b: Array): Array { + const c = new Array(384); + for (let i = 0; i < paramsN; i++) { + c[i] = a[i] + b[i]; + } + return c; +} + +// subtracts two polynomials. +function subtract(a: Array, b: Array): Array { + for (let i = 0; i < paramsN; i++) { + a[i] = a[i] - b[i]; + } + return a; +} + +// nttInverse performs an inplace inverse number-theoretic transform (NTT) +// in `Rq` and multiplication by Montgomery factor 2^16. +// The input is in bit-reversed order, the output is in standard order. +function nttInverse(r: Array): Array { + let j = 0; + let k = 0; + let zeta; + let t; + for (let l = 2; l <= 128; l <<= 1) { + for (let start = 0; start < 256; start = j + l) { + zeta = nttZetasInv[k]; + k = k + 1; + for (j = start; j < start + l; j++) { + t = r[j]; + r[j] = barrett(t + r[j + l]); + r[j + l] = t - r[j + l]; + r[j + l] = nttFqMul(zeta, r[j + l]); + } + } + } + for (j = 0; j < 256; j++) { + r[j] = nttFqMul(r[j], nttZetasInv[127]); + } + return r; +} + +// compress1 lossily compresses and serializes a vector of polynomials. +function compress1(u: Array>): Uint8Array { + let rr = 0; + const r = new Uint8Array(960); + const t = new Array(4); + for (let i = 0; i < paramsK; i++) { + for (let j = 0; j < paramsN / 4; j++) { + for (let k = 0; k < 4; k++) { + // parse {0,...,3328} to {0,...,1023} + t[k] = (((u[i][4 * j + k] << 10) + paramsQ / 2) / paramsQ) & + 0b1111111111; + } + // converts 4 12-bit coefficients {0,...,3328} to 5 8-bit bytes {0,...,255} + // 48 bits down to 40 bits per block + r[rr + 0] = byte(t[0] >> 0); + r[rr + 1] = byte((t[0] >> 8) | (t[1] << 2)); + r[rr + 2] = byte((t[1] >> 6) | (t[2] << 4)); + r[rr + 3] = byte((t[2] >> 4) | (t[3] << 6)); + r[rr + 4] = byte(t[3] >> 2); + rr = rr + 5; + } + } + return r; +} + +// compress2 lossily compresses and subsequently serializes a polynomial. +function compress2(v: Array): Uint8Array { + let rr = 0; + const r = new Uint8Array(128); + const t = new Uint8Array(8); + for (let i = 0; i < paramsN / 8; i++) { + for (let j = 0; j < 8; j++) { + t[j] = byte(((v[8 * i + j] << 4) + paramsQ / 2) / paramsQ) & 0b1111; + } + r[rr + 0] = t[0] | (t[1] << 4); + r[rr + 1] = t[2] | (t[3] << 4); + r[rr + 2] = t[4] | (t[5] << 4); + r[rr + 3] = t[6] | (t[7] << 4); + rr = rr + 4; + } + return r; +} + +// decompress1 de-serializes and decompresses a vector of polynomials and +// represents the approximate inverse of compress1. Since compression is lossy, +// the results of decompression will may not match the original vector of polynomials. +function decompress1(a: Uint8Array): Array> { + const r = new Array>(paramsK); + for (let i = 0; i < paramsK; i++) { + r[i] = new Array(384); + } + let aa = 0; + const t = new Array(4); + for (let i = 0; i < paramsK; i++) { + for (let j = 0; j < paramsN / 4; j++) { + t[0] = (uint16(a[aa + 0]) >> 0) | (uint16(a[aa + 1]) << 8); + t[1] = (uint16(a[aa + 1]) >> 2) | (uint16(a[aa + 2]) << 6); + t[2] = (uint16(a[aa + 2]) >> 4) | (uint16(a[aa + 3]) << 4); + t[3] = (uint16(a[aa + 3]) >> 6) | (uint16(a[aa + 4]) << 2); + aa = aa + 5; + for (let k = 0; k < 4; k++) { + r[i][4 * j + k] = int16( + (((uint32(t[k] & 0x3FF) >>> 0) * (uint32(paramsQ) >>> 0) >>> 0) + + 512) >> 10 >>> 0, + ); + } + } + } + return r; +} + +// subtract_q applies the conditional subtraction of q to each coefficient of a polynomial. +// if a is 3329 then convert to 0 +// Returns: a - q if a >= q, else a +function subtract_q(r: Array): Array { + for (let i = 0; i < paramsN; i++) { + r[i] = r[i] - paramsQ; // should result in a negative integer + // push left most signed bit to right most position + // javascript does bitwise operations in signed 32 bit + // add q back again if left most bit was 0 (positive number) + r[i] = r[i] + ((r[i] >> 31) & paramsQ); + } + return r; +} + +// decompress2 de-serializes and subsequently decompresses a polynomial, +// representing the approximate inverse of compress2. +// Note that compression is lossy, and thus decompression will not match the +// original input. +function decompress2(a: Uint8Array): Array { + const r = new Array(384); + let aa = 0; + for (let i = 0; i < paramsN / 2; i++) { + r[2 * i + 0] = int16(((uint16(a[aa] & 15) * uint16(paramsQ)) + 8) >> 4); + r[2 * i + 1] = int16(((uint16(a[aa] >> 4) * uint16(paramsQ)) + 8) >> 4); + aa = aa + 1; + } + return r; +} + +function byte(n: number): number { + return n % 256; +} + +function int16(n: number): number { + const end = -32768; + const start = 32767; + + if (n >= end && n <= start) { + return n; + } + if (n < end) { + n = n + 32769; + n = n % 65536; + return start + n; + } + // if (n > start) { + n = n - 32768; + n = n % 65536; + return end + n; +} + +function uint16(n: number): number { + return n % 65536; +} + +function int32(n: number): number { + const end = -2147483648; + const start = 2147483647; + + if (n >= end && n <= start) { + return n; + } + if (n < end) { + n = n + 2147483649; + n = n % 4294967296; + return start + n; + } + // if (n > start) { + n = n - 2147483648; + n = n % 4294967296; + return end + n; +} + +// any bit operations to be done in uint32 must have >>> 0 +// javascript calculates bitwise in SIGNED 32 bit so you need to convert +function uint32(n: number): number { + return n % 4294967296; +} + +// compares two arrays and returns 1 if they are the same or 0 if not +function compareArray(a: Uint8Array, b: Uint8Array): boolean { + // check array lengths + if (a.length != b.length) { + return false; + } + // check contents + for (let i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; +} diff --git a/src/utils/misc.ts b/src/utils/misc.ts index aa618967a..0f011a971 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -43,6 +43,16 @@ export function i2Osp(n: number, w: number): Uint8Array { return ret; } +/** + * Concatenates two Uint8Arrays. + */ +export function concat(a: Uint8Array, b: Uint8Array): Uint8Array { + const ret = new Uint8Array(a.length + b.length); + ret.set(a, 0); + ret.set(b, a.length); + return ret; +} + /** * Decodes Base64Url-encoded data. */ diff --git a/x/hybridkem-x25519-kyber768/README.md b/x/hybridkem-x25519-kyber768/README.md new file mode 100644 index 000000000..5296b37b9 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/README.md @@ -0,0 +1,241 @@ +

@hpke/hybridkem-x25519-kyber768

+ +
+A TypeScript Hybrid Public Key Encryption (HPKE) module extension for the hybrid post-quantum KEM(X25519, Kyber768) compliant with X25519Kyber768Draft00 hybrid post-quantum KEM for HPKE. Note that this implementation is EXPERIMENTAL and the specification has not been done yet. The kyber implementation included in this module is based on ntontutoveanu/crystals-kyber-javascript. +
+

+ +
+ +Documentation: +[deno.land](https://doc.deno.land/https://deno.land/x/hpke/x/hybridkem-x25519-kyber768/mod.ts) +| +[pages (only for the latest ver.)](https://dajiaji.github.io/hpke-js/hybridkem-x25519-kyber768/docs/) + +
+ +## Index + +- [Installation](#installation) + - [Web Browser](#web-browser) + - [Node.js](#nodejs) + - [Deno](#deno) + - [Cloudflare Workers](#cloudflare-workers) +- [Usage](#usage) +- [Contributing](#contributing) + +## Installation + +### Web Browser + +Followings are how to use with typical CDNs. Other CDNs can be used as well. + +Using esm.sh: + +```html + + + + + +``` + +Using unpkg: + +```html + + +``` + +### Node.js + +Using npm: + +```sh +npm install @hpke/hybridkem-x25519-kyber768 +``` + +Using yarn: + +```sh +yarn add @hpke/hybridkem-x25519-kyber768 +``` + +### Deno + +Using deno.land: + +```js +// use a specific version +import * as hpke from "https://deno.land/x/hpke@1.2.1/core/mod.ts"; +import * as x25519 from "https://deno.land/x/hpke@1.2.1/x/hybridkem-x25519-kyber768/mod.ts"; + +// use the latest stable version +import * as hpke from "https://deno.land/x/hpke/core/mod.ts"; +import * as x25519 from "https://deno.land/x/hpke/x/hybridkem-x25519-kyber768/mod.ts"; +``` + +### Cloudflare Workers + +```sh +git clone git@github.com:dajiaji/hpke-js.git +cd hpke-js/x/hybridkem-x25519-kyber768 +npm install -g esbuild +deno task dnt +deno task minify > $YOUR_SRC_PATH/hpke-hybridkem-x25519-kyber768.js +``` + +## Usage + +This section shows some typical usage examples. + +### Browsers + +```html + + + + + + + +``` + +### Node.js + +```js +import { Aes128Gcm, CipherSuite, HkdfSha256 } from "@hpke/core"; +import { HybridkemX25519Kyber768 } from "@hpke/hybridkem-x25519-kyber768"; +// const { HybridkemX25519Kyber768 } = require("@hpke/hybridkem-x25519-kyber768"); + +async function doHpke() { + // setup + const suite = new CipherSuite({ + kem: new HybridkemX25519Kyber768(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); + + // decrypt + try { + const pt = await recipient.open(ct); + + console.log("decrypted: ", new TextDecoder().decode(pt)); + // decrypted: my-secret-message + } catch (err) { + console.log("failed to decrypt."); + } +} + +doHpke(); +``` + +### Deno + +```js +import { Aes128Gcm, CipherSuite, HkdfSha256 } from "https://deno.land/x/hpke@1.2.1/core/mod.ts"; +import { HybridkemX25519Kyber768 } from "https://deno.land/x/hpke@1.2.1/x/hybridkem-x25519-kyber768/mod.ts"; + +async function doHpke() { + // setup + const suite = new CipherSuite({ + kem: new HybridkemX25519Kyber768(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); + + try { + // decrypt + const pt = await recipient.open(ct); + + console.log("decrypted: ", new TextDecoder().decode(pt)); + // decrypted: my-secret-message + } catch (_err: unknown) { + console.log("failed to decrypt."); + } +} + +doHpke(); +``` + +## Contributing + +We welcome all kind of contributions, filing issues, suggesting new features or +sending PRs. diff --git a/x/hybridkem-x25519-kyber768/deno.json b/x/hybridkem-x25519-kyber768/deno.json new file mode 100644 index 000000000..ae31e3ca3 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/deno.json @@ -0,0 +1,36 @@ +{ + "imports": { + "testing/": "https://deno.land/std@0.198.0/testing/", + "dnt": "https://deno.land/x/dnt@0.38.0/mod.ts" + }, + "fmt": { + "include": [ + "README.md", + "deno.json", + "dnt.ts", + "mod.ts", + "test/" + ], + "exclude": [ + "**/*/hpke*.js", + "test/runtimes/bun", + "test/runtimes/browsers/node_modules", + "test/runtimes/cloudflare" + ] + }, + "lint": { + "include": ["mod.ts", "test/"], + "exclude": [ + "**/*/hpke*.js", + "test/runtimes/bun", + "test/runtimes/browsers/node_modules", + "test/runtimes/cloudflare" + ] + }, + "tasks": { + "test": "deno fmt && deno lint && deno test test -A --fail-fast --doc --coverage=coverage --parallel --allow-read", + "cov": "deno coverage ./coverage --lcov --exclude='test'", + "dnt": "deno run -A dnt.ts $(git describe --tags $(git rev-list --tags --max-count=1))", + "minify": "esbuild npm/esm/x/hybridkem-x25519-kyber768/mod.js --bundle --format=esm --minify" + } +} diff --git a/x/hybridkem-x25519-kyber768/dnt.ts b/x/hybridkem-x25519-kyber768/dnt.ts new file mode 100644 index 000000000..c3c7466b4 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/dnt.ts @@ -0,0 +1,63 @@ +import { build, emptyDir } from "dnt"; + +await emptyDir("./npm"); + +await build({ + entryPoints: ["./mod.ts"], + outDir: "./npm", + typeCheck: "both", + test: true, + declaration: true, + scriptModule: "umd", + importMap: "./deno.json", + compilerOptions: { + lib: ["es2022", "dom"], + }, + shims: { + deno: "dev", + }, + package: { + name: "@hpke/hybridkem-x25519-kyber768", + version: Deno.args[0], + description: + "A Hybrid Public Key Encryption (HPKE) extension module for a hybrid qost-quantum KEM which is the parallel combination of DHKEM(X25519, HKDF-SHA256) and Kyber768 (EXPERIMENTAL)", + repository: { + type: "git", + url: "git+https://github.com/dajiaji/hpke-js.git", + }, + homepage: "https://github.com/dajiaji/hpke-js#readme", + license: "MIT", + module: "./esm/x/hybridkem-x25519-kyber768/mod.js", + main: "./script/x/hybridkem-x25519-kyber768/mod.js", + types: "./esm/x/hybridkem-x25519-kyber768/mod.d.ts", + sideEffects: false, + exports: { + ".": { + "import": "./esm/x/hybridkem-x25519-kyber768/mod.js", + "require": "./script/x/hybridkem-x25519-kyber768/mod.js", + }, + "./package.json": "./package.json", + }, + keywords: [ + "hpke", + "rfc9180", + "kem", + "kyber", + "x25519", + "post-quantum", + "security", + "encryption", + ], + engines: { + "node": ">=16.0.0", + }, + author: "Ajitomi Daisuke", + bugs: { + url: "https://github.com/dajiaji/hpke-js/issues", + }, + }, +}); + +// post build steps +Deno.copyFileSync("../../LICENSE", "npm/LICENSE"); +Deno.copyFileSync("README.md", "npm/README.md"); diff --git a/x/hybridkem-x25519-kyber768/mod.ts b/x/hybridkem-x25519-kyber768/mod.ts new file mode 100644 index 000000000..593c6b494 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/mod.ts @@ -0,0 +1 @@ +export { HybridkemX25519Kyber768 } from "../../src/kems/hybridkemX25519Kyber768.ts"; diff --git a/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts b/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts new file mode 100644 index 000000000..6f3630750 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts @@ -0,0 +1,173 @@ +// import { assertEquals, assertRejects } from "testing/asserts.ts"; +import { assertEquals } from "testing/asserts.ts"; +import { describe, it } from "testing/bdd.ts"; + +import { + AeadId, + Aes128Gcm, + CipherSuite, + HkdfSha256, + KdfId, + KemId, +} from "../../../core/mod.ts"; +import { Kyber768 } from "../../../src/kems/pqkemPrimitives/kyber768.ts"; +import { DhkemX25519HkdfSha256 } from "../../../src/kems/dhkemX25519.ts"; +import { hexToBytes } from "../../../test/utils.ts"; +import { HybridkemX25519Kyber768 } from "../mod.ts"; + +describe("constructor", () => { + describe("with HybridkemX25519Kyber768", () => { + it("should have a correct ciphersuite", () => { + const suite: CipherSuite = new CipherSuite({ + kem: new HybridkemX25519Kyber768(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + assertEquals(suite.kem.secretSize, 64); + assertEquals(suite.kem.encSize, 1120); + assertEquals(suite.kem.publicKeySize, 1216); + assertEquals(suite.kem.privateKeySize, 2432); + + // assert + assertEquals(suite.kem.id, KemId.HybridkemX25519Kyber768); + assertEquals(suite.kem.id, 0x0030); + assertEquals(suite.kdf.id, KdfId.HkdfSha256); + assertEquals(suite.kdf.id, 0x0001); + assertEquals(suite.aead.id, AeadId.Aes128Gcm); + assertEquals(suite.aead.id, 0x0001); + }); + }); +}); + +describe("README examples", () => { + describe("HybridkemX25519Kyber768/HkdfShar256/Aes128Gcm", () => { + it("should work normally", async () => { + const suite = new CipherSuite({ + kem: new HybridkemX25519Kyber768(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + const rkp = await suite.kem.generateKeyPair(); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + assertEquals(sender.enc.byteLength, suite.kem.encSize); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); +}); + +const testVector = { + mode: 0, + kem_id: 48, + kdf_id: 1, + aead_id: 1, + info: "486561722068656172", + ikmR: "3cb1eea988004b93103cfb0aeefd2a686e01fa4a58e8a3639ca8a1e3f9ae57e2", + pkRm: + "a3aa882fee0de0059cec0569c8e1b4872fb6cb4d82361b72ee1148dc7ddc0c2b210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5", + skRm: + "cf61f1a7b05c83f9c2a4b27dc0e9bdbf4e52ba1bbd906cb3776ac12268a9f4d0c348342d192f0458ab53d19c1dc135d11b48978c878bca6d7d1bc91428259e43aadc9700b76aa9aa66a65db91a77d72513e40697226557b53400bb6752fb4e11a5ba2fe12644698a48c9948ec121cc9c9ce7384c65f798012c9df8f5ac0cd371d7d19d9a24c30cb0909c665e43c89328735fe95a62653352fad3cfe6330b436a4f72c9ac9323babd912cf5970222eb0dd178c810bcc79beba0813039ec4333c33b13d4cc5183b6b14dc09cb9604d46242353a1a1df82999e4a4929f28f498c330d552ac64156cf123cceebbccf81b2fd86218f2a9112040943a359d7a858cd641467e54b25f03d66b9a150fbcf3f19bbd1791abec47269b2a72f4083a79c2559fbb6d0500208a78baa7392874443c39c38577b4c40db6220ca5c84b148ffb344164c723df1c0fb37ae52c0854bea023e2a45efaa8869c924ecf360008607e7079187978fdac30e8b76a3110349de272b25f5490dc87e3d8caf59a5a51a57230ea702dfda0f5d2c0fe94442254834b0f6aa71852601a8c5b7b211f108c1de2b1092e9b89df4a9feea882aa00ab97235940924e8a81d8f83597f72383f7a1b99c3c8481953be917fef0b44e32b0aa1f862eedc8d0d94030ae92e73097cde1b34de9b8279293322e0b5f9564395cb4998810818544ad2025018c40debf0b97bca2f1d861f8d5b51a2a84f35503cb37112b280ad0a4c99a2eb9c43300ce7c66eb89cb4443a44edc40869b8c2d90c5d484554557c408da7b46752bec14876815334b783207f60943d1738b5183d64394e27bb8f1dbb6ed9c58aa338171967bf5a613e9194c13395573615cee02012438a68aa104afd56a943d05caefc7f20a0104e2cced2a5191a1a68fe431920e1844a8154fc42a73d70c82f26846ec332fda50c340c1c5037965daaccd3cacfcab3c85a7516d712890fd6a2b1f5cb7c745cf1798dc0a49ed75717630c78d56bb8db272a85a009b2685ca9f4840c948226d224de1a0385565e569b8901c4508ae7b9214b88b6c2ac63807710d85e593a01ba20541cd03fa8364b4cb79f110745b30818521a7d0b6015a20483dde33188e94fc4aa224558bd53d384a9f6916964bbef0b0770b11e7b4117f41639bbe0c9ce119c8f8aab451608c2f06a8cd85f37519b7e3c1f9f07a6449059a972260cf80c23e52fe1b559e11c723b2618752672bfab67305358e7f048960475f1c720d8ba5fe4883981065c462c5062757bddd2666de67265990d0053229693a8bfd8811c84494853095c875639dcbcfcc02785910e35643f5bb4b0aa59af7a86ae94dcc01f952eb1d151c4ba1aa4da02c100b461904229f7b11aac35d307ab187255baa32eed32b3b262aaf2db6019089ad4250079280a0efb109ab27a364135ac3067ace5c82dea1fafb04dfedba9fabc196832878eb7b4314556e8aa8210e2c72959723e23176b703d4db42aabba62229790f6a743a2ec3c43dc8dbe0b4c36dc2323ec0ef21c116941b43bb12763460eed032a7a039185e36dcbf69d88f645e6728d3ba79dae0a25ddd4c3a8bba8334aa8fb6658a9dca99a8cc6362745d6080b0fd8af6af71e9f752d7b763035ec40c0fc98326081ea4c36cdf992e73a16719b9fb7c06e6c1bb7210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5137b60651b30bf805da1597faef1bc8b2645cda273144c4af1d13eaa2ad9101c7b58b14601aff81754afc776f8b7f7b9324d420b66706b96ea7f99f8fa11bed3", + ier: + "35b8cc873c23dc62b8d260169afa2f75ab916a58d974918835d25e6a435085b2badfd6dfaac359a5efbb7bcc4b59d538df9a04302e10c8bc1cbf1a0b3a5120ea", + enc: + "1d06980e46fd3842db6b87226231eedd2cc9684ee98a1d9d902bd9300e2c4d41b64fba47a50fe32dd0df3b0a75801c11022cd98a6ff5a83a8472ade82bdd6f1e8a65a94a88523ada0d8275165f707f1067a6a576e54525d9141e95223f5713456bda7ec5eb558adfc6b7f0d80de46222579a3274e45ab43fad14f7e9855a872d2716e8dc78d4c12027bef3184904476c8961552fd031361358f2d9deae8ad98194047a14222947612972574c57514266e9e67a3b6dd89972cc8a0882be7474f4923549dfcd944dcbe58b088079aa8b70c8f291cb4e45066bad4a832ccd8f40e51861f7a25b6a2358842f1bbe8108a6a6f0ae93153a2e7f9f53e180a90532531a632367b81bf08ed97effcf0140dd0e92cc438f6be7e6f3d97a9f7787f7e3981f971617f0bfb618caa7db1e453f33a386c3863b16d462229c41f4946b49e4e49c27e0f35d77e21304b6ad238a55a51e9e370dd39e713d626044fb970bb7c2af7d7b9cb9004394741a0ea2de592816359006f24abdfc2aa890720b00b2f7b8bb240120f22bdb84f9fc5c8fdc7ca7047ae633868c184c4d75e9e107eb9c6d8fe879415926457d818bc31e88b87a5881584a5650859e88b06faa2cfe1bde95dfa344af14f214cedecde4d89c87334c33e2d7ee3ab40d5df396cf0ff5a99588e0dcd205f1d876b380b963f5baccc0baeae569892a8d252f5eeeb7c751f663eb906ac99a165656224281add3ab271ff4f406b6932cbf1afff62109794f52ff3e723f5cdd706e3715d1d2d421bdac73fa047b5d9761569534fb2dd57b86a608f79db7d4ab99847490e76eaf0c683bdc54d12f2f2664a79de6a2f25bec3f43584f98ec41ad3fb19ba5ba936c3c893e9c0994b412ba3d07329086c20b04e1cd1d9b4f24a82f8c1f7b5db58b4056a4b4e27b60c957f5af8081bffab98d8455cab97e35042ed636c995931fd304b3d02fcf545df360cc421be64adc3d7a121ea75ab3440a9eba74fba1c5b40bdb66b54583ff2f76304ccaeae99ed94fb332d30d771fe0e45acb9e966f497b1629f5a5df15cea507d2fd1aa045a171e84bec932e4049639477f16fb9afdd107668f9b3531c3c7eb1d67753ac652c575b526e6f2965f1e4500e99f38ae1d34bce151a68e278f14405ad76f580b549d025b03be98b6a737f10238b9f84f1694173544ba2c97f811a17485129a146084bc5382e2086aaf51b11a4918bdb5485bf28a9be2d2c9d69468268fa04fa071c39942b43a0caf561278cfc1b47781fa9ef559f86b2dad703141b78b7ddb35c9c9ff4c1134580da26367dbf3db7eaf039dfbae238959c4cf55d40d78a2c5597ba038f2be5f994d60c79e8a92121fb0488eef9690d550ef9fa40b1774221aac8c8c1dcf97faa07c28e840feb9daf0bf3bed277a6e10a33490c0bee7e5fa318638f5b80a2272700e591ffc14985d0ed19876725c2bec9356b45ca96d295e30bce86effc626a2bd7839af05ae373801af510cfb378ce42088607909c91ceb4a90e4d7b2b6288b9cdfa262570ffda8692b58f0b05a7c7899a717a3a97b6e64489f56323b000793f807ca75ca991", + shared_secret: + "1368d71518fadbe42fb75fbd356e016b0aaad6b4d3d91ce7f207073e4fb08c537217aba238aea92a7f855820518a8342b3a31f82ebbcdb479f33ad82bdcdc953", + key_schedule_context: + "009f749a195d1c8b3eaa8d5c3f571dc7231aafbbc0405e4b484738352667c484867584e32e844cdf74d17b4ee224cc521bbc8bed221f21f34f8ccc9842772686cb", + secret: "95f863934be4d0ef683770c7bd385839d19e525b467a332f47ae715c54183e1d", + key: "6bb5532badb078ce8f326daa6cfaef84", + base_nonce: "ff2b9a604a84754614e9e772", + exporter_secret: + "fb6ca36cfb7881cf11dbcb8fde201f698f80d0b941b642bc0a6a3101c97b7fad", +}; + +describe("test-vectors", () => { + describe("Base/Hybridkem/X25519Kyber768/HkdfSha256/Aes128Gcm", () => { + it("should work normally", async () => { + const suite = new CipherSuite({ + kem: new HybridkemX25519Kyber768(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const info = hexToBytes(testVector.info); + const ikmR = hexToBytes(testVector.ikmR); + const pkRm = hexToBytes(testVector.pkRm); + const skRm = hexToBytes(testVector.skRm); + + // DeriveKeyPair + const rkp = await suite.kem.deriveKeyPair(ikmR); + const pkR = new Uint8Array( + await suite.kem.serializePublicKey(rkp.publicKey), + ); + const skR = new Uint8Array( + await suite.kem.serializePrivateKey(rkp.privateKey), + ); + assertEquals(skR, skRm); + assertEquals(pkR, pkRm); + + const enc = hexToBytes(testVector.enc); + const sharedSecret = hexToBytes(testVector.shared_secret); + const ier = hexToBytes(testVector.ier); + + // encap + const dhkem = new DhkemX25519HkdfSha256(); + const ekpA = await dhkem.deriveKeyPair(ier.subarray(0, 32)); + const pkRA = await dhkem.deserializePublicKey(pkR.subarray(0, 32)); + const senderA = await dhkem.encap({ + info: info, + recipientPublicKey: pkRA, + nonEphemeralKeyPair: ekpA, + }); + assertEquals(new Uint8Array(senderA.enc), enc.subarray(0, 32)); + assertEquals( + new Uint8Array(senderA.sharedSecret), + sharedSecret.subarray(0, 32), + ); + const kyber = new Kyber768(); + const res = kyber.encap(pkR.subarray(32), ier.subarray(32)); + assertEquals(res[0], enc.subarray(32)); + assertEquals(res[1], sharedSecret.subarray(32)); + + // decap + const ss = kyber.decap(enc.subarray(32), skR.subarray(32)); + assertEquals(new Uint8Array(ss), sharedSecret.subarray(32)); + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // }); + + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp, + // enc: sender.enc, + // }); + // assertEquals(sender.enc.byteLength, suite.kem.encSize); + + // // encrypt + // const ct = await sender.seal( + // new TextEncoder().encode("my-secret-message"), + // ); + + // // decrypt + // const pt = await recipient.open(ct); + + // // assert + // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); +}); From 9f85269b396fc4ac0a2d209e83a126ebd37032e9 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Fri, 18 Aug 2023 19:17:32 +0900 Subject: [PATCH 04/14] Extend an rename a parameter for testing 'nonEphemeralKeyPair' to 'ekm'. --- src/interfaces/senderContextParams.ts | 2 +- src/kems/dhkem.ts | 13 +- src/kems/hybridkem.ts | 13 +- src/kems/kemKyber768.ts | 17 +- test/conformanceTester.ts | 2 +- test/testVector.ts | 1 + .../test/hybridkemX25519Kyber768.test.ts | 198 +++++++++--------- .../test/vectors/test-vectors.json | 57 +++++ 8 files changed, 190 insertions(+), 113 deletions(-) create mode 100644 x/hybridkem-x25519-kyber768/test/vectors/test-vectors.json diff --git a/src/interfaces/senderContextParams.ts b/src/interfaces/senderContextParams.ts index 89325c264..0c275d105 100644 --- a/src/interfaces/senderContextParams.ts +++ b/src/interfaces/senderContextParams.ts @@ -11,5 +11,5 @@ export interface SenderContextParams extends KeyScheduleParams { senderKey?: CryptoKey | CryptoKeyPair; /** DO NOT USE. FOR DEBUGGING/TESTING PURPOSES ONLY. */ - nonEphemeralKeyPair?: CryptoKeyPair; + ekm?: CryptoKeyPair | ArrayBuffer; } diff --git a/src/kems/dhkem.ts b/src/kems/dhkem.ts index ff7eab389..bb6f9232a 100644 --- a/src/kems/dhkem.ts +++ b/src/kems/dhkem.ts @@ -87,9 +87,16 @@ export class Dhkem implements KemInterface { public async encap( params: SenderContextParams, ): Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }> { - const ke = params.nonEphemeralKeyPair === undefined - ? await this.generateKeyPair() - : params.nonEphemeralKeyPair; + let ke: CryptoKeyPair; + if (params.ekm === undefined) { + ke = await this.generateKeyPair(); + } else if (isCryptoKeyPair(params.ekm)) { + // params.ekm is only used for testing. + ke = params.ekm as CryptoKeyPair; + } else { + // params.ekm is only used for testing. + ke = await this.deriveKeyPair(params.ekm as ArrayBuffer); + } const enc = await this._prim.serializePublicKey(ke.publicKey); const pkrm = await this._prim.serializePublicKey( params.recipientPublicKey, diff --git a/src/kems/hybridkem.ts b/src/kems/hybridkem.ts index 988273250..bf9c4f18a 100644 --- a/src/kems/hybridkem.ts +++ b/src/kems/hybridkem.ts @@ -144,6 +144,15 @@ export class Hybridkem implements KemInterface { public async encap( params: SenderContextParams, ): Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }> { + let ekmA: ArrayBuffer | undefined = undefined; + let ekmB: ArrayBuffer | undefined = undefined; + if (params.ekm !== undefined && !isCryptoKeyPair(params.ekm)) { + if (params.ekm.byteLength !== 64) { + throw new InvalidParamError("ekm must be 32 bytes in length"); + } + ekmA = params.ekm.slice(0, 32); + ekmB = params.ekm.slice(32); + } const pkR = new Uint8Array( await this.serializePublicKey(params.recipientPublicKey), ); @@ -153,8 +162,8 @@ export class Hybridkem implements KemInterface { const pkRB = await this._b.deserializePublicKey( pkR.slice(this._a.publicKeySize), ); - const resA = await this._a.encap({ recipientPublicKey: pkRA }); - const resB = await this._b.encap({ recipientPublicKey: pkRB }); + const resA = await this._a.encap({ recipientPublicKey: pkRA, ekm: ekmA }); + const resB = await this._b.encap({ recipientPublicKey: pkRB, ekm: ekmB }); return { sharedSecret: concat( new Uint8Array(resA.sharedSecret), diff --git a/src/kems/kemKyber768.ts b/src/kems/kemKyber768.ts index 0d85b98eb..4710ea8ad 100644 --- a/src/kems/kemKyber768.ts +++ b/src/kems/kemKyber768.ts @@ -110,16 +110,19 @@ export class KemKyber768 implements KemInterface { public async encap( params: SenderContextParams, ): Promise<{ sharedSecret: ArrayBuffer; enc: ArrayBuffer }> { + // params.ekm is only used for testing + let ikm: Uint8Array | undefined = undefined; + if (params.ekm !== undefined && !isCryptoKeyPair(params)) { + if ((params.ekm as ArrayBuffer).byteLength !== 32) { + throw new InvalidParamError("ekm must be 32 bytes in length"); + } + ikm = new Uint8Array(params.ekm as ArrayBuffer); + } const pkR = new Uint8Array( await this.serializePublicKey(params.recipientPublicKey), ); try { - // const res = kyber.Encrypt768(pkR); - // return { - // sharedSecret: new Uint8Array(res[1]), - // enc: new Uint8Array(res[0]), - // }; - const res = this._prim.encap(pkR); + const res = this._prim.encap(pkR, ikm); return { sharedSecret: res[1], enc: res[0] }; } catch (e: unknown) { throw new EncapError(e); @@ -132,8 +135,6 @@ export class KemKyber768 implements KemInterface { : params.recipientKey; const serializedSkR = new Uint8Array(await this.serializePrivateKey(skR)); try { - // const res = kyber.Decrypt768(new Uint8Array(params.enc), serializedSkR); - // return new Uint8Array(res); return this._prim.decap(new Uint8Array(params.enc), serializedSkR); } catch (e: unknown) { throw new DecapError(e); diff --git a/test/conformanceTester.ts b/test/conformanceTester.ts index e85450854..66e00657e 100644 --- a/test/conformanceTester.ts +++ b/test/conformanceTester.ts @@ -92,7 +92,7 @@ export class ConformanceTester { psk: psk, recipientPublicKey: rkp.publicKey, senderKey: skp, - nonEphemeralKeyPair: ekp, // FOR DEBUGGING/TESTING PURPOSES ONLY. + ekm: ekp, // FOR DEBUGGING/TESTING PURPOSES ONLY. }); assertEquals(new Uint8Array(sender.enc), enc); diff --git a/test/testVector.ts b/test/testVector.ts index 7882ff290..248906e82 100644 --- a/test/testVector.ts +++ b/test/testVector.ts @@ -29,6 +29,7 @@ export interface TestVector { pkRm: string; pkSm?: string; pkEm: string; + ier: string; enc: string; shared_secret: string; key_schedule_context: string; diff --git a/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts b/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts index 6f3630750..21b2efdb1 100644 --- a/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts +++ b/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts @@ -1,6 +1,8 @@ // import { assertEquals, assertRejects } from "testing/asserts.ts"; import { assertEquals } from "testing/asserts.ts"; -import { describe, it } from "testing/bdd.ts"; +import { afterAll, beforeAll, describe, it } from "testing/bdd.ts"; + +import type { TestVector } from "../../../test/testVector.ts"; import { AeadId, @@ -9,10 +11,11 @@ import { HkdfSha256, KdfId, KemId, + PreSharedKey, } from "../../../core/mod.ts"; -import { Kyber768 } from "../../../src/kems/pqkemPrimitives/kyber768.ts"; -import { DhkemX25519HkdfSha256 } from "../../../src/kems/dhkemX25519.ts"; -import { hexToBytes } from "../../../test/utils.ts"; +// import { Kyber768 } from "../../../src/kems/pqkemPrimitives/kyber768.ts"; +// import { DhkemX25519HkdfSha256 } from "../../../src/kems/dhkemX25519.ts"; +import { hexToBytes, testVectorPath } from "../../../test/utils.ts"; import { HybridkemX25519Kyber768 } from "../mod.ts"; describe("constructor", () => { @@ -71,103 +74,102 @@ describe("README examples", () => { }); }); -const testVector = { - mode: 0, - kem_id: 48, - kdf_id: 1, - aead_id: 1, - info: "486561722068656172", - ikmR: "3cb1eea988004b93103cfb0aeefd2a686e01fa4a58e8a3639ca8a1e3f9ae57e2", - pkRm: - "a3aa882fee0de0059cec0569c8e1b4872fb6cb4d82361b72ee1148dc7ddc0c2b210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5", - skRm: - "cf61f1a7b05c83f9c2a4b27dc0e9bdbf4e52ba1bbd906cb3776ac12268a9f4d0c348342d192f0458ab53d19c1dc135d11b48978c878bca6d7d1bc91428259e43aadc9700b76aa9aa66a65db91a77d72513e40697226557b53400bb6752fb4e11a5ba2fe12644698a48c9948ec121cc9c9ce7384c65f798012c9df8f5ac0cd371d7d19d9a24c30cb0909c665e43c89328735fe95a62653352fad3cfe6330b436a4f72c9ac9323babd912cf5970222eb0dd178c810bcc79beba0813039ec4333c33b13d4cc5183b6b14dc09cb9604d46242353a1a1df82999e4a4929f28f498c330d552ac64156cf123cceebbccf81b2fd86218f2a9112040943a359d7a858cd641467e54b25f03d66b9a150fbcf3f19bbd1791abec47269b2a72f4083a79c2559fbb6d0500208a78baa7392874443c39c38577b4c40db6220ca5c84b148ffb344164c723df1c0fb37ae52c0854bea023e2a45efaa8869c924ecf360008607e7079187978fdac30e8b76a3110349de272b25f5490dc87e3d8caf59a5a51a57230ea702dfda0f5d2c0fe94442254834b0f6aa71852601a8c5b7b211f108c1de2b1092e9b89df4a9feea882aa00ab97235940924e8a81d8f83597f72383f7a1b99c3c8481953be917fef0b44e32b0aa1f862eedc8d0d94030ae92e73097cde1b34de9b8279293322e0b5f9564395cb4998810818544ad2025018c40debf0b97bca2f1d861f8d5b51a2a84f35503cb37112b280ad0a4c99a2eb9c43300ce7c66eb89cb4443a44edc40869b8c2d90c5d484554557c408da7b46752bec14876815334b783207f60943d1738b5183d64394e27bb8f1dbb6ed9c58aa338171967bf5a613e9194c13395573615cee02012438a68aa104afd56a943d05caefc7f20a0104e2cced2a5191a1a68fe431920e1844a8154fc42a73d70c82f26846ec332fda50c340c1c5037965daaccd3cacfcab3c85a7516d712890fd6a2b1f5cb7c745cf1798dc0a49ed75717630c78d56bb8db272a85a009b2685ca9f4840c948226d224de1a0385565e569b8901c4508ae7b9214b88b6c2ac63807710d85e593a01ba20541cd03fa8364b4cb79f110745b30818521a7d0b6015a20483dde33188e94fc4aa224558bd53d384a9f6916964bbef0b0770b11e7b4117f41639bbe0c9ce119c8f8aab451608c2f06a8cd85f37519b7e3c1f9f07a6449059a972260cf80c23e52fe1b559e11c723b2618752672bfab67305358e7f048960475f1c720d8ba5fe4883981065c462c5062757bddd2666de67265990d0053229693a8bfd8811c84494853095c875639dcbcfcc02785910e35643f5bb4b0aa59af7a86ae94dcc01f952eb1d151c4ba1aa4da02c100b461904229f7b11aac35d307ab187255baa32eed32b3b262aaf2db6019089ad4250079280a0efb109ab27a364135ac3067ace5c82dea1fafb04dfedba9fabc196832878eb7b4314556e8aa8210e2c72959723e23176b703d4db42aabba62229790f6a743a2ec3c43dc8dbe0b4c36dc2323ec0ef21c116941b43bb12763460eed032a7a039185e36dcbf69d88f645e6728d3ba79dae0a25ddd4c3a8bba8334aa8fb6658a9dca99a8cc6362745d6080b0fd8af6af71e9f752d7b763035ec40c0fc98326081ea4c36cdf992e73a16719b9fb7c06e6c1bb7210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5137b60651b30bf805da1597faef1bc8b2645cda273144c4af1d13eaa2ad9101c7b58b14601aff81754afc776f8b7f7b9324d420b66706b96ea7f99f8fa11bed3", - ier: - "35b8cc873c23dc62b8d260169afa2f75ab916a58d974918835d25e6a435085b2badfd6dfaac359a5efbb7bcc4b59d538df9a04302e10c8bc1cbf1a0b3a5120ea", - enc: - "1d06980e46fd3842db6b87226231eedd2cc9684ee98a1d9d902bd9300e2c4d41b64fba47a50fe32dd0df3b0a75801c11022cd98a6ff5a83a8472ade82bdd6f1e8a65a94a88523ada0d8275165f707f1067a6a576e54525d9141e95223f5713456bda7ec5eb558adfc6b7f0d80de46222579a3274e45ab43fad14f7e9855a872d2716e8dc78d4c12027bef3184904476c8961552fd031361358f2d9deae8ad98194047a14222947612972574c57514266e9e67a3b6dd89972cc8a0882be7474f4923549dfcd944dcbe58b088079aa8b70c8f291cb4e45066bad4a832ccd8f40e51861f7a25b6a2358842f1bbe8108a6a6f0ae93153a2e7f9f53e180a90532531a632367b81bf08ed97effcf0140dd0e92cc438f6be7e6f3d97a9f7787f7e3981f971617f0bfb618caa7db1e453f33a386c3863b16d462229c41f4946b49e4e49c27e0f35d77e21304b6ad238a55a51e9e370dd39e713d626044fb970bb7c2af7d7b9cb9004394741a0ea2de592816359006f24abdfc2aa890720b00b2f7b8bb240120f22bdb84f9fc5c8fdc7ca7047ae633868c184c4d75e9e107eb9c6d8fe879415926457d818bc31e88b87a5881584a5650859e88b06faa2cfe1bde95dfa344af14f214cedecde4d89c87334c33e2d7ee3ab40d5df396cf0ff5a99588e0dcd205f1d876b380b963f5baccc0baeae569892a8d252f5eeeb7c751f663eb906ac99a165656224281add3ab271ff4f406b6932cbf1afff62109794f52ff3e723f5cdd706e3715d1d2d421bdac73fa047b5d9761569534fb2dd57b86a608f79db7d4ab99847490e76eaf0c683bdc54d12f2f2664a79de6a2f25bec3f43584f98ec41ad3fb19ba5ba936c3c893e9c0994b412ba3d07329086c20b04e1cd1d9b4f24a82f8c1f7b5db58b4056a4b4e27b60c957f5af8081bffab98d8455cab97e35042ed636c995931fd304b3d02fcf545df360cc421be64adc3d7a121ea75ab3440a9eba74fba1c5b40bdb66b54583ff2f76304ccaeae99ed94fb332d30d771fe0e45acb9e966f497b1629f5a5df15cea507d2fd1aa045a171e84bec932e4049639477f16fb9afdd107668f9b3531c3c7eb1d67753ac652c575b526e6f2965f1e4500e99f38ae1d34bce151a68e278f14405ad76f580b549d025b03be98b6a737f10238b9f84f1694173544ba2c97f811a17485129a146084bc5382e2086aaf51b11a4918bdb5485bf28a9be2d2c9d69468268fa04fa071c39942b43a0caf561278cfc1b47781fa9ef559f86b2dad703141b78b7ddb35c9c9ff4c1134580da26367dbf3db7eaf039dfbae238959c4cf55d40d78a2c5597ba038f2be5f994d60c79e8a92121fb0488eef9690d550ef9fa40b1774221aac8c8c1dcf97faa07c28e840feb9daf0bf3bed277a6e10a33490c0bee7e5fa318638f5b80a2272700e591ffc14985d0ed19876725c2bec9356b45ca96d295e30bce86effc626a2bd7839af05ae373801af510cfb378ce42088607909c91ceb4a90e4d7b2b6288b9cdfa262570ffda8692b58f0b05a7c7899a717a3a97b6e64489f56323b000793f807ca75ca991", - shared_secret: - "1368d71518fadbe42fb75fbd356e016b0aaad6b4d3d91ce7f207073e4fb08c537217aba238aea92a7f855820518a8342b3a31f82ebbcdb479f33ad82bdcdc953", - key_schedule_context: - "009f749a195d1c8b3eaa8d5c3f571dc7231aafbbc0405e4b484738352667c484867584e32e844cdf74d17b4ee224cc521bbc8bed221f21f34f8ccc9842772686cb", - secret: "95f863934be4d0ef683770c7bd385839d19e525b467a332f47ae715c54183e1d", - key: "6bb5532badb078ce8f326daa6cfaef84", - base_nonce: "ff2b9a604a84754614e9e772", - exporter_secret: - "fb6ca36cfb7881cf11dbcb8fde201f698f80d0b941b642bc0a6a3101c97b7fad", -}; - describe("test-vectors", () => { - describe("Base/Hybridkem/X25519Kyber768/HkdfSha256/Aes128Gcm", () => { - it("should work normally", async () => { - const suite = new CipherSuite({ - kem: new HybridkemX25519Kyber768(), - kdf: new HkdfSha256(), - aead: new Aes128Gcm(), - }); + let count: number; + let testVectors: TestVector[]; + + beforeAll(async () => { + count = 0; + testVectors = JSON.parse( + await Deno.readTextFile(testVectorPath() + "/test-vectors.json"), + ); + }); - const info = hexToBytes(testVector.info); - const ikmR = hexToBytes(testVector.ikmR); - const pkRm = hexToBytes(testVector.pkRm); - const skRm = hexToBytes(testVector.skRm); + afterAll(() => { + console.log(`passed/total: ${count}/${testVectors.length}`); + }); - // DeriveKeyPair - const rkp = await suite.kem.deriveKeyPair(ikmR); - const pkR = new Uint8Array( - await suite.kem.serializePublicKey(rkp.publicKey), - ); - const skR = new Uint8Array( - await suite.kem.serializePrivateKey(rkp.privateKey), - ); - assertEquals(skR, skRm); - assertEquals(pkR, pkRm); - - const enc = hexToBytes(testVector.enc); - const sharedSecret = hexToBytes(testVector.shared_secret); - const ier = hexToBytes(testVector.ier); - - // encap - const dhkem = new DhkemX25519HkdfSha256(); - const ekpA = await dhkem.deriveKeyPair(ier.subarray(0, 32)); - const pkRA = await dhkem.deserializePublicKey(pkR.subarray(0, 32)); - const senderA = await dhkem.encap({ - info: info, - recipientPublicKey: pkRA, - nonEphemeralKeyPair: ekpA, - }); - assertEquals(new Uint8Array(senderA.enc), enc.subarray(0, 32)); - assertEquals( - new Uint8Array(senderA.sharedSecret), - sharedSecret.subarray(0, 32), - ); - const kyber = new Kyber768(); - const res = kyber.encap(pkR.subarray(32), ier.subarray(32)); - assertEquals(res[0], enc.subarray(32)); - assertEquals(res[1], sharedSecret.subarray(32)); - - // decap - const ss = kyber.decap(enc.subarray(32), skR.subarray(32)); - assertEquals(new Uint8Array(ss), sharedSecret.subarray(32)); - // const sender = await suite.createSenderContext({ - // recipientPublicKey: rkp.publicKey, - // }); - - // const recipient = await suite.createRecipientContext({ - // recipientKey: rkp, - // enc: sender.enc, - // }); - // assertEquals(sender.enc.byteLength, suite.kem.encSize); - - // // encrypt - // const ct = await sender.seal( - // new TextEncoder().encode("my-secret-message"), - // ); - - // // decrypt - // const pt = await recipient.open(ct); - - // // assert - // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + describe("Hybridkem/X25519Kyber768/HkdfSha256/Aes128Gcm", () => { + it("should work normally", async () => { + for (const v of testVectors) { + const suite = new CipherSuite({ + kem: new HybridkemX25519Kyber768(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const ikmR = hexToBytes(v.ikmR); + const pkRm = hexToBytes(v.pkRm); + const skRm = hexToBytes(v.skRm); + // const sharedSecret = hexToBytes(v.shared_secret); + + // deriveKeyPair + const rkp = await suite.kem.deriveKeyPair(ikmR); + const pkR = new Uint8Array( + await suite.kem.serializePublicKey(rkp.publicKey), + ); + const skR = new Uint8Array( + await suite.kem.serializePrivateKey(rkp.privateKey), + ); + assertEquals(skR, skRm); + assertEquals(pkR, pkRm); + + // create EncryptionContext + const info = hexToBytes(v.info); + let psk: PreSharedKey | undefined = undefined; + if (v.psk !== undefined && v.psk_id !== undefined) { + psk = { id: new ArrayBuffer(0), key: new ArrayBuffer(0) }; + psk.key = hexToBytes(v.psk); + psk.id = hexToBytes(v.psk_id); + } + const enc = hexToBytes(v.enc); + const ier = hexToBytes(v.ier); + + const sender = await suite.createSenderContext({ + info: info, + psk: psk, + recipientPublicKey: rkp.publicKey, + // senderKey: skp, + ekm: ier, // FOR DEBUGGING/TESTING PURPOSES ONLY. + }); + assertEquals(new Uint8Array(sender.enc), enc); + + const recipient = await suite.createRecipientContext({ + info: info, + psk: psk, + recipientKey: rkp, + enc: sender.enc, + // senderPublicKey: pks, + }); + + // seal and open + if (v.aead_id !== 0xFFFF) { + for (const ve of v.encryptions) { + const pt = hexToBytes(ve.pt); + const aad = hexToBytes(ve.aad); + const ct = hexToBytes(ve.ct); + + const sealed = await sender.seal(pt, aad); + const opened = await recipient.open(sealed, aad); + assertEquals(new Uint8Array(sealed), ct); + assertEquals(new Uint8Array(opened), pt); + } + } + + // export + for (const ve of v.exports) { + const ec = ve.exporter_context.length === 0 + ? new ArrayBuffer(0) + : hexToBytes(ve.exporter_context); + const ev = hexToBytes(ve.exported_value); + + let exported = await sender.export(ec, ve.L); + assertEquals(new Uint8Array(exported), ev); + exported = await recipient.export(ec, ve.L); + assertEquals(new Uint8Array(exported), ev); + } + count++; + } }); }); }); diff --git a/x/hybridkem-x25519-kyber768/test/vectors/test-vectors.json b/x/hybridkem-x25519-kyber768/test/vectors/test-vectors.json new file mode 100644 index 000000000..e36e570ed --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/vectors/test-vectors.json @@ -0,0 +1,57 @@ +[ + { + "mode": 0, + "kem_id": 48, + "kdf_id": 1, + "aead_id": 1, + "info": "486561722068656172", + "ikmR": "3cb1eea988004b93103cfb0aeefd2a686e01fa4a58e8a3639ca8a1e3f9ae57e2", + "pkRm": "a3aa882fee0de0059cec0569c8e1b4872fb6cb4d82361b72ee1148dc7ddc0c2b210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5", + "skRm": "cf61f1a7b05c83f9c2a4b27dc0e9bdbf4e52ba1bbd906cb3776ac12268a9f4d0c348342d192f0458ab53d19c1dc135d11b48978c878bca6d7d1bc91428259e43aadc9700b76aa9aa66a65db91a77d72513e40697226557b53400bb6752fb4e11a5ba2fe12644698a48c9948ec121cc9c9ce7384c65f798012c9df8f5ac0cd371d7d19d9a24c30cb0909c665e43c89328735fe95a62653352fad3cfe6330b436a4f72c9ac9323babd912cf5970222eb0dd178c810bcc79beba0813039ec4333c33b13d4cc5183b6b14dc09cb9604d46242353a1a1df82999e4a4929f28f498c330d552ac64156cf123cceebbccf81b2fd86218f2a9112040943a359d7a858cd641467e54b25f03d66b9a150fbcf3f19bbd1791abec47269b2a72f4083a79c2559fbb6d0500208a78baa7392874443c39c38577b4c40db6220ca5c84b148ffb344164c723df1c0fb37ae52c0854bea023e2a45efaa8869c924ecf360008607e7079187978fdac30e8b76a3110349de272b25f5490dc87e3d8caf59a5a51a57230ea702dfda0f5d2c0fe94442254834b0f6aa71852601a8c5b7b211f108c1de2b1092e9b89df4a9feea882aa00ab97235940924e8a81d8f83597f72383f7a1b99c3c8481953be917fef0b44e32b0aa1f862eedc8d0d94030ae92e73097cde1b34de9b8279293322e0b5f9564395cb4998810818544ad2025018c40debf0b97bca2f1d861f8d5b51a2a84f35503cb37112b280ad0a4c99a2eb9c43300ce7c66eb89cb4443a44edc40869b8c2d90c5d484554557c408da7b46752bec14876815334b783207f60943d1738b5183d64394e27bb8f1dbb6ed9c58aa338171967bf5a613e9194c13395573615cee02012438a68aa104afd56a943d05caefc7f20a0104e2cced2a5191a1a68fe431920e1844a8154fc42a73d70c82f26846ec332fda50c340c1c5037965daaccd3cacfcab3c85a7516d712890fd6a2b1f5cb7c745cf1798dc0a49ed75717630c78d56bb8db272a85a009b2685ca9f4840c948226d224de1a0385565e569b8901c4508ae7b9214b88b6c2ac63807710d85e593a01ba20541cd03fa8364b4cb79f110745b30818521a7d0b6015a20483dde33188e94fc4aa224558bd53d384a9f6916964bbef0b0770b11e7b4117f41639bbe0c9ce119c8f8aab451608c2f06a8cd85f37519b7e3c1f9f07a6449059a972260cf80c23e52fe1b559e11c723b2618752672bfab67305358e7f048960475f1c720d8ba5fe4883981065c462c5062757bddd2666de67265990d0053229693a8bfd8811c84494853095c875639dcbcfcc02785910e35643f5bb4b0aa59af7a86ae94dcc01f952eb1d151c4ba1aa4da02c100b461904229f7b11aac35d307ab187255baa32eed32b3b262aaf2db6019089ad4250079280a0efb109ab27a364135ac3067ace5c82dea1fafb04dfedba9fabc196832878eb7b4314556e8aa8210e2c72959723e23176b703d4db42aabba62229790f6a743a2ec3c43dc8dbe0b4c36dc2323ec0ef21c116941b43bb12763460eed032a7a039185e36dcbf69d88f645e6728d3ba79dae0a25ddd4c3a8bba8334aa8fb6658a9dca99a8cc6362745d6080b0fd8af6af71e9f752d7b763035ec40c0fc98326081ea4c36cdf992e73a16719b9fb7c06e6c1bb7210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5137b60651b30bf805da1597faef1bc8b2645cda273144c4af1d13eaa2ad9101c7b58b14601aff81754afc776f8b7f7b9324d420b66706b96ea7f99f8fa11bed3", + "ier": "35b8cc873c23dc62b8d260169afa2f75ab916a58d974918835d25e6a435085b2badfd6dfaac359a5efbb7bcc4b59d538df9a04302e10c8bc1cbf1a0b3a5120ea", + "enc": "1d06980e46fd3842db6b87226231eedd2cc9684ee98a1d9d902bd9300e2c4d41b64fba47a50fe32dd0df3b0a75801c11022cd98a6ff5a83a8472ade82bdd6f1e8a65a94a88523ada0d8275165f707f1067a6a576e54525d9141e95223f5713456bda7ec5eb558adfc6b7f0d80de46222579a3274e45ab43fad14f7e9855a872d2716e8dc78d4c12027bef3184904476c8961552fd031361358f2d9deae8ad98194047a14222947612972574c57514266e9e67a3b6dd89972cc8a0882be7474f4923549dfcd944dcbe58b088079aa8b70c8f291cb4e45066bad4a832ccd8f40e51861f7a25b6a2358842f1bbe8108a6a6f0ae93153a2e7f9f53e180a90532531a632367b81bf08ed97effcf0140dd0e92cc438f6be7e6f3d97a9f7787f7e3981f971617f0bfb618caa7db1e453f33a386c3863b16d462229c41f4946b49e4e49c27e0f35d77e21304b6ad238a55a51e9e370dd39e713d626044fb970bb7c2af7d7b9cb9004394741a0ea2de592816359006f24abdfc2aa890720b00b2f7b8bb240120f22bdb84f9fc5c8fdc7ca7047ae633868c184c4d75e9e107eb9c6d8fe879415926457d818bc31e88b87a5881584a5650859e88b06faa2cfe1bde95dfa344af14f214cedecde4d89c87334c33e2d7ee3ab40d5df396cf0ff5a99588e0dcd205f1d876b380b963f5baccc0baeae569892a8d252f5eeeb7c751f663eb906ac99a165656224281add3ab271ff4f406b6932cbf1afff62109794f52ff3e723f5cdd706e3715d1d2d421bdac73fa047b5d9761569534fb2dd57b86a608f79db7d4ab99847490e76eaf0c683bdc54d12f2f2664a79de6a2f25bec3f43584f98ec41ad3fb19ba5ba936c3c893e9c0994b412ba3d07329086c20b04e1cd1d9b4f24a82f8c1f7b5db58b4056a4b4e27b60c957f5af8081bffab98d8455cab97e35042ed636c995931fd304b3d02fcf545df360cc421be64adc3d7a121ea75ab3440a9eba74fba1c5b40bdb66b54583ff2f76304ccaeae99ed94fb332d30d771fe0e45acb9e966f497b1629f5a5df15cea507d2fd1aa045a171e84bec932e4049639477f16fb9afdd107668f9b3531c3c7eb1d67753ac652c575b526e6f2965f1e4500e99f38ae1d34bce151a68e278f14405ad76f580b549d025b03be98b6a737f10238b9f84f1694173544ba2c97f811a17485129a146084bc5382e2086aaf51b11a4918bdb5485bf28a9be2d2c9d69468268fa04fa071c39942b43a0caf561278cfc1b47781fa9ef559f86b2dad703141b78b7ddb35c9c9ff4c1134580da26367dbf3db7eaf039dfbae238959c4cf55d40d78a2c5597ba038f2be5f994d60c79e8a92121fb0488eef9690d550ef9fa40b1774221aac8c8c1dcf97faa07c28e840feb9daf0bf3bed277a6e10a33490c0bee7e5fa318638f5b80a2272700e591ffc14985d0ed19876725c2bec9356b45ca96d295e30bce86effc626a2bd7839af05ae373801af510cfb378ce42088607909c91ceb4a90e4d7b2b6288b9cdfa262570ffda8692b58f0b05a7c7899a717a3a97b6e64489f56323b000793f807ca75ca991", + "shared_secret": "1368d71518fadbe42fb75fbd356e016b0aaad6b4d3d91ce7f207073e4fb08c537217aba238aea92a7f855820518a8342b3a31f82ebbcdb479f33ad82bdcdc953", + "key_schedule_context": "009f749a195d1c8b3eaa8d5c3f571dc7231aafbbc0405e4b484738352667c484867584e32e844cdf74d17b4ee224cc521bbc8bed221f21f34f8ccc9842772686cb", + "secret": "95f863934be4d0ef683770c7bd385839d19e525b467a332f47ae715c54183e1d", + "key": "6bb5532badb078ce8f326daa6cfaef84", + "base_nonce": "ff2b9a604a84754614e9e772", + "exporter_secret": "fb6ca36cfb7881cf11dbcb8fde201f698f80d0b941b642bc0a6a3101c97b7fad", + "encryptions": [ + { + "pt": "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", + "aad": "436f756e742d30", + "nonce": "ff2b9a604a84754614e9e772", + "ct": "a78ab8f057becc31cb3a5cff2fe2b18983b93ce74c6e7c45e0a57c4acc1976eef755c08547564ceede3e5169f959ea6ad498" + }, + { + "pt": "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", + "aad": "436f756e742d31", + "nonce": "ff2b9a604a84754614e9e773", + "ct": "c7e392bb20d256f0020ba0888996c4b0e2518b486ad5873263834e7f30fc43e6f712ee8e42846179db284a56baba6252d38f" + }, + { + "pt": "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", + "aad": "436f756e742d32", + "nonce": "ff2b9a604a84754614e9e770", + "ct": "86eeadf1593871435b643b7dde3d5a18b4dcb8d706c21abb69ae057af2a788a672198d46facadd396fa9fa6e1072c43a4f74" + } + ], + "exports": [ + { + "exporter_context": "", + "L": 32, + "exported_value": "0d15e6f37d0791a924c5b8a5c766db83d95703ddae889e6240c73926168ae6a8" + }, + { + "exporter_context": "00", + "L": 32, + "exported_value": "6f7bc144a46519b718c93a86a4ce74dad186816c88791eeee4f39fd0a2dbcef2" + }, + { + "exporter_context": "54657374436f6e74657874", + "L": 32, + "exported_value": "3a0064f88b02de081e9d5f4398093e016b00b01816db91f0686d50330a9886b2" + } + ] + } +] From 76057ceeed93b38406ee77f90b15e14e69c0f09b Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Fri, 18 Aug 2023 19:52:27 +0900 Subject: [PATCH 05/14] Update README.md for X25519Kyber768Draft00. --- README.md | 6 +- x/hybridkem-x25519-kyber768/README.md | 100 +++++++++++++------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 25d6d148a..2e91e5055 100644 --- a/README.md +++ b/README.md @@ -117,10 +117,11 @@ The hpke-js includes the following packages. | ---------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | hpke-js | v0.1.0- | The HPKE module supporting all of the ciphersuites defined in [RFC9180](https://datatracker.ietf.org/doc/html/rfc9180), which consists of the following @hpke/{core, dhkem-x25519, dhkem-x448, chacha20poly1305} internally. | | @hpke/core | v1.0.0- | The HPKE core module implemented using only [Web Cryptography API](https://www.w3.org/TR/WebCryptoAPI/). It does not support the X25519/X448-based KEMs and the ChaCha20/Poly1305 AEAD, but it has no external module dependencies and is small in size. See [/core/README](https://github.com/dajiaji/hpke-js/blob/main/core/README.md). | +| @hpke/chacha20poly1305 | v1.0.0- | The HPKE extension module for ChaCha20Poly1305 AEAD. See [/x/chacha20poly1305/README](https://github.com/dajiaji/hpke-js/blob/main/x/chacha20poly1305/README.md). | | @hpke/dhkem-x25519 | v1.0.0- | The HPKE extension module for DHKEM(X25519, HKDF-SHA256). See [/x/dhkem-x25519/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-x25519/README.md). | | @hpke/dhkem-x448 | v1.0.0- | The HPKE extension module for DHKEM(X448, HKDF-SHA512). See [/x/dhkem-x448/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-x448/README.md). | -| @hpke/chacha20poly1305 | v1.0.0- | The HPKE extension module for ChaCha20Poly1305 AEAD. See [/x/chacha20poly1305/README](https://github.com/dajiaji/hpke-js/blob/main/x/chacha20poly1305/README.md). | -| @hpke/dhkem-secp256k1 | v1.0.0- | [EXPERIMENTAL AND NOT STANDARDIZED] The HPKE extension module for DHKEM(secp256k1, HKDF-SHA256). See [/x/dhkem-secp256k1/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-secp256k1/README.md). | +| @hpke/hybridkem-x25519-kyber768 | v1.2.1- | **EXPERIMENTAL AND NOT STANDARDIZED** The HPKE extension module for the hybrid post-quantum KEM currently named [X25519Kyber768Draft00](https://datatracker.ietf.org/doc/draft-westerbaan-cfrg-hpke-xyber768d00/). See [/x/hybridkem-x25519-kyber768/README](https://github.com/dajiaji/hpke-js/blob/main/x/hybridkem-x25519-kyber768/README.md). | +| @hpke/dhkem-secp256k1 | v1.0.0- | **EXPERIMENTAL AND NOT STANDARDIZED** The HPKE extension module for DHKEM(secp256k1, HKDF-SHA256). See [/x/dhkem-secp256k1/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-secp256k1/README.md). | ## Supported Features @@ -139,6 +140,7 @@ The hpke-js includes the following packages. | DHKEM (P-521, HKDF-SHA512) | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | | DHKEM (X25519, HKDF-SHA256) | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | | DHKEM (X448, HKDF-SHA512) | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | +| Hybrid KEM (X25519, Kyber768) | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | | DHKEM (secp256k1, HKDF-SHA256) | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ### Key Derivation Functions (KDFs) diff --git a/x/hybridkem-x25519-kyber768/README.md b/x/hybridkem-x25519-kyber768/README.md index 5296b37b9..ceec2b1f2 100644 --- a/x/hybridkem-x25519-kyber768/README.md +++ b/x/hybridkem-x25519-kyber768/README.md @@ -1,7 +1,7 @@

@hpke/hybridkem-x25519-kyber768

-A TypeScript Hybrid Public Key Encryption (HPKE) module extension for the hybrid post-quantum KEM(X25519, Kyber768) compliant with X25519Kyber768Draft00 hybrid post-quantum KEM for HPKE. Note that this implementation is EXPERIMENTAL and the specification has not been done yet. The kyber implementation included in this module is based on ntontutoveanu/crystals-kyber-javascript. +A TypeScript Hybrid Public Key Encryption (HPKE) module extension for the hybrid post-quantum KEM(X25519, Kyber768) compliant with X25519Kyber768Draft00 hybrid post-quantum KEM for HPKE. Note that this implementation is EXPERIMENTAL and the specification has not been done yet. The kyber implementation included in this module is based on ntontutoveanu/crystals-kyber-javascript published under the MIT license.

@@ -36,14 +36,14 @@ Using esm.sh: ``` @@ -54,7 +54,7 @@ Using unpkg: ``` @@ -80,11 +80,11 @@ Using deno.land: ```js // use a specific version import * as hpke from "https://deno.land/x/hpke@1.2.1/core/mod.ts"; -import * as x25519 from "https://deno.land/x/hpke@1.2.1/x/hybridkem-x25519-kyber768/mod.ts"; +import * as kyber from "https://deno.land/x/hpke@1.2.1/x/hybridkem-x25519-kyber768/mod.ts"; // use the latest stable version import * as hpke from "https://deno.land/x/hpke/core/mod.ts"; -import * as x25519 from "https://deno.land/x/hpke/x/hybridkem-x25519-kyber768/mod.ts"; +import * as kyber from "https://deno.land/x/hpke/x/hybridkem-x25519-kyber768/mod.ts"; ``` ### Cloudflare Workers @@ -113,34 +113,33 @@ This section shows some typical usage examples. globalThis.doHpke = async () => { - const suite = new CipherSuite({ - kem: new HybridkemX25519Kyber768(), - kdf: new HkdfSha256(), - aead: new Aes128Gcm(), - }); + try { + const suite = new CipherSuite({ + kem: new HybridkemX25519Kyber768(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); - const rkp = await suite.kem.generateKeyPair(); + const rkp = await suite.kem.generateKeyPair(); - const sender = await suite.createSenderContext({ - recipientPublicKey: rkp.publicKey - }); - - const recipient = await suite.createRecipientContext({ - recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. - enc: sender.enc, - }); - - // encrypt - const ct = await sender.seal(new TextEncoder().encode("hello world!")); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey + }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); - // decrypt - try { + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. + enc: sender.enc, + }); + + // decrypt const pt = await recipient.open(ct); - // hello world! + // Hello world! alert(new TextDecoder().decode(pt)); } catch (err) { - alert("failed to decrypt."); + alert("failed:", err.message); } } @@ -171,26 +170,25 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - // decrypt - try { - const pt = await recipient.open(ct); - - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt."); - } + const pt = await recipient.open(ct);j + console.log("decrypted: ", new TextDecoder().decode(pt)); + // decrypted: Hello world! } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### Deno @@ -213,26 +211,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - - try { - // decrypt - const pt = await recipient.open(ct); + // decrypt + const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (_err: unknown) { - console.log("failed to decrypt."); - } + console.log("decrypted: ", new TextDecoder().decode(pt)); + // decrypted: Hello world! } -doHpke(); +try { + doHpke(); +} catch (_err: unknown) { + console.log("failed."); +} ``` ## Contributing From 3e2dd3d1fa0694a5ae18373dd46571ed94fba986 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Fri, 18 Aug 2023 20:34:48 +0900 Subject: [PATCH 06/14] Refine samples. --- README.md | 291 +++++++++++++------------- x/chacha20poly1305/README.md | 90 ++++---- x/dhkem-secp256k1/README.md | 90 ++++---- x/dhkem-x25519/README.md | 88 ++++---- x/dhkem-x448/README.md | 90 ++++---- x/hybridkem-x25519-kyber768/README.md | 11 +- 6 files changed, 329 insertions(+), 331 deletions(-) diff --git a/README.md b/README.md index 2e91e5055..18e034e8e 100644 --- a/README.md +++ b/README.md @@ -77,15 +77,17 @@ async function doHpke() { recipientKey: rkp.privateKey, enc: sender.enc, }); - try { - const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - } catch (e) { - console.log("failed to decrypt:", e.message); - } + const pt = await recipient.open(ct); + + // Hello world! + console.log("new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (e) { + console.log("failed:", e.message); +} ``` ## Index @@ -113,15 +115,15 @@ doHpke(); The hpke-js includes the following packages. -| name | since | description | -| ---------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| hpke-js | v0.1.0- | The HPKE module supporting all of the ciphersuites defined in [RFC9180](https://datatracker.ietf.org/doc/html/rfc9180), which consists of the following @hpke/{core, dhkem-x25519, dhkem-x448, chacha20poly1305} internally. | -| @hpke/core | v1.0.0- | The HPKE core module implemented using only [Web Cryptography API](https://www.w3.org/TR/WebCryptoAPI/). It does not support the X25519/X448-based KEMs and the ChaCha20/Poly1305 AEAD, but it has no external module dependencies and is small in size. See [/core/README](https://github.com/dajiaji/hpke-js/blob/main/core/README.md). | -| @hpke/chacha20poly1305 | v1.0.0- | The HPKE extension module for ChaCha20Poly1305 AEAD. See [/x/chacha20poly1305/README](https://github.com/dajiaji/hpke-js/blob/main/x/chacha20poly1305/README.md). | -| @hpke/dhkem-x25519 | v1.0.0- | The HPKE extension module for DHKEM(X25519, HKDF-SHA256). See [/x/dhkem-x25519/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-x25519/README.md). | -| @hpke/dhkem-x448 | v1.0.0- | The HPKE extension module for DHKEM(X448, HKDF-SHA512). See [/x/dhkem-x448/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-x448/README.md). | -| @hpke/hybridkem-x25519-kyber768 | v1.2.1- | **EXPERIMENTAL AND NOT STANDARDIZED** The HPKE extension module for the hybrid post-quantum KEM currently named [X25519Kyber768Draft00](https://datatracker.ietf.org/doc/draft-westerbaan-cfrg-hpke-xyber768d00/). See [/x/hybridkem-x25519-kyber768/README](https://github.com/dajiaji/hpke-js/blob/main/x/hybridkem-x25519-kyber768/README.md). | -| @hpke/dhkem-secp256k1 | v1.0.0- | **EXPERIMENTAL AND NOT STANDARDIZED** The HPKE extension module for DHKEM(secp256k1, HKDF-SHA256). See [/x/dhkem-secp256k1/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-secp256k1/README.md). | +| name | since | description | +| ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| hpke-js | v0.1.0- | The HPKE module supporting all of the ciphersuites defined in [RFC9180](https://datatracker.ietf.org/doc/html/rfc9180), which consists of the following @hpke/{core, dhkem-x25519, dhkem-x448, chacha20poly1305} internally. | +| @hpke/core | v1.0.0- | The HPKE core module implemented using only [Web Cryptography API](https://www.w3.org/TR/WebCryptoAPI/). It does not support the X25519/X448-based KEMs and the ChaCha20/Poly1305 AEAD, but it has no external module dependencies and is small in size. See [/core/README](https://github.com/dajiaji/hpke-js/blob/main/core/README.md). | +| @hpke/chacha20poly1305 | v1.0.0- | The HPKE extension module for ChaCha20Poly1305 AEAD. See [/x/chacha20poly1305/README](https://github.com/dajiaji/hpke-js/blob/main/x/chacha20poly1305/README.md). | +| @hpke/dhkem-x25519 | v1.0.0- | The HPKE extension module for DHKEM(X25519, HKDF-SHA256). See [/x/dhkem-x25519/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-x25519/README.md). | +| @hpke/dhkem-x448 | v1.0.0- | The HPKE extension module for DHKEM(X448, HKDF-SHA512). See [/x/dhkem-x448/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-x448/README.md). | +| @hpke/hybridkem-x25519-kyber768 | v1.2.1- | **EXPERIMENTAL AND NOT STANDARDIZED** The HPKE extension module for the hybrid post-quantum KEM currently named [X25519Kyber768Draft00](https://datatracker.ietf.org/doc/draft-westerbaan-cfrg-hpke-xyber768d00/). See [/x/hybridkem-x25519-kyber768/README](https://github.com/dajiaji/hpke-js/blob/main/x/hybridkem-x25519-kyber768/README.md). | +| @hpke/dhkem-secp256k1 | v1.0.0- | **EXPERIMENTAL AND NOT STANDARDIZED** The HPKE extension module for DHKEM(secp256k1, HKDF-SHA256). See [/x/dhkem-secp256k1/README](https://github.com/dajiaji/hpke-js/blob/main/x/dhkem-secp256k1/README.md). | ## Supported Features @@ -133,15 +135,15 @@ The hpke-js includes the following packages. ### Key Encapsulation Machanisms (KEMs) -| KEMs | Browser | Node.js | Deno | Cloudflare
Workers | bun | -| ------------------------------ | ----------------------------------- | ----------------------------------- | ----------------------------------- | ----------------------------------- | ----------------------------------- | -| DHKEM (P-256, HKDF-SHA256) | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | -| DHKEM (P-384, HKDF-SHA384) | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | -| DHKEM (P-521, HKDF-SHA512) | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | -| DHKEM (X25519, HKDF-SHA256) | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | -| DHKEM (X448, HKDF-SHA512) | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | -| Hybrid KEM (X25519, Kyber768) | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | -| DHKEM (secp256k1, HKDF-SHA256) | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | +| KEMs | Browser | Node.js | Deno | Cloudflare
Workers | bun | +| ------------------------------ | ------------------------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- | +| DHKEM (P-256, HKDF-SHA256) | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | +| DHKEM (P-384, HKDF-SHA384) | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | +| DHKEM (P-521, HKDF-SHA512) | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | | ✅
hpke-js
@hpke/core | ✅
hpke-js
@hpke/core | +| DHKEM (X25519, HKDF-SHA256) | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | ✅
hpke-js
@hpke/dhkem-x25519 | +| DHKEM (X448, HKDF-SHA512) | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | ✅
hpke-js
@hpke/dhkem-x448 | +| Hybrid KEM (X25519, Kyber768) | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | ✅
@hpke/hybridkem-x25519-kyber768 | +| DHKEM (secp256k1, HKDF-SHA256) | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ✅
@hpke/dhkem-secp256k1 | ### Key Derivation Functions (KDFs) @@ -302,66 +304,65 @@ Browsers: // } from "@hpke/core@1.2.0"; globalThis.doHpke = async () => { - - const suite = new CipherSuite({ - kem: KemId.DhkemP256HkdfSha256, - kdf: KdfId.HkdfSha256, - aead: AeadId.Aes128Gcm - }); + try { + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm + }); - const rkp = await suite.kem.generateKeyPair(); - - const sender = await suite.createSenderContext({ - recipientPublicKey: rkp.publicKey - }); + const rkp = await suite.kem.generateKeyPair(); - // A JWK-formatted recipient public key can also be used. - // const jwkPkR = { - // kty: "EC", - // crv: "P-256", - // kid: "P-256-01", - // x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", - // y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", - // key_ops: [], - // }; - // const pkR = await suite.kem.importKey("jwk", jwkPkR, true); - // const sender = await suite.createSenderContext({ - // recipientPublicKey: pkR, - // }); - - const recipient = await suite.createRecipientContext({ - recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. - enc: sender.enc, - }); - - // A JWK-formatted recipient private key can also be used. - // const jwkSkR = { - // kty: "EC", - // crv: "P-256", - // kid: "P-256-01", - // x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", - // y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", - // d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", - // key_ops: ["deriveBits"], - // }; - // const skR = await suite.kem.importKey("jwk", jwkSkR, false); - // const recipient = await suite.createRecipientContext({ - // recipientKey: skR, - // enc: sender.enc, - // }); - - - // encrypt - const ct = await sender.seal(new TextEncoder().encode("hello world!")); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey + }); - // decrypt - try { + // A JWK-formatted recipient public key can also be used. + // const jwkPkR = { + // kty: "EC", + // crv: "P-256", + // kid: "P-256-01", + // x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + // y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + // key_ops: [], + // }; + // const pkR = await suite.kem.importKey("jwk", jwkPkR, true); + // const sender = await suite.createSenderContext({ + // recipientPublicKey: pkR, + // }); + + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. + enc: sender.enc, + }); + + // A JWK-formatted recipient private key can also be used. + // const jwkSkR = { + // kty: "EC", + // crv: "P-256", + // kid: "P-256-01", + // x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + // y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + // d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + // key_ops: ["deriveBits"], + // }; + // const skR = await suite.kem.importKey("jwk", jwkSkR, false); + // const recipient = await suite.createRecipientContext({ + // recipientKey: skR, + // enc: sender.enc, + // }); + + // decrypt const pt = await recipient.open(ct); - // hello world! + // Hello world! alert(new TextDecoder().decode(pt)); } catch (err) { - alert("failed to decrypt."); + alert("failed:", err.message); } } @@ -401,15 +402,17 @@ async function doHpke() { recipientKey: rkp.privateKey, enc: sender.enc, }); - try { - const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - } catch (e) { - console.log("failed to decrypt:", e.message); - } + const pt = await recipient.open(ct); + + // Hello world! + console.log("decrypted: ", new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (e) { + console.log("failed:", e.message); +} ``` Deno: @@ -443,26 +446,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - - try { - // decrypt - const pt = await recipient.open(ct); + // decrypt + const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (_err: unknown) { - console.log("failed to decrypt."); - } + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (_err: unknown) { + console.log("failed."); +} ``` ### Base mode with Single-Shot APIs @@ -486,23 +489,23 @@ async function doHpke() { }); const rkp = await suite.kem.generateKeyPair(); - const pt = new TextEncoder().encode('my-secret-message'), + const pt = new TextEncoder().encode('Hello world!'), // encrypt const { ct, enc } = await suite.seal({ recipientPublicKey: rkp.publicKey }, pt); // decrypt - try { const pt = await suite.open({ recipientKey: rkp.privateKey, enc: enc }, ct); - console.log('decrypted: ', new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt."); - } + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### Base mode with export-only AEAD @@ -549,7 +552,11 @@ async function doHpke() { // pskR === pskS } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### PSK mode @@ -582,6 +589,9 @@ async function doHpke() { }, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, @@ -592,21 +602,18 @@ async function doHpke() { }, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - // decrypt - try { - const pt = await recipient.open(ct); - - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt:", err.message); - } + const pt = await recipient.open(ct); + + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### Auth mode @@ -636,27 +643,27 @@ async function doHpke() { senderKey: skp, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, senderPublicKey: skp.publicKey, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - - try { - // decrypt - const pt = await recipient.open(ct); + // decrypt + const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt:", err.message); - } + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### AuthPSK mode @@ -691,6 +698,9 @@ async function doHpke() { }, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, @@ -702,21 +712,18 @@ async function doHpke() { }, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - // decrypt - try { - const pt = await recipient.open(ct); - - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt:", err.message); - } + const pt = await recipient.open(ct); + + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ## Contributing diff --git a/x/chacha20poly1305/README.md b/x/chacha20poly1305/README.md index 531391506..827e8d121 100644 --- a/x/chacha20poly1305/README.md +++ b/x/chacha20poly1305/README.md @@ -114,38 +114,36 @@ This section shows some typical usage examples. import { Chacha20Poly1305 } from "https://esm.sh/@hpke/chacha20poly1305@1.2.0"; globalThis.doHpke = async () => { - - const suite = new CipherSuite({ - kem: new DhkemP256HkdfSha256(), - kdf: new HkdfSha256(), - aead: new Chacha20Poly1305() - }); + try { + const suite = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Chacha20Poly1305() + }); - const rkp = await suite.kem.generateKeyPair(); + const rkp = await suite.kem.generateKeyPair(); - const sender = await suite.createSenderContext({ - recipientPublicKey: rkp.publicKey - }); - - const recipient = await suite.createRecipientContext({ - recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. - enc: sender.enc, - }); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey + }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("hello world!")); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); - // decrypt - try { + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. + enc: sender.enc, + }); + + // decrypt const pt = await recipient.open(ct); - // hello world! + // Hello world! alert(new TextDecoder().decode(pt)); } catch (err) { - alert("failed to decrypt."); + alert("failed:", err.message); } } - @@ -173,26 +171,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - // decrypt - try { - const pt = await recipient.open(ct); - - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt."); - } + const pt = await recipient.open(ct); + + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### Deno @@ -217,26 +215,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - - try { - // decrypt - const pt = await recipient.open(ct); + // decrypt + const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (_err: unknown) { - console.log("failed to decrypt."); - } + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (_err: unknown) { + console.log("failed."); +} ``` ## Contributing diff --git a/x/dhkem-secp256k1/README.md b/x/dhkem-secp256k1/README.md index 6044a5252..ea9898454 100644 --- a/x/dhkem-secp256k1/README.md +++ b/x/dhkem-secp256k1/README.md @@ -111,38 +111,36 @@ This section shows some typical usage examples. import { DhkemSecp256k1HkdfSha256 } from "https://esm.sh/@hpke/dhkem-secp256k1@1.2.0"; globalThis.doHpke = async () => { - - const suite = new CipherSuite({ - kem: new DhkemSecp256k1HkdfSha256(), - kdf: new HkdfSha256(), - aead: new Aes128Gcm(), - }); + try { + const suite = new CipherSuite({ + kem: new DhkemSecp256k1HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); - const rkp = await suite.kem.generateKeyPair(); + const rkp = await suite.kem.generateKeyPair(); - const sender = await suite.createSenderContext({ - recipientPublicKey: rkp.publicKey - }); - - const recipient = await suite.createRecipientContext({ - recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. - enc: sender.enc, - }); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey + }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("hello world!")); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); - // decrypt - try { + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. + enc: sender.enc, + }); + + // decrypt const pt = await recipient.open(ct); - // hello world! + // Hello world! alert(new TextDecoder().decode(pt)); } catch (err) { - alert("failed to decrypt."); + alert("failed:", err.message); } } - @@ -170,26 +168,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - // decrypt - try { - const pt = await recipient.open(ct); - - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt."); - } + const pt = await recipient.open(ct); + + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### Deno @@ -212,26 +210,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - - try { - // decrypt - const pt = await recipient.open(ct); + // decrypt + const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (_err: unknown) { - console.log("failed to decrypt."); - } + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (_err: unknown) { + console.log("failed."); +} ``` ## Contributing diff --git a/x/dhkem-x25519/README.md b/x/dhkem-x25519/README.md index c6500a422..44f6f522d 100644 --- a/x/dhkem-x25519/README.md +++ b/x/dhkem-x25519/README.md @@ -111,38 +111,36 @@ This section shows some typical usage examples. import { DhkemX25519HkdfSha256 } from "https://esm.sh/@hpke/dhkem-x25519@1.2.0"; globalThis.doHpke = async () => { - - const suite = new CipherSuite({ - kem: new DhkemX25519HkdfSha256(), - kdf: new HkdfSha256(), - aead: new Aes128Gcm(), - }); + try { + const suite = new CipherSuite({ + kem: new DhkemX25519HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); - const rkp = await suite.kem.generateKeyPair(); + const rkp = await suite.kem.generateKeyPair(); - const sender = await suite.createSenderContext({ - recipientPublicKey: rkp.publicKey - }); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey + }); - const recipient = await suite.createRecipientContext({ - recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. - enc: sender.enc, - }); + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. + enc: sender.enc, + }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("hello world!")); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); - // decrypt - try { + // decrypt const pt = await recipient.open(ct); - // hello world! + // Hello world! alert(new TextDecoder().decode(pt)); } catch (err) { - alert("failed to decrypt."); + alert("failed:", err.message); } } - @@ -170,26 +168,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - // decrypt - try { - const pt = await recipient.open(ct); - - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt."); - } + const pt = await recipient.open(ct); + + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### Deno @@ -212,26 +210,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - - try { - // decrypt - const pt = await recipient.open(ct); + // decrypt + const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (_err: unknown) { - console.log("failed to decrypt."); - } + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (_err: unknown) { + console.log("failed."); +} ``` ## Contributing diff --git a/x/dhkem-x448/README.md b/x/dhkem-x448/README.md index 71846e601..03fc0ce2b 100644 --- a/x/dhkem-x448/README.md +++ b/x/dhkem-x448/README.md @@ -111,38 +111,36 @@ This section shows some typical usage examples. import { DhkemX448HkdfSha512 } from "https://esm.sh/@hpke/dhkem-x448@1.2.0"; globalThis.doHpke = async () => { - - const suite = new CipherSuite({ - kem: new DhkemX448HkdfSha512(), - kdf: new HkdfSha512(), - aead: new Aes256Gcm(), - }); + try { + const suite = new CipherSuite({ + kem: new DhkemX448HkdfSha512(), + kdf: new HkdfSha512(), + aead: new Aes256Gcm(), + }); - const rkp = await suite.kem.generateKeyPair(); + const rkp = await suite.kem.generateKeyPair(); - const sender = await suite.createSenderContext({ - recipientPublicKey: rkp.publicKey - }); - - const recipient = await suite.createRecipientContext({ - recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. - enc: sender.enc, - }); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey + }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("hello world!")); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); - // decrypt - try { + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable. + enc: sender.enc, + }); + + // decrypt const pt = await recipient.open(ct); - // hello world! + // Hello world! alert(new TextDecoder().decode(pt)); } catch (err) { - alert("failed to decrypt."); + alert("failed:", err); } } - @@ -170,26 +168,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - // decrypt - try { - const pt = await recipient.open(ct); - - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (err) { - console.log("failed to decrypt."); - } + const pt = await recipient.open(ct); + + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (err) { + console.log("failed:", err.message); +} ``` ### Deno @@ -212,26 +210,26 @@ async function doHpke() { recipientPublicKey: rkp.publicKey, }); + // encrypt + const ct = await sender.seal(new TextEncoder().encode("Hello world!")); + const recipient = await suite.createRecipientContext({ recipientKey: rkp.privateKey, enc: sender.enc, }); - // encrypt - const ct = await sender.seal(new TextEncoder().encode("my-secret-message")); - - try { - // decrypt - const pt = await recipient.open(ct); + // decrypt + const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: my-secret-message - } catch (_err: unknown) { - console.log("failed to decrypt."); - } + // Hello world! + console.log(new TextDecoder().decode(pt)); } -doHpke(); +try { + doHpke(); +} catch (_err: unknown) { + console.log("failed."); +} ``` ## Contributing diff --git a/x/hybridkem-x25519-kyber768/README.md b/x/hybridkem-x25519-kyber768/README.md index ceec2b1f2..4552ec8ae 100644 --- a/x/hybridkem-x25519-kyber768/README.md +++ b/x/hybridkem-x25519-kyber768/README.md @@ -112,7 +112,6 @@ This section shows some typical usage examples. import { HybridkemX25519Kyber768 } from "https://esm.sh/@hpke/hybridkem-x25519-kyber768@1.2.1"; globalThis.doHpke = async () => { - try { const suite = new CipherSuite({ kem: new HybridkemX25519Kyber768(), @@ -142,7 +141,6 @@ This section shows some typical usage examples. alert("failed:", err.message); } } - @@ -180,8 +178,9 @@ async function doHpke() { // decrypt const pt = await recipient.open(ct);j - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: Hello world! + + // Hello world! + console.log(new TextDecoder().decode(pt)); } try { @@ -222,8 +221,8 @@ async function doHpke() { // decrypt const pt = await recipient.open(ct); - console.log("decrypted: ", new TextDecoder().decode(pt)); - // decrypted: Hello world! + // Hello world! + console.log(new TextDecoder().decode(pt)); } try { From 84a74ac624a4b71f0c52a19b08eae63499f154ea Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Fri, 18 Aug 2023 21:11:08 +0900 Subject: [PATCH 07/14] Fix package name in dnt.ts. --- dnt.ts | 4 ++-- x/hybridkem-x25519-kyber768/dnt.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dnt.ts b/dnt.ts index 4ff5ff38d..eafa47384 100644 --- a/dnt.ts +++ b/dnt.ts @@ -23,10 +23,10 @@ await build({ deno: "dev", }, package: { - name: "@hpke/hybridkem-x25519-kyber768", + name: "hpke-js", version: Deno.args[0], description: - "A Hybrid Public Key Encryption (HPKE) module extension for a hybrid post-quantum KEM, X25519Kyber768", + "A Hybrid Public Key Encryption (HPKE) module for various JavaScript runtimes", repository: { type: "git", url: "git+https://github.com/dajiaji/hpke-js.git", diff --git a/x/hybridkem-x25519-kyber768/dnt.ts b/x/hybridkem-x25519-kyber768/dnt.ts index c3c7466b4..1a9438e8f 100644 --- a/x/hybridkem-x25519-kyber768/dnt.ts +++ b/x/hybridkem-x25519-kyber768/dnt.ts @@ -20,7 +20,7 @@ await build({ name: "@hpke/hybridkem-x25519-kyber768", version: Deno.args[0], description: - "A Hybrid Public Key Encryption (HPKE) extension module for a hybrid qost-quantum KEM which is the parallel combination of DHKEM(X25519, HKDF-SHA256) and Kyber768 (EXPERIMENTAL)", + "A Hybrid Public Key Encryption (HPKE) module extension for a hybrid post-quantum KEM, X25519Kyber768Draft00", repository: { type: "git", url: "git+https://github.com/dajiaji/hpke-js.git", From 76f89380515ad7d8cea8b5bbbba80c9ab07ede08 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Fri, 18 Aug 2023 21:33:21 +0900 Subject: [PATCH 08/14] Fix typo on sample code. --- x/hybridkem-x25519-kyber768/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/hybridkem-x25519-kyber768/README.md b/x/hybridkem-x25519-kyber768/README.md index 4552ec8ae..e2d2c3d23 100644 --- a/x/hybridkem-x25519-kyber768/README.md +++ b/x/hybridkem-x25519-kyber768/README.md @@ -177,7 +177,7 @@ async function doHpke() { }); // decrypt - const pt = await recipient.open(ct);j + const pt = await recipient.open(ct); // Hello world! console.log(new TextDecoder().decode(pt)); From df489a1c3f4619cc8286b7644afcc253fa5e3619 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Fri, 18 Aug 2023 22:53:36 +0900 Subject: [PATCH 09/14] Fix conformance test for X25519Kyber768. --- src/kems/kemKyber768.ts | 6 +- src/kems/pqkemPrimitives/kyber768.ts | 62 ++++-- .../test/conformance.test.ts | 184 ++++++++++++++++++ .../test/hybridkemX25519Kyber768.test.ts | 109 +---------- .../test/testVector.ts | 61 ++++++ .../test/vectors/test-vectors.json | 57 ------ 6 files changed, 299 insertions(+), 180 deletions(-) create mode 100644 x/hybridkem-x25519-kyber768/test/conformance.test.ts create mode 100644 x/hybridkem-x25519-kyber768/test/testVector.ts delete mode 100644 x/hybridkem-x25519-kyber768/test/vectors/test-vectors.json diff --git a/src/kems/kemKyber768.ts b/src/kems/kemKyber768.ts index 4710ea8ad..f42312481 100644 --- a/src/kems/kemKyber768.ts +++ b/src/kems/kemKyber768.ts @@ -73,7 +73,7 @@ export class KemKyber768 implements KemInterface { // const keys = kyber.KeyGen768(); // const sk = await this.deserializePrivateKey(new Uint8Array(keys[1])); // const pk = await this.deserializePublicKey(new Uint8Array(keys[0])); - const keys = this._prim.generateKeyPair(); + const keys = await this._prim.generateKeyPair(); const sk = await this.deserializePrivateKey(keys[1]); const pk = await this.deserializePublicKey(keys[0]); return { publicKey: pk, privateKey: sk }; @@ -122,7 +122,7 @@ export class KemKyber768 implements KemInterface { await this.serializePublicKey(params.recipientPublicKey), ); try { - const res = this._prim.encap(pkR, ikm); + const res = await this._prim.encap(pkR, ikm); return { sharedSecret: res[1], enc: res[0] }; } catch (e: unknown) { throw new EncapError(e); @@ -135,7 +135,7 @@ export class KemKyber768 implements KemInterface { : params.recipientKey; const serializedSkR = new Uint8Array(await this.serializePrivateKey(skR)); try { - return this._prim.decap(new Uint8Array(params.enc), serializedSkR); + return await this._prim.decap(new Uint8Array(params.enc), serializedSkR); } catch (e: unknown) { throw new DecapError(e); } diff --git a/src/kems/pqkemPrimitives/kyber768.ts b/src/kems/pqkemPrimitives/kyber768.ts index 80efd0304..b26d88a58 100644 --- a/src/kems/pqkemPrimitives/kyber768.ts +++ b/src/kems/pqkemPrimitives/kyber768.ts @@ -1,12 +1,12 @@ -// @ts-ignore: for "npm:" import { sha3_256, sha3_512, shake128, shake256, + // @ts-ignore: for "npm:" } from "npm:@noble/hashes@1.3.1/sha3"; -import { concat } from "../../utils/misc.ts"; +import { concat, isBrowser, isCloudflareWorkers } from "../../utils/misc.ts"; // deno-fmt-ignore const nttZetas = [ @@ -43,31 +43,40 @@ const paramsQinv = 62209; const paramsETA = 2; export class Kyber768 { - private _api; + private _api: Crypto | undefined = undefined; - constructor() { - this._api = globalThis.crypto; - } + constructor() {} + + public async generateKeyPair(): Promise<[Uint8Array, Uint8Array]> { + await this._setup(); - public generateKeyPair(): [Uint8Array, Uint8Array] { const rnd = new Uint8Array(64); - this._api.getRandomValues(rnd); + (this._api as Crypto).getRandomValues(rnd); return this._deriveKeyPair(rnd); } - public deriveKeyPair(ikm: Uint8Array): [Uint8Array, Uint8Array] { + public async deriveKeyPair( + ikm: Uint8Array, + ): Promise<[Uint8Array, Uint8Array]> { + await this._setup(); + if (ikm.byteLength !== 64) { throw new Error("ikm must be 64 bytes in length"); } return this._deriveKeyPair(ikm); } - public encap(pk: Uint8Array, ikm?: Uint8Array): [Uint8Array, Uint8Array] { + public async encap( + pk: Uint8Array, + ikm?: Uint8Array, + ): Promise<[Uint8Array, Uint8Array]> { + await this._setup(); + // random 32 bytes m let m: Uint8Array; if (ikm === undefined) { m = new Uint8Array(32); - this._api.getRandomValues(m); + (this._api as Crypto).getRandomValues(m); } else { if (ikm.byteLength !== 32) { throw new Error("ikm must be 32 bytes in length"); @@ -85,7 +94,12 @@ export class Kyber768 { return [c, ss]; } - public decap(c: Uint8Array, privateKey: Uint8Array): Uint8Array { + public async decap( + c: Uint8Array, + privateKey: Uint8Array, + ): Promise { + await this._setup(); + // extract sk, pk, pkh and z const sk = privateKey.subarray(0, 1152); const pk = privateKey.subarray(1152, 2336); @@ -326,6 +340,30 @@ export class Kyber768 { mp = reduce(mp); return polyToMsg(mp); } + + protected async _setup() { + if (this._api !== undefined) { + return; + } + this._api = await loadCrypto(); + } +} + +async function loadCrypto(): Promise { + if (isBrowser() || isCloudflareWorkers()) { + if (globalThis.crypto !== undefined) { + return globalThis.crypto; + } + // jsdom + } + + try { + // @ts-ignore: to ignore "crypto" + const { webcrypto } = await import("crypto"); // node:crypto + return (webcrypto as unknown as Crypto); + } catch (_e: unknown) { + throw new Error("Web Cryptograph API not supported"); + } } // polyvecFromBytes deserializes a vector of polynomials. diff --git a/x/hybridkem-x25519-kyber768/test/conformance.test.ts b/x/hybridkem-x25519-kyber768/test/conformance.test.ts new file mode 100644 index 000000000..7e4555368 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/conformance.test.ts @@ -0,0 +1,184 @@ +import { assertEquals } from "testing/asserts.ts"; +import { afterAll, beforeAll, describe, it } from "testing/bdd.ts"; + +import type { TestVector } from "./testVector.ts"; + +import { + Aes128Gcm, + CipherSuite, + HkdfSha256, + PreSharedKey, +} from "../../../core/mod.ts"; +import { hexToBytes } from "../../../test/utils.ts"; + +import { HybridkemX25519Kyber768 } from "../mod.ts"; + +const TEST_VECTORS: Array = [ + { + mode: 0, + kem_id: 48, + kdf_id: 1, + aead_id: 1, + info: "486561722068656172", + ikmR: "3cb1eea988004b93103cfb0aeefd2a686e01fa4a58e8a3639ca8a1e3f9ae57e2", + pkRm: + "a3aa882fee0de0059cec0569c8e1b4872fb6cb4d82361b72ee1148dc7ddc0c2b210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5", + skRm: + "cf61f1a7b05c83f9c2a4b27dc0e9bdbf4e52ba1bbd906cb3776ac12268a9f4d0c348342d192f0458ab53d19c1dc135d11b48978c878bca6d7d1bc91428259e43aadc9700b76aa9aa66a65db91a77d72513e40697226557b53400bb6752fb4e11a5ba2fe12644698a48c9948ec121cc9c9ce7384c65f798012c9df8f5ac0cd371d7d19d9a24c30cb0909c665e43c89328735fe95a62653352fad3cfe6330b436a4f72c9ac9323babd912cf5970222eb0dd178c810bcc79beba0813039ec4333c33b13d4cc5183b6b14dc09cb9604d46242353a1a1df82999e4a4929f28f498c330d552ac64156cf123cceebbccf81b2fd86218f2a9112040943a359d7a858cd641467e54b25f03d66b9a150fbcf3f19bbd1791abec47269b2a72f4083a79c2559fbb6d0500208a78baa7392874443c39c38577b4c40db6220ca5c84b148ffb344164c723df1c0fb37ae52c0854bea023e2a45efaa8869c924ecf360008607e7079187978fdac30e8b76a3110349de272b25f5490dc87e3d8caf59a5a51a57230ea702dfda0f5d2c0fe94442254834b0f6aa71852601a8c5b7b211f108c1de2b1092e9b89df4a9feea882aa00ab97235940924e8a81d8f83597f72383f7a1b99c3c8481953be917fef0b44e32b0aa1f862eedc8d0d94030ae92e73097cde1b34de9b8279293322e0b5f9564395cb4998810818544ad2025018c40debf0b97bca2f1d861f8d5b51a2a84f35503cb37112b280ad0a4c99a2eb9c43300ce7c66eb89cb4443a44edc40869b8c2d90c5d484554557c408da7b46752bec14876815334b783207f60943d1738b5183d64394e27bb8f1dbb6ed9c58aa338171967bf5a613e9194c13395573615cee02012438a68aa104afd56a943d05caefc7f20a0104e2cced2a5191a1a68fe431920e1844a8154fc42a73d70c82f26846ec332fda50c340c1c5037965daaccd3cacfcab3c85a7516d712890fd6a2b1f5cb7c745cf1798dc0a49ed75717630c78d56bb8db272a85a009b2685ca9f4840c948226d224de1a0385565e569b8901c4508ae7b9214b88b6c2ac63807710d85e593a01ba20541cd03fa8364b4cb79f110745b30818521a7d0b6015a20483dde33188e94fc4aa224558bd53d384a9f6916964bbef0b0770b11e7b4117f41639bbe0c9ce119c8f8aab451608c2f06a8cd85f37519b7e3c1f9f07a6449059a972260cf80c23e52fe1b559e11c723b2618752672bfab67305358e7f048960475f1c720d8ba5fe4883981065c462c5062757bddd2666de67265990d0053229693a8bfd8811c84494853095c875639dcbcfcc02785910e35643f5bb4b0aa59af7a86ae94dcc01f952eb1d151c4ba1aa4da02c100b461904229f7b11aac35d307ab187255baa32eed32b3b262aaf2db6019089ad4250079280a0efb109ab27a364135ac3067ace5c82dea1fafb04dfedba9fabc196832878eb7b4314556e8aa8210e2c72959723e23176b703d4db42aabba62229790f6a743a2ec3c43dc8dbe0b4c36dc2323ec0ef21c116941b43bb12763460eed032a7a039185e36dcbf69d88f645e6728d3ba79dae0a25ddd4c3a8bba8334aa8fb6658a9dca99a8cc6362745d6080b0fd8af6af71e9f752d7b763035ec40c0fc98326081ea4c36cdf992e73a16719b9fb7c06e6c1bb7210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5137b60651b30bf805da1597faef1bc8b2645cda273144c4af1d13eaa2ad9101c7b58b14601aff81754afc776f8b7f7b9324d420b66706b96ea7f99f8fa11bed3", + ier: + "35b8cc873c23dc62b8d260169afa2f75ab916a58d974918835d25e6a435085b2badfd6dfaac359a5efbb7bcc4b59d538df9a04302e10c8bc1cbf1a0b3a5120ea", + enc: + "1d06980e46fd3842db6b87226231eedd2cc9684ee98a1d9d902bd9300e2c4d41b64fba47a50fe32dd0df3b0a75801c11022cd98a6ff5a83a8472ade82bdd6f1e8a65a94a88523ada0d8275165f707f1067a6a576e54525d9141e95223f5713456bda7ec5eb558adfc6b7f0d80de46222579a3274e45ab43fad14f7e9855a872d2716e8dc78d4c12027bef3184904476c8961552fd031361358f2d9deae8ad98194047a14222947612972574c57514266e9e67a3b6dd89972cc8a0882be7474f4923549dfcd944dcbe58b088079aa8b70c8f291cb4e45066bad4a832ccd8f40e51861f7a25b6a2358842f1bbe8108a6a6f0ae93153a2e7f9f53e180a90532531a632367b81bf08ed97effcf0140dd0e92cc438f6be7e6f3d97a9f7787f7e3981f971617f0bfb618caa7db1e453f33a386c3863b16d462229c41f4946b49e4e49c27e0f35d77e21304b6ad238a55a51e9e370dd39e713d626044fb970bb7c2af7d7b9cb9004394741a0ea2de592816359006f24abdfc2aa890720b00b2f7b8bb240120f22bdb84f9fc5c8fdc7ca7047ae633868c184c4d75e9e107eb9c6d8fe879415926457d818bc31e88b87a5881584a5650859e88b06faa2cfe1bde95dfa344af14f214cedecde4d89c87334c33e2d7ee3ab40d5df396cf0ff5a99588e0dcd205f1d876b380b963f5baccc0baeae569892a8d252f5eeeb7c751f663eb906ac99a165656224281add3ab271ff4f406b6932cbf1afff62109794f52ff3e723f5cdd706e3715d1d2d421bdac73fa047b5d9761569534fb2dd57b86a608f79db7d4ab99847490e76eaf0c683bdc54d12f2f2664a79de6a2f25bec3f43584f98ec41ad3fb19ba5ba936c3c893e9c0994b412ba3d07329086c20b04e1cd1d9b4f24a82f8c1f7b5db58b4056a4b4e27b60c957f5af8081bffab98d8455cab97e35042ed636c995931fd304b3d02fcf545df360cc421be64adc3d7a121ea75ab3440a9eba74fba1c5b40bdb66b54583ff2f76304ccaeae99ed94fb332d30d771fe0e45acb9e966f497b1629f5a5df15cea507d2fd1aa045a171e84bec932e4049639477f16fb9afdd107668f9b3531c3c7eb1d67753ac652c575b526e6f2965f1e4500e99f38ae1d34bce151a68e278f14405ad76f580b549d025b03be98b6a737f10238b9f84f1694173544ba2c97f811a17485129a146084bc5382e2086aaf51b11a4918bdb5485bf28a9be2d2c9d69468268fa04fa071c39942b43a0caf561278cfc1b47781fa9ef559f86b2dad703141b78b7ddb35c9c9ff4c1134580da26367dbf3db7eaf039dfbae238959c4cf55d40d78a2c5597ba038f2be5f994d60c79e8a92121fb0488eef9690d550ef9fa40b1774221aac8c8c1dcf97faa07c28e840feb9daf0bf3bed277a6e10a33490c0bee7e5fa318638f5b80a2272700e591ffc14985d0ed19876725c2bec9356b45ca96d295e30bce86effc626a2bd7839af05ae373801af510cfb378ce42088607909c91ceb4a90e4d7b2b6288b9cdfa262570ffda8692b58f0b05a7c7899a717a3a97b6e64489f56323b000793f807ca75ca991", + shared_secret: + "1368d71518fadbe42fb75fbd356e016b0aaad6b4d3d91ce7f207073e4fb08c537217aba238aea92a7f855820518a8342b3a31f82ebbcdb479f33ad82bdcdc953", + key_schedule_context: + "009f749a195d1c8b3eaa8d5c3f571dc7231aafbbc0405e4b484738352667c484867584e32e844cdf74d17b4ee224cc521bbc8bed221f21f34f8ccc9842772686cb", + secret: "95f863934be4d0ef683770c7bd385839d19e525b467a332f47ae715c54183e1d", + key: "6bb5532badb078ce8f326daa6cfaef84", + base_nonce: "ff2b9a604a84754614e9e772", + exporter_secret: + "fb6ca36cfb7881cf11dbcb8fde201f698f80d0b941b642bc0a6a3101c97b7fad", + encryptions: [ + { + pt: + "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", + aad: "436f756e742d30", + nonce: "ff2b9a604a84754614e9e772", + ct: + "a78ab8f057becc31cb3a5cff2fe2b18983b93ce74c6e7c45e0a57c4acc1976eef755c08547564ceede3e5169f959ea6ad498", + }, + { + pt: + "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", + aad: "436f756e742d31", + nonce: "ff2b9a604a84754614e9e773", + ct: + "c7e392bb20d256f0020ba0888996c4b0e2518b486ad5873263834e7f30fc43e6f712ee8e42846179db284a56baba6252d38f", + }, + { + pt: + "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", + aad: "436f756e742d32", + nonce: "ff2b9a604a84754614e9e770", + ct: + "86eeadf1593871435b643b7dde3d5a18b4dcb8d706c21abb69ae057af2a788a672198d46facadd396fa9fa6e1072c43a4f74", + }, + ], + exports: [ + { + exporter_context: "", + L: 32, + exported_value: + "0d15e6f37d0791a924c5b8a5c766db83d95703ddae889e6240c73926168ae6a8", + }, + { + exporter_context: "00", + L: 32, + exported_value: + "6f7bc144a46519b718c93a86a4ce74dad186816c88791eeee4f39fd0a2dbcef2", + }, + { + exporter_context: "54657374436f6e74657874", + L: 32, + exported_value: + "3a0064f88b02de081e9d5f4398093e016b00b01816db91f0686d50330a9886b2", + }, + ], + }, +]; + +describe("test-vectors", () => { + let count: number; + + beforeAll(() => { + count = 0; + }); + + afterAll(() => { + console.log(`passed/total: ${count}/${TEST_VECTORS.length}`); + }); + + describe("Hybridkem/X25519Kyber768/HkdfSha256/Aes128Gcm", () => { + it("should work normally", async () => { + for (const v of TEST_VECTORS) { + const suite = new CipherSuite({ + kem: new HybridkemX25519Kyber768(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const ikmR = hexToBytes(v.ikmR); + const pkRm = hexToBytes(v.pkRm); + const skRm = hexToBytes(v.skRm); + // const sharedSecret = hexToBytes(v.shared_secret); + + // deriveKeyPair + const rkp = await suite.kem.deriveKeyPair(ikmR); + const pkR = new Uint8Array( + await suite.kem.serializePublicKey(rkp.publicKey), + ); + const skR = new Uint8Array( + await suite.kem.serializePrivateKey(rkp.privateKey), + ); + assertEquals(skR, skRm); + assertEquals(pkR, pkRm); + + // create EncryptionContext + const info = hexToBytes(v.info); + let psk: PreSharedKey | undefined = undefined; + if (v.psk !== undefined && v.psk_id !== undefined) { + psk = { id: new ArrayBuffer(0), key: new ArrayBuffer(0) }; + psk.key = hexToBytes(v.psk); + psk.id = hexToBytes(v.psk_id); + } + const enc = hexToBytes(v.enc); + const ier = hexToBytes(v.ier); + + const sender = await suite.createSenderContext({ + info: info, + psk: psk, + recipientPublicKey: rkp.publicKey, + // senderKey: skp, + ekm: ier, // FOR DEBUGGING/TESTING PURPOSES ONLY. + }); + assertEquals(new Uint8Array(sender.enc), enc); + + const recipient = await suite.createRecipientContext({ + info: info, + psk: psk, + recipientKey: rkp, + enc: sender.enc, + // senderPublicKey: pks, + }); + + // seal and open + if (v.aead_id !== 0xFFFF) { + for (const ve of v.encryptions) { + const pt = hexToBytes(ve.pt); + const aad = hexToBytes(ve.aad); + const ct = hexToBytes(ve.ct); + + const sealed = await sender.seal(pt, aad); + const opened = await recipient.open(sealed, aad); + assertEquals(new Uint8Array(sealed), ct); + assertEquals(new Uint8Array(opened), pt); + } + } + + // export + for (const ve of v.exports) { + const ec = ve.exporter_context.length === 0 + ? new ArrayBuffer(0) + : hexToBytes(ve.exporter_context); + const ev = hexToBytes(ve.exported_value); + + let exported = await sender.export(ec, ve.L); + assertEquals(new Uint8Array(exported), ev); + exported = await recipient.export(ec, ve.L); + assertEquals(new Uint8Array(exported), ev); + } + count++; + } + }); + }); +}); diff --git a/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts b/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts index 21b2efdb1..7bc5deb43 100644 --- a/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts +++ b/x/hybridkem-x25519-kyber768/test/hybridkemX25519Kyber768.test.ts @@ -1,8 +1,5 @@ -// import { assertEquals, assertRejects } from "testing/asserts.ts"; import { assertEquals } from "testing/asserts.ts"; -import { afterAll, beforeAll, describe, it } from "testing/bdd.ts"; - -import type { TestVector } from "../../../test/testVector.ts"; +import { describe, it } from "testing/bdd.ts"; import { AeadId, @@ -11,11 +8,7 @@ import { HkdfSha256, KdfId, KemId, - PreSharedKey, } from "../../../core/mod.ts"; -// import { Kyber768 } from "../../../src/kems/pqkemPrimitives/kyber768.ts"; -// import { DhkemX25519HkdfSha256 } from "../../../src/kems/dhkemX25519.ts"; -import { hexToBytes, testVectorPath } from "../../../test/utils.ts"; import { HybridkemX25519Kyber768 } from "../mod.ts"; describe("constructor", () => { @@ -73,103 +66,3 @@ describe("README examples", () => { }); }); }); - -describe("test-vectors", () => { - let count: number; - let testVectors: TestVector[]; - - beforeAll(async () => { - count = 0; - testVectors = JSON.parse( - await Deno.readTextFile(testVectorPath() + "/test-vectors.json"), - ); - }); - - afterAll(() => { - console.log(`passed/total: ${count}/${testVectors.length}`); - }); - - describe("Hybridkem/X25519Kyber768/HkdfSha256/Aes128Gcm", () => { - it("should work normally", async () => { - for (const v of testVectors) { - const suite = new CipherSuite({ - kem: new HybridkemX25519Kyber768(), - kdf: new HkdfSha256(), - aead: new Aes128Gcm(), - }); - - const ikmR = hexToBytes(v.ikmR); - const pkRm = hexToBytes(v.pkRm); - const skRm = hexToBytes(v.skRm); - // const sharedSecret = hexToBytes(v.shared_secret); - - // deriveKeyPair - const rkp = await suite.kem.deriveKeyPair(ikmR); - const pkR = new Uint8Array( - await suite.kem.serializePublicKey(rkp.publicKey), - ); - const skR = new Uint8Array( - await suite.kem.serializePrivateKey(rkp.privateKey), - ); - assertEquals(skR, skRm); - assertEquals(pkR, pkRm); - - // create EncryptionContext - const info = hexToBytes(v.info); - let psk: PreSharedKey | undefined = undefined; - if (v.psk !== undefined && v.psk_id !== undefined) { - psk = { id: new ArrayBuffer(0), key: new ArrayBuffer(0) }; - psk.key = hexToBytes(v.psk); - psk.id = hexToBytes(v.psk_id); - } - const enc = hexToBytes(v.enc); - const ier = hexToBytes(v.ier); - - const sender = await suite.createSenderContext({ - info: info, - psk: psk, - recipientPublicKey: rkp.publicKey, - // senderKey: skp, - ekm: ier, // FOR DEBUGGING/TESTING PURPOSES ONLY. - }); - assertEquals(new Uint8Array(sender.enc), enc); - - const recipient = await suite.createRecipientContext({ - info: info, - psk: psk, - recipientKey: rkp, - enc: sender.enc, - // senderPublicKey: pks, - }); - - // seal and open - if (v.aead_id !== 0xFFFF) { - for (const ve of v.encryptions) { - const pt = hexToBytes(ve.pt); - const aad = hexToBytes(ve.aad); - const ct = hexToBytes(ve.ct); - - const sealed = await sender.seal(pt, aad); - const opened = await recipient.open(sealed, aad); - assertEquals(new Uint8Array(sealed), ct); - assertEquals(new Uint8Array(opened), pt); - } - } - - // export - for (const ve of v.exports) { - const ec = ve.exporter_context.length === 0 - ? new ArrayBuffer(0) - : hexToBytes(ve.exporter_context); - const ev = hexToBytes(ve.exported_value); - - let exported = await sender.export(ec, ve.L); - assertEquals(new Uint8Array(exported), ev); - exported = await recipient.export(ec, ve.L); - assertEquals(new Uint8Array(exported), ev); - } - count++; - } - }); - }); -}); diff --git a/x/hybridkem-x25519-kyber768/test/testVector.ts b/x/hybridkem-x25519-kyber768/test/testVector.ts new file mode 100644 index 000000000..186731a1f --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/testVector.ts @@ -0,0 +1,61 @@ +import type { AeadId, KdfId, KemId } from "../../../src/identifiers.ts"; + +interface TestVectorEncryption { + aad: string; + ct: string; + nonce: string; + pt: string; +} + +interface TestVectorExport { + exporter_context: string; + L: number; + exported_value: string; +} + +export interface TestVector { + mode: number; + kem_id: KemId; + kdf_id: KdfId; + aead_id: AeadId; + psk_id?: string; + psk?: string; + info: string; + ikmR: string; + ikmE?: string; + skRm: string; + skSm?: string; + skEm?: string; + pkRm: string; + pkSm?: string; + pkEm?: string; + ier: string; + enc: string; + shared_secret: string; + key_schedule_context: string; + secret: string; + key: string; + base_nonce: string; + exporter_secret: string; + encryptions: Array; + exports: Array; +} + +// The minimum interface to load test vectors on https://github.com/google/wycheproof +interface WycheproofTestCase { + tcId: number; + public: string; + flags: Array; + result: string; +} + +// The minimum interface to load test vectors on https://github.com/google/wycheproof +interface WycheproofTestGroup { + tests: Array; +} + +// The minimum interface to load test vectors on https://github.com/google/wycheproof +export interface WycheproofTestVector { + numberOfTests: number; + testGroups: Array; +} diff --git a/x/hybridkem-x25519-kyber768/test/vectors/test-vectors.json b/x/hybridkem-x25519-kyber768/test/vectors/test-vectors.json deleted file mode 100644 index e36e570ed..000000000 --- a/x/hybridkem-x25519-kyber768/test/vectors/test-vectors.json +++ /dev/null @@ -1,57 +0,0 @@ -[ - { - "mode": 0, - "kem_id": 48, - "kdf_id": 1, - "aead_id": 1, - "info": "486561722068656172", - "ikmR": "3cb1eea988004b93103cfb0aeefd2a686e01fa4a58e8a3639ca8a1e3f9ae57e2", - "pkRm": "a3aa882fee0de0059cec0569c8e1b4872fb6cb4d82361b72ee1148dc7ddc0c2b210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5", - "skRm": "cf61f1a7b05c83f9c2a4b27dc0e9bdbf4e52ba1bbd906cb3776ac12268a9f4d0c348342d192f0458ab53d19c1dc135d11b48978c878bca6d7d1bc91428259e43aadc9700b76aa9aa66a65db91a77d72513e40697226557b53400bb6752fb4e11a5ba2fe12644698a48c9948ec121cc9c9ce7384c65f798012c9df8f5ac0cd371d7d19d9a24c30cb0909c665e43c89328735fe95a62653352fad3cfe6330b436a4f72c9ac9323babd912cf5970222eb0dd178c810bcc79beba0813039ec4333c33b13d4cc5183b6b14dc09cb9604d46242353a1a1df82999e4a4929f28f498c330d552ac64156cf123cceebbccf81b2fd86218f2a9112040943a359d7a858cd641467e54b25f03d66b9a150fbcf3f19bbd1791abec47269b2a72f4083a79c2559fbb6d0500208a78baa7392874443c39c38577b4c40db6220ca5c84b148ffb344164c723df1c0fb37ae52c0854bea023e2a45efaa8869c924ecf360008607e7079187978fdac30e8b76a3110349de272b25f5490dc87e3d8caf59a5a51a57230ea702dfda0f5d2c0fe94442254834b0f6aa71852601a8c5b7b211f108c1de2b1092e9b89df4a9feea882aa00ab97235940924e8a81d8f83597f72383f7a1b99c3c8481953be917fef0b44e32b0aa1f862eedc8d0d94030ae92e73097cde1b34de9b8279293322e0b5f9564395cb4998810818544ad2025018c40debf0b97bca2f1d861f8d5b51a2a84f35503cb37112b280ad0a4c99a2eb9c43300ce7c66eb89cb4443a44edc40869b8c2d90c5d484554557c408da7b46752bec14876815334b783207f60943d1738b5183d64394e27bb8f1dbb6ed9c58aa338171967bf5a613e9194c13395573615cee02012438a68aa104afd56a943d05caefc7f20a0104e2cced2a5191a1a68fe431920e1844a8154fc42a73d70c82f26846ec332fda50c340c1c5037965daaccd3cacfcab3c85a7516d712890fd6a2b1f5cb7c745cf1798dc0a49ed75717630c78d56bb8db272a85a009b2685ca9f4840c948226d224de1a0385565e569b8901c4508ae7b9214b88b6c2ac63807710d85e593a01ba20541cd03fa8364b4cb79f110745b30818521a7d0b6015a20483dde33188e94fc4aa224558bd53d384a9f6916964bbef0b0770b11e7b4117f41639bbe0c9ce119c8f8aab451608c2f06a8cd85f37519b7e3c1f9f07a6449059a972260cf80c23e52fe1b559e11c723b2618752672bfab67305358e7f048960475f1c720d8ba5fe4883981065c462c5062757bddd2666de67265990d0053229693a8bfd8811c84494853095c875639dcbcfcc02785910e35643f5bb4b0aa59af7a86ae94dcc01f952eb1d151c4ba1aa4da02c100b461904229f7b11aac35d307ab187255baa32eed32b3b262aaf2db6019089ad4250079280a0efb109ab27a364135ac3067ace5c82dea1fafb04dfedba9fabc196832878eb7b4314556e8aa8210e2c72959723e23176b703d4db42aabba62229790f6a743a2ec3c43dc8dbe0b4c36dc2323ec0ef21c116941b43bb12763460eed032a7a039185e36dcbf69d88f645e6728d3ba79dae0a25ddd4c3a8bba8334aa8fb6658a9dca99a8cc6362745d6080b0fd8af6af71e9f752d7b763035ec40c0fc98326081ea4c36cdf992e73a16719b9fb7c06e6c1bb7210747403222b16597f4881d694c12366c53fde2b3d346b7ee87b16dd42f44ec594cea6ba78b256092cbbc16baaf6ccc46f2386da22de9d142f593739eb9c245018e0c61975514ac42639d3c5b0299b772acd59d55520a5d660f135075e33a673fd5b9e2d56803889fc62b0362f8cbe9990cb36b4cdef17586c8cc58d72d84fb9398f1c1efb0a6282508083c23965a9851acb89afc723e7a6c60bc4007a41ad1950c4590a2f8d2bb3b832f5db1707ad8bad1c4c426aaa7da97b34a921283415851f19b0f01ca3924754dba6596f9329454b1e3d9b5f357a66c59bf5fc4a045908b5eb107d3302f0cb9be0af9584846c1475b92d3c16051935dc7411acaa64c80c836b0643fd72b38cb0a33feb11f4813b66f705268b3838b8974e28c12b4f9bbc8623c936b32a015262d4a33172b7f3a69b6c2fab5a3c18ffdab2927e77598d1556d51a8559550c251796290b617ac9804167bd9a76e9d8bba64059d165acfe2483e9ed0cbc11cb71dd148776aa1cb862ce2b1026e773600d101a300671a70710a877a5c1732275c362085b2b8cc66206b3ec37c82ac873d1ec1862a8aa457fc9776960b396c23768c931cdc77731792c569c2088c52ddb5cc0c90ab9187c1e0ca2c98818859aa86fe44801be483cc1469d636cd3e019267c1cc684640359ca67c5abd1dc100c4d3c5924acf1b988d3b5019e7b06ef238412b7608dd23115c6047a59b4b1d7a731126925728c645c140aa4704c1b808b6c401be736bf18bb7d654342c6576236565c6c5b0727b25ae773c5fb76be794304dc1b672aa5909659b6bb8a1f430a141882b0f9753662794e625885782154dc148e632b6b2079087958d83c6c82cf55a47eb4ed819a409d94ceb0c74e8d497b95975a0a5c659f5bf0a033d2adca98a693304413fff95342319a09fd62f263b91a2c6540d2196dd2ba90dd113042428aeeb15156c03949660776b80bc1501b0d80a946a623906291ed3668f3c99c1889d3ae3c59819c38f6b0c46558c2ca520c2107c166452b917cea53bb50c4cb839a99f60e54e9236c6a419a8de5508f4e3545409499b97939ee940a9d48ed5547003350e391b4c96d657cb395b5c035370e9c8ece32c83b3cff347ca16bb1e2943669f370f48e70462d4369a07804bc09fcf399bc2d11b47b0370660916944a179423519a310cc0737407c55ef09255530c7ec817999c95e20aa23f8f6782aa820d34c89c2299ff0ec9a9021b6f7dbbd19503fa6f170d8770e12875d558bbb2ca66fd1136e0e5729ef30346109cd289a1ce0c531a493581ed64533e1749fc818b85ab664255bbfe4a641f6bdf43ac1695c28ab2b58b3bab5bed5893439455b669b63d65ceff75b8c5857f4ba5cf767cf57aa8e28691cc6dc67fca434e3b1560c6c53ce37c2a2f14764c1cf1e5697cd8757a544b05b766f4400cef7ecc46ec29a1d679d7fe385c4366579db06d1d840c9911fab8b6b5df2035cb95410f79b861411b4eb5a4119208f8872674639617452f6b6394c94c6d6f5b833690dd98406b5e7c0827b1a3617a03ba90c3d185a954252f1ba5b157a3f61749548e281fc543dec205e757932bcc717b99b7df7123500f3bcc660c080093b3fbac56ff51b9c3b037f76e3f43c0e46b5588cf617f4de85044390a9947daacba87cd5137b60651b30bf805da1597faef1bc8b2645cda273144c4af1d13eaa2ad9101c7b58b14601aff81754afc776f8b7f7b9324d420b66706b96ea7f99f8fa11bed3", - "ier": "35b8cc873c23dc62b8d260169afa2f75ab916a58d974918835d25e6a435085b2badfd6dfaac359a5efbb7bcc4b59d538df9a04302e10c8bc1cbf1a0b3a5120ea", - "enc": "1d06980e46fd3842db6b87226231eedd2cc9684ee98a1d9d902bd9300e2c4d41b64fba47a50fe32dd0df3b0a75801c11022cd98a6ff5a83a8472ade82bdd6f1e8a65a94a88523ada0d8275165f707f1067a6a576e54525d9141e95223f5713456bda7ec5eb558adfc6b7f0d80de46222579a3274e45ab43fad14f7e9855a872d2716e8dc78d4c12027bef3184904476c8961552fd031361358f2d9deae8ad98194047a14222947612972574c57514266e9e67a3b6dd89972cc8a0882be7474f4923549dfcd944dcbe58b088079aa8b70c8f291cb4e45066bad4a832ccd8f40e51861f7a25b6a2358842f1bbe8108a6a6f0ae93153a2e7f9f53e180a90532531a632367b81bf08ed97effcf0140dd0e92cc438f6be7e6f3d97a9f7787f7e3981f971617f0bfb618caa7db1e453f33a386c3863b16d462229c41f4946b49e4e49c27e0f35d77e21304b6ad238a55a51e9e370dd39e713d626044fb970bb7c2af7d7b9cb9004394741a0ea2de592816359006f24abdfc2aa890720b00b2f7b8bb240120f22bdb84f9fc5c8fdc7ca7047ae633868c184c4d75e9e107eb9c6d8fe879415926457d818bc31e88b87a5881584a5650859e88b06faa2cfe1bde95dfa344af14f214cedecde4d89c87334c33e2d7ee3ab40d5df396cf0ff5a99588e0dcd205f1d876b380b963f5baccc0baeae569892a8d252f5eeeb7c751f663eb906ac99a165656224281add3ab271ff4f406b6932cbf1afff62109794f52ff3e723f5cdd706e3715d1d2d421bdac73fa047b5d9761569534fb2dd57b86a608f79db7d4ab99847490e76eaf0c683bdc54d12f2f2664a79de6a2f25bec3f43584f98ec41ad3fb19ba5ba936c3c893e9c0994b412ba3d07329086c20b04e1cd1d9b4f24a82f8c1f7b5db58b4056a4b4e27b60c957f5af8081bffab98d8455cab97e35042ed636c995931fd304b3d02fcf545df360cc421be64adc3d7a121ea75ab3440a9eba74fba1c5b40bdb66b54583ff2f76304ccaeae99ed94fb332d30d771fe0e45acb9e966f497b1629f5a5df15cea507d2fd1aa045a171e84bec932e4049639477f16fb9afdd107668f9b3531c3c7eb1d67753ac652c575b526e6f2965f1e4500e99f38ae1d34bce151a68e278f14405ad76f580b549d025b03be98b6a737f10238b9f84f1694173544ba2c97f811a17485129a146084bc5382e2086aaf51b11a4918bdb5485bf28a9be2d2c9d69468268fa04fa071c39942b43a0caf561278cfc1b47781fa9ef559f86b2dad703141b78b7ddb35c9c9ff4c1134580da26367dbf3db7eaf039dfbae238959c4cf55d40d78a2c5597ba038f2be5f994d60c79e8a92121fb0488eef9690d550ef9fa40b1774221aac8c8c1dcf97faa07c28e840feb9daf0bf3bed277a6e10a33490c0bee7e5fa318638f5b80a2272700e591ffc14985d0ed19876725c2bec9356b45ca96d295e30bce86effc626a2bd7839af05ae373801af510cfb378ce42088607909c91ceb4a90e4d7b2b6288b9cdfa262570ffda8692b58f0b05a7c7899a717a3a97b6e64489f56323b000793f807ca75ca991", - "shared_secret": "1368d71518fadbe42fb75fbd356e016b0aaad6b4d3d91ce7f207073e4fb08c537217aba238aea92a7f855820518a8342b3a31f82ebbcdb479f33ad82bdcdc953", - "key_schedule_context": "009f749a195d1c8b3eaa8d5c3f571dc7231aafbbc0405e4b484738352667c484867584e32e844cdf74d17b4ee224cc521bbc8bed221f21f34f8ccc9842772686cb", - "secret": "95f863934be4d0ef683770c7bd385839d19e525b467a332f47ae715c54183e1d", - "key": "6bb5532badb078ce8f326daa6cfaef84", - "base_nonce": "ff2b9a604a84754614e9e772", - "exporter_secret": "fb6ca36cfb7881cf11dbcb8fde201f698f80d0b941b642bc0a6a3101c97b7fad", - "encryptions": [ - { - "pt": "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", - "aad": "436f756e742d30", - "nonce": "ff2b9a604a84754614e9e772", - "ct": "a78ab8f057becc31cb3a5cff2fe2b18983b93ce74c6e7c45e0a57c4acc1976eef755c08547564ceede3e5169f959ea6ad498" - }, - { - "pt": "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", - "aad": "436f756e742d31", - "nonce": "ff2b9a604a84754614e9e773", - "ct": "c7e392bb20d256f0020ba0888996c4b0e2518b486ad5873263834e7f30fc43e6f712ee8e42846179db284a56baba6252d38f" - }, - { - "pt": "546f2074686520756e6976657273616c206465706c6f796d656e74206f6620505143", - "aad": "436f756e742d32", - "nonce": "ff2b9a604a84754614e9e770", - "ct": "86eeadf1593871435b643b7dde3d5a18b4dcb8d706c21abb69ae057af2a788a672198d46facadd396fa9fa6e1072c43a4f74" - } - ], - "exports": [ - { - "exporter_context": "", - "L": 32, - "exported_value": "0d15e6f37d0791a924c5b8a5c766db83d95703ddae889e6240c73926168ae6a8" - }, - { - "exporter_context": "00", - "L": 32, - "exported_value": "6f7bc144a46519b718c93a86a4ce74dad186816c88791eeee4f39fd0a2dbcef2" - }, - { - "exporter_context": "54657374436f6e74657874", - "L": 32, - "exported_value": "3a0064f88b02de081e9d5f4398093e016b00b01816db91f0686d50330a9886b2" - } - ] - } -] From ec6a1604e767a11e5c13bc44f47a6a080bc1c7ba Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Sat, 19 Aug 2023 01:53:54 +0900 Subject: [PATCH 10/14] Add js runtime test for HybridkemX25519Kyber768. --- .../hybridkem-x25519-kyber768.spec.ts | 9 ++ .../test/runtimes/browsers/package.json | 5 + .../test/runtimes/browsers/pages/index.html | 103 ++++++++++++++++++ .../test/runtimes/browsers/pages/src/.gitkeep | 0 .../runtimes/browsers/playwright.config.ts | 20 ++++ .../bun/hybridkem-x25519-kyber768.spec.ts | 17 +++ .../test/runtimes/bun/src/index.js | 8 ++ .../hybridkem-x25519-kyber768.spec.ts | 17 +++ .../test/runtimes/cloudflare/package.json | 12 ++ .../test/runtimes/cloudflare/src/index.js | 7 ++ .../test/runtimes/cloudflare/wrangler.toml | 4 + .../test/runtimes/server.js | 103 ++++++++++++++++++ 12 files changed, 305 insertions(+) create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/browsers/package.json create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/browsers/pages/index.html create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/browsers/pages/src/.gitkeep create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/browsers/playwright.config.ts create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/package.json create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/src/index.js create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/wrangler.toml create mode 100644 x/hybridkem-x25519-kyber768/test/runtimes/server.js diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts new file mode 100644 index 000000000..bf0a648c6 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts @@ -0,0 +1,9 @@ +import { expect, test } from "@playwright/test"; + +test("basic test", async ({ page }) => { + await page.goto("https://dajiaji.github.io/hpke-js/hybridkem-x25519-kyber768"); + await page.click("text=run"); + await page.waitForTimeout(5000); + await expect(page.locator("id=pass")).toHaveText("6"); + await expect(page.locator("id=fail")).toHaveText("0"); +}); diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/browsers/package.json b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/package.json new file mode 100644 index 000000000..22ec1425b --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@playwright/test": "^1.36.1" + } +} diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/browsers/pages/index.html b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/pages/index.html new file mode 100644 index 000000000..99849ff5f --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/pages/index.html @@ -0,0 +1,103 @@ + + @hpke/hybridkem-x25519-kyber768 test + + + +

@hpke/hybridkem-x25519-kyber768 test

+ +
+ + +
+ +
+ +
+ + + + + + + + + +
pass: -
fail: -
+
+ + diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/browsers/pages/src/.gitkeep b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/pages/src/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/browsers/playwright.config.ts b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/playwright.config.ts new file mode 100644 index 000000000..ec692f847 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/playwright.config.ts @@ -0,0 +1,20 @@ +// playwright.config.ts +import { devices, PlaywrightTestConfig } from "@playwright/test"; + +const config: PlaywrightTestConfig = { + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + ], +}; +export default config; diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts b/x/hybridkem-x25519-kyber768/test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts new file mode 100644 index 000000000..958eb5b2d --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts @@ -0,0 +1,17 @@ +import { assertEquals } from "testing/asserts.ts"; +import { describe, it } from "testing/bdd.ts"; + +describe("Bun", () => { + describe("GET /test", () => { + it("should return ok", async () => { + for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + for (const aead of ["0x0001", "0x0002"]) { + const res = await fetch( + `http://localhost:3005/test?kdf=${kdf}&aead=${aead}`, + ); + assertEquals("ok", await res.text()); + } + } + }); + }); +}); diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js b/x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js new file mode 100644 index 000000000..4ea92425a --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js @@ -0,0 +1,8 @@ +import { testServer } from "../../server.js"; + +export default { + port: 3005, + async fetch(request) { + return await testServer(request); + }, +}; diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts new file mode 100644 index 000000000..63c92b264 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts @@ -0,0 +1,17 @@ +import { assertEquals } from "testing/asserts.ts"; +import { describe, it } from "testing/bdd.ts"; + +describe("Cloudflare Workers", () => { + describe("GET /test", () => { + it("should return ok", async () => { + for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + for (const aead of ["0x0001", "0x0002"]) { + const res = await fetch( + `http://localhost:8792/test?kdf=${kdf}&aead=${aead}`, + ); + assertEquals("ok", await res.text()); + } + } + }); + }); +}); diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/package.json b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/package.json new file mode 100644 index 000000000..6c61caf34 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/package.json @@ -0,0 +1,12 @@ +{ + "name": "wrangler", + "version": "0.1.0", + "devDependencies": { + "wrangler": "3.3.0" + }, + "private": true, + "scripts": { + "start": "wrangler dev --port 8792", + "deploy": "wrangler publish" + } +} diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/src/index.js b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/src/index.js new file mode 100644 index 000000000..2b1b697ea --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/src/index.js @@ -0,0 +1,7 @@ +import { testServer } from "../../server.js"; + +export default { + async fetch(request) { + return await testServer(request); + }, +}; diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/wrangler.toml b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/wrangler.toml new file mode 100644 index 000000000..a13a96a4d --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/wrangler.toml @@ -0,0 +1,4 @@ +name = "wrangler" +main = "src/index.js" +compatibility_date = "2022-07-01" +node_compat = false diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/server.js b/x/hybridkem-x25519-kyber768/test/runtimes/server.js new file mode 100644 index 000000000..b4b68f685 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/test/runtimes/server.js @@ -0,0 +1,103 @@ +import { + AeadId, + Aes128Gcm, + Aes256Gcm, + CipherSuite, + // DhkemP256HkdfSha256, + // DhkemP384HkdfSha384, + // DhkemP521HkdfSha512, + ExportOnly, + HkdfSha256, + HkdfSha384, + HkdfSha512, + KdfId, +} from "./hpke-core.js"; + +import { HybridkemX25519Kyber768 } from "./hpke-hybridkem-x25519-kyber768.js"; + +// function createKem(id) { +// switch (id) { +// case KemId.DhkemP256HkdfSha256: +// return new DhkemP256HkdfSha256(); +// case KemId.DhkemP384HkdfSha384: +// return new DhkemP384HkdfSha384(); +// case KemId.DhkemP521HkdfSha512: +// return new DhkemP521HkdfSha512(); +// default: +// break; +// } +// throw new Error("ng: invalid kem"); +// } + +function createKdf(id) { + switch (id) { + case KdfId.HkdfSha256: + return new HkdfSha256(); + case KdfId.HkdfSha384: + return new HkdfSha384(); + case KdfId.HkdfSha512: + return new HkdfSha512(); + default: + break; + } + throw new Error("ng: invalid kdf"); +} + +function createAead(id) { + switch (id) { + case AeadId.Aes128Gcm: + return new Aes128Gcm(); + case AeadId.Aes256Gcm: + return new Aes256Gcm(); + // case AeadId.Chacha20Poly1305: + // return new Chacha20Poly1305(); + case AeadId.ExportOnly: + return new ExportOnly(); + default: + break; + } + throw new Error("ng: invalid aead"); +} + +export async function testServer(request) { + const url = new URL(request.url); + if (url.pathname !== "/test") { + return new Response("ng: invalid path"); + } + const params = url.searchParams; + const kdfStr = params.get("kdf"); + const aeadStr = params.get("aead"); + if (kdfStr === null || aeadStr === null) { + return new Response("ng: invalid params"); + } + const kem = new HybridkemX25519Kyber768(); + const kdf = Number.parseInt(kdfStr); + const aead = Number.parseInt(aeadStr); + if (Number.isNaN(kdf) || Number.isNaN(aead)) { + return new Response("ng: invalid params"); + } + + try { + const suite = new CipherSuite({ + kem: kem, + kdf: createKdf(kdf), + aead: createAead(aead), + }); + const rkp = await suite.kem.generateKeyPair(); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + const ct = await sender.seal(new TextEncoder().encode("hello world!")); + const pt = await recipient.open(ct); + if ("hello world!" !== new TextDecoder().decode(pt)) { + return new Response("ng"); + } + } catch (e) { + return new Response("ng: " + e.message); + } + return new Response("ok"); +} From 9f4119d571c012105b50e5338762f5ebb80af292 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Sat, 19 Aug 2023 02:04:40 +0900 Subject: [PATCH 11/14] Add CI/CD for HybridkemX25519Kyber768. --- .github/workflows/ci.yml | 5 +++++ .github/workflows/ci_browser.yml | 10 ++++++++++ .github/workflows/ci_bun.yml | 11 +++++++++++ .github/workflows/ci_cloudflare.yml | 12 ++++++++++++ .github/workflows/ci_node.yml | 5 +++++ .github/workflows/publish.yml | 21 +++++++++++++++++++++ 6 files changed, 64 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9898edb09..5e4ceca0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,11 @@ jobs: run: | deno fmt --check deno task test + - name: Run deno test for /x/hybridkem-x25519-kyber768 + working-directory: ./x/hybridkem-x25519-kyber768 + run: | + deno fmt --check + deno task test - name: Run deno test for /x/dhkem-secp256k1 working-directory: ./x/dhkem-secp256k1 run: | diff --git a/.github/workflows/ci_browser.yml b/.github/workflows/ci_browser.yml index 1fd8b50a3..ae14d3608 100644 --- a/.github/workflows/ci_browser.yml +++ b/.github/workflows/ci_browser.yml @@ -35,6 +35,8 @@ jobs: cp -rf x/dhkem-x448/test/runtimes/browsers/pages/* test/runtimes/browsers/pages/dhkem-x448 mkdir test/runtimes/browsers/pages/chacha20poly1305 cp -rf x/chacha20poly1305/test/runtimes/browsers/pages/* test/runtimes/browsers/pages/chacha20poly1305 + mkdir test/runtimes/browsers/pages/hybridkem-x25519-kyber768 + cp -rf x/hybridkem-x25519-kyber768/test/runtimes/browsers/pages/* test/runtimes/browsers/pages/hybridkem-x25519-kyber768 mkdir test/runtimes/browsers/pages/dhkem-secp256k1 cp -rf x/dhkem-secp256k1/test/runtimes/browsers/pages/* test/runtimes/browsers/pages/dhkem-secp256k1 - working-directory: ./core @@ -45,6 +47,7 @@ jobs: deno task minify > ../test/runtimes/browsers/pages/dhkem-x25519/src/hpke-core.js deno task minify > ../test/runtimes/browsers/pages/dhkem-x448/src/hpke-core.js deno task minify > ../test/runtimes/browsers/pages/chacha20poly1305/src/hpke-core.js + deno task minify > ../test/runtimes/browsers/pages/hybridkem-x25519-kyber768/src/hpke-core.js deno task minify > ../test/runtimes/browsers/pages/dhkem-secp256k1/src/hpke-core.js - working-directory: ./x/dhkem-x25519 run: | @@ -61,6 +64,11 @@ jobs: npx typedoc --name "@hpke/chacha20poly1305 $(git describe --tags --abbrev=0)" --out ../../test/runtimes/browsers/pages/chacha20poly1305/docs mod.ts deno task dnt deno task minify > ../../test/runtimes/browsers/pages/chacha20poly1305/src/hpke-chacha20poly1305.js + - working-directory: ./x/hybridkem-x25519-kyber768 + run: | + npx typedoc --name "@hpke/hybridkem-x25519-kyber768 $(git describe --tags --abbrev=0)" --out ../../test/runtimes/browsers/pages/hybridkem-x25519-kyber768/docs mod.ts + deno task dnt + deno task minify > ../../test/runtimes/browsers/pages/hybridkem-x25519-kyber768/src/hpke-hybridkem-x25519-kyber768.js - working-directory: ./x/dhkem-secp256k1 run: | npx typedoc --name "@hpke/dhkem-secp256k1 $(git describe --tags --abbrev=0)" --out ../../test/runtimes/browsers/pages/dhkem-secp256k1/docs mod.ts @@ -91,5 +99,7 @@ jobs: run: npm install && npx playwright install && npx playwright test - working-directory: ./x/chacha20poly1305/test/runtimes/browsers run: npm install && npx playwright install && npx playwright test + - working-directory: ./x/hybridkem-x25519-kyber768/test/runtimes/browsers + run: npm install && npx playwright install && npx playwright test - working-directory: ./x/dhkem-secp256k1/test/runtimes/browsers run: npm install && npx playwright install && npx playwright test diff --git a/.github/workflows/ci_bun.yml b/.github/workflows/ci_bun.yml index 13d3402ef..f2a55d491 100644 --- a/.github/workflows/ci_bun.yml +++ b/.github/workflows/ci_bun.yml @@ -45,6 +45,7 @@ jobs: deno task minify > ../x/dhkem-x25519/test/runtimes/hpke-core.js deno task minify > ../x/dhkem-x448/test/runtimes/hpke-core.js deno task minify > ../x/chacha20poly1305/test/runtimes/hpke-core.js + deno task minify > ../x/hybridkem-x25519-kyber768/test/runtimes/hpke-core.js deno task minify > ../x/dhkem-secp256k1/test/runtimes/hpke-core.js - name: Run test for core working-directory: ./core/test/runtimes/bun @@ -82,6 +83,16 @@ jobs: nohup bun src/index.js & sleep 3 deno test chacha20poly1305.spec.ts --allow-net + - working-directory: ./x/hybridkem-x25519-kyber768 + run: | + deno task dnt + deno task minify > test/runtimes/hpke-hybridkem-x25519-kyber768.js + - name: Run test for hybridkem-x25519-kyber768 + working-directory: ./x/hybridkem-x25519-kyber768/test/runtimes/bun + run: | + nohup bun src/index.js & + sleep 3 + deno test hybridkem-x25519-kyber768.spec.ts --allow-net - working-directory: ./x/dhkem-secp256k1/ run: | deno task dnt diff --git a/.github/workflows/ci_cloudflare.yml b/.github/workflows/ci_cloudflare.yml index 3186b1502..12b70dd38 100644 --- a/.github/workflows/ci_cloudflare.yml +++ b/.github/workflows/ci_cloudflare.yml @@ -38,6 +38,7 @@ jobs: deno task minify > ../x/dhkem-x25519/test/runtimes/hpke-core.js deno task minify > ../x/dhkem-x448/test/runtimes/hpke-core.js deno task minify > ../x/chacha20poly1305/test/runtimes/hpke-core.js + deno task minify > ../x/hybridkem-x25519-kyber768/test/runtimes/hpke-core.js deno task minify > ../x/dhkem-secp256k1/test/runtimes/hpke-core.js - name: Run test for core working-directory: ./core/test/runtimes/cloudflare @@ -79,6 +80,17 @@ jobs: nohup npm start & sleep 3 deno test chacha20poly1305.spec.ts --allow-net + - working-directory: ./x/hybridkem-x25519-kyber768 + run: | + deno task dnt + deno task minify > test/runtimes/hpke-hybridkem-x25519-kyber768.js + - name: Run test for hybridkem-x25519-kyber768 + working-directory: ./x/hybridkem-x25519-kyber768/test/runtimes/cloudflare + run: | + npm install + nohup npm start & + sleep 3 + deno test hybridkem-x25519-kyber768.spec.ts --allow-net - working-directory: ./x/dhkem-secp256k1 run: | deno task dnt diff --git a/.github/workflows/ci_node.yml b/.github/workflows/ci_node.yml index acfa5a443..6908c8a04 100644 --- a/.github/workflows/ci_node.yml +++ b/.github/workflows/ci_node.yml @@ -47,6 +47,11 @@ jobs: run: | deno task dnt deno task minify > ./npm/hpke-chacha20poly1305.min.js + - name: Run dnt & minify for /x/hybridkem-x25519-kyber768 + working-directory: ./x/hybridkem-x25519-kyber768 + run: | + deno task dnt + deno task minify > ./npm/hpke-hybridkem-x25519-kyber768.min.js - name: Run dnt & minify for /x/dhkem-secp256k1 working-directory: ./x/dhkem-secp256k1 run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 452de010e..a1fd69d94 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -109,6 +109,27 @@ jobs: env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + publish-hybridkem-x25519-kyber768: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - name: Run dnt + working-directory: ./x/hybridkem-x25519-kyber768 + run: | + npm install -g esbuild + deno task dnt + - working-directory: ./x/hybridkem-x25519-kyber768/npm + run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + publish-dhkem-secp256k1: runs-on: ubuntu-latest steps: From 7ccd25c607c679ff63110cf1193d9923d12c523d Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Sat, 19 Aug 2023 02:09:05 +0900 Subject: [PATCH 12/14] Add tsconfig.json for tsdoc. --- x/hybridkem-x25519-kyber768/tsconfig.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 x/hybridkem-x25519-kyber768/tsconfig.json diff --git a/x/hybridkem-x25519-kyber768/tsconfig.json b/x/hybridkem-x25519-kyber768/tsconfig.json new file mode 100644 index 000000000..601a012c0 --- /dev/null +++ b/x/hybridkem-x25519-kyber768/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "es2022", + "target": "es2022", + "allowImportingTsExtensions": true + }, + "include": [ + "mod.ts", + "../../src/kems/hybridkemX25519Kyber768.ts" + ] +} \ No newline at end of file From 97f9af2887cc3b33669733cd3a4292047fa780de Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Sat, 19 Aug 2023 02:14:39 +0900 Subject: [PATCH 13/14] Fix lint error. --- .../test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts index bf0a648c6..0e1ce7168 100644 --- a/x/hybridkem-x25519-kyber768/test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts +++ b/x/hybridkem-x25519-kyber768/test/runtimes/browsers/hybridkem-x25519-kyber768.spec.ts @@ -1,7 +1,9 @@ import { expect, test } from "@playwright/test"; test("basic test", async ({ page }) => { - await page.goto("https://dajiaji.github.io/hpke-js/hybridkem-x25519-kyber768"); + await page.goto( + "https://dajiaji.github.io/hpke-js/hybridkem-x25519-kyber768", + ); await page.click("text=run"); await page.waitForTimeout(5000); await expect(page.locator("id=pass")).toHaveText("6"); From 8f79e25f4eed7d73bdd10a61f9acdf1335609822 Mon Sep 17 00:00:00 2001 From: Ajitomi Daisuke Date: Sat, 19 Aug 2023 02:33:19 +0900 Subject: [PATCH 14/14] Fix port number. --- .../test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts | 2 +- x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js | 2 +- .../test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts | 2 +- .../test/runtimes/cloudflare/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts b/x/hybridkem-x25519-kyber768/test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts index 958eb5b2d..e602bca59 100644 --- a/x/hybridkem-x25519-kyber768/test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts +++ b/x/hybridkem-x25519-kyber768/test/runtimes/bun/hybridkem-x25519-kyber768.spec.ts @@ -7,7 +7,7 @@ describe("Bun", () => { for (const kdf of ["0x0001", "0x0002", "0x0003"]) { for (const aead of ["0x0001", "0x0002"]) { const res = await fetch( - `http://localhost:3005/test?kdf=${kdf}&aead=${aead}`, + `http://localhost:3006/test?kdf=${kdf}&aead=${aead}`, ); assertEquals("ok", await res.text()); } diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js b/x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js index 4ea92425a..7957c402f 100644 --- a/x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js +++ b/x/hybridkem-x25519-kyber768/test/runtimes/bun/src/index.js @@ -1,7 +1,7 @@ import { testServer } from "../../server.js"; export default { - port: 3005, + port: 3006, async fetch(request) { return await testServer(request); }, diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts index 63c92b264..6b7131ad5 100644 --- a/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts +++ b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/hybridkem-x25519-kyber768.spec.ts @@ -7,7 +7,7 @@ describe("Cloudflare Workers", () => { for (const kdf of ["0x0001", "0x0002", "0x0003"]) { for (const aead of ["0x0001", "0x0002"]) { const res = await fetch( - `http://localhost:8792/test?kdf=${kdf}&aead=${aead}`, + `http://localhost:8793/test?kdf=${kdf}&aead=${aead}`, ); assertEquals("ok", await res.text()); } diff --git a/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/package.json b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/package.json index 6c61caf34..bfb95119a 100644 --- a/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/package.json +++ b/x/hybridkem-x25519-kyber768/test/runtimes/cloudflare/package.json @@ -6,7 +6,7 @@ }, "private": true, "scripts": { - "start": "wrangler dev --port 8792", + "start": "wrangler dev --port 8793", "deploy": "wrangler publish" } }