Ripple wallet created

This commit is contained in:
void-57 2025-08-10 07:26:49 +05:30
parent b9f0325d55
commit aec3cf0a5b
9 changed files with 17931 additions and 0 deletions

BIN
Ripple Web Wallet Tasks.pdf Normal file

Binary file not shown.

2821
css/main.css Normal file

File diff suppressed because it is too large Load Diff

12
favicon.svg Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns:xodm="http://www.corel.com/coreldraw/odm/2003" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 2500 2500" style="enable-background:new 0 0 2500 2500;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g id="Layer_x0020_1">
<g id="_2082244081712">
<circle cx="1250" cy="1250" r="1250"></circle>
<path class="st0" d="M1820.4,549.8h233.2l-485.5,503.4c-175.8,182.3-460.8,182.3-636.2,0L446.3,549.8h233.2l368.7,382.4 c111.5,115.3,291.8,115.3,403,0L1820.4,549.8L1820.4,549.8z M676.6,1950.2H443.3l488.6-506.8c175.8-182.3,460.8-182.3,636.6,0 l488.6,506.8h-233.3l-371.8-385.8c-111.5-115.3-291.8-115.3-403,0L676.6,1950.2z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 812 B

612
index.html Normal file
View File

@ -0,0 +1,612 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ripple Wallet</title>
<link rel="shortcut icon" href="favicon.svg" type="image/x-icon" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<link rel="stylesheet" href="css/main.css" />
<!-- External Libraries -->
<script src="https://cdn.jsdelivr.net/npm/xrpl@2.7.0/build/xrpl-latest-min.js"></script>
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
<script src="https://unpkg.com/@preact/signals-core@1.7.0/dist/signals-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/elliptic/6.6.1/elliptic.min.js"></script>
</head>
<body class="hidden">
<!-- Loading Screen -->
<div id="loading_page" class="loading-screen">
<div class="loading-content">
<div class="loading-spinner"></div>
<h2>Ripple Wallet</h2>
<p>Loading...</p>
</div>
</div>
<!-- Header -->
<header class="header">
<div class="header-content">
<div id="logo" class="app-brand">
<svg id="main_logo" class="icon" viewBox="0 0 27.25 32">
<title>RanchiMall</title>
<path
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z"
/>
</svg>
<div class="app-name">
<div class="app-name__company">RanchiMall</div>
<h4 class="app-name__title">Ripple Wallet</h4>
</div>
</div>
<div class="header-actions">
<button
id="themeToggle"
class="theme-toggle"
title="Toggle dark/light mode"
>
<i class="fas fa-sun" id="themeIcon"></i>
</button>
</div>
</div>
</header>
<!-- Main Container -->
<div class="container">
<!-- Sidebar Navigation -->
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<i class="fas fa-wallet"></i>
<h3>Ripple Wallet</h3>
</div>
<ul class="sidebar-menu">
<li>
<a
href="#"
onclick="showPage('connectPage')"
class="nav-link active"
>
<i class="fas fa-search-dollar"></i>Balance
</a>
</li>
<li>
<a href="#" onclick="showPage('sendPage')" class="nav-link">
<i class="fas fa-paper-plane"></i> Send XRP
</a>
</li>
<li>
<a href="#" onclick="showPage('generatePage')" class="nav-link">
<i class="fas fa-plus-circle"></i> Generate Address
</a>
</li>
<li>
<a href="#" onclick="showPage('recoverPage')" class="nav-link">
<i class="fas fa-key"></i> Retrieve Address
</a>
</li>
</ul>
</nav>
<!-- Main Content Area -->
<main class="main-content">
<!-- Check Balance & Transactions Page -->
<div id="connectPage" class="page">
<div class="page-header">
<h2><i class="fas fa-search-dollar"></i> Check Balance</h2>
<p>
Check the XRP balance and transaction history for any Ripple
address
</p>
</div>
<div class="card">
<div class="form-group">
<label for="checkAddress">
<i class="fas fa-wallet"></i> XRP Address or Private Key
</label>
<input
type="text"
id="checkAddress"
class="form-input"
placeholder="Enter XRP address (r...), private key (BTC/FLO)"
autocomplete="off"
/>
<div class="input-help">
Enter an XRP address,private key (BTC/FLO) to check XRP balance
</div>
</div>
<button
onclick="checkBalanceAndTransactions()"
class="btn btn-primary btn-block"
>
<i class="fas fa-search-dollar"></i> Check Balance
</button>
</div>
<!-- Balance Info Display -->
<div class="card balance-info" id="balanceInfo" style="display: none">
<div class="balance-header">
<h3><i class="fas fa-coins"></i> Account Balance</h3>
<div class="balance-display">
<span class="balance-amount" id="displayBalance">0 XRP</span>
</div>
</div>
<div class="account-details">
<div class="detail-row">
<label>Address:</label>
<div class="address-container">
<span id="checkedAddress" class="address-text">-</span>
<button
onclick="copyAddressToClipboard(document.getElementById('checkedAddress').textContent)"
class="btn-icon"
title="Copy Address"
>
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Transaction Section -->
<div
id="transactionSection"
class="card transaction-section"
style="display: none"
>
<!-- Transaction Header and Filter Controls -->
<div id="transactionControls" class="transaction-controls">
<div class="transaction-header">
<h3>Transactions</h3>
<div class="filter-buttons">
<button
class="filter-btn active"
data-filter="all"
onclick="setTransactionFilter('all')"
>
<i class="fas fa-list"></i> All
</button>
<button
class="filter-btn"
data-filter="received"
onclick="setTransactionFilter('received')"
>
<i class="fas fa-arrow-down"></i> Received
</button>
<button
class="filter-btn"
data-filter="sent"
onclick="setTransactionFilter('sent')"
>
<i class="fas fa-arrow-up"></i> Sent
</button>
</div>
</div>
</div>
<!-- Transaction Results -->
<div id="txList" class="transaction-list">
<!-- Transaction cards will be inserted here -->
</div>
<!-- Pagination Controls (moved below transaction list) -->
<div class="pagination-section">
<div class="pagination-info" id="paginationInfo">
Showing 0 - 0 of 0 transactions
</div>
<div class="pagination-controls">
<button
class="pagination-btn"
id="prevBtn"
onclick="goToPreviousPage()"
disabled
>
<i class="fas fa-chevron-left"></i>
</button>
<div class="page-numbers" id="pageNumbers">
<!-- Page numbers will be inserted here -->
</div>
<button
class="pagination-btn"
id="nextBtn"
onclick="goToNextPage()"
disabled
>
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Send XRP Page -->
<div id="sendPage" class="page hidden">
<div class="page-header">
<h2><i class="fas fa-paper-plane"></i> Send XRP</h2>
<p>Send XRP to another Ripple address</p>
</div>
<div class="card">
<div class="form-group">
<label for="sendKey">
<i class="fas fa-key"></i> Your Ripple Seed or Private Key
(FLO/BTC)
</label>
<input
type="password"
id="sendKey"
class="form-input"
placeholder="Enter your seed (s...) or private key (FLO/BTC)"
autocomplete="off"
/>
<div class="input-help">Use your Ripple seed (s...)</div>
</div>
<div class="form-group">
<label for="recipient">
<i class="fas fa-user"></i> Recipient Address
</label>
<input
type="text"
id="recipient"
class="form-input"
placeholder="Enter Ripple address (r...)"
/>
<small class="help-text"
>Enter a valid Ripple address starting with 'r' (25-34
characters)</small
>
</div>
<div class="form-group">
<label for="amount">
<i class="fas fa-coins"></i> Amount (XRP)
</label>
<input
type="number"
id="amount"
class="form-input"
placeholder="0.00"
step="0.000001"
min="0.000001"
/>
<div class="input-help">Minimum: 0.000001 XRP</div>
</div>
<button onclick="sendXRP()" class="btn btn-primary btn-block">
<i class="fas fa-paper-plane"></i> Send XRP
</button>
</div>
</div>
<!-- Generate Address Page -->
<div id="generatePage" class="page hidden">
<div class="page-header">
<h2><i class="fas fa-plus-circle"></i> Generate XRP Address</h2>
<p>Generate XRP addresses from private keys of any blockchain</p>
</div>
<div class="card">
<!-- Private Key Input for XRP Generation -->
<div class="form-group">
<label for="generateKey">
<i class="fas fa-key"></i> Private Key (Required for XRP)
</label>
<input
type="password"
id="generateKey"
placeholder="Enter private key from any blockchain (BTC/FLO)"
class="form-input"
/>
<small class="form-text">
Enter a private key from any blockchain (BTC/FLO) to generate
the corresponding XRP address.
</small>
</div>
<div class="generate-actions">
<button
onclick="generateXRPAddress()"
class="btn btn-primary btn-block"
>
<i class="fas fa-coins"></i> Generate XRP Address
</button>
</div>
<!-- XRP Generation Info -->
<div
id="xrpGenerationInfo"
class="card info-card"
style="display: none"
>
<div class="info-header">
<h3>
<i class="fas fa-info-circle"></i> XRP Address Generation
</h3>
</div>
<div class="info-content">
<p>
<strong>Note:</strong> This wallet doesn't generate XRP
addresses directly.
</p>
<p>To get an XRP address, please:</p>
<ol>
<li>Use our BTC wallet to generate a BTC private key</li>
<li>
Convert that BTC/FLO private key to Ripple seed using the
conversion tool below
</li>
</ol>
<button
onclick="showPage('convertPage')"
class="btn btn-primary"
>
<i class="fas fa-exchange-alt"></i> Go to Conversion Tool
</button>
</div>
</div>
</div>
<!-- Generated Address Output -->
<div id="walletOutput" class="card output-card" style="display: none">
<!-- Generated address info will be inserted here -->
</div>
</div>
<!-- Retrieve Address Page -->
<div id="recoverPage" class="page hidden">
<div class="page-header">
<h2><i class="fas fa-key"></i> Retrieve XRP Address</h2>
<p>Retrieve your XRP address from private key</p>
</div>
<div class="card">
<div class="form-group">
<label for="recoverKey">
<i class="fas fa-key"></i> Private Key / Seed
</label>
<input
type="password"
id="recoverKey"
class="form-input"
placeholder="Enter private key or seed"
autocomplete="off"
/>
<div class="input-help">
Enter your private key from any blockchain (BTC/FLO) or XRP seed
(s...) to retrieve your XRP address.
</div>
</div>
<div class="retrieve-actions">
<button
onclick="retrieveXRPAddress()"
class="btn btn-primary btn-block"
>
<i class="fas fa-coins"></i> Retrieve XRP Address
</button>
</div>
</div>
<!-- Retrieved Address Output -->
<div
id="recoveryOutput"
class="card output-card"
style="display: none"
>
<!-- Retrieved address info will be inserted here -->
</div>
</div>
</main>
</div>
<!-- Bottom Navigation (Mobile) -->
<div class="nav-box">
<button
onclick="showPage('connectPage')"
class="nav-btn active"
data-page="connectPage"
>
<i class="fas fa-search-dollar"></i>
<span>Balance</span>
</button>
<button
onclick="showPage('sendPage')"
class="nav-btn"
data-page="sendPage"
>
<i class="fas fa-paper-plane"></i>
<span>Send</span>
</button>
<button
onclick="showPage('generatePage')"
class="nav-btn"
data-page="generatePage"
>
<i class="fas fa-plus"></i>
<span>Generate</span>
</button>
<button
onclick="showPage('recoverPage')"
class="nav-btn"
data-page="recoverPage"
>
<i class="fas fa-key"></i>
<span>Retrieve</span>
</button>
</div>
<!-- Confirmation Popup -->
<sm-popup id="sendConfirm">
<div class="popup-content">
<h3><i class="fas fa-exclamation-triangle"></i> Confirm Transaction</h3>
<div class="transaction-details" id="transactionDetails">
<!-- Transaction details will be populated here -->
</div>
<div class="popup-actions">
<button onclick="closePopup()" class="btn btn-secondary">
<i class="fas fa-times"></i> Cancel
</button>
<button onclick="confirmSend()" class="btn btn-primary">
<i class="fas fa-paper-plane"></i> Send
</button>
</div>
</div>
</sm-popup>
<!-- Transaction Success Popup -->
<sm-popup id="transactionSuccess">
<div class="popup-content">
<h3><i class="fas fa-check-circle"></i> Transaction Successful!</h3>
<div class="transaction-details" id="successTransactionDetails">
<!-- Success transaction details will be populated here -->
</div>
<div class="popup-actions">
<button onclick="closePopup()" class="btn btn-primary">
<i class="fas fa-check"></i> Close
</button>
</div>
</div>
</sm-popup>
<!-- Standard UI Notifications -->
<!-- Confirmation Popup -->
<sm-popup id="confirmation_popup">
<div class="popup-content">
<h3 id="confirm_title">Confirm Action</h3>
<div id="confirm_message"></div>
<div class="popup-actions">
<button class="btn btn-secondary cancel-button">Cancel</button>
<button class="btn btn-primary confirm-button">OK</button>
</div>
</div>
</sm-popup>
<!-- Scripts -->
<script src="scripts/components.min.js"></script>
<script src="scripts/btcwallet_scripts_lib.js"></script>
<script type="text/javascript" src="scripts/floCrypto.js"></script>
<script type="text/javascript" src="scripts/btcOperator.js"></script>
<script type="module" src="scripts/wallet.js"></script>
<script>
// UI Enhancement Functions
function showPage(pageId) {
// Hide all pages
document.querySelectorAll(".page").forEach((page) => {
page.classList.add("hidden");
});
// Show selected page
document.getElementById(pageId).classList.remove("hidden");
// Update navigation states
updateNavigation(pageId);
}
function updateNavigation(activePageId) {
// Update sidebar navigation - remove active from all first
document.querySelectorAll(".sidebar .nav-link").forEach((link) => {
link.classList.remove("active");
});
// Update bottom navigation
document.querySelectorAll(".nav-btn").forEach((btn) => {
btn.classList.remove("active");
if (btn.dataset.page === activePageId) {
btn.classList.add("active");
}
});
// Find and activate corresponding sidebar link
const activeLink = document.querySelector(
`.sidebar .nav-link[onclick*="${activePageId}"]`
);
if (activeLink) {
activeLink.classList.add("active");
}
}
// Theme toggle functionality
function initializeTheme() {
const themeToggle = document.getElementById("themeToggle");
const themeIcon = document.getElementById("themeIcon");
const body = document.body;
// Check for saved theme preference or default to 'light'
const savedTheme = localStorage.getItem("theme");
const systemPrefersDark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
// Set initial theme
let currentTheme = savedTheme || (systemPrefersDark ? "dark" : "light");
// Apply theme
body.setAttribute("data-theme", currentTheme);
updateThemeIcon(currentTheme);
// Add event listener for theme toggle
themeToggle.addEventListener("click", () => {
currentTheme = currentTheme === "light" ? "dark" : "light";
body.setAttribute("data-theme", currentTheme);
localStorage.setItem("theme", currentTheme);
updateThemeIcon(currentTheme);
notify(`Switched to ${currentTheme} mode`, "success");
});
}
function updateThemeIcon(theme) {
const themeIcon = document.getElementById("themeIcon");
if (theme === "dark") {
themeIcon.className = "fas fa-sun";
} else {
themeIcon.className = "fas fa-moon";
}
}
// Initialize theme when DOM is loaded
document.addEventListener("DOMContentLoaded", function () {
initializeTheme();
// Initialize searched addresses list
if (typeof updateSearchedAddressesList === "function") {
updateSearchedAddressesList();
}
// Add real-time validation for recipient address
const recipientInput = document.getElementById("recipient");
const helpText = recipientInput
? recipientInput.nextElementSibling
: null;
if (recipientInput && helpText) {
recipientInput.addEventListener("input", function () {
const address = this.value.trim();
if (address && typeof validateAddressInput === "function") {
const validation = validateAddressInput(address);
if (validation.valid) {
helpText.textContent = "✓ Valid Ripple address";
helpText.style.color = "#22c55e";
} else {
helpText.textContent = validation.message;
helpText.style.color = "#ef4444";
}
} else {
helpText.textContent =
"Enter a valid Ripple address starting with 'r' (25-34 characters)";
helpText.style.color = "";
}
});
}
});
</script>
<div id="notification_drawer" class="notification-drawer"></div>
</body>
</html>

1499
scripts/btcOperator.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1
scripts/components.min.js vendored Normal file

File diff suppressed because one or more lines are too long

606
scripts/floCrypto.js Normal file
View File

@ -0,0 +1,606 @@
(function (EXPORTS) {
//floCrypto v2.3.6a
/* FLO Crypto Operators */
"use strict";
const floCrypto = EXPORTS;
const p = BigInteger(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
16
);
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
const ascii_alternatives = ` '\n '\n“ "\n” "\n --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
coinjs.compressed = true; //defaulting coinjs compressed to true;
function calculateY(x) {
let exp = exponent1();
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
return x
.modPow(BigInteger("3"), p)
.add(BigInteger("7"))
.mod(p)
.modPow(exp, p);
}
function getUncompressedPublicKey(compressedPublicKey) {
// Fetch x from compressedPublicKey
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
const prefix = pubKeyBytes.shift(); // remove prefix
let prefix_modulus = prefix % 2;
pubKeyBytes.unshift(0); // add prefix 0
let x = new BigInteger(pubKeyBytes);
let xDecimalValue = x.toString();
// Fetch y
let y = calculateY(x);
let yDecimalValue = y.toString();
// verify y value
let resultBigInt = y.mod(BigInteger("2"));
let check = resultBigInt.toString() % 2;
if (prefix_modulus !== check) yDecimalValue = y.negate().mod(p).toString();
return {
x: xDecimalValue,
y: yDecimalValue,
};
}
function getSenderPublicKeyString() {
let privateKey = ellipticCurveEncryption.senderRandom();
var senderPublicKeyString =
ellipticCurveEncryption.senderPublicString(privateKey);
return {
privateKey: privateKey,
senderPublicKeyString: senderPublicKeyString,
};
}
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
let receiverPublicKeyString =
getUncompressedPublicKey(receiverPublicKeyHex);
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
receiverPublicKeyString.x,
receiverPublicKeyString.y,
senderPrivateKey
);
return senderDerivedKey;
}
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
return ellipticCurveEncryption.receiverSharedKeyDerivation(
senderPublicKeyString.XValuePublicString,
senderPublicKeyString.YValuePublicString,
receiverPrivateKey
);
}
function getReceiverPublicKeyString(privateKey) {
return ellipticCurveEncryption.receiverPublicString(privateKey);
}
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
let pk = Bitcoin.Base58.decode(pk_wif);
pk.shift();
pk.splice(-4, 4);
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
if (isPubKeyCompressed == true) pk.pop();
pk.unshift(0);
let privateKeyDecimal = BigInteger(pk).toString();
let privateKeyHex = Crypto.util.bytesToHex(pk);
return {
privateKeyDecimal: privateKeyDecimal,
privateKeyHex: privateKeyHex,
};
}
//generate a random Interger within range
floCrypto.randInt = function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
};
//generate a random String within length (options : alphaNumeric chars only)
floCrypto.randString = function (length, alphaNumeric = true) {
var result = "";
var characters = alphaNumeric
? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():";
for (var i = 0; i < length; i++)
result += characters.charAt(
Math.floor(securedMathRandom() * characters.length)
);
return result;
};
//Encrypt Data using public-key
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
var senderECKeyData = getSenderPublicKeyString();
var senderDerivedKey = deriveSharedKeySender(
receiverPublicKeyHex,
senderECKeyData.privateKey
);
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
let secret = Crypto.AES.encrypt(data, senderKey);
return {
secret: secret,
senderPublicKeyString: senderECKeyData.senderPublicKeyString,
};
};
//Decrypt Data using private-key
floCrypto.decryptData = function (data, privateKeyHex) {
var receiverECKeyData = {};
if (typeof privateKeyHex !== "string")
throw new Error("No private key found.");
let privateKey = wifToDecimal(privateKeyHex, true);
if (typeof privateKey.privateKeyDecimal !== "string")
throw new Error("Failed to detremine your private key.");
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
var receiverDerivedKey = deriveSharedKeyReceiver(
data.senderPublicKeyString,
receiverECKeyData.privateKey
);
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
return decryptMsg;
};
//Sign data using private-key
floCrypto.signData = function (data, privateKeyHex) {
var key = new Bitcoin.ECKey(privateKeyHex);
var messageHash = Crypto.SHA256(data);
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
var sighex = Crypto.util.bytesToHex(messageSign);
return sighex;
};
//Verify signatue of the data using public-key
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
var msgHash = Crypto.SHA256(data);
var sigBytes = Crypto.util.hexToBytes(signatureHex);
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
return verify;
};
//Generates a new flo ID and returns private-key, public-key and floID
const generateNewID = (floCrypto.generateNewID = function () {
var key = new Bitcoin.ECKey(false);
key.setCompressed(true);
return {
floID: key.getBitcoinAddress(),
pubKey: key.getPubKeyHex(),
privKey: key.getBitcoinWalletImportFormat(),
};
});
Object.defineProperties(floCrypto, {
newID: {
get: () => generateNewID(),
},
hashID: {
value: (str) => {
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), {
asBytes: true,
});
bytes.unshift(bitjs.pub);
var hash = Crypto.SHA256(
Crypto.SHA256(bytes, {
asBytes: true,
}),
{
asBytes: true,
}
);
var checksum = hash.slice(0, 4);
return bitjs.Base58.encode(bytes.concat(checksum));
},
},
tmpID: {
get: () => {
let bytes = Crypto.util.randomBytes(20);
bytes.unshift(bitjs.pub);
var hash = Crypto.SHA256(
Crypto.SHA256(bytes, {
asBytes: true,
}),
{
asBytes: true,
}
);
var checksum = hash.slice(0, 4);
return bitjs.Base58.encode(bytes.concat(checksum));
},
},
});
//Returns public-key from private-key
floCrypto.getPubKeyHex = function (privateKeyHex) {
if (!privateKeyHex) return null;
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null) return null;
key.setCompressed(true);
return key.getPubKeyHex();
};
//Returns flo-ID from public-key or private-key
floCrypto.getFloID = function (keyHex) {
if (!keyHex) return null;
try {
var key = new Bitcoin.ECKey(keyHex);
if (key.priv == null) key.setPub(keyHex);
return key.getBitcoinAddress();
} catch {
return null;
}
};
floCrypto.getAddress = function (privateKeyHex, strict = false) {
if (!privateKeyHex) return;
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null) return null;
key.setCompressed(true);
let pubKey = key.getPubKeyHex(),
version = bitjs.Base58.decode(privateKeyHex)[0];
switch (version) {
case coinjs.priv: //BTC
return coinjs.bech32Address(pubKey).address;
case bitjs.priv: //FLO
return bitjs.pubkey2address(pubKey);
default:
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
}
};
//Verify the private-key for the given public-key or flo-ID
floCrypto.verifyPrivKey = function (
privateKeyHex,
pubKey_floID,
isfloID = true
) {
if (!privateKeyHex || !pubKey_floID) return false;
try {
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null) return false;
key.setCompressed(true);
if (isfloID && pubKey_floID == key.getBitcoinAddress()) return true;
else if (
!isfloID &&
pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase()
)
return true;
else return false;
} catch {
return null;
}
};
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
if (!Array.isArray(publicKeyList) || !publicKeyList.length) return null;
if (
!Number.isInteger(requiredSignatures) ||
requiredSignatures < 1 ||
requiredSignatures > publicKeyList.length
)
return null;
try {
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
return multisig;
} catch {
return null;
}
};
floCrypto.decodeRedeemScript = function (redeemScript) {
try {
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
return decoded;
} catch {
return null;
}
};
//Check if the given flo-id is valid or not
floCrypto.validateFloID = function (floID, regularOnly = false) {
if (!floID) return false;
try {
let addr = new Bitcoin.Address(floID);
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
return false;
return true;
} catch {
return false;
}
};
//Check if the given address (any blockchain) is valid or not
floCrypto.validateAddr = function (address, std = true, bech = true) {
let raw = decodeAddress(address);
if (!raw) return false;
if (typeof raw.version !== "undefined") {
//legacy or segwit
if (std == false) return false;
else if (
std === true ||
(!Array.isArray(std) && std === raw.version) ||
(Array.isArray(std) && std.includes(raw.version))
)
return true;
else return false;
} else if (typeof raw.bech_version !== "undefined") {
//bech32
if (bech === false) return false;
else if (
bech === true ||
(!Array.isArray(bech) && bech === raw.bech_version) ||
(Array.isArray(bech) && bech.includes(raw.bech_version))
)
return true;
else return false;
} //unknown
else return false;
};
//Check the public-key (or redeem-script) for the address (any blockchain)
floCrypto.verifyPubKey = function (pubKeyHex, address) {
let raw = decodeAddress(address);
if (!raw) return;
let pub_hash = Crypto.util.bytesToHex(
ripemd160(
Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })
)
);
if (typeof raw.bech_version !== "undefined" && raw.bytes.length == 32)
//bech32-multisig
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
return pub_hash === raw.hex;
};
//Convert the given address (any blockchain) to equivalent floID
floCrypto.toFloID = function (address, options = null) {
if (!address) return;
let raw = decodeAddress(address);
if (!raw) return;
else if (options) {
//if (optional) version check is passed
if (
typeof raw.version !== "undefined" &&
(!options.std || !options.std.includes(raw.version))
)
return;
if (
typeof raw.bech_version !== "undefined" &&
(!options.bech || !options.bech.includes(raw.bech_version))
)
return;
}
raw.bytes.unshift(bitjs.pub);
let hash = Crypto.SHA256(
Crypto.SHA256(raw.bytes, {
asBytes: true,
}),
{
asBytes: true,
}
);
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
};
//Convert raw address bytes to floID
floCrypto.rawToFloID = function (raw_bytes) {
if (typeof raw_bytes === "string")
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
if (raw_bytes.length != 20) return null;
raw_bytes.unshift(bitjs.pub);
let hash = Crypto.SHA256(
Crypto.SHA256(raw_bytes, {
asBytes: true,
}),
{
asBytes: true,
}
);
return bitjs.Base58.encode(raw_bytes.concat(hash.slice(0, 4)));
};
//Convert the given multisig address (any blockchain) to equivalent multisig floID
floCrypto.toMultisigFloID = function (address, options = null) {
if (!address) return;
let raw = decodeAddress(address);
if (!raw) return;
else if (options) {
//if (optional) version check is passed
if (
typeof raw.version !== "undefined" &&
(!options.std || !options.std.includes(raw.version))
)
return;
if (
typeof raw.bech_version !== "undefined" &&
(!options.bech || !options.bech.includes(raw.bech_version))
)
return;
}
if (typeof raw.bech_version !== "undefined") {
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
raw.bytes = ripemd160(raw.bytes, {
asBytes: true,
});
}
raw.bytes.unshift(bitjs.multisig);
let hash = Crypto.SHA256(
Crypto.SHA256(raw.bytes, {
asBytes: true,
}),
{
asBytes: true,
}
);
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
};
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
floCrypto.isSameAddr = function (addr1, addr2) {
if (!addr1 || !addr2) return;
let raw1 = decodeAddress(addr1),
raw2 = decodeAddress(addr2);
if (!raw1 || !raw2) return false;
else {
if (typeof raw1.bech_version !== "undefined" && raw1.bytes.length == 32)
//bech32-multisig
raw1.hex = Crypto.util.bytesToHex(
ripemd160(raw1.bytes, { asBytes: true })
);
if (typeof raw2.bech_version !== "undefined" && raw2.bytes.length == 32)
//bech32-multisig
raw2.hex = Crypto.util.bytesToHex(
ripemd160(raw2.bytes, { asBytes: true })
);
return raw1.hex === raw2.hex;
}
};
const decodeAddress = (floCrypto.decodeAddr = function (address) {
if (!address) return;
else if (address.length == 33 || address.length == 34) {
//legacy encoding
let decode = bitjs.Base58.decode(address);
let bytes = decode.slice(0, decode.length - 4);
let checksum = decode.slice(decode.length - 4),
hash = Crypto.SHA256(
Crypto.SHA256(bytes, {
asBytes: true,
}),
{
asBytes: true,
}
);
return hash[0] != checksum[0] ||
hash[1] != checksum[1] ||
hash[2] != checksum[2] ||
hash[3] != checksum[3]
? null
: {
version: bytes.shift(),
hex: Crypto.util.bytesToHex(bytes),
bytes,
};
} else if (address.length == 42 || address.length == 62) {
//bech encoding
let decode = coinjs.bech32_decode(address);
if (decode) {
let bytes = decode.data;
let bech_version = bytes.shift();
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
return {
bech_version,
hrp: decode.hrp,
hex: Crypto.util.bytesToHex(bytes),
bytes,
};
} else return null;
}
});
//Split the str using shamir's Secret and Returns the shares
floCrypto.createShamirsSecretShares = function (
str,
total_shares,
threshold_limit
) {
try {
if (str.length > 0) {
var strHex = shamirSecretShare.str2hex(str);
var shares = shamirSecretShare.share(
strHex,
total_shares,
threshold_limit
);
return shares;
}
return false;
} catch {
return false;
}
};
//Returns the retrived secret by combining the shamirs shares
const retrieveShamirSecret = (floCrypto.retrieveShamirSecret = function (
sharesArray
) {
try {
if (sharesArray.length > 0) {
var comb = shamirSecretShare.combine(
sharesArray.slice(0, sharesArray.length)
);
comb = shamirSecretShare.hex2str(comb);
return comb;
}
return false;
} catch {
return false;
}
});
//Verifies the shares and str
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
if (!str) return null;
else if (retrieveShamirSecret(sharesArray) === str) return true;
else return false;
};
const validateASCII = (floCrypto.validateASCII = function (
string,
bool = true
) {
if (typeof string !== "string") return null;
if (bool) {
let x;
for (let i = 0; i < string.length; i++) {
x = string.charCodeAt(i);
if (x < 32 || x > 127) return false;
}
return true;
} else {
let x,
invalids = {};
for (let i = 0; i < string.length; i++) {
x = string.charCodeAt(i);
if (x < 32 || x > 127)
if (x in invalids) invalids[string[i]].push(i);
else invalids[string[i]] = [i];
}
if (Object.keys(invalids).length) return invalids;
else return true;
}
});
floCrypto.convertToASCII = function (string, mode = "soft-remove") {
let chars = validateASCII(string, false);
if (chars === true) return string;
else if (chars === null) return null;
let convertor,
result = string,
refAlt = {};
ascii_alternatives.split("\n").forEach((a) => (refAlt[a[0]] = a.slice(2)));
mode = mode.toLowerCase();
if (mode === "hard-unicode")
convertor = (c) =>
`\\u${("000" + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "soft-unicode")
convertor = (c) =>
refAlt[c] || `\\u${("000" + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "hard-remove") convertor = (c) => "";
else if (mode === "soft-remove") convertor = (c) => refAlt[c] || "";
else return null;
for (let c in chars) result = result.replaceAll(c, convertor(c));
return result;
};
floCrypto.revertUnicode = function (string) {
return string.replace(/\\u[\dA-F]{4}/gi, (m) =>
String.fromCharCode(parseInt(m.replace(/\\u/g, ""), 16))
);
};
})("object" === typeof module ? module.exports : (window.floCrypto = {}));

2137
scripts/wallet.js Normal file

File diff suppressed because it is too large Load Diff