tic 默认使用和 btc, eth 一样的公私钥算法

This commit is contained in:
陆柯 2020-02-20 16:30:47 +08:00
parent 09d831af00
commit 72b89fe997

189
index.js
View File

@ -66,6 +66,31 @@ module.exports = {
return false
}
,
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)) {
}
,
isSignature(signature){
return /^[a-fA-F0-9]{128,144}$/.test(signature)
}
,
async encrypt(data, {keytype, key, input, output, cipher}={}){
if (keytype==='pwd') {
if (this.isHashable(data) && typeof(key)==='string') {
@ -113,34 +138,8 @@ module.exports = {
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
async sign(data, seckey, option={}) { // data can be string or buffer or object, results are the same
if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===64) { // 纯 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.
@ -148,27 +147,19 @@ module.exports = {
let signer=crypto.createSign(hasher)
signer.update(data, inputEncoding).end()
let signature = signer.sign(seckeyPEM, outputEncoding)
return signature // 发现同样的输入,每次调用会生成不同的 signature, 但都可以通过 verify。有一次我竟然徒手修改出一个新签名也通过验证。
return signature // 发现同样的输入,每次调用会生成不同的 signature, 且长度不定(140~144 hex) 但都可以通过 verify。有一次我竟然徒手修改出一个新签名也通过验证。
}
if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===128) { // 使用nacl的签名算法。注意nacl.sign需要的seckey是64字节=128字符。
option.output='buf' // 哈希必须输出为 buffer
let hashBuf = this.hash(data, option)
let signature = nacl.sign.detached(hashBuf, Buffer.from(seckey, 'hex'))
return Buffer.from(signature).toString('hex') // 签名是64节128个hex字符
}
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
if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey) && signature.length>=140){ // 纯 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.
@ -178,6 +169,14 @@ module.exports = {
let verified = verifier.verify(pubkeyPEM, signature, 'hex')
return verified
}
if (signature.length===128){ // nacl
option.output='buf' // 哈希必须输出为 buffer
let bufHash=this.hash(data, option)
let bufSignature = Buffer.from(signature, 'hex')
let bufPubkey = Buffer.from(pubkey, 'hex')
let verified = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey)
return verified
}
return null
}
,
@ -212,7 +211,7 @@ module.exports = {
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if(option.coin==='TIC') {
if(option.tool==='nacl') {
// 采用自己的算法bip39算法从secword到种子hash后用 nacl.sign.keyPair.fromSeed()方法。
option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
let hashBuf=crypto.createHash(option.hasher).update(this.secword2seed(secword, option.pass)).digest()
@ -233,6 +232,7 @@ module.exports = {
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
case 'TIC': key=hdmaster.derive("m/44'/66'/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"
@ -269,8 +269,7 @@ module.exports = {
return null
}
,
seckey2pubkey(seckey, option){
option=option||{}
seckey2pubkey(seckey, 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
@ -285,7 +284,7 @@ module.exports = {
// 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 私钥
}else if (this.isSeckey(seckey) && seckey.length===128){ // 用于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类型
}
@ -308,27 +307,6 @@ module.exports = {
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)){
@ -440,7 +418,7 @@ module.exports = {
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'){
if (option.tool==='nacl'){
return crypto.randomBytes(64).toString('hex') // Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节
}else{
return crypto.randomBytes(32).toString('hex') // Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节
@ -451,14 +429,23 @@ module.exports = {
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')
if (option.tool==='nacl'){
if (option.purpose==='encrypt'){
kp=nacl.box.keyPair()
}else{
kp=nacl.sign.keyPair()
}
return {
seckey:Buffer.from(kp.secretKey).toString('hex'),
pubkey:Buffer.from(kp.publicKey).toString('hex')
}
}else {
let seckey=this.randomSeckey()
let pubkey=this.seckey2pubkey(seckey)
return {
seckey,
pubkey
}
}
}
,
@ -471,19 +458,14 @@ module.exports = {
return text
}
,
randomNumber(option){ // 长度为 option.length 的随机数字,或者 (option.min||0) <= num < option.max
option=option||{}
randomNumber({length, min, max}={}){ // 长度为 length 的随机数字,或者 (min||0) <= num < max
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
if (typeof length === 'number' && length>0){
num=parseInt(Math.random()*Math.pow(10,length))
num.toString().padStart(length, '0')
}else if (typeof max === 'number' && max>0){
min = (typeof min === 'number' && min>=0) ? min : 0
num=parseInt(Math.random()*(max-min))+min
}else{ // 如果 option 为空
num=Math.random()
}
@ -617,10 +599,10 @@ module.exports = {
return encodeURIComponent(signer.update(string2Sign).sign(prikey, 'base64'))
}
,
rsaVerify(string2Verify, sign, pubkey, signType){
rsaVerify(string2Verify, signature, 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')
return verifier.update(string2Verify).verify(pubkey, signature, 'base64')
}
,
buf2hex(buffer) { // buffer is an ArrayBuffer
@ -633,20 +615,33 @@ module.exports = {
})) // 注意,arraybuffer没有 toString('hex')功能, Buffer才有。
}
,
hex2base58check(hex){
hex2b58c(hex){
return bs58check.encode(Buffer.from(hex, 'hex'))
}
,
base58check2hex(box){
b58c2hex(box){
try{
return bs58check.decode(box).toString('hex').toUpperCase()
return bs58check.decode(box).toString('hex')
}catch(exception){
return null
}
}
,
hex2eip55(){
hex2eip55(hex){
if (typeof(hex)==='string' && hex.length===64) {
hex = hex.toLowerCase().replace('0x', '')
let hash = keccak('keccak256').update(hex).digest('hex')
result = '0x'
for (var i = 0; i < hex.length; i++) {
if (parseInt(hash[i], 16) >= 8) {
result += hex[i].toUpperCase()
} else {
result += hex[i]
}
}
return result
}
return null
}
,
// test: https://iancoleman.io/bitcoin-key-compression/