tic-crypto/index.js

677 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// const BigNumber=require('bignumber.js') // 处理整数 https://github.com/MikeMcl/bignumber.js
const BigInt = require("big-integer") // 处理整数 https://github.com/peterolson/BigInteger.js
const crypto=require('crypto')
const nacl = require('tweetnacl')
const bs58check = require('bs58check')
const uuid = require('uuid')
const keccak = require('keccak')
const eccrypto = require('eccrypto') // 用于加解密。不知道怎么用 crypto 本身的加解密。
const keyman = require('js-crypto-key-utils') // 转换原始密钥和 PER/DER 格式。
const 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 里的相同功能
// const bitcorelib = require('bitcore-lib')
// const secp256k1 = require('secp256k1')
// 全部以hex为默认输入输出格式方便人的阅读以及方便函数之间统一接口
const my={}
my.HASHER='sha256' // 默认的哈希算法。could be md5, sha1, sha256, sha512, ripemd160 and much more。 可用 Crypto.getHashes/Ciphers/Curves() 查看支持的种类。
my.HASHER_LIST=crypto.getHashes()
my.CIPHER='aes-256-cfb' // 默认的加解密算法
my.CIPHER_LIST=crypto.getCiphers()
my.CURVE='secp256k1' // 默认的ECDH曲线用于把私钥转成公钥。
my.CURVE_LIST=['secp256k1'] // crypto.getCurves() 引入到浏览器里后出错,不支持 getCurves.
my.OUTPUT='hex' // 默认的哈希或加密的输入格式
my.OUTPUT_LIST=['hex','latin1','base64'] // or 'buf' to Buffer explicitly
my.INPUT='utf8' // 默认的加密方法的明文格式。utf8 能够兼容 latin1, ascii 的情形
my.INPUT_LIST=['utf8', 'ascii', 'latin1'] // ignored for Buffer/TypedArray/DataView
my.COIN='TIC' // 默认的币种
my.COIN_LIST=['TIC','BTC','ETH']
my.CHAINNET='mainnet' // 默认的链网
module.exports = {
hash(data, option){ // data can be anything, but converts to string or remains be Buffer/TypedArray/DataView
if (this.isHashable(data)) {
option=option||{}
if (typeof(data)!=='string' && !(data instanceof Buffer) && !(data instanceof DataView))
data=JSON.stringify(data)
if (option.salt && typeof(option.salt)==='string')
data=data+this.hash(option.salt)
let hasher= my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER // 默认为 sha256.
let inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView.
let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) // option.output: 留空=》默认输出hex格式或者手动指定 'buf', hex', 'latin1' or 'base64'
return crypto.createHash(hasher).update(data, inputEncoding).digest(outputEncoding)
}
return null
}
,
isHashable(data, option){
option=option||{}
if (option.strict) {
return data && typeof(data)!=='boolean' && data!==Infinity // 允许大多数数据,除了空值、布尔值、无限数
}
return typeof(data)!=='undefined' // 允许一切数据,除非 undefined
}
,
isHash(hash, option){
option=option||{}
option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
switch(option.hasher){
case 'sha256': return /^[a-fA-F0-9]{64}$/.test(hash)
case 'md5': return /^[a-fA-F0-9]{32}$/.test(hash)
case 'ripemd160': case 'sha1': return /^[a-fA-F0-9]{40}$/.test(hash)
case 'sha512': return /^[a-fA-F0-9]{128}$/.test(hash)
}
return false
}
,
async encrypt(data, {keytype, key, input, output, cipher}={}){
if (keytype==='pwd') {
if (this.isHashable(data) && typeof(key)==='string') {
let inputEncoding=my.INPUT_LIST.indexOf(input)>=0?input:my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView
let outputEncoding=(output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(output)>=0?output:my.OUTPUT) // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly
let cipher=crypto.createCipher(
my.CIPHER_LIST.indexOf(cipher)>=0?cipher:my.CIPHER,
this.hash(key))
if (typeof(data)!=='string' && !(data instanceof Buffer) && !(data instanceof DataView))
data=JSON.stringify(data)
let encrypted = cipher.update(data, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
return encrypted
}
}else if (keytype==='pubkey') { // data 应当是 utf8 的字符串。// 但在浏览器里不能使用 Failed to execute 'encrypt' on 'SubtleCrypto': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'
let cipherobject = await eccrypto.encrypt(this.hex2buf(key), data)
return cipherobject
}
return null
}
,
async decrypt(data, {keytype, key, input, output, cipher, format}={}){ // data 应当是 encrypt 输出的数据类型
if (keytype==='pwd') {
if (data && (typeof(data)==='string' || data instanceof Buffer) && typeof(key)==='string') {
let inputEncoding=my.OUTPUT_LIST.indexOf(input)>=0?input:my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer
let outputEncoding=(output==='buf')?undefined:(my.INPUT_LIST.indexOf(output)>=0?output:my.INPUT) // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly
let decipher=crypto.createDecipher(
my.CIPHER_LIST.indexOf(cipher)>=0?cipher:my.CIPHER,
this.hash(key))
let decrypted = decipher.update(data, inputEncoding, outputEncoding)
decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
if (format==='json') { // 如果用户输入错误密码deciper也能返回结果。为了判断是否正确结果对应当是 json 格式的原文做解析来验证。
try{
JSON.parse(decrypted)
}catch(exception){
return null
}
}
return decrypted
}else if (keytype==='seckey'){ // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象
let plaindata = await eccrypto.decrypt(Buffer.from(key, 'hex'), data) // eccrypto 需要调用 Buffer.compare 方法,不能在这里直接用 hex2buf
return plaindata.toString('utf8')
}
}
return null
}
,
/* 以下两个方法基于 eccrypto注意其在浏览器上会有问题。
*/
async encryptPubkey(plaindata, pubkey, option){ // plaindata 应当是 utf8 的字符串
let cipherobject = await eccrypto.encrypt(this.hex2buf(pubkey), plaindata)
return cipherobject
}
,
async decryptSeckey(cipherobject, seckey, option){ // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象
let plaindata
if (Buffer) { // nodejs
plaindata = await eccrypto.decrypt(Buffer.from(seckey, 'hex'), cipherobject) // eccrypto 需要调用 Buffer.compare 方法
}else { // browser
plaindata = await eccrypto.decrypt(this.hex2buf(seckey), cipherobject)
}
return plaindata.toString('utf8')
}
,
async sign(data, seckey, option) { // data can be string or buffer or object, results are the same
if (this.isHashable(data) && this.isSeckey(seckey)) {
option=option||{}
// 方案1: 使用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: 纯 crypto
let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), {namedCurve:'P-256K'}).export('pem')
let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
let 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)
signer.update(data, inputEncoding).end()
let signature = signer.sign(seckeyPEM, outputEncoding)
return signature // 发现同样的输入,每次调用会生成不同的 signature, 但都可以通过 verify。有一次我竟然徒手修改出一个新签名也通过验证。
}
return null
}
,
isSignature(signature){
return /^[a-fA-F0-9]{128,144}$/.test(signature)
}
,
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)){
// 方案1: nacl
// 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
// 方案2: 纯 crypto
let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), {namedCurve:'P-256K'}).export('pem')
let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
let 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 verifier = crypto.createVerify(hasher)
verifier.update(data, inputEncoding).end() // end() 在 nodejs 12 里返回verifier自身但在浏览器里返回 undefined因此不能串联运行。
let verified = verifier.verify(pubkeyPEM, signature, 'hex')
return verified
}
return null
}
,
pass2keypair(pass, option){ // 如果使用其他机制例如密码、随机数不使用secword也可生成keypair
if (this.isHashable(pass)){
option=option||{}
option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
var hashBuf = crypto.createHash(option.hasher).update(pass).digest()
var keypair = nacl.sign.keyPair.fromSeed(hashBuf)
return {
hash: hashBuf.toString('hex'),
pubkey: Buffer.from(keypair.publicKey).toString('hex'), // 测试过 不能直接keypair.publicKey.toString('hex')不是buffer类型
seckey: Buffer.from(keypair.secretKey).toString('hex')
}
}
return null
}
,
secword2keypair(secword, option){
// option.coin 币种;
// option.passphase 密码,默认为空;
// option.path==='master' 生成 HD master key不定义则默认为相应币种的第一对公私钥。
// path 规范为 m/Purpose'/CoinType'/Account'/Change/Index (https://learnblockchain.cn/2018/09/28/hdwallet/), 其中
// Purpose===44 for BIP44,
// CoinType===0 for BTC, 60 for ETH. (https://github.com/satoshilabs/slips/blob/master/slip-0044.md)
// Change===常量 0 用于外部链,常量 1 用于内部链(也称为更改地址)。外部链用于在钱包外可见的地址(例如,用于接收付款)。内部链用于在钱包外部不可见的地址,用于返回交易变更。 (所以一般使用 0)
// Index 地址索引,从 0 开始,代表生成第几个地址,官方建议,每个 account 下的 address_index 不要超过 20。
// 据测试, Purpose和CoinType都可以任意其他值不必要如规范所示' 引号可有可无,导致的密钥不一样;
// Account 最大为 0x7FFFFFFF, Change/Index 最大均为 0xFFFFFFFF(=4294967295)
// 但可以不断延伸下去:/xxx/xxx/xxx/xxx/...
if (Secword.isValid(secword)){
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if(option.coin==='TIC') {
// 采用自己的算法bip39算法从secword到种子hash后用 nacl.sign.keyPair.fromSeed()方法。
option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
let hashBuf=crypto.createHash(option.hasher).update(this.secword2seed(secword, option.pass)).digest()
let keypair = nacl.sign.keyPair.fromSeed(hashBuf) // nacl.sign.keyPair.fromSeed 要求32字节的种子而 this.secword2seed生成的是64字节种子所以要先做一次sha256
return {
coin: option.coin,
pubkey: Buffer.from(keypair.publicKey).toString('hex'), // 测试过 不能直接keypair.publicKey.toString('hex')不是buffer类型
seckey: Buffer.from(keypair.secretKey).toString('hex') // nacl.sign.keyPair.fromSeed 得到的 seckey 是64字节的不同于比特币/以太坊的32字节密钥。
}
}else {
// 用 bip39 算法从 secword 到种子,再用 bip32 算法从种子到根私钥。这是比特币、以太坊的标准方式,结果一致。
// let hdmaster=HDKey.fromMasterSeed(new Buffer(this.secword2seed(secword, option.pass), 'hex')) // 和 new Secword(secword).toHDPrivateKey 求出的公私钥一样!
let hdmaster=new Secword(secword).toHDPrivateKey(option.pass) // 和 ethers.HDNode.fromMnemonic(secword)的公私钥一样。而 ethers.HDNode.fromMnemonic(secword).derivePath("m/44'/60'/0'/0/0")的公私钥===ethers.Wallet.fromMnemonic(secword [,"m/44'/60'/0'/0/0"])
let key=hdmaster
if (option.path==='master'){
key=hdmaster
}else if (!option.path) {
switch(option.coin){
case 'BTC': key=hdmaster.derive("m/44'/0'/0'/0/0"); break
case 'ETH': key=hdmaster.derive("m/44'/60'/0'/0/0"); break
default: key=hdmaster.derive("m/44'/99'/0'/0/0"); break
}
}else { // 指定了路径 option.path例如 "m/44'/0'/0'/0/6" 或 "m/0/2147483647'/1"
key=hdmaster.derive(option.path)
}
return {
coin: option.coin,
seckey: key.privateKey.toString('hex'), // 或者 key.toJSON().privateKey。或者 key.privateKey.slice(2) 删除开头的'0x'如果是ethers.HDNode.fromMnemonic(secword)的结果
pubkey: key.publicKey.toString('hex')
}
}
}
return null
}
,
secword2account(secword, option){ // account 比 keypair 多了 address 字段。
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
let kp=this.secword2keypair(secword, option)
if (kp) {
kp.address=this.seckey2address(kp.seckey, option)
return kp
}
return null
}
,
secword2address(secword, option){
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
let kp=this.secword2keypair(secword, option)
if (kp) {
return this.seckey2address(kp.seckey,option)
}
return null
}
,
seckey2pubkey(seckey, option){
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (this.isSeckey(seckey) && seckey.length===64){ // 只能用于32字节的私钥BTC, ETH)。也就是不能用于 TIC 的私钥。
let curve = my.CURVE_LIST.indexOf(option.curve)>=0?option.curve:my.CURVE // 默认为 secp256k1
return new crypto.createECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex', option.compress===false?'uncompressed':'compressed') // ecdh.getPublicKey(不加参数) 默认为 'uncompressed'
// 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。但可惜,浏览器里不存在 crypto.ECDH。
// 或者 return this.buf2hex(require('secp256k1').publicKeyCreate(Buffer.from(seckey, 'hex'), option.compress!==false)) // 可用于浏览器。secp256k1缺省或true时输出压缩公钥false时输出非压缩公钥。
// 或者 bitcorelib.PublicKey.fromPrivateKey(new bitcorelib.PrivateKey(seckey)).toString('hex') // 可用于浏览器
// 或者 const ecc = require('eccrypto')
// if (option.compress===false){
// return ecc.getPublic(this.hex2buf(seckey)).toString('hex')
// }else{
// return ecc.getPublicCompressed(this.hex2buf(seckey)).toString('hex')
// }
// 注意Buffer.from(nacl.box.keyPair.fromSecretKey(Buffer.from(seckey,'hex')).publicKey).toString('hex') 得到的公钥与上面的不同
}else if (this.isSeckey(seckey) && seckey.length===128 && option.coin==='TIC'){ // 用于64字节=128 hex的 TIC 私钥
let keypair=nacl.sign.keyPair.fromSecretKey(Buffer.from(seckey,'hex'))
return Buffer.from(keypair.publicKey).toString('hex') // 测试过 不能直接keypair.publicKey.toString('hex')不是buffer类型
}
return null
}
,
seckey2address(seckey, option){
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (this.isSeckey(seckey)){
let pubkey
if (option.coin==='ETH'){
pubkey = this.seckey2pubkey(seckey, {compress:false})
return this.pubkey2address(pubkey, option)
}else {
pubkey = this.seckey2pubkey(seckey, {compress:true})
return this.pubkey2address(pubkey, option)
}
}
return null
}
,
isSecword(secword){
return Secword.isValid(secword)
}
,
isSeckey(seckey){
// 比特币、以太坊的私钥64 hex
// nacl.sign 的私钥 128 hex, nacl.box 的私钥 64 hex
return /^([a-fA-F0-9]{128}|[a-fA-F0-9]{64})$/.test(seckey)
}
,
isPubkey(pubkey){
// 比特币的公钥:压缩型 '02|03' + 64 hex 或 无压缩型 '04' + 128 hex
// 以太坊的公钥:'02|03' + 64 hex
// nacl.sign 的公钥64 hex
return /^((02|03)?[a-fA-F0-9]{64}|04[a-fA-F0-9]{128})$/.test(pubkey) // "d2f186a630f5558ba3ede10a4dd0549da5854eab3ed28ee8534350c2535d38b0"
}
,
isAddress (address) {
return /^[m|t|d|T][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{33}$/.test(address) // && address.length>25 && bs58check.decode(address.slice(1)) && ['A'].indexOf(address[0]>=0)) {
}
,
pubkey2position (pubkey, {coin}={}){
coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN
if(this.isPubkey(pubkey)){
if (coin==='ETH'){
// 注意必须要用非压缩的64字节的公钥的buffer并去掉开头的 04。
if (pubkey.length===66) {
pubkey = this.decompressPubkey(pubkey)
}
return keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40)
}else {
let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest()
let h160 = crypto.createHash('ripemd160').update(h256).digest('hex')
return h160
}
}
return null
}
,
position2address(position, {coin, netType}={}){
if (!position) return null
coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN
let address
if (coin==='ETH'){ // 对以太坊,按照 EIP55把纯位置转换为大小写敏感能自我验证的hex地址
position = position.toLowerCase().replace('0x', '')
let hash = keccak('keccak256').update(position).digest('hex')
address = '0x'
for (var i = 0; i < position.length; i++) {
if (parseInt(hash[i], 16) >= 8) {
address += position[i].toUpperCase()
} else {
address += position[i]
}
}
return address
}else if (coin === 'BTC'){ // 对比特币把纯位置转换为大小写敏感能自我验证的bs58check地址
let prefix
switch (netType) {
case 'mainnet': prefix='00'; break; // pubkey hash => 1
case 'mainnetSh': prefix='05'; break; // script hash => 3
case 'testnet': prefix='6f'; break; // testnet pubkey hash => m or n
case 'testnetSh': prefix='c4'; break // testnet script hash => 2
case 'namecoin': prefix='34'; break; // Namecoin pubkey hash => M or N
case 'compact': prefix='15'; break; // compact pubkey (proposed) => 4
default: prefix='00'
}
address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format
return address
}else {
let prefix
switch (netType){
case 'mainnet': prefix='42'; break; // '42'=>T, '6E'=>m
case 'testnet': prefix='7F'; break; // '7F'=>t
case 'devnet': prefix='5A'; break; // '5A'=>d
default: prefix='42' // 默认暂且为 42为了兼容已经运行的链。
}
address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format
return address
}
return null
}
,
pubkey2address (pubkey, option={}) { // pubkey 应当是string类型
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (this.isPubkey(pubkey)) {
let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest()
let h160 = crypto.createHash('ripemd160').update(h256).digest('hex')
let prefix
if (option.coin==='TIC'){
switch (option.netType){
case 'mainnet': prefix='42'; break; // '42'=>T, '6E'=>m
case 'testnet': prefix='7F'; break; // '7F'=>t
case 'devnet': prefix='5A'; break; // '5A'=>d
default: prefix='42' // 默认暂且为 42为了兼容已经运行的链。
}
}else if (option.coin==='BTC'){
switch (option.netType) {
case 'mainnet': prefix='00'; break; // 1
case 'testnet': prefix='6f'; break; // m or n
case 'p2sh': prefix='05'; break; // 3
default: prefix='00'
}
}else if (option.coin==='ETH'){
// 注意必须要用非压缩的64字节的公钥的buffer并去掉开头的 04。
return '0x' + keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40)
// 或 const { keccak256 } = require('ethereumjs-util'); keccak256(Buffer.from(pubkey.slice(2),'hex)).toString('hex').slice(40)
// 或 const { Keccak } = require('sha3'); new Keccak('').update(Bufer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40)
}else {
return null
}
var wifAddress=bs58check.encode(Buffer.from(prefix+h160,'hex')) // wallet import format
return wifAddress
}
return null
}
,
secword2seed(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(lang='ENGLISH'){ // Object.keys(Secword.Words) => [ 'CHINESE', 'ENGLISH', 'FRENCH', 'ITALIAN', 'JAPANESE', 'KOREAN', 'SPANISH' ]
let language = { zhCN: 'CHINESE', enUS: 'ENGLISH', frFR: 'FRENCH', itIT: 'ITALIAN', jaJP: 'JAPANESE', koKR: 'KOREAN', esES: 'SPANISH' }[lang]
|| (Secword.Words.hasOwnProperty(lang.toUpperCase()) ? lang.toUpperCase() : 'ENGLISH')
return new Secword(Secword.Words[language]).phrase
}
,
randomSeckey(option){ // todo: 使用 crypto.randomBytes(size)
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (option.coin==='TIC'){
return crypto.randomBytes(64).toString('hex') // Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节
}else{
return crypto.randomBytes(32).toString('hex') // Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节
}
}
,
randomKeypair(option){
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
let kp
if (option.coin==='TIC'){
kp=nacl.sign.keyPair()
}else{
kp=nacl.box.keyPair()
}
return {
seckey:Buffer.from(kp.secretKey).toString('hex'),
pubkey:Buffer.from(kp.publicKey).toString('hex')
}
}
,
randomString (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(option){ // 长度为 option.length 的随机数字,或者 (option.min||0) <= num < option.max
option=option||{}
var num=0
if (option.length>0){
num=parseInt(Math.random()*Math.pow(10,option.length))
let l = new String(num).length
while(l < option.length) {
num = '0' + num // 注意,这时返回的是字符串!
l++
}
}else if (option.max>0){
option.min = (option.min>=0)?option.min:0
num=parseInt(Math.random()*(option.max-option.min))+option.min
}else{ // 如果 option 为空
num=Math.random()
}
return num
}
,
randomUuid:uuid.v4
,
getMerkleHash(hashList, option){
// merkle算法略有难度暂时用最简单的hash代替
if(Array.isArray(hashList)){
option=option||{}
let output=(option.output==='buf')?undefined:(option.output||my.OUTPUT)
let hasher=crypto.createHash(my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER)
for (var hash of hashList){
hasher.update(hash)
}
return hasher.digest(output)
}
return null
}
,
getMerkleRoot(todoHashList, option){
//深拷贝传入数组,防止引用对象被改变
let hashList = [...todoHashList]
if(!Array.isArray(hashList))
return null
var border = hashList.length;
if(border == 0)
return this.hash('')
if(border == 1)
return this.hash(hashList[0]);
while(1){
let i = 1,j = 0;
for(; i < border; i = i + 2){
hashList[j] = this.hash(hashList[i - 1] + hashList[i]);
if(border == 2){
return hashList[0];
}
if(i + 1 == border) break;
j = j + 1;
if(i + 2 == border){
i = i + 1;
hashList[j] = this.hash(hashList[i]);
break;
}
}
border = j + 1;
}
return hashList
}
,
distanceSig(hash, sig){ // hash为64hex字符sig为128hex字符。返回用hex表达的距离。
if (this.isSignature(sig) && this.isHash(hash)){
var hashSig=this.hash(sig) // 把签名也转成32字节的哈希同样长度方便比较
return new BigInt(hash,16).sub(new BigInt(hashSig,16)).abs().toString(16)
}
return null
}
,
compareSig(hash, sig1, sig2){ // 返回距离hash更近的sig
if (this.isHash(hash)) {
if (this.isSignature(sig2) && this.isSignature(sig1)) {
var dis1=this.distanceSig(hash,sig1)
var dis2=this.distanceSig(hash,sig2)
if (dis1<dis2) {
return sig1
}else if (dis1>dis2) {
return sig2
}else if (dis1===dis2) { // 如果极其巧合的距离相等,也可能是一个在左、一个在右,那就按 signature 本身的字符串排序来比较。
return sig1<sig2 ? sig1 : sig1===sig2 ? sig1 : sig2
}
}else if (this.isSignature(sig2)){ // 允许其中一个signature是非法的例如undefined
return sig2
}else if (this.isSignature(sig1)){
return sig1
}
}
return null
}
,
sortSigList(hash, sigList) {
if (Array.isArray(sigList) && this.isHash(hash)){
sigList.sort(function(sig1, sig2){
if (this.isSignature(sig1) && this.isSignature(sig2)) {
var winner=this.compareSig(hash, sig1, sig2)
if (sig1===sig2) return 0
else if (winner===sig1) return -1
else if (winner===sig2) return 1
}else { // 如果 sig1 或 sig2 不是 signature 格式
throw 'Not a signature!'
}
})
return sigList
}
return null
}
,
/** 用于支付宝的接口
* 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
* @param $para 需要拼接的数组
* return 拼接完成以后的字符串
*/
getString2Sign (paramSet, converter, delimiter) {
if (paramSet && typeof paramSet==='object') {
var string2Sign = ''
var converter = converter || ''
var delimiter = delimiter || ''
for (var key of Object.keys(paramSet).sort()) {
var value=paramSet[key]
if (value && typeof value==='object'){ // 万一 bis_content 等对象直接送了进来。
value=JSON.stringify(value)
}
if ((typeof value==='string' && value!=='') || typeof value==='number') {
if (converter==='urlencode') value=encodeURIComponent(value)
string2Sign += (key + '=' + delimiter + value + delimiter + '&') // 根据产品、版本、请求或响应的不同有的需要key="vlaue"有的只要key=value。
}
}
string2Sign=string2Sign.replace(/&$/, '') // 删除末尾的 &
// if (get_magic_quotes_gpc()) { $string2Sign = stripslashes($string2Sign); }
// string2Sign=string2Sign.replace(/\\/g, ''); // 去除转义符 \ (似乎其实不去除,也完全不会影响,因为编程语言内部就会处理掉\)
// string2Sign=string2Sign.replace(/\//g, '\\/'); // 为了verify把正斜杠进行转义 / 参见 https://openclub.alipay.com/read.php?tid=559&fid=2
return string2Sign
}
return ''
}
,
rsaSign(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(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')
}
,
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才有。
}
,
hex2base58check(hex){
return bs58check.encode(Buffer.from(hex, 'hex'))
}
,
base58check2hex(box){
try{
return bs58check.decode(box).toString('hex').toUpperCase()
}catch(exception){
return null
}
}
,
hex2eip55(){
}
,
// test: https://iancoleman.io/bitcoin-key-compression/
// compress: https://hacpai.com/article/1550844562914
compressPubkey(uncompressed){
let [all, x, y]=uncompressed.toLowerCase().match(/^04(.{64})(.{64})$/)
if (/[1,3,5,7,9,b,d,f]$/.test(y)){
return '03'+x // y为奇数=>前缀03
}else{
return '02'+x // y为偶数=>前缀02
}
}
,
// uncompress: https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression/53478265#53478265
// https://en.bitcoin.it/wiki/Secp256k1
decompressPubkey(compressed){
// Consts for secp256k1 curve. Adjust accordingly
const prime = new BigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16) // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
const pIdent = new BigInt('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4);
var signY = new Number(compressed[1]) - 2
var x = new BigInt(compressed.substring(2), 16)
var y = x.modPow(3, prime).add(7).mod(prime).modPow( pIdent, prime ) // y mod p = +-(x^3 + 7)^((p+1)/4) mod p
if( y.mod(2).toJSNumber() !== signY ) { // If the parity doesn't match it's the *other* root
y = prime.subtract( y ) // y = prime - y
}
return '04' + x.toString(16).padStart(64, '0') + y.toString(16).padStart(64, '0')
}
}