删掉所有 option 参数,换用具体的 {...}

This commit is contained in:
陆柯 2021-05-08 22:34:01 +08:00
parent 8780cd1c40
commit 8873cd8884

169
index.js
View File

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