UNPKG

@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.

463 lines (442 loc) 13.1 kB
/** * @fileoverview Browser-specific SM2/SM3 Cryptographic Operations * * This module implements SM2 and SM3 cryptographic operations for browser * environments using the sm-crypto library. It provides a pure JavaScript * implementation that works across all modern browsers. * * Key Features: * - Pure JavaScript * - Cross-browser * - Zero dependencies * - Type preservation * - Format conversion * * Security Considerations: * - Browser RNG usage * - Parameter validation * - Format checking * - Memory safety * - Error handling * * Performance Notes: * - Optimized JS code * - Minimal copying * - Early validation * - Buffer reuse * - Type preservation * * Implementation Details: * ``` * Key Generation: * 1. Browser RNG * 2. Point generation * 3. Format conversion * * Signing: * 1. SM3 hashing * 2. SM2 signing * 3. Format conversion * * Verification: * 1. Parameter check * 2. SM3 hashing * 3. SM2 verification * ``` * * Standards Compliance: * - GB/T 32918.1-2016: SM2 Key * - GB/T 32918.2-2016: SM2 Sign * - GB/T 32905-2016: SM3 Hash * - RFC 5480: EC Public Key * - RFC 5915: EC Private Key * * Usage Example: * ```javascript * import crypto from './crypto/browser.js'; * * // Generate key pair * const { publicKey, secretKey } = crypto.generateKey(); * * // Create signer and verifier * const sign = crypto.createSigner({ publicKey, secretKey }); * const verify = crypto.createVerifier({ publicKey }); * * // Sign and verify * const message = new Uint8Array([1, 2, 3, 4]); * const signature = sign({ data: message }); * const isValid = verify({ data: message, signature }); * ``` * * @module crypto/browser * @see {@link https://github.com/JuneAndGreen/sm-crypto|sm-crypto} * @see {@link http://www.gmbz.org.cn/main/viewfile/20180108023812835219.html|GB/T 32918} */ import smCrypto from 'sm-crypto'; const { sm2, sm3 } = smCrypto; import { ArgumentError, FormatError, ErrorCodes } from '../core/errors.js'; import { isValidBinaryData, toBuffer, matchBinaryType } from '../utils/binary.js'; /** * Convert hexadecimal string to byte array * * This function converts a hexadecimal string to an array of bytes, * handling odd-length strings by padding with a leading zero. The * function is optimized for performance in browser environments. * * Processing Steps: * 1. Length check * 2. Padding if needed * 3. Byte conversion * 4. Array building * * Format Details: * ``` * Input: "1a2b3c" * Step 1: Check length (6 chars, even) * Step 2: Split into pairs ["1a","2b","3c"] * Step 3: Convert to bytes [26,43,60] * * Input: "a2b3c" * Step 1: Check length (5 chars, odd) * Step 2: Pad to "0a2b3c" * Step 3: Split into pairs ["0a","2b","3c"] * Step 4: Convert to bytes [10,43,60] * ``` * * @private * @param {string} hexStr - Hex string to convert * @returns {number[]} Array of byte values * @throws {Error} If input is not a valid hex string */ function hexToArray(hexStr) { const words = [] let hexStrLength = hexStr.length if (hexStrLength % 2 !== 0) { hexStr = leftPad(hexStr, hexStrLength + 1) } hexStrLength = hexStr.length for (let i = 0; i < hexStrLength; i += 2) { words.push(parseInt(hexStr.substr(i, 2), 16)) } return words } export default { /** * Generate a new SM2 key pair * * This function generates a new SM2 key pair using the browser's * cryptographic random number generator via sm-crypto. The keys * are returned in standard formats for compatibility. * * Processing Steps: * 1. Generate random * 2. Create point * 3. Format keys * 4. Validate output * 5. Return pair * * Security Considerations: * - Browser RNG use * - Parameter check * - Format validation * - Memory safety * - Error handling * * Performance Notes: * - Single allocation * - Early validation * - Buffer reuse * - Type matching * - Format caching * * Key Format Details: * ``` * Public Key (65 bytes): * | Prefix | X-coordinate | Y-coordinate | * | 0x04 | 32 bytes | 32 bytes | * * Private Key (32 bytes): * | D-value | * | 32 bytes | * ``` * * @returns {{publicKey: Buffer, secretKey: Buffer}} Generated key pair * @throws {Error} If key generation fails * * @example * ```javascript * // Generate a new key pair * const { publicKey, secretKey } = generateKey(); * * // Verify key formats * console.log(publicKey[0] === 0x04); // true (uncompressed) * console.log(publicKey.length); // 65 bytes * console.log(secretKey.length); // 32 bytes * * // Use the keys * const signer = createSigner({ publicKey, secretKey }); * ``` */ generateKey: function () { const keyPairHex = sm2.generateKeyPairHex(); return { publicKey: Buffer.from(keyPairHex.publicKey.slice(2), 'hex'), secretKey: Buffer.from(keyPairHex.privateKey, 'hex') } }, /** * Create an SM2 signature function * * This function creates a signing function that uses sm-crypto's SM2 * implementation. It handles all necessary format conversions and * provides a simple interface for signing messages. * * Processing Steps: * 1. Validate inputs * 2. Convert formats * 3. Hash message * 4. Create signature * 5. Format output * * Security Considerations: * - Key validation * - Format checking * - Memory safety * - Error handling * - Type verification * * Performance Notes: * - Format caching * - Early validation * - Buffer reuse * - Type matching * - Memory efficiency * * Format Details: * ``` * Input Keys: * | Public (65) | Private (32) | * |--------------|--------------| * | 0x04 + X + Y | D value | * * Input Message: * | Arbitrary length data | * * Output Signature: * | R value | S value | * | 32 bytes| 32 bytes| * ``` * * @param {{publicKey: Buffer|Uint8Array, secretKey: Buffer|Uint8Array}} key - Key pair * @returns {Function} Signing function * @throws {ArgumentError} If key format is invalid * * @example * ```javascript * // Create a signer * const sign = createSigner({ * publicKey: Buffer.alloc(65), // 0x04 + X + Y * secretKey: Buffer.alloc(32) // D value * }); * * // Sign different message types * const buf = Buffer.from('test'); * const sig1 = sign({ data: buf }); * * const uint8 = new Uint8Array([1, 2, 3]); * const sig2 = sign({ data: uint8 }); * console.log(sig2 instanceof Uint8Array); // true * ``` */ createSigner: function ({ publicKey, secretKey }) { // Convert keys to Buffer for hex conversion const pubKeyBuf = toBuffer(publicKey); const secKeyBuf = toBuffer(secretKey); const privateKeyHex = secKeyBuf.toString('hex'); const publicKeyHex = '04' + pubKeyBuf.toString('hex'); // Add '04' prefix for uncompressed point format // Returns a function that generates a signature return ({ data }) => { if (!isValidBinaryData(data)) { throw new ArgumentError('data must be Buffer or Uint8Array', { code: ErrorCodes.ERR_ARGUMENT_INVALID }); } const msgBuf = toBuffer(data); const signHex = sm2.doSignature( hexToArray(sm3(msgBuf)), privateKeyHex, { publicKey: publicKeyHex }); // Return same type as input message return matchBinaryType(data, Buffer.from(signHex, 'hex')); }; }, /** * Create an SM2 signature verification function * * This function creates a verification function that uses sm-crypto's * SM2 implementation to verify signatures. It handles all format * conversions and provides comprehensive validation. * * Processing Steps: * 1. Validate inputs * 2. Convert formats * 3. Hash message * 4. Verify signature * 5. Return result * * Security Considerations: * - Key validation * - Format checking * - Memory safety * - Error handling * - Type verification * * Performance Notes: * - Format caching * - Early validation * - Buffer reuse * - Type matching * - Memory efficiency * * Format Details: * ``` * Public Key (65 bytes): * | Prefix | X-coordinate | Y-coordinate | * | 0x04 | 32 bytes | 32 bytes | * * Input Message: * | Arbitrary length data | * * Input Signature: * | R-value | S-value | * | 32 bytes | 32 bytes | * ``` * * @param {{publicKey: Buffer|Uint8Array}} key - Public key for verification * @returns {Function} Verification function * @throws {ArgumentError} If key format is invalid * * @example * ```javascript * // Create a verifier * const verify = createVerifier({ * publicKey: Buffer.alloc(65) // 0x04 + X + Y * }); * * // Verify different types * const message = Buffer.from('test'); * const signature = Buffer.alloc(64); // R + S values * const isValid = verify({ data: message, signature }); * * // Error handling * try { * verify({ * data: new Uint8Array([1, 2, 3]), * signature: Buffer.alloc(64) * }); * } catch (err) { * console.error('Verification failed:', err); * } * ``` */ createVerifier: function ({ publicKey }) { // Convert public key to Buffer for hex conversion const pubKeyBuf = toBuffer(publicKey); const publicKeyHex = '04' + pubKeyBuf.toString('hex'); // Add '04' prefix for uncompressed point format // Return verification function return ({ data, signature }) => { if (!isValidBinaryData(data)) { throw new ArgumentError('data must be Buffer or Uint8Array', { code: ErrorCodes.ERR_ARGUMENT_INVALID }); } if (!isValidBinaryData(signature)) { throw new ArgumentError('signature must be Buffer or Uint8Array', { code: ErrorCodes.ERR_ARGUMENT_INVALID }); } const msgBuf = toBuffer(data); const sigBuf = toBuffer(signature); return sm2.doVerifySignature( hexToArray(sm3(msgBuf)), sigBuf.toString('hex'), publicKeyHex ); }; }, /** * Compute SM3 cryptographic hash * * This function computes the SM3 cryptographic hash of input data * using sm-crypto's implementation. SM3 is a cryptographic hash * function that produces a 256-bit (32-byte) hash value. * * Processing Steps: * 1. Validate input * 2. Convert format * 3. Compute hash * 4. Format output * 5. Match type * * Security Considerations: * - Input validation * - Memory safety * - Buffer bounds * - Error handling * - Type checking * * Performance Notes: * - Optimized hash * - Early validation * - Buffer reuse * - Type matching * - Memory efficiency * * Hash Details: * ``` * Algorithm: SM3 (GB/T 32905-2016) * Input: Arbitrary length * Output: 32 bytes (256 bits) * Block size: 64 bytes (512 bits) * Word size: 32 bits * Rounds: 64 * ``` * * @param {Buffer|Uint8Array} data - Data to hash * @returns {Buffer|Uint8Array} 32-byte hash value * @throws {ArgumentError} If input format is invalid * * @example * ```javascript * // Hash with Buffer * const buf = Buffer.from('test'); * const hash1 = digest(buf); * console.log(hash1.length); // 32 bytes * * // Hash with Uint8Array * const uint8 = new Uint8Array([1, 2, 3]); * const hash2 = digest(uint8); * console.log(hash2 instanceof Uint8Array); // true * * // Error handling * try { * digest('invalid'); // Throws ArgumentError * } catch (err) { * console.error('Invalid input type'); * } * ``` */ digest: function (data) { if (!isValidBinaryData(data)) { throw new ArgumentError('data must be Buffer or Uint8Array', { code: ErrorCodes.ERR_ARGUMENT_INVALID }); } const dataBuf = toBuffer(data); const hashHex = sm3(dataBuf); // Return same type as input return matchBinaryType(data, Buffer.from(hashHex, 'hex')); } };