diff --git a/index.js b/index.js index 5f637ab..ce30832 100644 --- a/index.js +++ b/index.js @@ -29,14 +29,38 @@ my.INPUT_LIST = ['utf8', 'ascii', 'latin1'] // ignored for Buffer/TypedArray/Dat my.COIN = 'TIC' // 默认的币种 my.COIN_LIST = ['TIC', 'BTC', 'ETH'] -module.exports = { - isHashable(data, { strict = false } = {}) { +/** + * + * + * @class TICrypto + */ +class TICrypto { + /** + * 测试输入数据是否可哈希混淆 + * + * @static + * @param {*} data 需要被哈希混淆的数据 + * @param {*} option 可选参数 + * @return {Boolean} + * @memberof TICrypto + */ + static isHashable(data, { strict = false } = {}) { if (strict) { - return data && typeof data !== 'boolean' && data !== Infinity // 允许大多数数据,除了空值、布尔值、无限数 + return data ? true : false && typeof data !== 'boolean' && data !== Infinity // 允许大多数数据,除了null、''、0、布尔值、无限数。注意 data 要放在最后,否则会被 return 直接返回,而不是返回 Boolean } return typeof data !== 'undefined' // 允许一切数据,除非 undefined - }, - isHash(hash, { hasher = my.HASHER } = {}) { + } + + /** + * 测试是否有效的哈希值 + * + * @static + * @param {String} hash + * @param {Object} option [{ hasher = my.HASHER }={}] + * @return {Boolean} + * @memberof TICrypto + */ + static isHash(hash, { hasher = my.HASHER } = {}) { if (my.HASHER_LIST.indexOf(hasher) >= 0) { switch (hasher) { case 'sha256': @@ -51,8 +75,18 @@ module.exports = { } } return false - }, - isSecword(secword, { mode = 'strict' } = {}) { + } + + /** + * 测试是否合法的助记词 + * + * @static + * @param {String} secword + * @param {Object} option [{ mode = 'strict' }={}] + * @return {Boolean} + * @memberof TICrypto + */ + static isSecword(secword, { mode = 'strict' } = {}) { // 注意 not all 12 words combinations are valid for both bitcore and bip39, because there are checksum in mnemonic. 另外,实际上bitcore和bip39对12, 15, 18, ... 长度的合法助记词都返回 true。 //// for bitcore-mnemonic. 注意,bitcore-mnemonic 对少于12词的会抛出异常,很蠢。 // if (typeof secword==='string' && 12===secword.split(/ +/).length) @@ -69,22 +103,59 @@ module.exports = { } } return false - }, - isSeckey(seckey) { + } + + /** + * 测试是否合法的私钥 + * + * @static + * @param {String} seckey + * @return {Boolean} + * @memberof TICrypto + */ + static isSeckey(seckey) { // 比特币、以太坊的私钥:64 hex // nacl.sign 的私钥 128 hex, nacl.box 的私钥 64 hex return /^([a-fA-F0-9]{128}|[a-fA-F0-9]{64})$/.test(seckey) - }, - isPubkey(pubkey) { + } + + /** + * 测试是否合法的公钥 + * + * @static + * @param {String} pubkey + * @return {Boolean} + * @memberof TICrypto + */ + static isPubkey(pubkey) { // 比特币的公钥:压缩型 '02|03' + 64 hex 或 无压缩型 '04' + 128 hex // 以太坊的公钥:'02|03' + 64 hex // nacl.sign 的公钥:64 hex return /^((02|03)?[a-fA-F0-9]{64}|04[a-fA-F0-9]{128})$/.test(pubkey) // "d2f186a630f5558ba3ede10a4dd0549da5854eab3ed28ee8534350c2535d38b0" - }, - isSignature(signature) { + } + + /** + * 测试是否合法的签名 + * + * @static + * @param {String} signature + * @return {Boolean} + * @memberof TICrypto + */ + static isSignature(signature) { return /^[a-fA-F0-9]{128,144}$/.test(signature) && signature.length % 2 === 0 // 128 for nacl, 140/142/144 for crypto and eccrypto in der format. - }, - hash(data, { hasher = my.HASHER, salt, input = my.INPUT, output = my.OUTPUT } = {}) { + } + + /** + * 哈希混淆 + * + * @static + * @param {*} data + * @param {option} [{ hasher = my.HASHER, salt, input = my.INPUT, output = my.OUTPUT }={}] + * @return {String} + * @memberof TICrypto + */ + static hash(data, { hasher = my.HASHER, salt, input = my.INPUT, output = my.OUTPUT } = {}) { // data can be anything, but converts to string or remains be Buffer/TypedArray/DataView if (this.isHashable(data)) { if (typeof data !== 'string' && !(data instanceof Buffer) && !(data instanceof DataView)) data = JSON.stringify(data) @@ -94,8 +165,18 @@ module.exports = { return crypto.createHash(hasher).update(data, inputEncoding).digest(outputEncoding) } return null - }, - async encrypt(data, { tool, keytype, key, input, output, cipher } = {}) { + } + + /** + * 加密 + * + * @static + * @param {*} data + * @param {*} option [{ tool, keytype, key, input, output, cipher }={}] + * @return {String} + * @memberof TICrypto + */ + static async encrypt(data, { tool, keytype, key, input, output, cipher } = {}) { if (keytype === 'pwd') { if (typeof key === 'string') { let inputEncoding = my.INPUT_LIST.indexOf(input) >= 0 ? input : my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView @@ -118,8 +199,18 @@ module.exports = { return crypto.publicEncrypt(pubkeyPEM, Buffer.from(data)) } return null - }, - async decrypt(data, { keytype, key, input, output, cipher, format } = {}) { + } + + /** + * 解密 + * + * @static + * @param {*} data + * @param {Object} option [{ keytype, key, input, output, cipher, format }={}] + * @return {String} + * @memberof TICrypto + */ + static async decrypt(data, { keytype, key, input, output, cipher, format } = {}) { // data 应当是 encrypt 输出的数据类型 if (keytype === 'pwd') { if (data && (typeof data === 'string' || data instanceof Buffer) && typeof key === 'string') { @@ -149,8 +240,19 @@ module.exports = { } } return null - }, - async sign(data, seckey, option = {}) { + } + + /** + * 签名 + * + * @static + * @param {*} data + * @param {String} seckey + * @param {Object} option [option={}] + * @return {String} + * @memberof TICrypto + */ + static async sign(data, seckey, option = {}) { // data can be string or buffer or object, results are the same if (this.isHashable(data) && this.isSeckey(seckey)) { if (option.tool === 'nacl' && seckey.length === 128) { @@ -174,8 +276,20 @@ module.exports = { } } return null - }, - async verify(data, signature, pubkey, option = {}) { + } + + /** + * 验证签名 + * + * @static + * @param {*} data + * @param {String} signature + * @param {String} pubkey + * @param {Object} option [option={}] + * @return {Boolean} + * @memberof TICrypto + */ + static async verify(data, signature, pubkey, option = {}) { // data could be anything, but converts to string or remains be Buffer/TypedArray/DataView if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey)) { if ('nacl' === option.tool && signature.length === 128) { @@ -205,8 +319,18 @@ module.exports = { } } return false - }, - pass2keypair(pass, option) { + } + + /** + * 从密码到公私钥 + * + * @static + * @param {String} pass + * @param {Object} option + * @return {Object} {pubkey, seckey, address,} + * @memberof TICrypto + */ + static pass2keypair(pass, option) { // 如果使用其他机制,例如密码、随机数,不使用secword,也可生成keypair if (this.isHashable(pass)) { option = option || {} @@ -220,16 +344,44 @@ module.exports = { } } return null - }, - entropy2secword(entropy) { + } + + /** + * 从墒到助记词 + * + * @static + * @param {*} entropy + * @return {String} + * @memberof TICrypto + */ + static entropy2secword(entropy) { // entropy could be hex string or buffer. Byte length could be of 16, 20, 24, 28, ... which outputs mnemonic of length 12, 15, 18, 21, ... return bip39.entropyToMnemonic(entropy) // results are the same for the same entropy. - }, - secword2entropy(secword) { + } + + /** + * 从助记词到墒 + * + * @static + * @param {String} secword + * @return {*} + * @memberof TICrypto + */ + static secword2entropy(secword) { // secword could be of length 12, 15, 18, ... which outputs hex of length 32, 40, ... return bip39.mnemonicToEntropy(secword) // results are the same for the same secword. - }, - secword2keypair(secword, option) { + } + + /** + * 从助记词到公私钥 + * + * @static + * @param {String} secword + * @param {Object} option + * @return {Object} {pubkey, seckey,} + * @memberof TICrypto + */ + static secword2keypair(secword, option) { // option.coin 币种; // option.passphase 密码,默认为空; // option.path==='master' 生成 HD master key,不定义则默认为相应币种的第一对公私钥。 @@ -242,6 +394,7 @@ module.exports = { // Account 最大为 0x7FFFFFFF, Change/Index 最大均为 0xFFFFFFFF(=4294967295) // 但可以不断延伸下去:/xxx/xxx/xxx/xxx/... option = option || {} + if (option.coin) option.coin = option.coin.toUpperCase() option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN if (option.tool === 'nacl') { @@ -287,8 +440,18 @@ module.exports = { } } return null - }, - seed2path(seed, { coin = 'TIC' } = { coin: 'TIC' }) { + } + + /** + * 从种子到路径 + * + * @static + * @param {*} seed + * @param {string} option [{ coin = 'TIC' }={ coin: 'TIC' }] + * @return {String} path + * @memberof TICrypto + */ + static seed2path(seed, { coin = 'TIC' } = { coin: 'TIC' }) { // 路径规范 BIP44: m/Purpose'/Coin'/Account'/Change/Index, // 但实际上 Purpose, Coin 都可任意定;' 可有可无; // Account/Change/Index 最大到 parseInt(0x7FFFFFFF, 16) @@ -300,6 +463,7 @@ module.exports = { let part3 = parseInt(hash.slice(18, 24), 16) let part4 = parseInt(hash.slice(24, 30), 16) let path = `${part0}'/${part1}/${part2}/${part3}/${part4}/${part5}` + if (coin) coin = coin.toUpperCase() switch (coin) { case 'BTC': return `m/44'/0'/${path}` @@ -309,10 +473,21 @@ module.exports = { default: return `m/44'/60000'/${path}` } - }, - secword2account(secword, option) { + } + + /** + * 从助记词到账户 + * + * @static + * @param {String} secword + * @param {Object} option + * @return {Object} + * @memberof TICrypto + */ + static secword2account(secword, option) { // account 比 keypair 多了 address 字段。 option = option || {} + if (option.coin) option.coin = option.coin.toUpperCase() option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN let kp = this.secword2keypair(secword, option) if (kp) { @@ -325,9 +500,20 @@ module.exports = { return kp } return null - }, - secword2address(secword, option) { + } + + /** + * 从助记词到地址 + * + * @static + * @param {String} secword + * @param {Object} option + * @return {String} address + * @memberof TICrypto + */ + static secword2address(secword, option) { option = option || {} + if (option.coin) option.coin = option.coin.toUpperCase() option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN let kp = this.secword2keypair(secword, option) if (kp) { @@ -340,8 +526,19 @@ module.exports = { return address } return null - }, - seckey2pubkey(seckey, option = {}) { + } + + /** + * 从私钥到公钥 + * + * @static + * @param {*} seckey + * @param {*} [option={}] + * @return {*} + * @memberof TICrypto + */ + static seckey2pubkey(seckey, option = {}) { + if (option.coin) option.coin = option.coin.toUpperCase() option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN if (this.isSeckey(seckey) && seckey.length === 64) { // 只能用于32字节的私钥(BTC, ETH)。也就是不能用于 TIC 的私钥。 @@ -363,11 +560,23 @@ module.exports = { return Buffer.from(keypair.publicKey).toString('hex') // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 } return null - }, - seckey2address(seckey, option) { + } + + /** + * 从私钥到地址 + * + * @static + * @param {*} seckey + * @param {*} option + * @return {*} + * @memberof TICrypto + */ + static seckey2address(seckey, option) { option = option || {} + if (option.coin) option.coin = option.coin.toUpperCase() option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN if (this.isSeckey(seckey)) { + /** @type {*} */ let pubkey if (option.coin === 'ETH') { pubkey = this.seckey2pubkey(seckey, { compress: false }) @@ -378,9 +587,20 @@ module.exports = { } } return null - }, - pubkey2position(pubkey, { coin } = {}) { + } + + /** + * 从公钥到位置 + * + * @static + * @param {*} pubkey + * @param {*} [{ coin }={}] + * @return {*} + * @memberof TICrypto + */ + static pubkey2position(pubkey, { coin } = {}) { // tic, btc, eth 的 position 都是 20节=40字符的。 + if (coin) coin = coin.toUpperCase() coin = my.COIN_LIST.indexOf(coin) >= 0 ? coin : my.COIN if (this.isPubkey(pubkey)) { if (coin === 'ETH') { @@ -399,9 +619,20 @@ module.exports = { } } return null - }, - position2address(position, { coin, world } = {}) { + } + + /** + * 从位置到地址 + * + * @static + * @param {*} position + * @param {*} [{ coin, world }={}] + * @return {*} + * @memberof TICrypto + */ + static position2address(position, { coin, world } = {}) { if (!/^[\da-fA-F]{40}$/.test(position)) return null // 不论 tic, btc, eth,其 position 都是 40字符的。 + if (coin) coin = coin.toUpperCase() coin = my.COIN_LIST.indexOf(coin) >= 0 ? coin : my.COIN let address if (coin === 'ETH') { @@ -468,8 +699,16 @@ module.exports = { return address } return null - }, - address2position() { + } + + /** + * 从地址到位置 + * + * @static + * @return {*} + * @memberof TICrypto + */ + static address2position() { if (/^0x[\da-fA-F]{40}$/.test(address)) { return address.toLowerCase() } else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address)) { @@ -486,8 +725,17 @@ module.exports = { } } return null - }, - isAddress(address) { + } + + /** + * 测试是否合法的地址 + * + * @static + * @param {String} address + * @return {Boolean} + * @memberof TICrypto + */ + static isAddress(address) { if (/^(0x)?[\da-fA-F]{40}$/.test(address)) { return 'ETH' } else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address) && address.length !== 32) { @@ -506,17 +754,47 @@ module.exports = { return 'TIC' } return null - }, - pubkey2address(pubkey, option = {}) { + } + + /** + * 从公钥到地址 + * + * @static + * @param {*} pubkey + * @param {*} [option={}] + * @return {*} + * @memberof TICrypto + */ + static pubkey2address(pubkey, option = {}) { // pubkey 应当是string类型 + if (option.coin) option.coin = option.coin.toUpperCase() option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN return this.position2address(this.pubkey2position(pubkey, option), option) - }, - secword2seed(secword, pass) { + } + + /** + * 从助记词到种子 + * + * @static + * @param {*} secword + * @param {*} pass + * @return {*} + * @memberof TICrypto + */ + static secword2seed(secword, pass) { // 遵循bip39的算法。和 ether.HDNode.mnemonic2Seed 结果一样,是64字节的种子。其实 return bip39.mnemonicToSeedSync(secword, pass).toString('hex') // 结果一致于 new BitcoreMnemonic(secword).toSeed(pass).toString('hex') 或 ethers.HDNode.mnemonic2Seed(secword)。其实,bip39.mnemonicToSeedSync 也接受不合法的 secword,只要是个string就行。 - }, - randomSecword(lang = 'english') { + } + + /** + * 生成随机的助记词 + * + * @static + * @param {string} [lang='english'] + * @return {*} + * @memberof TICrypto + */ + static randomSecword(lang = 'english') { // accepts case-insensitive lang, such as 'chinese, cn, tw, en' //// for BitcoreMnemonic // lang=lang.toUpperCase() @@ -550,18 +828,37 @@ module.exports = { } bip39.setDefaultWordlist(language) return bip39.generateMnemonic() - }, - randomSeckey(option) { + } + + /** + * 生成随机的私钥 + * + * @static + * @param {*} [option={}] + * @return {*} + * @memberof TICrypto + */ + static randomSeckey(option = {}) { // 跳过 secword 直接产生随机密钥 - option = option || {} + if (option.coin) option.coin = option.coin.toUpperCase() option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN if (option.tool === 'nacl') { return crypto.randomBytes(64).toString('hex') // Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节 } else { return crypto.randomBytes(32).toString('hex') // Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节 } - }, - randomKeypair(option = {}) { + } + + /** + * 生成随机的公私钥 + * + * @static + * @param {*} [option={}] + * @return {*} + * @memberof TICrypto + */ + static randomKeypair(option = {}) { + if (option.coin) option.coin = option.coin.toUpperCase() option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN let kp if (option.tool === 'nacl') { @@ -582,12 +879,31 @@ module.exports = { pubkey, } } - }, - randomAccount(option = {}) { + } + + /** + * 生成随机的账户 + * + * @static + * @param {*} [option={}] + * @return {*} + * @memberof TICrypto + */ + static randomAccount(option = {}) { let secword = this.randomSecword(option.lang) return this.secword2account(secword, option) - }, - randomString(length = 6, alphabet) { + } + + /** + * 生成随机的字符串 + * + * @static + * @param {number} [length=6] + * @param {*} alphabet + * @return {*} + * @memberof TICrypto + */ + static randomString(length = 6, alphabet) { // 长度为 length,字母表为 alphabet 的随机字符串 alphabet = alphabet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%^&*@' var text = '' @@ -595,8 +911,17 @@ module.exports = { text += alphabet.charAt(Math.floor(Math.random() * alphabet.length)) } return text - }, - randomNumber({ length, min, max } = {}) { + } + + /** + * 生成随机的数字 + * + * @static + * @param {*} [{ length, min, max }={}] + * @return {*} + * @memberof TICrypto + */ + static randomNumber({ length, min, max } = {}) { // 长度为 length 的随机数字,或者 (min||0) <= num < max var num = 0 if (typeof length === 'number' && length > 0) { @@ -610,20 +935,47 @@ module.exports = { num = Math.random() } return num - }, - padStart(string, targetLength, symbol) { + } + + /** + * 向前补足 + * + * @static + * @param {*} string + * @param {*} targetLength + * @param {*} symbol + * @return {*} + * @memberof TICrypto + */ + static padStart(string, targetLength, symbol) { // 2020-03: 发现在浏览器里,还不支持 string.padStart(),只好自己写个暂代。 let padLength = targetLength - string.length for (let index = 1; index <= padLength; index++) { string = symbol + string } return string - }, - randomUuid: uuid.v4, - getMerkleHash(hashList, option) { + } + + /** + * + * + * @static + * @memberof TICrypto + */ + static randomUuid = uuid.v4 + + /** + * 获取梅克哈希 + * + * @static + * @param {*} hashList + * @param {*} [option={}] + * @return {*} + * @memberof TICrypto + */ + static getMerkleHash(hashList, option = {}) { // merkle算法略有难度,暂时用最简单的hash代替 if (Array.isArray(hashList)) { - option = option || {} let output = option.output === 'buf' ? undefined : option.output || my.OUTPUT let hasher = crypto.createHash(my.HASHER_LIST.indexOf(option.hasher) >= 0 ? option.hasher : my.HASHER) for (var hash of hashList) { @@ -632,8 +984,18 @@ module.exports = { return hasher.digest(output) } return null - }, - getMerkleRoot(todoHashList, option) { + } + + /** + * 获取梅克根 + * + * @static + * @param {*} todoHashList + * @param {*} option + * @return {*} + * @memberof TICrypto + */ + static getMerkleRoot(todoHashList, option) { //深拷贝传入数组,防止引用对象被改变 let hashList = [...todoHashList] if (!Array.isArray(hashList)) return null @@ -659,16 +1021,37 @@ module.exports = { border = j + 1 } return hashList - }, - distanceSig(hash, sig) { + } + + /** + * 计算哈希距离 + * + * @static + * @param {*} hash + * @param {*} sig + * @return {*} + * @memberof TICrypto + */ + static distanceSig(hash, sig) { // hash为64hex字符,sig为128hex字符。返回用hex表达的距离。 if (this.isSignature(sig) && this.isHash(hash)) { var hashSig = this.hash(sig) // 把签名也转成32字节的哈希,同样长度方便比较 return new BigInt(hash, 16).subtract(new BigInt(hashSig, 16)).abs().toString(16) } return null - }, - compareSig(hash, sig1, sig2) { + } + + /** + * 比较签名 + * + * @static + * @param {*} hash + * @param {*} sig1 + * @param {*} sig2 + * @return {*} + * @memberof TICrypto + */ + static compareSig(hash, sig1, sig2) { // 返回距离hash更近的sig if (this.isHash(hash)) { if (this.isSignature(sig2) && this.isSignature(sig1)) { @@ -690,8 +1073,18 @@ module.exports = { } } return null - }, - sortSigList(hash, sigList) { + } + + /** + * 排序签名集 + * + * @static + * @param {*} hash + * @param {*} sigList + * @return {*} + * @memberof TICrypto + */ + static sortSigList(hash, sigList) { if (Array.isArray(sigList) && this.isHash(hash)) { sigList.sort(function (sig1, sig2) { if (this.isSignature(sig1) && this.isSignature(sig2)) { @@ -707,13 +1100,15 @@ module.exports = { return sigList } return null - }, - /** 用于支付宝的接口 - * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 - * @param $para 需要拼接的数组 - * return 拼接完成以后的字符串 + } + + /** + * 用于支付宝的支付交易接口 + * + * @param $para 需要拼接的数组,把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 + * @return 拼接完成以后的字符串 */ - getString2Sign(paramSet, converter, delimiter) { + static getString2Sign(paramSet, converter, delimiter) { if (paramSet && typeof paramSet === 'object') { var string2Sign = '' var converter = converter || '' @@ -736,52 +1131,138 @@ module.exports = { return string2Sign } return '' - }, - rsaSign(string2Sign, prikey, signType) { + } + + /** + * rsa签名 + * + * @static + * @param {*} string2Sign + * @param {*} prikey + * @param {*} signType + * @return {*} + * @memberof TICrypto + */ + static rsaSign(string2Sign, prikey, signType) { signType = signType || 'RSA-SHA1' // could be RSA-SHA256, RSA-SHA1 or more let signer = crypto.createSign(signType) return encodeURIComponent(signer.update(string2Sign).sign(prikey, 'base64')) - }, - rsaVerify(string2Verify, signature, pubkey, signType) { + } + + /** + * rsa验证签名 + * + * @static + * @param {*} string2Verify + * @param {*} signature + * @param {*} pubkey + * @param {*} signType + * @return {*} + * @memberof TICrypto + */ + static rsaVerify(string2Verify, signature, pubkey, signType) { signType = signType || 'RSA-SHA1' // could be RSA-SHA256, RSA-SHA1 or more let verifier = crypto.createVerify(signType) return verifier.update(string2Verify).verify(pubkey, signature, 'base64') - }, - buf2hex(buffer) { + } + + /** + * 缓存转十六进制 + * + * @static + * @param {*} buffer + * @return {*} + * @memberof TICrypto + */ + static buf2hex(buffer) { // buffer is an ArrayBuffer return Array.prototype.map.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)).join('') - }, - hex2buf(hex) { + } + + /** + * 十六进制转缓存 + * + * @static + * @param {*} hex + * @return {*} + * @memberof TICrypto + */ + static hex2buf(hex) { return new Uint8Array( hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) }) ) // 注意,arraybuffer没有 toString('hex')功能, Buffer才有。 - }, - hex2b58c(hex) { + } + + /** + * 十六进制转b58c + * + * @static + * @param {*} hex + * @return {*} + * @memberof TICrypto + */ + static hex2b58c(hex) { return bs58check.encode(Buffer.from(hex, 'hex')) - }, - b58c2hex(box) { + } + + /** + * b58c 转十六进制 + * + * @static + * @param {*} box + * @return {*} + * @memberof TICrypto + */ + static b58c2hex(box) { try { return bs58check.decode(box).toString('hex') } catch (exception) { return null } - }, - hex2b64u(hex) { + } + + /** + * 十六进制转b64u + * + * @static + * @param {*} hex + * @return {*} + * @memberof TICrypto + */ + static hex2b64u(hex) { if (/^[0-9a-fA-F]+$/.test(hex)) { return Buffer.from(hex, 'hex').toString('base64').replace(/\+/g, '-').replace(/\//g, '_') } return null - }, - b64u2hex(b64u) { + } + + /** + * b64u转16进制 + * + * @static + * @param {*} b64u + * @return {*} + * @memberof TICrypto + */ + static b64u2hex(b64u) { if (/^[0-9a-zA-Z\-_]+$/.test(b64u)) { let b64 = b64u.replace(/\-/g, '+').replace(/_/g, '/') return Buffer.from(b64, 'base64').toString('hex') } return null - }, - hex2eip55(hex) { + } + + /** + * 十六进制转eip55 + * + * @static + * @param {*} hex + * @return {*} + * @memberof TICrypto + */ + static hex2eip55(hex) { if (/^(0x)?[\da-fA-F]*$/.test(hex)) { hex = hex.toLowerCase().replace('0x', '') let hash = keccak('keccak256').update(hex).digest('hex') @@ -796,8 +1277,17 @@ module.exports = { return result } return null - }, - aiid2regcode(aiid) { + } + + /** + * 用户编号转邀请码 + * + * @static + * @param {*} aiid + * @return {*} + * @memberof TICrypto + */ + static aiid2regcode(aiid) { const alphabet = 'e5fcdg3hqa4b1n0pij2rstuv67mwx89klyz' const base = 16367 let num = (aiid + base) * (base - alphabet.length) @@ -809,8 +1299,17 @@ module.exports = { code = code + alphabet[mod] // 倒序存放 } return code - }, - regcode2aiid(code) { + } + + /** + * 邀请码转用户编号 + * + * @static + * @param {*} code + * @return {*} + * @memberof TICrypto + */ + static regcode2aiid(code) { const alphabet = 'e5fcdg3hqa4b1n0pij2rstuv67mwx89klyz' const base = 16367 let len = code.length @@ -819,10 +1318,19 @@ module.exports = { num += alphabet.indexOf(code[i]) * Math.pow(alphabet.length, i) } return num / (base - alphabet.length) - base - }, - // test: https://iancoleman.io/bitcoin-key-compression/ - // compress: https://hacpai.com/article/1550844562914 - compressPubkey(uncompressed) { + } + + /** + * 压缩公钥 + * + * @static + * @param {*} uncompressed + * @return {*} + * @memberof TICrypto + */ + static compressPubkey(uncompressed) { + // test: https://iancoleman.io/bitcoin-key-compression/ + // compress: https://hacpai.com/article/1550844562914 // 把 04xy 的非压缩公钥 转成 02x 或 03x 的压缩公钥 let [all, x, y] = uncompressed.toLowerCase().match(/^04(.{64})(.{64})$/) let compressed @@ -835,10 +1343,19 @@ module.exports = { return compressed } return null // 非压缩公钥有错误。 - }, - // uncompress: https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression/53478265#53478265 - // https://en.bitcoin.it/wiki/Secp256k1 - decompressPubkey(compressed) { + } + + /** + * 解压缩公钥 + * + * @static + * @param {*} compressed + * @return {*} + * @memberof TICrypto + */ + static decompressPubkey(compressed) { + // uncompress: https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression/53478265#53478265 + // https://en.bitcoin.it/wiki/Secp256k1 // 把 02x 或 03x 的压缩公钥 转成 04xy 的非压缩公钥 // Consts for secp256k1 curve. Adjust accordingly const prime = new BigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16) // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 @@ -851,5 +1368,7 @@ module.exports = { y = prime.subtract(y) // y = prime - y } return '04' + this.padStart(x.toString(16), 64, '0') + this.padStart(y.toString(16), 64, '0') - }, + } } + +module.exports = TICrypto diff --git a/package.json b/package.json index 84d472b..0366b00 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,12 @@ "tweetnacl": "^1.0.3", "uuid": "^8.3.1" }, - "devDependencies": {}, + "devDependencies": { + "docdash": "^1.2.0", + "jaguarjs-jsdoc": "^1.1.0", + "jsdoc": "^3.6.6", + "minami": "^1.2.3" + }, "scripts": { "setup": "npm install" },