diff --git a/ticc.js b/ticc.js index 417a5be..4eee4c4 100644 --- a/ticc.js +++ b/ticc.js @@ -1,5 +1,5 @@ // const bignum=require('bignumber.js') // 处理整数 https://github.com/MikeMcl/bignumber.js // size: 360K -const bigint = require('big-integer') // 处理整数 https://github.com/peterolson/BigInteger.js // size: 188K. ethers.js 24M. // 20241005 发现,在 node 控制台里,导入本库命名为 BigInt 后运行 BigInt(xxx) 会导致失败!因为现在已经有 JS 的新 primitive 也叫 BigInt, 不知道作为 server 运行时,怎么没有报错。改名为 bigint +// const bigint = require('big-integer') // 处理整数 https://github.com/peterolson/BigInteger.js // size: 188K. ethers.js 24M. // 20241005 发现,在 node 控制台里,导入本库命名为 BigInt 后运行 BigInt(xxx) 会导致失败!因为现在已经有 JS 的新 primitive 也叫 BigInt, 不知道作为 server 运行时,怎么没有报错。改名为 bigint const crypto = require('crypto') const nacl = require('tweetnacl') const bs58check = require('bs58check') @@ -611,19 +611,19 @@ class TicCrypto { * @return {*} * @memberof TicCrypto */ - static prikey_to_pubkey ({ prikey, curve, compress } = {}) { + static prikey_to_pubkey ({ prikey, curve, uncompress } = {}) { 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') + // return new crypto.createECDH(curve).setPrivateKey(prikey,'hex').getPublicKey('hex', uncompress?'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(prikey, 'hex'), compress !== false)) // 可用于浏览器。缺省输出压缩公钥,compress=false时输出非压缩公钥。 + return this.buf_to_hex(secp256k1.publicKeyCreate(Buffer.from(prikey, 'hex'), !uncompress)) // 可用于浏览器。缺省输出压缩公钥,第二个参数必须正好为false时输出非压缩公钥,为undefined或true时输出压缩公钥,其他时报错。 // 或者 bitcorelib.PublicKey.fromPrivateKey(new bitcorelib.PrivateKey(prikey)).toString('hex') // 可用于浏览器 // 或者 const ecc = require('eccrypto') - // if (compress===false){ - // return ecc.getPublic(this.hex_to_buf(prikey)).toString('hex') - // }else{ + // if (!uncompress){ // return ecc.getPublicCompressed(this.hex_to_buf(prikey)).toString('hex') + // } else{ + // return ecc.getPublic(this.hex_to_buf(prikey)).toString('hex') // } // 注意,Buffer.from(nacl.box.keyPair.fromSecretKey(Buffer.from(prikey,'hex')).publicKey).toString('hex') 得到的公钥与上面的不同 } else if (this.is_prikey({ prikey }) && prikey.length === 128) { @@ -648,11 +648,12 @@ class TicCrypto { if (this.is_prikey({ prikey })) { /** @type {*} */ let pubkey - if (coin === 'ETH') { - pubkey = this.prikey_to_pubkey({ prikey, compress: false }) + if (coin === 'ETH' || coinFamily === 'ETH') { + pubkey = this.prikey_to_pubkey({ prikey, uncompress: true }) return this.pubkey_to_address({ pubkey, coin, coinFamily, world }) + // 实际上发现,不论是否 compressed,最后转成的地址都是一样的,因为在 pubkey_to_position 里已经自动处理了。 } else { - pubkey = this.prikey_to_pubkey({ prikey, compress: true }) + pubkey = this.prikey_to_pubkey({ prikey, uncompress: false }) return this.pubkey_to_address({ pubkey, coin, coinFamily, world }) } } @@ -924,7 +925,7 @@ class TicCrypto { } } else { let prikey = this.randomize_seckey() - let pubkey = this.prikey_to_pubkey({ prikey }) + let pubkey = this.prikey_to_pubkey({ prikey, uncompress: false }) return { prikey, pubkey, @@ -1413,7 +1414,7 @@ class TicCrypto { * 压缩公钥 * * @static - * @param {*} uncompressed + * @param {*} uncompressed: strings like '0x1234567890abcedf...' * @return {*} * @memberof TicCrypto */ @@ -1434,6 +1435,56 @@ class TicCrypto { return null // 非压缩公钥有错误。 } + /** + * + * decompress_pubkey 需要用到 big-integer 的 modPow 方法。如果想用原生的 BigInt,就需要自己实现 modPow + * @param {*} base + * @param {*} exponent + * @param {*} modulus + * @returns + */ + static modPow (base, exponent, modulus) { + let result = BigInt(1) + base = BigInt(base) + exponent = BigInt(exponent) + modulus = BigInt(modulus) + + while (exponent > 0n) { + if (exponent & BigInt(1)) { + result = (result * base) % modulus + } + base = (base * base) % modulus + exponent = exponent >> BigInt(1) + } + + return result + } + + /** + * 解压缩公钥 + * + * @static + * @param {*} compressed: strings like '020123456789abcdef...' + * @return {*} + * @memberof TicCrypto + */ + static decompress_pubkey (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 = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16) // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + const pIdent = BigInt('0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4); + var signY = BigInt(Number(compressed[1]) - 2) + var x = BigInt('0x' + compressed.substr(2)) + var y = this.modPow((this.modPow(x, 3, prime) + BigInt(7)) % prime, pIdent, prime) // y mod p = +-(x^3 + 7)^((p+1)/4) mod p + if (y % BigInt(2) !== signY) { + // If the parity doesn't match it's the *other* root + y = prime - y + } + return '04' + this.padStart(x.toString(16), 64, '0') + this.padStart(y.toString(16), 64, '0') + } + /** * 解压缩公钥 * @@ -1442,32 +1493,12 @@ class TicCrypto { * @return {*} * @memberof TicCrypto */ - static decompress_pubkey (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 + static decompress_pubkey_bigint (compressed) { + const bigint = globalThis.bigint || require('big-integer') const prime = bigint('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16) // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 const pIdent = bigint('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4); var signY = new Number(compressed[1]) - 2 var x = bigint(compressed.substr(2), 16) - // 需要用到 big-integer 的 modPow 方法。如果想用原生的 BigInt,可以自己实现 modPow: - /* - function modPow(base, exponent, modulus) { - let result = BigInt(1); - base = BigInt(base); - - while (exponent > 0n) { - if (exponent & BigInt(1)) { - result = (result * base) % modulus; - } - base = (base * base) % modulus; - exponent = exponent >> BigInt(1); - } - - return result; - } - */ 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