From 74694866bc46316672d3b0b1d81d80300c45e2ed Mon Sep 17 00:00:00 2001 From: Luk Lu Date: Sat, 28 May 2022 09:08:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20cid=20=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.js | 247 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 173 insertions(+), 74 deletions(-) diff --git a/index.js b/index.js index 3041c2a..fa9afcb 100644 --- a/index.js +++ b/index.js @@ -31,6 +31,17 @@ my.INPUT = 'utf8' // 默认的加密方法的明文格式。utf8 能够兼容 la my.INPUT_LIST = ['utf8', 'ascii', 'latin1'] // ignored for Buffer/TypedArray/DataView my.COIN = 'TIC' // 默认的币种 my.COIN_LIST = ['TIC', 'EXT', 'BTC', 'ETH'] +my.REGEXP_ALPHABET = { + hex: /^[0-9a-fA-F]+$/, + b32: /^[A-Za-z2-7=]+$/, + b32h: /^[0-9A-Va-v=]+$/, + b36: /^[0-9A-Z-a-z]+$/, + b58: /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/, + b62: /^[A-Za-z0-9]+$/, + b64: /^[A-Za-z0-9\+\/=]+$/, + b64u: /^[A-Za-z0-9\-_]+$/, + b64t: /^[A-Za-z0-9\._]+$/, +} /** * @@ -46,7 +57,7 @@ class TICrypto { * @return {Boolean} * @memberof TICrypto */ - static isHashable(data, { strict = false } = {}) { + static isHashable (data, { strict = false } = {}) { if (strict) { return typeof data !== 'boolean' && data !== Infinity && data ? true : false // 允许大多数数据,除了null、''、0、布尔值、无限数。注意 data 要放在最后,否则会被 return 直接返回,而不是返回 Boolean } @@ -62,7 +73,7 @@ class TICrypto { * @return {Boolean} * @memberof TICrypto */ - static isHash(hash, { hasher = my.HASHER } = {}) { + static isHash (hash, { hasher = my.HASHER } = {}) { if (my.HASHER_LIST.indexOf(hasher) >= 0) { switch (hasher) { case 'sha256': @@ -88,7 +99,7 @@ class TICrypto { * @return {Boolean} * @memberof TICrypto */ - static isSecword(secword, { mode = 'strict' } = {}) { + 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) @@ -115,7 +126,7 @@ class TICrypto { * @return {Boolean} * @memberof TICrypto */ - static isSeckey(seckey) { + 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) @@ -129,7 +140,7 @@ class TICrypto { * @return {Boolean} * @memberof TICrypto */ - static isPubkey(pubkey) { + static isPubkey (pubkey) { // 比特币的公钥:压缩型 '02|03' + 64 hex 或 无压缩型 '04' + 128 hex // 以太坊的公钥:'02|03' + 64 hex // nacl.sign 的公钥:64 hex @@ -144,7 +155,7 @@ class TICrypto { * @return {Boolean} * @memberof TICrypto */ - static isSignature(signature) { + 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. } @@ -157,14 +168,17 @@ class TICrypto { * @return {String} * @memberof TICrypto */ - static hash(data, { hasher = my.HASHER, salt, input = my.INPUT, output = my.OUTPUT } = {}) { + 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) if (salt && typeof salt === 'string') data = data + this.hash(salt) let inputEncoding = input // my.INPUT_LIST.indexOf(input)>=0?input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView. let outputEncoding = output === 'buf' ? undefined : output // (my.OUTPUT_LIST.indexOf(output)>=0?output:my.OUTPUT) // output: 留空=》默认输出hex格式;或者手动指定 'buf', hex', 'latin1' or 'base64' - return crypto.createHash(hasher).update(data, inputEncoding).digest(outputEncoding) + return crypto + .createHash(hasher) + .update(data, inputEncoding) + .digest(outputEncoding) } return null } @@ -178,7 +192,7 @@ class TICrypto { * @return {String} * @memberof TICrypto */ - static async encrypt({ data, tool = 'crypto', keytype = 'pwd', key, input, output, cipher } = {}) { + static async encrypt ({ data, tool = 'crypto', keytype = 'pwd', key, input, output, cipher } = {}) { if (tool === 'eccrypto') { // data 应当是 utf8 的字符串。key 必须是 pubkey // eccrypto 能用 Uint8Array 和 Buffer @@ -218,7 +232,7 @@ class TICrypto { * @return {String} * @memberof TICrypto */ - static async decrypt({ data = {}, tool = 'crypto', keytype = 'pwd', key, input, output, cipher } = {}) { + static async decrypt ({ data = {}, tool = 'crypto', keytype = 'pwd', key, input, output, cipher } = {}) { // data 应当是 encrypt 输出的数据类型 if (tool === 'eccrypto') { try { @@ -267,7 +281,7 @@ class TICrypto { * @return {String} * @memberof TICrypto */ - static async sign({ data, seckey, tool = 'crypto', hasher }) { + static async sign ({ data, seckey, tool = 'crypto', hasher }) { // data can be string or buffer or object, results are the same if (this.isHashable(data) && this.isSeckey(seckey)) { if (tool === 'nacl' && seckey.length === 128) { @@ -304,7 +318,7 @@ class TICrypto { * @return {Boolean} * @memberof TICrypto */ - static async verify({ data, signature, pubkey, tool = 'crypto', hasher }) { + static async verify ({ data, signature, pubkey, tool = 'crypto', hasher }) { // 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' === tool && signature.length === 128) { @@ -345,11 +359,14 @@ class TICrypto { * @return {Object} {pubkey, seckey, address,} * @memberof TICrypto */ - static pass2keypair(pass, { hasher } = {}) { + static pass2keypair (pass, { hasher } = {}) { // 如果使用其他机制,例如密码、随机数,不使用secword,也可生成keypair if (this.isHashable(pass)) { hasher = my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER - var hashBuf = crypto.createHash(hasher).update(pass).digest() + var hashBuf = crypto + .createHash(hasher) + .update(pass) + .digest() var keypair = nacl.sign.keyPair.fromSeed(hashBuf) // nacl的seed要求是32字节 return { hash: hashBuf.toString('hex'), @@ -368,7 +385,7 @@ class TICrypto { * @return {String} * @memberof TICrypto */ - static entropy2secword(entropy) { + 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. } @@ -381,7 +398,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static secword2entropy(secword) { + 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. } @@ -395,7 +412,7 @@ class TICrypto { * @return {Object} {pubkey, seckey,} * @memberof TICrypto */ - static secword2keypair(secword, { coin, pass, path, tool, hasher } = {}) { + static secword2keypair (secword, { coin, pass, path, tool, hasher } = {}) { // coin 币种; // passphase 密码,默认为空; // path==='master' 生成 HD master key,不定义则默认为相应币种的第一对公私钥。 @@ -412,7 +429,10 @@ class TICrypto { if (tool === 'nacl') { // 采用自己的算法:bip39算法从secword到种子,hash后用 nacl.sign.keyPair.fromSeed()方法。 hasher = my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER - let hashBuf = crypto.createHash(hasher).update(this.secword2seed(secword, pass)).digest() + let hashBuf = crypto + .createHash(hasher) + .update(this.secword2seed(secword, pass)) + .digest() let keypair = nacl.sign.keyPair.fromSeed(hashBuf) // nacl.sign.keyPair.fromSeed 要求32字节的种子,而 this.secword2seed生成的是64字节种子,所以要先做一次sha256 return { coin: coin, @@ -466,7 +486,7 @@ class TICrypto { * @return {String} path * @memberof TICrypto */ - static seed2path(seed, { coin = 'TIC' } = { coin: 'TIC' }) { + static seed2path (seed, { coin = 'TIC' } = { coin: 'TIC' }) { // 路径规范 BIP44: m/Purpose'/Coin'/Account'/Change/Index, // 但实际上 Purpose, Coin 都可任意定;' 可有可无; // Account/Change/Index 最大到 parseInt(0x7FFFFFFF, 16) @@ -501,7 +521,7 @@ class TICrypto { * @return {Object} * @memberof TICrypto */ - static secword2account(secword, { coin, pass, path, tool, hasher } = {}) { + static secword2account (secword, { coin, pass, path, tool, hasher } = {}) { // account 比 keypair 多了 address 字段。 coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN let kp = this.secword2keypair(secword, { coin, pass, path, tool, hasher }) @@ -526,7 +546,7 @@ class TICrypto { * @return {String} address * @memberof TICrypto */ - static secword2address(secword, { coin, world, pass, path, tool, hasher } = {}) { + static secword2address (secword, { coin, world, pass, path, tool, hasher } = {}) { coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN let kp = this.secword2keypair(secword, { coin, pass, path, tool, hasher }) if (kp) { @@ -550,7 +570,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static seckey2pubkey(seckey, { curve, compress } = {}) { + static seckey2pubkey (seckey, { curve, compress } = {}) { if (this.isSeckey(seckey) && seckey.length === 64) { // 只能用于32字节的私钥(BTC, ETH)。也就是不能用于 TIC 的私钥。 curve = my.CURVE_LIST.indexOf(curve) >= 0 ? curve : my.CURVE // 默认为 secp256k1 @@ -582,7 +602,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static seckey2address(seckey, { coin, world } = {}) { + static seckey2address (seckey, { coin, world } = {}) { coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN if (this.isSeckey(seckey)) { /** @type {*} */ @@ -608,7 +628,7 @@ class TICrypto { * @memberof TICrypto * position 就是通常所说的 PubKeyHash,出现在比特币交易的锁定脚本里 */ - static pubkey2position(pubkey, { coin } = {}) { + static pubkey2position (pubkey, { coin } = {}) { // tic, btc, eth 的 position 都是 20节=40字符的。 coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN if (this.isPubkey(pubkey)) { @@ -622,8 +642,14 @@ class TICrypto { .digest('hex') .slice(-40) } else { - let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest() - let h160 = crypto.createHash('ripemd160').update(h256).digest('hex') + let h256 = crypto + .createHash('sha256') + .update(Buffer.from(pubkey, 'hex')) + .digest() + let h160 = crypto + .createHash('ripemd160') + .update(h256) + .digest('hex') return h160 } } @@ -639,7 +665,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static position2address(position, { coin, world } = {}) { + 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 @@ -647,7 +673,9 @@ class TICrypto { if (coin === 'ETH') { // 对以太坊,按照 EIP55,把纯位置转换为大小写敏感能自我验证的hex地址。仍然为20节=40符。 position = position.toLowerCase().replace('0x', '') - let hash = keccak('keccak256').update(position).digest('hex') + let hash = keccak('keccak256') + .update(position) + .digest('hex') address = '0x' for (var i = 0; i < position.length; i++) { if (parseInt(hash[i], 16) >= 8) { @@ -719,16 +747,19 @@ class TICrypto { * @memberof TICrypto * 地址和PubKeyHash(即position)之间能互相转化 */ - static address2position() { - if (/^0x[\da-fA-F]{40}$/.test(address)) { // ETH + static address2position () { + if (/^0x[\da-fA-F]{40}$/.test(address)) { + // ETH // todo: 如果是大小写敏感的,进行有效性验证 return address.toLowerCase() - } else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address)) { // BTC + } else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address)) { + // BTC let hex = this.b58c_to_hex(address) if (hex) { return hex.slice(2) // 去除网络前缀 } - } else if (/^[Tt][0-9a-zA-Z\._]{31}$/.test(address)) { // TIC + } else if (/^[Tt][0-9a-zA-Z\._]{31}$/.test(address)) { + // TIC // 格式合法 let hex = this.b64t_to_hex(address) let [all, prefix, position, checksum] = hex.match(/^([\da-fA-F]{2})([\da-fA-F]{40})([\da-fA-F]{6})$/) @@ -747,7 +778,7 @@ class TICrypto { * @return {Boolean} * @memberof TICrypto */ - static isAddress(address) { + static isAddress (address) { if (/^(0x)?[\da-fA-F]{40}$/.test(address)) { return 'ETH' } else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address) && address.length !== 32) { @@ -776,7 +807,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static pubkey2address(pubkey, { coin, world } = {}) { + static pubkey2address (pubkey, { coin, world } = {}) { // pubkey 应当是string类型 coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN return this.position2address(this.pubkey2position(pubkey, { coin }), { coin, world }) @@ -791,7 +822,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static secword2seed(secword, pass) { + static secword2seed (secword, pass) { // 遵循bip39的算法。和 ether.HDNode.mnemonic2Seed 结果一样,是64字节的种子。 // 注意,bip39.mnemonicToSeedSync 也接受不合法的 secword,只要是个string,或者是 undefined/null/0/''/false(这几个的结果都一样) return bip39.mnemonicToSeedSync(secword, pass).toString('hex') // 结果一致与 new BitcoreMnemonic(secword).toSeed(pass).toString('hex') 或 ethers.HDNode.mnemonic2Seed(secword)。 @@ -805,7 +836,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomSecword(lang = 'english') { + static randomSecword (lang = 'english') { // accepts case-insensitive lang, such as 'chinese, cn, tw, en' //// for BitcoreMnemonic // lang=lang.toUpperCase() @@ -849,7 +880,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomSeckey({ coin, tool } = {}) { + static randomSeckey ({ coin, tool } = {}) { // 跳过 secword 直接产生随机密钥 coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin : my.COIN if (tool === 'nacl') { @@ -867,7 +898,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomKeypair({ tool, purpose } = {}) { + static randomKeypair ({ tool, purpose } = {}) { let kp if (tool === 'nacl') { if (purpose === 'encrypt') { @@ -897,7 +928,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomAccount({ lang, coin, pass, path, tool, hasher } = {}) { + static randomAccount ({ lang, coin, pass, path, tool, hasher } = {}) { let secword = this.randomSecword(lang) return this.secword2account(secword, { coin, pass, path, tool, hasher }) } @@ -911,7 +942,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomString(length = 6, alphabet) { + static randomString (length = 6, alphabet) { // 长度为 length,字母表为 alphabet 的随机字符串 alphabet = alphabet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%^&*@' var text = '' @@ -929,7 +960,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomNumber({ length, min, max } = {}) { + static randomNumber ({ length, min, max } = {}) { // 长度为 length 的随机数字,或者 (min||0) <= num < max var num = 0 if (typeof length === 'number' && length > 0) { @@ -955,7 +986,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static padStart(string, targetLength, symbol) { + static padStart (string, targetLength, symbol) { // 2020-03: 发现在浏览器里,还不支持 string.padStart(),只好自己写个暂代。 let padLength = targetLength - string.length for (let index = 1; index <= padLength; index++) { @@ -970,7 +1001,7 @@ class TICrypto { * @static * @memberof TICrypto */ - static randomUuid() { + static randomUuid () { return uuid.v4() } @@ -983,7 +1014,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static getMerkleHash(hashList, { output, hasher } = {}) { + static getMerkleHash (hashList, { output, hasher } = {}) { // merkle算法略有难度,暂时用最简单的hash代替 if (Array.isArray(hashList)) { myhasher = crypto.createHash(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER) @@ -1004,7 +1035,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static getMerkleRoot(todoHashList) { + static getMerkleRoot (todoHashList) { //深拷贝传入数组,防止引用对象被改变 let hashList = [...todoHashList] if (!Array.isArray(hashList)) return null @@ -1041,11 +1072,14 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static distanceSig(hash, sig) { + 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 new BigInt(hash, 16) + .subtract(new BigInt(hashSig, 16)) + .abs() + .toString(16) } return null } @@ -1060,7 +1094,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static compareSig(hash, sig1, sig2) { + static compareSig (hash, sig1, sig2) { // 返回距离hash更近的sig if (this.isHash(hash)) { if (this.isSignature(sig2) && this.isSignature(sig1)) { @@ -1093,7 +1127,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static sortSigList(hash, sigList) { + static sortSigList (hash, sigList) { if (Array.isArray(sigList) && this.isHash(hash)) { sigList.sort(function (sig1, sig2) { if (this.isSignature(sig1) && this.isSignature(sig2)) { @@ -1117,7 +1151,7 @@ class TICrypto { * @param $para 需要拼接的数组,把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 * @return 拼接完成以后的字符串 */ - static getString2Sign(paramSet, converter, delimiter) { + static getString2Sign (paramSet, converter, delimiter) { if (paramSet && typeof paramSet === 'object') { var string2Sign = '' var converter = converter || '' @@ -1152,7 +1186,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static rsaSign(string2Sign, prikey, signType) { + 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')) @@ -1169,7 +1203,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static rsaVerify(string2Verify, signature, pubkey, signType) { + 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') @@ -1183,7 +1217,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static buf_to_hex(buffer) { + static buf_to_hex (buffer) { // buffer is an ArrayBuffer return Array.prototype.map.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)).join('') } @@ -1196,7 +1230,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static hex_to_buf(hex) { + static hex_to_buf (hex) { return new Uint8Array( hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) @@ -1215,7 +1249,7 @@ class TICrypto { * bs58check 和 bs58 可接受string, Buffer, ArrayBuffer, Array (包括空字符串'', 各种内容的数组例如包含 undefined,{...},等等); * 不可接受 undefined, null, {...}, 等等,会返回 exception */ - static hex_to_b58c(hex) { + static hex_to_b58c (hex) { try { return bs58check.encode(Buffer.from(hex, 'hex')) } catch (exception) { @@ -1223,7 +1257,7 @@ class TICrypto { } } - static hex_to_b58(hex) { + static hex_to_b58 (hex) { try { return bs58.encode(Buffer.from(hex, 'hex')) } catch (exception) { @@ -1239,7 +1273,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static b58c_to_hex(box) { + static b58c_to_hex (box) { try { return bs58check.decode(box).toString('hex') } catch (exception) { @@ -1247,7 +1281,7 @@ class TICrypto { } } - static b58_to_hex(box) { + static b58_to_hex (box) { try { return bs58.decode(box).toString('hex') } catch (exception) { @@ -1260,14 +1294,17 @@ class TICrypto { * 其中,+ 和 / 会在 url query string 里被转成 %2B 和 %2F * 因此定义 b64t (base64 for tic),用 . 和 _ 替换。 * (为何不用 base64url, 因为 base64url 把 + 变成 - 和空格一样导致 css white-space 自动换行。) - * @param {*} b64 - * @returns + * @param {*} b64 + * @returns */ - static b64_to_b64t(b64='') { - return b64.replace(/\+/g, '.').replace(/\//g, '_').replace(/=/g, '') + static b64_to_b64t (b64 = '') { + return b64 + .replace(/\+/g, '.') + .replace(/\//g, '_') + .replace(/=/g, '') } - static b64t_to_b64(b64t='') { + static b64t_to_b64 (b64t = '') { return b64t.replace(/\./g, '+').replace(/_/g, '/') } @@ -1279,7 +1316,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static hex_to_b64t(hex) { + static hex_to_b64t (hex) { if (/^[0-9a-fA-F]+$/.test(hex)) { return this.b64_to_b64t(Buffer.from(hex, 'hex').toString('base64')) } @@ -1294,7 +1331,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static b64t_to_hex(b64t) { + static b64t_to_hex (b64t) { if (/^[0-9a-zA-Z\._]+$/.test(b64t)) { return Buffer.from(this.b64t_to_b64(b64t), 'base64').toString('hex') } @@ -1302,25 +1339,25 @@ class TICrypto { } // https://en.wikipedia.org/wiki/Base32 - static hex_to_b32(hex, {encoding='RFC4648'}={}) { + static hex_to_b32 (hex, { encoding = 'RFC4648' } = {}) { if (/^[0-9a-fA-F]+$/.test(hex)) { return base32encode(Buffer.from(hex, 'hex'), encoding) } return null } - static b32_to_hex(b32, {encoding='RFC4648'}={}) { + static b32_to_hex (b32, { encoding = 'RFC4648' } = {}) { if (/^[A-Za-z2-7=]+$/.test(b32)) { return Buffer.from(base32decode(b32.toUpperCase(), encoding)).toString('hex') } return null } - static hex_to_b32h(hex, {encoding='RFC4648-HEX'}={}) { + static hex_to_b32h (hex, { encoding = 'RFC4648-HEX' } = {}) { if (/^[0-9a-fA-F]+$/.test(hex)) { return base32encode(Buffer.from(hex, 'hex'), encoding) } return null } - static b32h_to_hex(b32, {encoding='RFC4648-HEX'}={}) { + static b32h_to_hex (b32, { encoding = 'RFC4648-HEX' } = {}) { if (/^[0-9A-Va-v=]+$/.test(b32)) { return Buffer.from(base32decode(b32.toUpperCase(), encoding)).toString('hex') } @@ -1335,10 +1372,12 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static hex_to_eip55(hex) { + static hex_to_eip55 (hex) { if (/^(0x)?[\da-fA-F]+$/.test(hex)) { hex = hex.toLowerCase().replace('0x', '') - let hash = keccak('keccak256').update(hex).digest('hex') + let hash = keccak('keccak256') + .update(hex) + .digest('hex') let result = '' for (var i = 0; i < hex.length; i++) { if (parseInt(hash[i], 16) >= 8) { @@ -1360,7 +1399,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static compressPubkey(uncompressed) { + static compressPubkey (uncompressed) { // test: https://iancoleman.io/bitcoin-key-compression/ // compress: https://hacpai.com/article/1550844562914 // 把 04xy 的非压缩公钥 转成 02x 或 03x 的压缩公钥 @@ -1385,7 +1424,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static decompressPubkey(compressed) { + 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 的非压缩公钥 @@ -1394,13 +1433,73 @@ class TICrypto { const pIdent = new BigInt('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4); var signY = new Number(compressed[1]) - 2 var x = new BigInt(compressed.substr(2), 16) - var y = x.modPow(3, prime).add(7).mod(prime).modPow(pIdent, prime) // y mod p = +-(x^3 + 7)^((p+1)/4) mod p + var y = x + .modPow(3, prime) + .add(7) + .mod(prime) + .modPow(pIdent, prime) // y mod p = +-(x^3 + 7)^((p+1)/4) mod p if (y.mod(2).toJSNumber() !== signY) { // If the parity doesn't match it's the *other* root y = prime.subtract(y) // y = prime - y } return '04' + this.padStart(x.toString(16), 64, '0') + this.padStart(y.toString(16), 64, '0') } + + static cid_to_cosh ({ cid }) { + if (/^[Q|1]/.test(cid)) { + return this.b58_to_hex(cid).slice(4) + } else if (/^b/.test(cid)) { + return this.b32_to_hex(cid.substr(1)).slice(8) + } else if (/^z/.test(cid)) { + return this.b58_to_hex(cid.substr(1)).slice(8) + } + } + + static cosh_to_cid ({ cosh, cidBase = 'b32', cidVersion = 1, cidCodec = 'raw', cidAlgo = 'sha256' }) { + const multibase = { + identity: 0x00, + b2: '0', + b8: '7', + b10: '9', + b16: 'f', + B16: 'F', + b32: 'b', + B32: 'B', + b32h: 'v', + B32h: 'V', + b36: 'k', + b64: 'm', + b64p: 'M', + b64u: 'u', + b64up: 'U', + b58: 'z', + } + const multicodec = { + dagpb: '70', + p2pkey: '72', + raw: '55' + } + const multialgo = { + identify: '00', + sha256: '12' + } + if (cidVersion === 0) { + return this.hex_to_b58(`${multialgo[cidAlgo]}${Number(cosh.length/2).toString(16)}${cosh}`) + } + if (cidVersion === 1) { + let fullHex = `01${multicodec[cidCodec]}${multialgo[cidAlgo]}${Number(cosh.length/2).toString(16)}${cosh}` + if (cidBase==='b32') { + return multibase[cidBase] + this.hex_to_b32(fullHex).toLowerCase().replace(/=/g,'') + }else if (cidBase==='b58') { + return multibase[cidBase] + this.hex_to_b58(fullHex) + } + } + } + + static string_to_raw_cid (str) { + return this.cosh_to_cid({ cosh: this.hash(str), cidVersion:1, cidCodec:'raw' }) + } + } // 必须单独写 module.exports,不要和类定义写在一起,否则会导致 jsdoc 解析不到类内文档。