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 (
);
}
-
-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