UNPKG

btc-minikeytools

Version:

BTC-MiniKeyTools is a robust tool for working with Bitcoin Mini private keys. It provides validation, key generation, and conversion functionalities, including HEX and Wallet Import Format (WIF) keys. Fully equipped with CLI support and logging options, i

235 lines (216 loc) 7.47 kB
'use strict'; require('../../core/setupAliases'); const logger = require('@core/logger'); const crypto = require('crypto'); const { sha256 } = require('@utils/hashUtils'); const { encodeBase58 } = require('@utils/base58'); const config = require('@config/config.json'); /** * Class for managing and converting Bitcoin MiniKeys. * * This class provides utilities for: * - Generating valid 30-character Bitcoin MiniKeys. * - Validating MiniKeys to ensure they conform to the correct format. * - Converting MiniKeys to private keys in HEX format. * - Converting private keys in HEX format to Wallet Import Format (WIF). * * **Important:** MiniKeys cannot directly generate WIF keys. They must first be converted * to HEX format using the `miniToHex` method, and only then can they be converted to WIF using `hexToWif`. * * @example * const converter = new MiniKeyConverter(); * * // Generate a new MiniKey * const newMiniKey = converter.generateMiniKey(); * console.log(`Generated MiniKey: ${newMiniKey}`); * * // Validate a MiniKey * const isValid = converter.check('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy'); * if (isValid) { * console.log('Valid MiniKey!'); * } * * // Convert MiniKey to HEX * const hexKey = converter.miniToHex('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy'); * console.log(`HEX Key: ${hexKey}`); * * // Convert HEX to WIF * const wifKey = converter.hexToWif(hexKey, false, false); * console.log(`WIF Key: ${wifKey}`); */ class MiniKeyConverter { /** * Generates a valid 30-character MiniKey. * * The MiniKey will always start with 'S' and will contain 29 additional Base58 characters. * It ensures the generated MiniKey passes validation checks before returning it. * * @returns {string} A valid 30-character MiniKey. * @example * const newMiniKey = converter.generateMiniKey(); * console.log(`Generated MiniKey: ${newMiniKey}`); */ generateMiniKey() { let minikey; do { minikey = 'S' + crypto.randomBytes(32).toString('base64') .replace(/[^1-9A-HJ-NP-Za-km-z]/g, '') .slice(0, 29); } while (!this.check(minikey, true)); return minikey; } /** * Validates a MiniKey to ensure it conforms to the correct format. * * A valid MiniKey: * - Starts with 'S'. * - Contains only Base58 characters. * - Is either 22 or 30 characters long (or only 30 if strictMode is enabled). * - Has a SHA-256 hash (appended with '?') that starts with 0x00. * * @param {string} miniKey - The MiniKey to validate. * @param {boolean} [strictMode=false] - Enforce strict validation, allowing only 30-character MiniKeys. * @returns {boolean} True if the MiniKey is valid, false otherwise. * @example * const isValid = converter.check('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy'); * console.log(`Is valid: ${isValid}`); */ check(miniKey, strictMode = false) { logger.debug({ method: 'check', input: miniKey, strictMode, message: `Validating MiniKey` }); if (!miniKey || typeof miniKey !== 'string') { logger.warn({ method: 'check', message: 'Invalid MiniKey: Must be a non-empty string.' }); return false; } const validLengths = strictMode ? [30] : [22, 30]; if (!validLengths.includes(miniKey.length)) { logger.warn({ method: 'check', message: `Invalid MiniKey length: ${miniKey.length}. Allowed lengths: ${validLengths.join(', ')}.` }); return false; } if (!/^[S][1-9A-HJ-NP-Za-km-z]+$/.test(miniKey)) { logger.warn({ method: 'check', message: 'Invalid MiniKey format: Must start with "S" and contain only Base58 characters.' }); return false; } const isValid = /^00[0-9A-F]{62}$/.test(sha256(`${miniKey}?`).toString('hex').toUpperCase()); logger.debug({ method: 'check', input: miniKey, result: isValid, message: `MiniKey validation result` }); return isValid; } /** * Converts a MiniKey into its corresponding private key in HEX format. * * @param {string} key - The MiniKey to convert. * @returns {string} The private key in HEX format. * @example * const hexKey = converter.miniToHex('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy'); * console.log(`HEX Key: ${hexKey}`); */ miniToHex(key) { logger.debug({ method: 'miniToHex', input: key, message: `Converting MiniKey to HEX` }); const hexKey = sha256(key).toString('hex').toUpperCase(); logger.debug({ method: 'miniToHex', result: hexKey, message: `Converted HEX private key` }); return hexKey; } /** * Converts a private key in HEX format to Wallet Import Format (WIF). * * **Note:** The input key must be in HEX format, typically obtained by converting a MiniKey * using the `miniToHex` method. * * @param {string} key - The private key in HEX format (64 characters). * @param {boolean} [testnet=false] - Whether the key is for the Bitcoin testnet. * @param {boolean} [compressed=false] - Whether the key is compressed. * @returns {string} The private key in Wallet Import Format (WIF). * @example * const wifKey = converter.hexToWif(hexKey, false, false); * console.log(`WIF Key: ${wifKey}`); */ hexToWif(key, testnet = false, compressed = false) { logger.debug({ method: 'hexToWif', input: key, testnet, compressed, message: `Converting HEX to WIF` }); if (!/^[0-9A-Fa-f]{64}$/.test(key)) { logger.error({ method: 'hexToWif', message: `Invalid HEX private key format: ${key}` }); throw new Error('Invalid hexadecimal key format'); } try { const prefix = testnet ? config.networks.testnet : config.networks.mainnet; let extendedKey = Buffer.from(`${prefix}${key}`, 'hex'); if (compressed) { extendedKey = Buffer.concat([extendedKey, Buffer.from('01', 'hex')]); } const hash1 = sha256(extendedKey); const hash2 = sha256(hash1); const checksum = hash2.slice(0, 4); const fullKey = Buffer.concat([extendedKey, checksum]); const wif = encodeBase58(fullKey); logger.debug({ method: 'hexToWif', result: wif, message: `Successfully converted to WIF` }); return wif; } catch (error) { logger.error({ method: 'hexToWif', message: `Error converting to WIF: ${error.message}` }); throw error; } } /** * Verifies whether a WIF key matches a MiniKey. * * @param {string} miniKey - The Mini private key. * @param {string} wifKey - The WIF key to verify. * @param {boolean} [testnet=false] - Whether the WIF key is for testnet. * @param {boolean} [compressed=false] - Whether the WIF key is compressed. * @returns {boolean} - True if the WIF key matches the MiniKey, otherwise false. */ verifyWifAgainstMiniKey(miniKey, wifKey, testnet = false, compressed = false) { try { // Convert MiniKey to HEX const hexKey = this.miniToHex(miniKey); // Generate the expected WIF key const expectedWifKey = this.hexToWif(hexKey, testnet, compressed); // Compare the expected WIF key with the provided one return expectedWifKey === wifKey; } catch (error) { logger.error(`Verification failed: ${error.message}`); return false; } } } module.exports = MiniKeyConverter;