From 09d831af00bd46bb025bcc1a1533c8e19badff4b Mon Sep 17 00:00:00 2001 From: Luk Lu Date: Thu, 20 Feb 2020 13:36:44 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8eccrypto=E5=8A=A0=E8=A7=A3=E5=AF=86?= =?UTF-8?q?=EF=BC=8C=E7=94=A8crypto=E7=AD=BE=E5=90=8D=E3=80=82=E4=BA=92?= =?UTF-8?q?=E7=9B=B8=E8=BD=AC=E6=8D=A2=E5=8E=8B=E7=BC=A9=E5=92=8C=E9=9D=9E?= =?UTF-8?q?=E5=8E=8B=E7=BC=A9=E5=85=AC=E9=92=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dump.rdb | Bin 0 -> 444 bytes eccrypt.browser.js | 273 +++++++++++++++++++++++++++++++++++++++++ index.js | 300 +++++++++++++++++++++++++++++++++------------ package.json | 4 +- test-decompress.js | 38 ++++++ test.js | 190 ++++++++++++++++++++++++++++ 6 files changed, 726 insertions(+), 79 deletions(-) create mode 100644 dump.rdb create mode 100644 eccrypt.browser.js create mode 100644 test-decompress.js create mode 100644 test.js diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..074c2d9aba151fac03ede324bc9d3426ecba5cca GIT binary patch literal 444 zcmaKmzfQtX6o;=SC>V(*CJZDxbQW?;Tcj>Ru^JT&@-L!jyzRZ07PxI~t744L;N<9A zI5`oXz(>#rU~q7$iCA?t4rlnzFW))8s=B#TgAmF=%q7_LQkTSyNNJc}3+8?on(2GP zd=SxnlabN=uyh(kFFb-%jIhySa{&bN$fhaBC5UN#G6bLCV+MezZ#zog)_eIg?xxKd z#OxS;n_K63VKqeL86mVeNhVn+ib9SGGM16R%c3qr5hF_Y4E#ocilQb+k_^`n5A`e( zXjY^m4uuTGZSIzfb9E*Zv$k3?cD*f0JyI&0yyIZH-|gT`bx>}wZZZh~SPEwvfLRUo zk)@N^GkATEOu(W`bYl5GZEHT#d(+1!aga#-VZ2SkXM=)jvIFhBS#8)AOW!WJuEQ$1 z%(da$B{tYqS62Q|UqJBKnhJyO6V^ozH>;G%dxe&wS&G-{l?ztAc*T^W-*eT&fBH_A LpYFyFOXH0%XX}a7 literal 0 HcmV?d00001 diff --git a/eccrypt.browser.js b/eccrypt.browser.js new file mode 100644 index 0000000..b39a3e7 --- /dev/null +++ b/eccrypt.browser.js @@ -0,0 +1,273 @@ +"use strict"; + +var EC = require("elliptic").ec; + +var ec = new EC("secp256k1"); +var browserCrypto = global.crypto || global.msCrypto || {}; +var subtle = browserCrypto.subtle || browserCrypto.webkitSubtle; + +var nodeCrypto = require('crypto'); + +function ab2hex(buffer) { // buffer is an ArrayBuffer + return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); +} + +function hex2ab(hex){ + return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { + return parseInt(h, 16) + })) // 注意,arraybuffer没有 toString('hex')功能 +} + +const EC_GROUP_ORDER = hex2ab('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'); +const ZERO32 = new Uint8Array(32); + +function assert(condition, message) { + if (!condition) { + throw new Error(message || "Assertion failed"); + } +} + +function isScalar (x) { + return x instanceof Uint8Array && x.length === 32; +} + +function isValidPrivateKey(privateKey) { + if (!isScalar(privateKey)) + { + return false; + } + return true // 浏览器里不支持 Buffer,即使转成 Uint8Array 也没有 compare 方法,因此为了兼容浏览器,直接返回 true. + return privateKey.compare(ZERO32) > 0 && // > 0 + privateKey.compare(EC_GROUP_ORDER) < 0; // < G +} + +// Compare two buffers in constant time to prevent timing attacks. +function equalConstTime(b1, b2) { + if (b1.length !== b2.length) { + return false; + } + var res = 0; + for (var i = 0; i < b1.length; i++) { + res |= b1[i] ^ b2[i]; // jshint ignore:line + } + return res === 0; +} + +/* This must check if we're in the browser or +not, since the functions are different and does +not convert using browserify */ +function randomBytes(size) { + var arr = new Uint8Array(size); + if (typeof browserCrypto.getRandomValues === 'undefined') { + return new Uint8Array(nodeCrypto.randomBytes(size)); + } else { + browserCrypto.getRandomValues(arr); + } + return new Uint8Array(arr); +} + +function sha512(msg) { + return new Promise(function(resolve) { + var hash = nodeCrypto.createHash('sha512'); + var result = hash.update(msg).digest(); + resolve(new Uint8Array(result)); + }); +} + +function getAes(op) { + return function(iv, key, data) { + return new Promise(function(resolve) { + if (subtle) { + var importAlgorithm = {name: "AES-CBC"}; + var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]); + return keyp.then(function(cryptoKey) { + var encAlgorithm = {name: "AES-CBC", iv: iv}; + return subtle[op](encAlgorithm, cryptoKey, data); + }).then(function(result) { + resolve(new Uint8Array(result)); + }); + } else { + if (op === 'encrypt') { + var cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv); + cipher.update(data); + resolve(cipher.final()); + } + else if (op === 'decrypt') { + var decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv); + decipher.update(data); + resolve(decipher.final()); + } + } + }); + }; +} + +var aesCbcEncrypt = getAes("encrypt"); +var aesCbcDecrypt = getAes("decrypt"); + +function hmacSha256Sign(key, msg) { + return new Promise(function(resolve) { + var hmac = nodeCrypto.createHmac('sha256', new Uint8Array(key)); + hmac.update(msg); + var result = hmac.digest(); + resolve(result); + }); +} + +function hmacSha256Verify(key, msg, sig) { + return new Promise(function(resolve) { + var hmac = nodeCrypto.createHmac('sha256', new Uint8Array(key)); + hmac.update(msg); + var expectedSig = hmac.digest(); + resolve(equalConstTime(expectedSig, sig)); + }); +} + +/** + * Generate a new valid private key. Will use the window.crypto or window.msCrypto as source + * depending on your browser. + * @return {Buffer} A 32-byte private key. + * @function + */ +exports.generatePrivate = function () { + var privateKey = randomBytes(32); + while (!isValidPrivateKey(privateKey)) { + privateKey = randomBytes(32); + } + return privateKey; +}; + +var getPublic = exports.getPublic = function(privateKey) { + // This function has sync API so we throw an error immediately. + assert(privateKey.length === 32, "Bad private key"); + assert(isValidPrivateKey(privateKey), "Bad private key"); + // XXX(Kagami): `elliptic.utils.encode` returns array for every + // encoding except `hex`. + return new Uint8Array(ec.keyFromPrivate(privateKey).getPublic("arr")); +}; + +/** + * Get compressed version of public key. + */ +var getPublicCompressed = exports.getPublicCompressed = function(privateKey) { // jshint ignore:line + assert(privateKey.length === 32, "Bad private key"); + assert(isValidPrivateKey(privateKey), "Bad private key"); + // See https://github.com/wanderer/secp256k1-node/issues/46 + let compressed = true; + return new Uint8Array(ec.keyFromPrivate(privateKey).getPublic(compressed, "arr")); +}; + +// NOTE(Kagami): We don't use promise shim in Browser implementation +// because it's supported natively in new browsers (see +// ) and we can use only new browsers +// because of the WebCryptoAPI (see +// ). +exports.sign = function(privateKey, msg) { + return new Promise(function(resolve) { + assert(privateKey.length === 32, "Bad private key"); + assert(isValidPrivateKey(privateKey), "Bad private key"); + assert(msg.length > 0, "Message should not be empty"); + assert(msg.length <= 32, "Message is too long"); + resolve(new Uint8Array(ec.sign(msg, privateKey, {canonical: true}).toDER())); + }); +}; + +exports.verify = function(publicKey, msg, sig) { + return new Promise(function(resolve, reject) { + assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key"); + if (publicKey.length === 65) + { + assert(publicKey[0] === 4, "Bad public key"); + } + if (publicKey.length === 33) + { + assert(publicKey[0] === 2 || publicKey[0] === 3, "Bad public key"); + } + assert(msg.length > 0, "Message should not be empty"); + assert(msg.length <= 32, "Message is too long"); + if (ec.verify(msg, sig, publicKey)) { + resolve(null); + } else { + reject(new Error("Bad signature")); + } + }); +}; + +var derive = exports.derive = function(privateKeyA, publicKeyB) { + return new Promise(function(resolve) { + assert(privateKeyA instanceof Uint8Array, "Bad private key"); + assert(publicKeyB instanceof Uint8Array, "Bad public key"); + assert(privateKeyA.length === 32, "Bad private key"); + assert(isValidPrivateKey(privateKeyA), "Bad private key"); + assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key"); + if (publicKeyB.length === 65) + { + assert(publicKeyB[0] === 4, "Bad public key"); + } + if (publicKeyB.length === 33) + { + assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key"); + } + var keyA = ec.keyFromPrivate(privateKeyA); + var keyB = ec.keyFromPublic(publicKeyB); + var Px = keyA.derive(keyB.getPublic()); // BN instance + resolve(new Uint8Array(Px.toArray())); + }); +}; + +exports.encrypt = function(publicKeyTo, msg, opts) { + opts = opts || {}; + // Tmp variables to save context from flat promises; + var iv, ephemPublicKey, ciphertext, macKey; + return new Promise(function(resolve) { + var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); + // There is a very unlikely possibility that it is not a valid key + while(!isValidPrivateKey(ephemPrivateKey)) + { + ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); + } + ephemPublicKey = getPublic(ephemPrivateKey); + resolve(derive(ephemPrivateKey, publicKeyTo)); + }).then(function(Px) { + return sha512(Px); + }).then(function(hash) { + iv = opts.iv || randomBytes(16); + var encryptionKey = hash.slice(0, 32); + macKey = hash.slice(32); + return aesCbcEncrypt(iv, encryptionKey, msg); + }).then(function(data) { + ciphertext = data; + var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]); + return hmacSha256Sign(macKey, dataToMac); + }).then(function(mac) { + return { + iv: iv, + ephemPublicKey: ephemPublicKey, + ciphertext: ciphertext, + mac: mac, + }; + }); +}; + +exports.decrypt = function(privateKey, opts) { + // Tmp variable to save context from flat promises; + var encryptionKey; + return derive(privateKey, opts.ephemPublicKey).then(function(Px) { + return sha512(Px); + }).then(function(hash) { + encryptionKey = hash.slice(0, 32); + var macKey = hash.slice(32); + var dataToMac = Buffer.concat([ + opts.iv, + opts.ephemPublicKey, + opts.ciphertext + ]); + return hmacSha256Verify(macKey, dataToMac, opts.mac); + }).then(function(macGood) { + assert(macGood, "Bad MAC"); + return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext); + }).then(function(msg) { + return new Uint8Array(msg); + }); +}; + diff --git a/index.js b/index.js index 86c425d..1f07e2d 100644 --- a/index.js +++ b/index.js @@ -1,17 +1,22 @@ -const BigNumber=require('bignumber.js') // https://github.com/MikeMcl/bignumber.js 几个库的比较: node-bignum: 使用到openssl,在windows上需要下载二进制包,有时下载失败。bigi: 不错。 bignumber.js:不错。 +// const BigNumber=require('bignumber.js') // 处理整数 https://github.com/MikeMcl/bignumber.js +const BigInt = require("big-integer") // 处理整数 https://github.com/peterolson/BigInteger.js const crypto=require('crypto') const nacl = require('tweetnacl') const bs58check = require('bs58check') const uuid = require('uuid') const keccak = require('keccak') +const eccrypto = require('eccrypto') // 用于加解密。不知道怎么用 crypto 本身的加解密。 +const keyman = require('js-crypto-key-utils') // 转换原始密钥和 PER/DER 格式。 const Secword = require('bitcore-mnemonic') // https://bitcore.io/api/mnemonic/ https://github.com/bitpay/bitcore-mnemonic // const bip39 = require('bip39') // https://github.com/bitcoinjs/bip39 // 有更多语言,但不方便选择语言,也不能使用 pass // const HDKey = require('hdkey') // https://github.com/cryptocoinjs/hdkey // 或者用 bitcore-mnemonic 或者 ethers 里的相同功能 +// const bitcorelib = require('bitcore-lib') +// const secp256k1 = require('secp256k1') // 全部以hex为默认输入输出格式,方便人的阅读,以及方便函数之间统一接口 const my={} -my.HASHER='sha256' // 默认的哈希算法。could be md5, sha1, sha256, sha512, ripemd160。 可用 Crypto.getHashes/Ciphers/Curves() 查看支持的种类。 +my.HASHER='sha256' // 默认的哈希算法。could be md5, sha1, sha256, sha512, ripemd160 and much more。 可用 Crypto.getHashes/Ciphers/Curves() 查看支持的种类。 my.HASHER_LIST=crypto.getHashes() my.CIPHER='aes-256-cfb' // 默认的加解密算法 my.CIPHER_LIST=crypto.getCiphers() @@ -61,78 +66,117 @@ module.exports = { return false } , - encrypt(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(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 - } + async encrypt(data, {keytype, key, input, output, cipher}={}){ + if (keytype==='pwd') { + if (this.isHashable(data) && typeof(key)==='string') { + let inputEncoding=my.INPUT_LIST.indexOf(input)>=0?input:my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView + let outputEncoding=(output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(output)>=0?output:my.OUTPUT) // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly + let cipher=crypto.createCipher( + my.CIPHER_LIST.indexOf(cipher)>=0?cipher:my.CIPHER, + this.hash(key)) + if (typeof(data)!=='string' && !(data instanceof Buffer) && !(data instanceof DataView)) + data=JSON.stringify(data) + let encrypted = cipher.update(data, inputEncoding, outputEncoding) + encrypted += cipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string + return encrypted } - return decrypted + }else if (keytype==='pubkey') { // data 应当是 utf8 的字符串。// 但在浏览器里不能使用 Failed to execute 'encrypt' on 'SubtleCrypto': The provided value is not of type '(ArrayBuffer or ArrayBufferView)' + let cipherobject = await eccrypto.encrypt(this.hex2buf(key), data) + return cipherobject } return null } , - sign(data, seckey, option) { // data can be string or buffer or object, results are the same + async decrypt(data, {keytype, key, input, output, cipher, format}={}){ // data 应当是 encrypt 输出的数据类型 + if (keytype==='pwd') { + if (data && (typeof(data)==='string' || data instanceof Buffer) && typeof(key)==='string') { + let inputEncoding=my.OUTPUT_LIST.indexOf(input)>=0?input:my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer + let outputEncoding=(output==='buf')?undefined:(my.INPUT_LIST.indexOf(output)>=0?output:my.INPUT) // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly + let decipher=crypto.createDecipher( + my.CIPHER_LIST.indexOf(cipher)>=0?cipher:my.CIPHER, + this.hash(key)) + let decrypted = decipher.update(data, inputEncoding, outputEncoding) + decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string + if (format==='json') { // 如果用户输入错误密码,deciper也能返回结果。为了判断是否正确结果,对应当是 json 格式的原文做解析来验证。 + try{ + JSON.parse(decrypted) + }catch(exception){ + return null + } + } + return decrypted + }else if (keytype==='seckey'){ // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象 + let plaindata = await eccrypto.decrypt(Buffer.from(key, 'hex'), data) // eccrypto 需要调用 Buffer.compare 方法,不能在这里直接用 hex2buf + return plaindata.toString('utf8') + } + } + return null + } + , + /* 以下两个方法基于 eccrypto,注意其在浏览器上会有问题。 + */ + async encryptPubkey(plaindata, pubkey, option){ // plaindata 应当是 utf8 的字符串 + let cipherobject = await eccrypto.encrypt(this.hex2buf(pubkey), plaindata) + return cipherobject + } + , + async decryptSeckey(cipherobject, seckey, option){ // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象 + let plaindata + if (Buffer) { // nodejs + plaindata = await eccrypto.decrypt(Buffer.from(seckey, 'hex'), cipherobject) // eccrypto 需要调用 Buffer.compare 方法 + }else { // browser + plaindata = await eccrypto.decrypt(this.hex2buf(seckey), cipherobject) + } + return plaindata.toString('utf8') + } + , + async sign(data, seckey, option) { // data can be string or buffer or object, results are the same if (this.isHashable(data) && this.isSeckey(seckey)) { option=option||{} - // 使用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字节 + // 方案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:尚未彻底实现。 - // 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格式,因此这样写是不能用的。 + // 方案2: 纯 crypto + let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), {namedCurve:'P-256K'}).export('pem') + let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER + let inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView. + let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) + let signer=crypto.createSign(hasher) + signer.update(data, inputEncoding).end() + let signature = signer.sign(seckeyPEM, outputEncoding) + return signature // 发现同样的输入,每次调用会生成不同的 signature, 但都可以通过 verify。有一次我竟然徒手修改出一个新签名也通过验证。 } return null } , isSignature(signature){ - return /^[a-fA-F0-9]{128}$/.test(signature) + return /^[a-fA-F0-9]{128,144}$/.test(signature) } , - verify (data, signature, pubkey, option) { // data could be anything, but converts to string or remains be Buffer/TypedArray/DataView + 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)){ - 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 + // 方案1: nacl + // option=option||{} + // option.output='buf' // 哈希必须输出为 buffer + // var bufHash=this.hash(data, option) + // var bufSignature = Buffer.from(signature, 'hex') + // var bufPubkey = Buffer.from(pubkey, 'hex') + // var res = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey) + // return res + + // 方案2: 纯 crypto + let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), {namedCurve:'P-256K'}).export('pem') + let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER + let inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView. + let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) + let verifier = crypto.createVerify(hasher) + verifier.update(data, inputEncoding).end() // end() 在 nodejs 12 里返回verifier自身,但在浏览器里返回 undefined,因此不能串联运行。 + let verified = verifier.verify(pubkeyPEM, signature, 'hex') + return verified } return null } @@ -230,16 +274,16 @@ module.exports = { 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' - // if (option.compressed==='false'){ - // return ecc.getPublic(this.hex2arrbuf(seckey)).toString('hex') + return new crypto.createECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex', option.compress===false?'uncompressed':'compressed') // ecdh.getPublicKey(不加参数) 默认为 'uncompressed' + // 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。但可惜,浏览器里不存在 crypto.ECDH。 + // 或者 return this.buf2hex(require('secp256k1').publicKeyCreate(Buffer.from(seckey, 'hex'), option.compress!==false)) // 可用于浏览器。secp256k1缺省或true时输出压缩公钥,false时输出非压缩公钥。 + // 或者 bitcorelib.PublicKey.fromPrivateKey(new bitcorelib.PrivateKey(seckey)).toString('hex') // 可用于浏览器 + // 或者 const ecc = require('eccrypto') + // if (option.compress===false){ + // return ecc.getPublic(this.hex2buf(seckey)).toString('hex') // }else{ - // return ecc.getPublicCompressed(this.hex2arrbuf(seckey)).toString('hex') + // return ecc.getPublicCompressed(this.hex2buf(seckey)).toString('hex') // } - // 从 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')) @@ -254,10 +298,10 @@ module.exports = { if (this.isSeckey(seckey)){ let pubkey if (option.coin==='ETH'){ - pubkey = this.seckey2pubkey(seckey, {compress:'uncompressed'}) + pubkey = this.seckey2pubkey(seckey, {compress:false}) return this.pubkey2address(pubkey, option) }else { - pubkey = this.seckey2pubkey(seckey, {compress:'compressed'}) + pubkey = this.seckey2pubkey(seckey, {compress:true}) return this.pubkey2address(pubkey, option) } } @@ -285,11 +329,70 @@ module.exports = { return /^[m|t|d|T][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{33}$/.test(address) // && address.length>25 && bs58check.decode(address.slice(1)) && ['A'].indexOf(address[0]>=0)) { } , - pubkey2address (pubkey, option) { // pubkey 应当是string类型 - option=option||{} + pubkey2position (pubkey, {coin}={}){ + coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN + if(this.isPubkey(pubkey)){ + if (coin==='ETH'){ + // 注意,必须要用非压缩的64字节的公钥的buffer,并去掉开头的 04。 + if (pubkey.length===66) { + pubkey = this.decompressPubkey(pubkey) + } + return keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40) + }else { + let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest() + let h160 = crypto.createHash('ripemd160').update(h256).digest('hex') + return h160 + } + } + return null + } + , + position2address(position, {coin, netType}={}){ + if (!position) return null + coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN + let address + if (coin==='ETH'){ // 对以太坊,按照 EIP55,把纯位置转换为大小写敏感能自我验证的hex地址 + position = position.toLowerCase().replace('0x', '') + let hash = keccak('keccak256').update(position).digest('hex') + address = '0x' + for (var i = 0; i < position.length; i++) { + if (parseInt(hash[i], 16) >= 8) { + address += position[i].toUpperCase() + } else { + address += position[i] + } + } + return address + }else if (coin === 'BTC'){ // 对比特币,把纯位置转换为大小写敏感能自我验证的bs58check地址 + let prefix + switch (netType) { + case 'mainnet': prefix='00'; break; // pubkey hash => 1 + case 'mainnetSh': prefix='05'; break; // script hash => 3 + case 'testnet': prefix='6f'; break; // testnet pubkey hash => m or n + case 'testnetSh': prefix='c4'; break // testnet script hash => 2 + case 'namecoin': prefix='34'; break; // Namecoin pubkey hash => M or N + case 'compact': prefix='15'; break; // compact pubkey (proposed) => 4 + default: prefix='00' + } + address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format + return address + }else { + let prefix + switch (netType){ + case 'mainnet': prefix='42'; break; // '42'=>T, '6E'=>m + case 'testnet': prefix='7F'; break; // '7F'=>t + case 'devnet': prefix='5A'; break; // '5A'=>d + default: prefix='42' // 默认暂且为 42,为了兼容已经运行的链。 + } + address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format + return address + } + return null + } + , + pubkey2address (pubkey, option={}) { // pubkey 应当是string类型 option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN if (this.isPubkey(pubkey)) { - option = option||{} let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest() let h160 = crypto.createHash('ripemd160').update(h256).digest('hex') let prefix @@ -308,7 +411,7 @@ module.exports = { default: prefix='00' } }else if (option.coin==='ETH'){ - // 注意,必须要用非压缩的64字节的公钥的buffer。 + // 注意,必须要用非压缩的64字节的公钥的buffer,并去掉开头的 04。 return '0x' + keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40) // 或 const { keccak256 } = require('ethereumjs-util'); keccak256(Buffer.from(pubkey.slice(2),'hex)).toString('hex').slice(40) // 或 const { Keccak } = require('sha3'); new Keccak('').update(Bufer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40) @@ -334,13 +437,13 @@ module.exports = { return new Secword(Secword.Words[language]).phrase } , - randomSeckey(option){ + randomSeckey(option){ // todo: 使用 crypto.randomBytes(size) option=option||{} option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN if (option.coin==='TIC'){ - return Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节 + return crypto.randomBytes(64).toString('hex') // Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节 }else{ - return Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节 + return crypto.randomBytes(32).toString('hex') // Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节 } } , @@ -436,7 +539,7 @@ module.exports = { distanceSig(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 new BigInt(hash,16).sub(new BigInt(hashSig,16)).abs().toString(16) } return null } @@ -520,14 +623,55 @@ module.exports = { return verifier.update(string2Verify).verify(pubkey, sign, 'base64') } , - arrbuf2hex(buffer) { // buffer is an ArrayBuffer + buf2hex(buffer) { // buffer is an ArrayBuffer return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); } , - hex2arrbuf(hex){ + hex2buf(hex){ return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) - })) // 注意,arraybuffer没有 toString('hex')功能 + })) // 注意,arraybuffer没有 toString('hex')功能, Buffer才有。 } , + hex2base58check(hex){ + return bs58check.encode(Buffer.from(hex, 'hex')) + } + , + base58check2hex(box){ + try{ + return bs58check.decode(box).toString('hex').toUpperCase() + }catch(exception){ + return null + } + } + , + hex2eip55(){ + + } + , + // test: https://iancoleman.io/bitcoin-key-compression/ + // compress: https://hacpai.com/article/1550844562914 + compressPubkey(uncompressed){ + let [all, x, y]=uncompressed.toLowerCase().match(/^04(.{64})(.{64})$/) + if (/[1,3,5,7,9,b,d,f]$/.test(y)){ + return '03'+x // y为奇数=>前缀03 + }else{ + return '02'+x // y为偶数=>前缀02 + } + } + , + // uncompress: https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression/53478265#53478265 + // https://en.bitcoin.it/wiki/Secp256k1 + decompressPubkey(compressed){ + // Consts for secp256k1 curve. Adjust accordingly + const prime = new BigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16) // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + const pIdent = new BigInt('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4); + var signY = new Number(compressed[1]) - 2 + var x = new BigInt(compressed.substring(2), 16) + var y = x.modPow(3, prime).add(7).mod(prime).modPow( pIdent, prime ) // y mod p = +-(x^3 + 7)^((p+1)/4) mod p + if( y.mod(2).toJSNumber() !== signY ) { // If the parity doesn't match it's the *other* root + y = prime.subtract( y ) // y = prime - y + } + return '04' + x.toString(16).padStart(64, '0') + y.toString(16).padStart(64, '0') + } } \ No newline at end of file diff --git a/package.json b/package.json index 309b9b8..640955b 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { - "bignumber.js": "^9.0.0", + "big-integer": "^1.6.48", "bitcore-mnemonic": "^8.16.0", "bs58check": "^2.1.1", + "eccrypto": "^1.1.3", + "js-crypto-key-utils": "^0.7.3", "keccak": "^2.1.0", "tweetnacl": "^1.0.3", "uuid": "^3.3.2" diff --git a/test-decompress.js b/test-decompress.js new file mode 100644 index 0000000..f7e5b0a --- /dev/null +++ b/test-decompress.js @@ -0,0 +1,38 @@ +const bigInt = require("big-integer"); + +// Consts for P256 curve. Adjust accordingly +const two = new bigInt(2), +// 115792089210356248762697446949407573530086143415290314195533631308867097853951 +prime = two.pow(256).subtract( two.pow(224) ).add( two.pow(192) ).add( two.pow(96) ).subtract(1), +b = new bigInt( '41058363725152142129326129780047268409114441015993725554835256314039467401291' ), +// Pre-computed value, or literal +// 28948022302589062190674361737351893382521535853822578548883407827216774463488 +pIdent = prime.add(1).divide(4); + +function pad_with_zeroes(number, length) { + var retval = '' + number; + while (retval.length < length) { + retval = '0' + retval; + } + return retval; +} + +/** + * Point decompress NIST 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^2 = x^3 - 3x + b + var y = x.pow(3).subtract( x.multiply(3) ).add( b ).modPow( pIdent, prime ); + // If the parity doesn't match it's the *other* root + if( y.mod(2).toJSNumber() !== signY ) { + // y = prime - y + y = prime.subtract( y ); + } + return '04' + pad_with_zeroes(x.toString(16), 64) + pad_with_zeroes(y.toString(16), 64); +} + +console.log(ECPointDecompress('035d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2')) \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..9e409f9 --- /dev/null +++ b/test.js @@ -0,0 +1,190 @@ +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.randomSecword('chinese') +console.log('secword = ', w) + +let acc = tic.secword2account(w, {coin:'ETH'}) +console.log('account = ', acc) + +let add = tic.secword2address(w, {coin:'ETH'}) +console.log('address = ', add) + +/////////////////////// keyutil + +let seckeyObject = new keyutil.Key('oct', Buffer.from(acc.seckey, 'hex'), {namedCurve:'P-256K'}) // {P-256 : secp256r1, P-384 : secp384r1, P-521 : secp521r1, P-256K : secp256k1} +let seckeyObject2 = new keyutil.Key('oct', tic.hex2buf(acc.seckey, '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.seckey,'hex'), Buffer.alloc(16)) + +////////////////////// crypto + PEM + +toPEM=function (kp){ + let pubkey = crypto.createECDH('secp256k1').setPrivateKey(kp.seckey, 'hex').getPublicKey('hex','compressed') + console.log('ECDH created publickey = ', pubkey) + let mykey = '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420' + kp.seckey + '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.seckey, '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.seckey) // 注意,不需要 '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格式的字符串)。 +*/