fix(android): decode Base64 signature before broadcasting transaction

The TSS native bridge returns signatures in Base64 format, but the
broadcast function expected hex format. Added Base64 decoding in
broadcastTransaction() to properly parse r, s, v components.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-01 22:44:44 -08:00
parent 2365a50b1b
commit b3822e48eb
2 changed files with 36 additions and 12 deletions

View File

@ -556,7 +556,8 @@
"Bash(adb logcat:*)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(android\\): add 5-minute polling timeout mechanism for keygen/sign\n\nImplements Electron''s checkAndTriggerKeygen\\(\\) polling fallback:\n- Adds polling every 2 seconds with 5-minute timeout\n- Triggers keygen/sign via synthetic session_started event on in_progress status\n- Handles gRPC stream disconnection when app goes to background\n- Shows timeout error in UI via existing error mechanism\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(go list:*)",
"Bash(adb install:*)"
"Bash(adb install:*)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(tss\\): add real-time round progress from msg.Type\\(\\) parsing\n\nExtract current round number from tss-lib message type string using\nregex pattern `Round\\(\\\\d+\\)`. This enables real-time progress updates\n\\(1/4, 2/4... for keygen, 1/9, 2/9... for signing\\) instead of only\nshowing completion status.\n\nChanges across all three platforms:\n- tss-wasm/main.go: Add extractRoundFromMessageType\\(\\) and call\n OnProgress with parsed round on each outgoing message\n- service-party-android/tsslib/tsslib.go: Same implementation for\n Android gomobile binding\n- service-party-app/tss-party/main.go: Same implementation for\n Electron subprocess, with isKeygen parameter to distinguish\n keygen \\(4 rounds\\) vs signing \\(9 rounds\\)\n\nSafe fallback: Returns 0 if parsing fails, which doesn''t affect\nprotocol execution - only UI display.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
],
"deny": [],
"ask": []

View File

@ -2259,20 +2259,43 @@ class TssRepository @Inject constructor(
): Result<String> {
return withContext(Dispatchers.IO) {
try {
// Parse signature (format: 0x + r(64) + s(64) + v(2))
val sigHex = signature.removePrefix("0x")
if (sigHex.length != 130) {
return@withContext Result.failure(Exception("Invalid signature length"))
android.util.Log.d("TssRepository", "[BROADCAST] Input signature: ${signature.take(40)}...")
android.util.Log.d("TssRepository", "[BROADCAST] Signature length: ${signature.length}")
// Signature from TSS is Base64 encoded, need to decode and convert to hex
// Format after decode: r (32 bytes) + s (32 bytes) + v (1 byte) = 65 bytes
val sigBytes = try {
android.util.Base64.decode(signature, android.util.Base64.NO_WRAP)
} catch (e: Exception) {
// If not Base64, try to parse as hex directly
android.util.Log.d("TssRepository", "[BROADCAST] Not Base64, trying hex...")
signature.removePrefix("0x").hexToByteArray()
}
val rHex = sigHex.substring(0, 64)
val sHex = sigHex.substring(64, 128)
val vHex = sigHex.substring(128, 130)
android.util.Log.d("TssRepository", "[BROADCAST] Decoded signature bytes: ${sigBytes.size}")
val r = rHex.hexToByteArray()
val s = sHex.hexToByteArray()
val v = vHex.toInt(16)
val recoveryId = if (v >= 27) v - 27 else v
if (sigBytes.size != 65) {
return@withContext Result.failure(Exception("Invalid signature length: ${sigBytes.size} bytes, expected 65"))
}
// Extract r, s, v from decoded bytes
val rBytes = sigBytes.copyOfRange(0, 32)
val sBytes = sigBytes.copyOfRange(32, 64)
val vByte = sigBytes[64].toInt() and 0xFF
// Convert to hex for logging
val rHex = rBytes.joinToString("") { "%02x".format(it) }
val sHex = sBytes.joinToString("") { "%02x".format(it) }
android.util.Log.d("TssRepository", "[BROADCAST] r: ${rHex.take(16)}...")
android.util.Log.d("TssRepository", "[BROADCAST] s: ${sHex.take(16)}...")
android.util.Log.d("TssRepository", "[BROADCAST] v: $vByte")
val recoveryId = if (vByte >= 27) vByte - 27 else vByte
// Use byte arrays directly for finalizeTransaction
val r = rBytes
val s = sBytes
// Finalize transaction with signature
val signedTx = TransactionUtils.finalizeTransaction(