Issuer가 제기한 하나 이상의 Claim 집합, VC(Verifiable Credential)은 암호화된 검증을 생성할 수 있는 변조 방지 Credential이다. 이 크리덴셜을 스마트 컨트랙트로 개발을 해보고자 한다.
// 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] = "SEB";
alumniEnum[1] = "BEB";
alumniEnum[2] = "AIB";
}
function claimCredential(address _alumniAddress, uint8 _alumniType, string calldata _value) public returns(bool){
require(issuerAddress == msg.sender, "Not Issuer");
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];
}
}
이 코드는 Issuer와 Credential을 포함하는 Solidity 코드이다. 이번 실습 '졸업 증명서 개발하기'는 claimCredential
함수로 Credential을 발행하고, getCredential
함수를 통해 Credential을 발행한 주소에서 VC를 확인하는 간단한 구조이다.
struct Credential{
uint256 id;
address issuer;
uint8 alumniType;
string value;
}
function claimCredential(address _alumniAddress, uint8 _alumniType, string calldata _value) public returns(bool){
require(issuerAddress == msg.sender, "Not Issuer");
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;
}
claimCredential
함수를 통해 발급자(issuer)는 어떠한 주체(alumniAddress)에게 크리덴셜을 발행(claim)할 수 있게 된다.
function getCredential(address _alumniAddress) public view returns (Credential memory){
return credentials[_alumniAddress];
}
이 함수를 통해 어떠한 주체(alumniAddress)를 통하여 발행(claim)한 크리덴셜을 확인할 수 있다.
Remix를 이용하여 스마트 컨트랙트 배포 & 사용
credentialBox.sol
이라는 파일을 만들고, 코드를 입력한다.claimCredential
함수를 실행한다.// JWT로 암호화된 토큰
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWRwIjoiY29kZSBzdGF0ZXMiLCJ0eXBlIjoiYmViIiwidG9rZW4iOiJ0ZXN0IiwidmFsdWUiOiLsvZTrk5zsiqTthYzsnbTsuKAgRElEIOyImOujjOymnSDrsJzquInsnYQg7JyE7ZWcIO2BrOumrOuNtOyFnCDthYzsiqTtirgifQ.qXTgkPcK43uZ4_FBLBTFjaTsnmV9sAAekgK8BUZBt1g
getCredential
함수를 실행한다.getCredential
함수를 통해 해당 주소에게 저장되어 있던 VC(검증가능한 크리덴셜)일 발행되었음을 확인할 수 있다.
이렇게 'DID를 활용한 졸업증명서 개발하기'를 통해 DID에서 이야기하는 기본적인 그래프 표기인 주체-속성-값에 대한 클레임을 개발한 것이다.
기존 실습에 다양한 기능 추가하기
다음의 코드는 기존의 개발되었던 졸업증명서 코드에 이전에 배웠던 솔리디티 문법을 활용하여 다양한 기능을 추가한 것이다.
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.10;
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) onlyOwner public returns (bool) {
require(issuers[_addr] == true);
issuers[_addr] = false;
emit DelIssuer(_addr);
return true;
}
}
contract CredentialBox is IssuerHelper {
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() {
idCount = 1;
alumniEnum[0] = "SEB";
alumniEnum[1] = "BEB";
alumniEnum[2] = "AIB";
}
function claimCredential(address _alumniAddress, uint8 _alumniType, string calldata _value) onlyIssuer public 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;
}
function getCredential(address _alumniAddress) public view returns (Credential memory){
return credentials[_alumniAddress];
}
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];
}
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];
}
function changeStatus(address _alumni, uint8 _type) onlyIssuer public returns (bool) {
require(credentials[_alumni].id != 0);
require(bytes(statusEnum[_type]).length != 0);
credentials[_alumni].statusType = _type;
return true;
}
}
이전에 배웠던 솔리디티 문법을 추가하였다. 단순히 발급하는 형태의 스마트 컨트랙트에서 issuer를 컨트롤하고, 발급 시간에 대한 정보를 크리덴셜에 추가하고, 상태를 변경하는 함수도 추가하였다.
OwnerHelper는 기존의 Ownerhelper와 동일하다. 하지만 IssuerHelper가 추가되었다. IssuerHelper에서는 Issuer를 추가하고 삭제하는 기능이 존재한다. 추가하고 삭제하는 기능은 onlyOwner로 제한되어 Owner만 컨트롤이 가능하다.
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) onlyOwner public returns (bool) {
require(issuers[_addr] == true);
issuers[_addr] = false;
emit DelIssuer(_addr);
return true;
}
}
여기서 issuer로 추가되어 있지 않다면 bool 값은 false로 나온다.
cliamcredential
에서 block.timestamp를 활용해 크리덴셜을 클레임한 시간을 크리덴셜에 저장할 수 있다.
function claimCredential(address _alumniAddress, uint8 _alumniType, string calldata _value) onlyIssuer public 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;
}
alumniType은 초기에 3가지가 제공된다.
constructor() {
idCount = 1;
alumniEnum[0] = "SEB";
alumniEnum[1] = "BEB";
alumniEnum[2] = "AIB";
}
여기서 Alumni의 타입을 추가하는 함수를 입력한다.
function addAlumniType(uint8 _type, string calldata _value) onlyIssuer public returns (bool) {
require(bytes(alumniEnum[_type]).length == 0);
alumniEnum[_type] = _value;
return true;
}
Solidity 내부에는 String을 검사하는 두 가지 방법이 있다.
위의 코드에서는 첫 번째 방법을 이용하여 내부 alumniEnum의 Type이 중복되는 타입이 존재하는지 검사했다.
Remix에서 PMB을 추가하여 getAlumniType에서 PMB를 확인 할 수 있다.
changeStatus함수는 특정 사용자의 상태를 변경하는 함수이다. 해당 함수를 통해 크리덴셜 내부에 존재하는 statusType의 값을 가져와 변경할 수 있다.
function changeStatus(address _alumni, uint8 _type) onlyIssuer public returns (bool) {
require(credentials[_alumni].id != 0);
require(bytes(statusEnum[_type]).length != 0);
credentials[_alumni].statusType = _type;
return true;
}