Security Token Offering (ERC-3643)
구성요소 설명
Identify with ONCHAINID
- 온 체인 내에서 개인을 증명하는 수단, 일종의 Identity 시스템 (아래에 설명되는 Identity Registry와는 다르다.)
- ERC-3643에서는 ONCHAINID 라는 시스템을 이용해서 지갑과 연결하는 것을 권장한다.
- ONCHAINID는 ERC-734, 735 두개의 표준 스마트 컨트랙트와 그와 관련된 Off-Chain 어플리케이션이다.
- 만약에 당신이 ERC20 wallet을 보유한 경우에 만약 개인키와 관련된 wallet정보를 잃어버리면 영원히 접근 할 수 없으나, ONCHAINID 시스템과 permissioned 토큰을 사용하면 토큰에 대한 접근을 잃게 되더라도 ONCHAINID를 이용해서 소유자의 ID를 획득 한 이후 permissioned 토큰에 대한 접근을 복구 할 수 있다.
- ERC-734는 키의 보안과 유기적인 관리의 역할을 수행한다. 유저의 특정한 목적에 대한 public key를
등록, 삭제, 조회 등 관리 역할을 하고 있다. 컨트랙트는 유저 소유이므로 만약에 사용자가 신원을
추가한다면 새로운 유저에 대한 ERC-734 컨트랙트가 배포되는 것으로 볼 수 있다. (1:1 관계)
pragma solidity ^0.4.18; contract ERC734 { uint256 constant MANAGEMENT_KEY = 1; uint256 constant EXECUTION_KEY = 2; event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); event Approved(uint256 indexed executionId, bool approved); event KeysRequiredChanged(uint256 purpose, uint256 number); struct Key { uint256 purpose; //e.g., MANAGEMENT_KEY = 1, EXECUTION_KEY = 2, etc. uint256 keyType; // e.g. 1 = ECDSA, 2 = RSA, etc. bytes32 key; }; function getKey(bytes32 _key) public constant returns(uint256[] purposes, uint256 keyType, bytes32 key); function keyHasPurpose(bytes32 _key, uint256 _purpose) public constant returns (bool exists); function getKeysByPurpose(uint256 _purpose) public constant returns (bytes32[] keys); function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) public returns (bool success); function removeKey(bytes32 _key, uint256 _purpose) public returns (bool success); function changeKeysRequired(uint256 purpose, uint256 number) external; function getKeysRequired(uint256 purpose) external view returns(uint256); function execute(address _to, uint256 _value, bytes _data) public returns (uint256 executionId); function approve(uint256 _id, bool _approve) public returns (bool success); } - ERC-735는 키에 해당하는 claim(요청한 권리가 사용자의 정보 맞다는 증거)의 검증을 수행하는
역할을 한다. 해당 컨트랙트를 사용하는 주체로는 기관을 예로 들 수 있을것이다.
pragma solidity ^0.4.18; contract ERC735 { event ClaimRequested(uint256 indexed claimRequestId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimAdded(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimRemoved(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimChanged(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); struct Claim { uint256 topic; uint256 scheme; address issuer; // msg.sender bytes signature; // this.address + topic + data bytes data; string uri; }; function getClaim(bytes32 _claimId) public constant returns(uint256 topic, uint256 scheme, address issuer, bytes signature, bytes data, string uri); function getClaimIdsByTopic(uint256 _ topic) public constant returns(bytes32[] claimIds); function addClaim(uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) public returns (uint256 claimRequestId); changeClaim(bytes32 _claimId, uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) returns (bool success); function removeClaim(bytes32 _claimId) public returns (bool success); } - usecase-ONCHAINID> 해당 컨트랙트의 이용 예를 들면 Bob이 A기관의 인증된 개발자이고 A의 키들을 관리할 수 있는 ERC-734 컨트랙트가 등록된다. 이후 ERC-734에 등록된 Bob의 public키 중 일부를 이용해서 Signature를 생성, A기관이 등록한 ERC-735 컨트랙트에 Bob의 컨트랙트 주소와 Signature를 함께 등록 할 수 있다.
- 저장되는 표준 데이터 외 민감한 데이터들은 블록체인에 보관하지 말고 Off-Chain에 저장하도록 한다.
Registries
- IdentityRegistry
- 1개의 증권형 토큰당 1개의 IdentityRegistry가 등록된다.
- IdentityRegistryStorage
- 검증된 투자자들의 Identity 주소를 저장한다.
- 하나 이상의 IdentityRegistry와 연결 될 수 있음 –> n개의 IdentityRegistry가 1개의 IdentityRegistryStorage 공유가능
- ERC-173 기반의 소유권을 사용하며, 컨트랙트 owner에 의해 임명된 agent가 identity를 등록/제거를 권한을 소유함
- ClaimTopicRegistry
- 증권형 토큰의 신뢰 가능한 claim topic들, 개개인이 증권형 토큰을 거래하기 위해 어떠한 증명서들을 필요로 하는지가 저장되었음
- TrustedIssuerRegistry
- claim topic들에 대한 claim을 발행하는 주체들의 주소를 저장하고 있는 컨트랙트
- Identity는 유저가 증권형 토큰을 거래하고 소유하기 위해 이 레지스트리 컨트랙트에 저장된 발행자들의 서명이 필요함
Compliance Contract
- 거래가 토큰에 대해 확립된 Rule을 준수했는지 검사하는 컨트랙트
- Rule의 예를 몇가지 들자면 (국가당 최대 투자자 수, 투자자당 최대 토큰 금액, 토큰 유통에 허용되는 국가(IdentityRegistry 에서 투자자의 정보를 가져온 다음 국가코드 열람)) 등 과 같은 것들이 있을 수 있음
- 각 토큰 발행자에 따라서 토큰에 대한 요구 사항이 다를 수 있기 때문에 Compliance 개체를 생성한 이후 n개의 토큰을 한개의 Compliance에 등록, 제거를 수행할 수 있다.
- 모든 토큰 거래는 ICompliance의 canTransfer를 수행해야 하며 TRUE를 Return해야지 거래가 가능하다.
interface ICompliance { // events event TokenBound(address _token); event TokenUnbound(address _token); // functions // initialization of the compliance contract function bindToken(address _token) external; function unbindToken(address _token) external; // check the parameters of the compliance contract function isTokenBound(address _token) external view returns (bool); function getTokenBound() external view returns (address); // compliance check and state update function canTransfer(address _from, address _to, uint256 _amount) external view returns (bool); function transferred(address _from, address _to, uint256 _amount) external; function created(address _to, uint256 _amount) external; function destroyed(address _from, uint256 _amount) external; }
Security Token Contract
-
Security Token과 Utility Token의 차이
- Security Token: 자산, 주식, 채권 등의 금융 상품을 디지털화한 것으로 토큰 소유자는 해당 자산에 대한
- Utility Token: 특정 네트워크에서 제공하는 서비스나 재화에 대한 이용권리를 내제하고 있는 토큰 소유권, 배당금, 이자 등의 권리를 가질 수 있음
-
Security Contract는 Identity Registry와 상호작용하여 투자자의 적합성을 체크하고 토큰을 소유를 활성화 및 거래를 수행한다.
-
하나의 증권 종목당 Security Token Contract가 업데이트 된다. (1:1)
-
기본적으로 ERC-20표준을 바탕으로 구현되어 호환성을 가지도록 설계되어 있음, 다만 transfer이 이루어 지기 전에 Identity나 Compliance 컨트랙트를 통해서 사전 조건을 확인하고 이를 만족 할 경우에만 거래가 이루어짐
-
몇 가지 특이한 기능들
- freezePartialTokens: 증권형 토큰의 특성상 관리자에 의해 철저히 관리되기 위한 장치들이 필요하다. freezePartialTokens()를 수행해서 특정 지갑이 소유한 토큰을 freeze 할 수 있음
- pause: 컨트랙트에서 다루고 있는 토큰의 모든 거래를 일시적으로 중단할 수 있다.
- recoverAddress: private키 분실이나 wallet 해킹 등을 당한 투자자들을 위한 recovery 장치 역시 recoveryAddress 함수로 구현되어 있음.
interface IERC3643 is IERC20 { // events event UpdatedTokenInformation(string _newName, string _newSymbol, uint8 _newDecimals, string _newVersion, address _newOnchainID); event IdentityRegistryAdded(address indexed _identityRegistry); event ComplianceAdded(address indexed _compliance); event RecoverySuccess(address _lostWallet, address _newWallet, address _investorOnchainID); event AddressFrozen(address indexed _userAddress, bool indexed _isFrozen, address indexed _owner); event TokensFrozen(address indexed _userAddress, uint256 _amount); event TokensUnfrozen(address indexed _userAddress, uint256 _amount); event Paused(address _userAddress); event Unpaused(address _userAddress); // functions // getters function onchainID() external view returns (address); function version() external view returns (string memory); function identityRegistry() external view returns (IIdentityRegistry); function compliance() external view returns (ICompliance); function paused() external view returns (bool); function isFrozen(address _userAddress) external view returns (bool); function getFrozenTokens(address _userAddress) external view returns (uint256); // setters function setName(string calldata _name) external; function setSymbol(string calldata _symbol) external; function setOnchainID(address _onchainID) external; function pause() external; function unpause() external; function setAddressFrozen(address _userAddress, bool _freeze) external; function freezePartialTokens(address _userAddress, uint256 _amount) external; function unfreezePartialTokens(address _userAddress, uint256 _amount) external; function setIdentityRegistry(address _identityRegistry) external; function setCompliance(address _compliance) external; // transfer actions function forcedTransfer(address _from, address _to, uint256 _amount) external returns (bool); function mint(address _to, uint256 _amount) external; function burn(address _userAddress, uint256 _amount) external; function recoveryAddress(address _lostWallet, address _newWallet, address _investorOnchainID) external returns (bool); // batch functions function batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external; function batchForcedTransfer(address[] calldata _fromList, address[] calldata _toList, uint256[] calldata _amounts) external; function batchMint(address[] calldata _toList, uint256[] calldata _amounts) external; function batchBurn(address[] calldata _userAddresses, uint256[] calldata _amounts) external; function batchSetAddressFrozen(address[] calldata _userAddresses, bool[] calldata _freeze) external; function batchFreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; function batchUnfreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; }
Usecases
Artifacts
- ONCHAINID:
- IdentityRegistry
- IdentityRegistryStorage
- ClaimTopicRegistry
- TrustedIsuserRegistry
- ComplianceContract
- SecurityTokenContract
ONCHAINID를 이용해서 사용자 인증 후 서비스 이용하기
- IDOwner: ONCHAINID 인증을 받고 특정 분야 서비스를 제공받고 싶은 사용자
- ONCHAINID(ERC-734,ERC-735): ONCHAINID 스마트 컨트랙트 연동된 KYC를 제공해주는 서비스 (oauth로 치면 google, facebook, twitter와 같은 공인된 인증 서비스라고 봐도 될것 같다) -> 새로운 사용자가 등록 되면 ONCHAINID 비스 내부에서 사용자에 대한 ERC-734, ERC-735에 대한 컨트랙트가 등록될 것이다.
- Website: ONCHAINDID를 이용해서 사용자임을 확인하고 특정 서비스를 제공해주고자 하는 업체
case 1: 투자자 정보 Registry에 등록
case 2: 종목 등록 및 발행
case 3: 종목 거래
case 4: 거래 정지
References
- https://github.com/TokenySolutions/T-REX
- https://eips.ethereum.org/EIPS/eip-3643
- https://medium.com/decipher-media/sto-%EC%9D%B4%EB%8C%80%EB%A1%9C-%EA%B4%9C%EC%B0%AE%EC%9D%84%EA%B9%8C-%ED%95%98-d0163a98ac1e
- https://ethereum.stackexchange.com/questions/148806/how-to-deploy-a-token-using-the-erc-3643-standard