# Verifiable Credential Data Model

MyID 에서 발급하고 검증하는 credential은 W3C의 Verifiable Credential Data Model (opens new window)규격을 따릅니다. 또한 credential의 claim을 표현하는 규격으로 JSON-LD (opens new window)를 사용합니다. 그리고 credential 을 서명하고 암호화 하기 위해 JWT (opens new window)로 credential을 감싸고 있습니다.

위 세 규격은 상당히 복잡한 내용을 담고 있으나, MyID 에 참여하는 issuer, verifier들은 위의 규격을 상세히 이해하고 있지 않아도 credential을 발급하고 사용하는 데 어려움이 없도록 SDK와 가이드를 제공합니다.

새로운 credential 형식을 정의하고 발급하기 위해서는 위 규격들에 대한 이해가 필요합니다.
(만약 새로운 형식의 credential을 발급 하고자 한다면 파라메타 담당자에게 문의를 해서 도움을 받으시기 바랍니다.)

# MyID Credential Data Model

파라메타에서 정의한 휴대폰 본인인증 credential은 아래와 같은 형태로 제공이 됩니다.

휴대폰 본인인증 credential

// Credential 내용
{
   "version": "2.0",
   "type": "CREDENTIAL",
   "iss": "did:icon:03:c3bb75caef41476db6c8bab94a38df530d225e840bf5448c",
   "sub": "did:icon:03:a4b1234fsweqwexdb6c8bab94a38df530d225e840b1211d",
   "iat": 1578445403,
   "exp": 1578531803,
   "vc": {
       "@context": [
           "http://vc.zzeung.kr/credentials/v1.json",
           "http://vc.zzeung.kr/credentials/mobile_authentication/kor/v1.json"
       ],
       "id": "https://myid.id/credential/example/phone/vc/0000001",
       "type": ["VerifiableCredential","MobileAuthenticationKorCredential"],
       "cryptoType": "hash",
       "cryptoAlgorithm": "SHA-256",
       "credentialSubject": {
           "id": "did:icon:03:a4b1234fsweqwexdb6c8bab94a38df530d225e840b1211d",
           "name": "a3445c2236841af6bf25d0c3b9196f968082de2ba7e1657001e1dd4dbf8d894",
           "birthDate": "cc183c2236841af6bf25d0c3b9196f968082de2ba7e1657001e1dd4dbf8",
           "gender": "5583c2236841af6bf25d0c3b9196f968082de2ba7e1657001e1dd4dbf8d894",
           "telco": "108a3c2236841af6bf25d0c3b9196f968082de2ba7e1657001e1dd4dbf8d894",
           "phoneNumber": "123383c2236841af6bf25d0c3b9196f968082de2ba7e1657001e1dd4d",
           "connectingInformation": "ff383c2236841af6bf25d0c3b9196f968082de2ba7e165700",
           "citizenship": "ff383c2236841af6bf25d0c3b9196f968082de2ba7e1657001e1dd4"
       },
       "refreshService": {
           "id": "https://vc.example.com/refresh_service/",
           "type": "ManualRefreshService"
       }
   },
   "jti": "885c592008a5b95a8e348e56b92a2361",
   "nonce": "b0f184df3f4e92ea9496d9a0aad259ae"
}

// Credential 외부의 Parameter 내용
{
   "@context": [
       "http://vc.zzeung.kr/credentials/v1.json",
       "http://vc.zzeung.kr/credentials/mobile_authentication/kor/v1.json"
   ],
   "type": ["CredentialParam", "MobileAuthenticationKorCredential"],
   "credentialParam": {
       "claim": {
           "name": {
               "claimValue": "홍길순",
               "salt": "a1341c4b0cbff6bee9118da10d6e85a5"
           },
           "birthDate": {
               "claimValue": "2000-01-01",
               "salt": "65341c4b0cbff6bee9118da10d6e85a5"
           },
           "gender": {
               "claimValue": "female",
               "salt": "12341c4b0cbff6bee9118da10d6e85a5",
               "displayValue": "여성"
           },
           "telco": {
               "claimValue": "SKT",
               "salt": "91341c4b0cbff6bee9118da10d6e85a5"
           },
           "phoneNumber": {
               "claimValue": "01031142962",
               "salt": "e2341c4b0cbff6bee9118da10d6e85a5"
               "displayValue": "010-3114-2962"
           },
           "connectingInformation": {
               "claimValue": "0000000000000000000000000000000000000000",
               "salt": "ff341c4b0cbff6bee9118da10d6e85a5"
           },
           "citizenship": {
               "claimValue": true,
               "salt": "f2341c4b0cbff6bee9118da10d6e85a5",
               "displayValue": "내국인"
           }
       },
       "info": {
           "description": {
               "@type": [ "TextView" ],
               "name": "VC발급 동의내역",
               "content" : "2021년 1월22일에 example.com에서 본인인증 동의를 하고\nVC를 발급했습니다."
           }
       },

       "displayLayout": ["name", "gender", "phoneNumber", "telco", "birthDate"],
       "proofType": "hash",
       "hashAlgorithm": "SHA-256"
   }
}

# Selective Disclosure

Credential 내부의 claim은 실제 정보를 가지고 있지 않습니다. Credential 자체는 네트워크를 통해서 여러 verifier들에게 제출이 될 수 있기 때문에 holder의 개인 정보를 보호하기 위한 조치 입니다.

Credential을 제출할 때는 credential과 함께 verifier가 필요로 하는 (holder가 승인한) claim 만 parameter 형태로 전달이 됩니다. 이렇게 선택적으로 holder의 정보를 노출 하는 것을 selective disclosure라고 합니다.

# Display Value

Credential의 Claim 실제 값은 parameter 부분에 선언되어 있습니다. 때때로 credential이 주장 하고자 하는 값과, 쯩 앱에서 화면에 노출하고자 하는 값의 형태가 다른 경우가 있습니다.

아래 ‘gender’라는 property는 ‘female’ 이라는 값을 가집니다. 그리고 이를 처리하는 프로세서는 ‘female’ 이라는 단어를 인지 합니다. 하지만 쯩 앱에서는 ‘여성' 이라는 text로 사용자에게 노출하고 싶을 수 있습니다. 이때 아래와 같이 claim의 value와 관계 없이 화면에 표시를 하기 위한 "displayValue"를 별도로 추가해 주시면 됩니다. Server Implementation Guide를 참고하십시오.

"gender": {
   "claimValue": "female",
   "salt": "12341c4b0cbff6bee9118da10d6e85a5",
   "displayValue": "여성"
}

# Display Layout

Credential의 claim내용은 쯩 앱의 credential 상세화면에 노출 됩니다. 화면에 노출하는 여러가지 방법들이 있습니다.

화면에 보이는 순서
displayLout 배열에 선언되는 순서대로 화면에 표시가 됩니다.

"displayLayout": ["name", "gender", "birthDate", "telco", "phoneNumber"]

아래 그림과 같이 앱에 표시됩니다.

화면에 표시하지 않기
상세화면에서 claim을 표시하지 않고 싶다면 displayLayout의 배열에 포함하지 않으면 됩니다.

# Display Information

Claim이 아닌 정보를 제공하는 데이터를 credential에 추가하고 싶을 수 있습니다.
예를 들면 어떤 쿠폰번호를 가지는 credential이 있다고 하면 쿠폰넘버는 claim 으로 취급해서 검증을 수행합니다. 하지만 쿠폰과 관련된 부가정보(쿠폰 사용시 유의사항등)는 claim일 필요가 없지만 credential에 포함이 되어서 쯩 앱의 상세화면에 이 내용을 보여주고 싶을수 있습니다. 보통 이런 정보는 또한 상당히 많은 내용을 담고 있습니다. 때문에 이런 정보성 내용을 claim으로 취급하지 않고, 정보성 내용으로 credential의 param에 추가할 수 있습니다.

# TextView

@type이 "TextView"인 경우 쯩 앱에서 멀티라인 텍스트를 보여주는 UI 컴포넌트를 사용해서 사용자에게 보여줍니다.

"info": {
   "description": {
       "@type": [ "TextView" ],
       "name": "VC발급 동의내역",
       "content" : "2021년 1월22일에 example.com에서 본인인증 동의를 하고\nVC를 발급했습니다."
   }
},
  • @type: TextView
  • name: 이름을 표시합니다. 이 property가 없거나 빈 문자열인 경우 화면에 표시하지 않습니다.
  • content: 텍스트 내용을 표시합니다.

# WebView (TBD)

# Blockchain에서 Credential 폐기

Issuer는 자신이 발급한 VC를 blockchain에서 폐기할 수 있습니다. Issuer의 필요에 의해, 혹은 VC의 holder의 요청을 받는 경우 서버 개발 가이드의 Revoke Credential을 이용해서 폐기를 하십시오.

# Revocation Service

Issuer는 발급한 VC를 폐기 처리하는 서비스를 제공할 수 있습니다. 이 서비스를 제공하는 경우 쯩 앱의 VC상세화면에 "폐기"라는 버튼이 활성화 됩니다.

# ManualRevocationService (TBD)

ManualRevocationService를 추가하는 경우 해당 URL의 웹 서비스를 통해서 폐기 서비스를 제공할 수 있습니다. 쯩 앱은 WebView를 통해 폐기를 위한 웹 서비스를 실행합니다.

"revocationService": {
     "id": "https://example.com/revoke/vc",
     "type": "ManualRevocationService"
}

위 revocationService를 입력 하기 위해 개발가이드 문서의 ManualeRevocationService를 참고하십시오.

# SimpleRevocationService

Issuer가 사용자에게 폐기화면을 제공하지 않고, 간단하게 폐기 서비스를 제공합니다. 때문에 쯩 앱과 issuer는 폐기를 위한 VCP 메세지를 주고 받기 위한 약속을 합니다.

Credential에 아래와 같은 property를 추가 하십시오.

"revocationService": {
     "id": "https://example.com/revoke/vc",
     "type": "SimpleRevocationService",
     "shortDescription": "휴대폰 본인인증 VC를 폐기합니다.\n 이 VC는 앞으로 사용될 수 없습니다. "
}

위 revocationService를 입력 하기 위해 개발가이드 문서의 SimpleRevocationService를 참고하십시오.

폐기 요청과 응답 메세지를 처리하는 코드는 서버 개발가이드verify revocation requestcreate revocation response를 참고하십시오.

Issuer는 아래의 REST API 규격을 구현해야 합니다.

URL : https://example.com/revoke/vc
Method : POST
Content-Type : application/json
Parameter 
{
  "jwt": "eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6aWNvbjowMTo4MTUzYTE3YjYxZjhkZWEzOTIzN2M5MjM0MDYyZGY5NDA2YzVlMTk0MTIwOTc2N2Yjc2FtcGxlS2V5MSJ9.eyJpc3N1ZXJEaWQiOiJkaWQ6aWNvbjowMTo1ODY4ZWU0ZmY1Njg2ZjNlNjc4MzY2YmVjOTZmOGMxMzY0MzIyZDBhOGVkMDFlYWEiLCJvd25lckRpZCI6ImRpZDppY29uOjAxOjgxNTNhMTdiNjFmOGRlYTM5MjM3YzkyMzQwNjJkZjk0MDZjNWUxOTQxMjA5NzY3ZiIsImlzc3VlRGF0ZSI6MTYwMDExMDAsInNpZyI6Ikh4b3Z5Vll1S3lnVmZSODQ2aERTaGEyR01Ca18ta0lrNnRrOG1rWVByWlJXQ0pEMjdJY01EOHBPTklIeFlQRUIzQXZNVFRNeW84Tm0teWFUbEkyMTZBRT0ifQ.mTMrHIwTUHOJUNHNmRDLXteY8PJppF5Sd5NhA9fmsSE"
}
Response
{
  "jwt": "eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6aWNvbjowMTo4MTUzYTE3YjYxZjhkZWEzOTIzN2M5MjM0MDYyZGY5NDA2YzVlMTk0MTIwOTc2N2Yjc2FtcGxlS2V5MSJ9.eyJpc3N1ZXJEaWQiOiJkaWQ6aWNvbjowMTo1ODY4ZWU0ZmY1Njg2ZjNlNjc4MzY2YmVjOTZmOGMxMzY0MzIyZDBhOGVkMDFlYWEiLCJvd25lckRpZCI6ImRpZDppY29uOjAxOjgxNTNhMTdiNjFmOGRlYTM5MjM3YzkyMzQwNjJkZjk0MDZjNWUxOTQxMjA5NzY3ZiIsImlzc3VlRGF0ZSI6MTYwMDExMDAsInNpZyI6Ikh4b3Z5Vll1S3lnVmZSODQ2aERTaGEyR01Ca18ta0lrNnRrOG1rWVByWlJXQ0pEMjdJY01EOHBPTklIeFlQRUIzQXZNVFRNeW84Tm0teWFUbEkyMTZBRT0ifQ.mTMrHIwTUHOJUNHNmRDLXteY8PJppF5Sd5NhA9fmsSE"
}

요청과 응답에 사용되는 JWT Message는 Revocation Message를 참고하십시오.

에러 코드는 Appendix의 Revocation Request & Revocation Result를 참고하십시오.

# JSON-LD Context

Credential에서 claim 정보를 표현 하는 방법으로 JSON-LD (opens new window)를 사용합니다.

위 예제에서 ‘name’, ‘birthDate’ 등의 key 값을 JSON-LD (opens new window)에서는 property라고 합니다.

또한 이 property는 IRI (opens new window)라고 하는 유일한 식별자을 가지고 있어야 합니다. 이를 위해 위 내용에서 @context에 있는 아래와 같은 context 파일에 대한 정의가 필요하고 해당 파일은 웹 상에 반드시 게시 되어 있어야 합니다.

새로운 credential data를 정의하고 credential을 발급하는 것은 issuer가 JSON-LD (opens new window)Verifiable Credential Data Model (opens new window) 규격에 맞게 작성을 하면 됩니다. 다만 이 내용을 이해하고, @context 파일을 작성하고, 인터넷에 게시하는 것은 쉽지 않을 수 있습니다.

(만약 새로운 형식의 Credential을 발급 하고자 한다면 파라메타 담당자에게 문의를 해서 도움을 받으시기 바랍니다.)

위 JSON-LD Context 의 내용은 아래와 같습니다.

{
    "@context": ["https://www.w3.org/2018/credentials/v1", {
        "@version": 1.1,
        "@protected": true,
        "xsd": "http://www.w3.org/2001/XMLSchema#",
        "zcred": "https://vc.zzeung.kr/credentials#",

        "cryptoType": "zcred:cryptoType",
        "cryptoAlgorithm": "zcred:cryptoAlgorithm",

        "ManualRefreshService": "zcred:ManualRefreshService",
        "AutomaticRefreshService": "zcred:AutomaticRefreshService",

        "NoDisplay": "zcred:NoDisplay",
        "OneTime": "zcred:OneTime",

        "PresentationRequest": {
            "@id": "zcred:PresentationRequest",
            "@context": {
                "@version": 1.1,
                "@protected": true,
                "presentationUrl": {"@id": "zcred:presentationUrl", "@type": "@id"},
                "presentationRequest": {
                    "@id": "zcred:presentationRequest",
                    "@type": "@id",
                    "@context": {
                        "@version": 1.1,
                        "@protected": true,
                        "purpose": "zcred:purpose",
                        "purposeLabel": "zcred:purposeLabel",
                        "verifier": {"@id": "zcred:verifier", "@type": "@id"},
                        "condition": {"@id": "zcred:condition", "@type": "@id"}
                    }
                }
            }
        },

        "SimpleCondition": {
            "@id": "zcred:SimpleCondition",
            "@context": {
                "conditionId": "zcred:conditionId",
                "issuer": {"@id": "zcred:issuer", "@type": "@id"},
                "property": {"@id": "zcred:property", "@type": "@vocab"},
                "credentialType": {"@id": "zcred:credentialType", "@type": "@vocab"}
            }
        },

        "CompoundCondition": {
            "@id": "zcred:CompoundCondition",
            "@context": {
                "operator": "zcred:operator"
            }
        },

        "CredentialParam": {
            "@id": "zcred:CredentialParam",
            "@context": {
                "@version": 1.1,
                "@protected": true,
                "credentialParam": {
                    "@id": "zcred:credentialParam",
                    "@type": "@id",
                    "@context": {
                        "@version": 1.1,
                        "@protected": true,
                        "claim": {"@id": "zcred:claim", "@type": "@id"},
                        "claimValue": "zcred:claimValue",
                        "displayValue": "zcred:displayValue",
                        "salt": "zcred:salt",
                        "proofType": "zcred:proofType",
                        "hashAlgorithm": "zcred:hashAlgorithm",
                        "displayLayout": {"@id": "zcred:displayLayout", "@type": "@vocab"}
                    }
                }
            }
        },

        "PresentationResponse": {
            "@id": "zcred:PresentationResponse",
            "@context": {
                "@version": 1.1,
                "@protected": true,
                "presenter": {"@id": "zcred:presenter", "@type": "@id"},
                "fulfilledCriteria": {
                    "@id": "zcred:PresentationRequest#fulfilledCriteria",
                    "@context": {
                        "@version": 1.1,
                        "@protected": true,
                        "conditionId": "zcred:conditionId",
                        "verifiableCredential": "zcred:verifiableCredential",
                        "verifiableCredentialParam": {"@id": "zcred:verifiableCredentialParam", "@type": "@id"}
                    }
                }
            }
        }

    }]
}
{
    "@context": {
        "@version": 1.1,
        "@protected": true,
        "xsd": "http://www.w3.org/2001/XMLSchema#",
        "schema": "http://schema.org/",
        "zvocab": "https://vc.zzeung.kr/vocab#",

        "MobileAuthenticationKorCredential": "zvocab:MobileAuthenticationKorCredential",

        "name": "schema:name",
        "birthDate": {"@id": "schema:birthDate", "@type": "xsd:date"},
        "gender": "schema:gender",
        "telco": "zvocab:telco",
        "phoneNumber": "schema:telephone",
        "connectingInformation": "zvocab:connectingInformation",
        "citizenship": {"@id": "zvocab:citizenship", "@type": "xsd:boolean"}
    }
}