From d54cc58c80d72a941e62c3036869f90cdedd3531 Mon Sep 17 00:00:00 2001 From: "luk.lu" Date: Tue, 16 Aug 2022 12:50:24 +0800 Subject: [PATCH] u --- README.horizontal.drawio | 4 +-- ticc.js | 70 +++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/README.horizontal.drawio b/README.horizontal.drawio index 9c2cb86..1af10ad 100644 --- a/README.horizontal.drawio +++ b/README.horizontal.drawio @@ -213,7 +213,7 @@ - + @@ -222,7 +222,7 @@ - + diff --git a/ticc.js b/ticc.js index c54f51c..b070a2a 100644 --- a/ticc.js +++ b/ticc.js @@ -31,6 +31,7 @@ my.INPUT = 'utf8' // 默认的加密方法的明文格式。utf8 能够兼容 la my.INPUT_LIST = ['utf8', 'ascii', 'latin1'] // ignored for Buffer/TypedArray/DataView my.COIN = 'TIC' // 默认的币种 my.COIN_LIST = ['TIC', 'BTC', 'ETH'] +my.WORLD = 'COMET' my.REGEXP_ALPHABET = { hex: /^[0-9a-fA-F]+$/, b32: /^[A-Za-z2-7=]+$/, @@ -142,7 +143,7 @@ class TicCrypto { * @return {Boolean} * @memberof TicCrypto */ - static is_seckey ({ prikey } = {}) { + static is_prikey ({ prikey } = {}) { // 比特币、以太坊的私钥:64 hex // nacl.sign 的私钥 128 hex, nacl.box 的私钥 64 hex return /^([a-fA-F0-9]{128}|[a-fA-F0-9]{64})$/.test(prikey) @@ -230,8 +231,8 @@ class TicCrypto { } } else if (keytype === 'prikey') { // 尚未走通,不能使用 ticc 生成的 Elliptic curve 椭圆曲线算法公私钥,只能用 crypto.generateKeyPairSync('rsa') 生成的 rsa 公私钥 - let seckeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 - return crypto.privateEncrypt(seckeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果都一样。 + let prikeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 + return crypto.privateEncrypt(prikeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果都一样。 } else if (keytype === 'pubkey') { let pubkeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') return crypto.publicEncrypt(pubkeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果不一样。 @@ -274,8 +275,8 @@ class TicCrypto { } } else if (keytype === 'prikey') { // 尚未走通,不能使用 ticc 生成的 Elliptic curve 椭圆曲线算法公私钥 - let seckeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 - return crypto.privateDecrypt(seckeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果都一样。 + let prikeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 + return crypto.privateDecrypt(prikeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果都一样。 } else if (keytype === 'pubkey') { let pubkeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') return crypto.publicDecrypt(pubkeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果不一样。 @@ -295,24 +296,24 @@ class TicCrypto { */ static async sign_easy ({ data, prikey, tool = 'crypto', hasher = my.HASHER }) { // data can be string or buffer or object, results are the same - if (this.is_hashable({ data }) && this.is_seckey({ prikey })) { + if (this.is_hashable({ data }) && this.is_prikey({ prikey })) { if (tool === 'nacl' && prikey.length === 128) { - // 使用nacl的签名算法。注意,nacl.sign需要的seckey是64字节=128字符。 + // 使用 nacl 的签名算法。注意,nacl.sign 需要的 prikey 是64字节=128字符。 let hashBuf = this.hash_easy(data, { output: 'buf' }) // 哈希必须输出为 buffer let signature = nacl.sign.detached(hashBuf, Buffer.from(prikey, 'hex')) return Buffer.from(signature).toString('hex') // 签名是64节,128个hex字符 } else if (tool === 'eccrypto' && prikey.length === 64) { - // eccrypto 对同一组data,seckey生成的签名是固定的,观察到hex长度为140或142,是der格式。 + // eccrypto 对同一组data, prikey 生成的签名是固定的,观察到hex长度为140或142,是der格式。 let signature = await eccrypto.sign(Buffer.from(prikey, 'hex'), this.hash_easy(data, { output: 'buf' })) return signature.toString('hex') } else if (prikey.length === 64) { // 纯 crypto - let seckeyPEM = await new keyman.Key('oct', this.hex_to_buf(prikey), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 + let prikeyPEM = await new keyman.Key('oct', this.hex_to_buf(prikey), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 let signer = crypto.createSign(hasher) // 注意,不知为何,hasher必须含有'sha'才能完成签名,例如 sha1, sha256, sha512, sha3, RSA-SHA1, id-rsassa-pkcs1-v1_5-with-sha3-224, 其他都会报错。 signer.update(this.hash_easy(data)).end() - let signature = signer.sign(seckeyPEM, 'hex') + let signature = signer.sign(prikeyPEM, 'hex') // since nodejs 12, 有了 crypto.sign 方法,但在浏览器中无效: - // let signature = crypto.sign(hasher, Buffer.from(this.hash_easy(data)), seckeyPEM).toString('hex') + // let signature = crypto.sign(hasher, Buffer.from(this.hash_easy(data)), prikeyPEM).toString('hex') return signature // 发现同样的输入,nodejs里每次调用会生成不同的 signature, 且长度不定(140,142,144 hex) 但都可以通过 verify。但在浏览器里调用,signature却是固定的。 } } @@ -441,7 +442,7 @@ class TicCrypto { // 但可以不断延伸下去:/xxx/xxx/xxx/xxx/... coin = coin?.toUpperCase?.() || my.COIN - if (!this.is_secword(secword)) { + if (!this.is_secword({ secword })) { // 由于 secword_to_seed 可以对一切字符串都正常返回,为防止secword为空,在这里先做检查。 return null } @@ -550,15 +551,20 @@ class TicCrypto { * @return {Object} * @memberof TicCrypto */ - static secword_to_account ({ secword, coin = my.COIN, world, pass, pathRoot, pathIndex, path, tool, hasher } = {}) { + static secword_to_account ({ secword, coin, world, pass, pathRoot, pathIndex, path, tool, hasher } = {}) { // account 比 keypair 多了 address 字段。 coin = coin?.toUpperCase?.() || my.COIN let kp = this.secword_to_keypair({ secword, coin, pass, pathRoot, pathIndex, path, tool, hasher }) if (kp) { if (coin === 'ETH') { + world = world || 'mainnet' let uncompressedPubkey = this.decompress_pubkey(kp.pubkey) kp.address = this.pubkey_to_address({ pubkey: uncompressedPubkey, coin: 'ETH', world }) + } else if (coin === 'BTC') { + world = world || 'mainnet' + kp.address = this.pubkey_to_address({ pubkey: kp.pubkey, coin, world }) } else { + world = world || my.WORLD kp.address = this.pubkey_to_address({ pubkey: kp.pubkey, coin, world }) } return Object.assign(kp, { coin, world, secword }) @@ -575,19 +581,9 @@ class TicCrypto { * @return {String} address * @memberof TicCrypto */ - static secword_to_address ({ secword, coin = my.COIN, world, pass, pathRoot, pathIndex, path, tool, hasher } = {}) { - coin = coin?.toUpperCase?.() || my.COIN - let kp = this.secword_to_keypair({ secword, coin, pass, pathRoot, pathIndex, path, tool, hasher }) - if (kp) { - let address - if (coin === 'ETH') { - address = this.pubkey_to_address({ pubkey: this.decompress_pubkey(kp.pubkey), coin: 'ETH', world }) - } else { - address = this.pubkey_to_address({ pubkey: kp.pubkey, coin, world }) - } - return address - } - return null + static secword_to_address ({ secword, coin, world, pass, pathRoot, pathIndex, path, tool, hasher } = {}) { + const account = this.secword_to_account({ secword, coin, world, pass, pathRoot, pathIndex, path, tool, hasher }) + return account?.address } /** @@ -599,8 +595,8 @@ class TicCrypto { * @return {*} * @memberof TicCrypto */ - static seckey_to_pubkey ({ prikey, curve, compress } = {}) { - if (this.is_seckey({ prikey }) && prikey.length === 64) { + static prikey_to_pubkey ({ prikey, curve, compress } = {}) { + if (this.is_prikey({ prikey }) && prikey.length === 64) { // 只能用于32字节的私钥(BTC, ETH)。也就是不能用于 TIC 的私钥。 curve = my.CURVE_LIST.includes(curve) ? curve : my.CURVE // 默认为 secp256k1 // return new crypto.createECDH(curve).setPrivateKey(prikey,'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') @@ -614,7 +610,7 @@ class TicCrypto { // return ecc.getPublicCompressed(this.hex_to_buf(prikey)).toString('hex') // } // 注意,Buffer.from(nacl.box.keyPair.fromSecretKey(Buffer.from(prikey,'hex')).publicKey).toString('hex') 得到的公钥与上面的不同 - } else if (this.is_seckey({ prikey }) && prikey.length === 128) { + } else if (this.is_prikey({ prikey }) && prikey.length === 128) { // 用于64字节=128 hex的 TIC 私钥 let keypair = nacl.sign.keyPair.fromSecretKey(Buffer.from(prikey, 'hex')) return Buffer.from(keypair.publicKey).toString('hex') // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 @@ -631,16 +627,16 @@ class TicCrypto { * @return {*} * @memberof TicCrypto */ - static seckey_to_address ({ prikey, coin, world } = {}) { + static prikey_to_address ({ prikey, coin, world } = {}) { coin = coin?.toUpperCase() || my.COIN - if (this.is_seckey({ prikey })) { + if (this.is_prikey({ prikey })) { /** @type {*} */ let pubkey if (coin === 'ETH') { - pubkey = this.seckey_to_pubkey({ prikey, compress: false }) + pubkey = this.prikey_to_pubkey({ prikey, compress: false }) return this.pubkey_to_address({ pubkey: pubkey, coin, world }) } else { - pubkey = this.seckey_to_pubkey({ prikey, compress: true }) + pubkey = this.prikey_to_pubkey({ prikey, compress: true }) return this.pubkey_to_address({ pubkey: pubkey, coin, world }) } } @@ -696,7 +692,7 @@ class TicCrypto { */ static position_to_address ({ position, coin, world } = {}) { if (!/^[\da-fA-F]{40}$/.test(position)) return null // 不论 tic, btc, eth,其 position 都是 40字符的。 - coin = coin?.toUpperCase() || my.COIN + coin = coin?.toUpperCase?.() || my.COIN let address if (coin === 'ETH') { // 对以太坊,按照 EIP55,把纯位置转换为大小写敏感能自我验证的hex地址。仍然为20节=40符。 @@ -757,7 +753,7 @@ class TicCrypto { prefix = '74' break // Base58: 0x90 => d, Base 64: d=0x1d=0b00011101 => 0b 011101xx = 0x74~77 default: - prefix = '4c' + prefix = '74' } let checksum = this.hash_easy(this.hash_easy(prefix + position)).slice(0, 6) // 添加 checksum 使得能够检测大小写错误。[todo] 校验码里要不要包含 prefix? // address = this.hex_to_eip55(prefix + position + checksum) // 前缀1节,位置20节,校验3节,共24节=48字符(能够完全转化为8个色彩),再转eip55。 @@ -837,7 +833,7 @@ class TicCrypto { */ static pubkey_to_address ({ pubkey, coin, world } = {}) { // pubkey 应当是string类型 - coin = coin?.toUpperCase() || my.COIN + coin = coin?.toUpperCase?.() || my.COIN return this.position_to_address({ position: this.pubkey_to_position({ pubkey, coin }), coin, world }) } @@ -920,7 +916,7 @@ class TicCrypto { } } else { let prikey = this.randomize_seckey() - let pubkey = this.seckey_to_pubkey({ prikey }) + let pubkey = this.prikey_to_pubkey({ prikey }) return { prikey, pubkey,