From 72865cb7e85cdf5685fa175a34e2a1ff925351cf Mon Sep 17 00:00:00 2001 From: toaru05 Date: Tue, 17 Feb 2026 20:55:40 +0900 Subject: [PATCH] Done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit まだ変更予定あり --- Backend/Workspace/Routes/RegisterForNFC.js | 0 Backend/Workspace/Routes/SendToken.js | 109 ++++++++++++++++++++ Backend/Workspace/Tools/AESControl.js | 56 ++++++++++ Backend/Workspace/Tools/CreateTransferTx.js | 81 +++++++++++++++ Backend/Workspace/Tools/LeftToken.js | 13 +++ 5 files changed, 259 insertions(+) delete mode 100644 Backend/Workspace/Routes/RegisterForNFC.js create mode 100644 Backend/Workspace/Routes/SendToken.js create mode 100644 Backend/Workspace/Tools/AESControl.js create mode 100644 Backend/Workspace/Tools/CreateTransferTx.js create mode 100644 Backend/Workspace/Tools/LeftToken.js diff --git a/Backend/Workspace/Routes/RegisterForNFC.js b/Backend/Workspace/Routes/RegisterForNFC.js deleted file mode 100644 index e69de29b..00000000 diff --git a/Backend/Workspace/Routes/SendToken.js b/Backend/Workspace/Routes/SendToken.js new file mode 100644 index 00000000..857feb87 --- /dev/null +++ b/Backend/Workspace/Routes/SendToken.js @@ -0,0 +1,109 @@ +const express = require('express'); +const router = express.Router(); +const jwt = require('jsonwebtoken'); +const DBPerf = require('../Tools/DBPref'); +const VCM = require('../Tools/VerifyCookieMiddleware'); +const LeftToken = require('../Tools/LeftToken'); +const decrypt = require('../Tools/AESControl'); +const CreateTransferTx = require('../Tools/createTransferTx'); +const SignAndAnnounce = require('../Tools/SignAndAnnounce'); + +// 送金処理 +router.post('/SendToken', VCM('LoginToken', process.env.LOGIN_SECRET), async(req, res) => { + + //送金先のIDと送金額をリクエストボディから抽出 + const { sendtoUserID, Amount } = req.body; + //cookieから送金元のユーザーIDを抽出 + const fromUserID = req.auth.userID; + + //送金先のユーザーIDと送金額がリクエストボディに存在するかの確認 + if (!sendtoUserID || !Amount) { + console.log("送金先のユーザーIDまたは送金額が不足しています!"); + return res.status(400).send({ message: 'Missing required fields' }); + } + + // 送金元のユーザーIDと送金先のユーザーIDが存在するかの確認 + const fromUserInfor = await DBPerf("送金元ユーザーの存在確認", "SELECT UserID, Password, PrivateKey FROM IdentifyTable WHERE UserID = ?", [fromUserID]); + const toUserInfor = await DBPerf("送金先ユーザーの存在確認", "SELECT UserID, Address FROM IdentifyTable WHERE UserID = ?", [sendtoUserID]); + if (fromUserInfor.length === 0) { + console.log("送金元のユーザーIDが存在しません!"); + return res.status(400).send({ message: 'Invalid user ID' }); + } + if (toUserInfor.length === 0) { + console.log("送金先のユーザーIDが存在しません!"); + return res.status(400).send({ message: 'Invalid user ID' }); + } + + // 送金処理の実行 + const SendToAddress = toUserInfor[0].Address; + // 送金元のユーザーIDの秘密鍵とパスワードをデータベースから抽出 + const privateKey = fromUserInfor[0].PrivateKey; + const password = fromUserInfor[0].Password; + + //MosaicIDの取得 + const roomName = await DBPerf("送金するルームの名前の抽出", "SELECT RoomName FROM Rooms WHERE userID = ?", [fromUserID]); + const mosaicName = await DBPerf("MosaicIDの抽出", "SELECT MosaicName FROM RoomDetails WHERE RoomName = ?", [roomName[0].RoomName]); + const mosaicID = await DBPerf("MosaicIDの抽出", "SELECT MosaicID FROM Mosaics WHERE MosaicName = ?", [mosaicName[0].MosaicName]); + const MosaicID = mosaicID[0].MosaicID; + + // パスワードを照合して認証 + const decryptedPrivateKey = decrypt(password , privateKey); + + const {tx, keyPair, facade} = CreateTransferTx({ + networkType: 'testnet', + senderPrivateKey: decryptedPrivateKey, + recipientRawAddress: SendToAddress, + message: `Send ${Amount} tokens to ${sendtoUserID}`, + mosaics: [ + { + mosaicId: MosaicID, + amount: BigInt(Amount) * 1_000_000n } + ], + deadlineHours: 2, + }) + + // 署名とアナウンス + // NODEの定義 + const NODE_URL = 'https://sym-test-01.opening-line.jp:3001'; + // 実際に署名とアナウンスを行う + await SignAndAnnounce(tx, keyPair, facade, NODE_URL); + // Shutdown Log + console.log(`[${logOwner}] Shutdown!`); + + return res.status(200).json({ message: "OK: Send Successful"}); +}); + + + + +router.get('/LeftToken', VCM('LoginToken', process.env.LOGIN_SECRET), async(req, res) => { + const fromUserID = req.auth.userID; + const userInfor = await DBPerf("送金元ユーザーの存在確認", "SELECT Address FROM IdentifyTable WHERE userID = ?", [fromUserID]); + const address = userInfor[0].Address; + const NODE_URL = 'https://sym-test-01.opening-line.jp:3001'; + try{ + const leftToken = await LeftToken(address, NODE_URL); + res.status(200).send(leftToken); + }catch(err){ + console.error("Error in LeftToken:", err); + res.status(500).send({ message: "Error fetching left token" }); + } +}); + +// router.get('/sendRoomDetail', async(req, res) => { +// const {roomID} = req.body; +// const roomDetail = await DBPerf("ルームの詳細(ルーム名、ルームアイコン)の抽出", "SELECT * FROM RoomTable WHERE roomID = ?", [roomID]); +// res.status(200).send(roomDetail); +// }); + +module.exports = router; + + +/* +1. cookieからuserIDを抽出 +2. 送金先のユーザーIDと送金額をリクエストボディから抽出 +3. 送金元のユーザーIDと送金先のユーザーIDが存在するかの確認 +4. 送金元のユーザーIDのパスワードをデータベースから抽出 +5. パスワードを照合して認証 +6. 認証成功なら送金処理を実行(例: データベースの更新など) +*/ \ No newline at end of file diff --git a/Backend/Workspace/Tools/AESControl.js b/Backend/Workspace/Tools/AESControl.js new file mode 100644 index 00000000..aab86947 --- /dev/null +++ b/Backend/Workspace/Tools/AESControl.js @@ -0,0 +1,56 @@ +const crypto = require('crypto'); + +// ========== 暗号化 ========== +function encrypt(plainKey, plainText) { + // ① 平文1から32byteの鍵を作る + const key = crypto + .createHash('sha256') + .update(plainKey) + .digest(); + + // ② IV(12byteがGCM推奨) + const iv = crypto.randomBytes(12); + + // ③ 暗号化 + const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); + const encrypted = Buffer.concat([ + cipher.update(plainText, 'utf8'), + cipher.final() + ]); + + // ④ 認証タグ + const authTag = cipher.getAuthTag(); + + return { + iv: iv.toString('hex'), + data: encrypted.toString('hex'), + tag: authTag.toString('hex') + }; +} + +// ========== 復号化 ========== +function decrypt(plainKey, encryptedObj) { + const key = crypto + .createHash('sha256') + .update(plainKey) + .digest(); + + const iv = Buffer.from(encryptedObj.iv, 'hex'); + const encryptedText = Buffer.from(encryptedObj.data, 'hex'); + const authTag = Buffer.from(encryptedObj.tag, 'hex'); + + const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); + decipher.setAuthTag(authTag); + + const decrypted = Buffer.concat([ + decipher.update(encryptedText), + decipher.final() + ]); + + return decrypted.toString('utf8'); +} + +module.exports = { + encrypt, + decrypt +}; \ No newline at end of file diff --git a/Backend/Workspace/Tools/CreateTransferTx.js b/Backend/Workspace/Tools/CreateTransferTx.js new file mode 100644 index 00000000..bb9f8308 --- /dev/null +++ b/Backend/Workspace/Tools/CreateTransferTx.js @@ -0,0 +1,81 @@ +/*========== Manual ========== +# Input(obj) +networkType: mainnet or testnet +senderPrivateKey: 送り元の秘密鍵 +recipientRawAddress: 受け取り手の文字列アドレス +messageText: メッセージをつけたければ +mosaics: どのモザイクをいくつ送りたいか +例: +mosaics: [ + { mosaicId: XYM_ID, amount: 1_000_000n }, + { mosaicId: TOKEN_ID, amount: 5n } +] +deadlineHours: 有効期限[h] + +# Output +createTransferTx: 実際のトランザクション +keyPair: 署名時に必要な秘密鍵/公開鍵 +facade: mainnet or testnetの指定をしているがそれが一貫性を保てるように引き継ぐ + +#Description +mosaicはBigint型(数字末尾にnがつく)で指定する必要がある。 +1_000_000nは1000000と同じであり、ただ見やすくするだけのもの。 +========== Manual ==========*/ + +// CreateTransferTx.js +const symbolSdk = require('symbol-sdk'); + +function CreateTransferTx({ + networkType = 'testnet', + senderPrivateKey, + recipientRawAddress, + messageText = '', + mosaics = [], + deadlineHours = 2, +}) { + // Startup Log + const logOwner = "CreateTransferTx"; + console.log(`\n${logOwner}-Function is running!\n`); + // I/O Log + console.log(`[${logOwner}] Input => networkType: ${networkType}, recipientRawAddress: ${recipientRawAddress}, messageText: ${messageText}, mosaics: ${mosaics}, deadlineHours: ${deadlineHours}`); + + // Facade 初期化 + const facade = new symbolSdk.facade.SymbolFacade(networkType); + // 秘密鍵 → KeyPair + const keyPair = new symbolSdk.symbol.KeyPair( new symbolSdk.PrivateKey(senderPrivateKey) ); + // 宛先アドレス解析(Base32 → 生データ + ネットワーク検証) + const recipient = facade.network.parseAddress(recipientRawAddress); + // Deadline 作成 + const deadline = facade.network.fromDatetime(Date.now()).addHours(deadlineHours).timestamp; + // メッセージ + const message = new TextEncoder().encode(messageText); + + // トランザクション作成 + const createTransferTx = facade.transactionFactory.create({ + type: 'transfer_transaction_v1', + signerPublicKey: keyPair.publicKey, + recipientAddress: recipient.toString(), + mosaics, + message, + deadline + }); + + // I/O Log(JSON.stringify(表示するJSON, 表示項目指定, インデックス空白数指定)) + console.log(`[${logOwner}] Output => createTransferTx: \n${{ + type: createTransferTx.type, + recipientAddress: createTransferTx.recipientAddress, + mosaics: createTransferTx.mosaics, + message: createTransferTx.message, + deadline: createTransferTx.deadline, + }}`); + // Shutdown Log + console.log(`[${logOwner}] Shutdown!`); + + return { + createTransferTx, + keyPair, + facade + }; +} + +module.exports = CreateTransferTx; diff --git a/Backend/Workspace/Tools/LeftToken.js b/Backend/Workspace/Tools/LeftToken.js new file mode 100644 index 00000000..ae8c7ea7 --- /dev/null +++ b/Backend/Workspace/Tools/LeftToken.js @@ -0,0 +1,13 @@ +const axios = require('axious'); + +async function LeftToken(address, NODE_URL) { + try{ + const result = await axios.get(`${NODE_URL}/accounts/${address}`); + return result.data.account.mosaics; + }catch(err){ + console.error("Error in LeftToken:", err); + throw err; + } +} + +module.exports = LeftToken; \ No newline at end of file