From 3dee1c63dd7c90762d690d114caa803e56106e92 Mon Sep 17 00:00:00 2001 From: "luk.lu" Date: Sat, 23 Jul 2022 10:08:31 +0800 Subject: [PATCH] rename pass2keypair to pass_to_keypair --- src/utils/crypto.js | 678 +++++++++++++++++++++++++------------------- 1 file changed, 379 insertions(+), 299 deletions(-) diff --git a/src/utils/crypto.js b/src/utils/crypto.js index 749d908..5ff4403 100644 --- a/src/utils/crypto.js +++ b/src/utils/crypto.js @@ -1,321 +1,401 @@ -const crypto = require('crypto'); -const nacl = require('tweetnacl'); -const bs58check = require('bs58check'); -const { keccak256 } = require('js-sha3'); -const Secword = require('bitcore-mnemonic'); // https://bitcore.io/api/mnemonic/ https://github.com/bitpay/bitcore-mnemonic +const crypto = require('crypto') +const nacl = require('tweetnacl') +const bs58check = require('bs58check') +const { keccak256 } = require('js-sha3') +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为默认输入输出格式,方便人的阅读,以及方便函数之间统一接口 +// 全部以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 = 'BTC'; // 默认的币种 -my.COIN_LIST = ['TIC', 'BTC', 'ETH']; -my.CHAINNET = 'mainnet'; // 默认的链网 +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 = 'BTC' // 默认的币种 +my.COIN_LIST = ['TIC', 'BTC', 'ETH'] +my.CHAINNET = 'mainnet' // 默认的链网 module.exports = { - hash: function (data, option) { // data can be anything, but converts to string or remains be Buffer/TypedArray/DataView - if (typeof(data) !== 'boolean' && data !== Infinity) { - 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; + hash: function (data, option) { + // data can be anything, but converts to string or remains be Buffer/TypedArray/DataView + if (typeof data !== 'boolean' && data !== Infinity) { + 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) } - , - isHashable: function (data, option) { - option = option || {}; - if (option.strict) { - return data && typeof (data) !== 'boolean' && data !== Infinity; // 允许大多数数据,除了空值、布尔值、无限数 - } - return typeof (data) !== 'undefined'; // 允许一切数据,除非 undefined + return null + }, + isHashable: function (data, option) { + option = option || {} + if (option.strict) { + return data && typeof data !== 'boolean' && data !== Infinity // 允许大多数数据,除了空值、布尔值、无限数 } - , - 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; + 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) } - , - 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, option)); //哈希加盐 - 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; + 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, option) + ) //哈希加盐 + 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 } - , - 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, option)); // 哈希加盐 - let decrypted = decipher.update(data, inputEncoding, outputEncoding); - decrypted += decipher.final(outputEncoding); // 但是 Buffer + Buffer 还是会变成string - try { - JSON.parse(decrypted); - } catch (exception) { - return null; - } - return decrypted; - } - return null; + 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, option) + ) // 哈希加盐 + let decrypted = decipher.update(data, inputEncoding, outputEncoding) + decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string + try { + JSON.parse(decrypted) + } catch (exception) { + return null + } + return decrypted } - , - 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 || {}; + 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字节 + // 使用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格式,因此这样写是不能用的。 + // 方案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 + }, + pass_to_keypair: 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 = { coin: my.COIN, path: 'master' } + ) { + // option.coin 币种;option.passphase 密码,默认为空;option.path==='master' 生成 HD master key,不定义则默认为相应币种的第一对公私钥。 + if (Secword.isValid(secword)) { + const { coin = my.COIN, path = 'master', pass = '' } = option + // 用 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(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 (path === 'master') { + key = hdmaster + } else if (!path) { + switch (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 } - return null; + } else { + // 指定了路径 option.path,例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1" + key = hdmaster.derive(path) + } + return { + coin, + path, + seckey: key.privateKey.toString('hex'), // 或者 key.toJSON().privateKey。或者 key.privateKey.slice(2) 删除开头的'0x'如果是ethers.HDNode.fromMnemonic(secword)的结果 + pubkey: key.publicKey.toString('hex') + } } - , - isSignature: function (signature) { - return /^[a-fA-F0-9]{128}$/.test(signature); + return null + }, + seckey2pubkey: function (seckey, option = {}) { + if (this.isSeckey(seckey) && seckey.length === 64) { + // 只能用于32字节的私钥(BTC, ETH)。也就是不能用于 TIC 的私钥。 + const { curve = my.CURVE, compress = 'compressed' } = option + 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') 得到的公钥与上面的不同 } - , - 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 + }, + 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 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类型 + if (this.isPubkey(pubkey)) { + const { coin = 'BTC', netType = 'mainnet', curve = my.CURVE } = option + let h256 = crypto + .createHash('sha256') + .update(Buffer.from(pubkey, 'hex')) + .digest() + let h160 = crypto + .createHash('ripemd160') + .update(h256) + .digest('hex') + let prefix + if (coin === 'BTC') { + switch (netType) { + case 'mainnet': + prefix = '00' + break // 1 + case 'testnet': + prefix = '6f' + break // m or n + case 'p2sh': + prefix = '05' + break // 3 + default: + prefix = '00' } - return null; + return bs58check.encode(Buffer.from(prefix + h160, 'hex')) // wallet import format + } else if (coin === 'ETH') { + // 目前不支持 ETH或其他币种 地址转换,因为这会大量增加前端打包的js。 + const uncompressedPubkey = crypto.ECDH.convertKey( + pubkey, + curve, + 'hex', + 'buffer', + 'uncompressed' + ) + return '0x' + keccak256(uncompressedPubkey.slice(1)).slice(24) + } } - , - 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; + 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) } - , - secword2keypair: function (secword, option = {coin: my.COIN, path: 'master'}) { // option.coin 币种;option.passphase 密码,默认为空;option.path==='master' 生成 HD master key,不定义则默认为相应币种的第一对公私钥。 - if (Secword.isValid(secword)) { - const { coin = my.COIN, path = 'master', pass = '' } = option; - // 用 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(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 (path === 'master') { - key = hdmaster; - } else if (!path) { - switch (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(path); - } - return { - coin, - path, - seckey: key.privateKey.toString('hex'), // 或者 key.toJSON().privateKey。或者 key.privateKey.slice(2) 删除开头的'0x'如果是ethers.HDNode.fromMnemonic(secword)的结果 - pubkey: key.publicKey.toString('hex') - }; - - } - return null; + 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 () { + return Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节 + }, + randomKeypair: function () { + // 此函数有错!! + let kp = nacl.box.keyPair() + const seckey = Buffer.from(kp.secretKey).toString('hex') + const pubkey = this.seckey2pubkey(seckey) + return { + seckey, + pubkey } - , - seckey2pubkey: function (seckey, option = {}) { - if (this.isSeckey(seckey) && seckey.length === 64) { // 只能用于32字节的私钥(BTC, ETH)。也就是不能用于 TIC 的私钥。 - const {curve = my.CURVE, compress = 'compressed'} = option; - 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') 得到的公钥与上面的不同 - } - return null; + }, + 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)) } - , - 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; + return text + }, + randomNumber: function (option) { + // 长度为 option.length 的随机数字,或者 (option.min||0) <= num < option.max + option = option || {} + let 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() } - , - secword2address: function (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.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类型 - if (this.isPubkey(pubkey)) { - const { coin = 'BTC', netType = 'mainnet', curve = my.CURVE } = option; - let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest(); - let h160 = crypto.createHash('ripemd160').update(h256).digest('hex'); - let prefix; - if (coin === 'BTC') { - switch (netType) { - case 'mainnet': prefix = '00'; break; // 1 - case 'testnet': prefix = '6f'; break; // m or n - case 'p2sh': prefix = '05'; break; // 3 - default: prefix = '00'; - } - return bs58check.encode(Buffer.from(prefix + h160, 'hex')); // wallet import format - } else if (coin === 'ETH') { // 目前不支持 ETH或其他币种 地址转换,因为这会大量增加前端打包的js。 - const uncompressedPubkey = crypto.ECDH.convertKey(pubkey, curve, 'hex', 'buffer', 'uncompressed'); - return '0x' + keccak256(uncompressedPubkey.slice(1)).slice(24); - } - } - 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 () { - return Buffer.from(nacl.box.keyPair().secretKey).toString('hex'); // 32字节 - } - , - randomKeypair: function () { - // 此函数有错!! - let kp = nacl.box.keyPair(); - const seckey = Buffer.from(kp.secretKey).toString('hex'); - const pubkey = this.seckey2pubkey(seckey); - return { - seckey, pubkey - }; - } - , - 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 || {}; - let 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; - } - , - rsaSign: function (string2Sign, prikey, signType) { - signType = signType || 'RSA-SHA1'; // could be RSA-SHA256, RSA-SHA1 or more - let signer = crypto.createSign(signType); - return encodeURIComponent(signer.update(string2Sign).sign(prikey, 'base64')); - } - , - rsaVerify: function (string2Verify, sign, pubkey, signType) { - signType = signType || 'RSA-SHA1'; // could be RSA-SHA256, RSA-SHA1 or more - let verifier = crypto.createVerify(signType); - return verifier.update(string2Verify).verify(pubkey, sign, 'base64'); - } -}; \ No newline at end of file + return num + }, + rsaSign: function (string2Sign, prikey, signType) { + signType = signType || 'RSA-SHA1' // could be RSA-SHA256, RSA-SHA1 or more + let signer = crypto.createSign(signType) + return encodeURIComponent(signer.update(string2Sign).sign(prikey, 'base64')) + }, + rsaVerify: function (string2Verify, sign, pubkey, signType) { + signType = signType || 'RSA-SHA1' // could be RSA-SHA256, RSA-SHA1 or more + let verifier = crypto.createVerify(signType) + return verifier.update(string2Verify).verify(pubkey, sign, 'base64') + } +}