DID는 중앙화된 신뢰기관 없이 블록체인 혹은 분산원장 기술을 통해 데이터의 소유자가 자격증명을 처리할 수 있는 기술입니다. DID는 DID Document에 공개키를 등록하고 Issuer에게 VC를 받아 VP를 Verifier에게 제출함으로써 자격증명을 가능하게 합니다.
DID 졸업증명서 개발 코드
Issuer는 Holder가 요청하는 경우 하나 이상의 Claims 집합인 Verifiable Credential을 발급합니다. 이는 암호화된 검증을 가능하게 하는 변조 방지 Credential입니다. Holder는 발급받은 Verifiable Credential에서 필요한 Claims만 추려 Verifiable Presentation을 생성해 제출할 수 있습니다.
아래 코드는 Issuer 입장에서 Verifiable Credential을 발급하는 과정을 솔리디티 기반 컨트랙트로 작성하는 방법입니다.
상태변수 선언
issuerAddress는 Issuer의 주소입니다.
idCount는 발급하고자 하는 Credential의 번호입니다.
alumniEnum은 발급하고자 하는 졸업증명서의 종류입니다. 열거형으로 매핑시킨 변수로 선언합니다.
Credential 정보를 담은 구조체를 선언합니다.
- id는 순서를 표기합니다.
- issuer는 Credential의 발급자로 Holder가 요청한 Claims를 검증 가능한 Credential로 발급하는 주체입니다.
- alumniType : 발급하고자 하는 졸업 증명서의 타입입니다.
- value : Credential에 포함되는 암호화된 정보들입니다. Credential Metadata, Proofs 등이 저장됩니다.
address private issuerAddress;
uint256 private idCount;
mapping(uint8=>string) private alumniEnum;
struct Credential{
uint256 id;
address issuer;
uint8 alumniType;
string value;
}
Credential 구조체에 접근하는 credentials 매핑 변수를 선언합니다.
mapping(address => Credential) private credentials;
Credential을 발급하는 claimCredential 함수를 선언합니다.
인자로는 3가지 데이터를 받습니다.
- _alumniAddress는 Holder를 의미합니다.
- _alumniType 은 발급하고자 하는 claims을 의미합니다.
- _value는 Credential에 기록되는 추가 정보입니다.
function claimCredential(address _alumniAddress, uint8 _alumniType, string calldata _value) public returns(bool){
require(issuerAddress == msg.sender, "Not Iusser");
Credential storage credential = credentials[_alumniAddress];
require(credential.id == 0);
credential.id = idCount;
credential.issuer = msg.sender;
credential.alumniType = _alumniType;
credential.value = _value;
idCount += 1;
return true;
}
발급한 Credential을 확인할 수 있는 getCredential 함수를 선언합니다. Holder의 주소를 인자로 받아 credential 구조체 데이터를 반환합니다.
function getCredential(address _alumniAddress) public view returns (Credential memory){
return credentials[_alumniAddress];
}
DID 졸업증명서 발급
Remix에 접속합니다.
아래 전체 코드를 복사하고 컴파일합니다. 컴파일 시 버전을 맞춰줍니다.
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.10;
contract CredentialBox{
address private issuerAddress;
uint256 private idCount;
mapping(uint8=>string) private alumniEnum;
struct Credential{
uint256 id;
address issuer;
uint8 alumniType;
string value;
}
mapping(address => Credential) private credentials;
constructor(){
issuerAddress = msg.sender;
idCount = 1;
alumniEnum[0] = "Korea University";
alumniEnum[1] = "Berlin University";
alumniEnum[2] = "Tokyo University";
}
function claimCredential(address _alumniAddress, uint8 _alumniType, string calldata _value) public returns(bool){
require(issuerAddress == msg.sender, "Not Iusser");
Credential storage credential = credentials[_alumniAddress];
require(credential.id == 0);
credential.id = idCount;
credential.issuer = msg.sender;
credential.alumniType = _alumniType;
credential.value = _value;
idCount += 1;
return true;
}
function getCredential(address _alumniAddress) public view returns (Credential memory){
return credentials[_alumniAddress];
}
}
컴파일 시 버전을 맞춰줍니다.
ethereum testnet인 Ropsten 네트워크에 배포합니다.
배포 후 Credential을 발급하는 claimCredential 함수를 실행합니다. claimCredential 함수는 3가지 데이터를 인자로 받습니다.
- _alumniAddress 는 Credential을 발급받을 Holder의 주소입니다.
- _alumniType은 졸업증명서 타입입니다. 이 예제에서는 Holder가 3개의 대학을 나왔다는 것을 가정하고 있습니다. Tokyo 대학에 졸업한 경우 2를 입력합니다.
- _value : Credential Metadata와 Proofs에 관한 정보를 JWT로 암호화한 값을 입력합니다. JWT 토큰을 풀어보면 아래와 같은 정보가 기록됩니다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWRwIjoiRXhhbXBsZSBVbml2ZXJzaXR5IiwidHlwZSI6IlRva3lvIFVuaXZlcnNpdHkiLCJ0b2tlbiI6InRlc3QiLCJ2YWx1ZSI6IkRJRCBDcmVkZW50aWFsIFRlc3RpbmcifQ.iS7nOZk0G8ZT54MLCx4kBDvkSbmRcnrnUiHvllu0vqs
Credential을 발급하는 트랜잭션을 실행합니다.
Holder의 주소를 통해 발급된 Credential을 확인할 수 있습니다.
DID 컨트랙트 기능 추가하기
① OwnerHelper를 상속받은 abstract contract IssuerHelper를 생성합니다. Issuer만 접근할 수 있도록 제한기능을 수행합니다.
abstract contract OwnerHelper{
address private owner;
event OwnerTransferPropose(address indexed _from, address indexed _to);
modifier onlyOwner{
require(msg.sender == owner);
_;
}
constructor(){
owner = msg.sender;
}
function transferOwnership(address _to) onlyOwner public{
require(_to != owner);
require(_to != address(0x0));
owner = _to;
emit OwnerTransferPropose(owner, _to);
}
}
abstract contract IssuerHelper is OwnerHelper{
mapping(address => bool) public issuers;
event AddIssuer(address indexed _issuer);
event DelIssuer(address indexed _issuer);
modifier onlyIssuer{
require(isIssuer(msg.sender) == true);
_;
}
constructor(){
issuers[msg.sender] = true;
}
function isIssuer(address _addr) public view returns (bool){
return issuers[_addr];
}
function addIssuer(address _addr) onlyOwner public returns (bool){
require(issuers[_addr] == false);
issuers[_addr] = true;
emit AddIssuer(_addr);
return true;
}
function delIssuer(address _addr) public onlyOwner returns(bool){
require(issuers[_addr] == true);
issuers[_addr] = false;
emit DelIssuer(_addr);
return true;
}
}
② Holder의 상태를 기록하는 statusEnum을 선언합니다.
Credential이 생성되는 시점을 기록하기 위해 createDate를 block의 timestamp를 사용해 기록합니다.
contract CredentialBox is IssuerHelper{
// address private issuerAddress;
uint256 private idCount;
mapping(uint8 => string) private alumniEnum;
mapping(uint8 => string) private statusEnum;
struct Credential{
uint256 id;
address issuer;
uint8 alumniType;
uint8 statusType;
string value;
uint256 createDate;
}
mapping(address => Credential) private credentials;
constructor() {
// issuerAddress = msg.sender;
idCount = 1;
alumniEnum[0] = "Korea University";
alumniEnum[1] = "Berlin University";
alumniEnum[2] = "Tokyo University";
}
function claimCredential(address _alumniAddress, uint8 _alumniType, string calldata _value) public onlyIssuer returns(bool){
Credential storage credential = credentials[_alumniAddress];
require(credential.id == 0);
credential.id = idCount;
credential.issuer = msg.sender;
credential.alumniType = _alumniType;
credential.statusType = 0;
credential.value = _value;
credential.createDate = block.timestamp;
idCount += 1;
return true;
}
③ 자격증명인 alumni의 타입을 추가하고 가져오는 함수를 생성합니다.
중복값을 제거하기 위해 bytes().length == 0 문을 사용합니다. 중복제거를 위해 keccak256() 함수를 돌려 2개의 string 해시 결과값을 비교하는 방법도 있습니다.
function addAlumniType(uint8 _type, string calldata _value) onlyIssuer public returns (bool){
require(bytes(alumniEnum[_type]).length == 0);
alumniEnum[_type] = _value;
return true;
}
function getAlumniType(uint8 _type) public view returns(string memory){
return alumniEnum[_type];
}
④ Holder의 Status를 변경하고 가져오는 함수를 생성합니다.
function addStatusType(uint8 _type, string calldata _value) onlyIssuer public returns (bool){
require(bytes(statusEnum[_type]).length == 0);
statusEnum[_type] = _value;
return true;
}
function getstatusType(uint8 _type) public view returns(string memory){
return statusEnum[_type];
}
⑤ 마지막으로 특정 Holder의 상태를 변경하는 함수를 생성합니다.
function changeStatus(address _alumni, uint8 _type) public onlyIssuer returns(bool){
require(credentials[_alumni].id != 0);
require(bytes(statusEnum[_type]).length != 0);
credentials[_alumni].statusType = _type;
return true;
}
Reference
'Blockchain' 카테고리의 다른 글
[Blockchain] Infura 사용하는 이유 방법 (0) | 2022.07.29 |
---|---|
[Blockchain] Web3란? (0) | 2022.07.29 |
[Blockchain] 탈중앙화 신원 증명 방법 DID 이해하기 (0) | 2022.07.26 |
[Blockchain] VC VP DID 개념 및 작동 방식 (1) | 2022.07.25 |
[Blockchain] DID 자기 주권 신원(SSI)이란? (0) | 2022.07.25 |
댓글