UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

92 lines 3.75 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TOTP = void 0; const Hash_js_1 = require("../primitives/Hash.js"); const BigNumber_js_1 = __importDefault(require("../primitives/BigNumber.js")); // eslint-disable-next-line @typescript-eslint/no-extraneous-class class TOTP { /** * Generates a Time-based One-Time Password (TOTP). * @param {number[]} secret - The secret key for TOTP. * @param {TOTPOptions} options - Optional parameters for TOTP. * @returns {string} The generated TOTP. */ static generate(secret, options) { const _options = this.withDefaultOptions(options); const counter = this.getCounter(_options.timestamp, _options.period); const otp = generateHOTP(secret, counter, _options); return otp; } /** * Validates a Time-based One-Time Password (TOTP). * @param {number[]} secret - The secret key for TOTP. * @param {string} passcode - The passcode to validate. * @param {TOTPValidateOptions} options - Optional parameters for TOTP validation. * @returns {boolean} A boolean indicating whether the passcode is valid. */ static validate(secret, passcode, options) { const _options = this.withDefaultValidateOptions(options); passcode = passcode.trim(); if (passcode.length !== _options.digits) { return false; } const counter = this.getCounter(_options.timestamp, _options.period); const counters = [counter]; for (let i = 1; i <= _options.skew; i++) { counters.push(counter + i); counters.push(counter - i); } for (const c of counters) { if (passcode === generateHOTP(secret, c, _options)) { return true; } } return false; } static getCounter(timestamp, period) { const epochSeconds = Math.floor(timestamp / 1000); const counter = Math.floor(epochSeconds / period); return counter; } static withDefaultOptions(options) { return { digits: 2, algorithm: 'SHA-1', period: 30, timestamp: Date.now(), ...options }; } static withDefaultValidateOptions(options) { return { skew: 1, ...this.withDefaultOptions(options) }; } } exports.TOTP = TOTP; function generateHOTP(secret, counter, options) { const timePad = new BigNumber_js_1.default(counter).toArray('be', 8); const hmac = calcHMAC(secret, timePad, options.algorithm); const signature = hmac.digest(); // RFC 4226 https://datatracker.ietf.org/doc/html/rfc4226#section-5.4 const offset = signature[signature.length - 1] & 0x0f; // offset is the last byte in the hmac const fourBytesRange = signature.slice(offset, offset + 4); // starting from offset, get 4 bytes const mask = 0x7fffffff; // 32-bit number with a leading 0 followed by 31 ones [0111 (...) 1111] const masked = new BigNumber_js_1.default(fourBytesRange).toNumber() & mask; const otp = masked.toString().slice(-options.digits); return otp; } function calcHMAC(secret, timePad, algorithm) { switch (algorithm) { case 'SHA-1': return new Hash_js_1.SHA1HMAC(secret).update(timePad); case 'SHA-256': return new Hash_js_1.SHA256HMAC(secret).update(timePad); case 'SHA-512': return new Hash_js_1.SHA512HMAC(secret).update(timePad); default: throw new Error('unsupported HMAC algorithm'); } } //# sourceMappingURL=totp.js.map