@hpke/common
Version:
A Hybrid Public Key Encryption (HPKE) internal-use common module for @hpke family modules.
417 lines (416 loc) • 15.6 kB
JavaScript
// deno-lint-ignore-file no-explicit-any
/**
* This file is based on noble-curves (https://github.com/paulmillr/noble-curves).
*
* noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com)
*
* The original file is located at:
* https://github.com/paulmillr/noble-curves/blob/b9d49d2b41d550571a0c5be443ecb62109fa3373/src/utils.ts
*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./misc.js", "../consts.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.byteSwapIfBE = exports.isLE = void 0;
exports.isBytes = isBytes;
exports.anumber = anumber;
exports.abytes = abytes;
exports.aexists = aexists;
exports.aoutput = aoutput;
exports.u32 = u32;
exports.clean = clean;
exports.byteSwap = byteSwap;
exports.swap8IfBE = swap8IfBE;
exports.byteSwap32 = byteSwap32;
exports.swap32IfBE = swap32IfBE;
exports.createView = createView;
exports.rotr = rotr;
exports.bytesToHex = bytesToHex;
exports.hexToBytes = hexToBytes;
exports.utf8ToBytes = utf8ToBytes;
exports.bytesToUtf8 = bytesToUtf8;
exports.numberToHexUnpadded = numberToHexUnpadded;
exports.hexToNumber = hexToNumber;
exports.numberToBigint = numberToBigint;
exports.bytesToNumberBE = bytesToNumberBE;
exports.bytesToNumberLE = bytesToNumberLE;
exports.numberToBytesBE = numberToBytesBE;
exports.numberToBytesLE = numberToBytesLE;
exports.copyBytes = copyBytes;
exports.concatBytes = concatBytes;
exports.asciiToBytes = asciiToBytes;
exports.inRange = inRange;
exports.aInRange = aInRange;
exports.validateObject = validateObject;
exports.randomBytesAsync = randomBytesAsync;
exports.oidNist = oidNist;
/**
* Hex, bytes and number utilities.
* @module
*/
const misc_js_1 = require("./misc.js");
const consts_js_1 = require("../consts.js");
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
function isBytes(a) {
return a instanceof Uint8Array ||
(ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array");
}
/** Asserts something is positive integer. */
function anumber(n, title = "") {
if (!Number.isSafeInteger(n) || n < 0) {
const prefix = title && `"${title}" `;
throw new Error(`${prefix}expected integer >0, got ${n}`);
}
}
/** Asserts something is Uint8Array. */
function abytes(value, length, title = "") {
const bytes = isBytes(value);
const len = value?.length;
const needsLen = length !== undefined;
if (!bytes || (needsLen && len !== length)) {
const prefix = title && `"${title}" `;
const ofLen = needsLen ? ` of length ${length}` : "";
const got = bytes ? `length=${len}` : `type=${typeof value}`;
throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got);
}
return value;
}
// ahash function is now imported from ../hash/hash.ts
/** Asserts a hash instance has not been destroyed / finished */
function aexists(instance, checkFinished = true) {
if (instance.destroyed)
throw new Error("Hash instance has been destroyed");
if (checkFinished && instance.finished) {
throw new Error("Hash#digest() has already been called");
}
}
/** Asserts output is properly-sized byte array */
function aoutput(out, instance) {
abytes(out, undefined, "digestInto() output");
const min = instance.outputLen;
if (out.length < min) {
throw new Error('"digestInto() output" expected to be of length >=' + min);
}
}
// Used in weierstrass, der
function abignumer(n) {
if (typeof n === "bigint") {
if (!isPosBig(n))
throw new Error("positive bigint expected, got " + n);
}
else
anumber(n);
return n;
}
/** Cast u8 / u16 / u32 to u32. */
function u32(arr) {
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
}
/** Zeroize a byte array. Warning: JS provides no guarantees. */
function clean(...arrays) {
for (let i = 0; i < arrays.length; i++) {
arrays[i].fill(0);
}
}
/** Pre-computed buffer for endianness detection */
const _endianTestBuffer = /* @__PURE__ */ new Uint32Array([0x11223344]);
const _endianTestBytes = /* @__PURE__ */ new Uint8Array(_endianTestBuffer.buffer);
/** Is current platform little-endian? Most are. Big-Endian platform: IBM */
exports.isLE = _endianTestBytes[0] === 0x44;
/** The byte swap operation for uint32 */
function byteSwap(word) {
return (((word << 24) & 0xff000000) |
((word << 8) & 0xff0000) |
((word >>> 8) & 0xff00) |
((word >>> 24) & 0xff));
}
/** Conditionally byte swap if on a big-endian platform */
function swap8IfBE(n) {
return exports.isLE ? n : byteSwap(n);
}
/** @deprecated */
exports.byteSwapIfBE = swap8IfBE;
/** In place byte swap for Uint32Array */
function byteSwap32(arr) {
for (let i = 0; i < arr.length; i++) {
arr[i] = byteSwap(arr[i]);
}
return arr;
}
function swap32IfBE(u) {
return exports.isLE ? u : byteSwap32(u);
}
/** Create DataView of an array for easy byte-level manipulation. */
function createView(arr) {
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
}
/** The rotate right (circular right shift) operation for uint32 */
function rotr(word, shift) {
return (word << (32 - shift)) | (word >>> shift);
}
// Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex
const hasHexBuiltin = /* @__PURE__ */ (() =>
// @ts-ignore: to use toHex
typeof Uint8Array.from([]).toHex === "function" &&
// @ts-ignore: to use fromHex
typeof Uint8Array.fromHex === "function")();
// Array where index 0xf0 (240) is mapped to string 'f0'
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
const HEX_TO_BIGINT = /* @__PURE__ */ [
0n,
1n,
2n,
3n,
4n,
5n,
6n,
7n,
8n,
9n,
10n,
11n,
12n,
13n,
14n,
15n,
];
/**
* Convert byte array to hex string. Uses built-in function, when available.
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
function bytesToHex(bytes) {
abytes(bytes);
// @ts-ignore: to use toHex
if (hasHexBuiltin)
return bytes.toHex();
// pre-caching improves the speed 6x
let hex = "";
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
// We use optimized technique to convert hex string to byte array
const asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
function asciiToBase16(ch) {
if (ch >= asciis._0 && ch <= asciis._9)
return ch - asciis._0; // '2' => 50-48
if (ch >= asciis.A && ch <= asciis.F)
return ch - (asciis.A - 10); // 'B' => 66-(65-10)
if (ch >= asciis.a && ch <= asciis.f)
return ch - (asciis.a - 10); // 'b' => 98-(97-10)
return;
}
/**
* Convert hex string to byte array. Uses built-in function, when available.
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
function hexToBytes(hex) {
if (typeof hex !== "string") {
throw new Error("hex string expected, got " + typeof hex);
}
// @ts-ignore: to use fromHex
if (hasHexBuiltin)
return Uint8Array.fromHex(hex);
const hl = hex.length;
const al = hl / 2;
if (hl % 2) {
throw new Error("hex string expected, got unpadded hex of length " + hl);
}
const array = new Uint8Array(al);
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
const n1 = asciiToBase16(hex.charCodeAt(hi));
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
if (n1 === undefined || n2 === undefined) {
const char = hex[hi] + hex[hi + 1];
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' +
hi);
}
array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163
}
return array;
}
/**
* Converts string to bytes using UTF8 encoding.
* @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
*/
function utf8ToBytes(str) {
if (typeof str !== "string")
throw new Error("string expected");
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
/**
* Converts bytes to string using UTF8 encoding.
* @example bytesToUtf8(Uint8Array.from([97, 98, 99])) // 'abc'
*/
function bytesToUtf8(bytes) {
return new TextDecoder().decode(bytes);
}
function numberToHexUnpadded(num) {
const hex = abignumer(num).toString(16);
return hex.length & 1 ? "0" + hex : hex;
}
function hexToNumber(hex) {
if (typeof hex !== "string") {
throw new Error("hex string expected, got " + typeof hex);
}
let out = consts_js_1.N_0;
for (let i = 0; i < hex.length; i++) {
const n = asciiToBase16(hex.charCodeAt(i));
if (n === undefined) {
throw new Error('hex string expected, got non-hex character "' + hex[i] +
'" at index ' + i);
}
out = (out << 4n) | HEX_TO_BIGINT[n];
}
return out; // Big Endian
}
function numberToBigint(num) {
anumber(num, "numberToBigint");
let n = num;
let out = consts_js_1.N_0;
let bit = 1n;
while (n > 0) {
if (n % 2 === 1)
out += bit;
n = Math.floor(n / 2);
bit <<= 1n;
}
return out;
}
// BE: Big Endian, LE: Little Endian
function bytesToNumberBE(bytes) {
return hexToNumber(bytesToHex(bytes));
}
function bytesToNumberLE(bytes) {
return hexToNumber(bytesToHex(copyBytes(abytes(bytes)).reverse()));
}
function numberToBytesBE(n, len) {
anumber(len);
n = abignumer(n);
const res = hexToBytes(n.toString(16).padStart(len * 2, "0"));
if (res.length !== len)
throw new Error("number too large");
return res;
}
function numberToBytesLE(n, len) {
return numberToBytesBE(n, len).reverse();
}
/**
* Copies Uint8Array. We can't use u8a.slice(), because u8a can be Buffer,
* and Buffer#slice creates mutable copy. Never use Buffers!
*/
function copyBytes(bytes) {
return Uint8Array.from(bytes);
}
/** Copies several Uint8Arrays into one. */
function concatBytes(...arrays) {
let sum = 0;
for (let i = 0; i < arrays.length; i++) {
const a = arrays[i];
abytes(a);
sum += a.length;
}
const res = new Uint8Array(sum);
for (let i = 0, pad = 0; i < arrays.length; i++) {
const a = arrays[i];
res.set(a, pad);
pad += a.length;
}
return res;
}
/**
* Decodes 7-bit ASCII string to Uint8Array, throws on non-ascii symbols
* Should be safe to use for things expected to be ASCII.
* Returns exact same result as utf8ToBytes for ASCII or throws.
*/
function asciiToBytes(ascii) {
return Uint8Array.from(ascii, (c, i) => {
const charCode = c.charCodeAt(0);
if (c.length !== 1 || charCode > 127) {
throw new Error(`string contains non-ASCII character "${ascii[i]}" with code ${charCode} at position ${i}`);
}
return charCode;
});
}
// Is positive bigint
function isPosBig(n) {
return typeof n === "bigint" && consts_js_1.N_0 <= n;
}
function inRange(n, min, max) {
return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max;
}
/**
* Asserts min <= n < max. NOTE: It's < max and not <= max.
* @example
* aInRange('x', x, 1n, 256n); // would assume x is in (1n..255n)
*/
function aInRange(title, n, min, max) {
// Why min <= n < max and not a (min < n < max) OR b (min <= n <= max)?
// consider P=256n, min=0n, max=P
// - a for min=0 would require -1: `inRange('x', x, -1n, P)`
// - b would commonly require subtraction: `inRange('x', x, 0n, P - 1n)`
// - our way is the cleanest: `inRange('x', x, 0n, P)
if (!inRange(n, min, max)) {
throw new Error("expected valid " + title + ": " + min + " <= n < " + max + ", got " + n);
}
}
function validateObject(object, fields = {}, optFields = {}) {
if (!object || typeof object !== "object") {
throw new Error("expected valid options object");
}
function checkField(fieldName, expectedType, isOpt) {
const val = object[fieldName];
if (isOpt && val === undefined)
return;
const current = typeof val;
if (current !== expectedType || val === null) {
throw new Error(`param "${fieldName}" is invalid: expected ${expectedType}, got ${current}`);
}
}
const iter = (f, isOpt) => Object.entries(f).forEach(([k, v]) => checkField(k, v, isOpt));
iter(fields, false);
iter(optFields, true);
}
// createHasher function is now exported above with ahash
// /** Cryptographically secure PRNG. Uses internal OS-level `crypto.getRandomValues`. */
// export function randomBytes(bytesLength = 32): Uint8Array {
// const cr = typeof globalThis != null && (globalThis as any).crypto;
// if (!cr || typeof cr.getRandomValues !== "function") {
// throw new Error("crypto.getRandomValues must be defined");
// }
// return cr.getRandomValues(new Uint8Array(bytesLength));
// }
/** Cryptographically secure PRNG. Uses internal OS-level `crypto.getRandomValues`. */
async function randomBytesAsync(bytesLength = 32) {
const api = await (0, misc_js_1.loadCrypto)();
const rnd = new Uint8Array(bytesLength);
api.getRandomValues(rnd);
return rnd;
}
// 06 09 60 86 48 01 65 03 04 02
function oidNist(suffix) {
return {
oid: Uint8Array.from([
0x06,
0x09,
0x60,
0x86,
0x48,
0x01,
0x65,
0x03,
0x04,
0x02,
suffix,
]),
};
}
});