// const BigNumber=require('bignumber.js') // 处理整数 https://github.com/MikeMcl/bignumber.js const BigInt = require("big-integer") // 处理整数 https://github.com/peterolson/BigInteger.js const crypto=require('crypto') const nacl = require('tweetnacl') const bs58check = require('bs58check') const uuid = require('uuid') const keccak = require('keccak') const eccrypto = require('eccrypto') // 用于加解密。不知道怎么用 crypto 本身的加解密。 const keyman = require('js-crypto-key-utils') // 转换原始密钥和 PER/DER 格式。 // const BitcoreMnemonic = require('bitcore-mnemonic') // https://bitcore.io/api/mnemonic/ https://github.com/bitpay/bitcore-mnemonic // 打包成 app 里常有问题,试图访问 window 变量,无法生成 secword const bip39 = require('bip39') // https://github.com/bitcoinjs/bip39 // 有更多语言,但不方便选择语言,也不能使用 pass const HDKey = require('hdkey') // https://github.com/cryptocoinjs/hdkey // 或者用 bitcore-mnemonic 或者 ethers 里的相同功能 // const bitcorelib = require('bitcore-lib') // const secp256k1 = require('secp256k1') // 全部以hex为默认输入输出格式,方便人的阅读,以及方便函数之间统一接口 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.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'] my.CHAINNET='mainnet' // 默认的链网 module.exports = { hash(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(data, option){ option=option||{} if (option.strict) { return data && typeof(data)!=='boolean' && data!==Infinity // 允许大多数数据,除了空值、布尔值、无限数 } return typeof(data)!=='undefined' // 允许一切数据,除非 undefined } , isHash(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 } , isSecword(secword){ //// for bitcore-mnemonic. 注意,bitcore-mnemonic 对少于12词的会抛出异常,很蠢。 // if (typeof secword==='string' && 12===secword.split(/ +/).length) // return BitcoreMnemonic.isValid(secword) // else // return false //// for bip39. 注意,bip39对当前defaultWordlist之外其他语言的合法 mnemonic 也返回 false。所以不能直接 bip39.validateMnemonic(secword) if (typeof secword==='string' && 12===secword.split(/ +/).length) return true else return false } , isSeckey(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(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" } , isSignature(signature){ return /^[a-fA-F0-9]{128,144}$/.test(signature) } , async encrypt(data, {keytype, key, input, output, cipher}={}){ if (keytype==='pwd') { if (this.isHashable(data) && typeof(key)==='string') { let inputEncoding=my.INPUT_LIST.indexOf(input)>=0?input:my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView let outputEncoding=(output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(output)>=0?output:my.OUTPUT) // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly let cipher=crypto.createCipher( my.CIPHER_LIST.indexOf(cipher)>=0?cipher:my.CIPHER, this.hash(key)) 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 } }else if (keytype==='pubkey') { // data 应当是 utf8 的字符串。// 但在浏览器里不能使用 Failed to execute 'encrypt' on 'SubtleCrypto': The provided value is not of type '(ArrayBuffer or ArrayBufferView)' let cipherobject = await eccrypto.encrypt(this.hex2buf(key), data) return cipherobject } return null } , async decrypt(data, {keytype, key, input, output, cipher, format}={}){ // data 应当是 encrypt 输出的数据类型 if (keytype==='pwd') { if (data && (typeof(data)==='string' || data instanceof Buffer) && typeof(key)==='string') { let inputEncoding=my.OUTPUT_LIST.indexOf(input)>=0?input:my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer let outputEncoding=(output==='buf')?undefined:(my.INPUT_LIST.indexOf(output)>=0?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(cipher)>=0?cipher:my.CIPHER, this.hash(key)) let decrypted = decipher.update(data, inputEncoding, outputEncoding) decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string if (format==='json') { // 如果用户输入错误密码,deciper也能返回结果。为了判断是否正确结果,对应当是 json 格式的原文做解析来验证。 try{ JSON.parse(decrypted) }catch(exception){ return null } } return decrypted }else if (keytype==='seckey'){ // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象 let plaindata = await eccrypto.decrypt(Buffer.from(key, 'hex'), data) // eccrypto 需要调用 Buffer.compare 方法,不能在这里直接用 hex2buf return plaindata.toString('utf8') } } return null } , async sign(data, seckey, option={}) { // data can be string or buffer or object, results are the same if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===64) { // 纯 crypto let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), {namedCurve:'P-256K'}).export('pem') let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) let signer=crypto.createSign(hasher) signer.update(this.hash(data, option)).end() let signature = signer.sign(seckeyPEM, 'hex') return signature // 发现同样的输入,每次调用会生成不同的 signature, 且长度不定(140~144 hex) 但都可以通过 verify。有一次我竟然徒手修改出一个新签名也通过验证。 } if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===128) { // 使用nacl的签名算法。注意,nacl.sign需要的seckey是64字节=128字符。 option.output='buf' // 哈希必须输出为 buffer let hashBuf = this.hash(data, option) let signature = nacl.sign.detached(hashBuf, Buffer.from(seckey, 'hex')) return Buffer.from(signature).toString('hex') // 签名是64节,128个hex字符 } return null } , async verify (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) && signature.length>=140){ // 纯 crypto let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), {namedCurve:'P-256K'}).export('pem') let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER let verifier = crypto.createVerify(hasher) verifier.update(this.hash(data, option)).end() // end() 在 nodejs 12 里返回verifier自身,但在浏览器里返回 undefined,因此不能串联运行。 let verified = verifier.verify(pubkeyPEM, signature, 'hex') return verified } if (signature.length===128){ // nacl option.output='buf' // 哈希必须输出为 buffer let bufHash=this.hash(data, option) let bufSignature = Buffer.from(signature, 'hex') let bufPubkey = Buffer.from(pubkey, 'hex') let verified = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey) return verified } return null } , pass2keypair(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(secword, option){ // option.coin 币种; // option.passphase 密码,默认为空; // option.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) // Change===常量 0 用于外部链,常量 1 用于内部链(也称为更改地址)。外部链用于在钱包外可见的地址(例如,用于接收付款)。内部链用于在钱包外部不可见的地址,用于返回交易变更。 (所以一般使用 0) // Index 地址索引,从 0 开始,代表生成第几个地址,官方建议,每个 account 下的 address_index 不要超过 20。 // 据测试, Purpose和CoinType都可以任意其他值,不必要如规范所示;' 引号可有可无,导致的密钥不一样; // Account 最大为 0x7FFFFFFF, Change/Index 最大均为 0xFFFFFFFF(=4294967295) // 但可以不断延伸下去:/xxx/xxx/xxx/xxx/... if (this.isSecword(secword)){ option=option||{} option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN if(option.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() 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(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 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 case 'TIC': key=hdmaster.derive("m/44'/66'/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 } , secword2account(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.seckey2address(kp.seckey, option) return kp } return null } , secword2address(secword, option){ option=option||{} option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN let kp=this.secword2keypair(secword, option) if (kp) { return this.seckey2address(kp.seckey,option) } return null } , seckey2pubkey(seckey, 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 return new crypto.createECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex', option.compress===false?'uncompressed':'compressed') // ecdh.getPublicKey(不加参数) 默认为 'uncompressed' // 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。但可惜,浏览器里不存在 crypto.ECDH。 // 或者 return this.buf2hex(require('secp256k1').publicKeyCreate(Buffer.from(seckey, 'hex'), option.compress!==false)) // 可用于浏览器。secp256k1缺省或true时输出压缩公钥,false时输出非压缩公钥。 // 或者 bitcorelib.PublicKey.fromPrivateKey(new bitcorelib.PrivateKey(seckey)).toString('hex') // 可用于浏览器 // 或者 const ecc = require('eccrypto') // if (option.compress===false){ // return ecc.getPublic(this.hex2buf(seckey)).toString('hex') // }else{ // return ecc.getPublicCompressed(this.hex2buf(seckey)).toString('hex') // } // 注意,Buffer.from(nacl.box.keyPair.fromSecretKey(Buffer.from(seckey,'hex')).publicKey).toString('hex') 得到的公钥与上面的不同 }else if (this.isSeckey(seckey) && seckey.length===128){ // 用于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 } , seckey2address(seckey, option){ option=option||{} option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN if (this.isSeckey(seckey)){ let pubkey if (option.coin==='ETH'){ pubkey = this.seckey2pubkey(seckey, {compress:false}) return this.pubkey2address(pubkey, option) }else { pubkey = this.seckey2pubkey(seckey, {compress:true}) return this.pubkey2address(pubkey, option) } } return null } , pubkey2position (pubkey, {coin}={}){ // tic, btc, eth 的 position 都是 20节=40字符的。 coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN if(this.isPubkey(pubkey)){ if (coin==='ETH'){ // 注意,必须要用非压缩的64字节的公钥的buffer,并去掉开头的 04。 if (pubkey.length===66) { pubkey = this.decompressPubkey(pubkey) } return keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40) }else { let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest() let h160 = crypto.createHash('ripemd160').update(h256).digest('hex') return h160 } } return null } , position2address(position, {coin, net, format}={}){ if (!/^[\da-fA-F]{40}$/.test(position)) return null // 不论 tic, btc, eth,其 position 都是 40字符的。 coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN let address if (coin==='ETH'){ // 对以太坊,按照 EIP55,把纯位置转换为大小写敏感能自我验证的hex地址。仍然为20节=40符。 position = position.toLowerCase().replace('0x', '') let hash = keccak('keccak256').update(position).digest('hex') address = '0x' for (var i = 0; i < position.length; i++) { if (parseInt(hash[i], 16) >= 8) { address += position[i].toUpperCase() } else { address += position[i] } } return address }else if (coin === 'BTC'){ // 对比特币,把纯位置转换为大小写敏感能自我验证的bs58check地址:先加前缀,再加校验,共25字节,再转base58。得到26~34个字符,大多数34个。 let prefix switch (net) { case 'mainnet': prefix='00'; break; // pubkey hash => 1 case 'mainnetSh': prefix='05'; break; // script hash => 3 case 'testnet': prefix='6f'; break; // testnet pubkey hash => m or n case 'testnetSh': prefix='c4'; break // testnet script hash => 2 case 'namecoin': prefix='34'; break; // Namecoin pubkey hash => M or N case 'compact': prefix='15'; break; // compact pubkey (proposed) => 4 default: prefix='00' } address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format return address }else { // 默认为 TIC。把纯位置转换为大小写敏感能自我验证的 b64u(base64 for url) 地址。 let prefix switch (net){ // Base58: https://en.bitcoin.it/wiki/List_of_address_prefixes // Base64: https://baike.baidu.com/item/base64 case 'mainnet': prefix='4c'; break; // Base58: 0x42=66 => T, Base64: base64 T=0x13=0b00010011 => 0b010011xx = 0x4c~4f case 'testnet': prefix='b4'; break; // Base58: 0x7f=127,0x80=128 => t, Base64: t=0x2d=0b00101101 => 0b101101xx = 0xB4~B7 case 'devnet': prefix='74'; break; // Base58: 0x90 => d, Base 64: d=0x1d=0b00011101 => 0b 011101xx = 0x74~77 default: prefix='4c' } let checksum = this.hash(this.hash(prefix+position)).slice(0,6) // 添加 checksum 使得能够检测大小写错误。[todo] 校验码里要不要包含 prefix? // address = this.hex2eip55(prefix + position + checksum) // 前缀1节,位置20节,校验3节,共24节=48字符(能够完全转化为8个色彩),再转eip55。 address = this.hex2b64u(prefix + position + checksum) // 实际采用 b64u (named by luk.lu as base 64 for url), 共 32字符。 return address } return null } , address2position(){ if (/^0x[\da-fA-F]{40}$/.test(address)){ return address.toLowerCase() }else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address)){ let hex = this.b58c2hex(address) if (hex) { return hex.slice(2) // 去除网络前缀 } }else if (/^[Tt][0-9a-zA-Z\-_]{31}$/.test(address)){ // 格式合法 let hex = this.b64u2hex(address) let [all, prefix, position, checksum] = hex.match(/^([\da-fA-F]{2})([\da-fA-F]{40})([\da-fA-F]{6})$/) if (this.hash(this.hash(position)).slice(0,6) === checksum) { return position } } return null } , isAddress(address){ if (/^(0x)?[\da-fA-F]{40}$/.test(address)){ return 'ETH' }else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address)){ // 格式合法 let prefixedPosition = this.b58c2hex(address) if (prefixedPosition && prefixedPosition.length===42) // 内容合法 return 'BTC' }else if (/^[Ttd][0-9a-zA-Z\-_]{31}$/.test(address)){ // 格式合法 let b64 = address.replace('-', '+').replace('_', '/') let hex = Buffer.from(b64, 'base64').toString('hex') let [all, prefix, position, checksum] = hex.match(/^([\da-fA-F]{2})([\da-fA-F]{40})([\da-fA-F]{6})$/) // 内容合法 return this.hash(this.hash(prefix+position)).slice(0,6) === checksum // [todo] 校验码里要不要包含 prefix? } return null } , pubkey2address (pubkey, option={}) { // pubkey 应当是string类型 option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN return this.position2address(this.pubkey2position(pubkey, option), option) } , secword2seed(secword, pass) { // 遵循bip39的算法。和 ether.HDNode.mnemonic2Seed 结果一样,是64字节的种子。 if (this.isSecword(secword)) { // bip39.validateMnemonic(secword)) { return bip39.mnemonicToSeedSync(secword, pass).toString('hex') // 结果一致于 new BitcoreMnemonic(secword).toSeed(pass).toString('hex') 或 ethers.HDNode.mnemonic2Seed(secword) // return new BitcoreMnemonic(secword).toSeed(pass).toString('hex') } return null } , randomSecword(lang='english'){ // Object.keys(BitcoreMnemonic.Words) => [ 'CHINESE', 'ENGLISH', 'FRENCH', 'ITALIAN', 'JAPANESE', 'KOREAN', 'SPANISH' ] //// for BitcoreMnemonic // let language = { zhCN: 'CHINESE', enUS: 'ENGLISH', frFR: 'FRENCH', itIT: 'ITALIAN', jaJP: 'JAPANESE', koKR: 'KOREAN', esES: 'SPANISH' }[lang] // || (BitcoreMnemonic.Words.hasOwnProperty(lang.toUpperCase()) ? lang.toUpperCase() : 'ENGLISH') // return new BitcoreMnemonic(BitcoreMnemonic.Words[language]).phrase // for bip39 const langMap = { zhCN: 'chinese_simplified', zhTW: 'chinese_traditional', enUS: 'english', frFR: 'french', itIT: 'italian', jaJP: 'japanese', koKR: 'korean', esES: 'spanish' } let language = langMap[lang] || (typeof(lang)==='string' && Object.values(langMap).indexOf(lang.toLowerCase()>=0) ? lang.toLowerCase() : 'english') bip39.setDefaultWordlist(language) return bip39.generateMnemonic() } , randomSeckey(option){ // todo: 使用 crypto.randomBytes(size) option=option||{} option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN if (option.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字节 } } , randomKeypair(option){ option=option||{} option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN let kp if (option.tool==='nacl'){ if (option.purpose==='encrypt'){ kp=nacl.box.keyPair() }else{ kp=nacl.sign.keyPair() } return { seckey:Buffer.from(kp.secretKey).toString('hex'), pubkey:Buffer.from(kp.publicKey).toString('hex') } }else { let seckey=this.randomSeckey() let pubkey=this.seckey2pubkey(seckey) return { seckey, pubkey } } } , randomString (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({length, min, max}={}){ // 长度为 length 的随机数字,或者 (min||0) <= num < max var num=0 if (typeof length === 'number' && length>0){ num=parseInt(Math.random()*Math.pow(10,length)) num.toString().padStart(length, '0') }else if (typeof max === 'number' && max>0){ min = (typeof min === 'number' && min>=0) ? min : 0 num=parseInt(Math.random()*(max-min))+min }else{ // 如果 option 为空 num=Math.random() } return num } , randomUuid:uuid.v4 , getMerkleHash(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(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(hash, sig){ // hash为64hex字符,sig为128hex字符。返回用hex表达的距离。 if (this.isSignature(sig) && this.isHash(hash)){ var hashSig=this.hash(sig) // 把签名也转成32字节的哈希,同样长度方便比较 return new BigInt(hash,16).sub(new BigInt(hashSig,16)).abs().toString(16) } return null } , compareSig(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 ('00' + x.toString(16)).slice(-2)).join(''); } , hex2buf(hex){ return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) })) // 注意,arraybuffer没有 toString('hex')功能, Buffer才有。 } , hex2b58c(hex){ return bs58check.encode(Buffer.from(hex, 'hex')) } , b58c2hex(box){ try{ return bs58check.decode(box).toString('hex') }catch(exception){ return null } } , hex2b64u(hex){ if (/^[0-9a-fA-F]+$/.test(hex)) { return Buffer.from(hex,'hex').toString('base64').replace(/\+/g,'-').replace(/\//g, '_') } return null } , b64u2hex(b64u){ if (/^[0-9a-zA-Z\-_]+$/.test(b64u)){ let b64 = b64u.replace(/\-/g, '+').replace(/_/g, '/') return Buffer.from(b64, 'base64').toString('hex') } return null } , hex2eip55(hex){ if (/^(0x)?[\da-fA-F]*$/.test(hex)) { hex = hex.toLowerCase().replace('0x', '') let hash = keccak('keccak256').update(hex).digest('hex') let result = '' for (var i = 0; i < hex.length; i++) { if (parseInt(hash[i], 16) >= 8) { result += hex[i].toUpperCase() } else { result += hex[i] } } return result } return null } , // test: https://iancoleman.io/bitcoin-key-compression/ // compress: https://hacpai.com/article/1550844562914 compressPubkey(uncompressed){ // 把 04xy 的非压缩公钥 转成 02x 或 03x 的压缩公钥 let [all, x, y]=uncompressed.toLowerCase().match(/^04(.{64})(.{64})$/) let compressed if (/[1,3,5,7,9,b,d,f]$/.test(y)){ compressed = '03'+x // y为奇数=>前缀03 }else{ compressed = '02'+x // y为偶数=>前缀02 } if (this.decompressPubkey(compressed)===uncompressed) { return compressed } return null // 非压缩公钥有错误。 } , // uncompress: https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression/53478265#53478265 // https://en.bitcoin.it/wiki/Secp256k1 decompressPubkey(compressed){ // 把 02x 或 03x 的压缩公钥 转成 04xy 的非压缩公钥 // Consts for secp256k1 curve. Adjust accordingly const prime = new BigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16) // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 const pIdent = new BigInt('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4); var signY = new Number(compressed[1]) - 2 var x = new BigInt(compressed.substring(2), 16) 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 y = prime.subtract( y ) // y = prime - y } return '04' + x.toString(16).padStart(64, '0') + y.toString(16).padStart(64, '0') } }