UNPKG

nhb-toolbox

Version:

A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.

251 lines (250 loc) 9.28 kB
import { isUndefined } from '../guards/primitives.js'; import { isHexString } from '../guards/specials.js'; import { _bytesToRandomHex, _splitByCharLength } from './helpers.js'; export function randomHex(length, uppercase = false) { if (length <= 0) return ''; const bytes = new Uint8Array(Math.ceil(length / 2)); const expected = _bytesToRandomHex(bytes).slice(0, length); return uppercase ? expected.toUpperCase() : expected; } export function utf8ToBytes(str) { const out = []; for (let i = 0; i < str.length; i++) { const code = str.charCodeAt(i); if (code < 0x80) out.push(code); else if (code < 0x800) { out.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f)); } else if (code >= 0xd800 && code <= 0xdfff) { if (code < 0xdc00 && i + 1 < str.length) { const hi = code; const lo = str.charCodeAt(++i); const codePoint = 0x10000 + ((hi - 0xd800) << 10) + (lo - 0xdc00); out.push(0xf0 | (codePoint >> 18), 0x80 | ((codePoint >> 12) & 0x3f), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f)); } } else { out.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f)); } } return new Uint8Array(out); } export function bytesToUtf8(bytes) { let out = ''; let i = 0; while (i < bytes.length) { const b1 = bytes[i++]; if (b1 < 0x80) out += String.fromCharCode(b1); else if (b1 >= 0xc0 && b1 < 0xe0) { const b2 = bytes[i++]; out += String.fromCharCode(((b1 & 0x1f) << 6) | (b2 & 0x3f)); } else if (b1 >= 0xe0 && b1 < 0xf0) { const b2 = bytes[i++]; const b3 = bytes[i++]; out += String.fromCharCode(((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f)); } else { const b2 = bytes[i++]; const b3 = bytes[i++]; const b4 = bytes[i++]; let codePoint = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f); codePoint -= 0x10000; out += String.fromCharCode(0xd800 + ((codePoint >> 10) & 0x3ff), 0xdc00 + (codePoint & 0x3ff)); } } return out; } export function base64ToBytes(str) { const _b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; const out = []; let i = 0; while (i < str.length) { const h1 = _b64chars.indexOf(str[i++]); const h2 = _b64chars.indexOf(str[i++]); const h3 = _b64chars.indexOf(str[i++]); const h4 = _b64chars.indexOf(str[i++]); const o1 = (h1 << 2) | (h2 >> 4); const o2 = ((h2 & 15) << 4) | (h3 >> 2); const o3 = ((h3 & 3) << 6) | h4; out.push(o1); if (h3 !== 64) out.push(o2); if (h4 !== 64) out.push(o3); } return new Uint8Array(out); } export function bytesToBase64(bytes) { const _b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; let out = ''; let i = 0; while (i < bytes.length) { const o1 = bytes[i++]; const o2 = bytes[i++]; const o3 = bytes[i++]; const h1 = o1 >> 2; const h2 = ((o1 & 3) << 4) | (!isUndefined(o2) ? o2 >> 4 : 0); const h3 = !isUndefined(o2) ? ((o2 & 15) << 2) | (!isUndefined(o3) ? o3 >> 6 : 0) : 0; const h4 = !isUndefined(o3) ? o3 & 63 : 0; out += _b64chars[h1] + _b64chars[h2] + _b64chars[isUndefined(o2) ? 64 : h3] + _b64chars[isUndefined(o3) ? 64 : h4]; } return out; } export function concatBytes(...parts) { const len = parts.reduce((s, p) => s + p.length, 0); const out = new Uint8Array(len); let offset = 0; for (const p of parts) { out.set(p, offset); offset += p.length; } return out; } export function sha256Bytes(message) { const H = new Uint32Array([ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ]); const K = new Uint32Array([ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, ]); const ml = message.length * 8; const padding = new Uint8Array((message.length + 8 + 64) & ~63 || 64); padding.set(message, 0); padding[message.length] = 0x80; const lenView = new DataView(padding.buffer); lenView.setUint32(padding.length - 8, Math.floor(ml / 0x100000000), false); lenView.setUint32(padding.length - 4, ml & 0xffffffff, false); const w = new Uint32Array(64); const chunkView = new DataView(padding.buffer); for (let offset = 0; offset < padding.length; offset += 64) { for (let t = 0; t < 16; t++) { w[t] = chunkView.getUint32(offset + t * 4, false); } for (let t = 16; t < 64; t++) { const s0 = ((w[t - 15] >>> 7) | (w[t - 15] << 25)) ^ ((w[t - 15] >>> 18) | (w[t - 15] << 14)) ^ (w[t - 15] >>> 3); const s1 = ((w[t - 2] >>> 17) | (w[t - 2] << 15)) ^ ((w[t - 2] >>> 19) | (w[t - 2] << 13)) ^ (w[t - 2] >>> 10); w[t] = (w[t - 16] + s0 + w[t - 7] + s1) >>> 0; } let a = H[0]; let b = H[1]; let c = H[2]; let d = H[3]; let e = H[4]; let f = H[5]; let g = H[6]; let h = H[7]; for (let t = 0; t < 64; t++) { const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); const ch = (e & f) ^ (~e & g); const temp1 = (h + S1 + ch + K[t] + w[t]) >>> 0; const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); const maj = (a & b) ^ (a & c) ^ (b & c); const temp2 = (S0 + maj) >>> 0; h = g; g = f; f = e; e = (d + temp1) >>> 0; d = c; c = b; b = a; a = (temp1 + temp2) >>> 0; } H[0] = (H[0] + a) >>> 0; H[1] = (H[1] + b) >>> 0; H[2] = (H[2] + c) >>> 0; H[3] = (H[3] + d) >>> 0; H[4] = (H[4] + e) >>> 0; H[5] = (H[5] + f) >>> 0; H[6] = (H[6] + g) >>> 0; H[7] = (H[7] + h) >>> 0; } const out = new Uint8Array(32); const outView = new DataView(out.buffer); for (let i = 0; i < 8; i++) { outView.setUint32(i * 4, H[i], false); } return out; } export function hmacSha256(key, message) { const blockSize = 64; let k = key; if (k.length > blockSize) { k = sha256Bytes(k); } if (k.length < blockSize) { const tmp = new Uint8Array(blockSize); tmp.set(k, 0); k = tmp; } const oKeyPad = new Uint8Array(blockSize); const iKeyPad = new Uint8Array(blockSize); for (let i = 0; i < blockSize; i++) { oKeyPad[i] = k[i] ^ 0x5c; iKeyPad[i] = k[i] ^ 0x36; } const inner = new Uint8Array(iKeyPad.length + message.length); inner.set(iKeyPad, 0); inner.set(message, iKeyPad.length); const innerHash = sha256Bytes(inner); const outer = new Uint8Array(oKeyPad.length + innerHash.length); outer.set(oKeyPad, 0); outer.set(innerHash, oKeyPad.length); return sha256Bytes(outer); } export function uint8To32ArrayBE(bytes) { const len = Math.ceil(bytes.length / 4); const out = new Uint32Array(len); for (let i = 0; i < len; i++) { const base = i * 4; out[i] = ((bytes[base] || 0) << 24) | ((bytes[base + 1] || 0) << 16) | ((bytes[base + 2] || 0) << 8) | ((bytes[base + 3] || 0) << 0); } return out; } export function intTo4BytesBE(n) { const b = new Uint8Array(4); b[0] = (n >>> 24) & 0xff; b[1] = (n >>> 16) & 0xff; b[2] = (n >>> 8) & 0xff; b[3] = n & 0xff; return b; } export function bytesToHex(bytes) { let hex = ''; for (let i = 0; i < bytes.length; i++) { const byte = bytes[i].toString(16).padStart(2, '0'); hex += byte; } return hex; } export function hexToBytes(hex) { if (!isHexString(hex)) return new Uint8Array(); const bytes = _splitByCharLength(hex, 2).map((h) => parseInt(h, 16)); return new Uint8Array(bytes); }