diff --git a/client/package-lock.json b/client/package-lock.json index 3cf61093..9a0e4963 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": "^2.1.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -517,6 +518,61 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", + "dependencies": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", + "dependencies": { + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "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 +1234,17 @@ "node": ">=0.8.0" } }, + "node_modules/ethereum-cryptography": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "dependencies": { + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2107,6 +2174,43 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "requires": { + "@noble/hashes": "1.3.3" + } + }, + "@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==" + }, + "@scure/base": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==" + }, + "@scure/bip32": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", + "requires": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + } + }, + "@scure/bip39": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", + "requires": { + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + } + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -2497,6 +2601,17 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, + "ethereum-cryptography": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "requires": { + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" + } + }, "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..4b330fcb 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "axios": "^0.27.2", + "ethereum-cryptography": "^2.1.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/client/src/App.jsx b/client/src/App.jsx index 7254c1fa..8821fe68 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -6,6 +6,7 @@ import { useState } from "react"; function App() { const [balance, setBalance] = useState(0); const [address, setAddress] = useState(""); + const [privateKey, setPrivateKey] = useState(""); return (
@@ -14,8 +15,14 @@ function App() { setBalance={setBalance} address={address} setAddress={setAddress} + privateKey={privateKey} + setPrivateKey={setPrivateKey} + /> + -
); } diff --git a/client/src/Transfer.jsx b/client/src/Transfer.jsx index 7dac2bef..ecd04f78 100644 --- a/client/src/Transfer.jsx +++ b/client/src/Transfer.jsx @@ -1,15 +1,34 @@ -import { useState } from "react"; +import React, { useState } from "react"; +import { keccak_256 } from "@noble/hashes/sha3"; +import { secp256k1 } from '@noble/curves/secp256k1'; +import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils"; import server from "./server"; -function Transfer({ address, setBalance }) { +function Transfer({ address, setBalance, privateKey }) { + const [sendAmount, setSendAmount] = useState(""); const [recipient, setRecipient] = useState(""); - + const setValue = (setter) => (evt) => setter(evt.target.value); - async function transfer(evt) { + const hashMessage = (message) => { + const bytes = utf8ToBytes(message); + const hash = keccak_256(bytes); + return hash; + }; + + const signMessage = (message, privateKey) => { + const signed = secp256k1.sign(hashMessage(message), privateKey); + return signed; + }; + + const jsonReplacer = (key, value) => typeof value === "bigint" ? value.toString() : value; + + async function transfer (evt) { evt.preventDefault(); + const senderSignature = signMessage("transfer", privateKey); + try { const { data: { balance }, @@ -17,7 +36,9 @@ function Transfer({ address, setBalance }) { sender: address, amount: parseInt(sendAmount), recipient, + signature: JSON.stringify(senderSignature, jsonReplacer) }); + setBalance(balance); } catch (ex) { alert(ex.response.data.message); @@ -33,22 +54,26 @@ function Transfer({ address, setBalance }) { - + ); } + export default Transfer; diff --git a/client/src/Wallet.jsx b/client/src/Wallet.jsx index 8dde5242..0b7eadc4 100644 --- a/client/src/Wallet.jsx +++ b/client/src/Wallet.jsx @@ -1,31 +1,49 @@ import server from "./server"; +import { secp256k1 } from "@noble/curves/secp256k1"; +import { bytesToHex } from "@noble/hashes/utils"; -function Wallet({ address, setAddress, balance, setBalance }) { + +function Wallet({ address, setAddress, balance, setBalance, privateKey, setPrivateKey }) { + async function onChange(evt) { - const address = evt.target.value; + + const privateKey = evt.target.value; + setPrivateKey(privateKey); + + const address = bytesToHex(secp256k1.getPublicKey(privateKey)); setAddress(address); - if (address) { - const { - data: { balance }, - } = await server.get(`balance/${address}`); - setBalance(balance); - } else { - setBalance(0); - } + +if (address) { + const { + data: { balance }, + } = await server.get(`balance/${address}`); + setBalance(balance); +} else { + setBalance(0); } +} return (

Your Wallet

+
+ Public Key: {address ? + address.slice(0, 10) + "..." + address.slice(-10) : + "Invalid address"} +
+
Balance: {balance}
); } - -export default Wallet; + export default Wallet; diff --git a/server/index.js b/server/index.js index 3dbd053b..2e9cf216 100644 --- a/server/index.js +++ b/server/index.js @@ -1,38 +1,46 @@ const express = require("express"); const app = express(); const cors = require("cors"); +const { verify } = require("./verification"); const port = 3042; app.use(cors()); app.use(express.json()); const balances = { - "0x1": 100, - "0x2": 50, - "0x3": 75, +/*public key:*/ "03c781cf2662714a11a3a9f98654d09abf35b59bb29952cf35f55088ce27166dba": 100, // Private Key: 3ae0ffa9b45dcf48baba0c45eb4f4dc3b6d7f4ed8e8d38548409c33d851d3734 +/*public key:*/ "0226afe000841b07c5f988abdb85f069d969697f7e576f9ba3217f75086d9b906d": 50 , // Private Key: 328fa7d1cb0ff6fd9e3e86a217f467d17e42aa3125b6c76ca18c0ca8ea07b5d3 +/*public key:*/ "020e8767c081c4c37b88a3bee1d0a726f88a5530ea8f8a63221b8e34e7066b0e01": 75 , // Private Key: 214f36fd9ccb8c1cff2665d4f808a0d2227efbb6a7c3c3ac935bbad8f2ba75d0 }; app.get("/balance/:address", (req, res) => { const { address } = req.params; const balance = balances[address] || 0; + console.log(`Address: ${address}, Balance: ${balance}`); res.send({ balance }); }); -app.post("/send", (req, res) => { - const { sender, recipient, amount } = req.body; +app.post("/send", async (req, res) => { + const { sender, recipient, amount, signature } = req.body; + - setInitialBalance(sender); - setInitialBalance(recipient); +if (!verify(signature, "transfer", sender)) { + return res.status(400).send({ message: "You are not the account owner!" }); +} - if (balances[sender] < amount) { - res.status(400).send({ message: "Not enough funds!" }); - } else { - balances[sender] -= amount; - balances[recipient] += amount; - res.send({ balance: balances[sender] }); - } +setInitialBalance(sender); +setInitialBalance(recipient); + +if (balances[sender] < amount) { + return res.status(400).send({ message: "Not enough funds!" }); +} + balances[sender] -= amount; + balances[recipient] += amount; + return res.send({ balance: balances[sender] }); + }); + app.listen(port, () => { console.log(`Listening on port ${port}!`); }); diff --git a/server/package-lock.json b/server/package-lock.json index 612c6e2d..cf022af7 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,9 +10,65 @@ "license": "ISC", "dependencies": { "cors": "^2.8.5", + "ethereum-cryptography": "^2.1.3", "express": "^4.18.1" } }, + "node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", + "dependencies": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", + "dependencies": { + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -168,6 +224,17 @@ "node": ">= 0.6" } }, + "node_modules/ethereum-cryptography": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "dependencies": { + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" + } + }, "node_modules/express": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", @@ -611,6 +678,43 @@ } }, "dependencies": { + "@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "requires": { + "@noble/hashes": "1.3.3" + } + }, + "@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==" + }, + "@scure/base": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==" + }, + "@scure/bip32": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", + "requires": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + } + }, + "@scure/bip39": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", + "requires": { + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -728,6 +832,17 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "ethereum-cryptography": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "requires": { + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" + } + }, "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..6a1bec76 100644 --- a/server/package.json +++ b/server/package.json @@ -11,6 +11,8 @@ "license": "ISC", "dependencies": { "cors": "^2.8.5", - "express": "^4.18.1" + "ethereum-cryptography": "^2.1.3", + "express": "^4.18.1", + "nodemon": "^3.1.0" } } diff --git a/server/scripts/generate.js b/server/scripts/generate.js new file mode 100644 index 00000000..97a1d570 --- /dev/null +++ b/server/scripts/generate.js @@ -0,0 +1,11 @@ +const { secp256k1 } = require("@noble/curves/secp256k1"); +const { keccak_256 } = require("@noble/hashes/sha3"); +const { bytesToHex } = require("@noble/hashes/utils"); + +const privateKey = secp256k1.utils.randomPrivateKey(); +const publicKey = secp256k1.getPublicKey(privateKey); +const address = bytesToHex(keccak_256(publicKey).slice(-20)); + +console.log(`Private Key: ${bytesToHex(privateKey)}`); +console.log(`Public Key: ${bytesToHex(publicKey)}`); +console.log(`Address: ${address}`); \ No newline at end of file diff --git a/server/scripts/manual_check.js b/server/scripts/manual_check.js new file mode 100644 index 00000000..2378b65e --- /dev/null +++ b/server/scripts/manual_check.js @@ -0,0 +1,9 @@ +const { secp256k1 } = require("@noble/curves/secp256k1"); +const { keccak_256 } = require("@noble/hashes/sha3"); +const { bytesToHex, hexToBytes } = require("@noble/hashes/utils"); + +const privateKey = hexToBytes("ENTER_A_VALID_PRIVATE_KEY_HERE"); +const publicKey = secp256k1.getPublicKey(privateKey); +const address = bytesToHex(keccak_256(publicKey).slice(-20)); + +console.log(`Address: ${address}`); \ No newline at end of file diff --git a/server/verification.js b/server/verification.js new file mode 100644 index 00000000..ff9cd375 --- /dev/null +++ b/server/verification.js @@ -0,0 +1,24 @@ +const { secp256k1 } = require("@noble/curves/secp256k1"); +const { keccak_256 } = require("@noble/hashes/sha3"); +const { utf8ToBytes } = require("@noble/hashes/utils"); + +const decoder = (key, value) => { + if (key === "r" || key === "s") { + return BigInt(value); + } + return value; +}; + +const getSignature = (signature) => { + const sign = JSON.parse(signature, decoder); + const construcSign = new secp256k1.Signature(sign.r, sign.s, sign.recovery); + return construcSign; +}; + +const verify = (signature, message, publicKey) => { + const sign = getSignature(signature); + const hash = keccak_256(utf8ToBytes(message)); + return secp256k1.verify(sign, hash, publicKey); +}; + +module.exports = { verify }; \ No newline at end of file