feat: Update authorization logic for compatibility with crypto_new; enhance key generation and public key format

This commit is contained in:
k1ngsterr1
2026-01-16 04:53:48 +05:00
parent 306e854646
commit caf1d246d3
7 changed files with 774 additions and 15 deletions

View File

@@ -67,18 +67,37 @@ object CryptoManager {
}
/**
* Convert seed phrase to private key (64 bytes hex string)
* Convert seed phrase to private key (32 bytes hex string)
*
* ⚠️ НОВЫЙ МЕТОД (crypto_new): Использует SHA256(seedPhrase) вместо BIP39
* Совместимо с JavaScript реализацией crypto_new/crypto.ts:
* ```js
* const privateKey = sha256.create().update(seed).digest().toHex().toString();
* ```
*/
fun seedPhraseToPrivateKey(seedPhrase: List<String>): String {
val mnemonicCode = MnemonicCode.INSTANCE
val seed = MnemonicCode.toSeed(seedPhrase, "")
// Новый метод: SHA256(seedPhrase joined by space)
val seedString = seedPhrase.joinToString(" ")
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(seedString.toByteArray(Charsets.UTF_8))
// Convert to hex string (128 characters for 64 bytes)
return seed.joinToString("") { "%02x".format(it) }
// Convert to hex string (64 characters for 32 bytes)
return hash.joinToString("") { "%02x".format(it) }
}
/**
* Generate key pair from seed phrase using secp256k1 curve
*
* ⚠️ НОВЫЙ МЕТОД (crypto_new):
* - privateKey = SHA256(seedPhrase) - 32 байта
* - publicKey = secp256k1.getPublicKey(privateKey, compressed=true) - 33 байта
*
* Совместимо с JavaScript реализацией crypto_new/crypto.ts:
* ```js
* const privateKey = sha256.create().update(seed).digest().toHex().toString();
* const publicKey = secp256k1.getPublicKey(Buffer.from(privateKey, "hex"), true);
* ```
*
* 🚀 ОПТИМИЗАЦИЯ: Кэшируем результаты для избежания повторных вычислений
*/
fun generateKeyPairFromSeed(seedPhrase: List<String>): KeyPairData {
@@ -87,23 +106,27 @@ object CryptoManager {
// Проверяем кэш
keyPairCache[cacheKey]?.let { return it }
// Генерируем приватный ключ через SHA256
val privateKeyHex = seedPhraseToPrivateKey(seedPhrase)
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
// Use first 32 bytes of private key for secp256k1
val privateKeyBytes = privateKeyHex.take(64).chunked(2)
// Преобразуем hex в bytes (32 байта)
val privateKeyBytes = privateKeyHex.chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
val privateKeyBigInt = BigInteger(1, privateKeyBytes)
// Generate public key from private key
// Генерируем публичный ключ из приватного
val publicKeyPoint = ecSpec.g.multiply(privateKeyBigInt)
val publicKeyHex = publicKeyPoint.getEncoded(false)
// ⚡ ВАЖНО: Используем СЖАТЫЙ формат (compressed=true) - 33 байта вместо 65
// Это совместимо с crypto_new где используется: secp256k1.getPublicKey(..., true)
val publicKeyHex = publicKeyPoint.getEncoded(true)
.joinToString("") { "%02x".format(it) }
val keyPair = KeyPairData(
privateKey = privateKeyHex.take(64),
privateKey = privateKeyHex,
publicKey = publicKeyHex
)

View File

@@ -81,6 +81,9 @@ class AppleEmojiEditTextView @JvmOverloads constructor(
"[\\x{3030}]|[\\x{303D}]|" +
"[\\x{3297}]|[\\x{3299}]"
)
// 🔥 Паттерн для :emoji_XXXX: формата (как в десктопе)
val EMOJI_CODE_PATTERN: Pattern = Pattern.compile(":emoji_([a-fA-F0-9_-]+):")
// Кэш для bitmap и drawable
private val bitmapCache = LruCache<String, Bitmap>(500)
@@ -128,19 +131,46 @@ class AppleEmojiEditTextView @JvmOverloads constructor(
try {
val textStr = editable.toString()
val matcher = EMOJI_PATTERN.matcher(textStr)
val cursorPosition = selectionStart
// 🔥 Собираем все позиции эмодзи (и Unicode, и :emoji_code:)
data class EmojiMatch(val start: Int, val end: Int, val unified: String, val isCodeFormat: Boolean)
val emojiMatches = mutableListOf<EmojiMatch>()
// 1. Ищем :emoji_XXXX: формат
val codeMatcher = EMOJI_CODE_PATTERN.matcher(textStr)
while (codeMatcher.find()) {
val unified = codeMatcher.group(1) ?: continue
emojiMatches.add(EmojiMatch(codeMatcher.start(), codeMatcher.end(), unified, true))
}
// 2. Ищем реальные Unicode эмодзи
val matcher = EMOJI_PATTERN.matcher(textStr)
while (matcher.find()) {
val emoji = matcher.group()
val start = matcher.start()
val end = matcher.end()
// Проверяем что этот диапазон не перекрывается с :emoji_XXXX:
val overlaps = emojiMatches.any {
(start >= it.start && start < it.end) ||
(end > it.start && end <= it.end)
}
if (!overlaps) {
emojiMatches.add(EmojiMatch(start, end, emojiToUnified(emoji), false))
}
}
// 3. Обрабатываем все найденные эмодзи
for (match in emojiMatches) {
val start = match.start
val end = match.end
// Проверяем, есть ли уже ImageSpan
val existingSpans = editable.getSpans(start, end, ImageSpan::class.java)
if (existingSpans.isNotEmpty()) continue
val unified = emojiToUnified(emoji)
val unified = match.unified
var drawable = drawableCache.get(unified)
if (drawable == null) {

View File

@@ -522,7 +522,8 @@ fun EmojiButton(
interactionSource = interactionSource,
indication = null
) {
onClick(unifiedToEmoji(unified))
// 🔥 Отправляем эмодзи в формате :emoji_code: как в десктопе
onClick(":emoji_$unified:")
},
contentAlignment = Alignment.Center
) {

View File

@@ -391,7 +391,8 @@ private fun OptimizedEmojiButton(
indication = null, // 🚀 Убираем ripple
onClickLabel = "Select emoji"
) {
onClick(unifiedToEmoji(unified))
// 🔥 Отправляем эмодзи в формате :emoji_code: как в десктопе
onClick(":emoji_$unified:")
},
contentAlignment = Alignment.Center
) {