DID 생성 및 키 관리 기능과 Verifiable Credential 의 서명과 검증에 대한 기능을 Java 또는 Android 에 제공합니다.
-
DID (탈중앙화 신원증명 : Decentralized Identity)
- 개인의 데이터를 중앙화된 기관을 거치지 않으면서도 검증이 가능하게 하는 개념.
- W3C DID Spec
-
Claim
- 전체데이터의 각 단위 데이터 입니다.
- 예를 들어 디지털 신원 정보에서 이름, 생년월일, 성별 등과 각각의 값을 페어로 claim 이라 불림.
- W3C VC Claims
-
Verifiable Credential
- 발급자, 유효기한, 검증에 사용되는 발급자의 공개키 등과 claim 의 집합과 서명을 포함하는 검증 가능한 Credential 입니다.
- 위변조가 불가능하며 예로 휴대폰본인인증, 전자신분증 등 신원인증이 있습니다.
- 발급자가 사용자의 정보를 인증하여 발급하고 사용자에게 전달됩니다.
- W3C VC Credential
-
Verifiable Presentation
- 하나 이상의 Verifiable Credential 과 소유자의 공개키와 서명을 포함하는 검증 가능한 Presentation 입니다.
- 소유자가 발급자에게서 발급 받은 credential 을 검증자에게 제출 시 사용됩니다.
- W3C VC Presentation
- 발급자(Issuer)와 사용자(Holder)는 Credential, Presentation 을 발급하기 위해 DID 를 미리 생성한다.
- 사용자는 발급자에게 Credential 발급 요청을 한다. 사용자가 발급하려는 Credential의 소유자라는 임을 확인하기 위해 DID 를 전달합니다.
- 전달하는 DID에 대한 검증이 필요할 시 Credential 을 포함하지 않는 Presentation 을 전달
- Presentation 발급
- 발급자는 사용자의 DID 를 확인하고
- DID 가 presentation 으로 제출이 되면 presentation을 검증한다.
- Presentation 검증
- 확인된 claim을 포함하는 Credential을 발급 합니다. 사용자에게 전달된 Credential은 안전한 저장공간에 저장합니다.
- 사용자 검증자(Verifier)가 요구하는 Credential을 찾아 Presentation을 만들어 제출합니다.
- 검증자는 Presentation 이 사용자가 보낸 것인지 검증하고 요구하는 발급자의 Credential 인지 검증을 합니다.
SDK 는 필수사항으로 Java 1.8 을 요구한다.
Maven 설정:
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<!-- JitPack repository 사용 -->
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.METADIUM</groupId>
<artifactId>did-sdk-java</artifactId>
<version>0.3.3</version>
<!-- <version>0.3.3-android</version> --> <!--Android 경우 해당 버전 사용 -->
</dependency>
Gradle 설정:
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dependencies {
implementation 'com.github.METADIUM:did-sdk-java:0.3.3'
//implementation 'com.github.METADIUM:did-sdk-java:0.3.3-android' // Android 경우 해당 버전 사용
}
}
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" } // JitPack repository 사용
}
}
Runtime 에서 java.lang.NoSuchMethodError: okhttp3.RequestBody.create(Ljava/lang/String;Lokhttp3/MediaType;)Lokhttp3/RequestBody;
에러가 발생하면 okhttp3 아래와 같이 추가 하십시오.
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
DID 를 생성 및 사용하기 위한 네트워크를 설정을 설명합니다.
Delegator, Node, Resolver 의 end-point 와 did prefix 를 설정을 합니다.
추가로 Metadium mainnet, testnet 을 사용시에는 apiKey 는 Metadium 에서 발급을 받아야 합니다.
String apiKey = "......"; // 발급 필요
// Metadium Mainnet 설정
MetaDelegator delegator = new MetaDelegator(apiKey);
// Metadium Testnet 설정
MetaDelegator delegator = new MetaDelegator("https://testdelegator.metadium.com", "https://api.metadium.com/dev", "did:meta:testnet", apiKey);
DIDResolverAPI.getInstance().setResolverUrl("https://testnetresolver.metadium.com/1.0/");
// Custom network 설정. private network 일때 해당 네트워크에 각 end-point 를 설정합니다.
MetaDelegator delegator = new MetaDelegator("https://custom.delegator.metadium.com", "https://custom.api.metadium.com", "did:meta:custom");
DIDResolverAPI.getInstance().setResolverUrl("https://custom.resolver.metadium.com/1.0/");
DID 생성/삭제 등의 기능을 설명합니다.
Secp256k1 key pair 를 생성하고 해당 키로 DID 를 생성한다.
// 임의의 키로 DID 생성
MetadiumWallet wallet = MetadiumWallet.createDid(delegator);
// Wallet 에서 생성된 DID 정보 확인
MetadiumKey key = wallet.getKey(); // Getting key. DID 를 생성시킨 개인키
String did = wallet.getDid(); // 생성된 DID. ex) did:meta:00000000000000000000000000000000000000000000000000000000000432a0
String kid = wallet.getKid(); // 생성된 private key 의 id. ex)did:meta:00000000000000000000000000000000000000000000000000000000000432a0#MetaManagementKey#234f9445cd405a2a454245b94f7bc5e9286912eb
BigInteger privateKey = wallet.getKey().getPrivateKey(); // EC private key. bigint
ECPrivateKey ecPrivateKey = wallet.getKey().getECPrivateKey(); // EC private key. ECPrivateKey
DID 를 삭제한다.
wallet.deleteDid(delegator);
DID Document 정보를 얻는다.
DidDocument didDocument = wallet.getDidDocument();
DID 가 블록체인에 존재하는지 확인한다.
wallet.existsDid(delegator);
// serialize
String walletJson = wallet.toJson();
// Java : wallet json 을 파일을 암호화 하여 저장한다.
//
// Android : wallet json 을 AndroidKeystore 로 암호화 하여 파일 또는 SharedPreference 에 저장한다.
// https://developer.android.com/reference/androidx/security/crypto/package-summary 참조
// Java : 파일 복호화
//
// Android : 파일 또는 SharedPreference 에서 복호화
// https://developer.android.com/reference/androidx/security/crypto/package-summary 참조
// deserialize
MetadiumWallet newWallet = MetadiumWallet.fromJson(walletJson);
DID의 키로 서명(Elliptic Curve Digital Signature Algorithm)을 합니다.
byte[] message = "message".getBytes();
SignatureData signature = wallet.getKey().sign(message);
서명값을 16진수 문자열로 변화하는 예시 함수입니다.
public static String signatureDataToString(Sign.SignatureData signatureData) {
ByteBuffer buffer = ByteBuffer.allocate(65);
buffer.put(signatureData.getR());
buffer.put(signatureData.getS());
buffer.put(signatureData.getV());
return Numeric.toHexString(buffer.array());
}
Verifiable credential, Verifiable presentation 을 발급 및 검증 하는 방법을 설명합니다.
verifiable credential 을 발급한다.
발급자(issuer)는 DID 가 생성되어 있어야 하며 credential 의 이름(types), 사용자(holder)의 DID, 발급할 내용(claims) 가 필수로 필요하다.
아래는 여러 claim을 포함하는 credential 을 발급 하는 예제이다.
// set claims
Map<String, Object> claims = new HashMap<>();
claims.put("name", "YoungBaeJeon");
claims.put("birth", "19800101");
claims.put("id", "800101xxxxxxxx");
SignedJWT vc = wallet.issueCredential(
Collections.singletonList("PersonalIdCredential"), // types : credential 의 이름. "Credential" 로 끝나야 함.
URI.create("http://aa.metadium.com/credential/name/343"), // id : credential을 검증할 수 있는 고유 URL 을 입력해야 하며 필수는 아님.
issuanceDate, // issuance date
expirationDate, // expiration date
"did:meta:0000000...00001345", // ownerDid (holder의 did)
claims // claims
);
String personalIdVC = vc.serialize();
위와 같이 credential 을 발급 받은 경우 검증자에게는 해당 credential을 그대로 넘겨야 하기 때문에 특정 claim 만 선택해서 보내거나 불필요한 claim을 감춰서 보낼 수는 없다.
특정 claim 만 선택해서 보내기 위해서는 아래와 같이 검증자가 claim 별로 credential 을 선택적으로 제출받을 수 있도록 claim 단위별로 credential 을 나누어서 발급자가 발급해야 한다.
SignedJWT vc = wallet.issueCredential(
Arrays.asList("PersonalIdCredential", "NameCredential"), // 표현할 credential 의 이름을 나열. PersonalIdCredential의 NameCredential
URI.create("http://aa.metadium.com/credential/name/343"),
issuanceDate,
expirationDate,
"did:meta:0000000...00001345",
Collections.singletonMap("name", "YoungBaeJeon") // name
);
String nameVC = vc.serialize();
SignedJWT vc = wallet.issueCredential(
Arrays.asList("PersonalIdCredential", "BirthCredential" // 표현할 credential 의 이름을 나열. PersonalIdCredential의 BirthCredential
URI.create("http://aa.metadium.com/credential/name/343"),
issuanceDate,
expirationDate,
"did:meta:0000000...00001345",
Collections.singletonMap("birth", "19800101") // birth
);
String birthVC = vc.serialize();
SignedJWT vc = wallet.issueCredential(
Arrays.asList("PersonalIdCredential", "IdCredential") // 표현할 credential 의 이름을 나열. PersonalIdCredential의 IdCredential
URI.create("http://aa.metadium.com/credential/name/343")
issuanceDate,
expirationDate,
"did:meta:0000000...00001345",
Collections.singletonMap("id", "800101xxxxxxxx") // id
);
String idVC = vc.serialize();
전달해야 하는 credential 의 목록을 포함하여 presentation 을 발급한다. 검증자는 전달해야 하는 credential의 types 를 소유자에게 알려줘야 하며 소유자는 해당하는 Credential 을 presentation으로 전달해야 하며 발급된 presentation 은 검증자에게 전달하여 검증을 받는다.
검증자가 2개의 credential 을 요청하고 소유자가 presentation을 발급하여 전달하는 예제 (주민등록증, 운전면허증 요구)
// 검증자 요청 예제 : {"types":["TestPresentation"], "vc":[["PersonalIdCredential"], ["DriverLicenseCredential"]]}
List<String> holderAllVc = ..; // 소유자의 전체 credential 목록
String foundVcList = findVC(holderAllVc, Arrays.asList(Arrays.asList("PersonalIdCredential"), Arrays.asList("DriverLicenseCredential"));
SignedJWT vp = userWallet.issuePresentation(
Collections.singletonList("TestPresentation"), // presentation 의 이름. suffix 로 "Presentation"으로 끝나야 하며 검증자가 지정해서 소유자에게 알려줘야 함.
URI.create("http://aa.metadium.com/presentation/343"), // id : presentation 을 검증할 수 있는 고유 URL 을 입력해야 하며 필수는 아님.
issuanceDate, // issuance date
expirationDate, // expiration date
foundVcList // 전달할 credential 목록. 보통 전달해야할 credential 목록은 검증자가 소유자에게 알려줘야 함.
);
String serializedVP = vp.serialize();
위와 같이 하나의 credential에 여러 claim 을 포함하여 발급하는 경우에는 특정 claim 만 요구하여 제출받을 수 없습니다. 이러한 경우에는 claim 별로 credential 이 나눠서 발급하여 제출할 수 있으며 아래는 예제 입니다. (주민등록증의 이름과 id 만 제출 요청)
// 검증자 요청 예제 : {"types":["TestPresentation"], "vc":[["PersonalIdCredential", "NameCredential"], ["PersonalIdCredential", "IdCredential"]]}
List<String> holderAllVc = ..; // 소유자의 전체 credential 목록
String foundVcList = findVC(holderAllVc, Arrays.asList(Arrays.asList("PersonalIdCredential", "NameCredential"), Arrays.asList("PersonalIdCredential", "IdCredential"));
SignedJWT vp = userWallet.issuePresentation(
Collections.singletonList("TestPresentation"), // presentation 의 이름. suffix 로 "Presentation"으로 끝나야 하며 검증자가 지정해서 소유자에게 알려줘야 함.
URI.create("http://aa.metadium.com/presentation/343"), // id : presentation 을 검증할 수 있는 고유 URL 을 입력해야 하며 필수는 아님.
issuanceDate, // issuance date
expirationDate, // expiration date
foundVcList // 전달할 credential 목록. 보통 전달해야할 credential 목록은 검증자가 소유자에게 알려줘야 함.
);
String serializedVP = vp.serialize();
소유자 credential 목록에서 검증자가 요구하는 credential 찾는 예제
/**
* @param holderVcList 사용자 credential 목록
* @param typesOfRequireVcs 검증자가 요구하는 credential types 목록
*/
public List<String> findVC(List<String> holderVcList, List<List<String>> typesOfRequireVcs) {
List<String> ret = new ArrayList<>();
for (String serializedVc : holderVcList) {
VerifiableCredential credential = new VerifiableCredential(SignedJWT.parse(serializedVc));
for (List<String> types : typesOfRequireVcs) {
if (credential.getTypes().containsAll(types)) {
ret.add(serializedVc);
}
}
}
return ret;
}
네트워크가 메인넷이 안닌 경우 검증 전에 resolver URL 이 설정되어 있어야 정상적이 검증이 가능하다. Setup Network 참조
전달받은 credential 또는 presentation 을 검증을 한다.
Verifier verifier = new Verifier();
// 전달받은 서명된 credential 파싱
SignedJWT vc = SignedJWT.parse(serializedVC);
// credential 검증
if (!verifier.verify(vc)) {
// 검증 실패
}
else if (vc.getJWTClaimsSet().getExpirationTime() != null && vc.getJWTClaimsSet().getExpirationTime().getTime() < new Date().getTime()) {
// 유효기간 초과
}
presentation 에 나열되어 있는 credential 내역을 확인한다.
VerifiablePresentation vpObj = new VerifiablePresentation(SignedJWT.parse(serializedVp));
String holderDid = vpObj.getHolder().toString(); // Presentation 제출
URI vpId = vpObj.getId(); // presentation ID
for (Object o : vpObj.getVerifiableCredentials()) {
String serializedVc = (String)o;
}
credential 에 나열되어 있는 claim 의 내역을 확인한다.
VerifiableCredential credential = new VerifiableCredential(SignedJWT.parse(serializedVc));
Map<String, String> subjects = vc.getCredentialSubject();
for (Map.Entry<String, String> entry : subjects.entrySet()) {
// claim 정보
String claimName = entry.getKey();
String claimValue = entry.getValue();
}