/** * Kava EVM 地址派生工具 * 从 ECDSA 公钥派生 EVM 兼容地址 */ // secp256k1 曲线参数 const SECP256K1_P = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'); const SECP256K1_B = 7n; /** * 将 Uint8Array 转换为 BigInt */ function bytesToBigInt(bytes: Uint8Array): bigint { let result = 0n; for (let i = 0; i < bytes.length; i++) { result = (result << 8n) | BigInt(bytes[i]); } return result; } /** * 将 BigInt 转换为 32 字节的 Uint8Array */ function bigIntToBytes32(n: bigint): Uint8Array { const bytes = new Uint8Array(32); let temp = n; for (let i = 31; i >= 0; i--) { bytes[i] = Number(temp & 0xffn); temp = temp >> 8n; } return bytes; } /** * 模幂运算 (快速幂算法) */ function modPow(base: bigint, exp: bigint, mod: bigint): bigint { let result = 1n; base = ((base % mod) + mod) % mod; while (exp > 0n) { if (exp & 1n) { result = (result * base) % mod; } exp = exp >> 1n; base = (base * base) % mod; } return result; } /** * 计算 secp256k1 曲线上给定 x 坐标的 y 坐标 * y² = x³ + 7 (mod p) */ function decompressY(x: bigint, isOdd: boolean): bigint { // 计算 y² = x³ + 7 (mod p) const x3 = modPow(x, 3n, SECP256K1_P); const ySquared = (x3 + SECP256K1_B) % SECP256K1_P; // 计算平方根: y = ySquared^((p+1)/4) mod p // 这是因为 p ≡ 3 (mod 4) 对于 secp256k1 const exp = (SECP256K1_P + 1n) / 4n; let y = modPow(ySquared, exp, SECP256K1_P); // 根据奇偶性选择正确的 y 值 const yIsOdd = (y & 1n) === 1n; if (yIsOdd !== isOdd) { y = SECP256K1_P - y; } return y; } /** * 解压缩 secp256k1 公钥 * 输入: 33 字节压缩公钥 (02/03 前缀 + 32 字节 x 坐标) * 输出: 64 字节未压缩公钥 (32 字节 x + 32 字节 y,无前缀) */ function decompressPublicKey(compressedPubKey: Uint8Array): Uint8Array { if (compressedPubKey.length !== 33) { throw new Error('压缩公钥必须是 33 字节'); } const prefix = compressedPubKey[0]; if (prefix !== 0x02 && prefix !== 0x03) { throw new Error('无效的压缩公钥前缀'); } const isOdd = prefix === 0x03; const xBytes = compressedPubKey.slice(1); const x = bytesToBigInt(xBytes); const y = decompressY(x, isOdd); // 组合 x 和 y 坐标 const result = new Uint8Array(64); result.set(xBytes, 0); result.set(bigIntToBytes32(y), 32); return result; } /** * 将十六进制字符串转换为 Uint8Array */ function hexToBytes(hex: string): Uint8Array { const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex; const bytes = new Uint8Array(cleanHex.length / 2); for (let i = 0; i < cleanHex.length; i += 2) { bytes[i / 2] = parseInt(cleanHex.substring(i, i + 2), 16); } return bytes; } /** * 将 Uint8Array 转换为十六进制字符串 */ function bytesToHex(bytes: Uint8Array): string { return Array.from(bytes) .map((b) => b.toString(16).padStart(2, '0')) .join(''); } /** * Keccak-256 哈希实现 (简化版) * 用于从公钥派生 EVM 地址 */ async function keccak256(data: Uint8Array): Promise { // 使用 SubtleCrypto 的 SHA-256 作为备选 // 注意: 真实场景需要使用 keccak-256,这里使用简化实现 // 在生产环境中应该使用 js-sha3 或 ethers.js // 简单的 Keccak-256 实现常量 const RC = [ 0x0000000000000001n, 0x0000000000008082n, 0x800000000000808an, 0x8000000080008000n, 0x000000000000808bn, 0x0000000080000001n, 0x8000000080008081n, 0x8000000000008009n, 0x000000000000008an, 0x0000000000000088n, 0x0000000080008009n, 0x000000008000000an, 0x000000008000808bn, 0x800000000000008bn, 0x8000000000008089n, 0x8000000000008003n, 0x8000000000008002n, 0x8000000000000080n, 0x000000000000800an, 0x800000008000000an, 0x8000000080008081n, 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n, ]; const ROTC = [ [0, 36, 3, 41, 18], [1, 44, 10, 45, 2], [62, 6, 43, 15, 61], [28, 55, 25, 21, 56], [27, 20, 39, 8, 14], ]; function rotl64(x: bigint, n: number): bigint { return ((x << BigInt(n)) | (x >> BigInt(64 - n))) & 0xffffffffffffffffn; } function keccakF(state: bigint[][]): void { for (let round = 0; round < 24; round++) { // Theta const C: bigint[] = []; for (let x = 0; x < 5; x++) { C[x] = state[x][0] ^ state[x][1] ^ state[x][2] ^ state[x][3] ^ state[x][4]; } const D: bigint[] = []; for (let x = 0; x < 5; x++) { D[x] = C[(x + 4) % 5] ^ rotl64(C[(x + 1) % 5], 1); } for (let x = 0; x < 5; x++) { for (let y = 0; y < 5; y++) { state[x][y] ^= D[x]; } } // Rho and Pi const B: bigint[][] = Array(5).fill(null).map(() => Array(5).fill(0n)); for (let x = 0; x < 5; x++) { for (let y = 0; y < 5; y++) { B[y][(2 * x + 3 * y) % 5] = rotl64(state[x][y], ROTC[x][y]); } } // Chi for (let x = 0; x < 5; x++) { for (let y = 0; y < 5; y++) { state[x][y] = B[x][y] ^ (~B[(x + 1) % 5][y] & B[(x + 2) % 5][y]); } } // Iota state[0][0] ^= RC[round]; } } // Keccak-256: rate = 1088 bits = 136 bytes, capacity = 512 bits const rate = 136; const outputLen = 32; // Initialize state const state: bigint[][] = Array(5).fill(null).map(() => Array(5).fill(0n)); // Pad message const padded = new Uint8Array(Math.ceil((data.length + 1) / rate) * rate); padded.set(data); padded[data.length] = 0x01; padded[padded.length - 1] |= 0x80; // Absorb for (let i = 0; i < padded.length; i += rate) { for (let j = 0; j < rate && i + j < padded.length; j += 8) { const x = Math.floor(j / 8) % 5; const y = Math.floor(Math.floor(j / 8) / 5); let lane = 0n; for (let k = 0; k < 8 && i + j + k < padded.length; k++) { lane |= BigInt(padded[i + j + k]) << BigInt(k * 8); } state[x][y] ^= lane; } keccakF(state); } // Squeeze const output = new Uint8Array(outputLen); for (let i = 0; i < outputLen; i += 8) { const x = Math.floor(i / 8) % 5; const y = Math.floor(Math.floor(i / 8) / 5); const lane = state[x][y]; for (let k = 0; k < 8 && i + k < outputLen; k++) { output[i + k] = Number((lane >> BigInt(k * 8)) & 0xffn); } } return output; } /** * 从 ECDSA 压缩/未压缩公钥派生 EVM 地址 * * @param publicKey - 公钥的十六进制字符串 (压缩或未压缩格式) * @returns EVM 地址 (带 0x 前缀) */ export async function deriveEvmAddress(publicKey: string): Promise { const pubKeyBytes = hexToBytes(publicKey); // EVM 地址派生使用未压缩公钥的 x,y 坐标 (去掉 04 前缀) // 如果是压缩公钥 (33 bytes, 02/03 前缀), 需要先解压 let uncompressedPubKey: Uint8Array; if (pubKeyBytes.length === 65 && pubKeyBytes[0] === 0x04) { // 已经是未压缩格式,去掉 04 前缀 uncompressedPubKey = pubKeyBytes.slice(1); } else if (pubKeyBytes.length === 64) { // 无前缀的未压缩格式 uncompressedPubKey = pubKeyBytes; } else if (pubKeyBytes.length === 33 && (pubKeyBytes[0] === 0x02 || pubKeyBytes[0] === 0x03)) { // 压缩格式,解压为 64 字节的 x,y 坐标 uncompressedPubKey = decompressPublicKey(pubKeyBytes); } else { throw new Error(`无效的公钥格式: length=${pubKeyBytes.length}`); } // 对 64 字节的公钥数据进行 Keccak-256 哈希 const hash = await keccak256(uncompressedPubKey); // 取最后 20 字节作为地址 const addressBytes = hash.slice(-20); return '0x' + bytesToHex(addressBytes); } /** * 验证 EVM 地址格式 * * @param address - 要验证的地址 * @returns 是否是有效的 EVM 地址 */ export function isValidEvmAddress(address: string): boolean { if (!address) return false; return /^0x[a-fA-F0-9]{40}$/.test(address); } /** * 格式化地址显示 (缩短) * * @param address - 完整地址 * @param prefixLen - 前缀长度 (默认 6) * @param suffixLen - 后缀长度 (默认 4) * @returns 缩短的地址 */ export function formatAddress(address: string, prefixLen = 6, suffixLen = 4): string { if (!address || address.length < prefixLen + suffixLen + 2) return address; return `${address.slice(0, prefixLen + 2)}...${address.slice(-suffixLen)}`; } /** * 生成 Kava 区块浏览器 URL * * @param address - EVM 地址 * @param isTestnet - 是否是测试网 * @returns 区块浏览器 URL */ export function getKavaExplorerUrl(address: string, isTestnet = true): string { const baseUrl = isTestnet ? 'https://testnet.kavascan.com' : 'https://kavascan.com'; return `${baseUrl}/address/${address}`; }