# 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 request와 create 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"}
}
}