diff --git a/index.js b/index.js index 56d86b5..5f637ab 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ // 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 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') @@ -15,187 +15,189 @@ 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'] +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'] module.exports = { - isHashable(data, {strict=false}={}){ + isHashable(data, { strict = false } = {}) { if (strict) { - return data && typeof(data)!=='boolean' && data!==Infinity // 允许大多数数据,除了空值、布尔值、无限数 + return data && typeof data !== 'boolean' && data !== Infinity // 允许大多数数据,除了空值、布尔值、无限数 } - return typeof(data)!=='undefined' // 允许一切数据,除非 undefined - } - , - isHash(hash, {hasher=my.HASHER}={}){ - if (my.HASHER_LIST.indexOf(hasher)>=0) { - switch(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 typeof data !== 'undefined' // 允许一切数据,除非 undefined + }, + isHash(hash, { hasher = my.HASHER } = {}) { + if (my.HASHER_LIST.indexOf(hasher) >= 0) { + switch (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, {mode='strict'}={}){ // 注意 not all 12 words combinations are valid for both bitcore and bip39, because there are checksum in mnemonic. 另外,实际上bitcore和bip39对12, 15, 18, ... 长度的合法助记词都返回 true。 + }, + isSecword(secword, { mode = 'strict' } = {}) { + // 注意 not all 12 words combinations are valid for both bitcore and bip39, because there are checksum in mnemonic. 另外,实际上bitcore和bip39对12, 15, 18, ... 长度的合法助记词都返回 true。 //// for bitcore-mnemonic. 注意,bitcore-mnemonic 对少于12词的会抛出异常,很蠢。 // if (typeof secword==='string' && 12===secword.split(/ +/).length) - // return BitcoreMnemonic.isValid(secword) + // return BitcoreMnemonic.isValid(secword) // else // return false //// for bip39. 注意,bip39对当前defaultWordlist之外其他语言的合法 mnemonic 也返回 false,这一点不如 bitcore-mnemonic. 所以不能直接 bip39.validateMnemonic(secword) - if (typeof secword==='string' && !/(^\s)|\s\s|(\s$)/.test(secword) && 12===secword.split(/\s+/).length) { - if (mode==='easy') return true // easy模式不检查校验等等严格的合法性了,反正 secword2seed是接受一切字符串的 + if (typeof secword === 'string' && !/(^\s)|\s\s|(\s$)/.test(secword) && 12 === secword.split(/\s+/).length) { + if (mode === 'easy') return true // easy模式不检查校验等等严格的合法性了,反正 secword2seed是接受一切字符串的 for (let lang of Object.keys(bip39.wordlists)) { bip39.setDefaultWordlist(lang) - if (bip39.validateMnemonic(secword)) - return true + if (bip39.validateMnemonic(secword)) return true } } return false - } - , - isSeckey(seckey){ + }, + 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){ + }, + 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) && (signature.length % 2 === 0) // 128 for nacl, 140/142/144 for crypto and eccrypto in der format. - } - , - hash(data, {hasher=my.HASHER, salt, input=my.INPUT, output=my.OUTPUT}={}){ // data can be anything, but converts to string or remains be Buffer/TypedArray/DataView + }, + isSignature(signature) { + return /^[a-fA-F0-9]{128,144}$/.test(signature) && signature.length % 2 === 0 // 128 for nacl, 140/142/144 for crypto and eccrypto in der format. + }, + hash(data, { hasher = my.HASHER, salt, input = my.INPUT, output = my.OUTPUT } = {}) { + // data can be anything, but converts to string or remains be Buffer/TypedArray/DataView 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' + 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' return crypto.createHash(hasher).update(data, inputEncoding).digest(outputEncoding) } return null - } - , - async encrypt(data, {tool, keytype, key, input, output, cipher}={}){ - if (keytype==='pwd') { - if (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 ciph=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) + }, + async encrypt(data, { tool, keytype, key, input, output, cipher } = {}) { + if (keytype === 'pwd') { + if (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 ciph = 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 = ciph.update(data, inputEncoding, outputEncoding) encrypted += ciph.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string return encrypted } - }else if (tool==='eccrypto') { // data 应当是 utf8 的字符串。// 但在浏览器里不能使用 Failed to execute 'encrypt' on 'SubtleCrypto': The provided value is not of type '(ArrayBuffer or ArrayBufferView)' + } else if (tool === 'eccrypto') { + // 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 - }else if (keytype==='seckey'){ - let seckeyPEM = await new keyman.Key('oct', this.hex2buf(key), {namedCurve:'P-256K'}).export('pem') // 私钥导出的der格式为144字节。 + } else if (keytype === 'seckey') { + let seckeyPEM = await new keyman.Key('oct', this.hex2buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 return crypto.privateEncrypt(seckeyPEM, Buffer.from(data)) - }else if (keytype==='pubkey'){ - let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(key), {namedCurve:'P-256K'}).export('pem') // 私钥导出的der格式为144字节。 + } else if (keytype === 'pubkey') { + let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 return crypto.publicEncrypt(pubkeyPEM, Buffer.from(data)) } 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)) + }, + 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{ + if (format === 'json') { + // 如果用户输入错误密码,deciper也能返回结果。为了判断是否正确结果,对应当是 json 格式的原文做解析来验证。 + try { JSON.parse(decrypted) - }catch(exception){ + } catch (exception) { return null } } return decrypted } - }else if (keytype==='seckey'){ // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象 + } else if (keytype === 'seckey') { + // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象 try { let plaindata = await eccrypto.decrypt(Buffer.from(key, 'hex'), data) // eccrypto 需要调用 Buffer.compare 方法,不能在这里直接用 hex2buf return plaindata.toString('utf8') - }catch (exception){ // eccrypto 对无法解密的,会抛出异常 + } catch (exception) { + // eccrypto 对无法解密的,会抛出异常 return null } } return null - } - , - async sign(data, seckey, option={}) { // data can be string or buffer or object, results are the same + }, + async sign(data, seckey, option = {}) { + // data can be string or buffer or object, results are the same if (this.isHashable(data) && this.isSeckey(seckey)) { - if (option.tool==='nacl' && seckey.length===128) { // 使用nacl的签名算法。注意,nacl.sign需要的seckey是64字节=128字符。 - option.output='buf' // 哈希必须输出为 buffer + if (option.tool === 'nacl' && 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字符 - }else if (option.tool==='eccrypto' && seckey.length===64) { // eccrypto 对同一组data,seckey生成的签名是固定的,观察到hex长度为140或142,是der格式。 - let signature = await eccrypto.sign(Buffer.from(seckey,'hex'), this.hash(data, {output:'buf'})) + } else if (option.tool === 'eccrypto' && seckey.length === 64) { + // eccrypto 对同一组data,seckey生成的签名是固定的,观察到hex长度为140或142,是der格式。 + let signature = await eccrypto.sign(Buffer.from(seckey, 'hex'), this.hash(data, { output: 'buf' })) return signature.toString('hex') - }else if (seckey.length===64){ // 纯 crypto - let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), {namedCurve:'P-256K'}).export('pem') // 私钥导出的der格式为144字节。 - let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER - let signer=crypto.createSign(hasher) + } else if (seckey.length === 64) { + // 纯 crypto + let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 + let hasher = my.HASHER_LIST.indexOf(option.hasher) >= 0 ? option.hasher : my.HASHER + let signer = crypto.createSign(hasher) signer.update(this.hash(data, option)).end() let signature = signer.sign(seckeyPEM, 'hex') return signature // 发现同样的输入,nodejs里每次调用会生成不同的 signature, 且长度不定(140,142,144 hex) 但都可以通过 verify。但在浏览器里调用,signature却是固定的。 } } 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)){ - if ('nacl' === option.tool && signature.length === 128) { - option.output='buf' // 哈希必须输出为 buffer - let bufHash=this.hash(data, option) + }, + 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)) { + if ('nacl' === option.tool && signature.length === 128) { + 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 - }else if ('eccrypto' === option.tool && signature.length >= 140) { // 默认使用 eccrypto + } else if ('eccrypto' === option.tool && signature.length >= 140) { + // 默认使用 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不被转成字节。 + 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 - }catch(exception){ // 对能够验证的,eccrypto返回 null;对无法验证的,抛出异常 + } catch (exception) { + // 对能够验证的,eccrypto返回 null;对无法验证的,抛出异常 return false } - }else if (signature.length >= 140) { // 纯 crypto - let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), {namedCurve:'P-256K'}).export('pem') // 公钥导出的der格式为88字节。经测试,同一对压缩和非压缩公钥得出的结果一模一样。 - let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER + } else if (signature.length >= 140) { + // 纯 crypto + let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), { namedCurve: 'P-256K' }).export('pem') // 公钥导出的der格式为88字节。经测试,同一对压缩和非压缩公钥得出的结果一模一样。 + 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') // 如果给signature添加1位hex,crypto 的 verify结果也是true! 估计因为一位hex不被转成字节。 @@ -203,36 +205,35 @@ module.exports = { } } return false - } - , - pass2keypair(pass, option){ // 如果使用其他机制,例如密码、随机数,不使用secword,也可生成keypair - if (this.isHashable(pass)){ - option=option||{} - option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER + }, + 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) // nacl的seed要求是32字节 return { hash: hashBuf.toString('hex'), pubkey: Buffer.from(keypair.publicKey).toString('hex'), // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 - seckey: Buffer.from(keypair.secretKey).toString('hex') + seckey: Buffer.from(keypair.secretKey).toString('hex'), } } return null - } - , - entropy2secword(entropy){ // entropy could be hex string or buffer. Byte length could be of 16, 20, 24, 28, ... which outputs mnemonic of length 12, 15, 18, 21, ... + }, + entropy2secword(entropy) { + // entropy could be hex string or buffer. Byte length could be of 16, 20, 24, 28, ... which outputs mnemonic of length 12, 15, 18, 21, ... return bip39.entropyToMnemonic(entropy) // results are the same for the same entropy. - } - , - secword2entropy(secword){ // secword could be of length 12, 15, 18, ... which outputs hex of length 32, 40, ... + }, + secword2entropy(secword) { + // secword could be of length 12, 15, 18, ... which outputs hex of length 32, 40, ... return bip39.mnemonicToEntropy(secword) // results are the same for the same secword. - } - , - secword2keypair(secword, option){ + }, + 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/), 其中 + // 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) @@ -240,104 +241,114 @@ module.exports = { // 据测试, Purpose和CoinType都可以任意其他值,不必要如规范所示;' 引号可有可无,导致的密钥不一样; // Account 最大为 0x7FFFFFFF, Change/Index 最大均为 0xFFFFFFFF(=4294967295) // 但可以不断延伸下去:/xxx/xxx/xxx/xxx/... - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + option = option || {} + option.coin = my.COIN_LIST.indexOf(option.coin) >= 0 ? option.coin : my.COIN - if(option.tool==='nacl') { + 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() + 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, 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字节密钥。 + seckey: Buffer.from(keypair.secretKey).toString('hex'), // nacl.sign.keyPair.fromSeed 得到的 seckey 是64字节的,不同于比特币/以太坊的32字节密钥。 } - }else { + } 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': default: key=hdmaster.derive("m/44'/60000'/0'/0/0"); break + 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': + default: + key = hdmaster.derive("m/44'/60000'/0'/0/0") + break } - }else { // 指定了路径 option.path,例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1" - key=hdmaster.derive(option.path) + } else { + // 指定了路径 option.path,例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1" + key = hdmaster.derive(option.path) } return { coin: option.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') + pubkey: key.publicKey.toString('hex'), } } return null - } - , - seed2path(seed, {coin='TIC'}={coin:'TIC'}){ + }, + seed2path(seed, { coin = 'TIC' } = { coin: 'TIC' }) { // 路径规范 BIP44: m/Purpose'/Coin'/Account'/Change/Index, // 但实际上 Purpose, Coin 都可任意定;' 可有可无; // Account/Change/Index 最大到 parseInt(0x7FFFFFFF, 16) - // 后面还可继续延伸 /xxx/xxx/xxx/...... - let hash=this.hash(seed, {hasher:'md5'}) - let part0=parseInt(hash.slice(0,6), 16) - let part1=parseInt(hash.slice(6,12), 16) - let part2=parseInt(hash.slice(12,18), 16) - let part3=parseInt(hash.slice(18,24), 16) - let part4=parseInt(hash.slice(24,30), 16) - let path=`${part0}'/${part1}/${part2}/${part3}/${part4}/${part5}` - switch (coin){ - case 'BTC': return `m/44'/0'/${path}` - case 'ETH': return `m/44'/60'/${path}` - case 'TIC': default: return `m/44'/60000'/${path}` + // 后面还可继续延伸 /xxx/xxx/xxx/...... + let hash = this.hash(seed, { hasher: 'md5' }) + let part0 = parseInt(hash.slice(0, 6), 16) + let part1 = parseInt(hash.slice(6, 12), 16) + let part2 = parseInt(hash.slice(12, 18), 16) + let part3 = parseInt(hash.slice(18, 24), 16) + let part4 = parseInt(hash.slice(24, 30), 16) + let path = `${part0}'/${part1}/${part2}/${part3}/${part4}/${part5}` + switch (coin) { + case 'BTC': + return `m/44'/0'/${path}` + case 'ETH': + return `m/44'/60'/${path}` + case 'TIC': + default: + return `m/44'/60000'/${path}` } - } - , - 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) + }, + 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) { if (option.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(uncompressedPubkey, { coin: 'ETH' }) + } else { + kp.address = this.pubkey2address(kp.pubkey, option) } - return kp + 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) + }, + 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) { let address if (option.coin === 'ETH') { - address = this.pubkey2address(this.decompressPubkey(kp.pubkey), {coin:'ETH'}) - }else { + address = this.pubkey2address(this.decompressPubkey(kp.pubkey), { coin: 'ETH' }) + } else { address = this.pubkey2address(kp.pubkey, option) } return address } 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 + }, + 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(不加参数) 默认为 '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'), option.compress !== false)) // 可用于浏览器。secp256k1缺省或true时输出压缩公钥,false时输出非压缩公钥。 // 或者 bitcorelib.PublicKey.fromPrivateKey(new bitcorelib.PrivateKey(seckey)).toString('hex') // 可用于浏览器 // 或者 const ecc = require('eccrypto') // if (option.compress===false){ @@ -346,52 +357,55 @@ module.exports = { // 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')) + } 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)){ + }, + 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}) + if (option.coin === 'ETH') { + pubkey = this.seckey2pubkey(seckey, { compress: false }) return this.pubkey2address(pubkey, option) - }else { - pubkey = this.seckey2pubkey(seckey, {compress:true}) + } 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'){ + }, + 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) { + if (pubkey.length === 66) { pubkey = this.decompressPubkey(pubkey) } - return keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40) - }else { + 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, world}={}){ + }, + position2address(position, { coin, world } = {}) { if (!/^[\da-fA-F]{40}$/.test(position)) return null // 不论 tic, btc, eth,其 position 都是 40字符的。 - coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN + coin = my.COIN_LIST.indexOf(coin) >= 0 ? coin : my.COIN let address - if (coin==='ETH'){ // 对以太坊,按照 EIP55,把纯位置转换为大小写敏感能自我验证的hex地址。仍然为20节=40符。 + if (coin === 'ETH') { + // 对以太坊,按照 EIP55,把纯位置转换为大小写敏感能自我验证的hex地址。仍然为20节=40符。 position = position.toLowerCase().replace('0x', '') let hash = keccak('keccak256').update(position).digest('hex') address = '0x' @@ -403,82 +417,107 @@ module.exports = { } } return address - }else if (coin === 'BTC'){ // 对比特币,把纯位置转换为大小写敏感能自我验证的bs58check地址:先加前缀1,再加校验4,共25字节,再转base58。得到26~34个字符,大多数34个。 + } else if (coin === 'BTC') { + // 对比特币,把纯位置转换为大小写敏感能自我验证的bs58check地址:先加前缀1,再加校验4,共25字节,再转base58。得到26~34个字符,大多数34个。 let prefix switch (world) { - 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' + 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 + address = bs58check.encode(Buffer.from(prefix + position, 'hex')) // wallet import format return address - }else { // 默认为 TIC。把纯位置转换为大小写敏感能自我验证的 b64u(base64 for url) 地址。 + } else { + // 默认为 TIC。把纯位置转换为大小写敏感能自我验证的 b64u(base64 for url) 地址。 let prefix - switch (world){ + switch (world) { // Base58: https://en.bitcoin.it/wiki/List_of_address_prefixes // Base64: https://baike.baidu.com/item/base64 - case 'earth': prefix='4c'; break; // Base58: 0x42=66 => T, Base64: base64 T=0x13=0b00010011 => 0b010011xx = 0x4c~4f - case 'moon': prefix='b4'; break; // Base58: 0x7f=127,0x80=128 => t, Base64: t=0x2d=0b00101101 => 0b101101xx = 0xB4~B7 - case 'w1dev': prefix='74'; break; // Base58: 0x90 => d, Base 64: d=0x1d=0b00011101 => 0b 011101xx = 0x74~77 - default: prefix='4c' + case 'earth': + prefix = '4c' + break // Base58: 0x42=66 => T, Base64: base64 T=0x13=0b00010011 => 0b010011xx = 0x4c~4f + case 'moon': + prefix = 'b4' + break // Base58: 0x7f=127,0x80=128 => t, Base64: t=0x2d=0b00101101 => 0b101101xx = 0xB4~B7 + case 'w1dev': + 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。 + 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)){ + }, + address2position() { + if (/^0x[\da-fA-F]{40}$/.test(address)) { return address.toLowerCase() - }else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address)){ + } 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)){ // 格式合法 + } 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) { + if (this.hash(this.hash(position)).slice(0, 6) === checksum) { return position } } return null - } - , - isAddress(address){ - if (/^(0x)?[\da-fA-F]{40}$/.test(address)){ + }, + isAddress(address) { + if (/^(0x)?[\da-fA-F]{40}$/.test(address)) { return 'ETH' - }else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address) && address.length!==32){ // 格式合法。常见的是 33或34字符长度 + } else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address) && address.length !== 32) { + // 格式合法。常见的是 33或34字符长度 let prefixedPosition = this.b58c2hex(address) - if (prefixedPosition && prefixedPosition.length===42) // 内容合法 - return 'BTC' - }else if (/^[Ttd][0-9a-zA-Z\-_]{31}$/.test(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})$/) // 内容合法 - if (this.hash(this.hash(prefix+position)).slice(0,6) === checksum) // [todo] 校验码里要不要包含 prefix? + if (this.hash(this.hash(prefix + position)).slice(0, 6) === checksum) + // [todo] 校验码里要不要包含 prefix? return 'TIC' } return null - } - , - pubkey2address (pubkey, option={}) { // pubkey 应当是string类型 - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + }, + 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字节的种子。其实 + }, + secword2seed(secword, pass) { + // 遵循bip39的算法。和 ether.HDNode.mnemonic2Seed 结果一样,是64字节的种子。其实 return bip39.mnemonicToSeedSync(secword, pass).toString('hex') // 结果一致于 new BitcoreMnemonic(secword).toSeed(pass).toString('hex') 或 ethers.HDNode.mnemonic2Seed(secword)。其实,bip39.mnemonicToSeedSync 也接受不合法的 secword,只要是个string就行。 - } - , - randomSecword(lang='english'){ // accepts case-insensitive lang, such as 'chinese, cn, tw, en' + }, + randomSecword(lang = 'english') { + // accepts case-insensitive lang, such as 'chinese, cn, tw, en' //// for BitcoreMnemonic // lang=lang.toUpperCase() // let language = { ZHCN: 'CHINESE', ENUS: 'ENGLISH', FRFR: 'FRENCH', ITIT: 'ITALIAN', JAJP: 'JAPANESE', KOKR: 'KOREAN', ESES: 'SPANISH' }[lang] @@ -486,264 +525,263 @@ module.exports = { // 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' } - langMap.chinese=langMap.cn=langMap.zh=langMap.china=langMap.zhcn - langMap.taiwanese=langMap.tw=langMap.zhtw - langMap.en=langMap.us=langMap.uk=langMap.enus - langMap.fr=langMap.france=langMap.frfr - langMap.it=langMap.italy=langMap.itit - langMap.ko=langMap.kr=langMap.korean=langMap.kokr - langMap.ja=langMap.jp=langMap.japan=langMap.jajp - + const langMap = { + zhcn: 'chinese_simplified', + zhtw: 'chinese_traditional', + enus: 'english', + frfr: 'french', + itit: 'italian', + jajp: 'japanese', + kokr: 'korean', + eses: 'spanish', + } + langMap.chinese = langMap.cn = langMap.zh = langMap.china = langMap.zhcn + langMap.taiwanese = langMap.tw = langMap.zhtw + langMap.en = langMap.us = langMap.uk = langMap.enus + langMap.fr = langMap.france = langMap.frfr + langMap.it = langMap.italy = langMap.itit + langMap.ko = langMap.kr = langMap.korean = langMap.kokr + langMap.ja = langMap.jp = langMap.japan = langMap.jajp + let language = 'english' - if (typeof(lang)==='string'){ + if (typeof lang === 'string') { lang = lang.toLowerCase() language = langMap[lang] || (bip39.wordlists[lang] ? lang : 'english') } bip39.setDefaultWordlist(language) return bip39.generateMnemonic() - } - , - randomSeckey(option){ // 跳过 secword 直接产生随机密钥 - option=option||{} - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN - if (option.tool==='nacl'){ + }, + randomSeckey(option) { + // 跳过 secword 直接产生随机密钥 + 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{ + } else { return crypto.randomBytes(32).toString('hex') // Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节 } - } - , - randomKeypair(option={}){ - option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN + }, + randomKeypair(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() + 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) + 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 + pubkey, } } - } - , - randomAccount(option={}){ - let secword=this.randomSecword(option.lang) + }, + randomAccount(option = {}) { + let secword = this.randomSecword(option.lang) return this.secword2account(secword, option) - } - , - randomString (length=6, alphabet) { // 长度为 length,字母表为 alphabet 的随机字符串 - alphabet = alphabet||"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%^&*@" + }, + 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 + }, + 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)) + if (typeof length === 'number' && length > 0) { + num = parseInt(Math.random() * Math.pow(10, length)) num = this.padStart(num.toString(), 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 为空 + } 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 - } - , - padStart(string, targetLength, symbol){ // 2020-03: 发现在浏览器里,还不支持 string.padStart(),只好自己写个暂代。 + }, + padStart(string, targetLength, symbol) { + // 2020-03: 发现在浏览器里,还不支持 string.padStart(),只好自己写个暂代。 let padLength = targetLength - string.length for (let index = 1; index <= padLength; index++) { string = symbol + string } return string - } - , - randomUuid:uuid.v4 - , - getMerkleHash(hashList, option){ + }, + 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){ + 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){ + }, + 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 (!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; + 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).subtract(new BigInt(hashSig,16)).abs().toString(16) + 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).subtract(new BigInt(hashSig, 16)).abs().toString(16) } return null - } - , - compareSig(hash, sig1, sig2){ // 返回距离hash更近的sig + }, + 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) { + } else if (dis1 > dis2) { 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){ + }, + buf2hex(buffer) { + // buffer is an ArrayBuffer + return Array.prototype.map.call(new Uint8Array(buffer), (x) => ('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{ + }, + b58c2hex(box) { + try { return bs58check.decode(box).toString('hex') - }catch(exception){ + } catch (exception) { return null } - } - , - hex2b64u(hex){ + }, + hex2b64u(hex) { if (/^[0-9a-fA-F]+$/.test(hex)) { - return Buffer.from(hex,'hex').toString('base64').replace(/\+/g,'-').replace(/\//g, '_') + return Buffer.from(hex, 'hex').toString('base64').replace(/\+/g, '-').replace(/\//g, '_') } return null - } - , - b64u2hex(b64u){ - if (/^[0-9a-zA-Z\-_]+$/.test(b64u)){ + }, + 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){ + }, + hex2eip55(hex) { if (/^(0x)?[\da-fA-F]*$/.test(hex)) { hex = hex.toLowerCase().replace('0x', '') let hash = keccak('keccak256').update(hex).digest('hex') @@ -758,61 +796,60 @@ module.exports = { return result } return null - } - , + }, aiid2regcode(aiid) { const alphabet = 'e5fcdg3hqa4b1n0pij2rstuv67mwx89klyz' const base = 16367 - let num = (aiid+base)*(base - alphabet.length) + let num = (aiid + base) * (base - alphabet.length) let code = '' let mod - while ( num > 0) { - mod = num % alphabet.length; - num = (num - mod) / alphabet.length - code = code+alphabet[mod] // 倒序存放 + while (num > 0) { + mod = num % alphabet.length + num = (num - mod) / alphabet.length + code = code + alphabet[mod] // 倒序存放 } return code - } - , + }, regcode2aiid(code) { const alphabet = 'e5fcdg3hqa4b1n0pij2rstuv67mwx89klyz' const base = 16367 let len = code.length let num = 0 - for (let i=0; i < len; i++) { - num += alphabet.indexOf(code[i]) * Math.pow(alphabet.length, i) + for (let i = 0; i < len; i++) { + num += alphabet.indexOf(code[i]) * Math.pow(alphabet.length, i) } - return num/(base - alphabet.length)-base - } - , + return num / (base - alphabet.length) - base + }, // 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})$/) + 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 (/[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) { + 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 的非压缩公钥 + 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 + 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' + this.padStart(x.toString(16), 64, '0') + this.padStart(y.toString(16), 64, '0') - } -} \ No newline at end of file + }, +}