Files
cryptowallet/apps/api/src/lib/address-validators.ts

88 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Chain-specific address validators с CHECKSUM проверкой.
* Принципиально: regex/length недостаточно — TRX/BTC используют base58check,
* один испорченный символ может пройти regex, но кошелёк по такому адресу
* не восстановим → funds permanently lost.
*/
import { ethers } from 'ethers';
import { createHash } from 'crypto';
import bs58 from 'bs58';
import * as bitcoin from 'bitcoinjs-lib';
import { PublicKey } from '@solana/web3.js';
const TRX_RE = /^T[1-9A-HJ-NP-Za-km-z]{33}$/;
export type ChainCode = 'ETH' | 'BTC' | 'SOL' | 'TRX' | 'BSC';
export function isValidAddress(chain: ChainCode, address: string): boolean {
// Любой блокчейн-адрес помещается в ~64 chars. 256 был оверкилл и open vector
// для DoS (тратим CPU на bs58.decode 200-char garbage).
if (typeof address !== 'string' || address.length === 0 || address.length > 64) return false;
switch (chain) {
case 'BTC':
return isValidBtcAddress(address);
case 'TRX':
return isValidTrxAddress(address);
case 'ETH':
case 'BSC':
return ethers.utils.isAddress(address);
case 'SOL':
return isValidSolAddress(address);
default:
return false;
}
}
// ── BTC: bitcoinjs-lib проверяет version byte + checksum (P2PKH/P2SH/bech32) ──
function isValidBtcAddress(address: string): boolean {
try {
bitcoin.address.toOutputScript(address, bitcoin.networks.bitcoin);
return true;
} catch {
return false;
}
}
// ── TRX: base58check + первый байт 0x41 ──
function isValidTrxAddress(address: string): boolean {
if (!TRX_RE.test(address)) return false;
let decoded: Uint8Array;
try {
decoded = bs58.decode(address);
} catch {
return false;
}
if (decoded.length !== 25) return false; // 1 prefix + 20 payload + 4 checksum
if (decoded[0] !== 0x41) return false; // TRX mainnet prefix
const payload = decoded.subarray(0, 21);
const checksum = decoded.subarray(21);
const h1 = createHash('sha256').update(payload).digest();
const h2 = createHash('sha256').update(h1).digest();
for (let i = 0; i < 4; i++) {
if (h2[i] !== checksum[i]) return false;
}
return true;
}
// ── SOL: реальное base58-декодирование через PublicKey ──
function isValidSolAddress(address: string): boolean {
try {
const pk = new PublicKey(address);
// PublicKey принимает 32-байтовое значение; isOnCurve дополнительный sanity
return pk.toBytes().length === 32;
} catch {
return false;
}
}
export function isValidAmount(amount: string): boolean {
if (typeof amount !== 'string' || amount.length === 0 || amount.length > 78) return false;
if (!/^\d+$/.test(amount)) return false;
try {
return BigInt(amount) > 0n;
} catch {
return false;
}
}