fix: Implement robust checksum validation for Stellar secret keys and WIF keys, and refactor the CRC16-XModem function for shared use.

This commit is contained in:
void-57 2025-12-17 18:25:56 +05:30
parent 399acbeccf
commit c1c7f30c3b

View File

@ -28,6 +28,22 @@
}; };
} }
// Calculate CRC16-XModem checksum (shared function)
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;
}
// --- Multi-chain Generator (BTC, FLO, XLM) --- // --- Multi-chain Generator (BTC, FLO, XLM) ---
stellarCrypto.generateMultiChain = async function (inputWif) { stellarCrypto.generateMultiChain = async function (inputWif) {
const versions = { const versions = {
@ -74,19 +90,35 @@
} }
const decodedBytes = new Uint8Array(decoded); const decodedBytes = new Uint8Array(decoded);
// Extract seed (skip version byte 0x90, take 32 bytes, ignore checksum)
// Validate checksum
if (decodedBytes.length < 35) {
throw new Error('Invalid Stellar secret key: too short');
}
// Extract components: [version(1)] + [seed(32)] + [checksum(2)]
const payload = decodedBytes.slice(0, 33); // version + seed
const providedChecksum = (decodedBytes[34] << 8) | decodedBytes[33]; // little-endian
// Calculate expected checksum
const expectedChecksum = crc16XModem(payload);
// Verify checksum matches
if (providedChecksum !== expectedChecksum) {
throw new Error(`Invalid Stellar secret key: checksum mismatch (expected ${expectedChecksum.toString(16)}, got ${providedChecksum.toString(16)})`);
}
// Verify version byte
if (decodedBytes[0] !== 0x90) {
throw new Error(`Invalid Stellar secret key: wrong version byte (expected 0x90, got 0x${decodedBytes[0].toString(16)})`);
}
// Extract seed (skip version byte, take 32 bytes)
const seed = decodedBytes.slice(1, 33); const seed = decodedBytes.slice(1, 33);
privKeyHex = bytesToHex(seed); privKeyHex = bytesToHex(seed);
} catch (e) { } catch (e) {
console.warn("Invalid Stellar secret key:", e); console.error("Invalid Stellar secret key:", e.message);
// Fall through to generate new key throw new Error(`Failed to recover Stellar secret key: ${e.message}`);
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)) { } else if (hexOnly && (trimmedInput.length === 64 || trimmedInput.length === 128)) {
privKeyHex = privKeyHex =
@ -94,6 +126,36 @@
} else { } else {
try { try {
const decode = Bitcoin.Base58.decode(trimmedInput); const decode = Bitcoin.Base58.decode(trimmedInput);
// Validate WIF checksum
if (decode.length < 37) {
throw new Error('Invalid WIF key: too short');
}
// WIF format: [version(1)] + [private_key(32)] + [compression_flag(0-1)] + [checksum(4)]
const payload = decode.slice(0, decode.length - 4);
const providedChecksum = decode.slice(decode.length - 4);
// Calculate expected checksum using double SHA256
const hash1 = Crypto.SHA256(payload, { asBytes: true });
const hash2 = Crypto.SHA256(hash1, { asBytes: true });
const expectedChecksum = hash2.slice(0, 4);
// Verify checksum matches
let checksumMatch = true;
for (let i = 0; i < 4; i++) {
if (providedChecksum[i] !== expectedChecksum[i]) {
checksumMatch = false;
break;
}
}
if (!checksumMatch) {
const providedHex = providedChecksum.map(b => b.toString(16).padStart(2, '0')).join('');
const expectedHex = expectedChecksum.map(b => b.toString(16).padStart(2, '0')).join('');
throw new Error(`Invalid WIF key: checksum mismatch (expected ${expectedHex}, got ${providedHex})`);
}
const keyWithVersion = decode.slice(0, decode.length - 4); const keyWithVersion = decode.slice(0, decode.length - 4);
let key = keyWithVersion.slice(1); let key = keyWithVersion.slice(1);
if (key.length >= 33 && key[key.length - 1] === 0x01) { if (key.length >= 33 && key[key.length - 1] === 0x01) {
@ -102,14 +164,8 @@
} }
privKeyHex = bytesToHex(key); privKeyHex = bytesToHex(key);
} catch (e) { } catch (e) {
console.warn("Invalid WIF, generating new key:", e); console.error("Invalid WIF key:", e.message);
const newKey = generateNewID(); throw new Error(`Failed to recover from WIF key: ${e.message}`);
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 { } else {
@ -153,22 +209,6 @@
const versionByte = 0x30; // Results in 'G' prefix for public keys const versionByte = 0x30; // Results in 'G' prefix for public keys
const payload = new Uint8Array([versionByte, ...pubKey]); 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); const checksum = crc16XModem(payload);
// Checksum is stored in little-endian format // Checksum is stored in little-endian format
const checksumBytes = new Uint8Array([checksum & 0xFF, (checksum >> 8) & 0xFF]); const checksumBytes = new Uint8Array([checksum & 0xFF, (checksum >> 8) & 0xFF]);