Security Token Offering (ERC-3643)

[Witness Jo]

2024/05/10

Security Token Offering (ERC-3643)

구성요소 설명

Identify with ONCHAINID

  1. 온 체인 내에서 개인을 증명하는 수단, 일종의 Identity 시스템 (아래에 설명되는 Identity Registry와는 다르다.)
  2. ERC-3643에서는 ONCHAINID 라는 시스템을 이용해서 지갑과 연결하는 것을 권장한다.
  3. ONCHAINID는 ERC-734, 735 두개의 표준 스마트 컨트랙트와 그와 관련된 Off-Chain 어플리케이션이다.
  4. 만약에 당신이 ERC20 wallet을 보유한 경우에 만약 개인키와 관련된 wallet정보를 잃어버리면 영원히 접근 할 수 없으나, ONCHAINID 시스템과 permissioned 토큰을 사용하면 토큰에 대한 접근을 잃게 되더라도 ONCHAINID를 이용해서 소유자의 ID를 획득 한 이후 permissioned 토큰에 대한 접근을 복구 할 수 있다.
  5. 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);
    }
    
  6. 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);
    }
    
  7. usecase-ONCHAINID> 해당 컨트랙트의 이용 예를 들면 Bob이 A기관의 인증된 개발자이고 A의 키들을 관리할 수 있는 ERC-734 컨트랙트가 등록된다. 이후 ERC-734에 등록된 Bob의 public키 중 일부를 이용해서 Signature를 생성, A기관이 등록한 ERC-735 컨트랙트에 Bob의 컨트랙트 주소와 Signature를 함께 등록 할 수 있다.
  8. 저장되는 표준 데이터 외 민감한 데이터들은 블록체인에 보관하지 말고 Off-Chain에 저장하도록 한다.

Registries

  1. IdentityRegistry
    • 1개의 증권형 토큰당 1개의 IdentityRegistry가 등록된다.
  2. IdentityRegistryStorage
    • 검증된 투자자들의 Identity 주소를 저장한다.
    • 하나 이상의 IdentityRegistry와 연결 될 수 있음 –> n개의 IdentityRegistry가 1개의 IdentityRegistryStorage 공유가능
    • ERC-173 기반의 소유권을 사용하며, 컨트랙트 owner에 의해 임명된 agent가 identity를 등록/제거를 권한을 소유함
  3. ClaimTopicRegistry
    • 증권형 토큰의 신뢰 가능한 claim topic들, 개개인이 증권형 토큰을 거래하기 위해 어떠한 증명서들을 필요로 하는지가 저장되었음
  4. TrustedIssuerRegistry
    • claim topic들에 대한 claim을 발행하는 주체들의 주소를 저장하고 있는 컨트랙트
    • Identity는 유저가 증권형 토큰을 거래하고 소유하기 위해 이 레지스트리 컨트랙트에 저장된 발행자들의 서명이 필요함

Compliance Contract

  1. 거래가 토큰에 대해 확립된 Rule을 준수했는지 검사하는 컨트랙트
  2. Rule의 예를 몇가지 들자면 (국가당 최대 투자자 수, 투자자당 최대 토큰 금액, 토큰 유통에 허용되는 국가(IdentityRegistry 에서 투자자의 정보를 가져온 다음 국가코드 열람)) 등 과 같은 것들이 있을 수 있음
  3. 각 토큰 발행자에 따라서 토큰에 대한 요구 사항이 다를 수 있기 때문에 Compliance 개체를 생성한 이후 n개의 토큰을 한개의 Compliance에 등록, 제거를 수행할 수 있다.
  4. 모든 토큰 거래는 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

  1. Security Token과 Utility Token의 차이

    • Security Token: 자산, 주식, 채권 등의 금융 상품을 디지털화한 것으로 토큰 소유자는 해당 자산에 대한
    • Utility Token: 특정 네트워크에서 제공하는 서비스나 재화에 대한 이용권리를 내제하고 있는 토큰 소유권, 배당금, 이자 등의 권리를 가질 수 있음
  2. Security Contract는 Identity Registry와 상호작용하여 투자자의 적합성을 체크하고 토큰을 소유를 활성화 및 거래를 수행한다.

  3. 하나의 증권 종목당 Security Token Contract가 업데이트 된다. (1:1)

  4. 기본적으로 ERC-20표준을 바탕으로 구현되어 호환성을 가지도록 설계되어 있음, 다만 transfer이 이루어 지기 전에 Identity나 Compliance 컨트랙트를 통해서 사전 조건을 확인하고 이를 만족 할 경우에만 거래가 이루어짐

  5. 몇 가지 특이한 기능들

    • 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

  1. ONCHAINID:
  2. IdentityRegistry
  3. IdentityRegistryStorage
  4. ClaimTopicRegistry
  5. TrustedIsuserRegistry
  6. ComplianceContract
  7. SecurityTokenContract

ONCHAINID를 이용해서 사용자 인증 후 서비스 이용하기

case 1: 투자자 정보 Registry에 등록

case 2: 종목 등록 및 발행

case 3: 종목 거래

case 4: 거래 정지

References