Skip to content

Commit

Permalink
feat(Agent): Implement Credential Issue Protocol in PrismAgent (#27)
Browse files Browse the repository at this point in the history
* fix(DID) Adding JsExport and JsName for secondary constructor.

* Making Message JsExportable and add some default values for Message and MessageAttachment.

* Add apollo UUID and IssueProtocol data clases in preparation for the Issuance protocol itself.

* Add missing error to AgentErrors

* Add mocked ConnectionsManager and create interface.

* Fix existing code + add interface DIDCommConnection

* Create Issue Credential Protocol

* Adding OfferCredentialTest and adjust default values on models.

* Update prism-agent/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/prismagent/protocols/issueCredential/IssueCredentialTest.kt

---------

Co-authored-by: Cristian G <[email protected]>
  • Loading branch information
elribonazo and cristianIOHK authored Feb 17, 2023
1 parent 2536085 commit 0f635f3
Show file tree
Hide file tree
Showing 20 changed files with 1,139 additions and 13 deletions.
1 change: 1 addition & 0 deletions domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("io.iohk.atala.prism:uuid:1.0.0-alpha")
implementation("io.ktor:ktor-client-core:2.1.3")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
}
}
val commonTest by getting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import kotlin.jvm.JvmStatic
@Serializable
@JsExport
data class DID(
val schema: String,
val schema: String = "did",
val method: String,
val methodId: String,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ sealed class PrismAgentError(message: String? = null) : Throwable(message) {
class invalidMessageError(message: String? = null) : PrismAgentError(message)
class noMediatorAvailableError(message: String? = null) : PrismAgentError(message)
class mediationRequestFailedError(message: String? = null) : PrismAgentError(message)
class invalidStepError(message: String? = null) : PrismAgentError(message)
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
package io.iohk.atala.prism.walletsdk.domain.models

import io.iohk.atala.prism.apollo.uuid.UUID
import kotlinx.datetime.Clock
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.js.JsExport
import kotlin.time.Duration.Companion.days

@Serializable
@JsExport
data class Message(
val id: String,
data class Message constructor(
val id: String = UUID.randomUUID4().toString(),
val piuri: String,
val from: DID?,
val to: DID?,
val fromPrior: String?,
val from: DID? = null,
val to: DID? = null,
val fromPrior: String? = null,
val body: String,
val extraHeaders: Array<String>,
val createdTime: String,
val expiresTimePlus: String,
val attachments: Array<AttachmentDescriptor>,
val extraHeaders: Array<String> = arrayOf(),
val createdTime: String = Clock.System.now().toString(),
val expiresTimePlus: String = Clock.System.now().plus(1.days).toString(),
val attachments: Array<AttachmentDescriptor> = arrayOf(),
val thid: String? = null,
val pthid: String? = null,
val ack: Array<String>,
val direction: Direction,
val ack: Array<String>? = emptyArray(),
val direction: Direction = Direction.RECEIVED,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,13 @@ data class AttachmentDescriptor(
val id: String,
val mediaType: String? = null,
val data: AttachmentData,
val filename: Array<String>,
val filename: Array<String>? = null,
val format: String? = null,
val lastModTime: String? = null, // Date format
val byteCount: Int? = null,
val description: String? = null,
) : AttachmentData {

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
Expand Down
1 change: 1 addition & 0 deletions prism-agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ kotlin {
dependencies {
implementation("io.iohk.atala.prism:uuid:1.0.0-alpha")
implementation(project(":domain"))
implementation("io.iohk.atala.prism:apollo:1.0.0-alpha")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.iohk.atala.prism.walletsdk.prismagent.connectionsManager

import io.iohk.atala.prism.walletsdk.domain.models.DIDPair

interface ConnectionsManager {
suspend fun addConnection(paired: DIDPair)
suspend fun removeConnection(pair: DIDPair): DIDPair?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.iohk.atala.prism.walletsdk.prismagent.connectionsManager

import io.iohk.atala.prism.walletsdk.domain.models.DIDPair

class ConnectionsManagerImpl : ConnectionsManager {
override suspend fun addConnection(paired: DIDPair) {
TODO("Not yet implemented")
}

override suspend fun removeConnection(pair: DIDPair): DIDPair? {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.iohk.atala.prism.walletsdk.prismagent.connectionsManager

import io.iohk.atala.prism.walletsdk.domain.models.Message

interface DIDCommConnection {
suspend fun awaitMessages(): Array<Message>
suspend fun awaitMessageResponse(id: String): Message?
suspend fun sendMessage(message: Message): Message?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.iohk.atala.prism.walletsdk.prismagent.helpers

import io.iohk.atala.prism.apollo.base64.base64UrlEncoded
import io.iohk.atala.prism.apollo.uuid.UUID
import io.iohk.atala.prism.walletsdk.domain.models.AttachmentBase64
import io.iohk.atala.prism.walletsdk.domain.models.AttachmentDescriptor
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

inline fun <reified T : Serializable> AttachmentDescriptor.Companion.build(
id: String = UUID.randomUUID4().toString(),
payload: T,
mediaType: String? = "application/json"
): AttachmentDescriptor {
val encoded = Json.encodeToString(payload).base64UrlEncoded
val attachment = AttachmentBase64(base64 = encoded)
return AttachmentDescriptor(id, mediaType, attachment)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package io.iohk.atala.prism.walletsdk.prismagent.protocols.issueCredential

import io.iohk.atala.prism.apollo.base64.base64UrlEncoded
import io.iohk.atala.prism.apollo.uuid.UUID
import io.iohk.atala.prism.walletsdk.domain.models.AttachmentBase64
import io.iohk.atala.prism.walletsdk.domain.models.AttachmentDescriptor
import io.iohk.atala.prism.walletsdk.domain.models.DID
import io.iohk.atala.prism.walletsdk.domain.models.Message
import io.iohk.atala.prism.walletsdk.domain.models.PrismAgentError
import io.iohk.atala.prism.walletsdk.prismagent.helpers.build
import io.iohk.atala.prism.walletsdk.prismagent.protocols.ProtocolType
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.js.JsExport

@Serializable
@JsExport
data class IssueCredential(
val id: String? = UUID.randomUUID4().toString(),
val body: Body,
val attachments: Array<AttachmentDescriptor>,
val thid: String?,
val from: DID,
val to: DID,
) {
val type: String = ProtocolType.DidcommIssueCredential.value

fun makeMessage(): Message {
return Message(
id = id ?: UUID.randomUUID4().toString(),
piuri = type,
from = from,
to = to,
body = Json.encodeToString(body),
attachments = attachments,
thid = thid,
)
}

fun getCredentialStrings(): Array<String> {
return attachments.mapNotNull {
when (it.data) {
is AttachmentBase64 -> {
(it.data as AttachmentBase64).base64.base64UrlEncoded
}
else -> null
}
}.toTypedArray()
}

companion object {
fun fromMessage(fromMessage: Message): IssueCredential {
require(
fromMessage.piuri == ProtocolType.DidcommIssueCredential.value &&
fromMessage.from != null &&
fromMessage.to != null,
) {
throw PrismAgentError.invalidIssueCredentialMessageError()
}

val fromDID = fromMessage.from!!
val toDID = fromMessage.to!!
val body = Json.decodeFromString<IssueCredential.Body>(fromMessage.body)

return IssueCredential(
id = fromMessage.id,
body = body,
attachments = fromMessage.attachments,
thid = fromMessage.thid,
from = fromDID,
to = toDID,
)
}

fun makeIssueFromRequestCedential(msg: Message): IssueCredential {
val request = RequestCredential.fromMessage(msg)
return IssueCredential(
body = Body(
goalCode = request.body.goalCode,
comment = request.body.comment,
formats = request.body.formats,
),
attachments = request.attachments,
thid = msg.id,
from = request.to,
to = request.from,
)
}
}

@Serializable
data class Body(
val goalCode: String? = null,
val comment: String? = null,
val replacementId: String? = null,
val moreAvailable: String? = null,
val formats: Array<CredentialFormat>,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as Body

if (goalCode != other.goalCode) return false
if (comment != other.comment) return false
if (replacementId != other.replacementId) return false
if (moreAvailable != other.moreAvailable) return false
if (!formats.contentEquals(other.formats)) return false

return true
}

override fun hashCode(): Int {
var result = goalCode?.hashCode() ?: 0
result = 31 * result + (comment?.hashCode() ?: 0)
result = 31 * result + (replacementId?.hashCode() ?: 0)
result = 31 * result + (moreAvailable?.hashCode() ?: 0)
result = 31 * result + formats.contentHashCode()
return result
}
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as IssueCredential

if (id != other.id) return false
if (body != other.body) return false
if (!attachments.contentEquals(other.attachments)) return false
if (thid != other.thid) return false
if (from != other.from) return false
if (to != other.to) return false
if (type != other.type) return false

return true
}

override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + body.hashCode()
result = 31 * result + attachments.contentHashCode()
result = 31 * result + (thid?.hashCode() ?: 0)
result = 31 * result + from.hashCode()
result = 31 * result + to.hashCode()
result = 31 * result + type.hashCode()
return result
}
}

inline fun <reified T : Serializable> IssueCredential.Companion.build(
fromDID: DID,
toDID: DID,
thid: String?,
credentials: Map<String, T> = mapOf(),
): IssueCredential {
val aux = credentials.map { (key, value) ->
val attachment = AttachmentDescriptor.build(
payload = value,
)
val format = CredentialFormat(attachId = attachment.id, format = key)
format to attachment
}
return IssueCredential(
body = IssueCredential.Body(
formats = aux.map { it.first }.toTypedArray(),
),
attachments = aux.map { it.second }.toTypedArray(),
thid = thid,
from = fromDID,
to = toDID,
)
}
Loading

0 comments on commit 0f635f3

Please sign in to comment.