@bsv/sdk
Version:
BSV Blockchain Software Development Kit
92 lines • 3.75 kB
JavaScript
;
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