Skip to content

Commit 369bbdc

Browse files
Merge pull request #33 from internxt/add_xwing
[_] Add hybrid xwing algorithm
2 parents 5b3f523 + b1ecf75 commit 369bbdc

6 files changed

Lines changed: 84 additions & 1 deletion

File tree

src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@ export const MAX_CACHE_SIZE = 600000000; // 600 MB
3131
export const MAX_EMAIL_PER_BATCH = 100;
3232
export const DB_LABEL = 'email';
3333
export const DB_VERSION = 1;
34+
35+
export const XWING_PUBLIC_KEY_LENGTH = 1216;
36+
export const XWING_SECRET_KEY_LENGTH = 32;
37+
export const XWING_SEED_BYTE_LENGTH = 32;
38+
export const XWING_CIPHERTEXT_BYTE_LENGTH = 1120;

src/hybrid-crypto/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './xwing';

src/hybrid-crypto/xwing.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ml_kem768_x25519 as hybridCipher } from '@noble/post-quantum/hybrid.js';
2+
import { HybridKeyPair } from '../types';
3+
4+
export function genHybridKeys(seed?: Uint8Array): HybridKeyPair {
5+
return hybridCipher.keygen(seed);
6+
}
7+
8+
export function encapsulateHybrid(publicKey: Uint8Array): { cipherText: Uint8Array; sharedSecret: Uint8Array } {
9+
return hybridCipher.encapsulate(publicKey);
10+
}
11+
12+
export function decapsulateHybrid(cipherText: Uint8Array, secretKey: Uint8Array): Uint8Array {
13+
return hybridCipher.decapsulate(cipherText, secretKey);
14+
}

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export {
6363
} from './hash';
6464
export { unwrapKey, wrapKey } from './key-wrapper';
6565
export { createEncryptionAndRecoveryKeystores, openEncryptionKeystore, openRecoveryKeystore } from './keystore-crypto';
66-
export { generateKyberKeys, encapsulateKyber, decapsulateKyber } from './post-quantum-crypto/kyber768';
66+
export { generateKyberKeys, encapsulateKyber, decapsulateKyber } from './post-quantum-crypto';
6767
export {
6868
encryptSymmetrically,
6969
decryptSymmetrically,

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export type PublicKeysBase64 = {
2323
kyberPublicKeyBase64: string;
2424
};
2525

26+
export type HybridKeyPair = {
27+
publicKey: Uint8Array;
28+
secretKey: Uint8Array;
29+
};
30+
2631
export type PrivateKeys = {
2732
eccPrivateKey: CryptoKey;
2833
kyberPrivateKey: Uint8Array;

tests/hybrid-crypto/xwing.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { decapsulateHybrid, encapsulateHybrid, genHybridKeys } from '../../src/hybrid-crypto';
3+
import {
4+
XWING_PUBLIC_KEY_LENGTH,
5+
XWING_SECRET_KEY_LENGTH,
6+
XWING_SEED_BYTE_LENGTH,
7+
XWING_CIPHERTEXT_BYTE_LENGTH,
8+
} from '../../src/constants';
9+
import { randomBytes } from '@noble/hashes/utils.js';
10+
11+
import { base64ToUint8Array } from '../../src/utils/converters';
12+
13+
describe('Test key wrapping functions', () => {
14+
it('should scuessfully generate hybrid key', async () => {
15+
const keys = genHybridKeys();
16+
17+
expect(keys.publicKey).toBeInstanceOf(Uint8Array);
18+
expect(keys.secretKey).toBeInstanceOf(Uint8Array);
19+
20+
expect(keys.publicKey.length).toBe(XWING_PUBLIC_KEY_LENGTH);
21+
expect(keys.secretKey.length).toBe(XWING_SECRET_KEY_LENGTH);
22+
});
23+
24+
it('should generate identical keys for identical seeds', async () => {
25+
const seed = randomBytes(XWING_SEED_BYTE_LENGTH);
26+
const keys1 = genHybridKeys(seed);
27+
const keys2 = genHybridKeys(seed);
28+
29+
expect(keys1).toStrictEqual(keys2);
30+
});
31+
32+
it('should sucessufully decapsulate encapsulated secret', async () => {
33+
const keys = genHybridKeys();
34+
const { cipherText, sharedSecret } = encapsulateHybrid(keys.publicKey);
35+
expect(cipherText.length).toBe(XWING_CIPHERTEXT_BYTE_LENGTH);
36+
37+
const result = decapsulateHybrid(cipherText, keys.secretKey);
38+
39+
expect(result).toStrictEqual(sharedSecret);
40+
expect(result).toBeInstanceOf(Uint8Array);
41+
expect(result.length).toBe(XWING_SECRET_KEY_LENGTH);
42+
});
43+
it('should decapsulate existing ciphertext', () => {
44+
const sharedSecretStr = 'zxuY95i1M//uq9efQ7iKAykU2bU5MXOyoH9her0j72Q=';
45+
const secretKeyStr = 'ZrR3PqNwfff15hdGkTg9FnMs11TQYxF0BCUVf/VH6GI=';
46+
const ciphertextStr =
47+
'15/qsiUXK1lcmNXVG//coX7UKS6j5KEkDkOjWbgdXjHMun5lLmVwvbiYMBolsmkmiZfJ81ByPmFhUkeB1sgkRIS0mSKXsBZVYMSzceNiCI8YY7DM1LFK7F3M5nLXRSqHTEH7DV1ghIdz3FnwcKqXcbqs/WsNUgjvARcTyUfNiGi3Z8uq6w0cJFbJcKZrr7GQNAWFu1wrjqsqWv3GFcqt91KFc0jB/WplWn/VYISQPqDVgSvYss3DJjMEh9ZqZ4AF0N2GIwU2CNQs4sbTSGoGpoYi5FEmiqVQQidWH5/9qeU+ipcjnvBs1sXepJuL7v7HKB2bl/mbJDm0BNcuBqUJERMJj5OoMBpNF3q6BHIfIRdznYSyz7RCiURTpOo2lB/uevngrapUeL4NqbidJCSWvTHgjq3+fLZZNU64tngg1GhsTIC9PzUnkGkEbRbJhOB0eemC8fGb8PMKcp5Spoivmk+bPFlbEonvIr7dzkeiujS06zovvUtX53OLHSGXkKhc3DDu2n7bQYmgL34nfAPw6rYejkdLEjfT0gNz56Jf5M5qrTjFEwdz1I881BUy9KgB1oxmLFn3sUL19LTpXWQNwY2mx9qLZGdiF6y/kj/A1Wx0Vz+glItAMw0LG6s8G7igq388srvPOT3e4fTypRILZaLXQJ9Fbccgcr+4L7Sh8MoPB/pIVNa1gQ4yqXpCRbtfCKfUs7CT9iwsCnMYOckjIjceUM20Z0sc6OwVuqt600z3SaxD8Hfhy6LeGbmzus3UnGMvOTgLNDGZFcoOdywKbKbIdoFCWR7eYUbpPQwIdn6ZBhiHmWTRKfjTQrGvBYGO3VEzrIzFmuhxpwGKAhah/f/ds1hWY1b64wiyNU3XrXGixbJMjiUnPA//14K2p8IL/Yymuw4nQiNtazUSNCn/b8IC8M85znDwS7lrqyzfdvuFu+oLmy3lIOx9GFXN6Uv6MxKeUTjaFrF+bu8C1dX4s9XOIYXpEFdTuEySpbsdPDKsQufj0XFZDQUw+CWsBHzlK/4IdY45TaEH8bddwXf51yg4z8rFr0Mtw5l3fOZQIYhe/T2CUrOaIM3AnhfZ6lNMN9KMjP98bPytNCbHBVjGko1+G4lTXgj278lBWoV7VlNAuYgzLemS32KVXWw16I0CxO2pjWQz++IJhwi18FhUtkt7DLHilT8ju43PnAe8eu9CuSBUuhMCOHRaUTcxAP3bEbLawio1LzmNnIrkAD7ndP4Gxfl0bAVvu3xxZIDQrbfwh4l45yksIuvaM6k8X0vSFz2HWEafa4EQRexGZRTAK320VTVEpzACO3fH71kpapTM/YEbLtimzdam465Vz3oj/rYjJKNHB0NzT5mlrHLUPUybfPfs6jgTFzWg44/f4ytOGIp16pGNZm8weGfHKGalEgKGBhLEdEBBJ+UNgKCRMYORptHKt49ZtAfQiqMEPQZM5eQHUwPbQJlw2RkK6Ehnaar8Ivy8EGzc/aruE03EIg==';
48+
49+
const sharedSecret = base64ToUint8Array(sharedSecretStr);
50+
const secretKey = base64ToUint8Array(secretKeyStr);
51+
const cipherText = base64ToUint8Array(ciphertextStr);
52+
53+
const result = decapsulateHybrid(cipherText, secretKey);
54+
expect(result).toStrictEqual(sharedSecret);
55+
expect(result).toBeInstanceOf(Uint8Array);
56+
expect(result.length).toBe(XWING_SECRET_KEY_LENGTH);
57+
});
58+
});

0 commit comments

Comments
 (0)