From c2adfd5a14dfba41e5b1f9639e31443c9f31747e Mon Sep 17 00:00:00 2001 From: Luk Lu Date: Thu, 2 Jun 2022 13:06:42 +0800 Subject: [PATCH] u --- index.js | 158 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 82 insertions(+), 76 deletions(-) diff --git a/index.js b/index.js index 19e91f2..7fda745 100644 --- a/index.js +++ b/index.js @@ -30,7 +30,7 @@ my.OUTPUT_LIST = ['hex', 'latin1', 'base64'] // or 'buf' to Buffer explicitly my.INPUT = 'utf8' // 默认的加密方法的明文格式。utf8 能够兼容 latin1, ascii 的情形 my.INPUT_LIST = ['utf8', 'ascii', 'latin1'] // ignored for Buffer/TypedArray/DataView my.COIN = 'TIC' // 默认的币种 -my.COIN_LIST = ['TIC', 'EXT', 'BTC', 'ETH'] +my.COIN_LIST = ['TIC', 'BTC', 'ETH'] my.REGEXP_ALPHABET = { hex: /^[0-9a-fA-F]+$/, b32: /^[A-Za-z2-7=]+$/, @@ -74,7 +74,7 @@ class TICrypto { * @memberof TICrypto */ static isHash (hash, { hasher = my.HASHER } = {}) { - if (my.HASHER_LIST.indexOf(hasher) >= 0) { + if (my.HASHER_LIST.includes(hasher)) { switch (hasher) { case 'sha256': return /^[a-fA-F0-9]{64}$/.test(hash) @@ -173,8 +173,8 @@ class TICrypto { 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' + let inputEncoding = input // my.INPUT_LIST.includes(input)?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.includes(output)?output:my.OUTPUT) // output: 留空=》默认输出hex格式;或者手动指定 'buf', hex', 'latin1' or 'base64' return crypto .createHash(hasher) .update(data, inputEncoding) @@ -203,10 +203,10 @@ class TICrypto { } else 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 - let outputEncoding = output === 'buf' ? undefined : my.OUTPUT_LIST.indexOf(output) >= 0 ? output : my.OUTPUT // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly + let inputEncoding = my.INPUT_LIST.includes(input) ? input : my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView + let outputEncoding = output === 'buf' ? undefined : my.OUTPUT_LIST.includes(output) ? output : my.OUTPUT // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly const iv = crypto.randomBytes(16) - let encryptor = crypto.createCipheriv(my.CIPHER_LIST.indexOf(cipher) >= 0 ? cipher : my.CIPHER, this.hex_to_buf(this.hash(key)), iv) // cipher 和 key 的长度必须相同,例如 cipher 是 ***-192,那么 key 就必须是 192/8=24 字节 = 48 hex 的。 + let encryptor = crypto.createCipheriv(my.CIPHER_LIST.includes(cipher) ? cipher : my.CIPHER, this.hex_to_buf(this.hash(key)), iv) // cipher 和 key 的长度必须相同,例如 cipher 是 ***-192,那么 key 就必须是 192/8=24 字节 = 48 hex 的。 if (typeof data !== 'string' && !(data instanceof Buffer) && !(data instanceof DataView)) data = JSON.stringify(data) let ciphertext = encryptor.update(data, inputEncoding, outputEncoding) ciphertext += encryptor.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string @@ -248,10 +248,10 @@ class TICrypto { } else if (keytype === 'pwd') { // 对称解密 if ((typeof data.ciphertext === 'string' || data.ciphertext instanceof Buffer) && typeof key === 'string') { - let inputEncoding = my.OUTPUT_LIST.indexOf(input) >= 0 ? input : my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer - let outputEncoding = output === 'buf' ? undefined : my.INPUT_LIST.indexOf(output) >= 0 ? output : my.INPUT // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly + let inputEncoding = my.OUTPUT_LIST.includes(input) ? input : my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer + let outputEncoding = output === 'buf' ? undefined : my.INPUT_LIST.includes(output) ? output : my.INPUT // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly let decryptor = crypto.createDecipheriv( - my.CIPHER_LIST.indexOf(cipher) >= 0 ? cipher : my.CIPHER, + my.CIPHER_LIST.includes(cipher) ? cipher : my.CIPHER, this.hex_to_buf(this.hash(key)), Buffer.from(data.iv, 'hex') ) @@ -296,11 +296,11 @@ class TICrypto { } else if (seckey.length === 64) { // 纯 crypto let seckeyPEM = await new keyman.Key('oct', this.hex_to_buf(seckey), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 - let signer = crypto.createSign(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER) // 注意,不知为何,hasher必须含有'sha'才能完成签名,例如 sha1, sha256, sha512, sha3, RSA-SHA1, id-rsassa-pkcs1-v1_5-with-sha3-224, 其他都会报错。 + let signer = crypto.createSign(my.HASHER_LIST.includes(hasher) ? hasher : my.HASHER) // 注意,不知为何,hasher必须含有'sha'才能完成签名,例如 sha1, sha256, sha512, sha3, RSA-SHA1, id-rsassa-pkcs1-v1_5-with-sha3-224, 其他都会报错。 signer.update(this.hash(data)).end() let signature = signer.sign(seckeyPEM, 'hex') // since nodejs 12, 有了 crypto.sign 方法,但在浏览器中无效: - // let signature = crypto.sign(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER, Buffer.from(this.hash(data)), seckeyPEM).toString('hex') + // let signature = crypto.sign(my.HASHER_LIST.includes(hasher) ? hasher : my.HASHER, Buffer.from(this.hash(data)), seckeyPEM).toString('hex') return signature // 发现同样的输入,nodejs里每次调用会生成不同的 signature, 且长度不定(140,142,144 hex) 但都可以通过 verify。但在浏览器里调用,signature却是固定的。 } } @@ -339,11 +339,11 @@ class TICrypto { } else if (signature.length >= 140) { // 纯 crypto // 发现大小写不影响 crypto 验签!都能通过 let pubkeyPEM = await new keyman.Key('oct', this.hex_to_buf(pubkey), { namedCurve: 'P-256K' }).export('pem') // 公钥导出的der格式为88字节。经测试,同一对压缩和非压缩公钥得出的结果一模一样。 - let verifier = crypto.createVerify(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER) + let verifier = crypto.createVerify(my.HASHER_LIST.includes(hasher) ? hasher : my.HASHER) verifier.update(this.hash(data)).end() // end() 在 nodejs 12 里返回verifier自身,但在浏览器里返回 undefined,因此不能串联运行。 let verified = verifier.verify(pubkeyPEM, signature, 'hex') // 如果给signature添加1位hex,crypto 的 verify结果也是true! 估计因为一位hex不被转成字节。但减少1位会导致false // since nodejs 12, 有了 crypto.verify 方法,但在浏览器中无效: - // let verified = crypto.verify(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER, Buffer.from(this.hash(data)), pubkeyPEM, Buffer.from(signature, 'hex')) + // let verified = crypto.verify(my.HASHER_LIST.includes(hasher) ? hasher : my.HASHER, Buffer.from(this.hash(data)), pubkeyPEM, Buffer.from(signature, 'hex')) return verified } } @@ -362,7 +362,7 @@ class TICrypto { static pass2keypair (pass, { hasher } = {}) { // 如果使用其他机制,例如密码、随机数,不使用secword,也可生成keypair if (this.isHashable(pass)) { - hasher = my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER + hasher = my.HASHER_LIST.includes(hasher) ? hasher : my.HASHER var hashBuf = crypto .createHash(hasher) .update(pass) @@ -424,11 +424,11 @@ class TICrypto { // 据测试, Purpose和CoinType都可以任意其他值,不必要如规范所示;' 引号可有可无,导致的密钥不一样; // Account 最大为 0x7FFFFFFF, Change/Index 最大均为 0xFFFFFFFF(=4294967295) // 但可以不断延伸下去:/xxx/xxx/xxx/xxx/... - coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN + coin = coin?.toUpperCase() || my.COIN if (tool === 'nacl') { // 采用自己的算法:bip39算法从secword到种子,hash后用 nacl.sign.keyPair.fromSeed()方法。 - hasher = my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER + hasher = my.HASHER_LIST.includes(hasher) ? hasher : my.HASHER let hashBuf = crypto .createHash(hasher) .update(this.secword2seed(secword, pass)) @@ -447,25 +447,9 @@ class TICrypto { let key = hdmaster if (path === 'master') { key = hdmaster - } else if (!path) { - switch (coin) { - case 'BTC': - key = hdmaster.derive("m/44'/0'/0'/0/0") - break - case 'ETH': - key = hdmaster.derive("m/44'/60'/0'/0/0") - break - case 'EXT': - key = hdmaster.derive("m/44'/60398'/0'/0/0") - break - case 'TIC': - default: - key = hdmaster.derive("m/44'/60000'/0'/0/0") - break - } } else { - // 指定了路径 path,例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1" - key = hdmaster.derive(path) + // 指定了路径 path 例如 "m/0/2147483647'/1" 则用 path;否则调用 seed2path() 来获取默认的根路径 例如 "m/44'/0'/0'/0/0" 或 "m/44'/60'/0'/0/0" + key = hdmaster.derive(path || this.seed2path({ coin })) } return { coin: coin, @@ -482,34 +466,54 @@ class TICrypto { * * @static * @param {*} seed - * @param {string} option [{ coin = 'TIC' }={ coin: 'TIC' }] + * @param {string} option [{ coin = my.COIN }={ coin: my.COIN }] * @return {String} path * @memberof TICrypto */ - static seed2path (seed, { coin = 'TIC' } = { coin: 'TIC' }) { + static seed2path ({ seed, coin = my.COIN } = { coin: my.COIN }) { // 路径规范 BIP44: m/Purpose'/Coin'/Account'/Change/Index, // 但实际上 Purpose, Coin 都可任意定;' 可有可无; // Account/Change/Index 最大到 parseInt(0x7FFFFFFF, 16) // 后面还可继续延伸 /xxx/xxx/xxx/...... - let hash = this.hash(seed, { hasher: 'md5' }) - let part0 = parseInt(hash.slice(0, 6), 16) - let part1 = parseInt(hash.slice(6, 12), 16) - let part2 = parseInt(hash.slice(12, 18), 16) - let part3 = parseInt(hash.slice(18, 24), 16) - let part4 = parseInt(hash.slice(24, 30), 16) - let part5 = parseInt(hash.slice(30, 32), 16) - let path = `${part0}'/${part1}/${part2}/${part3}/${part4}/${part5}` - switch (coin.toUpperCase()) { - case 'BTC': - return `m/44'/0'/${path}` - case 'ETH': - return `m/44'/60'/${path}` - case 'EXT': - return `m/44'/60398'/${path}` - case 'TIC': - default: - return `m/44'/60000'/${path}` + let path + if (seed) { + let hash = this.hash(seed, { hasher: 'md5' }) + let part0 = parseInt(hash.slice(0, 6), 16) + let part1 = parseInt(hash.slice(6, 12), 16) + let part2 = parseInt(hash.slice(12, 18), 16) + let part3 = parseInt(hash.slice(18, 24), 16) + let part4 = parseInt(hash.slice(24, 30), 16) + let part5 = parseInt(hash.slice(30, 32), 16) + path = `${part0}'/${part1}/${part2}/${part3}/${part4}/${part5}` + } else { + path = "0'/0/0" } + coin = coin.toUpperCase() || my.COIN + if (coin === 'BTC') { + return `m/44'/0'/${path}` + } else if (coin === 'ETH') { + return `m/44'/60'/${path}` + } else if (coin === 'TIC' || !coin) { + return `m/44'/60000'/${path}` + } else if (/[A-Z]{3}/.test(coin)) { + return `m/44'/60${this.alpha_to_digit(coin)}'/${path}` + } else { + return '' + } + } + + static alpha_to_digit (name = '') { + let digits = name + .toLowerCase() + .replace(/[abc]/g, 2) + .replace(/[def]/g, 3) + .replace(/[ghi]/g, 4) + .replace(/[jkl]/g, 5) + .replace(/[mno]/g, 6) + .replace(/[pqrs]/g, 7) + .replace(/[tuv]/g, 8) + .replace(/[wxyz]/g, 9) + return parseInt(digits) } /** @@ -523,7 +527,7 @@ class TICrypto { */ static secword2account (secword, { coin, pass, path, tool, hasher } = {}) { // account 比 keypair 多了 address 字段。 - coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN + coin = coin?.toUpperCase() || my.COIN let kp = this.secword2keypair(secword, { coin, pass, path, tool, hasher }) if (kp) { if (coin === 'ETH') { @@ -547,7 +551,7 @@ class TICrypto { * @memberof TICrypto */ static secword2address (secword, { coin, world, pass, path, tool, hasher } = {}) { - coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN + coin = coin?.toUpperCase() || my.COIN let kp = this.secword2keypair(secword, { coin, pass, path, tool, hasher }) if (kp) { let address @@ -573,7 +577,7 @@ class TICrypto { 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 + curve = my.CURVE_LIST.includes(curve) ? curve : my.CURVE // 默认为 secp256k1 // return new crypto.createECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex', compress===false?'uncompressed':'compressed') // ecdh.getPublicKey(不加参数) 默认为 'compressed'。用 HBuilderX 2.6.4 打包成ios或安卓 app 后 setPrivateKey() 报错:TypeError: null is not an object (evaluating 'this.rand.getBytes') // 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。但可惜,浏览器里不存在 crypto.ECDH。 return this.buf_to_hex(secp256k1.publicKeyCreate(Buffer.from(seckey, 'hex'), compress !== false)) // 可用于浏览器。缺省输出压缩公钥,compress=false时输出非压缩公钥。 @@ -603,7 +607,7 @@ class TICrypto { * @memberof TICrypto */ static seckey2address (seckey, { coin, world } = {}) { - coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN + coin = coin?.toUpperCase() || my.COIN if (this.isSeckey(seckey)) { /** @type {*} */ let pubkey @@ -630,7 +634,7 @@ class TICrypto { */ static pubkey2position (pubkey, { coin } = {}) { // tic, btc, eth 的 position 都是 20节=40字符的。 - coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN + coin = coin?.toUpperCase() || my.COIN if (this.isPubkey(pubkey)) { if (coin === 'ETH') { // 注意,必须要用非压缩的64字节的公钥的buffer,并去掉开头的 04。 @@ -667,8 +671,7 @@ class 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 + coin = coin?.toUpperCase() || my.COIN let address if (coin === 'ETH') { // 对以太坊,按照 EIP55,把纯位置转换为大小写敏感能自我验证的hex地址。仍然为20节=40符。 @@ -809,7 +812,7 @@ class TICrypto { */ static pubkey2address (pubkey, { coin, world } = {}) { // pubkey 应当是string类型 - coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN + coin = coin?.toUpperCase() || my.COIN return this.position2address(this.pubkey2position(pubkey, { coin }), { coin, world }) } @@ -839,9 +842,9 @@ class TICrypto { static randomSecword (lang = 'english') { // accepts case-insensitive lang, such as 'chinese, cn, tw, en' //// for BitcoreMnemonic - // lang=lang.toUpperCase() + // lang=lang?.toUpperCase() // let language = { ZHCN: 'CHINESE', ENUS: 'ENGLISH', FRFR: 'FRENCH', ITIT: 'ITALIAN', JAJP: 'JAPANESE', KOKR: 'KOREAN', ESES: 'SPANISH' }[lang] - // || (BitcoreMnemonic.Words.hasOwnProperty(lang.toUpperCase()) ? lang.toUpperCase() : 'ENGLISH') + // || (BitcoreMnemonic.Words.hasOwnProperty(lang?.toUpperCase()) ? lang?.toUpperCase() : 'ENGLISH') // return new BitcoreMnemonic(BitcoreMnemonic.Words[language]).phrase // for bip39 @@ -882,7 +885,6 @@ class TICrypto { */ static randomSeckey ({ coin, tool } = {}) { // 跳过 secword 直接产生随机密钥 - coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin : my.COIN if (tool === 'nacl') { return crypto.randomBytes(64).toString('hex') // Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节 } else { @@ -1017,7 +1019,7 @@ class TICrypto { static getMerkleHash (hashList, { output, hasher } = {}) { // merkle算法略有难度,暂时用最简单的hash代替 if (Array.isArray(hashList)) { - myhasher = crypto.createHash(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER) + myhasher = crypto.createHash(my.HASHER_LIST.includes(hasher) ? hasher : my.HASHER) for (var hash of hashList) { myhasher.update(hash) } @@ -1477,29 +1479,33 @@ class TICrypto { const multicodec = { dagpb: '70', p2pkey: '72', - raw: '55' + raw: '55', } const multialgo = { identify: '00', - sha256: '12' + sha256: '12', } if (cidVersion === 0) { - return this.hex_to_b58(`${multialgo[cidAlgo]}${Number(cosh.length/2).toString(16)}${cosh}`) + 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') { + 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' }) + return this.cosh_to_cid({ cosh: this.hash(str), cidVersion: 1, cidCodec: 'raw' }) } - } // 必须单独写 module.exports,不要和类定义写在一起,否则会导致 jsdoc 解析不到类内文档。