From ba172e3b98349aa18eaa2d25cf6bfe5f13b7cb5a Mon Sep 17 00:00:00 2001 From: Temitope Alabi Date: Wed, 18 Apr 2018 11:15:45 -0700 Subject: [PATCH 1/9] updated gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3c3629e6..f854f3fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +.DS_Store +package-lock.json From 57bee7097d041fa7461d9c78dcff724f8f913639 Mon Sep 17 00:00:00 2001 From: Temitope Alabi Date: Wed, 18 Apr 2018 11:23:59 -0700 Subject: [PATCH 2/9] added tests and utils --- index.js | 37 ++++++++++ test/index.js | 45 +++++++++++- utils/eccrypto-lite.js | 155 +++++++++++++++++++++++++++++++++++++++++ utils/ecdh.node | Bin 0 -> 19272 bytes 4 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 utils/eccrypto-lite.js create mode 100755 utils/ecdh.node diff --git a/index.js b/index.js index 1d4b5bdc..49da7cb3 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ const ethUtil = require('ethereumjs-util') const ethAbi = require('ethereumjs-abi') +const eccrypto = require('./utils/eccrypto-lite') module.exports = { @@ -66,6 +67,42 @@ module.exports = { const publicKey = recoverPublicKey(msgHash, msgParams.sig) const sender = ethUtil.publicToAddress(publicKey) return ethUtil.bufferToHex(sender) + }, + + encrypt: async function(senderprivateKey, receiverPublicKey, msgParams) { + //first sign message using personalSign functions + const signed = this.personalSign(ethUtil.toBuffer(senderprivateKey), msgParams); + + // then create payload + const payload = { + message: msgParams.data, + signed + }; + + //then encrypt + const encrypted = await eccrypto.encryptWithPublicKey( + receiverPublicKey, // by encryping with bobs publicKey, only bob can decrypt the payload with his privateKey + JSON.stringify(payload) // we have to stringify the payload before we can encrypt it + ); + + return encrypted; + }, + + decrypt: async function(encryptedMsg, privateKey) { + const decrypted = await eccrypto.decryptWithPrivateKey( + privateKey, + encryptedMsg + ); + + const decryptedPayload = JSON.parse(decrypted); + + //check signature + const senderAddress = this.recoverPersonalSignature({ + data: decryptedPayload.message, + sig: decryptedPayload.signed + }); + + return { from: senderAddress, message: decryptedPayload.message }; } } diff --git a/test/index.js b/test/index.js index 96507ad3..7343269d 100644 --- a/test/index.js +++ b/test/index.js @@ -269,4 +269,47 @@ function typedSignatureHashThrowsTest(opts) { sigUtil.typedSignatureHash(argument) }) }) -} \ No newline at end of file +} + +//encryption test +test("encrypt text with ECDH", async t => { + t.plan(4); + + const senderPrivateKey = + "0x7f2e0a903b5e5fdee8458987386ca23c3f2db7d07eae02d3c2e6e5c6f17599a0"; + const receiverPublicKey = + "44be5ff4da3cc349f101b0cb189887a55b05efc2ed97ab3054d2a16c19ca488685c70fa48556ef3d6d5342cd996ba4b41df4214947ca5ecf6324b9bc39fa5246"; + const msgParams = { data: "My name is Satoshi Buterin" }; + + const result = await sigUtil.encrypt( + senderPrivateKey, + receiverPublicKey, + msgParams + ); + + t.ok(result.iv); + t.ok(result.ephemPublicKey); + t.ok(result.ciphertext); + t.ok(result.mac); +}); + +//decryption test +test("decrypt ECDh encrypted text", async t => { + t.plan(1); + const originalText = "My name is Satoshi Buterin"; + //encrypted data + const encryptedData = { + iv: "c70881072e88ccbf084d9c172ba96f52", + ephemPublicKey: + "0451e0077b7d1f87720d5e6cd19c6379998ac918569847c3b87b4ea19cfcd89b65c986ac6a1cc782bc61d85d0b9628176cf077fc384a7e30a051586015d94cdb37", + ciphertext: + "add5994482aacd0ad2f148fdb91820e89d4ee9b3b165bff04791b6489a8a8f5364de15b34f7173f6e41f2283e9a3be7506ec98a6176b91c473394b6a97e1cc3a71552467ab6e378c3cfa0dace7321f24c7306f7fba7f7e9fc7b6c52fb4c136d9f67d3c16144417f1b18bfb59475425bac63ddcdba295eac7d9688b2e0f319caac59ed723d7ceb1b475766a34f164dc16502e61541d3fab5fbad4b1bd1c82454ca1614f75cd23dec1593c50a11f4c3c9ab0503ba3aa4031452701ab137f3dedfc", + mac: "9f8ef19e474afa745187e59b73bbacbeabfc55e150a271497bc68bf8e40e2967" + }; + + //private key + const privateKey = + "0x7e5374ec2ef0d91761a6e72fdf8f6ac665519bfdf6da0a2329cf0d804514b816"; + const result = await sigUtil.decrypt(encryptedData, privateKey); + t.equal(result.message, originalText); +}); \ No newline at end of file diff --git a/utils/eccrypto-lite.js b/utils/eccrypto-lite.js new file mode 100644 index 00000000..601cd956 --- /dev/null +++ b/utils/eccrypto-lite.js @@ -0,0 +1,155 @@ +var crypto = require("crypto"); +var promise = + typeof Promise === "undefined" ? require("es6-promise").Promise : Promise; +var secp256k1 = require("secp256k1"); +var ecdh = require("./ecdh"); + +module.exports = { + decryptWithPrivateKey: function(pk, edata) { + const result = decryptWithPrivateKey(pk, edata); + return result; + }, + encryptWithPublicKey: function(pk, payload) { + const result = encryptWithPublicKey(pk, payload); + return result; + } +}; + +var encryptWithPublicKey = async function(publicKey, message) { + + //used in encryptedButterParams function + const encryptedBuffer = function(publicKeyTo, msg, opts) { + opts = opts || {}; + // Tmp variable to save context from flat promises; + var ephemPublicKey; + return new promise(function(resolve) { + var ephemPrivateKey = opts.ephemPrivateKey || crypto.randomBytes(32); + ephemPublicKey = getPublic(ephemPrivateKey); + resolve(derive(ephemPrivateKey, publicKeyTo)); + }).then(function(Px) { + var hash = sha512(Px); + var iv = opts.iv || crypto.randomBytes(16); + var encryptionKey = hash.slice(0, 32); + var macKey = hash.slice(32); + var ciphertext = aes256CbcEncrypt(iv, encryptionKey, msg); + var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]); + var mac = hmacSha256(macKey, dataToMac); + return { + iv: iv, + ephemPublicKey: ephemPublicKey, + ciphertext: ciphertext, + mac: mac + }; + }); + }; + + // re-add the compression-flag + const pubString = "04" + publicKey; + + const encryptedBufferParams = await encryptedBuffer( + new Buffer(pubString, "hex"), + Buffer(message) + ); + // + const encrypted = { + iv: encryptedBufferParams.iv.toString("hex"), + ephemPublicKey: encryptedBufferParams.ephemPublicKey.toString("hex"), + ciphertext: encryptedBufferParams.ciphertext.toString("hex"), + mac: encryptedBufferParams.mac.toString("hex") + }; + return encrypted; +}; + +var decryptWithPrivateKey = async function(privateKey, encrypted) { + // remove trailing '0x' from privateKey + const twoStripped = privateKey.replace(/^.{2}/g, ""); + + const encryptedBuffer = { + iv: new Buffer(encrypted.iv, "hex"), + ephemPublicKey: new Buffer(encrypted.ephemPublicKey, "hex"), + ciphertext: new Buffer(encrypted.ciphertext, "hex"), + mac: new Buffer(encrypted.mac, "hex") + }; + + const decryptedBuffer = await eccdecrypt( + new Buffer(twoStripped, "hex"), + encryptedBuffer + ); + return decryptedBuffer.toString(); +}; + +function sha512(msg) { + return crypto + .createHash("sha512") + .update(msg) + .digest(); +} + +function hmacSha256(key, msg) { + return crypto + .createHmac("sha256", key) + .update(msg) + .digest(); +} + +var derive = function(privateKeyA, publicKeyB) { + return new promise(function(resolve) { + resolve(ecdh.derive(privateKeyA, publicKeyB)); + }); +}; + +function assert(condition, message) { + if (!condition) { + throw new Error(message || "Assertion failed"); + } +} + +var getPublic = (exports.getPublic = function(privateKey) { + assert(privateKey.length === 32, "Bad private key"); + // See https://github.com/wanderer/secp256k1-node/issues/46 + var compressed = secp256k1.publicKeyCreate(privateKey); + return secp256k1.publicKeyConvert(compressed, false); +}); + +function aes256CbcDecrypt(iv, key, ciphertext) { + var cipher = crypto.createDecipheriv("aes-256-cbc", key, iv); + var firstChunk = cipher.update(ciphertext); + var secondChunk = cipher.final(); + return Buffer.concat([firstChunk, secondChunk]); +} + +function aes256CbcEncrypt(iv, key, plaintext) { + var cipher = crypto.createCipheriv("aes-256-cbc", key, iv); + var firstChunk = cipher.update(plaintext); + var secondChunk = cipher.final(); + return Buffer.concat([firstChunk, secondChunk]); +} + +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; +} + +eccdecrypt = function(privateKey, opts) { + return derive(privateKey, opts.ephemPublicKey).then(function(Px) { + var hash = sha512(Px); + var encryptionKey = hash.slice(0, 32); + var macKey = hash.slice(32); + var dataToMac = Buffer.concat([ + opts.iv, + opts.ephemPublicKey, + opts.ciphertext + ]); + var realMac = hmacSha256(macKey, dataToMac); + assert(equalConstTime(opts.mac, realMac), "Bad MAC"); + return aes256CbcDecrypt(opts.iv, encryptionKey, opts.ciphertext); + }); +}; + +// decrypt(e, pk); diff --git a/utils/ecdh.node b/utils/ecdh.node new file mode 100755 index 0000000000000000000000000000000000000000..380daa56c5987edf9435caf415996da5f4979303 GIT binary patch literal 19272 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 Date: Thu, 19 Apr 2018 19:30:20 -0700 Subject: [PATCH 3/9] updated function param names --- index.js | 6 +++--- utils/eccrypto-lite.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 49da7cb3..1d9fb02e 100644 --- a/index.js +++ b/index.js @@ -84,14 +84,14 @@ module.exports = { receiverPublicKey, // by encryping with bobs publicKey, only bob can decrypt the payload with his privateKey JSON.stringify(payload) // we have to stringify the payload before we can encrypt it ); - + return encrypted; }, - decrypt: async function(encryptedMsg, privateKey) { + decrypt: async function(encryptedData, privateKey) { const decrypted = await eccrypto.decryptWithPrivateKey( privateKey, - encryptedMsg + encryptedData ); const decryptedPayload = JSON.parse(decrypted); diff --git a/utils/eccrypto-lite.js b/utils/eccrypto-lite.js index 601cd956..11363d35 100644 --- a/utils/eccrypto-lite.js +++ b/utils/eccrypto-lite.js @@ -5,12 +5,12 @@ var secp256k1 = require("secp256k1"); var ecdh = require("./ecdh"); module.exports = { - decryptWithPrivateKey: function(pk, edata) { - const result = decryptWithPrivateKey(pk, edata); + decryptWithPrivateKey: function(privateKey, encryptedData) { + const result = decryptWithPrivateKey(privateKey, encryptedData); return result; }, - encryptWithPublicKey: function(pk, payload) { - const result = encryptWithPublicKey(pk, payload); + encryptWithPublicKey: function(receiverPublicKey, payload) { + const result = encryptWithPublicKey(receiverPublicKey, payload); return result; } }; From 34677b125170e5e58be8633b38e560c3895cdeac Mon Sep 17 00:00:00 2001 From: Temitope Alabi Date: Wed, 25 Apr 2018 18:39:45 -0700 Subject: [PATCH 4/9] added elliptic library for browser compatibility and changed form of eccrypto --- index.js | 8 +- package.json | 1 + utils/eccrypto-lite.js | 280 +++++++++++++++++++++-------------------- utils/ecdh.node | Bin 19272 -> 0 bytes 4 files changed, 151 insertions(+), 138 deletions(-) delete mode 100755 utils/ecdh.node diff --git a/index.js b/index.js index 1d9fb02e..b2d1cb66 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -const ethUtil = require('ethereumjs-util') -const ethAbi = require('ethereumjs-abi') -const eccrypto = require('./utils/eccrypto-lite') +const ethUtil = require('ethereumjs-util'); +const ethAbi = require('ethereumjs-abi'); +const eccrypto = require('./utils/eccrypto-lite'); module.exports = { @@ -72,7 +72,7 @@ module.exports = { encrypt: async function(senderprivateKey, receiverPublicKey, msgParams) { //first sign message using personalSign functions const signed = this.personalSign(ethUtil.toBuffer(senderprivateKey), msgParams); - + // then create payload const payload = { message: msgParams.data, diff --git a/package.json b/package.json index 81d14691..69ce29c3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "homepage": "https://github.com/flyswatter/eth-sig-util#readme", "dependencies": { + "elliptic": "^6.4.0", "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "ethereumjs-util": "^5.1.1" }, diff --git a/utils/eccrypto-lite.js b/utils/eccrypto-lite.js index 11363d35..f8646bb0 100644 --- a/utils/eccrypto-lite.js +++ b/utils/eccrypto-lite.js @@ -1,155 +1,167 @@ -var crypto = require("crypto"); -var promise = - typeof Promise === "undefined" ? require("es6-promise").Promise : Promise; -var secp256k1 = require("secp256k1"); -var ecdh = require("./ecdh"); +"use strict"; + +var EC = require("elliptic").ec; + +var ec = new EC("secp256k1"); +var cryptoObj = global.crypto || global.msCrypto || {}; +var subtle = cryptoObj.subtle || cryptoObj.webkitSubtle; module.exports = { - decryptWithPrivateKey: function(privateKey, encryptedData) { - const result = decryptWithPrivateKey(privateKey, encryptedData); - return result; + decryptWithPrivateKey: async function(privateKey, encrypted) { + console.log(privateKey) + const twoStripped = privateKey.replace(/^.{2}/g, ''); + console.log(twoStripped) + const encryptedBuffer = { + iv: new Buffer(encrypted.iv, 'hex'), + ephemPublicKey: new Buffer(encrypted.ephemPublicKey, 'hex'), + ciphertext: new Buffer(encrypted.ciphertext, 'hex'), + mac: new Buffer(encrypted.mac, 'hex') + }; + + const decryptedBuffer = await decrypt( + new Buffer(twoStripped, 'hex'), + encryptedBuffer + ); + return decryptedBuffer.toString(); + + }, - encryptWithPublicKey: function(receiverPublicKey, payload) { - const result = encryptWithPublicKey(receiverPublicKey, payload); - return result; + encryptWithPublicKey: async function(receiverPublicKey, payload) { + const pubString = '04' + receiverPublicKey; + const encryptedBuffers = await encrypt( + new Buffer(pubString, 'hex'), + Buffer(payload) + ); + const encrypted = { + iv: encryptedBuffers.iv.toString('hex'), + ephemPublicKey: encryptedBuffers.ephemPublicKey.toString('hex'), + ciphertext: encryptedBuffers.ciphertext.toString('hex'), + mac: encryptedBuffers.mac.toString('hex') + }; + return encrypted; } }; -var encryptWithPublicKey = async function(publicKey, message) { - - //used in encryptedButterParams function - const encryptedBuffer = function(publicKeyTo, msg, opts) { - opts = opts || {}; - // Tmp variable to save context from flat promises; - var ephemPublicKey; - return new promise(function(resolve) { - var ephemPrivateKey = opts.ephemPrivateKey || crypto.randomBytes(32); - ephemPublicKey = getPublic(ephemPrivateKey); - resolve(derive(ephemPrivateKey, publicKeyTo)); - }).then(function(Px) { - var hash = sha512(Px); - var iv = opts.iv || crypto.randomBytes(16); - var encryptionKey = hash.slice(0, 32); - var macKey = hash.slice(32); - var ciphertext = aes256CbcEncrypt(iv, encryptionKey, msg); - var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]); - var mac = hmacSha256(macKey, dataToMac); - return { - iv: iv, - ephemPublicKey: ephemPublicKey, - ciphertext: ciphertext, - mac: mac - }; - }); - }; - - // re-add the compression-flag - const pubString = "04" + publicKey; - - const encryptedBufferParams = await encryptedBuffer( - new Buffer(pubString, "hex"), - Buffer(message) - ); - // - const encrypted = { - iv: encryptedBufferParams.iv.toString("hex"), - ephemPublicKey: encryptedBufferParams.ephemPublicKey.toString("hex"), - ciphertext: encryptedBufferParams.ciphertext.toString("hex"), - mac: encryptedBufferParams.mac.toString("hex") - }; - return encrypted; -}; +function assert(condition, message) { + if (!condition) { + throw new Error(message || "Assertion failed"); + } +} -var decryptWithPrivateKey = async function(privateKey, encrypted) { - // remove trailing '0x' from privateKey - const twoStripped = privateKey.replace(/^.{2}/g, ""); - - const encryptedBuffer = { - iv: new Buffer(encrypted.iv, "hex"), - ephemPublicKey: new Buffer(encrypted.ephemPublicKey, "hex"), - ciphertext: new Buffer(encrypted.ciphertext, "hex"), - mac: new Buffer(encrypted.mac, "hex") - }; - - const decryptedBuffer = await eccdecrypt( - new Buffer(twoStripped, "hex"), - encryptedBuffer - ); - return decryptedBuffer.toString(); -}; +function randomBytes(size) { + var arr = new Uint8Array(size); + global.crypto.getRandomValues(arr); + return new Buffer(arr); +} function sha512(msg) { - return crypto - .createHash("sha512") - .update(msg) - .digest(); + return subtle.digest({name: "SHA-512"}, msg).then(function(hash) { + return new Buffer(new Uint8Array(hash)); + }); } -function hmacSha256(key, msg) { - return crypto - .createHmac("sha256", key) - .update(msg) - .digest(); +function getAes(op) { + return function(iv, key, data) { + 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) { + return new Buffer(new Uint8Array(result)); + }); + }; } -var derive = function(privateKeyA, publicKeyB) { - return new promise(function(resolve) { - resolve(ecdh.derive(privateKeyA, publicKeyB)); - }); -}; - -function assert(condition, message) { - if (!condition) { - throw new Error(message || "Assertion failed"); - } +var aesCbcEncrypt = getAes("encrypt"); +var aesCbcDecrypt = getAes("decrypt"); + +function hmacSha256Sign(key, msg) { + var algorithm = {name: "HMAC", hash: {name: "SHA-256"}}; + var keyp = subtle.importKey("raw", key, algorithm, false, ["sign"]); + return keyp.then(function(cryptoKey) { + return subtle.sign(algorithm, cryptoKey, msg); + }).then(function(sig) { + return new Buffer(new Uint8Array(sig)); + }); } -var getPublic = (exports.getPublic = function(privateKey) { - assert(privateKey.length === 32, "Bad private key"); - // See https://github.com/wanderer/secp256k1-node/issues/46 - var compressed = secp256k1.publicKeyCreate(privateKey); - return secp256k1.publicKeyConvert(compressed, false); -}); - -function aes256CbcDecrypt(iv, key, ciphertext) { - var cipher = crypto.createDecipheriv("aes-256-cbc", key, iv); - var firstChunk = cipher.update(ciphertext); - var secondChunk = cipher.final(); - return Buffer.concat([firstChunk, secondChunk]); +function hmacSha256Verify(key, msg, sig) { + var algorithm = {name: "HMAC", hash: {name: "SHA-256"}}; + var keyp = subtle.importKey("raw", key, algorithm, false, ["verify"]); + return keyp.then(function(cryptoKey) { + return subtle.verify(algorithm, cryptoKey, sig, msg); + }); } -function aes256CbcEncrypt(iv, key, plaintext) { - var cipher = crypto.createCipheriv("aes-256-cbc", key, iv); - var firstChunk = cipher.update(plaintext); - var secondChunk = cipher.final(); - return Buffer.concat([firstChunk, secondChunk]); -} +var getPublic = exports.getPublic = function(privateKey) { + assert(privateKey.length === 32, "Bad private key"); + return new Buffer(ec.keyFromPrivate(privateKey).getPublic("arr")); +}; -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; -} -eccdecrypt = function(privateKey, opts) { - return derive(privateKey, opts.ephemPublicKey).then(function(Px) { - var hash = sha512(Px); - var encryptionKey = hash.slice(0, 32); - var macKey = hash.slice(32); - var dataToMac = Buffer.concat([ - opts.iv, - opts.ephemPublicKey, - opts.ciphertext - ]); - var realMac = hmacSha256(macKey, dataToMac); - assert(equalConstTime(opts.mac, realMac), "Bad MAC"); - return aes256CbcDecrypt(opts.iv, encryptionKey, opts.ciphertext); - }); +var derive = exports.derive = function(privateKeyA, publicKeyB) { + return new Promise(function(resolve) { + assert(Buffer.isBuffer(privateKeyA), "Bad input"); + assert(Buffer.isBuffer(publicKeyB), "Bad input"); + assert(privateKeyA.length === 32, "Bad private key"); + assert(publicKeyB.length === 65, "Bad public key"); + assert(publicKeyB[0] === 4, "Bad public key"); + var keyA = ec.keyFromPrivate(privateKeyA); + var keyB = ec.keyFromPublic(publicKeyB); + var Px = keyA.derive(keyB.getPublic()); // BN instance + resolve(new Buffer(Px.toArray())); + }); }; -// decrypt(e, pk); +const encrypt = function(publicKeyTo, msg, opts) { + assert(subtle, "WebCryptoAPI is not available"); + 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); + 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, + }; + }); +}; +const decrypt = function(privateKey, opts) { + assert(subtle, "WebCryptoAPI is not available"); + // 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 Buffer(new Uint8Array(msg)); + }); +}; \ No newline at end of file diff --git a/utils/ecdh.node b/utils/ecdh.node deleted file mode 100755 index 380daa56c5987edf9435caf415996da5f4979303..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19272 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 Date: Mon, 7 May 2018 18:14:26 -0700 Subject: [PATCH 5/9] implemented nacl first run still need a solid technique for converting ethereum keys/addresses to their nacl compatible versions --- index.js | 59 +++++++++++++++++++++++++++------------------------ package.json | 4 +++- test/index.js | 58 ++++++++++++++++++++++++-------------------------- 3 files changed, 62 insertions(+), 59 deletions(-) diff --git a/index.js b/index.js index b2d1cb66..3ab3411b 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ const ethUtil = require('ethereumjs-util'); const ethAbi = require('ethereumjs-abi'); -const eccrypto = require('./utils/eccrypto-lite'); +const nacl = require('tweetnacl'); +nacl.util = require('tweetnacl-util'); module.exports = { @@ -69,40 +70,42 @@ module.exports = { return ethUtil.bufferToHex(sender) }, - encrypt: async function(senderprivateKey, receiverPublicKey, msgParams) { - //first sign message using personalSign functions - const signed = this.personalSign(ethUtil.toBuffer(senderprivateKey), msgParams); - - // then create payload - const payload = { - message: msgParams.data, - signed + encrypt: function(senderprivateKey, receiverPublicKey, msgParams) { + // assemble encryption parameters - from string to UInt8 + var privKeyUInt8Array = nacl.util.decodeBase64(senderprivateKey); + var pubKeyUInt8Array = nacl.util.decodeBase64(receiverPublicKey); + var msgParamsUInt8Array = nacl.util.decodeUTF8(msgParams.data); + + var nonce = nacl.randomBytes(nacl.box.nonceLength); + + // encrypt + var encryptedMessage = nacl.box(msgParamsUInt8Array, nonce, pubKeyUInt8Array, privKeyUInt8Array); + + // handle encrypted data + var output = { + version: '0x04', + nonce: nacl.util.encodeBase64(nonce), + ciphertext: nacl.util.encodeBase64(encryptedMessage) }; - //then encrypt - const encrypted = await eccrypto.encryptWithPublicKey( - receiverPublicKey, // by encryping with bobs publicKey, only bob can decrypt the payload with his privateKey - JSON.stringify(payload) // we have to stringify the payload before we can encrypt it - ); - - return encrypted; + // return encrypted msg data + return output; }, - decrypt: async function(encryptedData, privateKey) { - const decrypted = await eccrypto.decryptWithPrivateKey( - privateKey, - encryptedData - ); + decrypt: async function(encryptedData, receiverPrivateKey, senderPublicKey) { + //string to buffer to UInt8Array + var privKeyUInt8Array = nacl.util.decodeBase64(receiverPrivateKey); + var pubKeyUInt8Array = nacl.util.decodeBase64(senderPublicKey); - const decryptedPayload = JSON.parse(decrypted); + // assemble decryption parameters + var nonce = nacl.util.decodeBase64(encryptedData.nonce); + var ciphertext = nacl.util.decodeBase64(encryptedData.ciphertext); - //check signature - const senderAddress = this.recoverPersonalSignature({ - data: decryptedPayload.message, - sig: decryptedPayload.signed - }); + // decrypt + var decryptedMessage = nacl.box.open(ciphertext, nonce, pubKeyUInt8Array, privKeyUInt8Array); - return { from: senderAddress, message: decryptedPayload.message }; + // return decrypted msg data + return nacl.util.encodeUTF8(decryptedMessage); } } diff --git a/package.json b/package.json index 69ce29c3..e905aa71 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "dependencies": { "elliptic": "^6.4.0", "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", - "ethereumjs-util": "^5.1.1" + "ethereumjs-util": "^5.1.1", + "tweetnacl": "^1.0.0", + "tweetnacl-util": "^0.15.0" }, "devDependencies": { "mocha": "^4.0.0", diff --git a/test/index.js b/test/index.js index 7343269d..bc5386bf 100644 --- a/test/index.js +++ b/test/index.js @@ -271,45 +271,43 @@ function typedSignatureHashThrowsTest(opts) { }) } -//encryption test -test("encrypt text with ECDH", async t => { - t.plan(4); +// these keys were generated using nacl.box.keyPair() +// To-do: Convert ethereum keys into nacl compatible versions +const alice = { privateKey: 'B+TAO3bSusggdubN+IVZ29dvPGySf8V+1aBptUYJKJU=', + publicKey: 'eMiE7HauRAGMBEnmLUnT+mk78RWJ2I2AZhOoksfl/gg=' } + +const bob = { privateKey: '/oaOMvAzhHzVWqIlKqum1Pyg2/Xc2Wa+1DIr4PhAO1M=', + publicKey: 'TKF5xmj85gVm5WjWScJVfXLXWRLFmoxuXL6S1wCxmkI=' } - const senderPrivateKey = - "0x7f2e0a903b5e5fdee8458987386ca23c3f2db7d07eae02d3c2e6e5c6f17599a0"; - const receiverPublicKey = - "44be5ff4da3cc349f101b0cb189887a55b05efc2ed97ab3054d2a16c19ca488685c70fa48556ef3d6d5342cd996ba4b41df4214947ca5ecf6324b9bc39fa5246"; - const msgParams = { data: "My name is Satoshi Buterin" }; +const secretMessage = {data:'My name is Satoshi Buterin'}; + +//encryption test +test("encrypt text with nacl", async t => { + t.plan(3); const result = await sigUtil.encrypt( - senderPrivateKey, - receiverPublicKey, - msgParams + alice.privateKey, + bob.publicKey, + secretMessage ); - t.ok(result.iv); - t.ok(result.ephemPublicKey); + console.log("RESULT", result) + + t.ok(result.version); + t.ok(result.nonce); t.ok(result.ciphertext); - t.ok(result.mac); + }); //decryption test -test("decrypt ECDh encrypted text", async t => { +test("decrypt text with nacl", async t => { t.plan(1); - const originalText = "My name is Satoshi Buterin"; + //encrypted data - const encryptedData = { - iv: "c70881072e88ccbf084d9c172ba96f52", - ephemPublicKey: - "0451e0077b7d1f87720d5e6cd19c6379998ac918569847c3b87b4ea19cfcd89b65c986ac6a1cc782bc61d85d0b9628176cf077fc384a7e30a051586015d94cdb37", - ciphertext: - "add5994482aacd0ad2f148fdb91820e89d4ee9b3b165bff04791b6489a8a8f5364de15b34f7173f6e41f2283e9a3be7506ec98a6176b91c473394b6a97e1cc3a71552467ab6e378c3cfa0dace7321f24c7306f7fba7f7e9fc7b6c52fb4c136d9f67d3c16144417f1b18bfb59475425bac63ddcdba295eac7d9688b2e0f319caac59ed723d7ceb1b475766a34f164dc16502e61541d3fab5fbad4b1bd1c82454ca1614f75cd23dec1593c50a11f4c3c9ab0503ba3aa4031452701ab137f3dedfc", - mac: "9f8ef19e474afa745187e59b73bbacbeabfc55e150a271497bc68bf8e40e2967" - }; - - //private key - const privateKey = - "0x7e5374ec2ef0d91761a6e72fdf8f6ac665519bfdf6da0a2329cf0d804514b816"; - const result = await sigUtil.decrypt(encryptedData, privateKey); - t.equal(result.message, originalText); + const encryptedData = { version: '0x04', + nonce: 'SrmrV3Ob3UzuzXYN6sdj8jQwAC5WUIsY', + ciphertext: 'FXpiOUOSZIYG2Iviv0LQvSCE8d3NqUbCKZ7x3WWBAl1DJ/brlU/jzhq4' }; + + const result = await sigUtil.decrypt(encryptedData, bob.privateKey, alice.publicKey); + t.equal(result, secretMessage.data); }); \ No newline at end of file From 335cf538f11469dc68a4aa1ad78355a0cd82dd03 Mon Sep 17 00:00:00 2001 From: Temitope Alabi Date: Mon, 7 May 2018 18:39:55 -0700 Subject: [PATCH 6/9] added vesion to method --- index.js | 6 +++--- test/index.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 3ab3411b..d7a467c1 100644 --- a/index.js +++ b/index.js @@ -70,7 +70,7 @@ module.exports = { return ethUtil.bufferToHex(sender) }, - encrypt: function(senderprivateKey, receiverPublicKey, msgParams) { + encrypt: function(senderprivateKey, receiverPublicKey, msgParams, version) { // assemble encryption parameters - from string to UInt8 var privKeyUInt8Array = nacl.util.decodeBase64(senderprivateKey); var pubKeyUInt8Array = nacl.util.decodeBase64(receiverPublicKey); @@ -83,7 +83,7 @@ module.exports = { // handle encrypted data var output = { - version: '0x04', + version: 'x25519-xsalsa20-poly1305', nonce: nacl.util.encodeBase64(nonce), ciphertext: nacl.util.encodeBase64(encryptedMessage) }; @@ -92,7 +92,7 @@ module.exports = { return output; }, - decrypt: async function(encryptedData, receiverPrivateKey, senderPublicKey) { + decrypt: async function(encryptedData, receiverPrivateKey, senderPublicKey, version) { //string to buffer to UInt8Array var privKeyUInt8Array = nacl.util.decodeBase64(receiverPrivateKey); var pubKeyUInt8Array = nacl.util.decodeBase64(senderPublicKey); diff --git a/test/index.js b/test/index.js index bc5386bf..34a14970 100644 --- a/test/index.js +++ b/test/index.js @@ -304,7 +304,7 @@ test("decrypt text with nacl", async t => { t.plan(1); //encrypted data - const encryptedData = { version: '0x04', + const encryptedData = { version: 'x25519-xsalsa20-poly1305', nonce: 'SrmrV3Ob3UzuzXYN6sdj8jQwAC5WUIsY', ciphertext: 'FXpiOUOSZIYG2Iviv0LQvSCE8d3NqUbCKZ7x3WWBAl1DJ/brlU/jzhq4' }; From f1707307f1f0452770150f72ad622185023c8a6d Mon Sep 17 00:00:00 2001 From: Temitope Alabi Date: Thu, 10 May 2018 16:41:26 -0700 Subject: [PATCH 7/9] using nacl library, getEncryptionPublicKey(reciever), generate ephemKeypair() to use in nacl.box encryption --- index.js | 95 ++++++++++++++++++++++++++++++++++----------------- test/index.js | 41 +++++++++++++--------- 2 files changed, 88 insertions(+), 48 deletions(-) diff --git a/index.js b/index.js index d7a467c1..e8580aff 100644 --- a/index.js +++ b/index.js @@ -70,42 +70,66 @@ module.exports = { return ethUtil.bufferToHex(sender) }, - encrypt: function(senderprivateKey, receiverPublicKey, msgParams, version) { - // assemble encryption parameters - from string to UInt8 - var privKeyUInt8Array = nacl.util.decodeBase64(senderprivateKey); - var pubKeyUInt8Array = nacl.util.decodeBase64(receiverPublicKey); - var msgParamsUInt8Array = nacl.util.decodeUTF8(msgParams.data); - - var nonce = nacl.randomBytes(nacl.box.nonceLength); - - // encrypt - var encryptedMessage = nacl.box(msgParamsUInt8Array, nonce, pubKeyUInt8Array, privKeyUInt8Array); - - // handle encrypted data - var output = { - version: 'x25519-xsalsa20-poly1305', - nonce: nacl.util.encodeBase64(nonce), - ciphertext: nacl.util.encodeBase64(encryptedMessage) - }; - - // return encrypted msg data - return output; + encrypt: function(receiverPublicKey, msgParams, version) { + + switch(version) { + case 'some-version': + //code block + break; + default: + //generate ephemeral keypair + var ephemeralKeyPair = nacl.box.keyPair() + + // assemble encryption parameters - from string to UInt8 + var pubKeyUInt8Array = nacl.util.decodeBase64(receiverPublicKey); + var msgParamsUInt8Array = nacl.util.decodeUTF8(msgParams.data); + var nonce = nacl.randomBytes(nacl.box.nonceLength); + + // encrypt + var encryptedMessage = nacl.box(msgParamsUInt8Array, nonce, pubKeyUInt8Array, ephemeralKeyPair.secretKey); + + // handle encrypted data + var output = { + version: 'x25519-xsalsa20-poly1305', + nonce: nacl.util.encodeBase64(nonce), + ephemPublicKey: nacl.util.encodeBase64(ephemeralKeyPair.publicKey), + ciphertext: nacl.util.encodeBase64(encryptedMessage) + }; + + // return encrypted msg data + return output; + } }, - decrypt: async function(encryptedData, receiverPrivateKey, senderPublicKey, version) { - //string to buffer to UInt8Array - var privKeyUInt8Array = nacl.util.decodeBase64(receiverPrivateKey); - var pubKeyUInt8Array = nacl.util.decodeBase64(senderPublicKey); + decrypt: async function(encryptedData, receiverPrivateKey, version) { + + switch(version) { + case 'some-version': + //code block + break; + default: + //string to buffer to UInt8Array + var recieverPrivateKeyUint8Array = nacl_decodeHex(receiverPrivateKey) + var recieverEncryptionPrivateKey = nacl.box.keyPair.fromSecretKey(recieverPrivateKeyUint8Array).secretKey + + // assemble decryption parameters + var nonce = nacl.util.decodeBase64(encryptedData.nonce); + var ciphertext = nacl.util.decodeBase64(encryptedData.ciphertext); + var ephemPublicKey = nacl.util.decodeBase64(encryptedData.ephemPublicKey); - // assemble decryption parameters - var nonce = nacl.util.decodeBase64(encryptedData.nonce); - var ciphertext = nacl.util.decodeBase64(encryptedData.ciphertext); + // decrypt + var decryptedMessage = nacl.box.open(ciphertext, nonce, ephemPublicKey, recieverEncryptionPrivateKey); - // decrypt - var decryptedMessage = nacl.box.open(ciphertext, nonce, pubKeyUInt8Array, privKeyUInt8Array); + // return decrypted msg data + return nacl.util.encodeUTF8(decryptedMessage); + } + + }, - // return decrypted msg data - return nacl.util.encodeUTF8(decryptedMessage); + getEncryptionPublicKey: function(privateKey){ + var privateKeyUint8Array = nacl_decodeHex(privateKey) + var encryptionPublicKey = nacl.box.keyPair.fromSecretKey(privateKeyUint8Array).publicKey + return nacl.util.encodeBase64(encryptionPublicKey) } } @@ -156,3 +180,12 @@ function padWithZeroes (number, length) { } return myString } + +//converts hex strings to the Uint8Array format used by nacl +function nacl_decodeHex(msgHex) { + var msgBase64 = (new Buffer(msgHex, 'hex')).toString('base64'); + return nacl.util.decodeBase64(msgBase64); +} + + + diff --git a/test/index.js b/test/index.js index 34a14970..60cfcd36 100644 --- a/test/index.js +++ b/test/index.js @@ -271,43 +271,50 @@ function typedSignatureHashThrowsTest(opts) { }) } -// these keys were generated using nacl.box.keyPair() -// To-do: Convert ethereum keys into nacl compatible versions -const alice = { privateKey: 'B+TAO3bSusggdubN+IVZ29dvPGySf8V+1aBptUYJKJU=', - publicKey: 'eMiE7HauRAGMBEnmLUnT+mk78RWJ2I2AZhOoksfl/gg=' } - -const bob = { privateKey: '/oaOMvAzhHzVWqIlKqum1Pyg2/Xc2Wa+1DIr4PhAO1M=', - publicKey: 'TKF5xmj85gVm5WjWScJVfXLXWRLFmoxuXL6S1wCxmkI=' } +const bob = { + ethereumPrivateKey: '7e5374ec2ef0d91761a6e72fdf8f6ac665519bfdf6da0a2329cf0d804514b816', + encryptionPrivateKey: 'flN07C7w2Rdhpucv349qxmVRm/322gojKc8NgEUUuBY=', + encryptionPublicKey: 'C5YMNdqE4kLgxQhJO1MfuQcHP5hjVSXzamzd/TxlR0U=' } const secretMessage = {data:'My name is Satoshi Buterin'}; +test("Getting bob's encryptionPublicKey", async t => { + t.plan(1); + + const result = await sigUtil.getEncryptionPublicKey(bob.ethereumPrivateKey) + t.equal(result, bob.encryptionPublicKey); +}); + //encryption test -test("encrypt text with nacl", async t => { - t.plan(3); +test("Alice encrypts message with bob's encryptionPublicKey", async t => { + + + t.plan(4); const result = await sigUtil.encrypt( - alice.privateKey, - bob.publicKey, - secretMessage + bob.encryptionPublicKey, + secretMessage, ); console.log("RESULT", result) t.ok(result.version); t.ok(result.nonce); + t.ok(result.ephemPublicKey); t.ok(result.ciphertext); }); -//decryption test -test("decrypt text with nacl", async t => { +// decryption test +test("Bob decrypts message that Alice sent to him", async t => { t.plan(1); //encrypted data const encryptedData = { version: 'x25519-xsalsa20-poly1305', - nonce: 'SrmrV3Ob3UzuzXYN6sdj8jQwAC5WUIsY', - ciphertext: 'FXpiOUOSZIYG2Iviv0LQvSCE8d3NqUbCKZ7x3WWBAl1DJ/brlU/jzhq4' }; + nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', + ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', + ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }; - const result = await sigUtil.decrypt(encryptedData, bob.privateKey, alice.publicKey); + const result = await sigUtil.decrypt(encryptedData, bob.ethereumPrivateKey); t.equal(result, secretMessage.data); }); \ No newline at end of file From 366bfbab01f7b5c30494de0bda8bbd71a46cf393 Mon Sep 17 00:00:00 2001 From: Temitope Alabi Date: Wed, 16 May 2018 15:16:03 -0700 Subject: [PATCH 8/9] Updated version checking, added error handling --- index.js | 43 ++++++++++++++++++++++++++++++------------- test/index.js | 1 + 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index e8580aff..2bd6305b 100644 --- a/index.js +++ b/index.js @@ -73,15 +73,21 @@ module.exports = { encrypt: function(receiverPublicKey, msgParams, version) { switch(version) { - case 'some-version': - //code block - break; - default: + case 'x25519-xsalsa20-poly1305': + console.log(typeof msgParams.data ) + if( typeof msgParams.data == 'undefined'){ + throw new Error('Cannot detect secret message, message params should be of the form {data: "secret message"} ') + } //generate ephemeral keypair var ephemeralKeyPair = nacl.box.keyPair() // assemble encryption parameters - from string to UInt8 - var pubKeyUInt8Array = nacl.util.decodeBase64(receiverPublicKey); + try { + var pubKeyUInt8Array = nacl.util.decodeBase64(receiverPublicKey); + } catch (err){ + throw new Error('Bad public key') + } + var msgParamsUInt8Array = nacl.util.decodeUTF8(msgParams.data); var nonce = nacl.randomBytes(nacl.box.nonceLength); @@ -95,19 +101,19 @@ module.exports = { ephemPublicKey: nacl.util.encodeBase64(ephemeralKeyPair.publicKey), ciphertext: nacl.util.encodeBase64(encryptedMessage) }; - // return encrypted msg data return output; + + default: + throw new Error('Encryption type/version not supported') + } }, - decrypt: async function(encryptedData, receiverPrivateKey, version) { + decrypt: async function(encryptedData, receiverPrivateKey) { - switch(version) { - case 'some-version': - //code block - break; - default: + switch(encryptedData.version) { + case 'x25519-xsalsa20-poly1305': //string to buffer to UInt8Array var recieverPrivateKeyUint8Array = nacl_decodeHex(receiverPrivateKey) var recieverEncryptionPrivateKey = nacl.box.keyPair.fromSecretKey(recieverPrivateKeyUint8Array).secretKey @@ -121,7 +127,18 @@ module.exports = { var decryptedMessage = nacl.box.open(ciphertext, nonce, ephemPublicKey, recieverEncryptionPrivateKey); // return decrypted msg data - return nacl.util.encodeUTF8(decryptedMessage); + var output = nacl.util.encodeUTF8(decryptedMessage); + + if (output){ + return output; + }else{ + const error = new Error('Decrypt authentication failed. ') + throw error + } + + + default: + throw new Error('Encryption type/version not supported') } }, diff --git a/test/index.js b/test/index.js index 60cfcd36..576b76d3 100644 --- a/test/index.js +++ b/test/index.js @@ -294,6 +294,7 @@ test("Alice encrypts message with bob's encryptionPublicKey", async t => { const result = await sigUtil.encrypt( bob.encryptionPublicKey, secretMessage, + 'x25519-xsalsa20-poly1305' ); console.log("RESULT", result) From b2a4bcf93191d7298b418ef70a2c4040cdfd77fa Mon Sep 17 00:00:00 2001 From: Temitope Alabi Date: Sun, 27 May 2018 22:58:12 -0700 Subject: [PATCH 9/9] simplified error output, added error handling tests --- index.js | 13 ++++++---- test/index.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 2bd6305b..edb7ad2a 100644 --- a/index.js +++ b/index.js @@ -110,7 +110,7 @@ module.exports = { } }, - decrypt: async function(encryptedData, receiverPrivateKey) { + decrypt: function(encryptedData, receiverPrivateKey) { switch(encryptedData.version) { case 'x25519-xsalsa20-poly1305': @@ -127,18 +127,21 @@ module.exports = { var decryptedMessage = nacl.box.open(ciphertext, nonce, ephemPublicKey, recieverEncryptionPrivateKey); // return decrypted msg data - var output = nacl.util.encodeUTF8(decryptedMessage); + try { + var output = nacl.util.encodeUTF8(decryptedMessage); + }catch(err) { + throw new Error('Decryption failed.') + } if (output){ return output; }else{ - const error = new Error('Decrypt authentication failed. ') - throw error + throw new Error('Decryption failed.') } default: - throw new Error('Encryption type/version not supported') + throw new Error('Encryption type/version not supported.') } }, diff --git a/test/index.js b/test/index.js index 576b76d3..43fa5f80 100644 --- a/test/index.js +++ b/test/index.js @@ -278,6 +278,11 @@ const bob = { const secretMessage = {data:'My name is Satoshi Buterin'}; +const encryptedData = { version: 'x25519-xsalsa20-poly1305', +nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', +ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', +ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }; + test("Getting bob's encryptionPublicKey", async t => { t.plan(1); @@ -307,15 +312,64 @@ test("Alice encrypts message with bob's encryptionPublicKey", async t => { }); // decryption test -test("Bob decrypts message that Alice sent to him", async t => { +test("Bob decrypts message that Alice sent to him", t => { t.plan(1); - //encrypted data - const encryptedData = { version: 'x25519-xsalsa20-poly1305', + const result = sigUtil.decrypt(encryptedData, bob.ethereumPrivateKey); + t.equal(result, secretMessage.data); +}); + +test('Decryption failed because version is wrong or missing', t =>{ + t.plan(1) + + const badVersionData = { version: 'x256k1-aes256cbc', nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }; - const result = await sigUtil.decrypt(encryptedData, bob.ethereumPrivateKey); - t.equal(result, secretMessage.data); -}); \ No newline at end of file + t.throws( function() { sigUtil.decrypt(badVersionData, bob.ethereumPrivateKey)}, 'Encryption type/version not supported.') +}); + +test('Decryption failed because nonce is wrong or missing', t => { + t.plan(1); + + //encrypted data + const badNonceData = { version: 'x25519-xsalsa20-poly1305', + nonce: '', + ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', + ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }; + + t.throws(function() { sigUtil.decrypt(badNonceData, bob.ethereumPrivateKey)}, 'Decryption failed.') + +}); + +test('Decryption failed because ephemPublicKey is wrong or missing', t => { + t.plan(1); + + //encrypted data + const badEphemData = { version: 'x25519-xsalsa20-poly1305', + nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', + ephemPublicKey: 'FFFF/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', + ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }; + + t.throws(function() { sigUtil.decrypt(badEphemData, bob.ethereumPrivateKey)}, 'Decryption failed.') +}); + +test('Decryption failed because cyphertext is wrong or missing', async t => { + t.plan(1); + + //encrypted data + const badCypherData = { version: 'x25519-xsalsa20-poly1305', + nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', + ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', + ciphertext: 'ffffff/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }; + + t.throws(function() { sigUtil.decrypt(badEphemData, bob.ethereumPrivateKey)}, 'Decryption failed.') +}); + +test("Decryption fails because you are not the recipient", t => { + t.plan(1); + + t.throws(function() { sigUtil.decrypt(encryptedData, alice.ethereumPrivateKey)}, 'Decryption failed.') +}); +