UNPKG

mima-kit

Version:

mima-kit is a cryptographic suite implemented in TypeScript. The goal is to provide an easy-to-use cryptographic library. mima-kit 是一个使用 TypeScript 实现的密码学套件。目标是提供一个简单易用的密码学库。

591 lines (590 loc) 16.7 kB
// * Math Utility Functions /** * 32-bit 循环左移 / 32-bit Rotate Left * * @param {number | bigint} x - 数值 / value * @param {number | bigint} n - 位移 / shift */ export function rotateL32(x, n) { return ((x << n) | (x >>> (32 - n))) >>> 0; } /** * 32-bit 循环右移 / 32-bit Rotate Right * * @param {number | bigint} x - 数值 / value * @param {number | bigint} n - 位移 / shift */ export function rotateR32(x, n) { return (x >>> n) | ((x << (32 - n)) >>> 0); } /** * 位循环左移 / Rotate Left * * @param {number} bit - 限制位数 / limit bit * @param {number | bigint} x - 数值 / value * @param {number | bigint} n - 位移 / shift * @param {bigint} [mask] - 位掩码 / bit mask */ export function rotateL(bit, x, n, mask) { bit = BigInt(bit); mask ??= genBitMask(bit); x = BigInt(x); n = BigInt(n); x &= mask; n %= bit; x = (x << n) | (x >> (bit - n)); return x & mask; } /** * 位循环右移 / Rotate Right * * @param {number} bit - 限制位数 / limit bit * @param {number | bigint} x - 数值 / value * @param {number | bigint} n - 位移 / shift * @param {bigint} [mask] - 位掩码 / bit mask */ export function rotateR(bit, x, n, mask) { bit = BigInt(bit); mask ??= genBitMask(bit); x = BigInt(x); n = BigInt(n); x &= mask; n %= bit; x = (x >> n) | (x << (bit - n)); return x & mask; } /** * 在指定缓存区长度内生成随机大整数 / Generate random BigInt within specified buffer length * * @param {bigint} max - 最大值 (不包含) / maximum value (exclusive) * @param {number} byte - 缓存区长度 / buffer length in byte * @param {number} [max_attempts] - 最大尝试次数 / maximum attempts (default: 1000) * * @returns * - buffer: 生成的随机缓存区 / generated random buffer * - result: 生成的随机大整数 / generated random bigint */ export function genRandomBI(max, byte, max_attempts = 1000) { if (max <= 1n) throw new KitError('Max must be greater than 1'); // 创建缓存区 const buffer = new U8(byte); // 生成随机数直到小于 max let result = 0n; let attempts = 0; while (attempts < max_attempts) { crypto.getRandomValues(buffer); result = buffer.toBI(); if (result < max) return { buffer, result }; attempts++; } throw new KitError('Failed to generate random bigint within max attempts'); } /** * 获取大整数的比特长度 * * Get the bit length of a BigInt */ export function getBIBits(n) { return n === 0n ? 0 : n.toString(2).length; } /** * 生成位掩码 / Generate Bit Mask * * @param {number} w - 位数 / bit * * ```ts * const mask = genBitMask(8) // 0xFFn * ``` */ export function genBitMask(w) { w = BigInt(w); let mask = 0x0n; for (let i = 0; i < w; i++) { mask = (mask << 1n) | 1n; } return mask; } /** * 扩展欧几里得算法 * * Extended Euclidean Algorithm * * @returns * - gcd: 最大公约数 / greatest common divisor * - x: a 的贝祖系数 / Bézout coefficient of a */ export function extendedEuclidean(a, b) { let [s0, s1, t0, t1, r0, r1] = [1n, 0n, 0n, 1n, a, b]; if (b === 0n) { return { gcd: a, x: 1n, }; } while (r1 !== 0n) { const q = r0 / r1; [r0, r1] = [r1, r0 - q * r1]; [s0, s1] = [s1, s0 - q * s1]; [t0, t1] = [t1, t0 - q * t1]; } return { gcd: r0, x: s0, }; } /** * 勒让德符号 * * Legendre Symbol */ export function legendreSymbol(a, p) { return modPow(a, (p - 1n) >> 1n, p); } /** * 托内利-香克斯算法 * * Tonelli-Shanks Algorithm */ export function tonelliShanks(a, p) { if (legendreSymbol(a, p) !== 1n) { throw new KitError('There is no square root'); } if (a === 0n) { return 0n; } if (p === 2n) { return a; } if (p % 4n === 3n) { return modPow(a, (p + 1n) >> 2n, p); } let q = p - 1n; let s = 0n; while (mod(q, 2n) === 0n) { q >>= 1n; s++; } let z = 2n; while (legendreSymbol(z, p) !== p - 1n) { z++; } let m = s; let c = modPow(z, q, p); let t = modPow(a, q, p); let r = modPow(a, (q + 1n) >> 1n, p); while (t !== 0n && t !== 1n) { let t2i = t; let i = 1n; for (; i < m; i++) { t2i = modPow(t2i, 2n, p); if (t2i === 1n) { break; } } const b = modPow(c, 1n << (m - i - 1n), p); m = i; c = modPow(b, 2n, p); t = (t * c) % p; r = (r * b) % p; } return r; } /** * 最大公约数 * * Greatest Common Divisor */ export function gcd(a, b) { return extendedEuclidean(a, b).gcd; } /** * 最小公倍数 * * Least Common Multiple */ export function lcm(a, b) { return (a * b) / gcd(a, b); } /** * 求模: a mod b * * Modulo operation: a mod b * * @param {bigint} a - 被除数 / dividend * @param {bigint} b - 除数 / divisor */ export function mod(a, b) { const r = a % b; return r < 0n ? r + b : r; } /** * 模幂运算: x ^ y mod n * * Modular exponentiation: x ^ y mod n * * @param {bigint} x - base * @param {bigint} y - exponent * @param {bigint} n - modulus */ export function modPow(x, y, n) { x %= n; let r = 1n; while (y > 0n) { if (y & 1n) r = (r * x) % n; x = (x * x) % n; y >>= 1n; } return r; } /** * 模逆运算: e ≡ x ^ -1 (mod n) * * Modular inverse operation: e ≡ x ^ -1 (mod n) * * @param {bigint} x - base * @param {bigint} n - modulus */ export function modInverse(x, n) { const { gcd, x: _x } = extendedEuclidean(x, n); if (gcd !== 1n) throw new KitError('Modular inverse does not exist'); return mod(_x, n); } /** * 模素平方根运算: n ^ 0.5 (mod p) * * Modular prime square operation: n ^ 0.5 (mod p) */ export function modPrimeSquareRoot(n, p) { n = mod(n, p); if (n === 0n) return 0n; const r1 = tonelliShanks(n, p); const r2 = mod(p - r1, p); return r1 <= r2 ? r1 : r2; } // * Buffer Utility Functions /** * @extends Uint8Array */ export class U8 extends Uint8Array { /** * 从 U8 中获取一个字 / Get a word from U8 * * @param {number} word_size - 字长 / word size (byte) * @param {number} index - 字索引 / word index * @param {boolean} [little_endian] - 是否为小端序 / little-endian (default: false) */ getWord(word_size, index, little_endian = false) { const offset = index * word_size; const buffer = this.subarray(offset, offset + word_size); return little_endian ? buffer.toBI(true) : buffer.toBI(); } /** * 将一个字写入 U8 / Set a word to U8 * * @param {number} word_size - 字长 / word size (byte) * @param {number} index - 字索引 / word index * @param {bigint | Uint8Array} word - 字 / word * @param {boolean} [little_endian] - 是否为小端序 / little-endian (default: false) */ setWord(word_size, index, word, little_endian = false) { const offset = index * word_size; const buffer = typeof word === 'bigint' ? U8.fromBI(word, word_size) : word; this.set(little_endian ? buffer.toReversed() : buffer, offset); } /** * U8 视图 / U8 view * * @param {number} word_size - 字长 / word size (byte) */ view(word_size) { const length = Math.floor(this.length / word_size); const get = (index, little_endian = false) => this.getWord(word_size, index, little_endian); const set = (index, word, little_endian = false) => this.setWord(word_size, index, word, little_endian); return { get, set, length }; } /** * 将 U8 编码为字符串 / stringify U8 to encoded string */ to(codec) { return codec(this); } /** * 将 U8 转换为 BigInt / Convert U8 to BigInt * * @param {boolean} [little_endian] - 是否为小端序 / little-endian (default: false) */ toBI(little_endian = false) { const buffer = little_endian ? this.toReversed() : this; let bigint = 0n; buffer.forEach((byte) => { bigint = (bigint << 8n) | BigInt(byte); }); return bigint; } /** * Convert U8 to Uint8Array * * 将 U8 转换为 Uint8Array */ toUint8Array() { return new Uint8Array(this); } /** * Convert string to U8 * * 将 字符串 转换为 U8 */ static fromString(input, codec) { return codec(input); } /** * Convert BigInt to U8 * * 将 BigInt 转换为 U8 */ static fromBI(bigint, length, little_endian = false) { length = length || (getBIBits(bigint) + 7) >> 3; const buffer = new U8(length); if (little_endian) { for (let i = 0; i < buffer.length; i++) { buffer[i] = Number(bigint & 0xffn); bigint >>= 8n; } } else { for (let i = buffer.length - 1; i >= 0; i--) { buffer[i] = Number(bigint & 0xffn); bigint >>= 8n; } } return buffer; } /** * Returns the elements of an array that meet the condition specified in a callback function. * @param predicate A function that accepts up to three arguments. The filter method calls * the predicate function one time for each element in the array. * @param thisArg An object to which the this keyword can refer in the predicate function. * If thisArg is omitted, undefined is used as the this value. */ filter(predicate, thisArg) { const _ = super.filter(predicate, thisArg); return new U8(_.buffer, _.byteOffset, _.byteLength); } /** * Returns a new array from a set of elements. * @param items A set of elements to include in the new array object. */ static of(...items) { const _ = Uint8Array.of(...items); return new U8(_.buffer, _.byteOffset, _.byteLength); } static from(array_like, mapfn, this_arg) { const _ = Uint8Array.from(array_like, mapfn, this_arg); return new U8(_.buffer, _.byteOffset, _.byteLength); } /** * Calls a defined callback function on each element of an array, and returns an array that * contains the results. * @param callbackfn A function that accepts up to three arguments. The map method calls the * callbackfn function one time for each element in the array. * @param thisArg An object to which the this keyword can refer in the callbackfn function. * If thisArg is omitted, undefined is used as the this value. */ map(callbackfn, thisArg) { const _ = super.map(callbackfn, thisArg); return new U8(_.buffer, _.byteOffset, _.byteLength); } /** * Returns a section of an array. * @param start The beginning of the specified portion of the array. * @param end The end of the specified portion of the array. This is exclusive of the element at the index 'end'. */ slice(start, end) { const _ = super.slice(start, end); return new U8(_.buffer, _.byteOffset, _.byteLength); } /** * Gets a new Uint8Array view of the ArrayBuffer store for this array, referencing the elements * at begin, inclusive, up to end, exclusive. * @param begin The index of the beginning of the array. * @param end The index of the end of the array. */ subarray(begin, end) { const _ = super.subarray(begin, end); return new U8(_.buffer, _.byteOffset, _.byteLength); } /** * Copies the array and returns the copy with the elements in reverse order. */ toReversed() { const _ = super.toReversed(); return new U8(_.buffer, _.byteOffset, _.byteLength); } /** * Copies and sorts the array. * @param compareFn Function used to determine the order of the elements. It is expected to return * a negative value if the first argument is less than the second argument, zero if they're equal, and a positive * value otherwise. If omitted, the elements are sorted in ascending order. * ```ts * const myNums = Uint8Array.from([11, 2, 22, 1]); * myNums.toSorted((a, b) => a - b) // Uint8Array(4) [1, 2, 11, 22] * ``` */ toSorted(compareFn) { const _ = super.toSorted(compareFn); return new U8(_.buffer, _.byteOffset, _.byteLength); } /** * Copies the array and inserts the given number at the provided index. * @param index The index of the value to overwrite. If the index is * negative, then it replaces from the end of the array. * @param value The value to insert into the copied array. * @returns A copy of the original array with the inserted value. */ with(index, value) { const _ = super.with(index, value); return new U8(_.buffer, _.byteOffset, _.byteLength); } } /** * Convert TypedArray to Uint8Array * * 将 TypedArray 转换为 Uint8Array */ export function u8(source) { return new U8(source.buffer, source.byteOffset, source.byteLength); } /** * Convert TypedArray to Uint16Array * * 将 TypedArray 转换为 Uint16Array */ export function u16(source) { return new Uint16Array(source.buffer, source.byteOffset, source.byteLength >> 1); } /** * Convert TypedArray to Uint32Array * * 将 TypedArray 转换为 Uint32Array */ export function u32(source) { return new Uint32Array(source.buffer, source.byteOffset, source.byteLength >> 2); } /** * Merging multiple ArrayBuffers * * 合并多个 ArrayBuffer */ export function joinBuffer(...buffers) { const byte_total = buffers.reduce((acc, cur) => acc + cur.byteLength, 0); const result = new U8(byte_total); let offset = 0; for (const buffer of buffers) { result.set(new U8(buffer), offset); offset += buffer.byteLength; } return result; } /** * resize ArrayBuffer * * 调整 ArrayBuffer 大小 * * @param {ArrayBuffer} buffer * @param {number} size - byte */ export function resizeBuffer(buffer, size) { const b = new U8(size); b.set(buffer); return b; } const nibble_reverse_map = [ 0x0n, 0x8n, 0x4n, 0xcn, 0x2n, 0xan, 0x6n, 0xen, 0x1n, 0x9n, 0x5n, 0xdn, 0x3n, 0xbn, 0x7n, 0xfn, ]; /** * 快速翻转字节位序 / Fast Reverse Byte's Bit Order * * @param {number} byte - 字节 / byte */ export function reverseBit(byte) { byte &= 0xff; const b_h = nibble_reverse_map[byte >> 4]; const b_l = nibble_reverse_map[byte & 0xf]; return (b_l << 4n) | b_h; } export class Counter extends U8 { /** * @param {number} offset - 计数器偏移 / counter offset * @param {number} length - 计数器长度 / counter length */ inc(offset, length, little_endian = false) { // 如果不提供偏移,则默认计数器从 0 开始 offset = offset || 0; if (offset < 0 || offset >= this.length) { throw new KitError('Invalid counter offset'); } // 如果不提供长度,则默认计数器长度为剩余长度 length = length || this.length - offset; if (length < 0 || offset + length > this.length) { throw new KitError('Invalid counter length'); } if (little_endian) { for (let i = offset; i < offset + length; i++) { if (this[i] < 0xff) { this[i] += 1; break; } this[i] = 0; } } else { for (let i = offset + length - 1; i >= offset; i--) { if (this[i] < 0xff) { this[i] += 1; break; } this[i] = 0; } } } } // * Other Utility Functions export function trying(fn) { try { const result = fn(); return [null, result]; } catch (error) { return error instanceof Error ? [error, null] : [new KitError('Unknown error'), null]; } } export function wrap(...args) { // @ts-expect-error Object assign return Object.assign(...args); } export class KitError extends Error { constructor(message) { super(message); this.name = 'mima-kit Error'; } }