# Sample Source Code

예제로 사용된 전체 소스코드입니다.

# Source code for issuer

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import foundation.icon.did.Credential;
import foundation.icon.did.Presentation;
import foundation.icon.did.core.Algorithm;
import foundation.icon.did.core.AlgorithmProvider;
import foundation.icon.did.core.DidKeyHolder;
import foundation.icon.did.document.Document;
import foundation.icon.did.document.PublicKeyProperty;
import foundation.icon.did.exceptions.KeyPairException;
import foundation.icon.did.jwe.ECDHKey;
import foundation.icon.did.jwe.EphemeralPublicKey;
import foundation.icon.did.jwt.Jwt;
import foundation.icon.did.jwt.Payload;
import foundation.icon.did.protocol.ClaimRequest;
import foundation.icon.did.protocol.ProtocolMessage;
import foundation.icon.did.protocol.ProtocolType;
import foundation.icon.did.protocol.jsonld.*;
import foundation.icon.icx.KeyWallet;
import foundation.icon.myid.BaseService;
import foundation.icon.myid.IssuerService;
import foundation.icon.myid.credential.CredentialInfo;
import lombok.Data;

import javax.validation.constraints.NotNull;
import java.io.File;
import java.security.PublicKey;
import java.util.*;

public class PhoneVCIssVrfFullSource {
    // Issuer 또는 Verifier의 KeyHolder. DID 및 privateKey를 가지고 있음
    private DidKeyHolder didKeyHolder;
    private String nonce;   // 각 Holder마다 다른 nonce
    private ECDHKey ecdhKey;
    // DID 조회 및 VC 발급 시 사용하기 위한 클래스
    private IssuerService issuerService;

    // Holder Information
    private String holderDid;
    private EphemeralPublicKey holderEcdhPubKey;

    // MyID 서비스를 사용하기 위한 MyID Server URL
    private static final String MYID_WAS_URL = "https://iv-test.zzeung.kr";


    private void init() {
        try {
            String did = "did:icon:5000:9c87958f05ef8bdaa21767fe10b85666e25189f59cf635aa";
            KeyWallet wallet = KeyWallet.load("P@ssw0rd", new File("/did_icon_5000_9c87958f05ef8bdaa21767fe10b85666e25189f59cf635aa.json"));
            didKeyHolder = getDidKeyHolder(wallet, did);

            nonce = JsonLdUtil.getRandomNonce(32);
            ecdhKey = ECDHKey.generateKey(ECDHKey.CURVE_P256K);
        } catch (Exception e) {
            e.printStackTrace();
        }

        issuerService = IssuerService.create(MYID_WAS_URL);
    }

    private DidKeyHolder getDidKeyHolder(KeyWallet wallet, String did) throws KeyPairException {
        AlgorithmProvider.Type type = AlgorithmProvider.Type.ES256K;
        Algorithm algorithm = AlgorithmProvider.create(type);

        return new DidKeyHolder.Builder()
                .did(did)
                .keyId("key-1") // 고정
                .privateKey(algorithm.byteToPrivateKey(wallet.getPrivateKey().toByteArray()))
                .type(type)
                .build();
    }

    public IvProtocolMessage didInit() throws Exception {
        EphemeralPublicKey serverPublicKey = generateEphemeralPublicKey(nonce, ecdhKey);
        ClaimRequest claimRequest = new ClaimRequest.DidBuilder(ClaimRequest.Type.INIT)
                .version("2.0") // 고정
                .nonce(nonce)
                .didKeyHolder(didKeyHolder)
                .publicKey(serverPublicKey)
                .build();

        ProtocolMessage protocolMessage = new ProtocolMessage.RequestBuilder()
                .type(ProtocolType.DID_INIT)
                .claimRequest(claimRequest)
                .build();

        ProtocolMessage.SignResult signResult = protocolMessage.signEncrypt(didKeyHolder);

        if (!signResult.isSuccess())
            throw new Exception();  // 서명 실패

        return new IvProtocolMessage(signResult.getResult());
    }
    private EphemeralPublicKey generateEphemeralPublicKey(String nonce, ECDHKey ecdhKey) {
        return new EphemeralPublicKey.Builder()
                .kid(nonce)
                .epk(ecdhKey)
                .build();
    }

    public boolean verifyDidAuth(IvProtocolMessage ivProtocolMessage) throws Exception {
        // parse did_auth message
        ProtocolMessage authMessage = ProtocolMessage.valueOf(ivProtocolMessage.toString());
        // decrypt did_auth message (JWE -> JWT)
        if (authMessage.isProtected()) {
            authMessage.decryptJwe(ecdhKey);
        }

        // verify did_auth message (JWT)
        boolean authVerifyResult = verifyProtocolMessage(authMessage, ProtocolType.DID_AUTH.getTypeName());
        if (!authVerifyResult) {
            // did_auth message 서명 검증 실패
            return false;
        }

        String nonce = authMessage.getClaimResponseJwt().getNonce();
        if(!this.nonce.equals(nonce)) {
            return false;  // nonce unmatched
        }

        holderDid = authMessage.getClaimResponseJwt().getDid();
        holderEcdhPubKey = generateEphemeralPublicKey(nonce, authMessage.getJwe().getJweHeader().getEpk());

        return true;
    }

    private boolean verifyProtocolMessage(ProtocolMessage protocolMessage, String type) throws Exception {
        if (type == null) throw new IllegalArgumentException("The type for VCP message is null!");

        if (!protocolMessage.getType().equalsIgnoreCase(type)) {
            throw new Exception();  // VCP message's type is mismatched
        }

        Jwt jwt = protocolMessage.getJwt();

        boolean verifyResult = verifyJwt(jwt);
        if (!verifyResult) {
            return false;
        }
        // VCP message 발급 시간 확인
        verifyResult = jwt.verifyIat(3 * 60 * 1000L).isSuccess();
        return verifyResult;
    }

    private boolean verifyJwt(Jwt jwt) throws Exception {
        // jwt's getDid()
        String did = jwt.getHeader().getKid().split("#")[0];
        // jwt's getKeyId()
        String keyId = jwt.getHeader().getKid().split("#")[1];
        // document 조회
        Document doc = issuerService.getDid(did);
        if (doc == null) {
            return false;   // Failed to get DID Document from Blockchain
        }

        PublicKeyProperty publicKeyProperty = doc.getPublicKeyProperty(keyId);
        if (publicKeyProperty == null || publicKeyProperty.isRevoked()) {
            // DID Document is invalid
            return false;
        } else {
            PublicKey publicKey = publicKeyProperty.getPublicKey();
            Jwt.VerifyResult verifyResult = jwt.verify(publicKey);

            return verifyResult.isSuccess();
        }
    }

    public IvProtocolMessage issueCredential() throws Exception {
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("name", "홍길동");
        claims.put("birthDate", "19981010");
        claims.put("gender", "1");
        claims.put("telco", "LGT");
        claims.put("phoneNumber", "01012345678");
        claims.put("connectingInformation", "ddd/wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww8==");
        claims.put("citizenship", "0");

        MobileAuthenticationKorCredential mobileAuthenticationKorCredential = new MobileAuthenticationKorCredential();
        mobileAuthenticationKorCredential.setClaims(claims);
        ProtocolMessage credProtocolMessage = mobileAuthenticationKorCredential.createCredential(
                nonce,
                holderDid,
                holderEcdhPubKey,
                didKeyHolder,
                365 * 24 * 60 * 60 * 1000L);   // VC 유효 기간 1년

        ProtocolMessage.SignResult credSignResult = credProtocolMessage.signEncrypt(didKeyHolder, this.ecdhKey);
        if(!Boolean.TRUE.equals(credSignResult.isSuccess())) {
            // VC 서명 실패
            throw new Exception();
        }

        Credential credential = Credential.valueOf(credProtocolMessage.getJwt());
        BaseService.ServiceResult registerResult = issuerService.registerVC(credential, didKeyHolder);
        if(!Boolean.TRUE.equals(registerResult.isSuccess())) {
            // Blockchain에 VC 등록 실패
            throw new Exception();
        }

        return new IvProtocolMessage(credSignResult.getResult());
    }

    public IvProtocolMessage createVerifiablePresentationRequest() throws Exception {
        String nonce = JsonLdUtil.getRandomNonce(32);
        String conditionId = UUID.randomUUID().toString();
        VprCondition condition = new VprCondition.SimpleBuilder()
                .conditionId(conditionId)
                .context(Collections.singletonList("https://vc.zzeung.kr/credentials/mobile_authentication/kor/v1.json"))
                .credentialType(MobileAuthenticationKorCredential.VC_TYPE)
                .property(Arrays.asList("name", "birthDate", "phoneNumber"))
                .build();
        JsonLdVpr vpr = new JsonLdVpr.Builder()
                .context(Collections.singletonList("https://vc.zzeung.kr/credentials/v1.json"))
                .id("https://phonevrf.zzeung.kr/v1/verifier/vpr/" + nonce)
                .presentationUrl("https://phonevrf.zzeung.kr/v1/verifier/vp")
                .purpose("Confirmation of identity information")
                .verifier(didKeyHolder.getDid())
                .condition(condition)
                .build();
        ClaimRequest requestPresentation = new ClaimRequest.PresentationBuilder()
                .didKeyHolder(didKeyHolder)
                .requestDate(new Date())
                .nonce(nonce)
                .publicKey(generateEphemeralPublicKey(nonce, ecdhKey))
                .version("2.0")
                .vpr(vpr)
                .build();
        ProtocolMessage pm = new ProtocolMessage.RequestBuilder()
                .type(ProtocolType.REQUEST_PRESENTATION)
                .claimRequest(requestPresentation)
                .build();
        ProtocolMessage.SignResult signResult = pm.signEncrypt(didKeyHolder);
        if (!signResult.isSuccess()) {
            throw new Exception();  // vpr 생성 실패
        }

        return new IvProtocolMessage(signResult.getResult());
    }

    public boolean verifyPresentation(IvProtocolMessage ivProtocolMessage) throws Exception {
        ProtocolMessage vpMessage = ProtocolMessage.valueOf(ivProtocolMessage.toString());
        // presentation 서명 확인
        if (!verifyVp(vpMessage)) {
            return false;
        }

        Presentation presentation = vpMessage.getPresentationJwt();
        JsonLdVp jsonLdVp = presentation.getVp();
        VpCriteria vpCriteria = jsonLdVp.getFulfilledCriteria().get(0);

        // check credential's type
        List<String> vcTypes = vpCriteria.getVcParam().getTypes();
        if(!vcTypes.contains(MobileAuthenticationKorCredential.VC_TYPE)) {
            // 제출 받고자 하는 VC와 제출 받은 VC의 타입이 일치하지 않는 경우
            return false;
        }

        return true;
    }

    private boolean verifyVp(ProtocolMessage vpMessage) throws Exception {
        if (vpMessage.isProtected()) {
            vpMessage.decryptJwe(ecdhKey);
        }

        // presentation 서명 확인
        boolean vpVerifyResult = verifyProtocolMessage(vpMessage, ProtocolType.RESPONSE_PRESENTATION.getTypeName());
        if (!vpVerifyResult) {
            return false;
        }

        Presentation presentation = vpMessage.getPresentationJwt();
        VpCriteria criteria = presentation.getVp().getFulfilledCriteria().get(0);
        Credential credential = Credential.valueOf(criteria.getVc());

        // presentation sender 와 credential target did 확인 (동일한 holder인지 확인)
        if (!presentation.getDid().equals(credential.getTargetDid())) {
            return false;
        }

        // credential 서명 확인
        if (!verifyJwt(credential.getJwt())) {
            return false;
        }

        // check vc is revoked or expired
        String issuerDID = credential.getDid();
        String vcSig = credential.getJwt().getSignature();
        CredentialInfo credentialInfo = issuerService.getVC(issuerDID, vcSig);
        if (credentialInfo == null) {
            return false;   // 블록체인에 등록되지 않은 VC
        }
        if(credentialInfo.getIsRevoke() != null && credentialInfo.getIsRevoke()) {
            return false;   // 폐기된 VC
        }
        Date expiryDate = new Date(credentialInfo.getExpiryDate()*1000L);
        if(expiryDate.before(new Date())) {
            return false;   // 만료된 VC
        }

        // param 과 credentialSubject verify
        if (!criteria.isVerifyParam()) {
            return false;
        }

        return true;
    }

    public boolean revokeVc(IvProtocolMessage ivProtocolMessage) throws Exception {
        // parse request_revocation message
        ProtocolMessage revokeMessage = ProtocolMessage.valueOf(ivProtocolMessage.toString());

        // verify request_revocation message (JWT)
        boolean revokeVerifyResult = verifyProtocolMessage(revokeMessage, ProtocolType.REQUEST_REVOCATION.getTypeName());
        if (!revokeVerifyResult) {
            return false;
        }

        Payload payload = revokeMessage.getJwt().getPayload();
        String sig = payload.getSig();
        if (sig == null) {
            return false;
        }

        BaseService.ServiceResult revokeResult = issuerService.revokeVC(sig, didKeyHolder.getDid(), didKeyHolder);
        if (!Boolean.TRUE.equals(revokeResult.isSuccess())) {
            return false;
        }

        return true;
    }

    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class IvProtocolMessage {

        @NotNull
        String type;

        String message;

        @SerializedName("protected")
        @JsonProperty("protected")
        String _protected;

        String param;

        public IvProtocolMessage() {}
        public IvProtocolMessage(JsonObject json) {
            if(json.has("type")) {
                type = json.get("type").getAsString();
            }
            if(json.has("message")) {
                message = json.get("message").getAsString();
            }
            if(json.has("protected")) {
                _protected = json.get("protected").getAsString();
            }
            if(json.has("param")) {
                param = json.get("param").getAsString();
            }
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("{");
            if(type != null) {
                sb.append("\"type\":\"").append(type).append("\"").append(",");
            }
            if(_protected != null) {
                sb.append("\"protected\":\"").append(_protected).append("\"").append(",");
            }
            if(message != null) {
                sb.append("\"message\":\"").append(message).append("\"").append(",");
            }
            if(param != null) {
                sb.append("\"param\":\"").append(param).append("\"").append(",");
            }
            sb.delete(sb.length()-1, sb.length());
            sb.append("}");
            return sb.toString();
        }
    }
}