Compare commits

..

No commits in common. "main" and "20190109_preview" have entirely different histories.

9 changed files with 490 additions and 2155 deletions

113
.gitignore vendored
View File

@ -1,113 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# how to include another gitignore?
# https://stackoverflow.com/questions/7005142/can-i-include-other-gitignore-file-in-a-gitignore-file-like-include-in-c-li
# https://github.com/github/gitignore
# https://github.com/SlideWave/gitignore-include?tab=readme-ov-file#examples
# https://gitignore.io
### .gitignore.global.txt ###
# Self defined pattern to ignore
?*.gitignore
?*.gitignore/
?*.gitignore.*
?*.gitignore.*/
*.gitomit
*.gitomit.*
*.gitomit/
*.gitomit.*/
*.nogit
*.nogit.*
*.nogit/
*.nogit.*/
# 保留
!.gitignore
!.gitignore.*
!.gitkeep
# 通用
.svn/
.deploy_git/
.idea/
.sass-cache/
.wrangler
/test/unit/coverage/
/test/e2e/reports/
node_modules/
*.aab
*.apk
*.ipa
*.min.js
*.min.css
*.min.html
*.iml
*.njsproj
*.ntvs*
*.sw*
*.sln
*.suo
.gitattributes
.umi
.umi-production
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
selenium-debug.log
Thumbs.db
thumbs.db
_desktop.ini
# vue-cli 项目
/dist/
# 来自 vue-cli 创建项目的 .gitignore
.project
# hexo
/public/
# Hardhat
/artifacts/
/cache/
# seafile 临时文件
._*
.$*
# office 暂存文件
~$*
# 用户shell配置脚本
.bashrc_custom
# 苹果系统临时文件
.DS_Store
# 安卓缓存文件夹
.thumbnails
# local env files
.env.local
.env.*.local
# hexo
/db.json
# wo
# 服务端
/_archive/*
/_datastore/*
/_filestore/*
/_logstore/*
/_webroot/*
/_ssl/*
# uniapp 客户端
/unpackage/*
!/unpackage/res/
package-lock.json
pages4loader.json5
### .gitignore.local.txt ###

View File

@ -1,14 +0,0 @@
/* VSCode Prettier Prettier TrailingComma
VSCode Prettier Standard 无效似乎是集成了不能修改的配置 */
module.exports = {
printWidth: 160, // default 80
tabWidth: 2, // default 2
useTabs: false,
semi: false, // default true
singleQuote: true, // default false
trailingComma: "es5", // none (default in v 1.*), es5 (default in v2.0.0), all
bracketSpacing: true, // default true
bracketSameLine: true, // default false
arrowParens: "always", // avoid (default in v1.9.0), always (default since v2.0.0)
quoteProps: "as-needed" // as-needed (default), consistent, preserve
};

109
README.md
View File

@ -1,109 +0,0 @@
# tic-crypto
时光链区块链密码学算法工具库:为区块链相关应用开发提供一套底层的基础算法工具库,用来处理哈希、加解密、签名、助记词、等等。
- 支持 md5、sha256 等算法的哈希
- 基于 bip39 等算法的助记词生成、检验
- 基于 secp256k1 等曲线算法的签名、交易的加解密
- 其他辅助算法工具
## 硬件环境
- 机型Mac 或 PC 机
- 内存8GB 以上
- 硬盘500G 以上
## 软件环境
- 操作系统:跨平台通用,支持 MacOS, Linux, Windows
- 开发环境:推荐 Visual Studio Code
- 运行环境nodejs 12.16 版本
## 安装指南
在前后端软件的 package.json 的依赖清单中引入本库:
```
npm install git+https://git.tic.cc/npm/tic-crypto#RELEASE_OR_BRANCH --save
```
## 用法
基本用法示例:
```
let ticc=require('tic-crypto') // 引用
let sw=ticc.randomize_secword() // 生成一个随机的助记词(即密语)。或者使用现成的密语。
let kp=ticc.secword_to_keypair({secword:sw}) // 把密语转换成公私钥
let address=ticc.secword_to_address({secword:sw}) // 把密语转换成地址
```
## 其他
```
const keyPair = crypto.generateKeyPairSync('rsa', {
modulusLength: 520,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: ''
}
})
```
这样生成的 keyPair.privateKey 开头是 -----BEGIN ENCRYPTED PRIVATE KEY-----
如果直接
```
crypto.privateEncrypt(kp.privateKey, Buffer.from('sdafasfdsaf'))
```
会报错
```
Uncaught TypeError: Passphrase required for encrypted key
```
所以要这样才行
```
crypto.privateEncrypt({key:kp.privateKey, passphrase:''}, Buffer.from('sdafasfdsaf'))
```
我从 https://www.cnblogs.com/chyingp/p/nodejs-asymmetric-enc-dec.html 抄到一个 privateKey 可以直接使用,不需要 passphrase
返回 Buffer。每次结果都一样
这样生成的 keyPair.publicKey 开头是 -----BEGIN PUBLIC KEY-----
可以直接
```
crypto.publicEncrypt(kp.publicKey, Buffer.from('sdafasfdsaf'))
```
返回 Buffer。每次结果不一样
似乎 crypto 一定要 rsa 公私钥才可以用加解密ticc.randomize_keypair() 生成的 ecc 公私钥不行。
而 eccrypto 和 eccrypto-js 可以用。eccrypto.generateKeyPair() 生成的和 ticc.randomize_keypair() 一样
eccrypto 在 windows 上的安装有麻烦,一来需要手工安装 OpenSSL 到 c:\openssl-win64\,二来 openssl 1.1.0 起把 libeay32.lib 改名为 libcrypto.dll而 eccrypto 需要 c:\openssl-win64\lib\libeay32.lib会报错
eccrypto-js 在 devDependencies 里继承了 eccrypto因此 npm i --production 即可
base32 有多种字符集:[Base32 - Wikipedia](https://en.wikipedia.org/wiki/Base32)
IPFS 用的是 RFC4648 字符集
- 从数到数符串Number(数).toString(进制数)0x 数.toString(进制数), 0b 数.toString(进制数)
- 从数符串到数字parseInt(str, 进制数)
- Buffer 到数符串: Buffer.toString(编码方案例如'hex','base64',默认'utf8')
- 字符串到 Buffer: Buffer.from(data, 编码方案如'hex','base64',默认'utf8')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

483
index.js Normal file
View File

@ -0,0 +1,483 @@
const BigNumber=require('bignumber.js') // https://github.com/MikeMcl/bignumber.js 几个库的比较: node-bignum: 使用到openssl在windows上需要下载二进制包有时下载失败。bigi: 不错。 bignumber.js不错。
const crypto=require('crypto')
const nacl = require('tweetnacl')
const bs58check = require('bs58check')
const uuid = require('uuid')
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为默认输入输出格式方便人的阅读以及方便函数之间统一接口
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='TIC' // 默认的币种
my.COIN_LIST=['TIC','BTC','ETH']
module.exports = {
hash:function(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:function(data, option){
option=option||{}
if (option.strict) {
return data && typeof(data)!=='boolean' && data!==Infinity // 允许大多数数据,除了空值、布尔值、无限数
}
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)
}
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))
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
}
,
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))
let decrypted = decipher.update(data, inputEncoding, outputEncoding)
decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
if (option.format==='json') { // 如果用户输入错误密码deciper也能返回结果。为了判断是否正确结果对应当是 json 格式的原文做解析来验证。
try{
JSON.parse(decrypted)
}catch(exception){
return null
}
}
return decrypted
}
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字节
// 方案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
}
,
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
}
,
secword2keypair: function(secword, option){ // option.coin 币种option.passphase 密码默认为空option.path==='master' 生成 HD master key不定义则默认为相应币种的第一对公私钥。
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
}
,
seckey2pubkey:function(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
let compress = ['compressed', 'uncompressed'].indexOf(option.compress)>=0?option.compress:'compressed' // 默认为压缩格式的公钥
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') 得到的公钥与上面的不同
}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
}
,
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 address
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类型
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (this.isPubkey(pubkey)) {
option = option||{}
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='6f'
}
}else { // 目前不支持 ETH或其他币种 地址转换因为这会大量增加前端打包的js。
return null
}
var wifAddress=bs58check.encode(Buffer.from(prefix+h160,'hex')) // wallet import format
return wifAddress
}
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(option){
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (option.coin==='TIC'){
return Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节
}else{
return Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节
}
}
,
randomKeypair:function(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: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||{}
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:function(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:function(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:function(hash, sig){ // hash为64hex字符sig为128hex字符。返回用hex表达的距离。
if (this.isSignature(sig) && this.isHash(hash)){
var hashSig=this.hash(sig) // 把签名也转成32字节的哈希同样长度方便比较
return new BigNumber(hash,16).minus(new BigNumber(hashSig,16)).abs().toString(16)
}
return null
}
,
compareSig:function(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:function(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: function (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: 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')
}
}

View File

@ -1,28 +1,18 @@
{
"name": "tic-crypto",
"main": "ticc.js",
"name": "tic.crypto",
"version": "0.1.0",
"private": true,
"dependencies": {
"base32-decode": "^1.0.0",
"base32-encode": "^1.2.0",
"big-integer": "^1.6.52",
"bip39": "^3.0.4",
"bs58check": "^2.1.2",
"eccrypto-js": "^5.4.0",
"ethereum-rsa": "^1.0.5",
"hdkey": "^2.0.1",
"js-crypto-key-utils": "^1.0.4",
"keccak": "^3.0.2",
"secp256k1": "^4.0.3",
"tweetnacl": "^1.0.3"
"bignumber.js": "^6.0.0",
"bitcore-mnemonic": "^1.5.0",
"bs58check": "^2.1.1",
"tweetnacl": "^1.0.0",
"uuid": "^3.3.2"
},
"devDependencies": {
"docdash": "^1.2.0",
"jsdoc": "^3.6.6"
},
"scripts": {
"doc": "jsdoc ./index.js -t node_modules/docdash --verbose"
"setup": "npm install"
},
"author": ""
}

View File

@ -1,72 +0,0 @@
# https://help.seafile.com/syncing_client/excluding_files/
# 注释。通配符:* 匹配0到若干个字符包括代表目录的/。? 匹配1个字符包括/。
# seafile-ignore.txt 只能控制在客户端需要忽略哪些文件。你依然可以在 seahub 的 web 界面创建这些被客户端忽略的文件。
# 在这种情况下,
# 这些文件会被同步到客户端,但是用户在客户端对这些文件的后续修改会被忽略,不会被同步回服务器。
# 文件在服务器端的后续更改会被同步到客户端,如果客户端也同时修改了这些文件,系统会生成冲突文件。
# seafile-ignore.txt 只能忽略还没有被同步的文件。对于已经被同步的文件,如果后来把它添加到 seafile-ignore.txt 中,系统只会忽略后续更改,已经上传的版本不会受影响。
### seafile-ignore.global.txt ###
# 自定义的后缀名,凡有 sfignore 后缀的都不进行同步
*.sfignore
*.sfignore/
*.sfignore.*
*.sfignore.*/
*.sfomit
*.sfomit.*
*.sfomit/
*.sfomit.*/
*.nosf
*.nosf.*
*.nosf/
*.nosf.*/
.DS_Store
*/.DS_Store
.thumbnails
*/.thumbnails
Thumbs.db
*/Thumbs.db
thumbs.db
*/thumbs.db
_desktop.ini
*/_desktop.ini
._*
*/._*
.$*
*/.$*
~$*
*/~$*
node_modules/
*/node_modules/
package-lock.json
pages4loader.json5
.deploy_git/
*/.deploy_git/
# HBuilder 目录
unpackage/
*/unpackage/
Icon
OneDrive/Icon
# wrangler project
.dev.vars*
*/.dev.vars*
.wrangler/
*/.wrangler/
### seafile-ignore.local.txt ###

187
test.js
View File

@ -1,187 +0,0 @@
const bigInt = require('big-integer')
// Consts for secp256k1 curve. Adjust accordingly
// https://en.bitcoin.it/wiki/Secp256k1
const prime = new bigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16), // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
pIdent = new bigInt('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4);
console.log('pIdent=', pIdent.toString(), ' = ', pIdent.toString(16))
/**
* Point decompress secp256k1 curve
* @param {string} Compressed representation in hex string
* @return {string} Uncompressed representation in hex string
*/
function ECPointDecompress (comp) {
var signY = new Number(comp[1]) - 2
var x = new bigInt(comp.substring(2), 16)
// y mod p = +-(x^3 + 7)^((p+1)/4) mod p
console.log('ECP x=', x.toString(), ' = ', x.toString(16))
var y = x.modPow(3, prime).add(7).mod(prime).modPow(pIdent, prime)
// If the parity doesn't match it's the *other* root
console.log('ECP y=', y.toString(), ' = ', y.toString(16))
if (y.mod(2).toJSNumber() !== signY) {
// y = prime - y
y = prime.subtract(y)
}
console.log('ECP y=', y.toString(), ' = ', y.toString(16))
return '04' + x.toString(16).padStart(64, '0') + y.toString(16).padStart(64, '0')
}
let pubkey1 = ECPointDecompress('035d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2')
console.log(pubkey1) // "045d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b24a323dd24b19c55f0a060ccd4bce314323bd7e804f3dfa8a77f14e3ab1cc4749"
correct = '045d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2356d086fb7a78f3ce3359a4caee6dd4fcf0c19a961b1c36b5b442d031d219d75'
BigNumber = require('bignumber.js')
function uncompressPubkey (comp) {
// Consts for P256 curve. Adjust accordingly
const prime = new BigNumber('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16).integerValue(),
pIdent = prime.plus(1).idiv(4).integerValue()
console.log('pIdent=', pIdent.toString(), ' = ', pIdent.toString(16))
var signY = new Number(comp[1]) - 2
var x = new BigNumber(comp.substring(2), 16).integerValue()
console.log('x=', x.toString(), ' = ', x.toString(16))
// y^2 = x^3 - 3x + b
var y = x.pow(3).mod(prime).plus(7).mod(prime).pow(pIdent).mod(prime).integerValue()
console.log('y=', y.toString(), ' = ', y.toString(16))
// If the parity doesn't match it's the *other* root
if (y.mod(2).integerValue().toNumber() !== signY) {
// y = prime - y
y = prime.minus(y).integerValue()
}
console.log('yy=', y.toString(), ' = ', y.toString(16))
return '04' + x.toString(16).padStart(64, '0') + y.toString(16).padStart(64, '0')
}
let pubkey2 = uncompressPubkey('035d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2')
console.log(pubkey2)
///////////////////////////////////////
const tic = require('./index')
const crypto = require('crypto')
const keyutil = require('js-crypto-key-utils') // https://github.com/junkurihara/jscu/tree/master/packages/js-crypto-key-utils
// https://github.com/arvati/crypto-keys
// nodejs cipher/decipher 用同一个密码在stream上操作。
let w = '驳 惊 而 煤 靠 客 示 待 诉 屈 屏 未' // tic.randomize_secword({lang:'chinese'})
console.log('secword = ', w)
let acc = tic.secword_to_account({ secword: w, coin: 'ETH' })
console.log('account = ', acc)
let add = tic.secword_to_address({ secword: w, coin: 'ETH' })
console.log('address = ', add)
/////////////////////// keyutil
let seckeyObject = new keyutil.Key('oct', Buffer.from(acc.prikey, 'hex'), { namedCurve: 'P-256K' }) // {P-256 : secp256r1, P-384 : secp384r1, P-521 : secp521r1, P-256K : secp256k1}
let seckeyObject2 = new keyutil.Key('oct', tic.hex_to_buf(acc.prikey, 'hex'), { namedCurve: 'P-256K' })
let seckeyPEM
seckeyObject.export('pem').then((data) => (seckeyPEM = data))
let seckeyDER
seckeyObject2.export('der').then((data) => (seckeyDER = data))
var signerKU = crypto.createSign('sha256')
signerKU.write('毛主席万岁')
signerKU.end()
var signatureKU = signerKU.sign(seckeyPEM) // specify format in [pem,der] and type in [pkcs1, pkcs8, sec1]
console.log('signature = ', signatureKU.toString('hex'))
console.log('length = ', signatureKU.toString('hex').length)
var signerKUDER = crypto.createSign('sha256')
signerKUDER.write('毛主席万岁')
signerKUDER.end()
var signatureKUDER = signerKUDER.sign({ key: seckeyDER, format: 'der', type: 'pkcs8' }) // specify format in [pem,der] and type in [pkcs1, pkcs8, sec1]
console.log('signature DER = ', signatureKUDER.toString('hex'))
console.log('length DER = ', signatureKUDER.toString('hex').length)
let pubkeyObject = new keyutil.Key('oct', Buffer.from(acc.pubkey, 'hex'), { namedCurve: 'P-256K' })
let pubkeyPEM
pubkeyObject.export('der').then((data) => (pubkeyPEM = data))
var verifyKU = crypto.createVerify('sha256')
verifyKU.write('毛主席万岁')
verifyKU.end()
var verified = verifyKU.verify(pubkeyPEM, signatureKU) // specify format in [pem,der] and type in [pkcs1,spki]
console.log('verified = ', verified) // 可以验证通过但是用的privatekey没有成功使用publickey。
crypto.createCipheriv('aes-256-cfb', Buffer.from(acc.prikey, 'hex'), Buffer.alloc(16))
////////////////////// crypto + PEM
toPEM = function (kp) {
let pubkey = crypto.createECDH('secp256k1').setPrivateKey(kp.prikey, 'hex').getPublicKey('hex', 'compressed')
console.log('ECDH created publickey = ', pubkey)
let mykey = '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420' + kp.prikey + 'a144034200' + pubkey
console.log(mykey)
let privKey = '-----BEGIN PRIVATE KEY-----\n' + Buffer.from(mykey, 'hex').toString('base64') + '\n-----END PRIVATE KEY-----'
// pubKey2 = crypto.createPublicKey(privKey); //也可恢复出公钥。测试不成功。
return privKey
}
let privKeyPEM = toPEM(acc)
const signerPEM = crypto.createSign('sha256')
signerPEM.write('毛主席万岁')
signerPEM.end()
let signaturePEM = signerPEM.sign(privKeyPEM, 'hex') // 失败,无论对压缩或非压缩公钥
console.log('signaturePEM = ', signaturePEM)
let pemKP = toPEM(acc)
console.log('pemKP = ', pemKP)
//////////////////// crypto, DER
// https://stackoverflow.com/questions/58350484/why-nodejs-crypto-sign-function-only-accept-privatekey-pem-format
// https://www.shangyang.me/2017/05/24/encrypt-rsa-keyformat/
var buf1 = Buffer.from('308141020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420', 'hex') // specific byte-sequence for curve prime256v1
var buf2 = Buffer.from(acc.prikey, 'hex') // raw private key (32 bytes)
var privateKeyPkcs8Der = Buffer.concat([buf1, buf2], buf1.length + buf2.length)
var sign = crypto.createSign('sha256')
sign.write('毛主席万岁')
sign.end()
var signature = sign.sign({ key: privateKeyPkcs8Der, format: 'der', type: 'pkcs8' }) // specify format in [pem,der] and type in [pkcs1, pkcs8, sec1]
console.log('signature = ', signature.toString('hex'))
console.log('length = ', signature.toString('hex').length)
var buf3 = Buffer.from('3059301306072a8648ce3d020106082a8648ce3d030107034200', 'hex') // specific byte-sequence for curve prime256v1
var buf4 = Buffer.from(acc.pubkey, 'hex') // raw public key (uncompressed, 65 bytes, startting with 04)
// 这个key无法sign。reason: 'too long'
//var publicKeyX509Der = Buffer.concat([buf3, buf4], buf3.length + buf4.length);
//var publicKey = crypto.createPublicKey({key:publicKeyX509Der, format:'der', type:'spki'})
var publicKey = crypto.createPublicKey({ key: privateKeyPkcs8Der, type: 'pkcs8', format: 'der' })
var publicKeyX509Der = publicKey.export({ type: 'spki', format: 'der' })
var verify = crypto.createVerify('sha256')
verify.write('毛主席万岁')
verify.end()
var verified = verify.verify({ key: publicKeyX509Der, format: 'der', type: 'spki' }, signature) // specify format in [pem,der] and type in [pkcs1,spki]
console.log('verified = ', verified) // 可以验证通过但是用的privatekey没有成功使用publickey。
/////////////////////// elliptic
var EC = require('elliptic').ec
// Create and initialize EC context
// (better do it once and reuse it)
var ec = new EC('secp256k1')
// Generate keys
//var key = ec.genKeyPair();
var key = ec.keyFromPrivate(acc.prikey) // 注意,不需要 'hex' 参数
// Sign the message's hash (input must be an array, or a hex-string)
var msgHash = tic.hash('毛主席万岁')
var msgHashBad = tic.hash('毛主席万岁 ')
var signature2 = key.sign(msgHash)
// Export DER encoded signature in Array
var derSign = signature2.toDER() // 无法直接导出成 hex。可以
console.log('signature by elliptic = ', Buffer.from(derSign).toString('hex'))
// 或者重新创建使用 pubkey也能成功
// ec.keyFromPublic(acc.pubkey, 'hex').verify(msgHash, signature2)
console.log(key.verify(msgHash, signature2))
console.log(key.verify(msgHashBad, signature2))
//////////////////
/*
createCipher/Decipher: 使用 pwd, 对称加解密已放弃
createCipheriv/Deciperiv: 使用 key, 对称加解密
private/publicEncrypt/Decrypt: 非对称加解密
crypto.privateEncrypt(crypto.generateKeyPairSync('rsa', {modulusLength:2048}).privateKey, Buffer.from('锦瑟无端五十弦'))
以上是唯一测出来可用的privatekey不能用 'ec', {namedCurve:'secp256k1'}, 也不能用crypto.createPrivateKey(pem格式的字符串)
*/

1643
ticc.js

File diff suppressed because it is too large Load Diff