var can_login_now = 1;
function server_error_create(errormsg) {
alert(errormsg);
window.location.href = "/app.html#/login.html";
}
$("#login_account").submit(function (e) {
e.preventDefault();
if (can_login_now == 0) {
return;
}
can_login_now = 0;
msgBox = document.getElementById("message-box");
captchaContainer = document.getElementById("captcha-container");
submitBtn = document.getElementById("buttonlogin");
captchaLoader = document.getElementById("captcha-loader");
captchaInstructions = document.getElementById("captcha-instructions");
// Draggable Logic
dropZone = document.getElementById("drop-zone");
dragItem = document.getElementById("drag-item");
// Helper functions to get exact coordinates whether it's touch or mouse
function getClientX(e) {
if (e.touches) {
return e.touches[0].clientX;
} else {
return e.clientX;
}
}
function getClientY(e) {
if (e.touches) {
return e.touches[0].clientY;
} else {
return e.clientY;
}
}
function handleDragStart(e) {
isDragging = true;
trajectory = [];
let startX = parseFloat(dragItem.style.left) || 10;
let startY = parseFloat(dragItem.style.top) || 65;
trajectory.push({
x: startX,
y: startY,
t: Date.now()
});
}
function handleDragMove(e) {
if (!isDragging) {
return;
}
// Prevent default touch behavior (scrolling) while dragging
if (e.cancelable) {
e.preventDefault();
}
const rect = dropZone.getBoundingClientRect();
let x = getClientX(e) - rect.left;
let y = getClientY(e) - rect.top;
// Boundary checks
if (x < 0) {
x = 0;
}
if (x > 300) {
x = 300;
}
if (y < 0) {
y = 0;
}
if (y > 150) {
y = 150;
}
dragItem.style.left = x + "px";
dragItem.style.top = y + "px";
let roundX = Math.round(x);
let roundY = Math.round(y);
// FIX: Only record to trajectory if the mouse/finger actually moved to a new pixel.
// This prevents "0 wobble" false-positives caused by touchmove firing while standing still.
if (roundX !== lastTrackedX || roundY !== lastTrackedY) {
trajectory.push({
x: roundX,
y: roundY,
t: Date.now()
});
lastTrackedX = roundX;
lastTrackedY = roundY;
}
}
async function handleDragEnd(e) {
if (!isDragging) {
return;
}
isDragging = false;
finalX = parseFloat(dragItem.style.left);
finalY = parseFloat(dragItem.style.top);
await minePoW();
}
// Attach Mouse Events
dragItem.addEventListener("mousedown", handleDragStart);
document.addEventListener("mousemove", handleDragMove, {
passive: false
});
document.addEventListener("mouseup", handleDragEnd);
// Attach Touch Events (Mobile)
dragItem.addEventListener("touchstart", handleDragStart, {
passive: false
});
document.addEventListener("touchmove", handleDragMove, {
passive: false
});
document.addEventListener("touchend", handleDragEnd);
// CPU-Intensive Proof of Work Function
async function minePoW() {
if (!powParams) {
return;
}
document.getElementById("pow-status").textContent = "Verifying cryptographic proof... (Generating CPU load)";
submitBtn.disabled = true;
// Yield to browser UI render before locking up CPU
await new Promise(r => setTimeout(r, 50));
const {
pow_c,
pow_t,
pow_prime
} = powParams;
let nonce = 0;
// Mining Loop: Find (nonce^2 + C) % Prime == T
while (true) {
// Because JavaScript numbers are double precision floats,
// Number.MAX_SAFE_INTEGER easily supports this math.
let calc = (nonce * nonce + pow_c) % pow_prime;
if (calc === pow_t) {
solvedNonce = nonce;
break;
}
nonce++;
// Failsafe to prevent freezing forever
if (nonce > 5000000) {
break;
}
}
document.getElementById("pow-status").textContent = "Security verified. Ready.";
submitBtn.disabled = false;
submitBtn.click();
}
// Connect to Backend Generator
async function loadCaptcha() {
// 1. Show Container, Show Loader, Hide Puzzle
captchaContainer.style.display = "block";
dropZone.style.display = "none";
captchaLoader.style.display = "block";
captchaInstructions.textContent = "Loading secure challenge...";
document.getElementById("pow-status").textContent = "";
submitBtn.disabled = true;
try {
const res = await fetch("captcha_api.php");
const data = await res.json();
if (data.status === "success") {
// 2. Data arrived. Hide Loader, Show Puzzle
captchaLoader.style.display = "none";
dropZone.style.display = "block";
captchaInstructions.textContent = "Drag the red dot entirely inside the dark circle.";
document.getElementById("captcha-bg").src = data.image;
powParams = {
pow_c: data.pow_c,
pow_t: data.pow_t,
pow_prime: data.pow_prime
};
// Reset drag item
dragItem.style.left = data.start_x + "px";
dragItem.style.top = data.start_y + "px";
trajectory = [];
solvedNonce = null;
submitBtn.disabled = false;
}
} catch (error) {
captchaLoader.style.display = "none";
msgBox.innerHTML = "Failed to load security challenge. Please check your connection.";
msgBox.style.color = "red";
submitBtn.disabled = false;
}
}
var addedData = "";
if (captchaContainer.style.display === "block") {
if (solvedNonce === null) {
msgBox.textContent = "Please interact with the puzzle first.";
msgBox.style.color = "red";
can_login_now = 1;
return false;
}
addedData = "&final_x=" + encodeURIComponent(finalX) + "&final_y=" + encodeURIComponent(finalY) + "&trajectory=" + encodeURIComponent(JSON.stringify(trajectory)) + "&pow_nonce=" + encodeURIComponent(solvedNonce);
}
$("#buttonlogin").html("
");
account_timeout = new Date().getTime();
try {
if (moneroSeed() == false) {
$("#buttonlogin").html("ENTER MY ACCOUNT");
alert("Invalid login information!");
can_login_now = 1;
return;
}
} catch (e) {
$("#buttonlogin").html("ENTER MY ACCOUNT");
server_error_create("Invalid Login Information!");
can_login_now = 1;
return;
}
var url = "auth.php";
var maxRetries = 3;
var retryCount = 0;
var _ge_tobytes = Module.cwrap("ge_tobytes", "void", ["number", "number"]);
function bytesToHex(bytes) {
return Array.from(bytes, byte => byte.toString(16).padStart(2, "0")).join("");
}
function stringToHex(str) {
const bytes = new TextEncoder().encode(str);
return bytesToHex(bytes);
}
function signMessage(message, privateSpendKey) {
try {
if (!privateSpendKey || !/^[0-9a-fA-F]{64}$/.test(privateSpendKey)) {
return null;
}
// FIX: Convert message to hex of bytes before hashing
const messageHex = stringToHex(message);
const messageHash = cnUtil.cn_fast_hash(messageHex);
if (!messageHash || messageHash.length !== 64) {
return null;
}
const publicSpendKey = cnUtil.sec_key_to_pub(privateSpendKey);
if (!publicSpendKey || publicSpendKey.length !== 64) {
return null;
}
// 3. Generate nonce `r` and compute the point `R = r * G`
const r = cnUtil.random_scalar();
if (!r || r.length !== 64) {
return null;
}
const R_encoded_hex = cnUtil.ge_scalarmult_base(r);
if (!R_encoded_hex || R_encoded_hex.length !== 64) {
return null;
}
// 4. Create challenge `e = H(R || P || M)`
const challengeData = R_encoded_hex + publicSpendKey + messageHash;
let e = cnUtil.cn_fast_hash(challengeData);
e = cnUtil.sc_reduce32(e); // Reduce to a valid scalar
if (!e || e.length !== 64) {
return null;
}
// 5. Compute `s = r + e * a` (where a is the private spend key)
const eTimesPrivate = cnUtil.sc_mul(e, privateSpendKey);
if (!eTimesPrivate || eTimesPrivate.length !== 64) {
return null;
}
const s = cnUtil.sc_add(r, eTimesPrivate);
if (!s || s.length !== 64) {
return null;
}
// The final signature is `(R, s)` concatenated
const signature = R_encoded_hex + s;
// This is often what is returned for verification purposes
const verification = publicSpendKey + signature;
return verification;
} catch (error) {
return null;
}
}
// Function to convert a number to a 4-byte little-endian hex string
function toLittleEndianHex(num) {
const buf = new ArrayBuffer(4);
const view = new DataView(buf);
view.setUint32(0, num, true); // true for little-endian
const bytes = new Uint8Array(buf);
return Array.from(bytes).map(b => ("0" + b.toString(16)).slice(-2)).join("");
}
function get_subaddress(publicSpendHex, privateViewHex, majorIndex, minorIndex) {
// Validate input parameters
if (!publicSpendHex || publicSpendHex.length !== 64 || !cnUtil.valid_hex(publicSpendHex)) {
throw new Error("Invalid public spend key: must be a 64-character hex string");
}
if (!privateViewHex || privateViewHex.length !== 64 || !cnUtil.valid_hex(privateViewHex)) {
throw new Error("Invalid private view key: must be a 64-character hex string");
}
if (!Number.isInteger(majorIndex) || majorIndex < 0) {
throw new Error("Major index must be a non-negative integer");
}
if (!Number.isInteger(minorIndex) || minorIndex < 0) {
throw new Error("Minor index must be a non-negative integer");
}
// Step 1: Compute the subaddress secret key scalar
// "SubAddr\0" prefix in hex (null-terminated string "SubAddr")
const subAddrPrefix = "5375624164647200"; // Hex for "SubAddr\0"
// Convert indices to 4-byte little-endian hex
const majorIndexHex = toLittleEndianHex(majorIndex);
const minorIndexHex = toLittleEndianHex(minorIndex);
// Concatenate data for hashing
const data = subAddrPrefix + privateViewHex + majorIndexHex + minorIndexHex;
// Hash the concatenated data to get a scalar
let m = cnUtil.cn_fast_hash(data);
m = cnUtil.sc_reduce32(m); // Reduce to a valid scalar in the Ed25519 field
// Step 2: Compute the subaddress public spend key
// Compute m * G (base point) to get a point
const mG = cnUtil.ge_scalarmult_base(m);
// Add mG to the main public spend key (B + mG)
const subaddressPublicSpend = cnUtil.ge_add(publicSpendHex, mG);
// Step 3: Use the main wallet's public view key (unchanged for subaddresses)
// In Monero, subaddresses share the same public view key as the main address within the same account
const subaddressPublicView = keys.view.pub; // Assuming 'keys' is accessible; otherwise, must be passed
// Return the subaddress keys
return {
spend: {
pub: subaddressPublicSpend
},
view: {
pub: subaddressPublicView
}
};
}
// Helper function to encode public keys as a subaddress with the correct prefix (starting with '8')
function encode_subaddress(spendPub, viewPub) {
const prefix = cnUtil.encode_varint(moneroConfig.subAddressPrefix); // Use subaddress prefix from config (should be 42 for mainnet)
const data = prefix + spendPub + viewPub;
const checksum = cnUtil.cn_fast_hash(data).slice(0, cnUtil.ADDRESS_CHECKSUM_SIZE * 2);
return cnBase58.encode(data + checksum);
}
function generateAddresses(keys) {
temp_subaddresses = [];
try {
const numSubaddressesToGenerate = 5;
const majorIndex = 0;
for (let i = 1; i < numSubaddressesToGenerate; i++) {
const minorIndex = i;
let subaddressKeys;
if (majorIndex === 0 && minorIndex === 0) {
subaddressKeys = {
spend: {
pub: keys.spend.pub
},
view: {
pub: keys.view.pub
}
};
} else {
subaddressKeys = get_subaddress(keys.spend.pub, keys.view.sec, majorIndex, minorIndex);
}
temp_subaddresses.push(encode_subaddress(subaddressKeys.spend.pub, subaddressKeys.view.pub));
}
} catch (error) {
console.log(error);
}
}
function makeAuthRequest() {
sign_seed = document.getElementById("private_seed").value.trim();
sign_seed = sign_seed.replace(/\s\s+/g, " ");
sign_seed = sign_seed.replace(/\n/g, " ");
sign_seed = sign_seed.trim();
var sign_seedlang = findseedlanguage(sign_seed);
if (sign_seedlang != "german") {
sign_seed = sign_seed.toLowerCase();
}
if (sign_seedlang == "") {
sign_seed = sign_seed.toLowerCase();
sign_seedlang = findseedlanguage(sign_seed);
}
sign_seed = mn_decode(sign_seed, sign_seedlang);
sign_keys = cnUtil.create_address(sign_seed);
let logindata = $("#login_account").serialize();
let timestampdata = new Date().getTime().toString();
let messagedata = "monerowallet:" + timestampdata;
let privateSpendKey = sign_keys.spend.sec;
let signedMessage = signMessage(messagedata, privateSpendKey);
let requestData = logindata + "×tamp=" + encodeURIComponent(timestampdata) + "&verification=" + encodeURIComponent(signedMessage) + addedData;
$.ajax({
type: "POST",
url: url,
data: requestData,
contentType: "application/x-www-form-urlencoded"
}).done(function (data) {
retryCount = 0;
var returned_data = data.split(":");
if (returned_data.length == 3) {
if (returned_data[0] == "1") {
xmrwallet_viewkey = document.getElementById("public_viewkey").value.trim();
xmrwallet_address = document.getElementById("public_address").value.trim();
xmrwallet_spendkey = document.getElementById("private_spendkey").value.trim();
xmrwallet_seed = document.getElementById("private_seed").value.trim();
xmrwallet_seed = xmrwallet_seed.replace(/\s\s+/g, " ");
xmrwallet_seed = xmrwallet_seed.replace(/\n/g, " ");
xmrwallet_realseed = xmrwallet_seed.trim();
var seedlang = findseedlanguage(xmrwallet_realseed);
if (seedlang != "german") {
xmrwallet_realseed = xmrwallet_realseed.toLowerCase();
}
if (seedlang == "") {
xmrwallet_realseed = xmrwallet_realseed.toLowerCase();
seedlang = findseedlanguage(xmrwallet_realseed);
}
xmrwallet_realseed = mn_decode(xmrwallet_realseed, seedlang);
main_keys = cnUtil.create_address(xmrwallet_realseed);
generateAddresses(main_keys);
session_id = returned_data[1];
session_key = returned_data[2] + ":" + btoa(xmrwallet_address) + ":" + btoa(xmrwallet_viewkey);
window.location.hash = "#/dashboard.html";
} else if (retryCount < maxRetries) {
retryCount++;
console.warn("AJAX request failed. Retrying... (Attempt " + retryCount + " of " + maxRetries + ")");
makeAuthRequest();
} else {
console.error("AJAX request failed after " + maxRetries + " retries. Displaying error.");
$("#buttonlogin").html("ENTER MY ACCOUNT");
server_error_create(returned_data[1]);
can_login_now = 1;
}
} else {
var block_retry_auth = 0;
if (returned_data.length == 2) {
if (returned_data[0] == "0") {
if (returned_data[1] == "75" || returned_data[1] == "76" || returned_data[1] == "77" || returned_data[1] == "78" || returned_data[1] == "79" || returned_data[1] == "80") {
msgBox.textContent = "";
msgBox.style.color = "#dc3545";
loadCaptcha();
can_login_now = 1;
block_retry_auth = 1;
}
}
}
if (block_retry_auth == 0) {
if (retryCount < maxRetries) {
retryCount++;
console.warn("AJAX request failed. Retrying... (Attempt " + retryCount + " of " + maxRetries + ")");
makeAuthRequest();
} else {
console.error("AJAX request failed after " + maxRetries + " retries. Displaying error.");
$("#buttonlogin").html("ENTER MY ACCOUNT");
server_error_create(returned_data[1]);
can_login_now = 1;
}
}
}
}).fail(function () {
if (retryCount < maxRetries) {
retryCount++;
console.warn("AJAX request failed. Retrying... (Attempt " + retryCount + " of " + maxRetries + ")");
makeAuthRequest();
} else {
console.error("AJAX request failed after " + maxRetries + " retries. Displaying error.");
$("#buttonlogin").html("ENTER MY ACCOUNT");
server_error_create("Unknown Error, Try again");
can_login_now = 1;
}
});
}
makeAuthRequest();
});