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 did verification capabilities #20

Merged
merged 6 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apollo/src/commonMain/kotlin/ApolloMock.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ApolloMock : Apollo {
)
var publicKeyReturn: PublicKey = PublicKey(KeyCurve(Curve.ED25519), ByteArray(0))
var signMessageReturn: Signature = Signature(ByteArray(0))
var verifySignatureReturn: Boolean = false
var verifySignatureReturn: Boolean = true
var compressedPublicKeyDataReturn: CompressedPublicKey = CompressedPublicKey(
PublicKey(KeyCurve(Curve.ED25519), ByteArray(0)),
ByteArray(0)
Expand Down
1 change: 1 addition & 0 deletions castor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("io.iohk.atala.prism:didpeer:1.0.0-alpha")
implementation(project(":apollo"))
implementation(project(":domain"))
}
}
Expand Down
42 changes: 41 additions & 1 deletion castor/src/commonMain/kotlin/CastorImpl.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package io.iohk.atala.prism.castor

import io.iohk.atala.prism.apollo.ApolloImpl
import io.iohk.atala.prism.castor.io.iohk.atala.prism.castor.resolvers.PeerDIDResolver
import io.iohk.atala.prism.domain.buildingBlocks.Apollo
import io.iohk.atala.prism.domain.buildingBlocks.Castor
import io.iohk.atala.prism.domain.models.CastorError
import io.iohk.atala.prism.domain.models.Curve
import io.iohk.atala.prism.domain.models.DID
import io.iohk.atala.prism.domain.models.DIDDocument
import io.iohk.atala.prism.domain.models.DIDDocument.VerificationMethod.Companion.getCurveByType
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.domain.models.Signature
import io.iohk.atala.prism.mercury.didpeer.DIDCommServicePeerDID
import io.iohk.atala.prism.mercury.didpeer.VerificationMaterialAgreement
import io.iohk.atala.prism.mercury.didpeer.VerificationMaterialAuthentication
Expand All @@ -21,10 +26,17 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

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

constructor(
apollo: Apollo? = null,
) {
this.apollo = apollo ?: ApolloImpl()
}

override fun parseDID(did: String): DID {
return DIDParser.parse(did)
}
Expand Down Expand Up @@ -104,6 +116,34 @@ open class CastorImpl : Castor {
}

override suspend fun verifySignature(did: DID, challenge: ByteArray, signature: ByteArray): Boolean {
TODO("Not yet implemented")
val document = resolveDID(did.toString())
val keyPairs: MutableList<PublicKey> = mutableListOf()

document.coreProperties
.filterIsInstance<DIDDocument.Authentication>()
.flatMap { it.verificationMethods.toList() }
.mapNotNull {
it.publicKeyMultibase?.let { publicKey ->
keyPairs.add(
PublicKey(
curve = KeyCurve(getCurveByType(it.type)),
value = publicKey.encodeToByteArray()
)
)
}
}

if (keyPairs.isEmpty()) {
throw CastorError.InvalidKeyError()
}

for (keyPair in keyPairs) {
val verified = apollo.verifySignature(keyPair, challenge, Signature(signature))
if (verified) {
return true
}
}

return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,34 @@ package io.iohk.atala.prism.castor.io.iohk.atala.prism.castor.resolvers

import io.iohk.atala.prism.castor.DIDParser
import io.iohk.atala.prism.castor.DIDUrlParser
import io.iohk.atala.prism.domain.models.CastorError
import io.iohk.atala.prism.domain.models.Curve
import io.iohk.atala.prism.domain.models.DIDDocument
import io.iohk.atala.prism.domain.models.DIDDocumentCoreProperty
import io.iohk.atala.prism.domain.models.DIDResolver
import io.iohk.atala.prism.mercury.didpeer.DIDCommServicePeerDID
import io.iohk.atala.prism.mercury.didpeer.DIDDocPeerDID
import io.iohk.atala.prism.mercury.didpeer.MalformedPeerDIDException
import io.iohk.atala.prism.mercury.didpeer.VerificationMaterialFormatPeerDID
import io.iohk.atala.prism.mercury.didpeer.VerificationMethodPeerDID
import io.iohk.atala.prism.mercury.didpeer.VerificationMethodTypeAgreement
import io.iohk.atala.prism.mercury.didpeer.VerificationMethodTypeAuthentication
import io.iohk.atala.prism.mercury.didpeer.resolvePeerDID
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

class PeerDIDResolver() : DIDResolver {
override val method: String = "peer"
override suspend fun resolve(didString: String): DIDDocument {
val did = DIDParser.parse(didString)
val peerDIDDocument = try {
DIDDocPeerDID.fromJson(resolvePeerDID(didString))
} catch (e: MalformedPeerDIDException) {
throw CastorError.InvalidPeerDIDError()
} catch (e: Throwable) {
throw CastorError.NotPossibleToResolveDID()
}

val coreProperties: MutableList<DIDDocumentCoreProperty> = mutableListOf()
val peerDIDDocument = DIDDocPeerDID.fromJson(resolvePeerDID(didString))

coreProperties.add(
DIDDocument.Authentication(
Expand Down Expand Up @@ -55,7 +66,6 @@ class PeerDIDResolver() : DIDResolver {
)
)
}
// TODO() To add the OtherService and know which attributes we can extract from it.
}
}

Expand All @@ -65,6 +75,8 @@ class PeerDIDResolver() : DIDResolver {
)
)

val did = DIDParser.parse(didString)

return DIDDocument(
id = did,
coreProperties = coreProperties.toTypedArray()
Expand All @@ -77,7 +89,24 @@ class PeerDIDResolver() : DIDResolver {
): DIDDocument.VerificationMethod {
val didUrl = DIDUrlParser.parse(did)
val controller = DIDParser.parse(verificationMethod.controller)
val type = verificationMethod.verMaterial.type.value
val type = when (verificationMethod.verMaterial.type.value) {
VerificationMethodTypeAuthentication.ED25519_VERIFICATION_KEY_2020.value -> {
Curve.ED25519.value
}
VerificationMethodTypeAuthentication.ED25519_VERIFICATION_KEY_2018.value -> {
Curve.ED25519.value
}
VerificationMethodTypeAgreement.X25519_KEY_AGREEMENT_KEY_2020.value -> {
Curve.X25519.value
}
VerificationMethodTypeAgreement.X25519_KEY_AGREEMENT_KEY_2019.value -> {
Curve.X25519.value
}
else -> {
throw CastorError.InvalidKeyError()
}
}

return when (verificationMethod.verMaterial.format) {
VerificationMaterialFormatPeerDID.JWK -> {
DIDDocument.VerificationMethod(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package io.iohk.atala.prism.castor

import io.iohk.atala.prism.domain.models.Curve
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
Expand Down Expand Up @@ -31,7 +30,7 @@ class DIDResolverTest {
assertEquals(verificationMethod.id.did.toString(), didExample)
assertEquals(
verificationMethod.type,
VerificationMethodTypeAuthentication.ED25519_VERIFICATION_KEY_2020.value
Curve.ED25519.value
)
}
}
Expand All @@ -44,7 +43,7 @@ class DIDResolverTest {
assertEquals(verificationMethod.id.did.toString(), didExample)
assertEquals(
verificationMethod.type,
VerificationMethodTypeAgreement.X25519_KEY_AGREEMENT_KEY_2020.value
Curve.X25519.value
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.iohk.atala.prism.castor

import io.iohk.atala.prism.apollo.ApolloMock
import io.iohk.atala.prism.domain.models.CastorError
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class DIDSignatureVerificationTest {

@Test
fun testCastorVerifySignature_Should_return_true_when_correctKeysAreUsed() = runTest {
val verKeyStr = "z6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd"
val authKeyStr = "z6MkqgCXHEGr2wJZANPZGC8WFmeVuS3abAD9uvh7mTXygCFv"
val serviceStr = "eyJpZCI6IkRJRENvbW1WMiIsInQiOiJkbSIsInMiOiJsb2NhbGhvc3Q6ODA4MiIsInIiOltdLCJhIjpbImRtIl19"
val didString = "did:peer:2.E$verKeyStr.V$authKeyStr.S$serviceStr"

val castor = CastorImpl(ApolloMock())

val did = DIDParser.parse(didString)
val result = castor.verifySignature(did, "".encodeToByteArray(), "".encodeToByteArray())
assertEquals(true, result)
}

@Test
fun testCastorVerifySignature_Should_return_error_when_incorrectKeysAreUsed() = runTest {
val verKeyStr = "z6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd"
val serviceStr = "eyJpZCI6IkRJRENvbW1WMiIsInQiOiJkbSIsInMiOiJsb2NhbGhvc3Q6ODA4MiIsInIiOltdLCJhIjpbImRtIl19"
val didString = "did:peer:2.E$verKeyStr.V$verKeyStr.S$serviceStr"

val castor = CastorImpl(ApolloMock())

val did = DIDParser.parse(didString)

assertFailsWith<CastorError.InvalidPeerDIDError> {
castor.verifySignature(did, "".encodeToByteArray(), "".encodeToByteArray())
}
}
}
8 changes: 8 additions & 0 deletions castor/src/jsMain/kotlin/CastorImplJs.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io.iohk.atala.prism.castor.CastorImpl
import io.iohk.atala.prism.domain.models.DID
import io.iohk.atala.prism.domain.models.DIDDocument
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.promise
Expand All @@ -12,4 +13,11 @@ class CastorImplJs : CastorImpl() {
fun resolveDIDJS(did: String): Promise<DIDDocument> {
return GlobalScope.promise { resolveDID(did) }
}

@JsName("verifySignature")
fun verifySignatureJS(did: DID, challenge: ByteArray, signature: ByteArray): Promise<Boolean> {
return GlobalScope.promise {
verifySignature(did, challenge, signature)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,26 @@ data class DIDDocument(
val type: String,
val publicKeyJwk: Map<String, String>? = null,
val publicKeyMultibase: String? = null
)
) {
companion object {
fun getCurveByType(type: String): Curve {
return when (type) {
Curve.X25519.value -> {
Curve.X25519
}
Curve.ED25519.value -> {
Curve.ED25519
}
Curve.SECP256K1.value -> {
Curve.SECP256K1
}
else -> {
throw CastorError.InvalidKeyError()
}
}
}
}
}

@Serializable
data class Service(
Expand Down