Skip to content

Commit

Permalink
Merge pull request #1 from radixdlt/feature/cap26
Browse files Browse the repository at this point in the history
CAP-26 - Two default and one custom derivation path implemented.
  • Loading branch information
malexandru-rdx authored Nov 23, 2022
2 parents 60ebe56 + 9f1f1cd commit effc1b7
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/main/kotlin/com/radixdlt/bip44/BIP44.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ data class BIP44(val path: List<BIP44Element>) {

fun increment() = BIP44(path.subList(0, path.size - 1) +
path.last().let { BIP44Element(it.hardened, it.number + 1) })

}
33 changes: 33 additions & 0 deletions src/main/kotlin/com/radixdlt/derivation/AccountHDDerivationPath.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.radixdlt.derivation

import com.radixdlt.bip44.BIP44_PREFIX
import com.radixdlt.derivation.model.CoinType
import com.radixdlt.derivation.model.EntityType
import com.radixdlt.derivation.model.KeyType
import com.radixdlt.derivation.model.NetworkId

/**
* The **default** derivation path used to derive `Account` keys for signing off transactions or for signing authentication, at a certain account index (`ENTITY_INDEX`)
* and **unique per network** (`NETWORK_ID`) as per [CAP-26][cap26].
*
* Note that users can choose to use custom derivation path instead of this default
* one when deriving keys for accounts.
*
* The format is:
* `m/44'/1022'/<NETWORK_ID>'/525'/<ENTITY_INDEX>'/<KEY_TYPE>'`
*
* Where `'` denotes hardened path, which is **required** as per [SLIP-10][slip10].
* where `525` is ASCII sum of `"ACCOUNT"`, i.e. `"ACCOUNT".map{ $0.asciiValue! }.reduce(0, +)`
*
*/
data class AccountHDDerivationPath(
private val networkId: NetworkId,
private val accountIndex: Int,
private val keyType: KeyType
) {
private val coinType: CoinType = CoinType.RadixDlt
private val entityType: EntityType = EntityType.Account

val path: String
get() = "$BIP44_PREFIX/44'/${coinType.value}'/${networkId.value}'/${entityType.value}'/${accountIndex}'/${keyType.value}'"
}
27 changes: 27 additions & 0 deletions src/main/kotlin/com/radixdlt/derivation/CustomHDDerivationPath.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.radixdlt.derivation

import com.radixdlt.bip44.BIP44
import com.radixdlt.bip44.BIP44_PREFIX
import com.radixdlt.derivation.model.CoinType

/**
* A custom derivation path used to derive keys for whatever purpose. [CAP-26][cap26] states
* The format is:
* `m/44'/1022'`
* Where `'` denotes hardened path, which is **required** as per [SLIP-10][slip10].
*/
data class CustomHDDerivationPath(
val bip44: BIP44
) {
private val coinType: CoinType = CoinType.RadixDlt

/**
* Check if path starts with m/44'/1022' (hardened or not). Otherwise throw exception
*/
val path: String
get() = if (
bip44.toString().startsWith("$BIP44_PREFIX/44'/${coinType.value}") ||
bip44.toString().startsWith("$BIP44_PREFIX/44/${coinType.value}")
) bip44.toString() else throw IllegalArgumentException("Invalid derivation path")

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.radixdlt.derivation

import com.radixdlt.bip44.BIP44_PREFIX
import com.radixdlt.derivation.model.CoinType
import com.radixdlt.derivation.model.EntityType
import com.radixdlt.derivation.model.KeyType
import com.radixdlt.derivation.model.NetworkId

/**
* The **default** derivation path used to derive `Identity` (Persona) keys for signing authentication,
* at a certain (Persona) index (`ENTITY_INDEX`) and **unique per network** (`NETWORK_ID`) as per [CAP-26][cap26].
*
* Note that users can choose to use custom derivation path instead of this default one
* when deriving keys for identities (personas).
*
* The format is:
* `m/44'/1022'/<NETWORK_ID>'/618'/<ENTITY_INDEX>'/<KEY_TYPE>'`
*
* Where `'` denotes hardened path, which is **required** as per [SLIP-10][slip10].
* where `618` is ASCII sum of `"IDENTITY"`, i.e. `"IDENTITY".map{ $0.asciiValue! }.reduce(0, +)`
*
*/
data class IdentityHDDerivationPath(
private val networkId: NetworkId,
private val identityIndex: Int,
private val keyType: KeyType
) {
private val coinType: CoinType = CoinType.RadixDlt
private val entityType: EntityType = EntityType.Identity

val path: String
get() = "$BIP44_PREFIX/44'/${coinType.value}'/${networkId.value}'/${entityType.value}'/${identityIndex}'/${keyType.value}'"

}
8 changes: 8 additions & 0 deletions src/main/kotlin/com/radixdlt/derivation/model/CoinType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.radixdlt.derivation.model

/**
* Currently we only support Radix coin which is documented here -> https://github.com/satoshilabs/slips/pull/1137
*/
enum class CoinType(val value: Int) {
RadixDlt(1022)
}
6 changes: 6 additions & 0 deletions src/main/kotlin/com/radixdlt/derivation/model/EntityType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.radixdlt.derivation.model

enum class EntityType(val value: Int) {
Account(525),
Identity(618)
}
8 changes: 8 additions & 0 deletions src/main/kotlin/com/radixdlt/derivation/model/KeyType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.radixdlt.derivation.model

enum class KeyType(val value: Int) {
// Key to be used for signing transactions.
SignTransaction(1238),
// Key to be used for signing authentication.
SignAuth(706)
}
11 changes: 11 additions & 0 deletions src/main/kotlin/com/radixdlt/derivation/model/NetworkId.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.radixdlt.derivation.model

/**
* Full list of networks is documented here -> https://github.com/radixdlt/babylon-node/blob/main/common/src/main/java
* /com/radixdlt/networks/Network.java
*/
enum class NetworkId(val value: Int) {
Mainnet(1),
Adapanet(10),
Enkinet(33)
}
43 changes: 43 additions & 0 deletions src/test/kotlin/com/radixdlt/bip44/BIP44Test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.radixdlt.bip44
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class BIP44Test {

Expand Down Expand Up @@ -79,4 +80,46 @@ class BIP44Test {
assertThat(BIP44("m/0/1/2").increment())
.isEqualTo(BIP44("m/0/1/3"))
}

@Test
fun verifyHardenedDerivationPath() {
val bip44 = BIP44(
path = listOf(
BIP44Element(
hardened = true,
number = 10
),
BIP44Element(
hardened = true,
number = 20
),
BIP44Element(
hardened = true,
number = 30
)
)
)
assertEquals(bip44.toString(), "m/10'/20'/30'")
}

@Test
fun verifyUnhardenedDerivationPath() {
val bip44 = BIP44(
path = listOf(
BIP44Element(
hardened = false,
number = 10
),
BIP44Element(
hardened = false,
number = 20
),
BIP44Element(
hardened = false,
number = 30
)
)
)
assertEquals(bip44.toString(), "m/10/20/30")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.radixdlt.derivation

import com.radixdlt.derivation.model.KeyType
import com.radixdlt.derivation.model.NetworkId
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class AccountHDDerivationPathTest {

@Test
fun `verify account derivation path for mainnet at index 0 for signing authentication`() {
val accountHDDerivationPath = AccountHDDerivationPath(
networkId = NetworkId.Mainnet,
accountIndex = 0,
keyType = KeyType.SignAuth
)

assertEquals(accountHDDerivationPath.path, "m/44'/1022'/1'/525'/0'/706'")
}

@Test
fun `verify account derivation path for betanet at index 0 for signing authentication`() {
val accountHDDerivationPath = AccountHDDerivationPath(
networkId = NetworkId.Adapanet,
accountIndex = 0,
keyType = KeyType.SignAuth
)

assertEquals(accountHDDerivationPath.path, "m/44'/1022'/10'/525'/0'/706'")
}

@Test
fun `verify account derivation path for betanet at index 1 for signing authentication`() {
val accountHDDerivationPath = AccountHDDerivationPath(
networkId = NetworkId.Adapanet,
accountIndex = 1,
keyType = KeyType.SignAuth
)

assertEquals(accountHDDerivationPath.path, "m/44'/1022'/10'/525'/1'/706'")
}

@Test
fun `verify account derivation path for betanet at index 1 for signing transaction`() {
val accountHDDerivationPath = AccountHDDerivationPath(
networkId = NetworkId.Adapanet,
accountIndex = 1,
keyType = KeyType.SignTransaction
)

assertEquals(accountHDDerivationPath.path, "m/44'/1022'/10'/525'/1'/1238'")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.radixdlt.derivation

import com.radixdlt.bip44.BIP44
import com.radixdlt.bip44.BIP44Element
import com.radixdlt.derivation.model.CoinType
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class CustomHDDerivationPathTest {

@Test
fun `verify custom derivation path for 3 hardened bip44 elements`() {
val bip44 = BIP44(
path = listOf(
BIP44Element(
hardened = true,
number = 44
),
BIP44Element(
hardened = true,
number = CoinType.RadixDlt.value
),
BIP44Element(
hardened = true,
number = 10
),
BIP44Element(
hardened = true,
number = 20
),
BIP44Element(
hardened = true,
number = 30
)
)
)
val customHDDerivationPath = CustomHDDerivationPath(
bip44 = bip44
)

assertEquals(customHDDerivationPath.path, "m/44'/1022'/10'/20'/30'")
}

@Test
fun `verify invalid custom derivation path without radix coin`() {
val bip44 = BIP44(
path = listOf(
BIP44Element(
hardened = true,
number = 10
),
BIP44Element(
hardened = false,
number = 20
),
BIP44Element(
hardened = false,
number = 30
)
)
)
val customHDDerivationPath = CustomHDDerivationPath(
bip44 = bip44
)
assertThrows(IllegalArgumentException::class.java) {
customHDDerivationPath.path
}
}

@Test
fun `verify empty custom derivation path for no bip44 elements`() {
val bip44 = BIP44(
path = emptyList()
)
val customHDDerivationPath = CustomHDDerivationPath(
bip44 = bip44
)

assertThrows(IllegalArgumentException::class.java) {
customHDDerivationPath.path
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.radixdlt.derivation

import com.radixdlt.derivation.model.KeyType
import com.radixdlt.derivation.model.NetworkId
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class IdentityHDDerivationPathTest {

@Test
fun `verify identity derivation path for mainnet at index 0 for signing authentication`() {
val identityHDDerivationPath = IdentityHDDerivationPath(
networkId = NetworkId.Mainnet,
identityIndex = 0,
keyType = KeyType.SignAuth
)

assertEquals(identityHDDerivationPath.path, "m/44'/1022'/1'/618'/0'/706'")
}

@Test
fun `verify identity derivation path for alphanet at index 0 for signing authentication`() {
val identityHDDerivationPath = IdentityHDDerivationPath(
networkId = NetworkId.Adapanet,
identityIndex = 0,
keyType = KeyType.SignAuth
)

assertEquals(identityHDDerivationPath.path, "m/44'/1022'/10'/618'/0'/706'")
}

@Test
fun `verify identity derivation path for mainnet at index 1 for signing authentication`() {
val identityHDDerivationPath = IdentityHDDerivationPath(
networkId = NetworkId.Mainnet,
identityIndex = 1,
keyType = KeyType.SignAuth
)

assertEquals(identityHDDerivationPath.path, "m/44'/1022'/1'/618'/1'/706'")
}

@Test
fun `verify identity derivation path for mainnet at index 0 for signing transaction`() {
val identityHDDerivationPath = IdentityHDDerivationPath(
networkId = NetworkId.Mainnet,
identityIndex = 0,
keyType = KeyType.SignTransaction
)

assertEquals(identityHDDerivationPath.path, "m/44'/1022'/1'/618'/0'/1238'")
}
}

0 comments on commit effc1b7

Please sign in to comment.