feat: Implement initial Stellar Wallet application with multi-blockchain address generation and recovery.
This commit is contained in:
parent
b868a791dc
commit
4bb4d8cec7
1900
index.html
Normal file
1900
index.html
Normal file
File diff suppressed because it is too large
Load Diff
11473
lib.stellar.js
Normal file
11473
lib.stellar.js
Normal file
File diff suppressed because it is too large
Load Diff
263
stellarCrypto.js
Normal file
263
stellarCrypto.js
Normal file
@ -0,0 +1,263 @@
|
||||
(function (EXPORTS) {
|
||||
"use strict";
|
||||
const stellarCrypto = EXPORTS;
|
||||
|
||||
// Helper functions
|
||||
function hexToBytes(hex) {
|
||||
const bytes = [];
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
bytes.push(parseInt(hex.substr(i, 2), 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function bytesToHex(bytes) {
|
||||
return Array.from(bytes)
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
// Generate a new random key
|
||||
function generateNewID() {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat(),
|
||||
};
|
||||
}
|
||||
|
||||
// --- Multi-chain Generator (BTC, FLO, XLM) ---
|
||||
stellarCrypto.generateMultiChain = async function (inputWif) {
|
||||
const versions = {
|
||||
BTC: { pub: 0x00, priv: 0x80 },
|
||||
FLO: { pub: 0x23, priv: 0xa3 },
|
||||
};
|
||||
|
||||
const origBitjsPub = bitjs.pub;
|
||||
const origBitjsPriv = bitjs.priv;
|
||||
const origBitjsCompressed = bitjs.compressed;
|
||||
const origCoinJsCompressed = coinjs.compressed;
|
||||
|
||||
bitjs.compressed = true;
|
||||
coinjs.compressed = true;
|
||||
|
||||
let privKeyHex;
|
||||
let compressed = true;
|
||||
|
||||
if (typeof inputWif === "string" && inputWif.trim().length > 0) {
|
||||
const trimmedInput = inputWif.trim();
|
||||
const hexOnly = /^[0-9a-fA-F]+$/.test(trimmedInput);
|
||||
|
||||
// Check if it's a Stellar secret key (starts with 'S' and is 56 chars)
|
||||
if (trimmedInput.startsWith('S') && trimmedInput.length === 56) {
|
||||
try {
|
||||
// Decode Stellar secret key (Base32)
|
||||
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
const decoded = [];
|
||||
let bits = 0;
|
||||
let value = 0;
|
||||
|
||||
for (let i = 0; i < trimmedInput.length; i++) {
|
||||
const char = trimmedInput[i];
|
||||
const charValue = BASE32_ALPHABET.indexOf(char);
|
||||
if (charValue === -1) throw new Error('Invalid Base32 character');
|
||||
|
||||
value = (value << 5) | charValue;
|
||||
bits += 5;
|
||||
|
||||
while (bits >= 8) {
|
||||
decoded.push((value >>> (bits - 8)) & 0xFF);
|
||||
bits -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
const decodedBytes = new Uint8Array(decoded);
|
||||
// Extract seed (skip version byte 0x90, take 32 bytes, ignore checksum)
|
||||
const seed = decodedBytes.slice(1, 33);
|
||||
privKeyHex = bytesToHex(seed);
|
||||
} catch (e) {
|
||||
console.warn("Invalid Stellar secret key:", e);
|
||||
// Fall through to generate new key
|
||||
const newKey = generateNewID();
|
||||
const decode = Bitcoin.Base58.decode(newKey.privKey);
|
||||
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||
let key = keyWithVersion.slice(1);
|
||||
if (key.length >= 33 && key[key.length - 1] === 0x01)
|
||||
key = key.slice(0, key.length - 1);
|
||||
privKeyHex = bytesToHex(key);
|
||||
}
|
||||
} else if (hexOnly && (trimmedInput.length === 64 || trimmedInput.length === 128)) {
|
||||
privKeyHex =
|
||||
trimmedInput.length === 128 ? trimmedInput.substring(0, 64) : trimmedInput;
|
||||
} else {
|
||||
try {
|
||||
const decode = Bitcoin.Base58.decode(trimmedInput);
|
||||
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||
let key = keyWithVersion.slice(1);
|
||||
if (key.length >= 33 && key[key.length - 1] === 0x01) {
|
||||
key = key.slice(0, key.length - 1);
|
||||
compressed = true;
|
||||
}
|
||||
privKeyHex = bytesToHex(key);
|
||||
} catch (e) {
|
||||
console.warn("Invalid WIF, generating new key:", e);
|
||||
const newKey = generateNewID();
|
||||
const decode = Bitcoin.Base58.decode(newKey.privKey);
|
||||
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||
let key = keyWithVersion.slice(1);
|
||||
if (key.length >= 33 && key[key.length - 1] === 0x01)
|
||||
key = key.slice(0, key.length - 1);
|
||||
privKeyHex = bytesToHex(key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Generate new key if no input
|
||||
const newKey = generateNewID();
|
||||
const decode = Bitcoin.Base58.decode(newKey.privKey);
|
||||
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||
let key = keyWithVersion.slice(1);
|
||||
if (key.length >= 33 && key[key.length - 1] === 0x01)
|
||||
key = key.slice(0, key.length - 1);
|
||||
privKeyHex = bytesToHex(key);
|
||||
}
|
||||
|
||||
// --- Derive addresses for each chain ---
|
||||
const result = { BTC: {}, FLO: {}, XLM: {} };
|
||||
|
||||
// BTC
|
||||
bitjs.pub = versions.BTC.pub;
|
||||
bitjs.priv = versions.BTC.priv;
|
||||
const pubKeyBTC = bitjs.newPubkey(privKeyHex);
|
||||
result.BTC.address = coinjs.bech32Address(pubKeyBTC).address;
|
||||
result.BTC.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||
|
||||
// FLO
|
||||
bitjs.pub = versions.FLO.pub;
|
||||
bitjs.priv = versions.FLO.priv;
|
||||
const pubKeyFLO = bitjs.newPubkey(privKeyHex);
|
||||
result.FLO.address = bitjs.pubkey2address(pubKeyFLO);
|
||||
result.FLO.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||
|
||||
// XLM (Stellar)
|
||||
try {
|
||||
const privBytes = hexToBytes(privKeyHex.substring(0, 64));
|
||||
const seed = new Uint8Array(privBytes.slice(0, 32));
|
||||
|
||||
// Generate Ed25519 keypair from seed
|
||||
const keyPair = nacl.sign.keyPair.fromSeed(seed);
|
||||
const pubKey = keyPair.publicKey;
|
||||
|
||||
// Stellar address encoding: version byte (0x30 for public key 'G') + public key + CRC16-XModem checksum
|
||||
const versionByte = 0x30; // Results in 'G' prefix for public keys
|
||||
const payload = new Uint8Array([versionByte, ...pubKey]);
|
||||
|
||||
// Calculate CRC16-XModem checksum
|
||||
function crc16XModem(data) {
|
||||
let crc = 0x0000;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
crc ^= data[i] << 8;
|
||||
for (let j = 0; j < 8; j++) {
|
||||
if (crc & 0x8000) {
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
} else {
|
||||
crc = crc << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc & 0xFFFF;
|
||||
}
|
||||
|
||||
const checksum = crc16XModem(payload);
|
||||
// Checksum is stored in little-endian format
|
||||
const checksumBytes = new Uint8Array([checksum & 0xFF, (checksum >> 8) & 0xFF]);
|
||||
const addressBytes = new Uint8Array([...payload, ...checksumBytes]);
|
||||
|
||||
// Base32 encode the address (RFC 4648)
|
||||
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
let bits = 0;
|
||||
let value = 0;
|
||||
let output = '';
|
||||
|
||||
for (let i = 0; i < addressBytes.length; i++) {
|
||||
value = (value << 8) | addressBytes[i];
|
||||
bits += 8;
|
||||
|
||||
while (bits >= 5) {
|
||||
output += BASE32_ALPHABET[(value >>> (bits - 5)) & 31];
|
||||
bits -= 5;
|
||||
}
|
||||
}
|
||||
|
||||
if (bits > 0) {
|
||||
output += BASE32_ALPHABET[(value << (5 - bits)) & 31];
|
||||
}
|
||||
|
||||
const xlmAddress = output;
|
||||
|
||||
// Stellar secret key format: version byte (0x90 for secret key 'S') + seed + CRC16
|
||||
const secretVersionByte = 0x90; // Results in 'S' prefix for secret keys
|
||||
const secretPayload = new Uint8Array([secretVersionByte, ...seed]);
|
||||
const secretChecksum = crc16XModem(secretPayload);
|
||||
const secretChecksumBytes = new Uint8Array([secretChecksum & 0xFF, (secretChecksum >> 8) & 0xFF]);
|
||||
const secretKeyBytes = new Uint8Array([...secretPayload, ...secretChecksumBytes]);
|
||||
|
||||
// Base32 encode the secret key
|
||||
bits = 0;
|
||||
value = 0;
|
||||
let secretOutput = '';
|
||||
|
||||
for (let i = 0; i < secretKeyBytes.length; i++) {
|
||||
value = (value << 8) | secretKeyBytes[i];
|
||||
bits += 8;
|
||||
|
||||
while (bits >= 5) {
|
||||
secretOutput += BASE32_ALPHABET[(value >>> (bits - 5)) & 31];
|
||||
bits -= 5;
|
||||
}
|
||||
}
|
||||
|
||||
if (bits > 0) {
|
||||
secretOutput += BASE32_ALPHABET[(value << (5 - bits)) & 31];
|
||||
}
|
||||
|
||||
const xlmPrivateKey = secretOutput;
|
||||
|
||||
result.XLM.address = xlmAddress;
|
||||
result.XLM.privateKey = xlmPrivateKey;
|
||||
} catch (error) {
|
||||
console.error("Error generating XLM address:", error);
|
||||
result.XLM.address = "Error generating address";
|
||||
result.XLM.privateKey = privKeyHex;
|
||||
}
|
||||
|
||||
bitjs.pub = origBitjsPub;
|
||||
bitjs.priv = origBitjsPriv;
|
||||
bitjs.compressed = origBitjsCompressed;
|
||||
coinjs.compressed = origCoinJsCompressed;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Sign Stellar Transaction
|
||||
stellarCrypto.signXlm = async function (txBytes, xlmPrivateKey) {
|
||||
const privKeyOnly = xlmPrivateKey.substring(0, 64);
|
||||
const privBytes = hexToBytes(privKeyOnly);
|
||||
const seed = new Uint8Array(privBytes.slice(0, 32));
|
||||
|
||||
const keypair = nacl.sign.keyPair.fromSeed(seed);
|
||||
|
||||
let txData;
|
||||
if (typeof txBytes === 'string') {
|
||||
txData = new Uint8Array(atob(txBytes).split('').map(c => c.charCodeAt(0)));
|
||||
} else {
|
||||
txData = new Uint8Array(txBytes);
|
||||
}
|
||||
|
||||
const signature = nacl.sign.detached(txData, keypair.secretKey);
|
||||
|
||||
return signature;
|
||||
};
|
||||
|
||||
})("object" === typeof module ? module.exports : (window.stellarCrypto = {}));
|
||||
4
stellar_favicon.svg
Normal file
4
stellar_favicon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236.36 200">
|
||||
<path fill="#000000" d="M203,26.16l-28.46,14.5-137.43,70a82.49,82.49,0,0,1-.7-10.69A81.87,81.87,0,0,1,158.2,28.6l16.29-8.3,2.43-1.24A100,100,0,0,0,18.18,100q0,3.82.29,7.61a18.19,18.19,0,0,1-9.88,17.58L0,129.57V150l25.29-12.89,0,0,8.19-4.18,8.07-4.11v0L186.43,55l16.28-8.29,33.65-17.15V9.14Z"/>
|
||||
<path fill="#000000" d="M236.36,50,49.78,145,33.5,153.31,0,170.38v20.41l33.27-16.95,28.46-14.5L199.3,89.24A83.45,83.45,0,0,1,200,100,81.87,81.87,0,0,1,78.09,171.36l-1,.53-17.66,9A100,100,0,0,0,218.18,100c0-2.57-.1-5.14-.29-7.68a18.2,18.2,0,0,1,9.87-17.58l8.6-4.38Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 639 B |
Loading…
Reference in New Issue
Block a user