feat: Add Ripple (XRP) private key validation and decoding

This commit is contained in:
void-57 2026-03-03 23:00:55 +05:30
parent b61209902a
commit 9349aaf48a
2 changed files with 101 additions and 33 deletions

View File

@ -78,37 +78,62 @@
document.body.prepend(document.createElement('adblocker-warning'))
return
}
floGlobals.myFloID = floCrypto.getFloID(floDapps.user.public);
floGlobals.myBtcID = btcOperator.convert.legacy2bech(floGlobals.myFloID)
floGlobals.myEthID = floEthereum.ethAddressFromCompressedPublicKey(floDapps.user.public)
// AVAX C-Chain uses same address format as Ethereum
floGlobals.myAvaxID = floGlobals.myEthID;
// BSC (Binance Smart Chain) uses same address format as Ethereum
floGlobals.myBscID = floGlobals.myEthID;
// MATIC (Polygon) uses same address format as Ethereum
floGlobals.myMaticID = floGlobals.myEthID;
// Arbitrum uses same address format as Ethereum
floGlobals.myArbID = floGlobals.myEthID;
// Optimism uses same address format as Ethereum
floGlobals.myOpID = floGlobals.myEthID;
// HBAR (Hedera) uses same address format as Ethereum
floGlobals.myHbarID = floGlobals.myEthID;
// Initialize private-key-dependent addresses to null (will be derived after messenger init)
floGlobals.myXrpID = null;
floGlobals.mySuiID = null;
floGlobals.myAdaID = null;
floGlobals.myTonID = null;
floGlobals.myTronID = null;
floGlobals.myDogeID = null;
floGlobals.myLtcID = null;
floGlobals.myBchID = null;
floGlobals.myDotID = null;
floGlobals.myAlgoID = null;
floGlobals.myXlmID = null;
floGlobals.mySolID = null;
// Note: Cardano (ADA) address will be derived from private key later
let activeChain = localStorage.getItem(`${floGlobals.application}#activeChain`);
if (activeChain === 'XRP') {
// XRP-only login: skip FLO/BTC/ETH derivations
floGlobals.myFloID = floDapps.user.id; // This is the XRP address (r...)
floGlobals.myXrpID = floDapps.user.id;
floGlobals.myBtcID = null;
floGlobals.myEthID = null;
floGlobals.myAvaxID = null;
floGlobals.myBscID = null;
floGlobals.myMaticID = null;
floGlobals.myArbID = null;
floGlobals.myOpID = null;
floGlobals.myHbarID = null;
floGlobals.mySuiID = null;
floGlobals.myAdaID = null;
floGlobals.myTonID = null;
floGlobals.myTronID = null;
floGlobals.myDogeID = null;
floGlobals.myLtcID = null;
floGlobals.myBchID = null;
floGlobals.myDotID = null;
floGlobals.myAlgoID = null;
floGlobals.myXlmID = null;
floGlobals.mySolID = null;
} else {
floGlobals.myFloID = floCrypto.getFloID(floDapps.user.public);
floGlobals.myBtcID = btcOperator.convert.legacy2bech(floGlobals.myFloID)
floGlobals.myEthID = floEthereum.ethAddressFromCompressedPublicKey(floDapps.user.public)
// AVAX C-Chain uses same address format as Ethereum
floGlobals.myAvaxID = floGlobals.myEthID;
// BSC (Binance Smart Chain) uses same address format as Ethereum
floGlobals.myBscID = floGlobals.myEthID;
// MATIC (Polygon) uses same address format as Ethereum
floGlobals.myMaticID = floGlobals.myEthID;
// Arbitrum uses same address format as Ethereum
floGlobals.myArbID = floGlobals.myEthID;
// Optimism uses same address format as Ethereum
floGlobals.myOpID = floGlobals.myEthID;
// HBAR (Hedera) uses same address format as Ethereum
floGlobals.myHbarID = floGlobals.myEthID;
// Initialize private-key-dependent addresses to null (will be derived after messenger init)
floGlobals.myXrpID = null;
floGlobals.mySuiID = null;
floGlobals.myAdaID = null;
floGlobals.myTonID = null;
floGlobals.myTronID = null;
floGlobals.myDogeID = null;
floGlobals.myLtcID = null;
floGlobals.myBchID = null;
floGlobals.myDotID = null;
floGlobals.myAlgoID = null;
floGlobals.myXlmID = null;
floGlobals.mySolID = null;
// Note: Cardano (ADA) address will be derived from private key later
}
document.querySelectorAll('.user-profile-id').forEach(el => el.textContent = floGlobals.myFloID)
//load messages from IDB and render them
console.log(`Loading Data! Please Wait...`)
@ -230,7 +255,7 @@
}
})
} catch (e) {
notify(error, "error")
notify(e, "error")
}
}
</script>
@ -2119,6 +2144,7 @@
const idList = allIds.filter(item => {
if (!item.id) return false;
if (activeChain === 'XRP') return item.label === 'XRP';
if (item.label === 'FLO') return true; // Always include FLO
if (item.label === activeChain) return true; // Include active chain
// For EVM compatible chains which share the ETH ID:
@ -2775,6 +2801,8 @@
value.startsWith('suiprivkey1')
) {
isValid = true;
} else if (value.startsWith('s') && value.length >= 29 && value.length <= 32) {
isValid = true;
} else if (value.startsWith('S') && value.length === 56) {
isValid = true;
} else {
@ -2837,7 +2865,22 @@
console.error("Failed to decode SUI key", e);
}
}
else if (privateKey.startsWith('s')) activeChain = 'XRP';
else if (privateKey.startsWith('s') && privateKey.length >= 29 && privateKey.length <= 32) {
activeChain = 'XRP';
// Convert XRP secret to workable FLO WIF using the private key bytes
try {
let wallet = xrpl.Wallet.fromSeed(privateKey);
let privKeyHex = wallet.privateKey;
// Remove ED prefix if present (Ed25519 keys)
if (privKeyHex.startsWith('ED') || privKeyHex.startsWith('ed'))
privKeyHex = privKeyHex.substring(2);
let key = new Bitcoin.ECKey(privKeyHex);
key.setCompressed(true);
privateKey = key.getBitcoinWalletImportFormat();
} catch (e) {
console.error("Failed to decode XRP key", e);
}
}
else if (privateKey.startsWith('Q')) activeChain = 'DOGE';
else if (privateKey.startsWith('T') && privateKey.length === 51) activeChain = 'LTC';
else if (privateKey.startsWith('K') || privateKey.startsWith('L') || privateKey.startsWith('5')) {
@ -3327,8 +3370,19 @@
}
},
profile() {
let activeChain = localStorage.getItem(`${floGlobals.application}#activeChain`);
return html`
<div class="grid gap-1-5">
${activeChain === 'XRP' ? html`
<div class="grid gap-0-5">
<h4>XRP-only login</h4>
<p>You are logged in with an XRP private key. Only the XRP address is available.</p>
</div>
<div class="grid gap-0-5">
<b>My XRP (Ripple) address</b>
<sm-copy class="user-xrp-id" value=${floGlobals.myXrpID}></sm-copy>
</div>
` : html`
<div class="grid gap-0-5">
<h4>
BTC integrated with FLO
@ -3429,6 +3483,7 @@
Unlock all addresses
</button>
` : ''}
`}
<button class="button button--danger justify-self-start" onclick="signOut()">Sign out</button>
</div>
<div class="grid gap-1">

View File

@ -393,8 +393,21 @@
return;
var bytes;
// XRP Address (Base58, starts with 'r', 25-35 chars) - must be checked before FLO/BTC legacy
if (address.length >= 25 && address.length <= 35 && address.startsWith('r')) {
try {
// XRP address - hash the raw address for unique proxy ID
let addrBytes = [];
for (let i = 0; i < address.length; i++) {
addrBytes.push(address.charCodeAt(i));
}
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
} catch (e) {
bytes = undefined;
}
}
// FLO/BTC legacy encoding (33-34 chars)
if (address.length == 33 || address.length == 34) {
else if (address.length == 33 || address.length == 34) {
let decode = bitjs.Base58.decode(address);
bytes = decode.slice(0, decode.length - 4);
let checksum = decode.slice(decode.length - 4),