Updated on 2026-08-14
This commit is contained in:
parent
e3b857b79a
commit
02e44033c9
18 changed files with 266 additions and 316 deletions
|
|
@ -32,8 +32,7 @@ dependencies {
|
|||
// endregion
|
||||
|
||||
// region Test libraries
|
||||
testImplementation(deps.test.junit5)
|
||||
testImplementation(projects.test.core)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.truth)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -25,17 +25,15 @@ class AccountNodeRecognizer(private val blockchain: Blockchain) {
|
|||
val nodesCount = derivationPath.nodes.size
|
||||
|
||||
val index = when {
|
||||
blockchain == Blockchain.Tezos -> {
|
||||
UTXO_BLOCKCHAIN_NODE_INDEX.takeIf { nodesCount == 4 }
|
||||
}
|
||||
blockchain == Blockchain.Quai || blockchain.isUTXO -> {
|
||||
UTXO_BLOCKCHAIN_NODE_INDEX.takeIf { nodesCount == 5 }
|
||||
}
|
||||
!blockchain.isUTXO -> {
|
||||
(nodesCount - 1).takeIf { nodesCount == 3 || nodesCount == 5 }
|
||||
}
|
||||
else -> null
|
||||
// region Ethereum-like blockchains
|
||||
blockchain == Blockchain.Tezos -> ACCOUNT_NODE_INDEX
|
||||
blockchain == Blockchain.Quai -> ACCOUNT_NODE_INDEX
|
||||
blockchain.isEvm() -> ADDRESS_INDEX_NODE_INDEX
|
||||
// endregion
|
||||
blockchain.isUTXO -> ACCOUNT_NODE_INDEX
|
||||
else -> ACCOUNT_NODE_INDEX
|
||||
}
|
||||
.takeIf { nodesCount >= it }
|
||||
|
||||
if (index == null) {
|
||||
Timber.e("Cannot determine account node index for ${blockchain.fullName}: ${derivationPath.rawPath}")
|
||||
|
|
@ -251,6 +249,7 @@ class AccountNodeRecognizer(private val blockchain: Blockchain) {
|
|||
}
|
||||
|
||||
private companion object {
|
||||
const val UTXO_BLOCKCHAIN_NODE_INDEX = 2
|
||||
const val ACCOUNT_NODE_INDEX = 2
|
||||
const val ADDRESS_INDEX_NODE_INDEX = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -3,175 +3,214 @@ package com.tangem.lib.crypto.derivation
|
|||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class AccountNodeRecognizerTest {
|
||||
|
||||
private val utxoBlockchain = Blockchain.Bitcoin
|
||||
private val ethLikeBlockchain = Blockchain.Ethereum
|
||||
|
||||
@Nested
|
||||
inner class RecognizeAsDerivationPath {
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Recognize {
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and UTXO blockchain`() {
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun recognize(model: TestModel) {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'/0/0")
|
||||
val recognizer = AccountNodeRecognizer(blockchain = model.blockchain)
|
||||
val derivationPath = DerivationPath(rawPath = model.derivationPath)
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
val expected = 1
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'/0")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
private fun provideTestModels(): List<TestModel> {
|
||||
return provideEthLikeTestModels() +
|
||||
provideUTXOTestModels() +
|
||||
provideOtherBlockchainTests()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'/2/3")
|
||||
private fun provideEthLikeTestModels() = listOf(
|
||||
// region Tezos blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tezos,
|
||||
derivationPath = "m/44'/1729'/1'/0/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tezos,
|
||||
derivationPath = "m/44'/1729'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tezos,
|
||||
derivationPath = "m/44'/1729'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tezos,
|
||||
derivationPath = "m/44'/1729'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
// region Quai blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Quai,
|
||||
derivationPath = "m/44'/994'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Quai,
|
||||
derivationPath = "m/44'/994'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Quai,
|
||||
derivationPath = "m/44'/994'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
|
||||
// Assert
|
||||
val expected = 3
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
// region Ethereum-like blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationPath = "m/44'/60'/0'/0/1",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationPath = "m/44'/60'/0'/0",
|
||||
expected = null,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationPath = "m/44'/60'/0'",
|
||||
expected = null,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationPath = "m/44'/60'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'/2")
|
||||
private fun provideUTXOTestModels() = listOf(
|
||||
// region Bitcoin blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'/0'/1'/0/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'/0'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'/0'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'/0'",
|
||||
expected = null,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
private fun provideOtherBlockchainTests(): List<TestModel> = listOf(
|
||||
// region Solana blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Solana,
|
||||
derivationPath = "m/44'/501'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Solana,
|
||||
derivationPath = "m/44'/501'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
// region Cardano blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = "m/1852'/1815'/1'/0/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = "m/1852'/1815'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = "m/1852'/1815'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = "m/1852'/1815'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 3 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
val expected = 1
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and Tezos blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Tezos)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/0/5'")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
val expected = 0
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and Tezos blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Tezos)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/0'/5/1")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and Quai blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Quai)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/0/5'")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and Quai blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Quai)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/0'/5/1")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
val expected = 0
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null if derivation path is shorter than expected`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
// region Tron blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tron,
|
||||
derivationPath = "m/44'/195'/1'/0/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tron,
|
||||
derivationPath = "m/44'/195'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tron,
|
||||
derivationPath = "m/44'/195'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tron,
|
||||
derivationPath = "m/44'/195'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
}
|
||||
|
||||
data class TestModel(
|
||||
val blockchain: Blockchain,
|
||||
val derivationPath: String,
|
||||
val expected: Long?,
|
||||
)
|
||||
|
||||
@Nested
|
||||
inner class RecognizeAsString {
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and UTXO blockchain`() {
|
||||
fun `GIVEN empty derivation path THEN returns null`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'/0/0"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 1
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'/0"
|
||||
val derivationPathValue = "invalid/path"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
|
@ -181,115 +220,7 @@ internal class AccountNodeRecognizerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'/2/3"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 3
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'/2"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 3 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 1
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and Tezos blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Tezos)
|
||||
val derivationPathValue = "m/44'/0'/0/5'"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 0
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and Tezos blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Tezos)
|
||||
val derivationPathValue = "m/44'/0'/0'/5/1"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and Quai blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Quai)
|
||||
val derivationPathValue = "m/44'/0'/0/5'"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and Quai blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Quai)
|
||||
val derivationPathValue = "m/44'/0'/0'/5/1"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 0
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null if derivation path is shorter than expected`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPathValue = "m/44'/0'"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null if derivation path string is invalid`() {
|
||||
fun `GIVEN invalid derivation path THEN returns null`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPathValue = "invalid/path"
|
||||
|
|
@ -301,4 +232,34 @@ internal class AccountNodeRecognizerTest {
|
|||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class RecognizeAsNetworkDerivationPath {
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty derivation path THEN returns null`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPath = Network.DerivationPath.None
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN invalid derivation path THEN returns null`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPathValue = Network.DerivationPath.Card("invalid/path")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue