Contract TermiunsDID
Refer here for the management of the Terminus DID contract.
Design
This chapter introduces the design of the Terminus DID contract. We have refined the design of the core contract according to the general blueprint of the Terminus DID contract system.
About DID
Data Contained in DIDs
name
Unique DID Identifiers
Naming rules of TName
A valid label of TName, i.e. each part separated by .
:
- is a well-formed non-empty UTF-8 sequence; and
- contains only Unicode code points in category L, M, N, P, S; and
- does not contain Unicode code points in the following ranges:
- Full Stop (U+002E)
- Mongolian Free Variation Selectors (U+180B..U+180D)
- Variation Selectors (U+FE00..U+FE0F)
- Replacement Characters (U+FFFC..U+FFFD)
- Variation Selectors Supplement (U+E0100..U+E01EF). Example
- Case-sensitive, e.g.
MAX.com
andmax.com
are different TNames. - Supports multiple characters including Chinese, e.g.
博物馆.中国
Ref: https://www.compart.com/en/unicode/category
id
Each DID is also a NFT (following ERC-721 standard) and id
is its unique identifier which is calculated by keccak256(name)
.
did Derived from mnemonic phrases, used for authentication in systems relying on DIDs.
owner
Also derived from mnemonic phrases (following BIP-44 standard) and used for authentication (mostly on-chain contracts).
note
DIDs have the following kinds for now:
- Individual
- Organization
- Entity
Tag System
Besides basic data we hope to maintain some state or information in DIDs so the tag system comes in.
how to use
- Define a tag
- Write to the tag
NOTE
The definition and structure of tags are complicated and we don't dive into it here. Refer to the technical details of the contract if you are interested. Here we briefly introduce the information in a tag definition.
- name: the name of this tag
- did: the DID that defines this tag
- abiType: the data type of this tag, which follows the Solidity ABI specification and supports complicated structures
- fieldNames: the field names of structs inside this tag, flatten as a 2D string array using pre-order traversal
the scope of tags
Every tag is only applicable to the DID who defines it and its subdomains. For example, if a-certain-company.com
defines the tag employeeId
, its subdomain james.a-certain-company.com
can set employeeId
value of itself to "001"
, while the DIDs who neither have defined the tag nor is a subdomain of the definer, e.g. another-company.com
and emma.another-company.com
, cannot set the tag value.
taggers
The tagger is part of necessary infomation in each tag. Unlike the fixed definition, taggers can be changed. A tagger represents the only person or program that has the authority to modify the tag value. It can be a wallet address or smart contract.
NOTE
We recommend you to use smart contracts for taggers.
- Please refer to some official tagger implementations. You can utilize the Terminus DID contract for complete operator authentication, and also achieve more fine-grained custom access control.
- For complicated tag structures or data with special formats, using contract taggers can verify data format on-chain and customize the rules. For example, in the official tagger we will verify the bytes data in PKCS8 ASN.1 format on-chain to prevent accidentally setting unparsable values for the
RSAPubKey
tag.
Who to Create DIDs
Since not everyone has enough gas for on-chain operations and is willing to use the owner address of existing DIDs, we offer the choice of sending tx by official forwarders on your behalf. Of couse you can also operate on your own if willing.
Priviledges of DIDs
A DID has management priviledge over itself and all its subdomains. For 3rd-level domains which is registered directly, e.g. a.b.c
without the registration of b.c
, the owner of b.c
will be set to 0x0...00d1d
and it is managed by the official.
Introduction to processes and concepts
The extended information of TNames is managed by the tag system of the Terminus DID contract. Every tag has not only a name but also a tag type. For now, supported types include int
, uint
, bool
, string
, address
, bytes
, bytesN
, array
, arrayN
and tuple
. Tuples supports additional field names. Since tags are bound to TNames, the steps for setting a tag are:
- Define a tag in a TName with the tag name, tag type and field names in tuples. Setting the field names is not trivial so refer to the documentation "DIDv2.3Tag 类型注册流程介绍" for details.
Code example
function defineTag(
string calldata domain,
string calldata name,
bytes calldata abiType,
string[][] calldata fieldNames
) public
- Set the tagger which is responsible for setting this tag. The tagger can be EOA or a contract address. Complicated validation logic can be implemented in contract taggers.
Code example
function setTagger(string calldata domain, string calldata name, address tagger) public
- Next you can perform CRUD of this tag on the definer and its subdomains.
function addTag(string calldata from, string calldata to, string calldata name, bytes calldata value) public
function removeTag(string calldata from, string calldata to, string calldata name) public
function getTagElem(string calldata from, string calldata to, string calldata name, uint256[] calldata elemPath)
public
view
returns (bytes memory)
function updateTagElem(
string calldata from,
string calldata to,
string calldata name,
uint256[] calldata elemPath,
bytes calldata value
) public
function getTagElemLength(
string calldata from,
string calldata to,
string calldata name,
uint256[] calldata elemPath
) public view returns (uint256)
function pushTagElem(
string calldata from,
string calldata to,
string calldata name,
uint256[] calldata elemPath,
bytes calldata value
) public
function popTagElem(string calldata from, string calldata to, string calldata name, uint256[] calldata elemPath) public
Among these
from
is the TName that defines this tagto
is the TName to set this tagname
is the tag namevalue
is the bytes value afterabi.encode
elemPath
is provided forarray
andtuple
to access a single element. For other types just set it to an empty array. Its value is treated like indices of multi-dimensional arrays (thinktuple
s as arrays too). For example, the following typeStudent
has 4-level nested tuples, and we can setelemPath
to[1,2,1,0,0]
to read or updateStudent s -> Class class -> Teacher[1] teachers -> People info -> string name
. Note that we only have 4-level nested tuples butelemPath
has length 5 becauseteachers
is an array and adds another nesting level.
struct Student {
People info;
Class class;
}
struct Class {
uint8 grade;
uint8 classNum;
Teacher[] teachers;
}
struct People {
string name;
uint8 age;
string gender;
}
struct Teacher {
People info;
string subject;
}
Student s;
- The Terminus DID contract provides a set of special tags, known as the official tags. It is defined in the empty TName
""
and its tagger is specified as theRootTagger
. All TNames can set official tags and we'll introduce the detailed access control policy later.
Access Control of Tags
- Defining tags in a TName, i.e. setting the tag name and type:]
- Official tags: the
operator
has permission - Other tags: the owner of this TName has permission Defining tags can fail in the following situations:
- A tag name can only be defined once in one TName, no duplicates
- Tag names and field names of tuples must start with [a-z] and only contain [a-zA-Z0-9]
- The bytes representation of a tag type cannot exceed length 31
- The tag type must follow the type constraints in the
ABI
library - The value of
fieldNames
must match tuple types in the definition - A tuple cannot have duplicate field names
- Setting the tagger:
- Official tags: the
operator
has permission - Other tags: the owner of this TName has permission
- Setting the tag: Taggers are responsible for access control of setting tags. Next we introduce the cases of official tags.
Registration Rules and Access Control
From the contract's perspective, for now only the operator can register top-level TName. Normal registration is done by calling the register
interface with arguments including the owner of the TName and some metadata. The metadata is immutable after registration. Sub-TNames can be registered by the operator or the owner of one of its parent TNames. That said, registration can only be performed by the following roles:
operator
of the contract- the owner of a parent TName can register a sub-TName Resgitration can fail in the following cases:
- when TName is not top-level: the parent TName is not registered yet or the parent's metadata specifies it cannot have sub-TNames
- the TName contains an invalid label
- the TName is already registered
- the owner of the TName is specified as zero address
Code example
struct Metadata {
string domain;
string did;
string notes;
bool allowSubdomain;
}
function register(address tokenOwner, Metadata calldata metadata) public returns (uint256 tokenId)
After registration, the contract will mint a ERC-721 NFT whose token ID is the above return value tokenId
. It is the Keccak-256 hash of the TName string. Code example
function tokenId(string memory domain) internal pure returns (uint256) {
return uint256(keccak256(bytes(domain)));
}
Since the NFT is ERC721-compatible, it supports standard ERC-721 operations like transferFrom
and approve
, so the ownership can be transferred by these addresses:
- the owner of the TName
- the delegator of the owner
- addresses approved by the owner The Terminus DID contract adds two addresses that have transfer priviledge:
operator
of the contract- the owner of the parent TName
Roles of the Contract
The Terminus DID contract has an owner for upgrading itself. It also has a superuser, i.e. the operator
, to facilitate configuration. Every TName has its owner. Here we introduce the priviledge of the owner of the contract and other roles will come later when we introduce the detailed functions. The priviledges of the contract owner include:
- setting the operator address
- transferring ownership of the contract
- upgrading the contract
Official Taggers
RootTagger
For now we define the following tags and let RootTagger
be the taggers for them:
- rsaPubKey: the RSA public key of a TName type:
bytes
; access: the operator, the owner of the TName, or the owner of a parent TName - dnsARecord: the IP address of a TName type:
bytes4
; access: the operator, the owner of the TName, or the owner of a parent TName - latestDID: the latest DID of a TName (this is added because the metadata is immutable) type:
string
; access: the operator, the owner of the TName, or the owner of a parent TName - authAddresses: the addresses controlled by the owner of a TName type:
tuple(uint8,address)[]
; access: anyone with EIP-712 signatures of the owner of the TName and the added address
AppStoreReputation
There is another special tagger AppStoreReputation
for the tag ratings
with type tuple(string,uint8)[]
defined in the TName app.myterminus.com
. It provides on-chain storage for ratings of apps in Terminus OS. Each app has a corresponding sub-TName <appVersion>.<appId>.app.myterminus.com
where anyone who has a TName can submit ratings for these apps.
Release History
V2.0.0
Implement core architecture of DID module in Terminus DID
V2.1.0
Add tag system without definition
V2.2.0
Upgrade tag system to include type definition
V2.3.0
Optimize contract structure and reduce core contract size
V2.3.1
Bug-fix: Revert instead of going on with zero address when EIP-712 signature recover fails
FAQ
Transaction Fee
When you apply for a Terminus Name, we can afford the transaction fee for calling the contract. The rough process is:
- You provide us the data and signature signed by your private key
- We send transaction to the smart contract
- The blockchain records your data after signature verification
If you need to manage data under your domain in the future, you should call the contract via MetaMask or TermiPass in the Terminus Space pay the the transaction fee by yourself.