263 lines
8.8 KiB
JavaScript
263 lines
8.8 KiB
JavaScript
(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 = {})); |