diff --git a/index.js b/index.js index 6ec0471..14b14f5 100644 --- a/index.js +++ b/index.js @@ -1,483 +1,483 @@ -const BigNumber=require('bignumber.js') // https://github.com/MikeMcl/bignumber.js 几个库的比较: node-bignum: 使用到openssl,在windows上需要下载二进制包,有时下载失败。bigi: 不错。 bignumber.js:不错。 -const crypto=require('crypto') -const nacl = require('tweetnacl') -const bs58check = require('bs58check') -const uuid = require('uuid') -const Secword = require('bitcore-mnemonic') // https://bitcore.io/api/mnemonic/ https://github.com/bitpay/bitcore-mnemonic -// const bip39 = require('bip39') // https://github.com/bitcoinjs/bip39 // 有更多语言,但不方便选择语言,也不能使用 pass -// const HDKey = require('hdkey') // https://github.com/cryptocoinjs/hdkey // 或者用 bitcore-mnemonic 或者 ethers 里的相同功能 - -// 全部以hex为默认输入输出格式,方便人的阅读,以及方便函数之间统一接口 - -const my={} -my.HASHER='sha256' // 默认的哈希算法。could be md5, sha1, sha256, sha512, ripemd160。 可用 Crypto.getHashes/Ciphers/Curves() 查看支持的种类。 -my.HASHER_LIST=crypto.getHashes() -my.CIPHER='aes-256-cfb' // 默认的加解密算法 -my.CIPHER_LIST=crypto.getCiphers() -my.CURVE='secp256k1' // 默认的ECDH曲线,用于把私钥转成公钥。 -my.CURVE_LIST=['secp256k1'] // crypto.getCurves() 引入到浏览器里后出错,不支持 getCurves. -my.OUTPUT='hex' // 默认的哈希或加密的输入格式 -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','BTC','ETH'] - -module.exports = { - hash:function(data, option){ // data can be anything, but converts to string or remains be Buffer/TypedArray/DataView - if (this.isHashable(data)) { - option=option||{} - if (typeof(data)!=='string' && !(data instanceof Buffer) && !(data instanceof DataView)) - data=JSON.stringify(data) - if (option.salt && typeof(option.salt)==='string') - data=data+this.hash(option.salt) - let hasher= my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER // 默认为 sha256. - let inputEncoding=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=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) // option.output: 留空=》默认输出hex格式;或者手动指定 'buf', hex', 'latin1' or 'base64' - return crypto.createHash(hasher).update(data, inputEncoding).digest(outputEncoding) - } - return null - } - , - isHashable:function(data, option){ - option=option||{} - if (option.strict) { - return data && typeof(data)!=='boolean' && data!==Infinity // 允许大多数数据,除了空值、布尔值、无限数 - } - return typeof(data)!=='undefined' // 允许一切数据,除非 undefined - } - , - isHash:function(hash, option){ - option=option||{} - option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER - switch(option.hasher){ - case 'sha256': return /^[a-fA-F0-9]{64}$/.test(hash) - case 'md5': return /^[a-fA-F0-9]{32}$/.test(hash) - case 'ripemd160': case 'sha1': return /^[a-fA-F0-9]{40}$/.test(hash) - case 'sha512': return /^[a-fA-F0-9]{128}$/.test(hash) - } - return false - } - , - encrypt: function(data, pwd, option){ - if (this.isHashable(data) && typeof(pwd)==='string') { - option=option||{} - let inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView - let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly - let cipher=crypto.createCipher( - my.CIPHER_LIST.indexOf(option.cipher)>=0?option.cipher:my.CIPHER, - this.hash(pwd)) - if (typeof(data)!=='string' && !(data instanceof Buffer) && !(data instanceof DataView)) - data=JSON.stringify(data) - let encrypted = cipher.update(data, inputEncoding, outputEncoding) - encrypted += cipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string - return encrypted - } - return null - } - , - decrypt: function(data, pwd, option){ // data 应当是 encrypt 输出的数据类型 - if (data && (typeof(data)==='string' || data instanceof Buffer) && typeof(pwd)==='string') { - option=option||{} - let inputEncoding=my.OUTPUT_LIST.indexOf(option.input)>=0?option.input:my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer - let outputEncoding=(option.output==='buf')?undefined:(my.INPUT_LIST.indexOf(option.output)>=0?option.output:my.INPUT) // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly - let decipher=crypto.createDecipher( - my.CIPHER_LIST.indexOf(option.cipher)>=0?option.cipher:my.CIPHER, - this.hash(pwd)) - let decrypted = decipher.update(data, inputEncoding, outputEncoding) - decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string - if (option.format==='json') { // 如果用户输入错误密码,deciper也能返回结果。为了判断是否正确结果,对应当是 json 格式的原文做解析来验证。 - try{ - JSON.parse(decrypted) - }catch(exception){ - return null - } - } - return decrypted - } - return null - } - , - sign: function(data, seckey, option) { // data can be string or buffer or object, results are the same - if (this.isHashable(data) && this.isSeckey(seckey)) { - option=option||{} - - // 使用nacl的签名算法。注意,nacl.sign需要的seckey是64字节=512位,而比特币/以太坊的seckey是32字节。因此本方法只能用于 TIC 币的 keypair。 - option.output='buf' // 哈希必须输出为 buffer - var hashBuf = this.hash(data, option) - var signature = nacl.sign.detached(hashBuf, Buffer.from(seckey, 'hex')) - return Buffer.from(signature).toString('hex') // 返回128个hex字符,64字节 - - // 方案2:尚未彻底实现。 - // let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER - // let inputEncoding=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=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) - // let signer=crypto.createSign(hasher) - // return signer.update(data, inputEncoding).sign(seckey, outputEncoding) // todo: crypto的sign要求的seckey必须是PEM格式,因此这样写是不能用的。 - } - return null - } - , - isSignature:function(signature){ - return /^[a-fA-F0-9]{128}$/.test(signature) - } - , - verify: function (data, signature, pubkey, option) { // data could be anything, but converts to string or remains be Buffer/TypedArray/DataView - if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey)){ - option=option||{} - option.output='buf' // 哈希必须输出为 buffer - var bufHash=this.hash(data, option) - var bufSignature = Buffer.from(signature, 'hex') - var bufPubkey = Buffer.from(pubkey, 'hex') - var res = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey) - return res - } - return null - } - , - pass2keypair:function(pass, option){ // 如果使用其他机制,例如密码、随机数,不使用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() - var keypair = nacl.sign.keyPair.fromSeed(hashBuf) - return { - hash: hashBuf.toString('hex'), - pubkey: Buffer.from(keypair.publicKey).toString('hex'), // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 - seckey: Buffer.from(keypair.secretKey).toString('hex') - } - } - return null - } - , - secword2keypair: function(secword, option){ // option.coin 币种;option.passphase 密码,默认为空;option.path==='master' 生成 HD master key,不定义则默认为相应币种的第一对公私钥。 - if (Secword.isValid(secword)){ - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN - - if(option.coin==='TIC') { - // 采用自己的算法: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() - let keypair = nacl.sign.keyPair.fromSeed(hashBuf) // nacl.sign.keyPair.fromSeed 要求32字节的种子,而 this.secword2seed生成的是64字节种子,所以要先做一次sha256 - return { - coin: option.coin, - 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(new Buffer(this.secword2seed(secword, option.pass), 'hex')) // 和 new Secword(secword).toHDPrivateKey 求出的公私钥一样! - let hdmaster=new Secword(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 key=hdmaster - if (option.path==='master'){ - key=hdmaster - }else if (!option.path) { - switch(option.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 - default: key=hdmaster.derive("m/44'/99'/0'/0/0"); break - } - }else { // 指定了路径 option.path,例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1" - key=hdmaster.derive(option.path) - } - return { - coin: option.coin, - seckey: key.privateKey.toString('hex'), // 或者 key.toJSON().privateKey。或者 key.privateKey.slice(2) 删除开头的'0x'如果是ethers.HDNode.fromMnemonic(secword)的结果 - pubkey: key.publicKey.toString('hex') - } - } - } - return null - } - , - seckey2pubkey:function(seckey, option){ - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN - 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 - let compress = ['compressed', 'uncompressed'].indexOf(option.compress)>=0?option.compress:'compressed' // 默认为压缩格式的公钥 - return new crypto.ECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex',compress).toString('hex') // ecdh.getPublicKey(不加参数) 默认为 'uncompressed' - // 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。 - // 或者 require('secp256k1').publicKeyCreate(Buffer.from(seckey, 'hex'),compress).toString('hex') - // 或者 require('bitcore-lib').PublicKey.fromPrivateKey(new Btc.PrivateKey(seckey)).toString('hex') - // 注意,Buffer.from(nacl.box.keyPair.fromSecretKey(Buffer.from(seckey,'hex')).publicKey).toString('hex') 得到的公钥与上面的不同 - }else if (this.isSeckey(seckey) && seckey.length===128 && option.coin==='TIC'){ // 用于64字节=128 hex的 TIC 私钥 - let keypair=nacl.sign.keyPair.fromSecretKey(Buffer.from(seckey,'hex')) - return Buffer.from(keypair.publicKey).toString('hex') // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 - } - return null - } - , - secword2account:function(secword, option){ // account 比 keypair 多了 address 字段。 - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN - let kp=this.secword2keypair(secword, option) - if (kp) { - kp.address=this.pubkey2address(kp.pubkey, option) - return kp - } - return null - } - , - secword2address:function(secword, option){ - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN - let address - let kp=this.secword2keypair(secword, option) - if (kp) { - return this.pubkey2address(kp.pubkey,option) - } - return null - } - , - isSecword:function(secword){ - return Secword.isValid(secword) - } - , - isSeckey:function(seckey){ - // 比特币、以太坊的私钥:64 hex - // nacl.sign 的私钥 128 hex, nacl.box 的私钥 64 hex - return /^([a-fA-F0-9]{128}|[a-fA-F0-9]{64})$/.test(seckey) - } - , - isPubkey:function(pubkey){ - // 比特币的公钥:压缩型 '02|03' + 64 hex 或 无压缩型 '04' + 128 hex - // 以太坊的公钥:'02|03' + 64 hex - // nacl.sign 的公钥:64 hex - return /^((02|03)?[a-fA-F0-9]{64}|04[a-fA-F0-9]{128})$/.test(pubkey) // "d2f186a630f5558ba3ede10a4dd0549da5854eab3ed28ee8534350c2535d38b0" - } - , - isAddress: function (address) { - return /^[m|t|d|T][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{33}$/.test(address) // && address.length>25 && bs58check.decode(address.slice(1)) && ['A'].indexOf(address[0]>=0)) { - } - , - pubkey2address:function (pubkey, option) { // pubkey 应当是string类型 - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN - if (this.isPubkey(pubkey)) { - option = option||{} - let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest() - let h160 = crypto.createHash('ripemd160').update(h256).digest('hex') - let prefix - if (option.coin==='TIC'){ - switch(option.netType){ - case 'mainnet': prefix='42'; break; // '42'=>T, '6E'=>m - case 'testnet': prefix='7F'; break; // '7F'=>t - case 'devnet': prefix='5A'; break; // '5A'=>d - default: prefix='42' // 默认暂且为 42,为了兼容已经运行的链。 - } - }else if(option.coin==='BTC'){ - switch (option.netType) { - case 'mainnet': prefix='00'; break; // 1 - case 'testnet': prefix='6f'; break; // m or n - case 'p2sh': prefix='05'; break; // 3 - default: prefix='6f' - } - }else { // 目前不支持 ETH或其他币种 地址转换,因为这会大量增加前端打包的js。 - return null - } - var wifAddress=bs58check.encode(Buffer.from(prefix+h160,'hex')) // wallet import format - return wifAddress - } - return null - } - , - secword2seed:function(secword, pass) { // 遵循bip39的算法。和 ether.HDNode.mnemonic2Seed 结果一样,是64字节的种子。 - if (Secword.isValid(secword)) { // bip39.validateMnemonic(secword)) { - return new Secword(secword).toSeed(pass).toString('hex') // 结果一致于 bip39.mnemonicToSeedHex(secword) 或 ethers.HDNode.mnemonic2Seed(secword) - } - return null - } - , - randomSecword:function(lang){ // Object.keys(Secword.Words) => [ 'CHINESE', 'ENGLISH', 'FRENCH', 'ITALIAN', 'JAPANESE', 'SPANISH' ] - lang = (lang && Secword.Words.hasOwnProperty(lang.toUpperCase())) ? lang.toUpperCase() : 'ENGLISH' - return new Secword(Secword.Words[lang]).phrase - } - , - randomSeckey:function(option){ - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN - if (option.coin==='TIC'){ - return Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节 - }else{ - return Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节 - } - } - , - randomKeypair:function(option){ - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN - let kp - if (option.coin==='TIC'){ - kp=nacl.sign.keyPair() - }else{ - kp=nacl.box.keyPair() - } - return { - seckey:Buffer.from(kp.secretKey).toString('hex'), - pubkey:Buffer.from(kp.publicKey).toString('hex') - } - } - , - randomString:function (length=6, alphabet) { // 长度为 length,字母表为 alphabet 的随机字符串 - alphabet = alphabet||"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%^&*@" - var text = '' - for (var i = 0; i < length; i++) { - text += alphabet.charAt(Math.floor(Math.random() * alphabet.length)) - } - return text - } - , - randomNumber:function(option){ // 长度为 option.length 的随机数字,或者 (option.min||0) <= num < option.max - option=option||{} - var num=0 - if (option.length>0){ - num=parseInt(Math.random()*Math.pow(10,option.length)) - let l = new String(num).length - while(l < option.length) { - num = '0' + num // 注意,这时返回的是字符串! - l++ - } - }else if (option.max>0){ - option.min = (option.min>=0)?option.min:0 - num=parseInt(Math.random()*(option.max-option.min))+option.min - }else{ // 如果 option 为空 - num=Math.random() - } - return num - } - , - randomUuid:uuid.v4 - , - getMerkleRoot:function(hashList, option){ - // merkle算法略有难度,暂时用最简单的hash代替 - if(Array.isArray(hashList)){ - option=option||{} - 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) - for (var hash of hashList){ - hasher.update(hash) - } - return hasher.digest(output) - } - return null - } - , - getMerkleRoot:function(todoHashList, option){ - //深拷贝传入数组,防止引用对象被改变 - let hashList = [...todoHashList] - if(!Array.isArray(hashList)) - return null - var border = hashList.length; - if(border == 0) - return this.hash('') - if(border == 1) - return this.hash(hashList[0]); - while(1){ - let i = 1,j = 0; - for(; i < border; i = i + 2){ - hashList[j] = this.hash(hashList[i - 1] + hashList[i]); - if(border == 2){ - return hashList[0]; - } - if(i + 1 == border) break; - j = j + 1; - if(i + 2 == border){ - i = i + 1; - hashList[j] = this.hash(hashList[i]); - break; - } - } - border = j + 1; - } - return hashList - } - , - distanceSig:function(hash, sig){ // hash为64hex字符,sig为128hex字符。返回用hex表达的距离。 - if (this.isSignature(sig) && this.isHash(hash)){ - var hashSig=this.hash(sig) // 把签名也转成32字节的哈希,同样长度方便比较 - return new BigNumber(hash,16).minus(new BigNumber(hashSig,16)).abs().toString(16) - } - return null - } - , - compareSig:function(hash, sig1, sig2){ // 返回距离hash更近的sig - if (this.isHash(hash)) { - if (this.isSignature(sig2) && this.isSignature(sig1)) { - var dis1=this.distanceSig(hash,sig1) - var dis2=this.distanceSig(hash,sig2) - if (dis1dis2) { - return sig2 - }else if (dis1===dis2) { // 如果极其巧合的距离相等,也可能是一个在左、一个在右,那就按 signature 本身的字符串排序来比较。 - return sig1=0?option.hasher:my.HASHER // 默认为 sha256. + let inputEncoding=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=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) // option.output: 留空=》默认输出hex格式;或者手动指定 'buf', hex', 'latin1' or 'base64' + return crypto.createHash(hasher).update(data, inputEncoding).digest(outputEncoding) + } + return null + } + , + isHashable:function(data, option){ + option=option||{} + if (option.strict) { + return data && typeof(data)!=='boolean' && data!==Infinity // 允许大多数数据,除了空值、布尔值、无限数 + } + return typeof(data)!=='undefined' // 允许一切数据,除非 undefined + } + , + isHash:function(hash, option){ + option=option||{} + option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER + switch(option.hasher){ + case 'sha256': return /^[a-fA-F0-9]{64}$/.test(hash) + case 'md5': return /^[a-fA-F0-9]{32}$/.test(hash) + case 'ripemd160': case 'sha1': return /^[a-fA-F0-9]{40}$/.test(hash) + case 'sha512': return /^[a-fA-F0-9]{128}$/.test(hash) + } + return false + } + , + encrypt: function(data, pwd, option){ + if (this.isHashable(data) && typeof(pwd)==='string') { + option=option||{} + let inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView + let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly + let cipher=crypto.createCipher( + my.CIPHER_LIST.indexOf(option.cipher)>=0?option.cipher:my.CIPHER, + this.hash(pwd)) + if (typeof(data)!=='string' && !(data instanceof Buffer) && !(data instanceof DataView)) + data=JSON.stringify(data) + let encrypted = cipher.update(data, inputEncoding, outputEncoding) + encrypted += cipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string + return encrypted + } + return null + } + , + decrypt: function(data, pwd, option){ // data 应当是 encrypt 输出的数据类型 + if (data && (typeof(data)==='string' || data instanceof Buffer) && typeof(pwd)==='string') { + option=option||{} + let inputEncoding=my.OUTPUT_LIST.indexOf(option.input)>=0?option.input:my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer + let outputEncoding=(option.output==='buf')?undefined:(my.INPUT_LIST.indexOf(option.output)>=0?option.output:my.INPUT) // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly + let decipher=crypto.createDecipher( + my.CIPHER_LIST.indexOf(option.cipher)>=0?option.cipher:my.CIPHER, + this.hash(pwd)) + let decrypted = decipher.update(data, inputEncoding, outputEncoding) + decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string + if (option.format==='json') { // 如果用户输入错误密码,deciper也能返回结果。为了判断是否正确结果,对应当是 json 格式的原文做解析来验证。 + try{ + JSON.parse(decrypted) + }catch(exception){ + return null + } + } + return decrypted + } + return null + } + , + sign: function(data, seckey, option) { // data can be string or buffer or object, results are the same + if (this.isHashable(data) && this.isSeckey(seckey)) { + option=option||{} + + // 使用nacl的签名算法。注意,nacl.sign需要的seckey是64字节=512位,而比特币/以太坊的seckey是32字节。因此本方法只能用于 TIC 币的 keypair。 + option.output='buf' // 哈希必须输出为 buffer + var hashBuf = this.hash(data, option) + var signature = nacl.sign.detached(hashBuf, Buffer.from(seckey, 'hex')) + return Buffer.from(signature).toString('hex') // 返回128个hex字符,64字节 + + // 方案2:尚未彻底实现。 + // let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER + // let inputEncoding=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=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) + // let signer=crypto.createSign(hasher) + // return signer.update(data, inputEncoding).sign(seckey, outputEncoding) // todo: crypto的sign要求的seckey必须是PEM格式,因此这样写是不能用的。 + } + return null + } + , + isSignature:function(signature){ + return /^[a-fA-F0-9]{128}$/.test(signature) + } + , + verify: function (data, signature, pubkey, option) { // data could be anything, but converts to string or remains be Buffer/TypedArray/DataView + if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey)){ + option=option||{} + option.output='buf' // 哈希必须输出为 buffer + var bufHash=this.hash(data, option) + var bufSignature = Buffer.from(signature, 'hex') + var bufPubkey = Buffer.from(pubkey, 'hex') + var res = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey) + return res + } + return null + } + , + pass2keypair:function(pass, option){ // 如果使用其他机制,例如密码、随机数,不使用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() + var keypair = nacl.sign.keyPair.fromSeed(hashBuf) + return { + hash: hashBuf.toString('hex'), + pubkey: Buffer.from(keypair.publicKey).toString('hex'), // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 + seckey: Buffer.from(keypair.secretKey).toString('hex') + } + } + return null + } + , + secword2keypair: function(secword, option){ // option.coin 币种;option.passphase 密码,默认为空;option.path==='master' 生成 HD master key,不定义则默认为相应币种的第一对公私钥。 + if (Secword.isValid(secword)){ + option=option||{} + option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + + if(option.coin==='TIC') { + // 采用自己的算法: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() + let keypair = nacl.sign.keyPair.fromSeed(hashBuf) // nacl.sign.keyPair.fromSeed 要求32字节的种子,而 this.secword2seed生成的是64字节种子,所以要先做一次sha256 + return { + coin: option.coin, + 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(new Buffer(this.secword2seed(secword, option.pass), 'hex')) // 和 new Secword(secword).toHDPrivateKey 求出的公私钥一样! + let hdmaster=new Secword(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 key=hdmaster + if (option.path==='master'){ + key=hdmaster + }else if (!option.path) { + switch(option.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 + default: key=hdmaster.derive("m/44'/99'/0'/0/0"); break + } + }else { // 指定了路径 option.path,例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1" + key=hdmaster.derive(option.path) + } + return { + coin: option.coin, + seckey: key.privateKey.toString('hex'), // 或者 key.toJSON().privateKey。或者 key.privateKey.slice(2) 删除开头的'0x'如果是ethers.HDNode.fromMnemonic(secword)的结果 + pubkey: key.publicKey.toString('hex') + } + } + } + return null + } + , + seckey2pubkey:function(seckey, option){ + option=option||{} + option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + 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 + let compress = ['compressed', 'uncompressed'].indexOf(option.compress)>=0?option.compress:'compressed' // 默认为压缩格式的公钥 + return new crypto.ECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex',compress).toString('hex') // ecdh.getPublicKey(不加参数) 默认为 'uncompressed' + // 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。 + // 或者 require('secp256k1').publicKeyCreate(Buffer.from(seckey, 'hex'),compress).toString('hex') + // 或者 require('bitcore-lib').PublicKey.fromPrivateKey(new Btc.PrivateKey(seckey)).toString('hex') + // 注意,Buffer.from(nacl.box.keyPair.fromSecretKey(Buffer.from(seckey,'hex')).publicKey).toString('hex') 得到的公钥与上面的不同 + }else if (this.isSeckey(seckey) && seckey.length===128 && option.coin==='TIC'){ // 用于64字节=128 hex的 TIC 私钥 + let keypair=nacl.sign.keyPair.fromSecretKey(Buffer.from(seckey,'hex')) + return Buffer.from(keypair.publicKey).toString('hex') // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 + } + return null + } + , + secword2account:function(secword, option){ // account 比 keypair 多了 address 字段。 + option=option||{} + option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + let kp=this.secword2keypair(secword, option) + if (kp) { + kp.address=this.pubkey2address(kp.pubkey, option) + return kp + } + return null + } + , + secword2address:function(secword, option){ + option=option||{} + option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + let address + let kp=this.secword2keypair(secword, option) + if (kp) { + return this.pubkey2address(kp.pubkey,option) + } + return null + } + , + isSecword:function(secword){ + return Secword.isValid(secword) + } + , + isSeckey:function(seckey){ + // 比特币、以太坊的私钥:64 hex + // nacl.sign 的私钥 128 hex, nacl.box 的私钥 64 hex + return /^([a-fA-F0-9]{128}|[a-fA-F0-9]{64})$/.test(seckey) + } + , + isPubkey:function(pubkey){ + // 比特币的公钥:压缩型 '02|03' + 64 hex 或 无压缩型 '04' + 128 hex + // 以太坊的公钥:'02|03' + 64 hex + // nacl.sign 的公钥:64 hex + return /^((02|03)?[a-fA-F0-9]{64}|04[a-fA-F0-9]{128})$/.test(pubkey) // "d2f186a630f5558ba3ede10a4dd0549da5854eab3ed28ee8534350c2535d38b0" + } + , + isAddress: function (address) { + return /^[m|t|d|T][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{33}$/.test(address) // && address.length>25 && bs58check.decode(address.slice(1)) && ['A'].indexOf(address[0]>=0)) { + } + , + pubkey2address:function (pubkey, option) { // pubkey 应当是string类型 + option=option||{} + option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + if (this.isPubkey(pubkey)) { + option = option||{} + let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest() + let h160 = crypto.createHash('ripemd160').update(h256).digest('hex') + let prefix + if (option.coin==='TIC'){ + switch(option.netType){ + case 'mainnet': prefix='42'; break; // '42'=>T, '6E'=>m + case 'testnet': prefix='7F'; break; // '7F'=>t + case 'devnet': prefix='5A'; break; // '5A'=>d + default: prefix='42' // 默认暂且为 42,为了兼容已经运行的链。 + } + }else if(option.coin==='BTC'){ + switch (option.netType) { + case 'mainnet': prefix='00'; break; // 1 + case 'testnet': prefix='6f'; break; // m or n + case 'p2sh': prefix='05'; break; // 3 + default: prefix='6f' + } + }else { // 目前不支持 ETH或其他币种 地址转换,因为这会大量增加前端打包的js。 + return null + } + var wifAddress=bs58check.encode(Buffer.from(prefix+h160,'hex')) // wallet import format + return wifAddress + } + return null + } + , + secword2seed:function(secword, pass) { // 遵循bip39的算法。和 ether.HDNode.mnemonic2Seed 结果一样,是64字节的种子。 + if (Secword.isValid(secword)) { // bip39.validateMnemonic(secword)) { + return new Secword(secword).toSeed(pass).toString('hex') // 结果一致于 bip39.mnemonicToSeedHex(secword) 或 ethers.HDNode.mnemonic2Seed(secword) + } + return null + } + , + randomSecword:function(lang){ // Object.keys(Secword.Words) => [ 'CHINESE', 'ENGLISH', 'FRENCH', 'ITALIAN', 'JAPANESE', 'SPANISH' ] + lang = (lang && Secword.Words.hasOwnProperty(lang.toUpperCase())) ? lang.toUpperCase() : 'ENGLISH' + return new Secword(Secword.Words[lang]).phrase + } + , + randomSeckey:function(option){ + option=option||{} + option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + if (option.coin==='TIC'){ + return Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节 + }else{ + return Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节 + } + } + , + randomKeypair:function(option){ + option=option||{} + option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + let kp + if (option.coin==='TIC'){ + kp=nacl.sign.keyPair() + }else{ + kp=nacl.box.keyPair() + } + return { + seckey:Buffer.from(kp.secretKey).toString('hex'), + pubkey:Buffer.from(kp.publicKey).toString('hex') + } + } + , + randomString:function (length=6, alphabet) { // 长度为 length,字母表为 alphabet 的随机字符串 + alphabet = alphabet||"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%^&*@" + var text = '' + for (var i = 0; i < length; i++) { + text += alphabet.charAt(Math.floor(Math.random() * alphabet.length)) + } + return text + } + , + randomNumber:function(option){ // 长度为 option.length 的随机数字,或者 (option.min||0) <= num < option.max + option=option||{} + var num=0 + if (option.length>0){ + num=parseInt(Math.random()*Math.pow(10,option.length)) + let l = new String(num).length + while(l < option.length) { + num = '0' + num // 注意,这时返回的是字符串! + l++ + } + }else if (option.max>0){ + option.min = (option.min>=0)?option.min:0 + num=parseInt(Math.random()*(option.max-option.min))+option.min + }else{ // 如果 option 为空 + num=Math.random() + } + return num + } + , + randomUuid:uuid.v4 + , + getMerkleHash:function(hashList, option){ + // merkle算法略有难度,暂时用最简单的hash代替 + if(Array.isArray(hashList)){ + option=option||{} + 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) + for (var hash of hashList){ + hasher.update(hash) + } + return hasher.digest(output) + } + return null + } + , + getMerkleRoot:function(todoHashList, option){ + //深拷贝传入数组,防止引用对象被改变 + let hashList = [...todoHashList] + if(!Array.isArray(hashList)) + return null + var border = hashList.length; + if(border == 0) + return this.hash('') + if(border == 1) + return this.hash(hashList[0]); + while(1){ + let i = 1,j = 0; + for(; i < border; i = i + 2){ + hashList[j] = this.hash(hashList[i - 1] + hashList[i]); + if(border == 2){ + return hashList[0]; + } + if(i + 1 == border) break; + j = j + 1; + if(i + 2 == border){ + i = i + 1; + hashList[j] = this.hash(hashList[i]); + break; + } + } + border = j + 1; + } + return hashList + } + , + distanceSig:function(hash, sig){ // hash为64hex字符,sig为128hex字符。返回用hex表达的距离。 + if (this.isSignature(sig) && this.isHash(hash)){ + var hashSig=this.hash(sig) // 把签名也转成32字节的哈希,同样长度方便比较 + return new BigNumber(hash,16).minus(new BigNumber(hashSig,16)).abs().toString(16) + } + return null + } + , + compareSig:function(hash, sig1, sig2){ // 返回距离hash更近的sig + if (this.isHash(hash)) { + if (this.isSignature(sig2) && this.isSignature(sig1)) { + var dis1=this.distanceSig(hash,sig1) + var dis2=this.distanceSig(hash,sig2) + if (dis1dis2) { + return sig2 + }else if (dis1===dis2) { // 如果极其巧合的距离相等,也可能是一个在左、一个在右,那就按 signature 本身的字符串排序来比较。 + return sig1