Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-02 10:37:01 +05:00
parent adb8c50ae2
commit dd95bf238a
7 changed files with 189 additions and 0 deletions

View file

@ -0,0 +1,31 @@
package com.tangem.lib.crypto.derivation
import com.tangem.common.card.EllipticCurve
import com.tangem.crypto.hdWallet.DerivationPath
/**
* Checks whether this [EllipticCurve] is able to derive the given [path].
*
* `ed25519` and `ed25519_slip0010` support hardened derivation only (SLIP-0010), so any path that contains a
* non-hardened node cannot produce a key/address for them. This is the root cause of the "custom token added without
* an address" bug: e.g. an Algorand (ed25519) token with an EVM derivation path like `m/44'/60'/0'/0/0`.
*
* `secp256k1`, `secp256r1` and `bip0340` support both hardened and non-hardened derivation, so any path is fine.
*
* BLS curves do not support derivation at all.
*/
fun EllipticCurve.supportsDerivationPath(path: DerivationPath): Boolean {
return when (this) {
EllipticCurve.Ed25519,
EllipticCurve.Ed25519Slip0010,
-> path.nodes.all { it.isHardened }
EllipticCurve.Secp256k1,
EllipticCurve.Secp256r1,
EllipticCurve.Bip0340,
-> true
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Bls12381G2Pop,
-> false
}
}

View file

@ -0,0 +1,68 @@
package com.tangem.lib.crypto.derivation
import com.google.common.truth.Truth
import com.tangem.common.card.EllipticCurve
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.test.core.ProvideTestModels
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class EllipticCurveDerivationSupportTest {
@ParameterizedTest
@ProvideTestModels
fun supportsDerivationPath(model: TestModel) {
// Arrange
val path = DerivationPath(rawPath = model.derivationPath)
// Act
val actual = model.curve.supportsDerivationPath(path)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels() = provideEd25519Models() +
provideSecpModels() +
provideBlsModels()
private fun provideEd25519Models() = listOf(
// region ed25519-family require a fully-hardened path
// Algorand default path — fully hardened
TestModel(curve = EllipticCurve.Ed25519, derivationPath = "m/44'/283'/0'/0'/0'", expected = true),
TestModel(curve = EllipticCurve.Ed25519Slip0010, derivationPath = "m/44'/283'/0'/0'/0'", expected = true),
// The reported bug: Algorand (ed25519) + ApeChain/EVM path with non-hardened tail
TestModel(curve = EllipticCurve.Ed25519, derivationPath = "m/44'/60'/0'/0/0", expected = false),
TestModel(curve = EllipticCurve.Ed25519Slip0010, derivationPath = "m/44'/60'/0'/0/0", expected = false),
// Solana default path — fully hardened
TestModel(curve = EllipticCurve.Ed25519Slip0010, derivationPath = "m/44'/501'/0'/0'", expected = true),
// A single non-hardened node is enough to make it unsupported
TestModel(curve = EllipticCurve.Ed25519, derivationPath = "m/44'/283'/0'/0'/0", expected = false),
// endregion
)
private fun provideSecpModels() = listOf(
// region secp256k1 / secp256r1 / bip0340 accept any path
TestModel(curve = EllipticCurve.Secp256k1, derivationPath = "m/44'/60'/0'/0/0", expected = true),
TestModel(curve = EllipticCurve.Secp256k1, derivationPath = "m/44'/0'/0'/0/0", expected = true),
TestModel(curve = EllipticCurve.Secp256k1, derivationPath = "m/44'/283'/0'/0'/0'", expected = true),
TestModel(curve = EllipticCurve.Secp256r1, derivationPath = "m/44'/60'/0'/0/0", expected = true),
TestModel(curve = EllipticCurve.Bip0340, derivationPath = "m/44'/60'/0'/0/0", expected = true),
// endregion
)
private fun provideBlsModels() = listOf(
// region BLS curves do not support derivation at all
TestModel(curve = EllipticCurve.Bls12381G2, derivationPath = "m/44'/60'/0'/0/0", expected = false),
TestModel(curve = EllipticCurve.Bls12381G2Aug, derivationPath = "m/44'/60'/0'/0'/0'", expected = false),
TestModel(curve = EllipticCurve.Bls12381G2Pop, derivationPath = "m/44'/60'/0'/0'/0'", expected = false),
// endregion
)
data class TestModel(
val curve: EllipticCurve,
val derivationPath: String,
val expected: Boolean,
)
}