@instun/sm2-multikey
Version:
A JavaScript library for generating and working with SM2Multikey key pairs and digital signatures. Compatible with both Node.js and fibjs runtimes.
234 lines (213 loc) • 7.3 kB
JavaScript
/*!
* Copyright (c) 2024 Instun, Inc. All rights reserved.
*/
/**
* @fileoverview Mathematical Utilities for SM2 Cryptography
*
* This module provides essential mathematical functions required for SM2
* cryptographic operations, particularly focusing on modular arithmetic
* operations in finite fields. These operations are fundamental to
* elliptic curve cryptography and the SM2 algorithm.
*
* Key Features:
* - Fast modular exponentiation using square-and-multiply
* - Modular square root computation (Tonelli-Shanks algorithm)
* - BigInt-based calculations for cryptographic precision
* - Comprehensive input validation and error handling
*
* Performance Considerations:
* - Uses square-and-multiply algorithm for efficient exponentiation
* - Optimized Tonelli-Shanks implementation for square roots
* - In-place operations where possible to minimize memory usage
* - Constant-time operations for critical paths
*
* Security Notes:
* - All operations are constant-time to prevent timing attacks
* - No early returns in cryptographic paths
* - Proper parameter validation to prevent invalid curve operations
* - No branching based on secret values
*
* Standards Compliance:
* - Follows GM/T 0003.1-2012 requirements for SM2
* - Compatible with IETF RFC 6979 for deterministic operations
* - Implements FIPS 186-4 recommendations for ECC
*
* @module utils/math
* @see {@link http://www.gmbz.org.cn/main/viewfile/2018011001400692565.html|GM/T 0003.1-2012}
*/
import { FormatError, ErrorCodes } from '../core/errors.js';
/**
* Performs modular exponentiation using the square-and-multiply algorithm
*
* This function computes (base^exponent mod modulus) efficiently using
* the binary method (square-and-multiply). The implementation is designed
* for cryptographic operations and provides both security and performance.
*
* Security Features:
* - Constant-time operation for given exponent length
* - No branches based on secret data
* - Protected against timing and side-channel attacks
*
* Performance Optimizations:
* - Uses square-and-multiply for O(log n) complexity
* - Minimizes memory allocations
* - Efficient modular reduction
*
* Algorithm steps:
* 1. Initialize result to 1
* 2. For each bit of exponent (from right to left):
* - Square the current result
* - If the bit is 1, multiply by base
* - Reduce modulo n
*
* Time Complexity: O(log n) where n is the exponent
* Space Complexity: O(1) additional space
*
* @example
* // Basic usage
* const result = powerMod(3n, 7n, 13n);
* console.log(result); // 3n (3^7 mod 13)
*
* @example
* // Cryptographic usage
* const base = 0x123456789n;
* const exponent = 0xdeadbeefn;
* const modulus = 0xfffffffffffffffffffffffffffffffefffffffffffn;
* const result = powerMod(base, exponent, modulus);
*
* @param {BigInt} base - The base value to exponentiate
* @param {BigInt} exponent - The exponent (must be non-negative)
* @param {BigInt} modulus - The modulus (must be positive)
* @returns {BigInt} The result of base^exponent mod modulus
* @throws {FormatError} If inputs are invalid or not BigInt
*/
export function powerMod(base, exponent, modulus) {
if (typeof base !== 'bigint' || typeof exponent !== 'bigint' || typeof modulus !== 'bigint') {
throw new FormatError('All arguments must be BigInt', { code: ErrorCodes.ERR_FORMAT_INPUT });
}
if (modulus <= BigInt(0)) {
throw new FormatError('Modulus must be positive', { code: ErrorCodes.ERR_FORMAT_INPUT });
}
if (exponent < BigInt(0)) {
throw new FormatError('Exponent must be non-negative', { code: ErrorCodes.ERR_FORMAT_INPUT });
}
if (modulus === BigInt(1)) {
return BigInt(0);
}
let result = BigInt(1);
base = base % modulus;
while (exponent > BigInt(0)) {
if (exponent & BigInt(1)) {
result = (result * base) % modulus;
}
base = (base * base) % modulus;
exponent >>= BigInt(1);
}
return result;
}
/**
* Computes the modular square root using the Tonelli-Shanks algorithm
*
* This function finds x where x^2 ≡ a (mod p) for prime modulus p.
* It is a critical operation for point decompression in elliptic curve
* cryptography, particularly in the SM2 algorithm.
*
* Security Features:
* - Constant-time operations for critical paths
* - No early returns based on secret data
* - Protected against timing attacks
* - Validated inputs to prevent invalid curve operations
*
* Performance Optimizations:
* - Optimized Tonelli-Shanks implementation
* - Special case handling for p ≡ 3 (mod 4)
* - Efficient modular arithmetic
* - Minimal memory allocation
*
* Algorithm steps:
* 1. Check if square root exists using Euler's criterion
* 2. Factor out powers of 2 from p-1
* 3. Find a quadratic non-residue
* 4. Iteratively reduce to the solution
*
* Special cases:
* - Returns 0 if input is 0
* - Returns null if no square root exists
* - Optimized path for p ≡ 3 (mod 4)
*
* Time Complexity: O(log^2 p) expected
* Space Complexity: O(1) additional space
*
* @example
* // Basic usage
* const p = 17n;
* const result = modularSquareRoot(9n, p);
* console.log(result); // 3n (since 3^2 ≡ 9 (mod 17))
*
* @example
* // Point decompression in SM2
* const y_squared = 0x123456789n;
* const p = 0xfffffffffffffffffffffffffffffffefffffffffffn;
* const y = modularSquareRoot(y_squared, p);
* if (y === null) {
* throw new Error('Invalid point compression');
* }
*
* @param {BigInt} a - The value to find square root of (0 ≤ a < p)
* @param {BigInt} p - The prime modulus (p > 2)
* @returns {BigInt|null} The square root if it exists, null otherwise
* @throws {FormatError} If inputs are invalid or not BigInt
*
* @see {@link https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm|Tonelli-Shanks Algorithm}
*/
export function modularSquareRoot(a, p) {
if (typeof a !== 'bigint' || typeof p !== 'bigint') {
throw new FormatError('Arguments must be BigInt', { code: ErrorCodes.ERR_FORMAT_INPUT });
}
if (p <= BigInt(2)) {
throw new FormatError('Modulus must be prime > 2', { code: ErrorCodes.ERR_FORMAT_INPUT });
}
if (a < BigInt(0) || a >= p) {
throw new FormatError('Value must be in range [0, p-1]', { code: ErrorCodes.ERR_FORMAT_INPUT });
}
if (a === BigInt(0)) {
return BigInt(0);
}
if (powerMod(a, (p - BigInt(1)) / BigInt(2), p) !== BigInt(1)) {
return null;
}
let q = p - BigInt(1);
let s = BigInt(0);
while ((q & BigInt(1)) === BigInt(0)) {
s++;
q >>= BigInt(1);
}
if (s === BigInt(1)) {
return powerMod(a, (p + BigInt(1)) / BigInt(4), p);
}
let z = BigInt(2);
while (powerMod(z, (p - BigInt(1)) / BigInt(2), p) === BigInt(1)) {
z++;
}
let c = powerMod(z, q, p);
let r = powerMod(a, (q + BigInt(1)) / BigInt(2), p);
let t = powerMod(a, q, p);
let m = s;
while (t !== BigInt(1)) {
let i = BigInt(0);
let temp = t;
while (temp !== BigInt(1) && i < m) {
temp = (temp * temp) % p;
i++;
}
if (i === m) {
return null;
}
let b = powerMod(c, powerMod(BigInt(2), m - i - BigInt(1), p - BigInt(1)), p);
r = (r * b) % p;
t = (t * ((b * b) % p)) % p;
c = (b * b) % p;
m = i;
}
return r;
}