//! otpauth 9.3.1 | (c) Héctor Molinero Fernández | MIT | https://github.com/hectorm/otpauth //! noble-hashes 1.4.0 | (c) Paul Miller | MIT | https://github.com/paulmillr/noble-hashes /// // @ts-nocheck /** * Converts an integer to an Uint8Array. * @param {number} num Integer. * @returns {Uint8Array} Uint8Array. */ const uintDecode = (num)=>{ const buf = new ArrayBuffer(8); const arr = new Uint8Array(buf); let acc = num; for(let i = 7; i >= 0; i--){ if (acc === 0) break; arr[i] = acc & 255; acc -= arr[i]; acc /= 256; } return arr; }; function number(n) { if (!Number.isSafeInteger(n) || n < 0) throw new Error(`positive integer expected, not ${n}`); } // copied from utils function isBytes(a) { return a instanceof Uint8Array || a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array'; } function bytes(b, ...lengths) { if (!isBytes(b)) throw new Error('Uint8Array expected'); if (lengths.length > 0 && !lengths.includes(b.length)) throw new Error(`Uint8Array expected of length ${lengths}, not of length=${b.length}`); } function hash(h) { if (typeof h !== 'function' || typeof h.create !== 'function') throw new Error('Hash should be wrapped by utils.wrapConstructor'); number(h.outputLen); number(h.blockLen); } function exists(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'); } function output(out, instance) { bytes(out); const min = instance.outputLen; if (out.length < min) { throw new Error(`digestInto() expects output buffer of length at least ${min}`); } } /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */ // We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+. // node.js versions earlier than v19 don't declare it in global scope. // For node.js, package.json#exports field mapping rewrites import // from `crypto` to `cryptoNode`, which imports native module. // Makes the utils un-importable in browsers without a bundler. // Once node.js 18 is deprecated (2025-04-30), we can just drop the import. const u32 = (arr)=>new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4)); // Cast array to view const createView = (arr)=>new DataView(arr.buffer, arr.byteOffset, arr.byteLength); // The rotate right (circular right shift) operation for uint32 const rotr = (word, shift)=>word << 32 - shift | word >>> shift; // The rotate left (circular left shift) operation for uint32 const rotl = (word, shift)=>word << shift | word >>> 32 - shift >>> 0; const isLE = new Uint8Array(new Uint32Array([ 0x11223344 ]).buffer)[0] === 0x44; // The byte swap operation for uint32 const byteSwap = (word)=>word << 24 & 0xff000000 | word << 8 & 0xff0000 | word >>> 8 & 0xff00 | word >>> 24 & 0xff; // In place byte swap for Uint32Array function byteSwap32(arr) { for(let i = 0; i < arr.length; i++){ arr[i] = byteSwap(arr[i]); } } /** * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99]) */ function utf8ToBytes(str) { if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`); return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809 } /** * Normalizes (non-hex) string or Uint8Array to Uint8Array. * Warning: when Uint8Array is passed, it would NOT get copied. * Keep in mind for future mutable operations. */ function toBytes(data) { if (typeof data === 'string') data = utf8ToBytes(data); bytes(data); return data; } // For runtime check if class implements interface class Hash { // Safe version that clones internal state clone() { return this._cloneInto(); } } function wrapConstructor(hashCons) { const hashC = (msg)=>hashCons().update(toBytes(msg)).digest(); const tmp = hashCons(); hashC.outputLen = tmp.outputLen; hashC.blockLen = tmp.blockLen; hashC.create = ()=>hashCons(); return hashC; } // HMAC (RFC 2104) class HMAC extends Hash { update(buf) { exists(this); this.iHash.update(buf); return this; } digestInto(out) { exists(this); bytes(out, this.outputLen); this.finished = true; this.iHash.digestInto(out); this.oHash.update(out); this.oHash.digestInto(out); this.destroy(); } digest() { const out = new Uint8Array(this.oHash.outputLen); this.digestInto(out); return out; } _cloneInto(to) { // Create new instance without calling constructor since key already in state and we don't know it. to || (to = Object.create(Object.getPrototypeOf(this), {})); const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this; to = to; to.finished = finished; to.destroyed = destroyed; to.blockLen = blockLen; to.outputLen = outputLen; to.oHash = oHash._cloneInto(to.oHash); to.iHash = iHash._cloneInto(to.iHash); return to; } destroy() { this.destroyed = true; this.oHash.destroy(); this.iHash.destroy(); } constructor(hash$1, _key){ super(); this.finished = false; this.destroyed = false; hash(hash$1); const key = toBytes(_key); this.iHash = hash$1.create(); if (typeof this.iHash.update !== 'function') throw new Error('Expected instance of class which extends utils.Hash'); this.blockLen = this.iHash.blockLen; this.outputLen = this.iHash.outputLen; const blockLen = this.blockLen; const pad = new Uint8Array(blockLen); // blockLen can be bigger than outputLen pad.set(key.length > blockLen ? hash$1.create().update(key).digest() : key); for(let i = 0; i < pad.length; i++)pad[i] ^= 0x36; this.iHash.update(pad); // By doing update (processing of first block) of outer hash here we can re-use it between multiple calls via clone this.oHash = hash$1.create(); // Undo internal XOR && apply outer XOR for(let i = 0; i < pad.length; i++)pad[i] ^= 0x36 ^ 0x5c; this.oHash.update(pad); pad.fill(0); } } /** * HMAC: RFC2104 message authentication code. * @param hash - function that would be used e.g. sha256 * @param key - message key * @param message - message data */ const hmac = (hash, key, message)=>new HMAC(hash, key).update(message).digest(); hmac.create = (hash, key)=>new HMAC(hash, key); // Polyfill for Safari 14 function setBigUint64(view, byteOffset, value, isLE) { if (typeof view.setBigUint64 === 'function') return view.setBigUint64(byteOffset, value, isLE); const _32n = BigInt(32); const _u32_max = BigInt(0xffffffff); const wh = Number(value >> _32n & _u32_max); const wl = Number(value & _u32_max); const h = isLE ? 4 : 0; const l = isLE ? 0 : 4; view.setUint32(byteOffset + h, wh, isLE); view.setUint32(byteOffset + l, wl, isLE); } // Choice: a ? b : c const Chi = (a, b, c)=>a & b ^ ~a & c; // Majority function, true if any two inpust is true const Maj = (a, b, c)=>a & b ^ a & c ^ b & c; /** * Merkle-Damgard hash construction base class. * Could be used to create MD5, RIPEMD, SHA1, SHA2. */ class HashMD extends Hash { update(data) { exists(this); const { view, buffer, blockLen } = this; data = toBytes(data); const len = data.length; for(let pos = 0; pos < len;){ const take = Math.min(blockLen - this.pos, len - pos); // Fast path: we have at least one block in input, cast it to view and process if (take === blockLen) { const dataView = createView(data); for(; blockLen <= len - pos; pos += blockLen)this.process(dataView, pos); continue; } buffer.set(data.subarray(pos, pos + take), this.pos); this.pos += take; pos += take; if (this.pos === blockLen) { this.process(view, 0); this.pos = 0; } } this.length += data.length; this.roundClean(); return this; } digestInto(out) { exists(this); output(out, this); this.finished = true; // Padding // We can avoid allocation of buffer for padding completely if it // was previously not allocated here. But it won't change performance. const { buffer, view, blockLen, isLE } = this; let { pos } = this; // append the bit '1' to the message buffer[pos++] = 0b10000000; this.buffer.subarray(pos).fill(0); // we have less than padOffset left in buffer, so we cannot put length in // current block, need process it and pad again if (this.padOffset > blockLen - pos) { this.process(view, 0); pos = 0; } // Pad until full block byte with zeros for(let i = pos; i < blockLen; i++)buffer[i] = 0; // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen. // So we just write lowest 64 bits of that value. setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE); this.process(view, 0); const oview = createView(out); const len = this.outputLen; // NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT if (len % 4) throw new Error('_sha2: outputLen should be aligned to 32bit'); const outLen = len / 4; const state = this.get(); if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state'); for(let i = 0; i < outLen; i++)oview.setUint32(4 * i, state[i], isLE); } digest() { const { buffer, outputLen } = this; this.digestInto(buffer); const res = buffer.slice(0, outputLen); this.destroy(); return res; } _cloneInto(to) { to || (to = new this.constructor()); to.set(...this.get()); const { blockLen, buffer, length, finished, destroyed, pos } = this; to.length = length; to.pos = pos; to.finished = finished; to.destroyed = destroyed; if (length % blockLen) to.buffer.set(buffer); return to; } constructor(blockLen, outputLen, padOffset, isLE){ super(); this.blockLen = blockLen; this.outputLen = outputLen; this.padOffset = padOffset; this.isLE = isLE; this.finished = false; this.length = 0; this.pos = 0; this.destroyed = false; this.buffer = new Uint8Array(blockLen); this.view = createView(this.buffer); } } // SHA1 (RFC 3174) was cryptographically broken. It's still used. Don't use it for a new protocol. // Initial state const SHA1_IV = /* @__PURE__ */ new Uint32Array([ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]); // Temporary buffer, not used to store anything between runs // Named this way because it matches specification. const SHA1_W = /* @__PURE__ */ new Uint32Array(80); class SHA1 extends HashMD { get() { const { A, B, C, D, E } = this; return [ A, B, C, D, E ]; } set(A, B, C, D, E) { this.A = A | 0; this.B = B | 0; this.C = C | 0; this.D = D | 0; this.E = E | 0; } process(view, offset) { for(let i = 0; i < 16; i++, offset += 4)SHA1_W[i] = view.getUint32(offset, false); for(let i = 16; i < 80; i++)SHA1_W[i] = rotl(SHA1_W[i - 3] ^ SHA1_W[i - 8] ^ SHA1_W[i - 14] ^ SHA1_W[i - 16], 1); // Compression function main loop, 80 rounds let { A, B, C, D, E } = this; for(let i = 0; i < 80; i++){ let F, K; if (i < 20) { F = Chi(B, C, D); K = 0x5a827999; } else if (i < 40) { F = B ^ C ^ D; K = 0x6ed9eba1; } else if (i < 60) { F = Maj(B, C, D); K = 0x8f1bbcdc; } else { F = B ^ C ^ D; K = 0xca62c1d6; } const T = rotl(A, 5) + F + E + K + SHA1_W[i] | 0; E = D; D = C; C = rotl(B, 30); B = A; A = T; } // Add the compressed chunk to the current hash value A = A + this.A | 0; B = B + this.B | 0; C = C + this.C | 0; D = D + this.D | 0; E = E + this.E | 0; this.set(A, B, C, D, E); } roundClean() { SHA1_W.fill(0); } destroy() { this.set(0, 0, 0, 0, 0); this.buffer.fill(0); } constructor(){ super(64, 20, 8, false); this.A = SHA1_IV[0] | 0; this.B = SHA1_IV[1] | 0; this.C = SHA1_IV[2] | 0; this.D = SHA1_IV[3] | 0; this.E = SHA1_IV[4] | 0; } } const sha1 = /* @__PURE__ */ wrapConstructor(()=>new SHA1()); // SHA2-256 need to try 2^128 hashes to execute birthday attack. // BTC network is doing 2^67 hashes/sec as per early 2023. // Round constants: // first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) // prettier-ignore const SHA256_K = /* @__PURE__ */ 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 ]); // Initial state: // first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 // prettier-ignore const SHA256_IV = /* @__PURE__ */ new Uint32Array([ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]); // Temporary buffer, not used to store anything between runs // Named this way because it matches specification. const SHA256_W = /* @__PURE__ */ new Uint32Array(64); class SHA256 extends HashMD { get() { const { A, B, C, D, E, F, G, H } = this; return [ A, B, C, D, E, F, G, H ]; } // prettier-ignore set(A, B, C, D, E, F, G, H) { this.A = A | 0; this.B = B | 0; this.C = C | 0; this.D = D | 0; this.E = E | 0; this.F = F | 0; this.G = G | 0; this.H = H | 0; } process(view, offset) { // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array for(let i = 0; i < 16; i++, offset += 4)SHA256_W[i] = view.getUint32(offset, false); for(let i = 16; i < 64; i++){ const W15 = SHA256_W[i - 15]; const W2 = SHA256_W[i - 2]; const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3; const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10; SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0; } // Compression function main loop, 64 rounds let { A, B, C, D, E, F, G, H } = this; for(let i = 0; i < 64; i++){ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25); const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0; const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22); const T2 = sigma0 + Maj(A, B, C) | 0; H = G; G = F; F = E; E = D + T1 | 0; D = C; C = B; B = A; A = T1 + T2 | 0; } // Add the compressed chunk to the current hash value A = A + this.A | 0; B = B + this.B | 0; C = C + this.C | 0; D = D + this.D | 0; E = E + this.E | 0; F = F + this.F | 0; G = G + this.G | 0; H = H + this.H | 0; this.set(A, B, C, D, E, F, G, H); } roundClean() { SHA256_W.fill(0); } destroy() { this.set(0, 0, 0, 0, 0, 0, 0, 0); this.buffer.fill(0); } constructor(){ super(64, 32, 8, false); // We cannot use array here since array allows indexing by variable // which means optimizer/compiler cannot use registers. this.A = SHA256_IV[0] | 0; this.B = SHA256_IV[1] | 0; this.C = SHA256_IV[2] | 0; this.D = SHA256_IV[3] | 0; this.E = SHA256_IV[4] | 0; this.F = SHA256_IV[5] | 0; this.G = SHA256_IV[6] | 0; this.H = SHA256_IV[7] | 0; } } // Constants from https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf class SHA224 extends SHA256 { constructor(){ super(); this.A = 0xc1059ed8 | 0; this.B = 0x367cd507 | 0; this.C = 0x3070dd17 | 0; this.D = 0xf70e5939 | 0; this.E = 0xffc00b31 | 0; this.F = 0x68581511 | 0; this.G = 0x64f98fa7 | 0; this.H = 0xbefa4fa4 | 0; this.outputLen = 28; } } /** * SHA2-256 hash function * @param message - data that would be hashed */ const sha256 = /* @__PURE__ */ wrapConstructor(()=>new SHA256()); const sha224 = /* @__PURE__ */ wrapConstructor(()=>new SHA224()); const U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1); const _32n = /* @__PURE__ */ BigInt(32); // We are not using BigUint64Array, because they are extremely slow as per 2022 function fromBig(n, le = false) { if (le) return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) }; return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 }; } function split(lst, le = false) { let Ah = new Uint32Array(lst.length); let Al = new Uint32Array(lst.length); for(let i = 0; i < lst.length; i++){ const { h, l } = fromBig(lst[i], le); [Ah[i], Al[i]] = [ h, l ]; } return [ Ah, Al ]; } const toBig = (h, l)=>BigInt(h >>> 0) << _32n | BigInt(l >>> 0); // for Shift in [0, 32) const shrSH = (h, _l, s)=>h >>> s; const shrSL = (h, l, s)=>h << 32 - s | l >>> s; // Right rotate for Shift in [1, 32) const rotrSH = (h, l, s)=>h >>> s | l << 32 - s; const rotrSL = (h, l, s)=>h << 32 - s | l >>> s; // Right rotate for Shift in (32, 64), NOTE: 32 is special case. const rotrBH = (h, l, s)=>h << 64 - s | l >>> s - 32; const rotrBL = (h, l, s)=>h >>> s - 32 | l << 64 - s; // Right rotate for shift===32 (just swaps l&h) const rotr32H = (_h, l)=>l; const rotr32L = (h, _l)=>h; // Left rotate for Shift in [1, 32) const rotlSH = (h, l, s)=>h << s | l >>> 32 - s; const rotlSL = (h, l, s)=>l << s | h >>> 32 - s; // Left rotate for Shift in (32, 64), NOTE: 32 is special case. const rotlBH = (h, l, s)=>l << s - 32 | h >>> 64 - s; const rotlBL = (h, l, s)=>h << s - 32 | l >>> 64 - s; // JS uses 32-bit signed integers for bitwise operations which means we cannot // simple take carry out of low bit sum by shift, we need to use division. function add(Ah, Al, Bh, Bl) { const l = (Al >>> 0) + (Bl >>> 0); return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 }; } // Addition with more than 2 elements const add3L = (Al, Bl, Cl)=>(Al >>> 0) + (Bl >>> 0) + (Cl >>> 0); const add3H = (low, Ah, Bh, Ch)=>Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0; const add4L = (Al, Bl, Cl, Dl)=>(Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0); const add4H = (low, Ah, Bh, Ch, Dh)=>Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0; const add5L = (Al, Bl, Cl, Dl, El)=>(Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0); const add5H = (low, Ah, Bh, Ch, Dh, Eh)=>Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0; // prettier-ignore const u64 = { fromBig, split, toBig, shrSH, shrSL, rotrSH, rotrSL, rotrBH, rotrBL, rotr32H, rotr32L, rotlSH, rotlSL, rotlBH, rotlBL, add, add3L, add3H, add4L, add4H, add5H, add5L }; // Round contants (first 32 bits of the fractional parts of the cube roots of the first 80 primes 2..409): // prettier-ignore const [SHA512_Kh, SHA512_Kl] = /* @__PURE__ */ (()=>u64.split([ '0x428a2f98d728ae22', '0x7137449123ef65cd', '0xb5c0fbcfec4d3b2f', '0xe9b5dba58189dbbc', '0x3956c25bf348b538', '0x59f111f1b605d019', '0x923f82a4af194f9b', '0xab1c5ed5da6d8118', '0xd807aa98a3030242', '0x12835b0145706fbe', '0x243185be4ee4b28c', '0x550c7dc3d5ffb4e2', '0x72be5d74f27b896f', '0x80deb1fe3b1696b1', '0x9bdc06a725c71235', '0xc19bf174cf692694', '0xe49b69c19ef14ad2', '0xefbe4786384f25e3', '0x0fc19dc68b8cd5b5', '0x240ca1cc77ac9c65', '0x2de92c6f592b0275', '0x4a7484aa6ea6e483', '0x5cb0a9dcbd41fbd4', '0x76f988da831153b5', '0x983e5152ee66dfab', '0xa831c66d2db43210', '0xb00327c898fb213f', '0xbf597fc7beef0ee4', '0xc6e00bf33da88fc2', '0xd5a79147930aa725', '0x06ca6351e003826f', '0x142929670a0e6e70', '0x27b70a8546d22ffc', '0x2e1b21385c26c926', '0x4d2c6dfc5ac42aed', '0x53380d139d95b3df', '0x650a73548baf63de', '0x766a0abb3c77b2a8', '0x81c2c92e47edaee6', '0x92722c851482353b', '0xa2bfe8a14cf10364', '0xa81a664bbc423001', '0xc24b8b70d0f89791', '0xc76c51a30654be30', '0xd192e819d6ef5218', '0xd69906245565a910', '0xf40e35855771202a', '0x106aa07032bbd1b8', '0x19a4c116b8d2d0c8', '0x1e376c085141ab53', '0x2748774cdf8eeb99', '0x34b0bcb5e19b48a8', '0x391c0cb3c5c95a63', '0x4ed8aa4ae3418acb', '0x5b9cca4f7763e373', '0x682e6ff3d6b2b8a3', '0x748f82ee5defb2fc', '0x78a5636f43172f60', '0x84c87814a1f0ab72', '0x8cc702081a6439ec', '0x90befffa23631e28', '0xa4506cebde82bde9', '0xbef9a3f7b2c67915', '0xc67178f2e372532b', '0xca273eceea26619c', '0xd186b8c721c0c207', '0xeada7dd6cde0eb1e', '0xf57d4f7fee6ed178', '0x06f067aa72176fba', '0x0a637dc5a2c898a6', '0x113f9804bef90dae', '0x1b710b35131c471b', '0x28db77f523047d84', '0x32caab7b40c72493', '0x3c9ebe0a15c9bebc', '0x431d67c49c100d4c', '0x4cc5d4becb3e42b6', '0x597f299cfc657e2a', '0x5fcb6fab3ad6faec', '0x6c44198c4a475817' ].map((n)=>BigInt(n))))(); // Temporary buffer, not used to store anything between runs const SHA512_W_H = /* @__PURE__ */ new Uint32Array(80); const SHA512_W_L = /* @__PURE__ */ new Uint32Array(80); class SHA512 extends HashMD { // prettier-ignore get() { const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this; return [ Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl ]; } // prettier-ignore set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) { this.Ah = Ah | 0; this.Al = Al | 0; this.Bh = Bh | 0; this.Bl = Bl | 0; this.Ch = Ch | 0; this.Cl = Cl | 0; this.Dh = Dh | 0; this.Dl = Dl | 0; this.Eh = Eh | 0; this.El = El | 0; this.Fh = Fh | 0; this.Fl = Fl | 0; this.Gh = Gh | 0; this.Gl = Gl | 0; this.Hh = Hh | 0; this.Hl = Hl | 0; } process(view, offset) { // Extend the first 16 words into the remaining 64 words w[16..79] of the message schedule array for(let i = 0; i < 16; i++, offset += 4){ SHA512_W_H[i] = view.getUint32(offset); SHA512_W_L[i] = view.getUint32(offset += 4); } for(let i = 16; i < 80; i++){ // s0 := (w[i-15] rightrotate 1) xor (w[i-15] rightrotate 8) xor (w[i-15] rightshift 7) const W15h = SHA512_W_H[i - 15] | 0; const W15l = SHA512_W_L[i - 15] | 0; const s0h = u64.rotrSH(W15h, W15l, 1) ^ u64.rotrSH(W15h, W15l, 8) ^ u64.shrSH(W15h, W15l, 7); const s0l = u64.rotrSL(W15h, W15l, 1) ^ u64.rotrSL(W15h, W15l, 8) ^ u64.shrSL(W15h, W15l, 7); // s1 := (w[i-2] rightrotate 19) xor (w[i-2] rightrotate 61) xor (w[i-2] rightshift 6) const W2h = SHA512_W_H[i - 2] | 0; const W2l = SHA512_W_L[i - 2] | 0; const s1h = u64.rotrSH(W2h, W2l, 19) ^ u64.rotrBH(W2h, W2l, 61) ^ u64.shrSH(W2h, W2l, 6); const s1l = u64.rotrSL(W2h, W2l, 19) ^ u64.rotrBL(W2h, W2l, 61) ^ u64.shrSL(W2h, W2l, 6); // SHA256_W[i] = s0 + s1 + SHA256_W[i - 7] + SHA256_W[i - 16]; const SUMl = u64.add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]); const SUMh = u64.add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]); SHA512_W_H[i] = SUMh | 0; SHA512_W_L[i] = SUMl | 0; } let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this; // Compression function main loop, 80 rounds for(let i = 0; i < 80; i++){ // S1 := (e rightrotate 14) xor (e rightrotate 18) xor (e rightrotate 41) const sigma1h = u64.rotrSH(Eh, El, 14) ^ u64.rotrSH(Eh, El, 18) ^ u64.rotrBH(Eh, El, 41); const sigma1l = u64.rotrSL(Eh, El, 14) ^ u64.rotrSL(Eh, El, 18) ^ u64.rotrBL(Eh, El, 41); //const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0; const CHIh = Eh & Fh ^ ~Eh & Gh; const CHIl = El & Fl ^ ~El & Gl; // T1 = H + sigma1 + Chi(E, F, G) + SHA512_K[i] + SHA512_W[i] // prettier-ignore const T1ll = u64.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]); const T1h = u64.add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]); const T1l = T1ll | 0; // S0 := (a rightrotate 28) xor (a rightrotate 34) xor (a rightrotate 39) const sigma0h = u64.rotrSH(Ah, Al, 28) ^ u64.rotrBH(Ah, Al, 34) ^ u64.rotrBH(Ah, Al, 39); const sigma0l = u64.rotrSL(Ah, Al, 28) ^ u64.rotrBL(Ah, Al, 34) ^ u64.rotrBL(Ah, Al, 39); const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch; const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl; Hh = Gh | 0; Hl = Gl | 0; Gh = Fh | 0; Gl = Fl | 0; Fh = Eh | 0; Fl = El | 0; ({ h: Eh, l: El } = u64.add(Dh | 0, Dl | 0, T1h | 0, T1l | 0)); Dh = Ch | 0; Dl = Cl | 0; Ch = Bh | 0; Cl = Bl | 0; Bh = Ah | 0; Bl = Al | 0; const All = u64.add3L(T1l, sigma0l, MAJl); Ah = u64.add3H(All, T1h, sigma0h, MAJh); Al = All | 0; } // Add the compressed chunk to the current hash value ({ h: Ah, l: Al } = u64.add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0)); ({ h: Bh, l: Bl } = u64.add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0)); ({ h: Ch, l: Cl } = u64.add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0)); ({ h: Dh, l: Dl } = u64.add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0)); ({ h: Eh, l: El } = u64.add(this.Eh | 0, this.El | 0, Eh | 0, El | 0)); ({ h: Fh, l: Fl } = u64.add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0)); ({ h: Gh, l: Gl } = u64.add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0)); ({ h: Hh, l: Hl } = u64.add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0)); this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl); } roundClean() { SHA512_W_H.fill(0); SHA512_W_L.fill(0); } destroy() { this.buffer.fill(0); this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); } constructor(){ super(128, 64, 16, false); // We cannot use array here since array allows indexing by variable which means optimizer/compiler cannot use registers. // Also looks cleaner and easier to verify with spec. // Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19): // h -- high 32 bits, l -- low 32 bits this.Ah = 0x6a09e667 | 0; this.Al = 0xf3bcc908 | 0; this.Bh = 0xbb67ae85 | 0; this.Bl = 0x84caa73b | 0; this.Ch = 0x3c6ef372 | 0; this.Cl = 0xfe94f82b | 0; this.Dh = 0xa54ff53a | 0; this.Dl = 0x5f1d36f1 | 0; this.Eh = 0x510e527f | 0; this.El = 0xade682d1 | 0; this.Fh = 0x9b05688c | 0; this.Fl = 0x2b3e6c1f | 0; this.Gh = 0x1f83d9ab | 0; this.Gl = 0xfb41bd6b | 0; this.Hh = 0x5be0cd19 | 0; this.Hl = 0x137e2179 | 0; } } class SHA384 extends SHA512 { constructor(){ super(); // h -- high 32 bits, l -- low 32 bits this.Ah = 0xcbbb9d5d | 0; this.Al = 0xc1059ed8 | 0; this.Bh = 0x629a292a | 0; this.Bl = 0x367cd507 | 0; this.Ch = 0x9159015a | 0; this.Cl = 0x3070dd17 | 0; this.Dh = 0x152fecd8 | 0; this.Dl = 0xf70e5939 | 0; this.Eh = 0x67332667 | 0; this.El = 0xffc00b31 | 0; this.Fh = 0x8eb44a87 | 0; this.Fl = 0x68581511 | 0; this.Gh = 0xdb0c2e0d | 0; this.Gl = 0x64f98fa7 | 0; this.Hh = 0x47b5481d | 0; this.Hl = 0xbefa4fa4 | 0; this.outputLen = 48; } } const sha512 = /* @__PURE__ */ wrapConstructor(()=>new SHA512()); const sha384 = /* @__PURE__ */ wrapConstructor(()=>new SHA384()); // SHA3 (keccak) is based on a new design: basically, the internal state is bigger than output size. // It's called a sponge function. // Various per round constants calculations const SHA3_PI = []; const SHA3_ROTL = []; const _SHA3_IOTA = []; const _0n = /* @__PURE__ */ BigInt(0); const _1n = /* @__PURE__ */ BigInt(1); const _2n = /* @__PURE__ */ BigInt(2); const _7n = /* @__PURE__ */ BigInt(7); const _256n = /* @__PURE__ */ BigInt(256); const _0x71n = /* @__PURE__ */ BigInt(0x71); for(let round = 0, R = _1n, x = 1, y = 0; round < 24; round++){ // Pi [x, y] = [ y, (2 * x + 3 * y) % 5 ]; SHA3_PI.push(2 * (5 * y + x)); // Rotational SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64); // Iota let t = _0n; for(let j = 0; j < 7; j++){ R = (R << _1n ^ (R >> _7n) * _0x71n) % _256n; if (R & _2n) t ^= _1n << (_1n << /* @__PURE__ */ BigInt(j)) - _1n; } _SHA3_IOTA.push(t); } const [SHA3_IOTA_H, SHA3_IOTA_L] = /* @__PURE__ */ split(_SHA3_IOTA, true); // Left rotation (without 0, 32, 64) const rotlH = (h, l, s)=>s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s); const rotlL = (h, l, s)=>s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s); // Same as keccakf1600, but allows to skip some rounds function keccakP(s, rounds = 24) { const B = new Uint32Array(5 * 2); // NOTE: all indices are x2 since we store state as u32 instead of u64 (bigints to slow in js) for(let round = 24 - rounds; round < 24; round++){ // Theta θ for(let x = 0; x < 10; x++)B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40]; for(let x = 0; x < 10; x += 2){ const idx1 = (x + 8) % 10; const idx0 = (x + 2) % 10; const B0 = B[idx0]; const B1 = B[idx0 + 1]; const Th = rotlH(B0, B1, 1) ^ B[idx1]; const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1]; for(let y = 0; y < 50; y += 10){ s[x + y] ^= Th; s[x + y + 1] ^= Tl; } } // Rho (ρ) and Pi (π) let curH = s[2]; let curL = s[3]; for(let t = 0; t < 24; t++){ const shift = SHA3_ROTL[t]; const Th = rotlH(curH, curL, shift); const Tl = rotlL(curH, curL, shift); const PI = SHA3_PI[t]; curH = s[PI]; curL = s[PI + 1]; s[PI] = Th; s[PI + 1] = Tl; } // Chi (χ) for(let y = 0; y < 50; y += 10){ for(let x = 0; x < 10; x++)B[x] = s[y + x]; for(let x = 0; x < 10; x++)s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10]; } // Iota (ι) s[0] ^= SHA3_IOTA_H[round]; s[1] ^= SHA3_IOTA_L[round]; } B.fill(0); } class Keccak extends Hash { keccak() { if (!isLE) byteSwap32(this.state32); keccakP(this.state32, this.rounds); if (!isLE) byteSwap32(this.state32); this.posOut = 0; this.pos = 0; } update(data) { exists(this); const { blockLen, state } = this; data = toBytes(data); const len = data.length; for(let pos = 0; pos < len;){ const take = Math.min(blockLen - this.pos, len - pos); for(let i = 0; i < take; i++)state[this.pos++] ^= data[pos++]; if (this.pos === blockLen) this.keccak(); } return this; } finish() { if (this.finished) return; this.finished = true; const { state, suffix, pos, blockLen } = this; // Do the padding state[pos] ^= suffix; if ((suffix & 0x80) !== 0 && pos === blockLen - 1) this.keccak(); state[blockLen - 1] ^= 0x80; this.keccak(); } writeInto(out) { exists(this, false); bytes(out); this.finish(); const bufferOut = this.state; const { blockLen } = this; for(let pos = 0, len = out.length; pos < len;){ if (this.posOut >= blockLen) this.keccak(); const take = Math.min(blockLen - this.posOut, len - pos); out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos); this.posOut += take; pos += take; } return out; } xofInto(out) { // Sha3/Keccak usage with XOF is probably mistake, only SHAKE instances can do XOF if (!this.enableXOF) throw new Error('XOF is not possible for this instance'); return this.writeInto(out); } xof(bytes) { number(bytes); return this.xofInto(new Uint8Array(bytes)); } digestInto(out) { output(out, this); if (this.finished) throw new Error('digest() was already called'); this.writeInto(out); this.destroy(); return out; } digest() { return this.digestInto(new Uint8Array(this.outputLen)); } destroy() { this.destroyed = true; this.state.fill(0); } _cloneInto(to) { const { blockLen, suffix, outputLen, rounds, enableXOF } = this; to || (to = new Keccak(blockLen, suffix, outputLen, enableXOF, rounds)); to.state32.set(this.state32); to.pos = this.pos; to.posOut = this.posOut; to.finished = this.finished; to.rounds = rounds; // Suffix can change in cSHAKE to.suffix = suffix; to.outputLen = outputLen; to.enableXOF = enableXOF; to.destroyed = this.destroyed; return to; } // NOTE: we accept arguments in bytes instead of bits here. constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24){ super(); this.blockLen = blockLen; this.suffix = suffix; this.outputLen = outputLen; this.enableXOF = enableXOF; this.rounds = rounds; this.pos = 0; this.posOut = 0; this.finished = false; this.destroyed = false; // Can be passed from user as dkLen number(outputLen); // 1600 = 5x5 matrix of 64bit. 1600 bits === 200 bytes if (0 >= this.blockLen || this.blockLen >= 200) throw new Error('Sha3 supports only keccak-f1600 function'); this.state = new Uint8Array(200); this.state32 = u32(this.state); } } const gen = (suffix, blockLen, outputLen)=>wrapConstructor(()=>new Keccak(blockLen, suffix, outputLen)); const sha3_224 = /* @__PURE__ */ gen(0x06, 144, 224 / 8); /** * SHA3-256 hash function * @param message - that would be hashed */ const sha3_256 = /* @__PURE__ */ gen(0x06, 136, 256 / 8); const sha3_384 = /* @__PURE__ */ gen(0x06, 104, 384 / 8); const sha3_512 = /* @__PURE__ */ gen(0x06, 72, 512 / 8); /** * "globalThis" ponyfill. * @see [A horrifying globalThis polyfill in universal JavaScript](https://mathiasbynens.be/notes/globalthis) * @type {Object.} */ const globalScope = (()=>{ if (typeof globalThis === "object") return globalThis; else { Object.defineProperty(Object.prototype, "__GLOBALTHIS__", { get () { return this; }, configurable: true }); try { // @ts-ignore // eslint-disable-next-line no-undef if (typeof __GLOBALTHIS__ !== "undefined") return __GLOBALTHIS__; } finally{ // @ts-ignore delete Object.prototype.__GLOBALTHIS__; } } // Still unable to determine "globalThis", fall back to a naive method. if (typeof self !== "undefined") return self; else if (typeof window !== "undefined") return window; else if (typeof global !== "undefined") return global; return undefined; })(); /** * OpenSSL-Noble hashes map. * @type {Object.} */ const OPENSSL_NOBLE_HASHES = { SHA1: sha1, SHA224: sha224, SHA256: sha256, SHA384: sha384, SHA512: sha512, "SHA3-224": sha3_224, "SHA3-256": sha3_256, "SHA3-384": sha3_384, "SHA3-512": sha3_512 }; /** * Calculates an HMAC digest. * In Node.js, the command "openssl list -digest-algorithms" displays the available digest algorithms. * @param {string} algorithm Algorithm. * @param {Uint8Array} key Key. * @param {Uint8Array} message Message. * @returns {Uint8Array} Digest. */ const hmacDigest = (algorithm, key, message)=>{ if (hmac) { const hash = OPENSSL_NOBLE_HASHES[algorithm.toUpperCase()]; if (!hash) throw new TypeError("Unknown hash function"); return hmac(hash, key, message); } else { throw new Error("Missing HMAC function"); } }; /** * RFC 4648 base32 alphabet without pad. * @type {string} */ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; /** * Converts a base32 string to an Uint8Array (RFC 4648). * @see [LinusU/base32-decode](https://github.com/LinusU/base32-decode) * @param {string} str Base32 string. * @returns {Uint8Array} Uint8Array. */ const base32Decode = (str)=>{ // Canonicalize to all upper case and remove padding if it exists. let end = str.length; while(str[end - 1] === "=")--end; const cstr = (end < str.length ? str.substring(0, end) : str).toUpperCase(); const buf = new ArrayBuffer(cstr.length * 5 / 8 | 0); const arr = new Uint8Array(buf); let bits = 0; let value = 0; let index = 0; for(let i = 0; i < cstr.length; i++){ const idx = ALPHABET.indexOf(cstr[i]); if (idx === -1) throw new TypeError(`Invalid character found: ${cstr[i]}`); value = value << 5 | idx; bits += 5; if (bits >= 8) { bits -= 8; arr[index++] = value >>> bits; } } return arr; }; /** * Converts an Uint8Array to a base32 string (RFC 4648). * @see [LinusU/base32-encode](https://github.com/LinusU/base32-encode) * @param {Uint8Array} arr Uint8Array. * @returns {string} Base32 string. */ const base32Encode = (arr)=>{ let bits = 0; let value = 0; let str = ""; for(let i = 0; i < arr.length; i++){ value = value << 8 | arr[i]; bits += 8; while(bits >= 5){ str += ALPHABET[value >>> bits - 5 & 31]; bits -= 5; } } if (bits > 0) { str += ALPHABET[value << 5 - bits & 31]; } return str; }; /** * Converts a hexadecimal string to an Uint8Array. * @param {string} str Hexadecimal string. * @returns {Uint8Array} Uint8Array. */ const hexDecode = (str)=>{ const buf = new ArrayBuffer(str.length / 2); const arr = new Uint8Array(buf); for(let i = 0; i < str.length; i += 2){ arr[i / 2] = parseInt(str.substring(i, i + 2), 16); } return arr; }; /** * Converts an Uint8Array to a hexadecimal string. * @param {Uint8Array} arr Uint8Array. * @returns {string} Hexadecimal string. */ const hexEncode = (arr)=>{ let str = ""; for(let i = 0; i < arr.length; i++){ const hex = arr[i].toString(16); if (hex.length === 1) str += "0"; str += hex; } return str.toUpperCase(); }; /** * Converts a Latin-1 string to an Uint8Array. * @param {string} str Latin-1 string. * @returns {Uint8Array} Uint8Array. */ const latin1Decode = (str)=>{ const buf = new ArrayBuffer(str.length); const arr = new Uint8Array(buf); for(let i = 0; i < str.length; i++){ arr[i] = str.charCodeAt(i) & 0xff; } return arr; }; /** * Converts an Uint8Array to a Latin-1 string. * @param {Uint8Array} arr Uint8Array. * @returns {string} Latin-1 string. */ const latin1Encode = (arr)=>{ let str = ""; for(let i = 0; i < arr.length; i++){ str += String.fromCharCode(arr[i]); } return str; }; /** * TextEncoder instance. * @type {TextEncoder|null} */ const ENCODER = globalScope.TextEncoder ? new globalScope.TextEncoder() : null; /** * TextDecoder instance. * @type {TextDecoder|null} */ const DECODER = globalScope.TextDecoder ? new globalScope.TextDecoder() : null; /** * Converts an UTF-8 string to an Uint8Array. * @param {string} str String. * @returns {Uint8Array} Uint8Array. */ const utf8Decode = (str)=>{ if (!ENCODER) { throw new Error("Encoding API not available"); } return ENCODER.encode(str); }; /** * Converts an Uint8Array to an UTF-8 string. * @param {Uint8Array} arr Uint8Array. * @returns {string} String. */ const utf8Encode = (arr)=>{ if (!DECODER) { throw new Error("Encoding API not available"); } return DECODER.decode(arr); }; /** * Returns random bytes. * @param {number} size Size. * @returns {Uint8Array} Random bytes. */ const randomBytes = (size)=>{ { if (!globalScope.crypto?.getRandomValues) { throw new Error("Cryptography API not available"); } return globalScope.crypto.getRandomValues(new Uint8Array(size)); } }; /** * OTP secret key. */ class Secret { /** * Converts a Latin-1 string to a Secret object. * @param {string} str Latin-1 string. * @returns {Secret} Secret object. */ static fromLatin1(str) { return new Secret({ buffer: latin1Decode(str).buffer }); } /** * Converts an UTF-8 string to a Secret object. * @param {string} str UTF-8 string. * @returns {Secret} Secret object. */ static fromUTF8(str) { return new Secret({ buffer: utf8Decode(str).buffer }); } /** * Converts a base32 string to a Secret object. * @param {string} str Base32 string. * @returns {Secret} Secret object. */ static fromBase32(str) { return new Secret({ buffer: base32Decode(str).buffer }); } /** * Converts a hexadecimal string to a Secret object. * @param {string} str Hexadecimal string. * @returns {Secret} Secret object. */ static fromHex(str) { return new Secret({ buffer: hexDecode(str).buffer }); } /** * Secret key buffer. * @deprecated For backward compatibility, the "bytes" property should be used instead. * @type {ArrayBufferLike} */ get buffer() { return this.bytes.buffer; } /** * Latin-1 string representation of secret key. * @type {string} */ get latin1() { Object.defineProperty(this, "latin1", { enumerable: true, writable: false, configurable: false, value: latin1Encode(this.bytes) }); return this.latin1; } /** * UTF-8 string representation of secret key. * @type {string} */ get utf8() { Object.defineProperty(this, "utf8", { enumerable: true, writable: false, configurable: false, value: utf8Encode(this.bytes) }); return this.utf8; } /** * Base32 string representation of secret key. * @type {string} */ get base32() { Object.defineProperty(this, "base32", { enumerable: true, writable: false, configurable: false, value: base32Encode(this.bytes) }); return this.base32; } /** * Hexadecimal string representation of secret key. * @type {string} */ get hex() { Object.defineProperty(this, "hex", { enumerable: true, writable: false, configurable: false, value: hexEncode(this.bytes) }); return this.hex; } /** * Creates a secret key object. * @param {Object} [config] Configuration options. * @param {ArrayBufferLike} [config.buffer] Secret key buffer. * @param {number} [config.size=20] Number of random bytes to generate, ignored if 'buffer' is provided. */ constructor({ buffer, size = 20 } = {}){ /** * Secret key. * @type {Uint8Array} * @readonly */ this.bytes = typeof buffer === "undefined" ? randomBytes(size) : new Uint8Array(buffer); // Prevent the "bytes" property from being modified. Object.defineProperty(this, "bytes", { enumerable: true, writable: false, configurable: false, value: this.bytes }); } } /** * Returns true if a is equal to b, without leaking timing information that would allow an attacker to guess one of the values. * @param {string} a String a. * @param {string} b String b. * @returns {boolean} Equality result. */ const timingSafeEqual = (a, b)=>{ { if (a.length !== b.length) { throw new TypeError("Input strings must have the same length"); } let i = -1; let out = 0; while(++i < a.length){ out |= a.charCodeAt(i) ^ b.charCodeAt(i); } return out === 0; } }; /** * HOTP: An HMAC-based One-time Password Algorithm. * @see [RFC 4226](https://tools.ietf.org/html/rfc4226) */ class HOTP { /** * Default configuration. * @type {{ * issuer: string, * label: string, * issuerInLabel: boolean, * algorithm: string, * digits: number, * counter: number * window: number * }} */ static get defaults() { return { issuer: "", label: "OTPAuth", issuerInLabel: true, algorithm: "SHA1", digits: 6, counter: 0, window: 1 }; } /** * Generates an HOTP token. * @param {Object} config Configuration options. * @param {Secret} config.secret Secret key. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm. * @param {number} [config.digits=6] Token length. * @param {number} [config.counter=0] Counter value. * @returns {string} Token. */ static generate({ secret, algorithm = HOTP.defaults.algorithm, digits = HOTP.defaults.digits, counter = HOTP.defaults.counter }) { const digest = hmacDigest(algorithm, secret.bytes, uintDecode(counter)); const offset = digest[digest.byteLength - 1] & 15; const otp = ((digest[offset] & 127) << 24 | (digest[offset + 1] & 255) << 16 | (digest[offset + 2] & 255) << 8 | digest[offset + 3] & 255) % 10 ** digits; return otp.toString().padStart(digits, "0"); } /** * Generates an HOTP token. * @param {Object} [config] Configuration options. * @param {number} [config.counter=this.counter++] Counter value. * @returns {string} Token. */ generate({ counter = this.counter++ } = {}) { return HOTP.generate({ secret: this.secret, algorithm: this.algorithm, digits: this.digits, counter }); } /** * Validates an HOTP token. * @param {Object} config Configuration options. * @param {string} config.token Token value. * @param {Secret} config.secret Secret key. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm. * @param {number} config.digits Token length. * @param {number} [config.counter=0] Counter value. * @param {number} [config.window=1] Window of counter values to test. * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid. */ static validate({ token, secret, algorithm, digits, counter = HOTP.defaults.counter, window = HOTP.defaults.window }) { // Return early if the token length does not match the digit number. if (token.length !== digits) return null; let delta = null; const check = (/** @type {number} */ i)=>{ const generatedToken = HOTP.generate({ secret, algorithm, digits, counter: i }); if (timingSafeEqual(token, generatedToken)) { delta = i - counter; } }; check(counter); for(let i = 1; i <= window && delta === null; ++i){ check(counter - i); if (delta !== null) break; check(counter + i); if (delta !== null) break; } return delta; } /** * Validates an HOTP token. * @param {Object} config Configuration options. * @param {string} config.token Token value. * @param {number} [config.counter=this.counter] Counter value. * @param {number} [config.window=1] Window of counter values to test. * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid. */ validate({ token, counter = this.counter, window }) { return HOTP.validate({ token, secret: this.secret, algorithm: this.algorithm, digits: this.digits, counter, window }); } /** * Returns a Google Authenticator key URI. * @returns {string} URI. */ toString() { const e = encodeURIComponent; return "otpauth://hotp/" + `${this.issuer.length > 0 ? this.issuerInLabel ? `${e(this.issuer)}:${e(this.label)}?issuer=${e(this.issuer)}&` : `${e(this.label)}?issuer=${e(this.issuer)}&` : `${e(this.label)}?`}` + `secret=${e(this.secret.base32)}&` + `algorithm=${e(this.algorithm)}&` + `digits=${e(this.digits)}&` + `counter=${e(this.counter)}`; } /** * Creates an HOTP object. * @param {Object} [config] Configuration options. * @param {string} [config.issuer=''] Account provider. * @param {string} [config.label='OTPAuth'] Account label. * @param {boolean} [config.issuerInLabel=true] Include issuer prefix in label. * @param {Secret|string} [config.secret=Secret] Secret key. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm. * @param {number} [config.digits=6] Token length. * @param {number} [config.counter=0] Initial counter value. */ constructor({ issuer = HOTP.defaults.issuer, label = HOTP.defaults.label, issuerInLabel = HOTP.defaults.issuerInLabel, secret = new Secret(), algorithm = HOTP.defaults.algorithm, digits = HOTP.defaults.digits, counter = HOTP.defaults.counter } = {}){ /** * Account provider. * @type {string} */ this.issuer = issuer; /** * Account label. * @type {string} */ this.label = label; /** * Include issuer prefix in label. * @type {boolean} */ this.issuerInLabel = issuerInLabel; /** * Secret key. * @type {Secret} */ this.secret = typeof secret === "string" ? Secret.fromBase32(secret) : secret; /** * HMAC hashing algorithm. * @type {string} */ this.algorithm = algorithm.toUpperCase(); /** * Token length. * @type {number} */ this.digits = digits; /** * Initial counter value. * @type {number} */ this.counter = counter; } } /** * TOTP: Time-Based One-Time Password Algorithm. * @see [RFC 6238](https://tools.ietf.org/html/rfc6238) */ class TOTP { /** * Default configuration. * @type {{ * issuer: string, * label: string, * issuerInLabel: boolean, * algorithm: string, * digits: number, * period: number * window: number * }} */ static get defaults() { return { issuer: "", label: "OTPAuth", issuerInLabel: true, algorithm: "SHA1", digits: 6, period: 30, window: 1 }; } /** * Generates a TOTP token. * @param {Object} config Configuration options. * @param {Secret} config.secret Secret key. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm. * @param {number} [config.digits=6] Token length. * @param {number} [config.period=30] Token time-step duration. * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds. * @returns {string} Token. */ static generate({ secret, algorithm, digits, period = TOTP.defaults.period, timestamp = Date.now() }) { return HOTP.generate({ secret, algorithm, digits, counter: Math.floor(timestamp / 1000 / period) }); } /** * Generates a TOTP token. * @param {Object} [config] Configuration options. * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds. * @returns {string} Token. */ generate({ timestamp = Date.now() } = {}) { return TOTP.generate({ secret: this.secret, algorithm: this.algorithm, digits: this.digits, period: this.period, timestamp }); } /** * Validates a TOTP token. * @param {Object} config Configuration options. * @param {string} config.token Token value. * @param {Secret} config.secret Secret key. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm. * @param {number} config.digits Token length. * @param {number} [config.period=30] Token time-step duration. * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds. * @param {number} [config.window=1] Window of counter values to test. * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid. */ static validate({ token, secret, algorithm, digits, period = TOTP.defaults.period, timestamp = Date.now(), window }) { return HOTP.validate({ token, secret, algorithm, digits, counter: Math.floor(timestamp / 1000 / period), window }); } /** * Validates a TOTP token. * @param {Object} config Configuration options. * @param {string} config.token Token value. * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds. * @param {number} [config.window=1] Window of counter values to test. * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid. */ validate({ token, timestamp, window }) { return TOTP.validate({ token, secret: this.secret, algorithm: this.algorithm, digits: this.digits, period: this.period, timestamp, window }); } /** * Returns a Google Authenticator key URI. * @returns {string} URI. */ toString() { const e = encodeURIComponent; return "otpauth://totp/" + `${this.issuer.length > 0 ? this.issuerInLabel ? `${e(this.issuer)}:${e(this.label)}?issuer=${e(this.issuer)}&` : `${e(this.label)}?issuer=${e(this.issuer)}&` : `${e(this.label)}?`}` + `secret=${e(this.secret.base32)}&` + `algorithm=${e(this.algorithm)}&` + `digits=${e(this.digits)}&` + `period=${e(this.period)}`; } /** * Creates a TOTP object. * @param {Object} [config] Configuration options. * @param {string} [config.issuer=''] Account provider. * @param {string} [config.label='OTPAuth'] Account label. * @param {boolean} [config.issuerInLabel=true] Include issuer prefix in label. * @param {Secret|string} [config.secret=Secret] Secret key. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm. * @param {number} [config.digits=6] Token length. * @param {number} [config.period=30] Token time-step duration. */ constructor({ issuer = TOTP.defaults.issuer, label = TOTP.defaults.label, issuerInLabel = TOTP.defaults.issuerInLabel, secret = new Secret(), algorithm = TOTP.defaults.algorithm, digits = TOTP.defaults.digits, period = TOTP.defaults.period } = {}){ /** * Account provider. * @type {string} */ this.issuer = issuer; /** * Account label. * @type {string} */ this.label = label; /** * Include issuer prefix in label. * @type {boolean} */ this.issuerInLabel = issuerInLabel; /** * Secret key. * @type {Secret} */ this.secret = typeof secret === "string" ? Secret.fromBase32(secret) : secret; /** * HMAC hashing algorithm. * @type {string} */ this.algorithm = algorithm.toUpperCase(); /** * Token length. * @type {number} */ this.digits = digits; /** * Token time-step duration. * @type {number} */ this.period = period; } } /** * Key URI regex (otpauth://TYPE/[ISSUER:]LABEL?PARAMETERS). * @type {RegExp} */ const OTPURI_REGEX = /^otpauth:\/\/([ht]otp)\/(.+)\?([A-Z0-9.~_-]+=[^?&]*(?:&[A-Z0-9.~_-]+=[^?&]*)*)$/i; /** * RFC 4648 base32 alphabet with pad. * @type {RegExp} */ const SECRET_REGEX = /^[2-7A-Z]+=*$/i; /** * Regex for supported algorithms. * @type {RegExp} */ const ALGORITHM_REGEX = /^SHA(?:1|224|256|384|512|3-224|3-256|3-384|3-512)$/i; /** * Integer regex. * @type {RegExp} */ const INTEGER_REGEX = /^[+-]?\d+$/; /** * Positive integer regex. * @type {RegExp} */ const POSITIVE_INTEGER_REGEX = /^\+?[1-9]\d*$/; /** * HOTP/TOTP object/string conversion. * @see [Key URI Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) */ class URI { /** * Parses a Google Authenticator key URI and returns an HOTP/TOTP object. * @param {string} uri Google Authenticator Key URI. * @returns {HOTP|TOTP} HOTP/TOTP object. */ static parse(uri) { let uriGroups; try { uriGroups = uri.match(OTPURI_REGEX); // eslint-disable-next-line no-unused-vars } catch (_) { /* Handled below */ } if (!Array.isArray(uriGroups)) { throw new URIError("Invalid URI format"); } // Extract URI groups. const uriType = uriGroups[1].toLowerCase(); const uriLabel = uriGroups[2].split(/(?::|%3A) *(.+)/i, 2).map(decodeURIComponent); /** @type {Object.} */ const uriParams = uriGroups[3].split("&").reduce((acc, cur)=>{ const pairArr = cur.split(/=(.*)/, 2).map(decodeURIComponent); const pairKey = pairArr[0].toLowerCase(); const pairVal = pairArr[1]; /** @type {Object.} */ const pairAcc = acc; pairAcc[pairKey] = pairVal; return pairAcc; }, {}); // 'OTP' will be instantiated with 'config' argument. let OTP; const config = {}; if (uriType === "hotp") { OTP = HOTP; // Counter: required if (typeof uriParams.counter !== "undefined" && INTEGER_REGEX.test(uriParams.counter)) { config.counter = parseInt(uriParams.counter, 10); } else { throw new TypeError("Missing or invalid 'counter' parameter"); } } else if (uriType === "totp") { OTP = TOTP; // Period: optional if (typeof uriParams.period !== "undefined") { if (POSITIVE_INTEGER_REGEX.test(uriParams.period)) { config.period = parseInt(uriParams.period, 10); } else { throw new TypeError("Invalid 'period' parameter"); } } } else { throw new TypeError("Unknown OTP type"); } // Label: required // Issuer: optional if (typeof uriParams.issuer !== "undefined") { config.issuer = uriParams.issuer; } if (uriLabel.length === 2) { config.label = uriLabel[1]; if (typeof config.issuer === "undefined" || config.issuer === "") { config.issuer = uriLabel[0]; } else if (uriLabel[0] === "") { config.issuerInLabel = false; } } else { config.label = uriLabel[0]; if (typeof config.issuer !== "undefined" && config.issuer !== "") { config.issuerInLabel = false; } } // Secret: required if (typeof uriParams.secret !== "undefined" && SECRET_REGEX.test(uriParams.secret)) { config.secret = uriParams.secret; } else { throw new TypeError("Missing or invalid 'secret' parameter"); } // Algorithm: optional if (typeof uriParams.algorithm !== "undefined") { if (ALGORITHM_REGEX.test(uriParams.algorithm)) { config.algorithm = uriParams.algorithm; } else { throw new TypeError("Invalid 'algorithm' parameter"); } } // Digits: optional if (typeof uriParams.digits !== "undefined") { if (POSITIVE_INTEGER_REGEX.test(uriParams.digits)) { config.digits = parseInt(uriParams.digits, 10); } else { throw new TypeError("Invalid 'digits' parameter"); } } return new OTP(config); } /** * Converts an HOTP/TOTP object to a Google Authenticator key URI. * @param {HOTP|TOTP} otp HOTP/TOTP object. * @returns {string} Google Authenticator Key URI. */ static stringify(otp) { if (otp instanceof HOTP || otp instanceof TOTP) { return otp.toString(); } throw new TypeError("Invalid 'HOTP/TOTP' object"); } } /** * Library version. * @type {string} */ const version = "9.3.1"; export { HOTP, Secret, TOTP, URI, version };