diff --git a/index.js b/index.js index 1f07e2d..9a48c4f 100644 --- a/index.js +++ b/index.js @@ -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/