fix: Tron transfers signed with the wrong key (or crashed) due to missing multi-chain-encryption case

SecretsSigner.multiChainEncryptionFor() only recognized substrate accounts, standard
Ethereum accounts, and explicit per-chain override accounts. A Tron account's accountId
matches none of those (same secp256k1 scheme as Ethereum, but a different SLIP-44 derivation
path/keypair), so it fell through to null and crashed on the `!!` - found live testing the
send flow end-to-end, reproduced on every Confirm tap.

Fixing just the null case would have signed Tron transfers with the Ethereum keypair
instead of the Tron one (getMetaAccountKeypair only distinguished ethereum/substrate),
producing an invalid signature. Threaded a proper isTronBased flag through
down to mapMetaAccountSecretsToKeypair so Tron gets its own MetaAccountSecrets.TronKeypair.
This commit is contained in:
2026-07-11 19:20:02 -07:00
parent e1d199931c
commit bc9087136c
2 changed files with 22 additions and 8 deletions
@@ -142,11 +142,13 @@ class SecretsSigner(
private suspend fun getKeypair(accountId: AccountId): Keypair {
val chainsById = chainRegistry.chainsById()
val multiChainEncryption = metaAccount.multiChainEncryptionFor(accountId, chainsById)!!
val isTronBased = metaAccount.tronAddress?.contentEquals(accountId) == true
return secretStoreV2.getKeypair(
metaAccount = metaAccount,
accountId = accountId,
isEthereumBased = multiChainEncryption is MultiChainEncryption.Ethereum
isEthereumBased = multiChainEncryption is MultiChainEncryption.Ethereum,
isTronBased = isTronBased
)
}
@@ -159,11 +161,12 @@ class SecretsSigner(
private suspend fun SecretStoreV2.getKeypair(
metaAccount: MetaAccount,
accountId: AccountId,
isEthereumBased: Boolean
isEthereumBased: Boolean,
isTronBased: Boolean = false,
) = if (hasChainSecrets(metaAccount.id, accountId)) {
getChainAccountKeypair(metaAccount.id, accountId)
} else {
getMetaAccountKeypair(metaAccount.id, isEthereumBased)
getMetaAccountKeypair(metaAccount.id, isEthereumBased, isTronBased)
}
/**
@@ -173,6 +176,10 @@ class SecretsSigner(
return when {
substrateAccountId.contentEquals(accountId) -> substrateCryptoType?.let(MultiChainEncryption.Companion::substrateFrom)
ethereumAccountId().contentEquals(accountId) -> MultiChainEncryption.Ethereum
// Tron reuses the exact same secp256k1 signing scheme as Ethereum - it just has its own accountId
// (different SLIP-44 derivation path), so it doesn't match ethereumAccountId() above and was
// falling through to the chainAccounts lookup, which doesn't cover it either -> null -> NPE on `!!`.
tronAddress?.contentEquals(accountId) == true -> MultiChainEncryption.Ethereum
else -> {
val chainAccount = chainAccounts.values.firstOrNull { it.accountId.contentEquals(accountId) } ?: return null
val cryptoType = chainAccount.cryptoType ?: return null