diff --git a/index.js b/index.js index cabaaae..9c7150f 100644 --- a/index.js +++ b/index.js @@ -17,11 +17,11 @@ const secp256k1 = require('secp256k1') const my = {} my.HASHER = 'sha256' // 默认的哈希算法。could be md5, sha1, sha256, sha512, ripemd160 and much more。 可用 Crypto.getHashes/Ciphers/Curves() 查看支持的种类。 -my.HASHER_LIST = crypto.getHashes() +my.HASHER_LIST = typeof crypto.getHashes === 'function' ? crypto.getHashes() : [my.HASHER] my.CIPHER = 'aes-256-cfb' // 默认的加解密算法 -my.CIPHER_LIST = crypto.getCiphers() +my.CIPHER_LIST = typeof crypto.getCiphers === 'function' ? crypto.getCiphers() : [my.CIPHER] my.CURVE = 'secp256k1' // 默认的ECDH曲线,用于把私钥转成公钥。 -my.CURVE_LIST = ['secp256k1'] // crypto.getCurves() 引入到浏览器里后出错,不支持 getCurves. +my.CURVE_LIST = typeof crypto.getCurves === 'function' ? crypto.getCurves() : [my.CURVE] // crypto.getCurves() 引入到浏览器里后出错,不支持 getCurves. my.OUTPUT = 'hex' // 默认的哈希或加密的输入格式 my.OUTPUT_LIST = ['hex', 'latin1', 'base64'] // or 'buf' to Buffer explicitly my.INPUT = 'utf8' // 默认的加密方法的明文格式。utf8 能够兼容 latin1, ascii 的情形 @@ -30,7 +30,6 @@ my.COIN = 'TIC' // 默认的币种 my.COIN_LIST = ['TIC', 'BTC', 'ETH'] /** - * * * @class TICrypto */ @@ -160,8 +159,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(option.input)>=0?option.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) // option.output: 留空=》默认输出hex格式;或者手动指定 'buf', hex', 'latin1' or 'base64' + 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 null @@ -280,9 +279,11 @@ class TICrypto { } else if (seckey.length === 64) { // 纯 crypto let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 - let signer = crypto.createSign(['sha1', 'sha256', 'sha512'].indexOf(hasher) >= 0 ? hasher : my.HASHER) - signer.update(this.hash(data)).end() - let signature = signer.sign(seckeyPEM, 'hex') + // 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, 其他都会报错。 + // 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') return signature // 发现同样的输入,nodejs里每次调用会生成不同的 signature, 且长度不定(140,142,144 hex) 但都可以通过 verify。但在浏览器里调用,signature却是固定的。 } } @@ -310,7 +311,7 @@ class TICrypto { let verified = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey) return verified } else if ('eccrypto' === tool && signature.length >= 140) { - // 默认使用 eccrypto + // 默认使用 eccrypto // 发现大小写不影响 eccrypto 验签!都能通过 try { let result = await eccrypto.verify(Buffer.from(pubkey, 'hex'), this.hash(data, { output: 'buf' }), Buffer.from(signature, 'hex')) // 如果给signature添加1位hex,eccrypto 的 verify结果也是true! 估计因为一位hex不被转成字节。 return true @@ -319,11 +320,18 @@ class TICrypto { return false } } else if (signature.length >= 140) { - // 纯 crypto + // 纯 crypto // 发现大小写不影响 crypto 验签!都能通过 let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), { namedCurve: 'P-256K' }).export('pem') // 公钥导出的der格式为88字节。经测试,同一对压缩和非压缩公钥得出的结果一模一样。 - let verifier = crypto.createVerify(['sha1', 'sha256', 'sha512'].indexOf(hasher) >= 0 ? 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 + // let verifier = crypto.createVerify(my.HASHER_LIST.indexOf(hasher) >= 0 ? 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') + ) return verified } } @@ -339,12 +347,11 @@ class TICrypto { * @return {Object} {pubkey, seckey, address,} * @memberof TICrypto */ - static pass2keypair(pass, option) { + static pass2keypair(pass, { hasher } = {}) { // 如果使用其他机制,例如密码、随机数,不使用secword,也可生成keypair if (this.isHashable(pass)) { - option = option || {} - option.hasher = my.HASHER_LIST.indexOf(option.hasher) >= 0 ? option.hasher : my.HASHER - var hashBuf = crypto.createHash(option.hasher).update(pass).digest() + hasher = my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER + var hashBuf = crypto.createHash(hasher).update(pass).digest() var keypair = nacl.sign.keyPair.fromSeed(hashBuf) // nacl的seed要求是32字节 return { hash: hashBuf.toString('hex'), @@ -390,10 +397,10 @@ class TICrypto { * @return {Object} {pubkey, seckey,} * @memberof TICrypto */ - static secword2keypair(secword, option) { - // option.coin 币种; - // option.passphase 密码,默认为空; - // option.path==='master' 生成 HD master key,不定义则默认为相应币种的第一对公私钥。 + static secword2keypair(secword, { coin, pass, path, tool, hasher } = {}) { + // coin 币种; + // passphase 密码,默认为空; + // path==='master' 生成 HD master key,不定义则默认为相应币种的第一对公私钥。 // path 规范为 m/Purpose'/CoinType'/Account'/Change/Index (https://learnblockchain.cn/2018/09/28/hdwallet/), 其中 // Purpose===44 for BIP44, // CoinType===0 for BTC, 60 for ETH. (https://github.com/satoshilabs/slips/blob/master/slip-0044.md) @@ -402,30 +409,28 @@ class TICrypto { // 据测试, Purpose和CoinType都可以任意其他值,不必要如规范所示;' 引号可有可无,导致的密钥不一样; // Account 最大为 0x7FFFFFFF, Change/Index 最大均为 0xFFFFFFFF(=4294967295) // 但可以不断延伸下去:/xxx/xxx/xxx/xxx/... - option = option || {} - if (option.coin) option.coin = option.coin.toUpperCase() - option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN + coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN - if (option.tool === 'nacl') { + if (tool === 'nacl') { // 采用自己的算法:bip39算法从secword到种子,hash后用 nacl.sign.keyPair.fromSeed()方法。 - option.hasher = my.HASHER_LIST.indexOf(option.hasher) >= 0 ? option.hasher : my.HASHER - let hashBuf = crypto.createHash(option.hasher).update(this.secword2seed(secword, option.pass)).digest() + hasher = my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER + 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: option.coin, + coin: coin, secword: secword, pubkey: Buffer.from(keypair.publicKey).toString('hex'), // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 seckey: Buffer.from(keypair.secretKey).toString('hex'), // nacl.sign.keyPair.fromSeed 得到的 seckey 是64字节的,不同于比特币/以太坊的32字节密钥。 } } else { // 用 bip39 算法从 secword 到种子,再用 bip32 算法从种子到根私钥。这是比特币、以太坊的标准方式,结果一致。 - let hdmaster = hdkey.fromMasterSeed(Buffer.from(this.secword2seed(secword, option.pass), 'hex')) // 和 new BitcoreMnemonic(secword).toHDPrivateKey 求出的公私钥一样! - // let hdmaster=new BitcoreMnemonic(secword).toHDPrivateKey(option.pass) // 和 ethers.HDNode.fromMnemonic(secword)的公私钥一样。而 ethers.HDNode.fromMnemonic(secword).derivePath("m/44'/60'/0'/0/0")的公私钥===ethers.Wallet.fromMnemonic(secword [,"m/44'/60'/0'/0/0"]) + let hdmaster = hdkey.fromMasterSeed(Buffer.from(this.secword2seed(secword, pass), 'hex')) // 和 new BitcoreMnemonic(secword).toHDPrivateKey 求出的公私钥一样! + // let hdmaster=new BitcoreMnemonic(secword).toHDPrivateKey(pass) // 和 ethers.HDNode.fromMnemonic(secword)的公私钥一样。而 ethers.HDNode.fromMnemonic(secword).derivePath("m/44'/60'/0'/0/0")的公私钥===ethers.Wallet.fromMnemonic(secword [,"m/44'/60'/0'/0/0"]) let key = hdmaster - if (option.path === 'master') { + if (path === 'master') { key = hdmaster - } else if (!option.path) { - switch (option.coin) { + } else if (!path) { + switch (coin) { case 'BTC': key = hdmaster.derive("m/44'/0'/0'/0/0") break @@ -438,11 +443,11 @@ class TICrypto { break } } else { - // 指定了路径 option.path,例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1" - key = hdmaster.derive(option.path) + // 指定了路径 path,例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1" + key = hdmaster.derive(path) } return { - coin: option.coin, + coin: coin, secword: secword, seckey: key.privateKey.toString('hex'), // 或者 key.toJSON().privateKey。或者 key.privateKey.slice(2) 删除开头的'0x'如果是ethers.HDNode.fromMnemonic(secword)的结果 pubkey: key.publicKey.toString('hex'), @@ -493,18 +498,16 @@ class TICrypto { * @return {Object} * @memberof TICrypto */ - static secword2account(secword, option) { + static secword2account(secword, { coin, pass, path, tool, hasher } = {}) { // account 比 keypair 多了 address 字段。 - option = option || {} - if (option.coin) option.coin = option.coin.toUpperCase() - option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN - let kp = this.secword2keypair(secword, option) + coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN + let kp = this.secword2keypair(secword, { coin, pass, path, tool, hasher }) if (kp) { - if (option.coin === 'ETH') { + if (coin === 'ETH') { let uncompressedPubkey = this.decompressPubkey(kp.pubkey) kp.address = this.pubkey2address(uncompressedPubkey, { coin: 'ETH' }) } else { - kp.address = this.pubkey2address(kp.pubkey, option) + kp.address = this.pubkey2address(kp.pubkey, { coin }) } return kp } @@ -520,17 +523,15 @@ class TICrypto { * @return {String} address * @memberof TICrypto */ - static secword2address(secword, option) { - option = option || {} - if (option.coin) option.coin = option.coin.toUpperCase() - option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN - let kp = this.secword2keypair(secword, option) + static secword2address(secword, { coin, 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) { let address - if (option.coin === 'ETH') { + if (coin === 'ETH') { address = this.pubkey2address(this.decompressPubkey(kp.pubkey), { coin: 'ETH' }) } else { - address = this.pubkey2address(kp.pubkey, option) + address = this.pubkey2address(kp.pubkey, { coin }) } return address } @@ -546,18 +547,16 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static seckey2pubkey(seckey, option = {}) { - if (option.coin) option.coin = option.coin.toUpperCase() - option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN + static seckey2pubkey(seckey, { curve, compress } = {}) { if (this.isSeckey(seckey) && seckey.length === 64) { // 只能用于32字节的私钥(BTC, ETH)。也就是不能用于 TIC 的私钥。 - let curve = my.CURVE_LIST.indexOf(option.curve) >= 0 ? option.curve : my.CURVE // 默认为 secp256k1 - // return new crypto.createECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex', option.compress===false?'uncompressed':'compressed') // ecdh.getPublicKey(不加参数) 默认为 'compressed'。用 HBuilderX 2.6.4 打包成ios或安卓 app 后 setPrivateKey() 报错:TypeError: null is not an object (evaluating 'this.rand.getBytes') + curve = my.CURVE_LIST.indexOf(curve) >= 0 ? 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.buf2hex(secp256k1.publicKeyCreate(Buffer.from(seckey, 'hex'), option.compress !== false)) // 可用于浏览器。secp256k1缺省或true时输出压缩公钥,false时输出非压缩公钥。 + return this.buf2hex(secp256k1.publicKeyCreate(Buffer.from(seckey, 'hex'), compress !== false)) // 可用于浏览器。缺省输出压缩公钥,compress=false时输出非压缩公钥。 // 或者 bitcorelib.PublicKey.fromPrivateKey(new bitcorelib.PrivateKey(seckey)).toString('hex') // 可用于浏览器 // 或者 const ecc = require('eccrypto') - // if (option.compress===false){ + // if (compress===false){ // return ecc.getPublic(this.hex2buf(seckey)).toString('hex') // }else{ // return ecc.getPublicCompressed(this.hex2buf(seckey)).toString('hex') @@ -580,19 +579,17 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static seckey2address(seckey, option) { - option = option || {} - if (option.coin) option.coin = option.coin.toUpperCase() - option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN + static seckey2address(seckey, { coin } = {}) { + coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN if (this.isSeckey(seckey)) { /** @type {*} */ let pubkey - if (option.coin === 'ETH') { + if (coin === 'ETH') { pubkey = this.seckey2pubkey(seckey, { compress: false }) - return this.pubkey2address(pubkey, option) + return this.pubkey2address(pubkey, { coin }) } else { pubkey = this.seckey2pubkey(seckey, { compress: true }) - return this.pubkey2address(pubkey, option) + return this.pubkey2address(pubkey, { coin }) } } return null @@ -609,8 +606,7 @@ class TICrypto { */ static pubkey2position(pubkey, { coin } = {}) { // tic, btc, eth 的 position 都是 20节=40字符的。 - if (coin) coin = coin.toUpperCase() - coin = my.COIN_LIST.indexOf(coin) >= 0 ? coin : my.COIN + coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN if (this.isPubkey(pubkey)) { if (coin === 'ETH') { // 注意,必须要用非压缩的64字节的公钥的buffer,并去掉开头的 04。 @@ -774,11 +770,10 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static pubkey2address(pubkey, option = {}) { + static pubkey2address(pubkey, { coin } = {}) { // pubkey 应当是string类型 - if (option.coin) option.coin = option.coin.toUpperCase() - option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN - return this.position2address(this.pubkey2position(pubkey, option), option) + coin = my.COIN_LIST.indexOf(coin?.toUpperCase()) >= 0 ? coin.toUpperCase() : my.COIN + return this.position2address(this.pubkey2position(pubkey, { coin }), { coin }) } /** @@ -847,11 +842,10 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomSeckey(option = {}) { + static randomSeckey({ coin, tool } = {}) { // 跳过 secword 直接产生随机密钥 - if (option.coin) option.coin = option.coin.toUpperCase() - option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN - if (option.tool === 'nacl') { + 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 { return crypto.randomBytes(32).toString('hex') // Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节 @@ -866,12 +860,10 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomKeypair(option = {}) { - if (option.coin) option.coin = option.coin.toUpperCase() - option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN + static randomKeypair({ tool, purpose } = {}) { let kp - if (option.tool === 'nacl') { - if (option.purpose === 'encrypt') { + if (tool === 'nacl') { + if (purpose === 'encrypt') { kp = nacl.box.keyPair() } else { kp = nacl.sign.keyPair() @@ -898,9 +890,9 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static randomAccount(option = {}) { - let secword = this.randomSecword(option.lang) - return this.secword2account(secword, option) + static randomAccount({ lang, coin, pass, path, tool, hasher } = {}) { + let secword = this.randomSecword(lang) + return this.secword2account(secword, { coin, pass, path, tool, hasher }) } /** @@ -984,15 +976,14 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static getMerkleHash(hashList, option = {}) { + static getMerkleHash(hashList, { output, hasher } = {}) { // merkle算法略有难度,暂时用最简单的hash代替 if (Array.isArray(hashList)) { - let output = option.output === 'buf' ? undefined : option.output || my.OUTPUT - let hasher = crypto.createHash(my.HASHER_LIST.indexOf(option.hasher) >= 0 ? option.hasher : my.HASHER) + myhasher = crypto.createHash(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER) for (var hash of hashList) { - hasher.update(hash) + myhasher.update(hash) } - return hasher.digest(output) + return myhasher.digest(output === 'buf' ? undefined : output || my.OUTPUT) } return null } @@ -1006,7 +997,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static getMerkleRoot(todoHashList, option) { + static getMerkleRoot(todoHashList) { //深拷贝传入数组,防止引用对象被改变 let hashList = [...todoHashList] if (!Array.isArray(hashList)) return null