js-noise/src/crypto.ts

151 lines
4.0 KiB
TypeScript
Raw Normal View History

import { ChaCha20Poly1305, TAG_LENGTH } from "@stablelib/chacha20poly1305";
2023-01-06 13:34:32 -04:00
import { HKDF as hkdf } from "@stablelib/hkdf";
2022-11-13 09:39:26 -04:00
import { hash, SHA256 } from "@stablelib/sha256";
import * as x25519 from "@stablelib/x25519";
import { concat as uint8ArrayConcat } from "uint8arrays/concat";
2022-11-13 09:39:26 -04:00
2023-01-06 13:34:32 -04:00
import type { bytes32 } from "./@types/basic.js";
2022-11-13 09:39:26 -04:00
import type { KeyPair } from "./@types/keypair.js";
export const Curve25519KeySize = x25519.PUBLIC_KEY_LENGTH;
2023-01-06 13:34:32 -04:00
export const ChachaPolyTagLen = TAG_LENGTH;
2022-11-13 09:39:26 -04:00
2023-01-06 13:34:32 -04:00
/**
* Generate hash using SHA2-256
* @param data data to hash
* @returns hash digest
*/
2022-11-13 09:39:26 -04:00
export function hashSHA256(data: Uint8Array): Uint8Array {
return hash(data);
}
2023-01-06 13:34:32 -04:00
/**
* Convert an Uint8Array into a 32-byte value. If the input data length is different
* from 32, throw an error. This is used mostly as a validation function to ensure
* that an Uint8Array represents a valid x25519 key
* @param s input data
* @return 32-byte key
*/
2022-11-13 09:39:26 -04:00
export function intoCurve25519Key(s: Uint8Array): bytes32 {
if (s.length != x25519.PUBLIC_KEY_LENGTH) {
2022-11-15 17:56:25 -04:00
throw new Error("invalid public key length");
2022-11-13 09:39:26 -04:00
}
return s;
}
2023-01-06 13:34:32 -04:00
/**
* HKDF key derivation function using SHA256
* @param ck chaining key
* @param ikm input key material
* @param length length of each generated key
* @param numKeys number of keys to generate
* @returns array of `numValues` length containing Uint8Array keys of a given byte `length`
*/
export function HKDF(ck: bytes32, ikm: Uint8Array, length: number, numKeys: number): Array<Uint8Array> {
const numBytes = length * numKeys;
const okm = new hkdf(SHA256, ikm, ck).expand(numBytes);
const result = [];
for (let i = 0; i < numBytes; i += length) {
const k = okm.subarray(i, i + length);
result.push(k);
}
return result;
2022-11-21 19:08:24 -04:00
}
2023-01-06 13:34:32 -04:00
/**
* Generate a random keypair
* @returns Keypair
*/
2022-11-13 09:39:26 -04:00
export function generateX25519KeyPair(): KeyPair {
const keypair = x25519.generateKeyPair();
return {
publicKey: keypair.publicKey,
privateKey: keypair.secretKey,
};
}
2023-01-06 13:34:32 -04:00
/**
* Generate x25519 keypair using an input seed
* @param seed 32-byte secret
* @returns Keypair
*/
export function generateX25519KeyPairFromSeed(seed: bytes32): KeyPair {
2022-11-13 09:39:26 -04:00
const keypair = x25519.generateKeyPairFromSeed(seed);
return {
publicKey: keypair.publicKey,
privateKey: keypair.secretKey,
};
}
2023-01-06 13:34:32 -04:00
/**
* Encrypt and authenticate data using ChaCha20-Poly1305
* @param plaintext data to encrypt
* @param nonce 12 byte little-endian nonce
* @param ad associated data
* @param k 32-byte key
* @returns sealed ciphertext including authentication tag
*/
export function chaCha20Poly1305Encrypt(
plaintext: Uint8Array,
nonce: Uint8Array,
ad: Uint8Array,
k: bytes32
): Uint8Array {
2022-11-13 09:39:26 -04:00
const ctx = new ChaCha20Poly1305(k);
return ctx.seal(nonce, plaintext, ad);
}
2023-01-06 13:34:32 -04:00
/**
* Authenticate and decrypt data using ChaCha20-Poly1305
* @param ciphertext data to decrypt
* @param nonce 12 byte little-endian nonce
* @param ad associated data
* @param k 32-byte key
* @returns plaintext if decryption was successful, `null` otherwise
*/
2022-11-13 09:39:26 -04:00
export function chaCha20Poly1305Decrypt(
ciphertext: Uint8Array,
nonce: Uint8Array,
ad: Uint8Array,
k: bytes32
2023-01-06 13:34:32 -04:00
): Uint8Array | null {
2022-11-13 09:39:26 -04:00
const ctx = new ChaCha20Poly1305(k);
return ctx.open(nonce, ciphertext, ad);
}
2023-01-06 13:34:32 -04:00
/**
* Perform a DiffieHellman key exchange
* @param privateKey x25519 private key
* @param publicKey x25519 public key
* @returns shared secret
*/
2022-11-13 09:39:26 -04:00
export function dh(privateKey: bytes32, publicKey: bytes32): bytes32 {
try {
2023-01-06 13:34:32 -04:00
const derivedU8 = x25519.sharedKey(privateKey, publicKey);
2022-11-13 09:39:26 -04:00
if (derivedU8.length === 32) {
return derivedU8;
}
return derivedU8.subarray(0, 32);
} catch (e) {
console.error(e);
return new Uint8Array(32);
}
}
2023-01-06 13:34:32 -04:00
/**
* Generates a random static key commitment using a public key pk for randomness r as H(pk || s)
* @param publicKey x25519 public key
* @param r random fixed-length value
* @returns 32 byte hash
*/
export function commitPublicKey(publicKey: bytes32, r: Uint8Array): bytes32 {
return hashSHA256(uint8ArrayConcat([publicKey, r]));
}