Compare commits
No commits in common. "main" and "20181205" have entirely different histories.
113
.gitignore
vendored
113
.gitignore
vendored
@ -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 ###
|
||||
|
@ -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
109
README.md
@ -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 |
453
index.js
Normal file
453
index.js
Normal file
@ -0,0 +1,453 @@
|
||||
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
|
||||
,
|
||||
getMerkleRoot: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
|
||||
}
|
||||
,
|
||||
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')
|
||||
}
|
||||
}
|
24
package.json
24
package.json
@ -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": ""
|
||||
}
|
||||
|
@ -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
187
test.js
@ -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格式的字符串)。
|
||||
*/
|
Loading…
Reference in New Issue
Block a user