Skip to content

Commit

Permalink
Merge branch 'main' into update-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
waltkb authored Jun 26, 2023
2 parents 83a9084 + 8faa45f commit 1a94b27
Show file tree
Hide file tree
Showing 13 changed files with 58 additions and 82 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dependencies {
implementation("com.microsoft.azure:azure-client-authentication:1.7.14")
implementation("com.nimbusds:nimbus-jose-jwt:9.30.2")
implementation("com.nimbusds:oauth2-oidc-sdk:10.7")
implementation("id.walt:waltid-sd-jwt-jvm:1.2306160840.0")
implementation("id.walt:waltid-sd-jwt-jvm:1.2306191408.0")

implementation("org.bouncycastle:bcprov-jdk15to18:1.72")
implementation("org.bouncycastle:bcpkix-jdk15to18:1.72")
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/cli/did/DidCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class CreateDidCommand : CliktCommand(
is WebMethodOption -> DidService.create(web, keyId, DidWebCreateOptions((method as WebMethodOption).domain, (method as WebMethodOption).path))
is EbsiMethodOption -> DidService.create(ebsi, keyId, DidEbsiCreateOptions((method as EbsiMethodOption).version))
is CheqdMethodOption -> DidService.create(cheqd, keyId, DidCheqdCreateOptions((method as CheqdMethodOption).network))
is KeyMethodOption -> DidService.create(key, keyId, DidKeyCreateOptions((method as KeyMethodOption).isJwk))
is KeyMethodOption -> DidService.create(key, keyId, DidKeyCreateOptions((method as KeyMethodOption).useJwkJcsPubMulticodec))
else -> DidService.create(DidMethod.valueOf(method.method), keyId)
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/cli/did/DidMethodOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ class CheqdMethodOption : DidMethodOption("cheqd") {
val network: String by option("-n", "--network", help = "did:cheqd - Specify the network [testnet]").choice("mainnet", "testnet").default("testnet")
}
class KeyMethodOption : DidMethodOption("key") {
val isJwk: Boolean by option("-j", "--is-jwk", help = "did:key - is jwk_jcs-pub").flag(default = false)
val useJwkJcsPubMulticodec: Boolean by option("-j", "--useJwkJcsPub", help = "did:key - use jwk_jcs-pub multicodec (0xeb51)").flag(default = false)
}
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/rest/core/DidController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ object DidController {
is WebCreateDidRequest -> DidWebCreateOptions(request.domain ?: "walt.id", request.path)
is EbsiCreateDidRequest -> DidEbsiCreateOptions(request.version)
is CheqdCreateDidRequest -> DidCheqdCreateOptions(request.network)
is KeyCreateDidRequest -> DidKeyCreateOptions(request.isJwk)
is KeyCreateDidRequest -> DidKeyCreateOptions(request.useJwkJcsPub)
else -> null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class CreateDidRequestMethodAdapter : TypeAdapter<CreateDidRequest> {
@Serializable
class KeyCreateDidRequest(
override val keyAlias: String? = null,
val isJwk: Boolean = false,
val useJwkJcsPub: Boolean = false,
) : CreateDidRequest("key")
@Serializable
class WebCreateDidRequest(
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/services/did/DidOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ sealed class DidOptions
data class DidWebCreateOptions(val domain: String?, val path: String? = null) : DidOptions()
data class DidEbsiCreateOptions(val version: Int) : DidOptions()
data class DidCheqdCreateOptions(val network: String) : DidOptions()
data class DidKeyCreateOptions(val isJwk: Boolean) : DidOptions()
data class DidKeyCreateOptions(val useJwkJcsPub: Boolean) : DidOptions()

data class DidEbsiResolveOptions(val isRaw: Boolean) : DidOptions()
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,27 @@ class DidKeyDocumentComposer(
* @param [pubKey] - the public key byte array
* @return the [Did] document
*/
private fun constructDidKey(didUrl: DidUrl, pubKey: ByteArray): DidKey = DidKey(
context = listOf(
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
),
id = didUrl.did,
verificationMethod = listOf(
VerificationMethod(
id = "${didUrl.did}#${didUrl.did}",
type = "JsonWebKey2020",
controller = didUrl.did,
publicKeyJwk = Klaxon().parse(JWK.parse(String(pubKey)).toJSONString())
)
),
assertionMethod = listOf(VerificationMethod.Reference(didUrl.did)),
authentication = listOf(VerificationMethod.Reference(didUrl.did)),
capabilityInvocation = listOf(VerificationMethod.Reference(didUrl.did)),
capabilityDelegation = listOf(VerificationMethod.Reference(didUrl.did)),
)
private fun constructDidKey(didUrl: DidUrl, pubKey: ByteArray): DidKey = "${didUrl.did}#${didUrl.identifier}".let {
DidKey(
context = listOf(
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
),
id = didUrl.did,
verificationMethod = listOf(
VerificationMethod(
id = it,
type = "JsonWebKey2020",
controller = didUrl.did,
publicKeyJwk = Klaxon().parse(JWK.parse(String(pubKey)).toJSONString())
)
),
assertionMethod = listOf(VerificationMethod.Reference(it)),
authentication = listOf(VerificationMethod.Reference(it)),
capabilityInvocation = listOf(VerificationMethod.Reference(it)),
capabilityDelegation = listOf(VerificationMethod.Reference(it)),
)
}

/**
* other types of did:key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class DidKeyFactory(
}

private fun getIdentifierComponents(key: Key, options: DidKeyCreateOptions?): IdentifierComponents =
options?.takeIf { it.isJwk }?.let {
options?.takeIf { it.useJwkJcsPub }?.let {
IdentifierComponents(JwkJcsPubMultiCodecKeyCode, getJwkPubKeyRequiredMembersBytes(key))
} ?: IdentifierComponents(getMulticodecKeyCode(key.algorithm), getPublicKeyBytesForDidKey(key))

Expand Down
7 changes: 4 additions & 3 deletions src/main/kotlin/id/walt/signatory/rest/SignatoryController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import id.walt.credentials.w3c.VerifiableCredential
import id.walt.credentials.w3c.builder.W3CCredentialBuilder
import id.walt.credentials.w3c.toVerifiableCredential
import id.walt.sdjwt.SDMap
import id.walt.sdjwt.SDMapBuilder
import id.walt.signatory.ProofConfig
import id.walt.signatory.ProofType
import id.walt.signatory.Signatory
Expand Down Expand Up @@ -110,9 +111,9 @@ object SignatoryController {

fun issueCredentialDocs() = document().operation {
it.summary("Issue a credential").operationId("issue").addTagsItem("Credentials").description(
"Based on a template (maintained in the VcLib), this call creates a W3C Verifiable Credential. Note that the '<b>templateId</b>, <b>issuerDid</b>, and the <b>subjectDid</b>, are mandatory parameters. All other parameters are optional. <br><br> This is a example request, that also demonstrates how to populate the credential with custom data: the <br><br>{<br>" + " \"templateId\": \"VerifiableId\",<br>" + " \"config\": {<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; \"issuerDid\": \"did:ebsi:zuathxHtXTV8psijTjtuZD7\",<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; \"subjectDid\": \"did:key:z6MkwfgBDSMRqXaJtw5DjhkJdDsDmRNSrvrM1L6UMBDtvaSX\",<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; \"selectiveDisclosure\": {\n" +
" \"credentialSubject\": { \"sd\": true, \"nestedMap\": { \"firstName\": { \"sd\": true }}}\n" +
"}<br>},<br>" + " \"credentialData\": {<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; \"credentialSubject\": {<br>" + " &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"firstName\": \"Severin\"<br>" + " &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; }<br>" + "}<br>"
"Based on a template (maintained in the VcLib), this call creates a W3C Verifiable Credential. Note that the '<b>templateId</b>, <b>issuerDid</b>, and the <b>subjectDid</b>, are mandatory parameters. All other parameters are optional. <br><br> This is a example request, that also demonstrates how to populate the credential with custom data: the <br><br>{<br>" + " \"templateId\": \"VerifiableId\",<br>" + " \"config\": {<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; \"issuerDid\": \"did:ebsi:zuathxHtXTV8psijTjtuZD7\",<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; \"subjectDid\": \"did:key:z6MkwfgBDSMRqXaJtw5DjhkJdDsDmRNSrvrM1L6UMBDtvaSX\",<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; \"selectiveDisclosure\": \n" +
SDMapBuilder().addField("credentialSubject", false, SDMapBuilder().addField("firstName", true).build()).build().toJSON().toString() + "\n" +
"<br>},<br>" + " \"credentialData\": {<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; \"credentialSubject\": {<br>" + " &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"firstName\": \"Severin\"<br>" + " &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>" + " &nbsp;&nbsp;&nbsp;&nbsp; }<br>" + "}<br>"
)
}.body<String>()
{ it.description("IssueCredentialRequest: templateId: String, config: ProofConfig, credentialData: JsonObject") }.json<String>("200")
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/id/walt/services/did/DidKeyCreationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class DidKeyCreationTest : StringSpec({
"Create jwk_jcs-pub did:key" {
val jwkPubKey = "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"ngy44T1vxAT6Di4nr-UaM9K3Tlnz9pkoksDokKFkmNc\",\"y\":\"QCRfOKlSM31GTkb4JHx3nXB4G_jSPMsbdjzlkT_UpPc\"}"
val keyId = keyService.importKey(jwkPubKey)
val result = createAndLoadDid(keyId, DidKeyCreateOptions(isJwk = true))
val result = createAndLoadDid(keyId, DidKeyCreateOptions(useJwkJcsPub = true))
result.id shouldBe "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
}
})
2 changes: 1 addition & 1 deletion src/test/kotlin/id/walt/services/did/DidServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class DidServiceTest : AnnotationSpec() {
val expectedResult = Did.decode(readWhenContent(File("src/test/resources/dids/did-key-jwk_jcs-pub.json")))!!
val jwkPubKey = "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"ngy44T1vxAT6Di4nr-UaM9K3Tlnz9pkoksDokKFkmNc\",\"y\":\"QCRfOKlSM31GTkb4JHx3nXB4G_jSPMsbdjzlkT_UpPc\"}"
val keyId = keyService.importKey(jwkPubKey)
val did = DidService.create(DidMethod.key, keyId.id, DidKeyCreateOptions(isJwk = true))
val did = DidService.create(DidMethod.key, keyId.id, DidKeyCreateOptions(useJwkJcsPub = true))
// when
val result = DidService.resolve(did)
// then
Expand Down
27 changes: 0 additions & 27 deletions src/test/kotlin/id/walt/signatory/SignatoryApiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -342,33 +342,6 @@ class SignatoryApiTest : AnnotationSpec() {
})
}
)
val request = "{\n" +
" \"templateId\": \"VerifiableId\",\n" +
" \"config\": {\n" +
" \"issuerDid\": \"$did\",\n" +
" \"subjectDid\": \"$did\",\n" +
" \"proofType\": \"SD_JWT\",\n" +
" \"selectiveDisclosure\": {\n" +
" \"fields\": {\n" +
" \"credentialSubject\": {\n" +
" \"sd\": true,\n" +
" \"children\": {\n" +
" \"fields\": {\n" +
" \"firstName\": {\n" +
" \"sd\": true\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"credentialData\": {\n" +
" \"credentialSubject\": {\n" +
" \"firstName\": \"Severin\"\n" +
" }\n" +
" }\n" +
"}"
val vc = httpPost("/v1/credentials/issue", KlaxonWithConverters().toJsonString(reqObj))
println(vc)
val parsedVc = vc?.toVerifiableCredential()
Expand Down
48 changes: 24 additions & 24 deletions src/test/resources/dids/did-key-jwk_jcs-pub.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
{
"assertionMethod" : [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
],
"authentication" : [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
],
"capabilityDelegation" : [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
],
"capabilityInvocation" : [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
],
"@context" : [
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id" : "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r",
"verificationMethod" : [
"id": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r",
"verificationMethod": [
{
"controller" : "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r",
"id" : "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r",
"publicKeyJwk" : {
"crv" : "P-256",
"kty" : "EC",
"x" : "ngy44T1vxAT6Di4nr-UaM9K3Tlnz9pkoksDokKFkmNc",
"y" : "QCRfOKlSM31GTkb4JHx3nXB4G_jSPMsbdjzlkT_UpPc"
},
"type" : "JsonWebKey2020"
"id": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r",
"type": "JsonWebKey2020",
"controller": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r",
"publicKeyJwk": {
"crv": "P-256",
"kty": "EC",
"x": "ngy44T1vxAT6Di4nr-UaM9K3Tlnz9pkoksDokKFkmNc",
"y": "QCRfOKlSM31GTkb4JHx3nXB4G_jSPMsbdjzlkT_UpPc"
}
}
],
"authentication": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
],
"assertionMethod": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
],
"capabilityInvocation": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
],
"capabilityDelegation": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r"
]
}

0 comments on commit 1a94b27

Please sign in to comment.