Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"dependencies": {
"@emailjs/browser": "^4.4.1",
"@noble/hashes": "^2.0.1",
"@noble/post-quantum": "^0.5.2",
"@scure/bip39": "^2.0.1",
"axios": "^1.11.0",
Expand Down
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export const CONTEXT_ENC_KEYSTORE = 'CRYPTO library 2025-07-30 16:18:03 key for
export const CONTEXT_RECOVERY = 'CRYPTO library 2025-07-30 16:20:00 key for account recovery';
export const CONTEXT_INDEX = 'CRYPTO library 2025-07-30 17:20:00 key for protecting current search indices';
export const CONTEXT_DERIVE = 'CRYPTO library 2025-08-27 17:08:00 derive one key from two keys';
export const CONTEXT_RATCHET = 'CRYPTO library 2025-08-28 18:27:00 ratchet media key';

export const PREFIX_MEDIA_KEY_COMMITMENT = 'CRYPTO library 2025-08-26 18:29:00 prefix for commitment to media keys';
export const PREFIX_PUBLIC_KEY_COMMITMENT = 'CRYPTO library 2025-08-26 18:30:00 prefix for commitment to public keys';
Expand Down
32 changes: 0 additions & 32 deletions src/derive-key/deriveKeyFromTwoKeys.ts

This file was deleted.

64 changes: 39 additions & 25 deletions src/derive-key/deriveKeysFromKey.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { blake3 } from 'hash-wasm';
import { AES_KEY_BIT_LENGTH, HASH_BIT_LEN } from '../constants';
import { hexToUint8Array } from '../utils';
import { blake3 } from '@noble/hashes/blake3.js';
import { AES_KEY_BIT_LENGTH, CONTEXT_DERIVE } from '../constants';
import { UTF8ToUint8 } from '../utils';
import { importSymmetricCryptoKey } from '../symmetric-crypto';

/**
Expand All @@ -11,18 +11,8 @@ import { importSymmetricCryptoKey } from '../symmetric-crypto';
* @param baseKey - The base key (NOT PASSWORD!)
* @returns The derived secret key
*/
export async function deriveSymmetricKeyFromContext(
context: string,
baseKey: Uint8Array | string,
): Promise<Uint8Array> {
try {
const context_key = await blake3(context);
const buffer_context_key = hexToUint8Array(context_key);
const result = await blake3(baseKey, AES_KEY_BIT_LENGTH, buffer_context_key);
return hexToUint8Array(result);
} catch (error) {
throw new Error('Failed to derive key from base key and context', { cause: error });
}
export function deriveSymmetricKeyFromContext(context: string, baseKey: Uint8Array): Uint8Array {
return blake3(baseKey, { context: UTF8ToUint8(context) });
}

/**
Expand All @@ -41,7 +31,7 @@ export async function deriveSymmetricCryptoKeyFromContext(context: string, baseK
if (!context) {
throw new Error('Context is empry');
}
const keyBytes = await deriveSymmetricKeyFromContext(context, baseKey);
const keyBytes = deriveSymmetricKeyFromContext(context, baseKey);
const key = await importSymmetricCryptoKey(keyBytes);

return key;
Expand All @@ -57,19 +47,43 @@ export async function deriveSymmetricCryptoKeyFromContext(context: string, baseK
* @param key2 - The 32-bytes key
* @returns The derived secret key
*/
export async function deriveKeyFromTwoKeysAndContext(
key1: Uint8Array,
key2: Uint8Array,
context: string,
): Promise<Uint8Array> {
export function deriveSymmetricKeyFromTwoKeys(key1: Uint8Array, key2: Uint8Array): Uint8Array {
return deriveKeyFromTwoKeysAndContext(key1, key2, CONTEXT_DERIVE);
}

/**
* Derives a symmetric CryptoKey from two keys
*
* @param key1 - The 32-bytes key
* @param key2 - The 32-bytes key
* @returns The derived secret CryptoKey
*/
export async function deriveSymmetricCryptoKeyFromTwoKeys(key1: Uint8Array, key2: Uint8Array): Promise<CryptoKey> {
try {
const keyBytes = deriveSymmetricKeyFromTwoKeys(key1, key2);
const key = await importSymmetricCryptoKey(keyBytes);
return key;
} catch (error) {
throw new Error('Failed to derive symmetric CryptoKey from two keys', { cause: error });
}
}

/**
* Derives a symmetric key from two keys and context
*
* @param key1 - The 32-bytes key
* @param key2 - The 32-bytes key
* @param context - The context string
* @returns The derived symmetric key
*/
export function deriveKeyFromTwoKeysAndContext(key1: Uint8Array, key2: Uint8Array, context: string): Uint8Array {
try {
if (key2.length != AES_KEY_BIT_LENGTH / 8 || key1.length != AES_KEY_BIT_LENGTH / 8) {
throw new Error(`Input key length must be exactly ${AES_KEY_BIT_LENGTH / 8} bytes`);
}
const combined_key = await blake3(key1, HASH_BIT_LEN, key2);
const result = await deriveSymmetricKeyFromContext(context, combined_key);
return result;
const key = blake3(key1, { key: key2 });
return blake3(key, { context: UTF8ToUint8(context) });
} catch (error) {
throw new Error('Failed to derive symmetric key from two keys', { cause: error });
throw new Error('Failed to derive symmetric key from two keys and context', { cause: error });
}
}
1 change: 0 additions & 1 deletion src/derive-key/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './deriveKeysFromKey';
export * from './deriveKeysFromPassword';
export * from './deriveKeyFromTwoKeys';
98 changes: 81 additions & 17 deletions src/hash/blake3.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,113 @@
import { computeHash } from './core';
import { HASH_BIT_LEN } from '../constants';
import { blake3 } from '@noble/hashes/blake3.js';
import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils.js';

/**
* Hashes the given array of data
*
* @param data - The data to hash
* @returns The resulting hash hex string
* @returns The resulting hash array
*/
export async function hashData(data: string[] | Uint8Array[]): Promise<string> {
export function hashData(data: Uint8Array[]): Uint8Array {
try {
const hasher = await computeHash(HASH_BIT_LEN, data);
const hasher = blake3.create();
for (const d of data) {
hasher.update(d);
}
return hasher.digest();
} catch (error) {
throw new Error('Failed to compute hash', { cause: error });
}
}

export function keyedHashHex(hashKey: Uint8Array, data: Uint8Array[]): string {
try {
const hash = keyedHash(hashKey, data);
return bytesToHex(hash);
} catch (error) {
throw new Error('Failed to compute hash hex', { cause: error });
}
}

export function keyedHash(hashKey: Uint8Array, data: Uint8Array[]): Uint8Array {
try {
const hasher = blake3.create({ key: hashKey });
for (const d of data) {
hasher.update(d);
}
return hasher.digest();
} catch (error) {
throw new Error('Failed to compute hash', { cause: error });
}
}

/**
* Hashes the given array of data
*
* @param data - The data to hash
* @returns The resulting hash hex string
*/
export function hashDataHex(data: Uint8Array[]): string {
try {
const hash = hashData(data);
return bytesToHex(hash);
} catch (error) {
throw new Error('Failed to compute hash', { cause: error });
}
}

/**
* Hashes the given array of data using blake3 algorithm
*
* @param bits - The desired output bit-length, must be multiple of 8
* @param bytes - The desired output byte-length
* @param data - The data to hash
* @returns The resulting hash value
*/
export async function getBitsFromData(bits: number, data: string[] | Uint8Array[]): Promise<Uint8Array> {
export function getBytesFromData(bytes: number, data: Uint8Array[]): Uint8Array {
try {
const hasher = await computeHash(bits, data);
return hasher.digest('binary');
const hasher = blake3.create({ dkLen: bytes });
for (const chunk of data) {
hasher.update(chunk);
}
return hasher.digest();
} catch (error) {
throw new Error('Failed to get bits from data', { cause: error });
throw new Error('Failed to get bytes from data', { cause: error });
}
}

/**
* Hashes the given string using blake3 algorithm
* Hashes the given array of data using blake3 algorithm
*
* @param bits - The desired output bit-length, must be multiple of 8
* @param value - The string to hash
* @param bytes - The desired output byte-length
* @param data - The data to hash
* @returns The resulting hash value
*/
export async function getBitsFromString(bits: number, value: string): Promise<Uint8Array> {
export function getBytesFromDataHex(bytes: number, data: Uint8Array[]): string {
try {
const hasher = await computeHash(bits, [value]);
return hasher.digest('binary');
const hash = getBytesFromData(bytes, data);
return bytesToHex(hash);
} catch (error) {
throw new Error('Failed to get bits from string', { cause: error });
throw new Error('Failed to get bytes from data', { cause: error });
}
}

/**
* Hashes the given string using blake3 algorithm
*
* @param bytes - The desired output byte-length
* @param value - The string to hash
* @returns The resulting hash value
*/
export function getBytesFromString(bytes: number, value: string): Uint8Array {
return blake3(utf8ToBytes(value), { dkLen: bytes });
}

/**
* Hashes the given string using blake3 algorithm
*
* @param bytes - The desired output byte-length
* @param value - The string to hash
* @returns The resulting hash value
*/
export function getBytesFromStrinHex(bytes: number, value: string): string {
return bytesToHex(getBytesFromString(bytes, value));
}
42 changes: 0 additions & 42 deletions src/hash/core.ts

This file was deleted.

14 changes: 7 additions & 7 deletions src/hash/mac.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { computeKeyedHash } from './core';
import { HASH_BIT_LEN, AES_KEY_BIT_LENGTH } from '../constants';
import { getBitsFromString } from './blake3';
import { bytesToHex } from '@noble/hashes/utils.js';
import { AES_KEY_BIT_LENGTH } from '../constants';
import { getBytesFromString, keyedHash } from './blake3';

/**
* Computes mac for the given key material and data
Expand All @@ -9,11 +9,11 @@ import { getBitsFromString } from './blake3';
* @param data - The data to hash
* @returns The resulting hash hex string
*/
export async function computeMac(keyMaterial: string, data: string[]): Promise<string> {
export function computeMac(keyMaterial: string, data: Uint8Array[]): string {
try {
const key = await getBitsFromString(AES_KEY_BIT_LENGTH, keyMaterial);
const hasher = await computeKeyedHash(HASH_BIT_LEN, key, data);
return hasher.digest();
const key = getBytesFromString(AES_KEY_BIT_LENGTH / 8, keyMaterial);
const hash = keyedHash(key, data);
return bytesToHex(hash);
} catch (error) {
throw new Error('Failed to compute mac', { cause: error });
}
Expand Down
4 changes: 2 additions & 2 deletions src/keystore-crypto/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SymmetricCiphertext } from '../types';
import sessionStorageService from '../storage-service/sessionStorageService';
import { deriveSymmetricCryptoKeyFromContext } from '../derive-key';
import { CONTEXT_LOGIN, CONTEXT_ENC_KEYSTORE, AES_KEY_BIT_LENGTH, CONTEXT_RECOVERY } from '../constants';
import { getBitsFromString } from '../hash';
import { getBytesFromString } from '../hash';

/**
* Encrypts the keystore content using symmetric encryption
Expand Down Expand Up @@ -107,7 +107,7 @@ export async function deriveIdentityKeystoreKey(baseKey: Uint8Array): Promise<Cr
* @returns The derived secret key for protecting the recovery keystore
*/
export async function deriveRecoveryKey(recoveryCodes: string): Promise<CryptoKey> {
const recoveryCodesBuffer = await getBitsFromString(AES_KEY_BIT_LENGTH, recoveryCodes);
const recoveryCodesBuffer = getBytesFromString(AES_KEY_BIT_LENGTH / 8, recoveryCodes);
return deriveSymmetricCryptoKeyFromContext(CONTEXT_RECOVERY, recoveryCodesBuffer);
}

Expand Down
2 changes: 1 addition & 1 deletion src/symmetric-crypto/aes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function encryptSymmetrically(
freeField?: string,
): Promise<SymmetricCiphertext> {
try {
const iv = await createNISTbasedIV(freeField);
const iv = createNISTbasedIV(freeField);
const additionalData = await makeAuxFixedLength(aux);
const ciphertext = await encryptMessage(message, encryptionKey, iv, additionalData);
return { ciphertext, iv };
Expand Down
Loading