diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..496ee2ca --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 3cf61093..a401f485 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "axios": "^0.27.2", + "ethereum-cryptography": "^1.1.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -517,6 +518,70 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1178,6 +1243,17 @@ "node": ">=0.8.0" } }, + "node_modules/ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "dependencies": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2107,6 +2183,40 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==" + }, + "@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==" + }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, + "@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "requires": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "requires": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -2497,6 +2607,17 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, + "ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "requires": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", diff --git a/client/package.json b/client/package.json index f662261b..d7cb5ce1 100644 --- a/client/package.json +++ b/client/package.json @@ -4,12 +4,13 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host 0.0.0.0", "build": "vite build", "preview": "vite preview" }, "dependencies": { "axios": "^0.27.2", + "ethereum-cryptography": "^1.1.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100755 index 00000000..fbd47dfa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: "3" + +services: + node-server: + tty: true + build: + context: . + dockerfile: docker/nodejs/Dockerfile + ports: + - 3042:3042 + - 5173:5173 + restart: unless-stopped + volumes: + - ./:/app + \ No newline at end of file diff --git a/docker/nodejs/Dockerfile b/docker/nodejs/Dockerfile new file mode 100755 index 00000000..6bc21df1 --- /dev/null +++ b/docker/nodejs/Dockerfile @@ -0,0 +1,5 @@ +FROM node:18.12 +LABEL name="Fazal-E-Rabbi" email="fazal.newtech@gmail.com" + +WORKDIR /app +EXPOSE 3042 5173 diff --git a/readme.md b/readme.md index 55d551d8..45779a3f 100644 --- a/readme.md +++ b/readme.md @@ -5,27 +5,34 @@ This project is an example of using a client and server to facilitate transfers However, something that we would like to incoporate is Public Key Cryptography. By using Elliptic Curve Digital Signatures we can make it so the server only allows transfers that have been signed for by the person who owns the associated address. ### Video instructions + For an overview of this project as well as getting started instructions, check out the following video: https://www.loom.com/share/0d3c74890b8e44a5918c4cacb3f646c4 - + ### Client The client folder contains a [react app](https://reactjs.org/) using [vite](https://vitejs.dev/). To get started, follow these steps: 1. Open up a terminal in the `/client` folder 2. Run `npm install` to install all the depedencies -3. Run `npm run dev` to start the application +3. Run `npm run dev` to start the application 4. Now you should be able to visit the app at http://127.0.0.1:5173/ ### Server The server folder contains a node.js server using [express](https://expressjs.com/). To run the server, follow these steps: -1. Open a terminal within the `/server` folder -2. Run `npm install` to install all the depedencies -3. Run `node index` to start the server +1. Open a terminal within the `/server` folder +2. Run `npm install` to install all the depedencies +3. Run `node index` to start the server -The application should connect to the default server port (3042) automatically! +The application should connect to the default server port (3042) automatically! _Hint_ - Use [nodemon](https://www.npmjs.com/package/nodemon) instead of `node` to automatically restart the server on any changes. + +### Private keys: + +1. cdb9e4375c44d33c80ee6c09f71ec3040fab2bb078a70e829e1193efb1aa1551 +2. 4791bdd76e771b729152b4464fca807d2e8ad55cb6a15601ea21e3b80a6f0535 +3. 2a51b21a31600e1342cf1d258d911aab314c7f01c9ba753eabc24021089d9dc2 diff --git a/server/index.js b/server/index.js index 3dbd053b..e2aed016 100644 --- a/server/index.js +++ b/server/index.js @@ -1,35 +1,43 @@ const express = require("express"); const app = express(); const cors = require("cors"); +const { getAddress } = require("./scripts/cryptography"); const port = 3042; app.use(cors()); app.use(express.json()); const balances = { - "0x1": 100, - "0x2": 50, - "0x3": 75, + "3b01872578dc3a74bbaafa4ded424b54cefc7883": 100, + "5342f7810d21bb46fb2093e77fc3cf9a0b4a8b20": 50, + "50aeafd07d1cf0bc3fa3665e94e4a7e91fd70d46": 75, }; -app.get("/balance/:address", (req, res) => { - const { address } = req.params; +app.get("/balance/:privateKey", async (req, res) => { + const { privateKey } = req.params; + const message = "Get Blanace"; + const address = await getAddress(message, privateKey); + console.log("address: ", address); + const balance = balances[address] || 0; res.send({ balance }); }); -app.post("/send", (req, res) => { +app.post("/send", async (req, res) => { const { sender, recipient, amount } = req.body; - setInitialBalance(sender); - setInitialBalance(recipient); + const senderAddress = await getAddress("Get Balance", sender); + const recipientAddress = await getAddress("Get Balance", recipient); - if (balances[sender] < amount) { + setInitialBalance(senderAddress); + setInitialBalance(recipientAddress); + + if (balances[senderAddress] < amount) { res.status(400).send({ message: "Not enough funds!" }); } else { - balances[sender] -= amount; - balances[recipient] += amount; - res.send({ balance: balances[sender] }); + balances[senderAddress] -= amount; + balances[recipientAddress] += amount; + res.send({ balance: balances[senderAddress] }); } }); diff --git a/server/package-lock.json b/server/package-lock.json index 612c6e2d..cb8a76e5 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,9 +10,74 @@ "license": "ISC", "dependencies": { "cors": "^2.8.5", + "ethereum-cryptography": "^1.1.2", "express": "^4.18.1" } }, + "node_modules/@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -168,6 +233,17 @@ "node": ">= 0.6" } }, + "node_modules/ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "dependencies": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, "node_modules/express": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", @@ -611,6 +687,40 @@ } }, "dependencies": { + "@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==" + }, + "@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==" + }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, + "@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "requires": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "requires": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -728,6 +838,17 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "requires": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, "express": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", diff --git a/server/package.json b/server/package.json index c03b40a9..63bd6325 100644 --- a/server/package.json +++ b/server/package.json @@ -11,6 +11,7 @@ "license": "ISC", "dependencies": { "cors": "^2.8.5", + "ethereum-cryptography": "^1.1.2", "express": "^4.18.1" } } diff --git a/server/scripts/cryptography.js b/server/scripts/cryptography.js new file mode 100644 index 00000000..88d79d95 --- /dev/null +++ b/server/scripts/cryptography.js @@ -0,0 +1,33 @@ +const secp = require('ethereum-cryptography/secp256k1'); +const { keccak256 } = require("ethereum-cryptography/keccak"); +const { toHex, utf8ToBytes } = require('ethereum-cryptography/utils'); + + +const hashMessage = (message) => { + let bytes = utf8ToBytes(message); + return keccak256(bytes); +} + +const signMessage = async (message, privateKey) => { + let hash = hashMessage(message); + return secp.sign(hash, privateKey, {recovered: true}); +} + +const recoverKey = (message, signature, recoveryBit) => { + let hash = hashMessage(message); + return secp.recoverPublicKey(hash, signature, recoveryBit); +} + +const getAddress = async (message, privateKey) => { + const [sig, recoveryBit] = await signMessage(message, privateKey); + const recoveryKey = recoverKey(message, sig, recoveryBit); + const slicedPublickey = recoveryKey.slice(1); + const keccak = keccak256(slicedPublickey); + return toHex(keccak.slice(-20)); +} + +module.exports = { getAddress }; + +// private key 1: cdb9e4375c44d33c80ee6c09f71ec3040fab2bb078a70e829e1193efb1aa1551 ----> address: 3b01872578dc3a74bbaafa4ded424b54cefc7883 +// private key 2: 4791bdd76e771b729152b4464fca807d2e8ad55cb6a15601ea21e3b80a6f0535 ----> address: 5342f7810d21bb46fb2093e77fc3cf9a0b4a8b20 +// private key 3: 2a51b21a31600e1342cf1d258d911aab314c7f01c9ba753eabc24021089d9dc2 ----> address: 50aeafd07d1cf0bc3fa3665e94e4a7e91fd70d46 diff --git a/server/scripts/generate-privatekey.js b/server/scripts/generate-privatekey.js new file mode 100644 index 00000000..55e91ce0 --- /dev/null +++ b/server/scripts/generate-privatekey.js @@ -0,0 +1,8 @@ +const secp = require('ethereum-cryptography/secp256k1'); +const {toHex} = require('ethereum-cryptography/utils'); + +const privateKey = secp.utils.randomPrivateKey(); +console.log("PrivateKey: ", toHex(privateKey)); + +const publicKey = secp.getPublicKey(privateKey); +console.log("Public Key: ", toHex(publicKey));