-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
945 additions
and
0 deletions.
There are no files selected for viewing
119 changes: 119 additions & 0 deletions
119
didpeer/src/commonMain/kotlin/io.iohk.atala.prism.mercury.didpeer/DIDDoc.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package io.iohk.atala.prism.mercury.didpeer | ||
|
||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.decodeFromString | ||
import kotlinx.serialization.encodeToString | ||
import kotlinx.serialization.json.Json | ||
|
||
const val SERVICE_ID = "id" | ||
const val SERVICE_TYPE = "type" | ||
const val SERVICE_ENDPOINT = "serviceEndpoint" | ||
const val SERVICE_DIDCOMM_MESSAGING = "DIDCommMessaging" | ||
const val SERVICE_ROUTING_KEYS = "routingKeys" | ||
const val SERVICE_ACCEPT = "accept" | ||
|
||
@Serializable | ||
data class DIDDocPeerDID( | ||
val did: String, | ||
val authentication: List<VerificationMethodPeerDID>, | ||
val keyAgreement: List<VerificationMethodPeerDID> = emptyList(), | ||
val service: List<Service>? = null | ||
) { | ||
val authenticationKids get() = authentication.map { it.id } | ||
val agreementKids get() = keyAgreement.map { it.id } | ||
|
||
fun toDict(): Map<String, Any> { | ||
val res = mutableMapOf( | ||
"id" to did, | ||
"authentication" to authentication.map { it.toDict() }, | ||
) | ||
if (keyAgreement.isNotEmpty()) { | ||
res["keyAgreement"] = keyAgreement.map { it.toDict() } | ||
} | ||
service?.let { | ||
res["service"] = service.map { | ||
when (it) { | ||
is OtherService -> it.data | ||
is DIDCommServicePeerDID -> it.toDict() | ||
} | ||
} | ||
} | ||
return res | ||
} | ||
|
||
fun toJson(): String { | ||
// GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(toDict()) | ||
return Json.encodeToString(toDict()) | ||
} | ||
|
||
companion object { | ||
/** | ||
* Creates a new instance of DIDDocPeerDID from the given DID Doc JSON. | ||
* | ||
* @param value DID Doc JSON | ||
* @throws MalformedPeerDIDDOcException if the input DID Doc JSON is not a valid peerdid DID Doc | ||
* @return [DIDDocPeerDID] instance | ||
*/ | ||
fun fromJson(value: JSON): DIDDocPeerDID { | ||
try { | ||
// Two ways | ||
// return didDocFromJson(Json.parseToJsonElement(value).jsonObject) | ||
return Json.decodeFromString<DIDDocPeerDID>(value) | ||
} catch (e: Exception) { | ||
throw MalformedPeerDIDDOcException(e) | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Serializable | ||
data class VerificationMethodPeerDID( | ||
val id: String, | ||
val controller: String, | ||
val verMaterial: VerificationMaterialPeerDID<out VerificationMethodTypePeerDID> | ||
) { | ||
|
||
private fun publicKeyField() = | ||
when (verMaterial.format) { | ||
VerificationMaterialFormatPeerDID.BASE58 -> PublicKeyField.BASE58 | ||
VerificationMaterialFormatPeerDID.JWK -> PublicKeyField.JWK | ||
VerificationMaterialFormatPeerDID.MULTIBASE -> PublicKeyField.MULTIBASE | ||
} | ||
|
||
fun toDict() = mapOf( | ||
"id" to id, | ||
"type" to verMaterial.type.value, | ||
"controller" to controller, | ||
publicKeyField().value to verMaterial.value, | ||
) | ||
} | ||
|
||
sealed interface Service | ||
|
||
data class OtherService(val data: Map<String, Any>) : Service | ||
|
||
data class DIDCommServicePeerDID( | ||
val id: String, | ||
val type: String, | ||
val serviceEndpoint: String, | ||
val routingKeys: List<String>, | ||
val accept: List<String> | ||
) : Service { | ||
|
||
fun toDict(): MutableMap<String, Any> { | ||
val res = mutableMapOf<String, Any>( | ||
SERVICE_ID to id, | ||
SERVICE_TYPE to type, | ||
) | ||
res[SERVICE_ENDPOINT] = serviceEndpoint | ||
res[SERVICE_ROUTING_KEYS] = routingKeys | ||
res[SERVICE_ACCEPT] = accept | ||
return res | ||
} | ||
} | ||
|
||
enum class PublicKeyField(val value: String) { | ||
BASE58("publicKeyBase58"), | ||
MULTIBASE("publicKeyMultibase"), | ||
JWK("publicKeyJwk"); | ||
} |
26 changes: 26 additions & 0 deletions
26
didpeer/src/commonMain/kotlin/io.iohk.atala.prism.mercury.didpeer/Exceptions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package io.iohk.atala.prism.mercury.didpeer | ||
|
||
/** | ||
* The base class for all PeerDID errors and exceptions. | ||
* | ||
* @param message - the detail message. | ||
* @param cause - the cause of this. | ||
*/ | ||
open class PeerDIDException(message: String, cause: Throwable? = null) : Throwable(message, cause) | ||
|
||
/** | ||
* Raised if the peer DID to be resolved in not a valid peer DID. | ||
* | ||
* @param message - the detail message. | ||
* @param cause - the cause of this. | ||
*/ | ||
class MalformedPeerDIDException(message: String, cause: Throwable? = null) : | ||
PeerDIDException("Invalid peer DID provided. $message", cause) | ||
|
||
/** | ||
* Raised if the resolved peer DID Doc to be resolved in not a valid peer DID. | ||
* | ||
* @param cause - the cause of this. | ||
*/ | ||
class MalformedPeerDIDDOcException(cause: Throwable? = null) : | ||
PeerDIDException("Invalid peer DID Doc", cause) |
73 changes: 73 additions & 0 deletions
73
didpeer/src/commonMain/kotlin/io.iohk.atala.prism.mercury.didpeer/PeerDIDCreator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
@file:JvmName("PeerDIDCreator") | ||
package io.iohk.atala.prism.mercury.didpeer | ||
|
||
import io.iohk.atala.prism.mercury.didpeer.core.Numalgo2Prefix | ||
import io.iohk.atala.prism.mercury.didpeer.core.createMultibaseEncnumbasis | ||
import io.iohk.atala.prism.mercury.didpeer.core.encodeService | ||
import io.iohk.atala.prism.mercury.didpeer.core.validateAgreementMaterialType | ||
import io.iohk.atala.prism.mercury.didpeer.core.validateAuthenticationMaterialType | ||
import kotlin.jvm.JvmName | ||
|
||
/** | ||
* Checks if [peerDID] param matches PeerDID spec | ||
* @see | ||
* <a href="https://identity.foundation/peer-did-method-spec/index.html#matching-regex">Specification</a> | ||
* @param [peerDID] PeerDID to check | ||
* @return true if [peerDID] matches spec, otherwise false | ||
*/ | ||
fun isPeerDID(peerDID: String): Boolean { | ||
val regex = | ||
( | ||
"^did:peer:(([0](z)([1-9a-km-zA-HJ-NP-Z]{46,47}))" + | ||
"|(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{46,47}))+(.(S)[0-9a-zA-Z=]*)?)))$" | ||
).toRegex() | ||
return regex.matches(peerDID) | ||
} | ||
|
||
/** | ||
* Generates PeerDID according to the zero algorithm | ||
* For this type of algorithm DIDDoc can be obtained from PeerDID | ||
* @see | ||
* <a href="https://identity.foundation/peer-did-method-spec/index.html#generation-method">Specification</a> | ||
* @param [inceptionKey] the key that creates the DID and authenticates when exchanging it with the first peer | ||
* @throws IllegalArgumentException if the [inceptionKey] is not correctly encoded | ||
* @return generated PeerDID | ||
*/ | ||
fun createPeerDIDNumalgo0(inceptionKey: VerificationMaterialAuthentication): PeerDID { | ||
validateAuthenticationMaterialType(inceptionKey) | ||
return "did:peer:0${createMultibaseEncnumbasis(inceptionKey)}" | ||
} | ||
|
||
/** | ||
* Generates PeerDID according to the second algorithm | ||
* For this type of algorithm DIDDoc can be obtained from PeerDID | ||
* @see | ||
* <a href="https://identity.foundation/peer-did-method-spec/index.html#generation-method">Specification</a> | ||
* @param [encryptionKeys] list of encryption keys | ||
* @param [signingKeys] list of signing keys | ||
* @param [service] JSON string conforming to the DID specification | ||
* @throws IllegalArgumentException | ||
* - if at least one of keys is not properly encoded | ||
* - if service is not a valid JSON | ||
* @return generated PeerDID | ||
*/ | ||
fun createPeerDIDNumalgo2( | ||
encryptionKeys: List<VerificationMaterialAgreement>, | ||
signingKeys: List<VerificationMaterialAuthentication>, | ||
service: JSON? | ||
): PeerDID { | ||
encryptionKeys.forEach { validateAgreementMaterialType(it) } | ||
signingKeys.forEach { validateAuthenticationMaterialType(it) } | ||
|
||
val encodedEncryptionKeysStr = encryptionKeys | ||
.map { createMultibaseEncnumbasis(it) } | ||
.map { ".${Numalgo2Prefix.KEY_AGREEMENT.prefix}$it" } | ||
.joinToString("") | ||
val encodedSigningKeysStr = signingKeys | ||
.map { createMultibaseEncnumbasis(it) } | ||
.map { ".${Numalgo2Prefix.AUTHENTICATION.prefix}$it" } | ||
.joinToString("") | ||
val encodedService = if (service.isNullOrEmpty()) "" else encodeService(service) | ||
|
||
return "did:peer:2$encodedEncryptionKeysStr$encodedSigningKeysStr$encodedService" | ||
} |
115 changes: 115 additions & 0 deletions
115
didpeer/src/commonMain/kotlin/io.iohk.atala.prism.mercury.didpeer/PeerDIDResolver.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
@file:JvmName("PeerDIDResolver") | ||
package io.iohk.atala.prism.mercury.didpeer | ||
|
||
import io.iohk.atala.prism.mercury.didpeer.core.DecodedEncumbasis | ||
import io.iohk.atala.prism.mercury.didpeer.core.Numalgo2Prefix | ||
import io.iohk.atala.prism.mercury.didpeer.core.decodeMultibaseEncnumbasis | ||
import io.iohk.atala.prism.mercury.didpeer.core.decodeService | ||
import io.iohk.atala.prism.mercury.didpeer.core.getVerificationMethod | ||
import io.iohk.atala.prism.mercury.didpeer.core.validateAgreementMaterialType | ||
import io.iohk.atala.prism.mercury.didpeer.core.validateAuthenticationMaterialType | ||
import kotlin.jvm.JvmName | ||
|
||
/** Resolves [DIDDocPeerDID] from [PeerDID] | ||
* @param [peerDID] PeerDID to resolve | ||
* @param [format] The format of public keys in the DID DOC. Default format is multibase. | ||
* @throws MalformedPeerDIDException | ||
* - if [peerDID] parameter does not match [peerDID] spec | ||
* - if a valid DIDDoc cannot be produced from the [peerDID] | ||
* @return resolved [DIDDocPeerDID] as JSON string | ||
*/ | ||
fun resolvePeerDID( | ||
peerDID: PeerDID, | ||
format: VerificationMaterialFormatPeerDID = VerificationMaterialFormatPeerDID.MULTIBASE | ||
): String { | ||
if (!isPeerDID(peerDID)) { | ||
throw MalformedPeerDIDException("Does not match peer DID regexp: $peerDID") | ||
} | ||
val didDoc = when (peerDID[9]) { | ||
'0' -> buildDIDDocNumalgo0(peerDID, format) | ||
'2' -> buildDIDDocNumalgo2(peerDID, format) | ||
else -> throw IllegalArgumentException("Invalid numalgo of Peer DID: $peerDID") | ||
} | ||
return didDoc.toJson() | ||
} | ||
|
||
private fun buildDIDDocNumalgo0(peerDID: PeerDID, format: VerificationMaterialFormatPeerDID): DIDDocPeerDID { | ||
val inceptionKey = peerDID.substring(10) | ||
val decodedEncumbasis = decodeMultibaseEncnumbasisAuth(inceptionKey, format) | ||
return DIDDocPeerDID( | ||
did = peerDID, | ||
authentication = listOf(getVerificationMethod(peerDID, decodedEncumbasis)) | ||
) | ||
} | ||
|
||
private fun buildDIDDocNumalgo2(peerDID: PeerDID, format: VerificationMaterialFormatPeerDID): DIDDocPeerDID { | ||
val keys = peerDID.drop(11) | ||
|
||
var service = "" | ||
val authentications = mutableListOf<VerificationMethodPeerDID>() | ||
val keyAgreement = mutableListOf<VerificationMethodPeerDID>() | ||
|
||
keys.split(".").forEach { | ||
val prefix = it[0] | ||
val value = it.drop(1) | ||
|
||
when (prefix) { | ||
Numalgo2Prefix.SERVICE.prefix -> service = value | ||
|
||
Numalgo2Prefix.AUTHENTICATION.prefix -> { | ||
val decodedEncumbasis = decodeMultibaseEncnumbasisAuth(value, format) | ||
authentications.add(getVerificationMethod(peerDID, decodedEncumbasis)) | ||
} | ||
|
||
Numalgo2Prefix.KEY_AGREEMENT.prefix -> { | ||
val decodedEncumbasis = decodeMultibaseEncnumbasisAgreement(value, format) | ||
keyAgreement.add(getVerificationMethod(peerDID, decodedEncumbasis)) | ||
} | ||
|
||
else -> throw IllegalArgumentException("Unsupported transform part of PeerDID: $prefix") | ||
} | ||
} | ||
|
||
val decodedService = doDecodeService(service, peerDID) | ||
|
||
return DIDDocPeerDID( | ||
did = peerDID, | ||
authentication = authentications, | ||
keyAgreement = keyAgreement, | ||
service = decodedService | ||
) | ||
} | ||
|
||
private fun decodeMultibaseEncnumbasisAuth( | ||
multibase: String, | ||
format: VerificationMaterialFormatPeerDID | ||
): DecodedEncumbasis { | ||
try { | ||
val decodedEncumbasis = decodeMultibaseEncnumbasis(multibase, format) | ||
validateAuthenticationMaterialType(decodedEncumbasis.verMaterial) | ||
return decodedEncumbasis | ||
} catch (e: IllegalArgumentException) { | ||
throw MalformedPeerDIDException("Invalid key $multibase", e) | ||
} | ||
} | ||
|
||
private fun decodeMultibaseEncnumbasisAgreement( | ||
multibase: String, | ||
format: VerificationMaterialFormatPeerDID | ||
): DecodedEncumbasis { | ||
try { | ||
val decodedEncumbasis = decodeMultibaseEncnumbasis(multibase, format) | ||
validateAgreementMaterialType(decodedEncumbasis.verMaterial) | ||
return decodedEncumbasis | ||
} catch (e: IllegalArgumentException) { | ||
throw MalformedPeerDIDException("Invalid key $multibase", e) | ||
} | ||
} | ||
|
||
private fun doDecodeService(service: String, peerDID: String): List<Service>? { | ||
try { | ||
return decodeService(service, peerDID) | ||
} catch (e: IllegalArgumentException) { | ||
throw MalformedPeerDIDException("Invalid service", e) | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
didpeer/src/commonMain/kotlin/io.iohk.atala.prism.mercury.didpeer/Types.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package io.iohk.atala.prism.mercury.didpeer | ||
|
||
import kotlinx.serialization.Contextual | ||
import kotlinx.serialization.Serializable | ||
|
||
enum class VerificationMaterialFormatPeerDID { | ||
JWK, | ||
BASE58, | ||
MULTIBASE; | ||
} | ||
|
||
@Serializable | ||
sealed class VerificationMethodTypePeerDID(val value: String) | ||
|
||
sealed class VerificationMethodTypeAgreement(value: String) : VerificationMethodTypePeerDID(value) { | ||
object JSON_WEB_KEY_2020 : VerificationMethodTypeAgreement("JsonWebKey2020") | ||
object X25519_KEY_AGREEMENT_KEY_2019 : VerificationMethodTypeAgreement("X25519KeyAgreementKey2019") | ||
object X25519_KEY_AGREEMENT_KEY_2020 : VerificationMethodTypeAgreement("X25519KeyAgreementKey2020") | ||
} | ||
|
||
sealed class VerificationMethodTypeAuthentication(value: String) : VerificationMethodTypePeerDID(value) { | ||
object JSON_WEB_KEY_2020 : VerificationMethodTypeAuthentication("JsonWebKey2020") | ||
object ED25519_VERIFICATION_KEY_2018 : VerificationMethodTypeAuthentication("Ed25519VerificationKey2018") | ||
object ED25519_VERIFICATION_KEY_2020 : VerificationMethodTypeAuthentication("Ed25519VerificationKey2020") | ||
} | ||
|
||
@Serializable | ||
data class VerificationMaterialPeerDID<T : VerificationMethodTypePeerDID>( | ||
val format: VerificationMaterialFormatPeerDID, | ||
@Contextual val value: Any, | ||
val type: T | ||
) | ||
|
||
typealias VerificationMaterialAgreement = VerificationMaterialPeerDID<VerificationMethodTypeAgreement> | ||
typealias VerificationMaterialAuthentication = VerificationMaterialPeerDID<VerificationMethodTypeAuthentication> | ||
|
||
typealias JSON = String | ||
typealias PeerDID = String |
Oops, something went wrong.