Add blockchain address derivation utilities for XRP, SUI, TON, TRON, and DOGE
This commit is contained in:
parent
7b56defa5c
commit
0c3c07b5d2
211
index.html
211
index.html
@ -18,6 +18,10 @@
|
||||
}
|
||||
</script>
|
||||
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xrpl@2.7.0/build/xrpl-latest-min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script>
|
||||
<script src="https://unpkg.com/tonweb/dist/tonweb.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/tronweb@5.3.2/dist/TronWeb.js"></script>
|
||||
<script src="scripts/lib.min.js"></script>
|
||||
<script src="scripts/floCrypto.js"></script>
|
||||
<script src="scripts/btcOperator.js"></script>
|
||||
@ -28,7 +32,9 @@
|
||||
<script src="scripts/keccak.js"></script>
|
||||
<script src="scripts/messengerEthereum.js"></script>
|
||||
<script src="scripts/messenger.min.js"></script>
|
||||
<script src="scripts/blockchainAddresses.js"></script>
|
||||
<script id="onLoadStartUp">
|
||||
|
||||
function canConnectToCloud() {
|
||||
const nodes = Object.values(floCloudAPI.nodes);
|
||||
// check if any node is online with websocket
|
||||
@ -70,6 +76,16 @@
|
||||
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;
|
||||
|
||||
// Initialize private-key-dependent addresses to null (will be derived after messenger init)
|
||||
floGlobals.myXrpID = null;
|
||||
floGlobals.mySuiID = null;
|
||||
floGlobals.myTonID = null;
|
||||
floGlobals.myTronID = null;
|
||||
floGlobals.myDogeID = null;
|
||||
|
||||
document.querySelectorAll('.user-profile-id').forEach(el => el.textContent = floGlobals.myFloID)
|
||||
//load messages from IDB and render them
|
||||
console.log(`Loading Data! Please Wait...`)
|
||||
@ -81,8 +97,64 @@
|
||||
rmMessenger.renderUI.mails = m => renderMailList(m, false);
|
||||
rmMessenger.renderUI.marked = renderMarked;
|
||||
//init messenger
|
||||
rmMessenger.init().then(result => {
|
||||
rmMessenger.init().then(async result => {
|
||||
console.log(result);
|
||||
|
||||
// Check if account is password-secured by checking the stored secret length
|
||||
try {
|
||||
const indexArr = localStorage.getItem(`${floGlobals.application}#privKey`);
|
||||
if (indexArr) {
|
||||
const indices = JSON.parse(indexArr);
|
||||
const shares = await Promise.all(indices.map(idx => compactIDB.readData('credentials', idx, floGlobals.application)));
|
||||
const secret = floCrypto.retrieveShamirSecret(shares);
|
||||
if (secret && secret.length !== 52) {
|
||||
// Secret is longer than 52 chars = it's AES encrypted = password-secured
|
||||
floGlobals.isPrivKeySecured = true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not check password security status:', e);
|
||||
}
|
||||
|
||||
// Derive private-key-dependent blockchain addresses after messenger is initialized
|
||||
// Check if we have a stored login password (for password-secured accounts)
|
||||
if (floGlobals._loginPassword) {
|
||||
// Use stored password to derive addresses
|
||||
try {
|
||||
const privKey = await getPrivKeyWithPassword(floGlobals._loginPassword);
|
||||
if (typeof privKey === 'string' && privKey.length > 0) {
|
||||
try { floGlobals.myXrpID = convertWIFtoXrpAddress(privKey); } catch (e) { console.warn('XRP derivation failed:', e); }
|
||||
try { floGlobals.mySuiID = convertWIFtoSuiAddress(privKey); } catch (e) { console.warn('SUI derivation failed:', e); }
|
||||
try { floGlobals.myTonID = await convertWIFtoTonAddress(privKey); } catch (e) { console.warn('TON derivation failed:', e); }
|
||||
try { floGlobals.myTronID = convertWIFtoTronAddress(privKey); } catch (e) { console.warn('TRON derivation failed:', e); }
|
||||
try { floGlobals.myDogeID = convertWIFtoDogeAddress(privKey); } catch (e) { console.warn('DOGE derivation failed:', e); }
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to derive addresses with stored password:', e);
|
||||
}
|
||||
// Clear the stored password for security
|
||||
delete floGlobals._loginPassword;
|
||||
} else {
|
||||
// Try to access private key directly (non-password-secured accounts)
|
||||
try {
|
||||
let privKey = floDapps.user.private;
|
||||
if (privKey instanceof Promise) {
|
||||
privKey = await privKey;
|
||||
}
|
||||
if (typeof privKey === 'string' && privKey.length > 0) {
|
||||
try { floGlobals.myXrpID = convertWIFtoXrpAddress(privKey); } catch (e) { console.warn('XRP derivation failed:', e); }
|
||||
try { floGlobals.mySuiID = convertWIFtoSuiAddress(privKey); } catch (e) { console.warn('SUI derivation failed:', e); }
|
||||
try { floGlobals.myTonID = await convertWIFtoTonAddress(privKey); } catch (e) { console.warn('TON derivation failed:', e); }
|
||||
try { floGlobals.myTronID = convertWIFtoTronAddress(privKey); } catch (e) { console.warn('TRON derivation failed:', e); }
|
||||
try { floGlobals.myDogeID = convertWIFtoDogeAddress(privKey); } catch (e) { console.warn('DOGE derivation failed:', e); }
|
||||
}
|
||||
} catch (e) {
|
||||
// Private key is password-secured but we don't have the password
|
||||
floGlobals.isPrivKeySecured = true;
|
||||
console.info('Private key is password-secured. Use Unlock button in Profile to derive addresses.');
|
||||
}
|
||||
}
|
||||
|
||||
//Check for available bg image
|
||||
setBgImage();
|
||||
routeTo(window.location.hash, { firstLoad: true })
|
||||
@ -1858,11 +1930,22 @@
|
||||
removeNotificationBadge('#chat_page_button')
|
||||
if (floGlobals.idInterval)
|
||||
clearInterval(floGlobals.idInterval)
|
||||
let showingFloID = true
|
||||
// alternating between floID and btcID every 10 seconds
|
||||
// Cycle through all blockchain addresses: FLO -> BTC -> ETH -> AVAX -> XRP -> SUI -> TON -> TRON -> DOGE
|
||||
let currentIdIndex = 0
|
||||
const idList = [
|
||||
{ id: floGlobals.myFloID, label: 'FLO' },
|
||||
{ id: floGlobals.myBtcID, label: 'BTC' },
|
||||
{ id: floGlobals.myEthID, label: 'ETH' },
|
||||
{ id: floGlobals.myAvaxID, label: 'AVAX' },
|
||||
{ id: floGlobals.myXrpID, label: 'XRP' },
|
||||
{ id: floGlobals.mySuiID, label: 'SUI' },
|
||||
{ id: floGlobals.myTonID, label: 'TON' },
|
||||
{ id: floGlobals.myTronID, label: 'TRON' },
|
||||
{ id: floGlobals.myDogeID, label: 'DOGE' }
|
||||
].filter(item => item.id)
|
||||
floGlobals.idInterval = setInterval(() => {
|
||||
document.querySelectorAll('.user-profile-id').forEach(el => el.textContent = showingFloID ? floGlobals.myFloID : floGlobals.myBtcID)
|
||||
showingFloID = !showingFloID
|
||||
currentIdIndex = (currentIdIndex + 1) % idList.length
|
||||
document.querySelectorAll('.user-profile-id').forEach(el => el.textContent = idList[currentIdIndex].id)
|
||||
}, 10000)
|
||||
break;
|
||||
case 'mail_page':
|
||||
@ -2348,6 +2431,90 @@
|
||||
target.type = target.type === 'password' ? 'text' : 'password';
|
||||
target.focusIn()
|
||||
}
|
||||
|
||||
// Get private key by decrypting with password
|
||||
async function getPrivKeyWithPassword(password) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const indexArr = localStorage.getItem(`${floGlobals.application}#privKey`);
|
||||
if (!indexArr) {
|
||||
reject('No login credentials found');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const indices = JSON.parse(indexArr);
|
||||
// Read from the app database where credentials are stored
|
||||
const promises = indices.map(idx => compactIDB.readData('credentials', idx, floGlobals.application));
|
||||
const shares = await Promise.all(promises);
|
||||
const secret = floCrypto.retrieveShamirSecret(shares);
|
||||
if (!secret) {
|
||||
reject('Failed to retrieve credentials');
|
||||
return;
|
||||
}
|
||||
if (secret.length === 52) {
|
||||
// Not password-secured, return directly
|
||||
resolve(secret);
|
||||
} else {
|
||||
// Password-secured, decrypt with password
|
||||
try {
|
||||
const privKey = Crypto.AES.decrypt(secret, password);
|
||||
resolve(privKey);
|
||||
} catch (e) {
|
||||
reject('Incorrect password');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Derive private-key-dependent blockchain addresses (XRP, SUI, TON, TRON, DOGE) with password
|
||||
async function derivePrivKeyAddresses() {
|
||||
try {
|
||||
const password = await getPromptInput('Enter password to unlock addresses', '', { isPassword: true });
|
||||
if (!password) {
|
||||
notify('Password is required', 'error');
|
||||
return;
|
||||
}
|
||||
// Get private key using the password
|
||||
const privKey = await getPrivKeyWithPassword(password);
|
||||
if (typeof privKey === 'string' && privKey.length > 0) {
|
||||
// Derive all addresses
|
||||
try {
|
||||
floGlobals.myXrpID = convertWIFtoXrpAddress(privKey);
|
||||
} catch (e) {
|
||||
console.warn('XRP derivation failed:', e);
|
||||
}
|
||||
try {
|
||||
floGlobals.mySuiID = convertWIFtoSuiAddress(privKey);
|
||||
} catch (e) {
|
||||
console.warn('SUI derivation failed:', e);
|
||||
}
|
||||
try {
|
||||
floGlobals.myTonID = await convertWIFtoTonAddress(privKey);
|
||||
} catch (e) {
|
||||
console.warn('TON derivation failed:', e);
|
||||
}
|
||||
try {
|
||||
floGlobals.myTronID = convertWIFtoTronAddress(privKey);
|
||||
} catch (e) {
|
||||
console.warn('TRON derivation failed:', e);
|
||||
}
|
||||
try {
|
||||
floGlobals.myDogeID = convertWIFtoDogeAddress(privKey);
|
||||
} catch (e) {
|
||||
console.warn('DOGE derivation failed:', e);
|
||||
}
|
||||
// Re-render profile to show new addresses
|
||||
routeTo(window.location.hash);
|
||||
notify('Addresses unlocked successfully!', 'success');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to derive addresses:', e);
|
||||
notify(typeof e === 'string' ? e : 'Failed to unlock addresses', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function getSignedIn(passwordType) {
|
||||
return new Promise((resolve, reject) => {
|
||||
routeTo(window.location.hash)
|
||||
@ -2384,6 +2551,10 @@
|
||||
let privateKey = getRef('private_key_field').value.trim();
|
||||
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) //if hex private key might be ethereum
|
||||
privateKey = coinjs.privkey2wif(privateKey);
|
||||
// Store password temporarily for deriving other blockchain addresses
|
||||
if (floGlobals.isPrivKeySecured) {
|
||||
floGlobals._loginPassword = privateKey;
|
||||
}
|
||||
resolve(privateKey);
|
||||
getRef('private_key_field').value = '';
|
||||
routeTo('loading');
|
||||
@ -2849,6 +3020,36 @@
|
||||
<b>My Ethereum address</b>
|
||||
<sm-copy class="user-eth-id" clip-text value=${floGlobals.myEthID}></sm-copy>
|
||||
</div>
|
||||
<div class="grid gap-0-5">
|
||||
<b>My AVAX (Avalanche C-Chain) address</b>
|
||||
<sm-copy class="user-avax-id" clip-text value=${floGlobals.myAvaxID}></sm-copy>
|
||||
</div>
|
||||
<div class="grid gap-0-5">
|
||||
<b>My XRP (Ripple) address</b>
|
||||
<sm-copy class="user-xrp-id" clip-text value=${floGlobals.myXrpID || 'Not available'}></sm-copy>
|
||||
</div>
|
||||
<div class="grid gap-0-5">
|
||||
<b>My SUI address</b>
|
||||
<sm-copy class="user-sui-id" value=${floGlobals.mySuiID || 'Not available'}></sm-copy>
|
||||
</div>
|
||||
<div class="grid gap-0-5">
|
||||
<b>My TON address</b>
|
||||
<sm-copy class="user-ton-id" value=${floGlobals.myTonID || 'Not available'}></sm-copy>
|
||||
</div>
|
||||
<div class="grid gap-0-5">
|
||||
<b>My TRON address</b>
|
||||
<sm-copy class="user-tron-id" value=${floGlobals.myTronID || 'Not available'}></sm-copy>
|
||||
</div>
|
||||
<div class="grid gap-0-5">
|
||||
<b>My DOGE address</b>
|
||||
<sm-copy class="user-doge-id" value=${floGlobals.myDogeID || 'Not available'}></sm-copy>
|
||||
</div>
|
||||
${(!floGlobals.myXrpID && floGlobals.isPrivKeySecured) ? html`
|
||||
<button class="button button--primary justify-self-start" onclick=${derivePrivKeyAddresses}>
|
||||
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></svg>
|
||||
Unlock all addresses
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="button button--danger justify-self-start" onclick="signOut()">Sign out</button>
|
||||
</div>
|
||||
<div class="grid gap-1">
|
||||
|
||||
407
scripts/blockchainAddresses.js
Normal file
407
scripts/blockchainAddresses.js
Normal file
@ -0,0 +1,407 @@
|
||||
/**
|
||||
* Blockchain Address Derivation Utilities
|
||||
* Derives addresses for multiple blockchains from a single FLO/BTC WIF private key
|
||||
*
|
||||
* Supported Blockchains:
|
||||
* - XRP (Ripple) - via xrpl library
|
||||
* - SUI - via nacl + BLAKE2b
|
||||
* - TON - via nacl + TonWeb
|
||||
* - TRON - via TronWeb
|
||||
* - DOGE - via bitjs with version byte 0x1e
|
||||
*
|
||||
* Dependencies :
|
||||
* - bitjs (for WIF decoding)
|
||||
* - Crypto.util (for hex/bytes conversion)
|
||||
* - xrpl (for XRP)
|
||||
* - nacl/TweetNaCl (for SUI, TON)
|
||||
* - TonWeb (for TON)
|
||||
* - TronWeb (for TRON)
|
||||
*/
|
||||
|
||||
// BlakeJS - BLAKE2b hashing implementation for SUI
|
||||
const blakejs = (function () {
|
||||
const BLAKE2B_IV32 = new Uint32Array([
|
||||
0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, 0xfe94f82b, 0x3c6ef372,
|
||||
0x5f1d36f1, 0xa54ff53a, 0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c,
|
||||
0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19,
|
||||
]);
|
||||
const SIGMA8 = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15,
|
||||
13, 6, 1, 12, 0, 2, 11, 7, 5, 3, 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6,
|
||||
7, 1, 9, 4, 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 9, 0, 5,
|
||||
7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 2, 12, 6, 10, 0, 11, 8, 3, 4,
|
||||
13, 7, 5, 15, 14, 1, 9, 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8,
|
||||
11, 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 6, 15, 14, 9, 11,
|
||||
3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14,
|
||||
3, 12, 13, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10,
|
||||
4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
|
||||
];
|
||||
const SIGMA82 = new Uint8Array(SIGMA8.map((x) => x * 2));
|
||||
const v = new Uint32Array(32);
|
||||
const m = new Uint32Array(32);
|
||||
const parameterBlock = new Uint8Array(64);
|
||||
|
||||
function B2B_GET32(arr, i) {
|
||||
return arr[i] ^ (arr[i + 1] << 8) ^ (arr[i + 2] << 16) ^ (arr[i + 3] << 24);
|
||||
}
|
||||
function ADD64AA(v, a, b) {
|
||||
const o0 = v[a] + v[b];
|
||||
let o1 = v[a + 1] + v[b + 1];
|
||||
if (o0 >= 0x100000000) o1++;
|
||||
v[a] = o0;
|
||||
v[a + 1] = o1;
|
||||
}
|
||||
function ADD64AC(v, a, b0, b1) {
|
||||
let o0 = v[a] + b0;
|
||||
if (b0 < 0) o0 += 0x100000000;
|
||||
let o1 = v[a + 1] + b1;
|
||||
if (o0 >= 0x100000000) o1++;
|
||||
v[a] = o0;
|
||||
v[a + 1] = o1;
|
||||
}
|
||||
function B2B_G(a, b, c, d, ix, iy) {
|
||||
const x0 = m[ix],
|
||||
x1 = m[ix + 1],
|
||||
y0 = m[iy],
|
||||
y1 = m[iy + 1];
|
||||
ADD64AA(v, a, b);
|
||||
ADD64AC(v, a, x0, x1);
|
||||
let xor0 = v[d] ^ v[a],
|
||||
xor1 = v[d + 1] ^ v[a + 1];
|
||||
v[d] = xor1;
|
||||
v[d + 1] = xor0;
|
||||
ADD64AA(v, c, d);
|
||||
xor0 = v[b] ^ v[c];
|
||||
xor1 = v[b + 1] ^ v[c + 1];
|
||||
v[b] = (xor0 >>> 24) ^ (xor1 << 8);
|
||||
v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8);
|
||||
ADD64AA(v, a, b);
|
||||
ADD64AC(v, a, y0, y1);
|
||||
xor0 = v[d] ^ v[a];
|
||||
xor1 = v[d + 1] ^ v[a + 1];
|
||||
v[d] = (xor0 >>> 16) ^ (xor1 << 16);
|
||||
v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16);
|
||||
ADD64AA(v, c, d);
|
||||
xor0 = v[b] ^ v[c];
|
||||
xor1 = v[b + 1] ^ v[c + 1];
|
||||
v[b] = (xor1 >>> 31) ^ (xor0 << 1);
|
||||
v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1);
|
||||
}
|
||||
function blake2bCompress(ctx, last) {
|
||||
let i;
|
||||
for (i = 0; i < 16; i++) {
|
||||
v[i] = ctx.h[i];
|
||||
v[i + 16] = BLAKE2B_IV32[i];
|
||||
}
|
||||
v[24] = v[24] ^ ctx.t;
|
||||
v[25] = v[25] ^ (ctx.t / 0x100000000);
|
||||
if (last) {
|
||||
v[28] = ~v[28];
|
||||
v[29] = ~v[29];
|
||||
}
|
||||
for (i = 0; i < 32; i++) m[i] = B2B_GET32(ctx.b, 4 * i);
|
||||
for (i = 0; i < 12; i++) {
|
||||
B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
|
||||
B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]);
|
||||
B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]);
|
||||
B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]);
|
||||
B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]);
|
||||
B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]);
|
||||
B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]);
|
||||
B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]);
|
||||
}
|
||||
for (i = 0; i < 16; i++) ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16];
|
||||
}
|
||||
function blake2bInit(outlen, key) {
|
||||
const ctx = {
|
||||
b: new Uint8Array(128),
|
||||
h: new Uint32Array(16),
|
||||
t: 0,
|
||||
c: 0,
|
||||
outlen: outlen,
|
||||
};
|
||||
parameterBlock.fill(0);
|
||||
parameterBlock[0] = outlen;
|
||||
if (key) parameterBlock[1] = key.length;
|
||||
parameterBlock[2] = 1;
|
||||
parameterBlock[3] = 1;
|
||||
for (let i = 0; i < 16; i++)
|
||||
ctx.h[i] = BLAKE2B_IV32[i] ^ B2B_GET32(parameterBlock, i * 4);
|
||||
if (key) {
|
||||
blake2bUpdate(ctx, key);
|
||||
ctx.c = 128;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
function blake2bUpdate(ctx, input) {
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (ctx.c === 128) {
|
||||
ctx.t += ctx.c;
|
||||
blake2bCompress(ctx, false);
|
||||
ctx.c = 0;
|
||||
}
|
||||
ctx.b[ctx.c++] = input[i];
|
||||
}
|
||||
}
|
||||
function blake2bFinal(ctx) {
|
||||
ctx.t += ctx.c;
|
||||
while (ctx.c < 128) ctx.b[ctx.c++] = 0;
|
||||
blake2bCompress(ctx, true);
|
||||
const out = new Uint8Array(ctx.outlen);
|
||||
for (let i = 0; i < ctx.outlen; i++)
|
||||
out[i] = ctx.h[i >> 2] >> (8 * (i & 3));
|
||||
return out;
|
||||
}
|
||||
function blake2b(input, key, outlen) {
|
||||
outlen = outlen || 64;
|
||||
if (!(input instanceof Uint8Array)) {
|
||||
if (typeof input === "string") {
|
||||
const enc = unescape(encodeURIComponent(input));
|
||||
input = new Uint8Array(enc.length);
|
||||
for (let i = 0; i < enc.length; i++) input[i] = enc.charCodeAt(i);
|
||||
} else throw new Error("Input must be string or Uint8Array");
|
||||
}
|
||||
const ctx = blake2bInit(outlen, key);
|
||||
blake2bUpdate(ctx, input);
|
||||
return blake2bFinal(ctx);
|
||||
}
|
||||
return { blake2b: blake2b };
|
||||
})();
|
||||
|
||||
/**
|
||||
* Convert WIF private key to XRP (Ripple) address
|
||||
* Uses xrpl library with Ed25519 derivation
|
||||
* @param {string} wif - WIF format private key
|
||||
* @returns {string|null} XRP address or null on error
|
||||
*/
|
||||
function convertWIFtoXrpAddress(wif) {
|
||||
try {
|
||||
if (typeof window.xrpl === "undefined") {
|
||||
throw new Error("xrpl library not loaded");
|
||||
}
|
||||
if (typeof bitjs === "undefined") {
|
||||
throw new Error("bitjs library not loaded");
|
||||
}
|
||||
// Use bitjs.wif2privkey to decode WIF and get the raw private key hex
|
||||
const decoded = bitjs.wif2privkey(wif);
|
||||
if (!decoded || !decoded.privkey) {
|
||||
throw new Error("Failed to decode WIF private key");
|
||||
}
|
||||
// Convert hex string to byte array for xrpl
|
||||
const keyBytes = Crypto.util.hexToBytes(decoded.privkey);
|
||||
// Create XRP wallet from entropy (raw private key bytes)
|
||||
const wallet = xrpl.Wallet.fromEntropy(keyBytes);
|
||||
return wallet.address;
|
||||
} catch (error) {
|
||||
console.error("WIF to XRP conversion error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert WIF private key to SUI address
|
||||
* Uses Ed25519 keypair + BLAKE2b-256 hashing
|
||||
* @param {string} wif - WIF format private key
|
||||
* @returns {string|null} SUI address (0x prefixed) or null on error
|
||||
*/
|
||||
function convertWIFtoSuiAddress(wif) {
|
||||
try {
|
||||
if (typeof nacl === "undefined") {
|
||||
throw new Error("nacl (TweetNaCl) library not loaded");
|
||||
}
|
||||
if (typeof bitjs === "undefined") {
|
||||
throw new Error("bitjs library not loaded");
|
||||
}
|
||||
// Use bitjs.wif2privkey to decode WIF and get the raw private key hex
|
||||
const decoded = bitjs.wif2privkey(wif);
|
||||
if (!decoded || !decoded.privkey) {
|
||||
throw new Error("Failed to decode WIF private key");
|
||||
}
|
||||
// Get first 32 bytes (64 hex chars) for Ed25519 seed
|
||||
const privKeyHex = decoded.privkey.substring(0, 64);
|
||||
const privBytes = Crypto.util.hexToBytes(privKeyHex);
|
||||
const seed = new Uint8Array(privBytes.slice(0, 32));
|
||||
// Generate Ed25519 keypair from seed
|
||||
const keyPair = nacl.sign.keyPair.fromSeed(seed);
|
||||
const pubKey = keyPair.publicKey;
|
||||
// Prefix public key with 0x00 (Ed25519 scheme flag)
|
||||
const prefixedPubKey = new Uint8Array([0x00, ...pubKey]);
|
||||
// Hash with BLAKE2b-256
|
||||
const hash = blakejs.blake2b(prefixedPubKey, null, 32);
|
||||
// Convert to hex address with 0x prefix
|
||||
const suiAddress = "0x" + Crypto.util.bytesToHex(hash);
|
||||
return suiAddress;
|
||||
} catch (error) {
|
||||
console.error("WIF to SUI conversion error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert WIF private key to TON address
|
||||
* Uses Ed25519 keypair + TonWeb v4R2 wallet
|
||||
* @param {string} wif - WIF format private key
|
||||
* @returns {Promise<string|null>} TON address (bounceable format) or null on error
|
||||
*/
|
||||
async function convertWIFtoTonAddress(wif) {
|
||||
try {
|
||||
if (typeof nacl === "undefined") {
|
||||
throw new Error("nacl (TweetNaCl) library not loaded");
|
||||
}
|
||||
if (typeof TonWeb === "undefined") {
|
||||
throw new Error("TonWeb library not loaded");
|
||||
}
|
||||
if (typeof bitjs === "undefined") {
|
||||
throw new Error("bitjs library not loaded");
|
||||
}
|
||||
// Use bitjs.wif2privkey to decode WIF and get the raw private key hex
|
||||
const decoded = bitjs.wif2privkey(wif);
|
||||
if (!decoded || !decoded.privkey) {
|
||||
throw new Error("Failed to decode WIF private key");
|
||||
}
|
||||
// Get first 32 bytes (64 hex chars) for Ed25519 seed
|
||||
const privKeyHex = decoded.privkey.substring(0, 64);
|
||||
const seed = Crypto.util.hexToBytes(privKeyHex);
|
||||
// Generate Ed25519 keypair from seed
|
||||
const keyPair = nacl.sign.keyPair.fromSeed(new Uint8Array(seed));
|
||||
// Create TON wallet using TonWeb v4R2 wallet
|
||||
const tonweb = new TonWeb();
|
||||
const WalletClass = TonWeb.Wallets.all.v4R2;
|
||||
if (!WalletClass) {
|
||||
throw new Error("TonWeb v4R2 wallet not available");
|
||||
}
|
||||
const wallet = new WalletClass(tonweb.provider, {
|
||||
publicKey: keyPair.publicKey,
|
||||
});
|
||||
const address = await wallet.getAddress();
|
||||
// Return user-friendly bounceable address
|
||||
return address.toString(true, true, false);
|
||||
} catch (error) {
|
||||
console.error("WIF to TON conversion error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert WIF private key to TRON address
|
||||
* Uses TronWeb library for address derivation
|
||||
* @param {string} wif - WIF format private key
|
||||
* @returns {string|null} TRON address (Base58 format) or null on error
|
||||
*/
|
||||
function convertWIFtoTronAddress(wif) {
|
||||
try {
|
||||
if (typeof TronWeb === "undefined") {
|
||||
throw new Error("TronWeb library not loaded");
|
||||
}
|
||||
if (typeof bitjs === "undefined") {
|
||||
throw new Error("bitjs library not loaded");
|
||||
}
|
||||
// Use bitjs.wif2privkey to decode WIF and get the raw private key hex
|
||||
const decoded = bitjs.wif2privkey(wif);
|
||||
if (!decoded || !decoded.privkey) {
|
||||
throw new Error("Failed to decode WIF private key");
|
||||
}
|
||||
// Get the hex private key (64 chars)
|
||||
const privKeyHex = decoded.privkey.substring(0, 64);
|
||||
// Use TronWeb to derive address from private key
|
||||
const tronAddress = TronWeb.address.fromPrivateKey(privKeyHex);
|
||||
return tronAddress;
|
||||
} catch (error) {
|
||||
console.error("WIF to TRON conversion error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive all blockchain addresses from a WIF private key
|
||||
* @param {string} wif - WIF format private key
|
||||
* @returns {Promise<Object>} Object containing all derived addresses
|
||||
*/
|
||||
async function deriveAllBlockchainAddresses(wif) {
|
||||
const addresses = {
|
||||
xrp: null,
|
||||
sui: null,
|
||||
ton: null,
|
||||
tron: null,
|
||||
doge: null,
|
||||
};
|
||||
|
||||
try {
|
||||
addresses.xrp = convertWIFtoXrpAddress(wif);
|
||||
} catch (e) {
|
||||
console.warn("XRP derivation failed:", e);
|
||||
}
|
||||
try {
|
||||
addresses.sui = convertWIFtoSuiAddress(wif);
|
||||
} catch (e) {
|
||||
console.warn("SUI derivation failed:", e);
|
||||
}
|
||||
try {
|
||||
addresses.ton = await convertWIFtoTonAddress(wif);
|
||||
} catch (e) {
|
||||
console.warn("TON derivation failed:", e);
|
||||
}
|
||||
try {
|
||||
addresses.tron = convertWIFtoTronAddress(wif);
|
||||
} catch (e) {
|
||||
console.warn("TRON derivation failed:", e);
|
||||
}
|
||||
try {
|
||||
addresses.doge = convertWIFtoDogeAddress(wif);
|
||||
} catch (e) {
|
||||
console.warn("DOGE derivation failed:", e);
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert WIF private key to DOGE (Dogecoin) address
|
||||
* Uses secp256k1 with version byte 0x1e (30)
|
||||
* @param {string} wif - WIF format private key
|
||||
* @returns {string|null} DOGE address (Base58 format starting with 'D') or null on error
|
||||
*/
|
||||
function convertWIFtoDogeAddress(wif) {
|
||||
try {
|
||||
// Store original settings
|
||||
const origPub = bitjs.pub;
|
||||
const origPriv = bitjs.priv;
|
||||
const origBitjsCompressed = bitjs.compressed;
|
||||
|
||||
// Decode WIF to get raw private key and determine if compressed
|
||||
const decode = Bitcoin.Base58.decode(wif);
|
||||
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||
let key = keyWithVersion.slice(1);
|
||||
|
||||
let compressed = true;
|
||||
if (key.length >= 33 && key[key.length - 1] === 0x01) {
|
||||
// Compressed WIF has 0x01 suffix
|
||||
key = key.slice(0, key.length - 1);
|
||||
compressed = true;
|
||||
} else {
|
||||
compressed = false;
|
||||
}
|
||||
|
||||
const privKeyHex = Crypto.util.bytesToHex(key);
|
||||
|
||||
// Set DOGE version bytes and compression
|
||||
bitjs.pub = 0x1e;
|
||||
bitjs.priv = 0x9e;
|
||||
bitjs.compressed = compressed;
|
||||
|
||||
// Generate public key from private key
|
||||
const pubKey = bitjs.newPubkey(privKeyHex);
|
||||
// Generate DOGE address from public key
|
||||
const dogeAddress = bitjs.pubkey2address(pubKey);
|
||||
|
||||
// Restore original settings
|
||||
bitjs.pub = origPub;
|
||||
bitjs.priv = origPriv;
|
||||
bitjs.compressed = origBitjsCompressed;
|
||||
|
||||
return dogeAddress;
|
||||
} catch (error) {
|
||||
console.error("WIF to DOGE conversion error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user