This commit is contained in:
陆柯 2022-08-16 12:50:24 +08:00
parent 71eb5d5dce
commit d54cc58c80
2 changed files with 35 additions and 39 deletions

View File

@ -213,7 +213,7 @@
<mxCell id="61" value="plaintext ^ 明文" style="whiteSpace=wrap;html=1;rounded=1;fontColor=#ffffff;strokeColor=#005700;fillColor=#008a00;" parent="1" vertex="1"> <mxCell id="61" value="plaintext ^ 明文" style="whiteSpace=wrap;html=1;rounded=1;fontColor=#ffffff;strokeColor=#005700;fillColor=#008a00;" parent="1" vertex="1">
<mxGeometry x="770" y="120" width="120" height="60" as="geometry"/> <mxGeometry x="770" y="120" width="120" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="111" value="seckey_to_pubkey" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="111" value="prikey_to_pubkey" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="680" y="650" width="290" height="30" as="geometry"/> <mxGeometry x="680" y="650" width="290" height="30" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="112" value="secword_to_keypair" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="112" value="secword_to_keypair" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
@ -222,7 +222,7 @@
<mxCell id="113" value="pubkey_to_address" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="113" value="pubkey_to_address" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="970" y="650" width="630" height="30" as="geometry"/> <mxGeometry x="970" y="650" width="630" height="30" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="114" value="seckey_to_address" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="114" value="prikey_to_address" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="680" y="730" width="920" height="30" as="geometry"/> <mxGeometry x="680" y="730" width="920" height="30" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="115" value="secword_to_address, secword_to_account" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="115" value="secword_to_address, secword_to_account" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=#1ba1e2;routingCenterX=-0.5;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">

70
ticc.js
View File

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