diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 488dfd025..5891f28de 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -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 diff --git a/castor/src/commonMain/kotlin/CastorImpl.kt b/castor/src/commonMain/kotlin/CastorImpl.kt index 8840dafe6..618dcd2d3 100644 --- a/castor/src/commonMain/kotlin/CastorImpl.kt +++ b/castor/src/commonMain/kotlin/CastorImpl.kt @@ -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 = arrayOf( + private var resolvers: Array = arrayOf( PeerDIDResolver() ) @@ -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, services: Array ): DID { - TODO("Not yet implemented") + var encryptionKeys: MutableList = mutableListOf() + var signingKeys: MutableList = 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") - } } diff --git a/castor/src/commonTest/kotlin/io/iohk/atala/prism/castor/DIDCreateTest.kt b/castor/src/commonTest/kotlin/io/iohk/atala/prism/castor/DIDCreateTest.kt new file mode 100644 index 000000000..ed7e345b7 --- /dev/null +++ b/castor/src/commonTest/kotlin/io/iohk/atala/prism/castor/DIDCreateTest.kt @@ -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 = 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 = 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 { + castor.createPeerDID(keyPairs, arrayOf(service)) + } + } +} diff --git a/castor/src/commonTest/kotlin/io/iohk/atala/prism/castor/DIDResolverTest.kt b/castor/src/commonTest/kotlin/io/iohk/atala/prism/castor/DIDResolverTest.kt index 081790b4c..2c6fc98a4 100644 --- a/castor/src/commonTest/kotlin/io/iohk/atala/prism/castor/DIDResolverTest.kt +++ b/castor/src/commonTest/kotlin/io/iohk/atala/prism/castor/DIDResolverTest.kt @@ -3,6 +3,7 @@ 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 @@ -10,12 +11,13 @@ 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) diff --git a/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/buildingBlocks/Castor.kt b/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/buildingBlocks/Castor.kt index 5def0a277..e5c393afe 100644 --- a/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/buildingBlocks/Castor.kt +++ b/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/buildingBlocks/Castor.kt @@ -18,8 +18,7 @@ interface Castor { @Throws() // TODO: Add throw classes fun createPeerDID( - keyAgreementKeyPair: KeyPair, - authenticationKeyPair: KeyPair, + keyPairs: Array, services: Array ): DID @@ -32,10 +31,4 @@ interface Castor { challenge: ByteArray, signature: ByteArray ): Boolean - - @Throws() // TODO: Add throw classes - fun getEcnumbasis( - did: DID, - keyPair: KeyPair - ): String } diff --git a/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/DID.kt b/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/DID.kt index 1e6033909..c99518578 100644 --- a/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/DID.kt +++ b/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/DID.kt @@ -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, diff --git a/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/DIDDocument.kt b/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/DIDDocument.kt index 849fadff0..d0d6dadc9 100644 --- a/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/DIDDocument.kt +++ b/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/DIDDocument.kt @@ -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 @@ -33,6 +36,7 @@ data class DIDDocument( val publicKeyMultibase: String? = null ) + @Serializable data class Service( val id: String, val type: Array, @@ -59,6 +63,7 @@ data class DIDDocument( } } + @Serializable data class ServiceEndpoint( val uri: String, val accept: Array? = arrayOf(), diff --git a/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/PublicKey.kt b/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/PublicKey.kt index fd9d96200..9c0c1aadf 100644 --- a/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/PublicKey.kt +++ b/domain/src/commonMain/kotlin/io.iohk.atala.prism.domain/models/PublicKey.kt @@ -1,7 +1,7 @@ package io.iohk.atala.prism.domain.models data class PublicKey( - val curve: String, + val curve: KeyCurve, val value: String )