Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(castor): Add peerDID Create method + tests. #15

Merged
merged 9 commits into from
Jan 26, 2023
1 change: 1 addition & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
VALIDATE_ALL_CODEBASE: ${{ github.ref_name == 'main' }}
DISABLE: COPYPASTE,SPELL
GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }}
DISABLE_LINTERS: REPOSITORY_GITLEAKS
steps:
- name: Checkout Code
uses: actions/checkout@v3
Expand Down
80 changes: 68 additions & 12 deletions castor/src/commonMain/kotlin/CastorImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@ import io.iohk.atala.prism.domain.models.CastorError
import io.iohk.atala.prism.domain.models.DID
import io.iohk.atala.prism.domain.models.DIDDocument
import io.iohk.atala.prism.domain.models.DIDResolver
import io.iohk.atala.prism.domain.models.KeyCurve
import io.iohk.atala.prism.domain.models.KeyPair
import io.iohk.atala.prism.domain.models.PublicKey
import io.iohk.atala.prism.mercury.didpeer.DIDCommServicePeerDID
import io.iohk.atala.prism.mercury.didpeer.VerificationMaterialAgreement
import io.iohk.atala.prism.mercury.didpeer.VerificationMaterialAuthentication
import io.iohk.atala.prism.mercury.didpeer.VerificationMaterialFormatPeerDID
import io.iohk.atala.prism.mercury.didpeer.VerificationMethodTypeAgreement
import io.iohk.atala.prism.mercury.didpeer.VerificationMethodTypeAuthentication
import io.iohk.atala.prism.mercury.didpeer.core.toJsonElement
import io.iohk.atala.prism.mercury.didpeer.createPeerDIDNumalgo2
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

open class CastorImpl : Castor {
var resolvers: Array<DIDResolver> = arrayOf(
private var resolvers: Array<DIDResolver> = arrayOf(
PeerDIDResolver()
)

Expand All @@ -21,26 +32,71 @@ open class CastorImpl : Castor {
TODO("Not yet implemented")
}

@Throws(CastorError.InvalidKeyError::class)
override fun createPeerDID(
keyAgreementKeyPair: KeyPair,
authenticationKeyPair: KeyPair,
keyPairs: Array<KeyPair>,
services: Array<DIDDocument.Service>
): DID {
TODO("Not yet implemented")
var encryptionKeys: MutableList<VerificationMaterialAgreement> = mutableListOf()
var signingKeys: MutableList<VerificationMaterialAuthentication> = mutableListOf()

keyPairs.forEach {
when (it.curve) {
KeyCurve.ED25519 -> {
encryptionKeys.add(
VerificationMaterialAgreement(
format = VerificationMaterialFormatPeerDID.MULTIBASE,
value = it.publicKey.value,
type = VerificationMethodTypeAgreement.X25519_KEY_AGREEMENT_KEY_2020
)
)
}
KeyCurve.X25519 -> {
signingKeys.add(
VerificationMaterialAuthentication(
format = VerificationMaterialFormatPeerDID.MULTIBASE,
value = it.publicKey.value,
type = VerificationMethodTypeAuthentication.ED25519_VERIFICATION_KEY_2020
)
)
}
else -> {
throw CastorError.InvalidKeyError()
}
}
}

if (signingKeys.isEmpty() || encryptionKeys.isEmpty()) {
throw CastorError.InvalidKeyError()
}

val peerDID = createPeerDIDNumalgo2(
encryptionKeys = encryptionKeys,
signingKeys = signingKeys,
service = services.map {
Json.encodeToString(
DIDCommServicePeerDID(
id = it.id,
type = it.type[0],
serviceEndpoint = it.serviceEndpoint.uri,
routingKeys = listOf(),
accept = it.serviceEndpoint.accept?.asList() ?: listOf()
).toDict().toJsonElement()
)
}.first()
)

return DIDParser.parse(peerDID)
}

@Throws(CastorError.NotPossibleToResolveDID::class)
override suspend fun resolveDID(didString: String): DIDDocument {
val did = DIDParser.parse(didString)
val resolver = resolvers.find { it.method == did.method } ?: throw CastorError.NotPossibleToResolveDID()
return resolver.resolve(didString)
override suspend fun resolveDID(did: String): DIDDocument {
val parsedDID = DIDParser.parse(did)
val resolver = resolvers.find { it.method == parsedDID.method } ?: throw CastorError.NotPossibleToResolveDID()
return resolver.resolve(did)
}

override suspend fun verifySignature(did: DID, challenge: ByteArray, signature: ByteArray): Boolean {
TODO("Not yet implemented")
}

override fun getEcnumbasis(did: DID, keyPair: KeyPair): String {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.iohk.atala.prism.castor

import io.iohk.atala.prism.domain.models.CastorError
import io.iohk.atala.prism.domain.models.DIDDocument
import io.iohk.atala.prism.domain.models.KeyCurve
import io.iohk.atala.prism.domain.models.KeyPair
import io.iohk.atala.prism.domain.models.PrivateKey
import io.iohk.atala.prism.domain.models.PublicKey
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class DIDCreateTest {

@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun it_should_create_peerDID_correctly() = runTest {
val verKeyStr = "z6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd"
val authKeyStr = "z6MkqgCXHEGr2wJZANPZGC8WFmeVuS3abAD9uvh7mTXygCFv"
val serviceStr = "eyJpZCI6IkRJRENvbW1WMiIsInQiOiJkbSIsInMiOiJsb2NhbGhvc3Q6ODA4MiIsInIiOltdLCJhIjpbImRtIl19"

val fakeVerPrivateKey = PrivateKey(KeyCurve.ED25519, "".encodeToByteArray())
val fakeAuthPrivateKey = PrivateKey(KeyCurve.X25519, "".encodeToByteArray())
val verificationPubKey = PublicKey(KeyCurve.ED25519, verKeyStr)
val authenticationPubKey = PublicKey(KeyCurve.X25519, authKeyStr)
val verificationKeyPair = KeyPair(KeyCurve.ED25519, fakeVerPrivateKey, verificationPubKey)
val authenticationKeyPair = KeyPair(KeyCurve.X25519, fakeAuthPrivateKey, authenticationPubKey)
val service = DIDDocument.Service(
id = "DIDCommV2",
type = arrayOf("DIDCommMessaging"),
serviceEndpoint = DIDDocument.ServiceEndpoint(
uri = "localhost:8082",
accept = arrayOf("DIDCommMessaging"),
routingKeys = arrayOf()
)
)
val keyPairs: Array<KeyPair> = arrayOf(verificationKeyPair, authenticationKeyPair)
val castor = CastorImpl()
val did = castor.createPeerDID(keyPairs, arrayOf(service))
assertEquals(did.toString(), "did:peer:2.E$verKeyStr.V$authKeyStr.S$serviceStr")
}

@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun it_should_throw_errors_if_wrong_keys_are_provided() = runTest {
val verKeyStr = "z6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd"
val authKeyStr = "z6MkqgCXHEGr2wJZANPZGC8WFmeVuS3abAD9uvh7mTXygCFv"

val fakeVerPrivateKey = PrivateKey(KeyCurve.ED25519, "".encodeToByteArray())
val fakeAuthPrivateKey = PrivateKey(KeyCurve.X25519, "".encodeToByteArray())

val verificationPubKey = PublicKey(KeyCurve.ED25519, verKeyStr)
val authenticationPubKey = PublicKey(KeyCurve.X25519, authKeyStr)

val verificationKeyPair = KeyPair(KeyCurve.ED25519, fakeVerPrivateKey, verificationPubKey)
val authenticationKeyPair = KeyPair(KeyCurve.ED25519, fakeAuthPrivateKey, authenticationPubKey)

val keyPairs: Array<KeyPair> = arrayOf(verificationKeyPair, authenticationKeyPair)

val service = DIDDocument.Service(
id = "DIDCommV2",
type = arrayOf("DIDCommMessaging"),
serviceEndpoint = DIDDocument.ServiceEndpoint(
uri = "localhost:8082",
accept = arrayOf("DIDCommMessaging"),
routingKeys = arrayOf()
)
)

val castor = CastorImpl()

assertFailsWith<CastorError.InvalidKeyError> {
castor.createPeerDID(keyPairs, arrayOf(service))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@ package io.iohk.atala.prism.castor
import io.iohk.atala.prism.domain.models.DIDDocument
import io.iohk.atala.prism.mercury.didpeer.VerificationMethodTypeAgreement
import io.iohk.atala.prism.mercury.didpeer.VerificationMethodTypeAuthentication
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals

class DIDResolverTest {

@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun it_should_resolve_valid_dids() = runTest {
val didExample =
"did:peer:2.Ez6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd.Vz6MkqgCXHEGr2wJZANPZGC8WFmeVuS3abAD9uvh7mTXygCFv.SeyJ0IjoiZG0iLCJzIjoibG9jYWxob3N0OjgwODIiLCJyIjpbXSwiYSI6WyJkaWRjb21tL3YyIl19"
val castor = CastorImpl()
var response = castor.resolveDID(didExample)
val response = castor.resolveDID(didExample)

assertEquals(response.id.toString(), didExample)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ interface Castor {

@Throws() // TODO: Add throw classes
fun createPeerDID(
keyAgreementKeyPair: KeyPair,
authenticationKeyPair: KeyPair,
keyPairs: Array<KeyPair>,
services: Array<DIDDocument.Service>
): DID

Expand All @@ -32,10 +31,4 @@ interface Castor {
challenge: ByteArray,
signature: ByteArray
): Boolean

@Throws() // TODO: Add throw classes
fun getEcnumbasis(
did: DID,
keyPair: KeyPair
): String
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.iohk.atala.prism.domain.models

import kotlinx.serialization.Serializable
import kotlin.jvm.JvmStatic

@Serializable
data class DID(
val schema: String,
val method: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.iohk.atala.prism.domain.models

import kotlinx.serialization.Serializable

interface DIDDocumentCoreProperty

@Serializable
data class DIDDocument(
val id: DID,
val coreProperties: Array<DIDDocumentCoreProperty>
Expand Down Expand Up @@ -33,6 +36,7 @@ data class DIDDocument(
val publicKeyMultibase: String? = null
)

@Serializable
data class Service(
val id: String,
val type: Array<String>,
Expand All @@ -59,6 +63,7 @@ data class DIDDocument(
}
}

@Serializable
data class ServiceEndpoint(
val uri: String,
val accept: Array<String>? = arrayOf(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.iohk.atala.prism.domain.models

data class PublicKey(
val curve: String,
val curve: KeyCurve,
val value: String
)

Expand Down