Skip to content
Merged

Done #27

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
109 changes: 109 additions & 0 deletions Backend/Workspace/Routes/SendToken.js
Original file line number Diff line number Diff line change
@@ -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');
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The require statement only imports the 'decrypt' function from AESControl.js, but the module exports both 'encrypt' and 'decrypt' as named exports in an object. The correct import should be: const { decrypt } = require('../Tools/AESControl'); or const AESControl = require('../Tools/AESControl'); followed by AESControl.decrypt()

Suggested change
const decrypt = require('../Tools/AESControl');
const { decrypt } = require('../Tools/AESControl');

Copilot uses AI. Check for mistakes.
const CreateTransferTx = require('../Tools/createTransferTx');
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import path 'createTransferTx' should match the actual filename 'CreateTransferTx' (capital C). JavaScript module resolution is case-sensitive on many systems, and this inconsistency will cause module loading to fail on case-sensitive file systems.

Suggested change
const CreateTransferTx = require('../Tools/createTransferTx');
const CreateTransferTx = require('../Tools/CreateTransferTx');

Copilot uses AI. Check for mistakes.
const SignAndAnnounce = require('../Tools/SignAndAnnounce');

Comment on lines +9 to +10
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module '../Tools/SignAndAnnounce' is imported but does not exist in the codebase. This will cause a module not found error at runtime. The SignAndAnnounce.js file needs to be created or the import path needs to be corrected.

Suggested change
const SignAndAnnounce = require('../Tools/SignAndAnnounce');
const https = require('https');
/**
* 署名とアナウンスを行うヘルパー関数
* @param {Object} tx - 作成済みトランザクション
* @param {Object} keyPair - 送信者のキーペア
* @param {Object} facade - ネットワーク用ファサード
* @param {string} nodeUrl - ノードのベースURL (: https://sym-test-01.opening-line.jp:3001)
*/
async function SignAndAnnounce(tx, keyPair, facade, nodeUrl) {
// トランザクションに署名
const signedTx = facade.signTransaction(keyPair, tx);
// 署名済みトランザクションオブジェクトからペイロードを抽出
const payload =
(signedTx && (signedTx.payload || signedTx.serializedTransaction || signedTx.serialized)) ||
signedTx;
const body = JSON.stringify({ payload });
// /transactions エンドポイントへアナウンス
const url = new URL('/transactions', nodeUrl);
await new Promise((resolve, reject) => {
const req = https.request(
url,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
},
},
(res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve();
} else {
reject(
new Error(
`Failed to announce transaction: ${res.statusCode} ${data}`
)
);
}
});
}
);
req.on('error', (err) => {
reject(err);
});
req.write(body);
req.end();
});
}

Copilot uses AI. Check for mistakes.
// 送金処理
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' });
}
Comment on lines +20 to +23
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation for the Amount parameter. The code should validate that Amount is a positive number before processing. Negative or zero values, non-numeric strings, or excessively large values could cause issues or be exploited.

Copilot uses AI. Check for mistakes.

// 送金元のユーザー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]);
Comment on lines +44 to +46
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for database query results. If the database query returns an empty result on line 44 or lines 45-46, accessing roomName[0].RoomName or mosaicName[0].MosaicName will throw a TypeError. Add validation to check if the query results are not empty before accessing array elements.

Suggested change
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 roomName = await DBPerf("送金するルームの名前の抽出", "SELECT RoomName FROM Rooms WHERE userID = ?", [fromUserID]);
if (!roomName || roomName.length === 0) {
console.log("送金に使用するルームが見つかりません!");
return res.status(400).send({ message: 'Room information not found for user' });
}
const mosaicName = await DBPerf("MosaicIDの抽出", "SELECT MosaicName FROM RoomDetails WHERE RoomName = ?", [roomName[0].RoomName]);
if (!mosaicName || mosaicName.length === 0) {
console.log("送金に使用するMosaicNameが見つかりません!");
return res.status(400).send({ message: 'Mosaic name not found for room' });
}
const mosaicID = await DBPerf("MosaicIDの抽出", "SELECT MosaicID FROM Mosaics WHERE MosaicName = ?", [mosaicName[0].MosaicName]);
if (!mosaicID || mosaicID.length === 0) {
console.log("送金に使用するMosaicIDが見つかりません!");
return res.status(400).send({ message: 'Mosaic ID not found for mosaic name' });
}

Copilot uses AI. Check for mistakes.
const MosaicID = mosaicID[0].MosaicID;

// パスワードを照合して認証
const decryptedPrivateKey = decrypt(password , privateKey);
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential security issue: The decrypt function is called with the password and privateKey, but there's no verification that the decryption was successful before using the decryptedPrivateKey. If the password is incorrect or the privateKey is corrupted, the decrypt function might return invalid data that could cause issues downstream. Consider validating the decrypted result before use.

Suggested change
const decryptedPrivateKey = decrypt(password , privateKey);
let decryptedPrivateKey;
try {
decryptedPrivateKey = decrypt(password, privateKey);
} catch (error) {
console.error("秘密鍵の復号に失敗しました:", error);
return res.status(500).send({ message: 'Failed to decrypt private key' });
}
if (!decryptedPrivateKey || typeof decryptedPrivateKey !== 'string') {
console.error("秘密鍵の復号結果が無効です");
return res.status(400).send({ message: 'Invalid decrypted private key' });
}

Copilot uses AI. Check for mistakes.

const {tx, keyPair, facade} = CreateTransferTx({
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The destructured variable 'tx' does not match the exported property name from CreateTransferTx. According to the CreateTransferTx.js file (line 74-78), the function returns an object with properties 'createTransferTx', 'keyPair', and 'facade'. This should be: const {createTransferTx, keyPair, facade} = CreateTransferTx({...})

Copilot uses AI. Check for mistakes.
networkType: 'testnet',
senderPrivateKey: decryptedPrivateKey,
recipientRawAddress: SendToAddress,
message: `Send ${Amount} tokens to ${sendtoUserID}`,
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message parameter in CreateTransferTx is incorrectly named. According to the function signature in CreateTransferTx.js (line 32), the parameter is named 'messageText', but here it's being passed as 'message'. This will result in the messageText defaulting to an empty string instead of using the intended message.

Suggested change
message: `Send ${Amount} tokens to ${sendtoUserID}`,
messageText: `Send ${Amount} tokens to ${sendtoUserID}`,

Copilot uses AI. Check for mistakes.
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);
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'tx' used here is incorrectly destructured on line 52. It should be 'createTransferTx' to match the return value from the CreateTransferTx function.

Copilot uses AI. Check for mistakes.
// Shutdown Log
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'logOwner' is undefined. It is used in this log statement but was never declared in the function scope. This will cause a ReferenceError at runtime.

Suggested change
// Shutdown Log
// Shutdown Log
const logOwner = 'SendToken';

Copilot uses AI. Check for mistakes.
console.log(`[${logOwner}] Shutdown!`);

return res.status(200).json({ message: "OK: Send Successful"});
Comment on lines +50 to +73
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no error handling around the CreateTransferTx function call and the SignAndAnnounce call. If these functions throw errors (e.g., due to invalid parameters or network issues), the route handler will crash without providing a helpful error message to the client. This makes debugging difficult and provides a poor user experience. Consider wrapping these operations in a try-catch block.

Suggested change
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"});
try {
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"});
} catch (err) {
console.error("Error in SendToken:", err);
return res.status(500).json({ message: "Error sending token" });
}

Copilot uses AI. Check for mistakes.
});




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);
Comment on lines +81 to +88
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for database query result. If the database query returns an empty result on line 81, accessing userInfor[0].Address will throw a TypeError. Add validation to check if userInfor is not empty before accessing the Address property.

Suggested change
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);
try{
const userInfor = await DBPerf("送金元ユーザーの存在確認", "SELECT Address FROM IdentifyTable WHERE userID = ?", [fromUserID]);
if (!userInfor || userInfor.length === 0 || !userInfor[0].Address) {
return res.status(404).json({ message: "User not found or address missing" });
}
const address = userInfor[0].Address;
const NODE_URL = 'https://sym-test-01.opening-line.jp:3001';
const leftToken = await LeftToken(address, NODE_URL);
res.status(200).send(leftToken);
}catch(err){
console.error("Error in LeftToken route:", err);

Copilot uses AI. Check for mistakes.
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. 認証成功なら送金処理を実行(例: データベースの更新など)
*/
56 changes: 56 additions & 0 deletions Backend/Workspace/Tools/AESControl.js
Original file line number Diff line number Diff line change
@@ -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');
Comment on lines +38 to +40
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AES encryption uses a random IV (Initialization Vector) for each encryption operation, which is correct for security. However, the decrypt function should validate that the encryptedObj contains all required fields (iv, data, tag) before attempting decryption. Missing fields will cause the decryption to fail with unclear error messages.

Copilot uses AI. Check for mistakes.

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
};
81 changes: 81 additions & 0 deletions Backend/Workspace/Tools/CreateTransferTx.js
Original file line number Diff line number Diff line change
@@ -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,
}}`);
Comment on lines +64 to +70
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console.log statement attempts to log an object literal instead of using JSON.stringify. This will output '[object Object]' instead of the actual transaction details. Based on the comment on line 63, this should use JSON.stringify: console.log([${logOwner}] Output => createTransferTx: \n${JSON.stringify({...}, null, 2)})

Suggested change
console.log(`[${logOwner}] Output => createTransferTx: \n${{
type: createTransferTx.type,
recipientAddress: createTransferTx.recipientAddress,
mosaics: createTransferTx.mosaics,
message: createTransferTx.message,
deadline: createTransferTx.deadline,
}}`);
console.log(`[${logOwner}] Output => createTransferTx: \n${JSON.stringify({
type: createTransferTx.type,
recipientAddress: createTransferTx.recipientAddress,
mosaics: createTransferTx.mosaics,
message: createTransferTx.message,
deadline: createTransferTx.deadline,
}, null, 2)}`);

Copilot uses AI. Check for mistakes.
// Shutdown Log
console.log(`[${logOwner}] Shutdown!`);

return {
createTransferTx,
keyPair,
facade
};
}

module.exports = CreateTransferTx;
13 changes: 13 additions & 0 deletions Backend/Workspace/Tools/LeftToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const axios = require('axious');
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package name 'axious' is misspelled. It should be 'axios'. This will cause a module not found error at runtime.

Suggested change
const axios = require('axious');
const axios = require('axios');

Copilot uses AI. Check for mistakes.

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;