Welcome to NNS’s documentation!

NNS 背景介绍

NNS 是什么

NNS(NEO Name Service)是Neo的域名服务,是一个基于Neo区块链的分布式、开源和可扩展的域名系统。 旨在将钱包地址、智能合约Hash等人类难以记忆的无规则的字符串用单词短语简写等代替。我们首先提供以”.neo”结尾的域名服务。

通过域名服务,人们再也不用记忆看不懂的地址和Hash,只要知道一个单词或一个短语就能进行转账、调用合约。

NNS可以将域名解析到各种目标。最容易联想到的是Neo的账户(Address),或者智能合约地址(ScriptHash)。 我们预留了足够的扩展性,可以在不更新合约的情况下支持更多的解析目标协议。

为什么需要 NNS

中本聪在设计比特币地址的时候,没有采用程序界常用的base64编码,而是自创了base58编码,去除了几个看起来会产生歧义的字符, 如 0 (零), O (大写字母O), I (大写的字母i) and l (小写的字母L) ,这体现了中本聪对区块链地址易用性的考虑。

然而,区块链地址对于人类还是不够友好,太长,不方便记忆,不容易比较对错。未来随着区块链越来越普及,地址转账的缺点会越来越明显, 就像我们今天发邮件很难用一个32位字符串作为一个邮箱账号一样。因此一个别名服务对于区块链系统的易用性有非常大的帮助, 例如IPFS上有别名服务IPNS,以太坊上有自己的域名服务ENS,我们认为NEO系统也应该有自己的别名服务,我们称为NEO Name Service(NNS), NEL社区将实现NNS服务以提升NEO区块链易用性。

NNS 的使用场景

别名服务的最主要使用场景在于别名转账,尤其是那些需要公开自己转账地址并且不常更换地址的账户,例如在ICO时, 项目方需要在官网提前公开自己官方账户地址,如果黑客篡改了ICO账户地址,投资人将很难发现。但是如果项目方提前公布一个简短易记的地址别名, 由于简短的有意义的词组很难被篡改,因此可以防止黑客的恶意攻击,避免不必要的损失。

一个别名要指向什么样的资源,是可以灵活扩展的,只需要实现相应的解析器即可。除了可以指向一个账户地址外,也可以指向一个合约地址,进而可以实现别名和智能合约交互。

区块链作为下一代互联网的基础设施,未来会有越来越多的服务基于区块链进行构建,例如去中心化云存储服务。云存储中的文件寻址是通过文件哈希值唯一标识实现的, 我们可以为哈希值取一个容易理解别名例如文件名,然后将别名映射到文件哈希,从而实现文件精准寻址,因此别名服务未来可以和NEO上的去中心化文件存储NEOFS结合使用。 随着构建在NEO上服务越来越多,NNS将逐步拓展以为去中心化消息通信、邮件服务等提供解析服务。

NNS 和 ENS 的关系

NNS和ENS具有相同的目标,都是为了提升区块链的易用性,但是基于不同的区块链平台实现,服务于不同的区块链平台。NNS在做系统设计时参考借鉴了ENS的系统设计, 在此对他们表示感谢,同时我们也针对NEO平台做出了很多创新设计,例如将所有者合约从注册器模块中拆分出来以实现更灵活的所有权控制,在解析方式上, 分为了快速解析和完整解析两种方式,在经济模型上引入了一种新型的智能代币,以实现系统费用的高效循环。

NNS 系统设计概述

NNS 系统功能

NNS系统有两个作用 一是将beautiful.neo 等人类可读的名称解析为机器使用的标识符,如Neo的地址等。 二是为域名提供描述性数据,比如whois,合约接口描述等。

NNS 和 DNS的目标类似,但是基于区块链架构设计,是去中心化的,服务于区块链网络。NNS使用和DNS一样用点(.)分割的域名称系统,域的所有者对隶属于他的子域名有完全的分配权利。

.neo .gas 这样的根域名由一个称为(注册器Registry)的智能合约管理。一个注册器管理一个根域名,并设定取得其下一级域名所有权的规则。任何人均可遵照对应的注册器设定的规则取得下一级域名的所有权。

NNS 系统架构

NNS有四个系统组件

  1. 顶级域名合约(域名根是管理根域名的脚本)
  2. 所有者(所有者可以是一个个人账户address,也可以是一个智能合约)
  3. 注册机(专门负责给一个域名的子域名分配所有者的智能合约,根域名也会指定一个根域名的注册机)
  4. 解析器(负责解析一个域名或者他的子域名)

顶级域名合约

域名根是一个根域名 比如.test 所有信息的管理者。 无论二级域名 aa.test 还是 三级域名 bbb.aa.test,他们的所有者都保存在域名根之中。 域名根以字典的形式保存如下数据:

  1. 域名的所有者(owner)
  2. 域名的注册器(register)
  3. 域名的解析器(resolver)
  4. 域名的TTL(域名到期时间)

所有者

域名的所有者可以是一个账户地址或者一个智能合约。(ens的设计是拥有域名的智能合约叫做注册器,实际上注册器只是owner的一个特例, 我们将域名的所有者和注册器分开了,这个系统会变得更加清晰) 域名的所有者(owner)可以:

  1. 将域名的所有权转移到另一个地址
  2. 更改注册器,最常见域名注册器为“管理员手动分配子域名”
  3. 更改解析器

允许所有者是一个智能合约,可以提供多种多样的所有权模式

  1. 比如双人共有域名,要两人签名才可以转让域名或者更改注册器
  2. 比如多人共有域名,超过50%人签名才可以转让域名或者更改注册器

如果域名的所有者是一个账户地址,那么用户可以调用注册器的接口管理二级域名。

注册器

(ens的设计是拥有域名的智能合约叫做注册器,实际上注册器只是owner的一个特例, 我们将域名的所有者和注册器分开了,这个系统会变得更加清晰。 大部分用户并不会去卖自己的二级域名,所以大部分用户无需配置注册器,配置解析器即可)。

注册器专门负责将一个域名的子域名重新分配给其他所有者。 注册器会调用域名根脚本进行操作。 域名根会检查注册器是否有权限操作此域名。 注册器有两个功能:

  1. 将一个域名的子域名重新分配给其他所有者。
  2. 查询一个子域名的拥有者是否合法,因为存在三级域名卖掉了,然后二级域名转让给别人这种情况。

所以在做完整解析的情况下,解析过程会询问注册器,他的下级域名是不是分配给了指定的所有者,如果没有,则此解析无效。

注册器是一个智能合约,可以有不同种类的注册器:

  1. 先到先得注册器,大家可以自由抢域名。测试网.test后缀域名将采用先到先得注册方式。
  2. 管理员手动分配注册器,由一个管理员来设置将子域名的所有权如何处理。通常情况下,个人持有的二级域名会通过手动方式分配子域名。
  3. 拍卖注册器。测试网.neo后缀的域名及主网.neo后缀域名都会通过抵押拍卖的方式注册。

解析器

NNS最主要的功能,就是完成从域名到解析器的映射。 解析器是一个智能合约,他来完成实际将名字翻译成地址的实际过程。 只要遵循NNS解析器规范的智能合约就可以被配置为解析器。NNS会提供通用的解析器。

如果要增加新的协议类型,在对现有NNS规范没有颠覆性改变的情况下,都可以不需改动NNS系统,直接配置实现。

解析规则

域名的存储

NNS中存储的域名为32字节散列值,而不是域名原文的文本。这有几个设计原因:

  1. 处理过程统一,允许任意长度的域名。
  2. 一定程度保留了域名的隐私。
  3. 将域名转换为散列的算法称为NameHash,我们将在其他的文档资料中对他进行解释。 NameHash的定义方式为递归式。

比如aaa.neo 对应

hashA  =  hash256(hash256("neo") + "aaa")

bbb.aaa.neo对应

hashB  =  hash256(hashA + "bbb")

那么 ccc.bbb.aaa.neo 对应

HashC  =  hash256(hashB+"ccc")

这样的定义方式让我们可以将所有层次的域名,一级,二级到无数级,都扁平化的保存在一个Map<hash256, 解析器> 的数据结构中。

这正是注册器保存域名解析的方法,这个递归计算NameHash的方式,可以用一个函数表达:

Hash = NameHash("xxx.xxx.xxx…");

NameHash实现方法请参考 NameHash算法详解

解析过程

用户调用根域名的解析函数进行解析,根域名提供完整和快速两种解析方式。可根据需要调用,也可以直接查询解析器,自行调用。

快速解析方式

快速方式域名根直接查表完整域名的解析器,如果没有,查询父域名的解析器。然后调用解析器解析。 快速方式运算次数少,但可能存在一个漏洞,即为三级域名卖给了别人,解析器存在,但是二级域名已经转让的情况。 此时依然可以正常解析

完整解析方式

完整方式,域名根将从根域名开始,逐层检查所有权和TTL,如果不符合将失败。 运算次数较多,与域名级数线性增长。

NNS经济模型

在NNS的经济模型里引入两中代币,一种是NNC,是由NEL为NNS发行维护的股权证明代币。总发行量10亿枚。 另一种是CGAS,前期由NEL提出并开发,后由NEO官方进行维护,是一种基于Nep5发型的代币,可以与GAS进行1:1绑定,支持双向兑换。

NNS根域名通过NNC持有者投票启动,投票分为两种模式,一种是管理员启动根域名投票, 3天内反对票少于30%则根域名启动,一种是任何NNC持有者启动域名投票, 3天内赞成票超过50%则根域名启动,无论哪种方式,投票者或不投票者都是博弈的参与者, 域名注册GAS会再分配给NNC持有者。

在NNS系统中,所有拍卖域名收取的手续费会进入奖池系统进行重新分配, 针对用户持有的NNC的数量,分配相应比例的CGAS。

NNC股权证明代币

NNC是NNS系统引入的一种股权证明代币。NNS为了实现系统的可持续性, 引入了手续费再分配系统, 所有域名拍卖收取的手续费将完全再分配给NNC的持有者。 除此之外,当域名系统移植到其他公链(例如本体网络)时,持有NNC的用户依然可以在其他公链获取等比例的代币。 当官方开放域名二级交易市场时,NNC将作为支付代币进行域名的买卖。在未来,NNC的使用场景也会不断拓宽。

NNC的初始发放为空投的形式,接收NNC空投需要用户持有NEO。空投按照NEO持有量1:0.1进行发放。

除了空头外,在NNS系统上线后,用户注册新的域名也将获得一定数量的NNC作为奖励,NNS理事会将会拿出总共10%的NNC来激励用户注册域名。

CGAS燃料代币

为了方便GAS在应用合约中的使用,NNS发行了一种基于NEP5的代币CGAS,总量一亿枚,与NEO的GAS燃料币进行1:1绑定,支持双向兑换。

兑换CGAS使用的GAS将会保存在CGAS合约的账户中,NNS不会转移或者使用这些GAS,因此可以保证只要用户持有CGAS,一定可以兑换到等量的GAS。 .. note:

CGAS原名SGAS,由NEL开发并发布,现由NEO官方维护。

在NNS系统中,CGAS主要有以下功能:

  • 与GAS双向兑换。
  • 向注册器充值/提款。
  • 参与域名竞拍。
  • 竞拍手续费支付。

除了在NNS系统内使用之外,由于CGAS本身是发布在主网上的NEP5代币系统,因此所有的合约应用也都可以使用这个CGAS合约来进行便捷的合约内GAS操作。

分红池

用户在域名竞拍时,NNS将会产生CGAS的收入,主要来源有两个:

  1. 用户拍卖款。如果用户拍卖得到域名所有权,那么将收取用户全部的竞拍款作为手续费。
  2. 流拍用户的拍卖手续费。对于参与竞拍,但是没有拍得域名的用户,收取出价的5%作为手续费。

所有的手续费收入将会转入分红池中,在奖池中,所有持有NNC的用户可以收到依据自己持有的NNC比例发放的CGAS分红。

分红池将以中心化形式进行。

域名浏览器

NNS域名浏览器是提供NNS域名查询,拍卖,转让等功能的入口。

反向解析

NNS将支持反向解析,反向解析将称为验证地址、验证智能合约的一个有效手段。

路线图

2018年1季度

  • 2018.1 正式发布NNS技术白皮书
  • 2018.1 完成技术原理测试和验证
  • 2018.1.31 在测试网发布包括注册器、解析器的NNS第一阶段测试服务,任何人可以注册未被注册且符合规则的域名

2018年2季度

  • 2018.3 发布基于测试网的域名浏览器V1
  • 2018.4 在测试网发行NNC
  • 2018.5 在测试网发布包含竞标服务的NNS第二阶段测试服务,任何人可以向NEL申请NNC进行竞标测试域名
  • 2018.5 发布基于测试网的域名浏览器V2

2018年3季度

  • 2018.6 在主网发行NNC
  • 2018.10 在主网上发布NNS合约,开放5个字符以上的.neo后缀域名,Neo域名时代来临
  • 2018.11 发布基于正式网的域名浏览器

2018年4季度

  • 实现域名交易所

2019年

  • 实现租金机制
  • 完全开放.neo后缀域名

详细设计

NNS协议规范

通常我们在互联网上使用的url如下

http://aaa.bbb.test/xxx

其中:

  • http是协议(protocol),NNS服务请求时会把域名和协议分开传递
  • aaa.bbb.test是域名,NNS服务请求时使用域名的hash
  • xxx是路径,路径不是在dns的层次处理,对于nns也一样,如果有路径,交由其他的方式处理

NNS的协议使用字符串定义

以下均为暂定

http协议

http 协议指向一个string,表示一个互联网地址

addr协议

addr 协议指向一个string,表示一个NEO address,形如 AdzQq1DmnHq86yyDUkU3jKdHwLUe2MLAVv

script协议

script 协议指向一个byte[],表示一个NEO ScriptHash,形如0xf3b1c99910babe5c23d0b4fd0104ee84ffeec2a5

同一域名的不同协议做不同处理

http://abc.test 可以指向 http://www.163.com

addr://abc.test 可以指向 AdzQq1DmnHq86yyDUkU3jKdHwLUe2MLAVv

script://abc.test 可以指向 0xf3b1c99910babe5c23d0b4fd0104ee84ffeec2a5

NameHash算法详解

NNS中存储的域名为32字节散列值,而不是域名原文的文本。这有几个设计原因:

  • 处理过程统一,允许任意长度的域名。
  • 一定程度保留了域名的隐私
  • 将域名转换为散列的算法称为NameHash

域名协议

通常我们在互联网上使用的url如下

http://aaa.bbb.test/xxx

其中:

  1. http是协议(protocol),NNS服务请求时会把域名和协议分开传递
  2. aaa.bbb.test是域名,NNS服务请求时使用域名的hash
  3. xxx是路径,路径不是在dns的层次处理,对于nns也一样,如果有路径,交由其他的方式处理

NNS服务使用的不是域名,而是域名的名字数组,这样处理起来更加直接

域名 aaa.bb.test 转成字节数组就是[“test”,”bb”,”aa”]

你可以这样调用解析

NNS.ResolveFull("http",["test","bb","aa"]);

交由合约去计算出namehash ## NameHash算法 NameHash算法是将域名转成DomainArray以后,逐级连接计算hash的方法,代码如下:

//域名转hash算法
static byte[] nameHash(string domain)
{
    return SmartContract.Sha256(domain.AsByteArray());
}
static byte[] nameHashSub(byte[] roothash, string subdomain)
{
    var domain = SmartContract.Sha256(subdomain.AsByteArray()).Concat(roothash);
    return SmartContract.Sha256(domain);
}
static byte[] nameHashArray(string[] domainarray)
{
    byte[] hash = nameHash(domainarray[0]);
    for (var i = 1; i < domainarray.Length; i++)
    {
        hash = nameHashSub(hash, domainarray[i]);
    }
    return hash;
}

快速解析

完整的解析传入整个DomainArray,由智能合约去逐个检查一层层解析。 计算NameHash的过程也可以挪到客户端算好,再传入智能合约。 调用方式如下

//查询 http://aaa.bbb.test
var hash = nameHashArray(["test","bbb"]);//可以客户端计算
NNS.Resolve("http",hash,"aaa");//调用智能合约

或者

//查询 http://bbb.test
var hash = nameHashArray(["test","bbb"]);//可以客户端计算
NNS.Resolve("http",hash,"");//调用智能合约

你也许会考虑查询 aaa.bbb.test 的过程为什么不是这样

//查询 http://aaa.bbb.test
var hash = nameHashArray(["test","bbb","aaa"]);//可以客户端计算
NNS.Resolve("http",hash,"");//调用智能合约

我们要考虑aaa.bb.test 是否拥有一个独立的解析器,如果aaa.bb.test被卖给了别人,他指定了一个独立的解析器,这样是可以查询到的。 如果aaa.bb.test 并没有独立的解析器,他是有bb.test的解析器来解析。 那么这样就无法查询到

而采用第一种查询方式,无论aaa.bb.test 是否拥有一个独立的解析器,都可以查询到。

顶级域名合约详解

顶级域名合约的函数入口如下:

public static object Main(string method, object[] args)

部署时采用 参数 0710,返回值 05 的配置

顶级域名的接口分为三部分

  • 通用接口 不需要权限验证,所有人都可以调用
  • 所有者接口 仅接受所有者签名,或者由所有者脚本调用有效
  • 注册器接口 仅接受由注册器脚本调用有效

通用接口

通用接口,不需要权限验证。 代码如下

if (method == "rootName")
    return rootName();
if (method == "rootNameHash")
    return rootNameHash();
if (method == "getInfo")
    return getInfo((byte[])args[0]);
if (method == "nameHash")
    return nameHash((string)args[0]);
if (method == "nameHashSub")
    return nameHashSub((byte[])args[0], (string)args[1]);
if (method == "nameHashArray")
    return nameHashArray((string[])args[0]);
if (method == "resolve")
    return resolve((string)args[0], (byte[])args[1], (string)args[2]);
if (method == "resolveFull")
    return resolveFull((string)args[0], (string[])args[1]);
根域名
rootName()

返回当前顶级域名合约对应的根域名 返回值为string

顶级域名哈希
rootNameHash()

返回当前顶级域名合约对应的NameHash 返回值为byte[]

域名信息
getOwnerInfo(byte[] namehash)

返回一个域名的信息 返回值为一个如下数组

[
    byte[] owner//所有者
    byte[] register//注册器
    byte[] resolver//解析器
    BigInteger ttl//到期时间
]
单级域名哈希
nameHash(string domain)

将域名的一节转换为NameHash 比如

nameHash("test")
nameHash("abc")

返回值为byte[]

子域名哈希
nameHashSub(byte[] domainhash,string subdomain)

计算子域名的NameHash, 比如

var hash = nameHash("test");
var hashsub = nameHashSub(hash,"abc")//计算abc.test 的namehash

返回值为byte[]

域名哈希
nameHashArray(string[] nameArray)

计算NameArray的NameHash,aa.bb.cc.test, 对应的nameArray是[“test”,”cc”,”bb”,”aa”]

var hash = nameHashArray(["test","cc","bb","aa"]);
域名解析
resolve(string protocol,byte[] hash,string or int(0) subdomain)
  • protocol 协议类型。比如http是将域名映射为一个网络地址,addr是将域名映射为一个NEO地址(这可能是最常用的映射)
  • hash 要解析的域名Hash
  • subdomain 要解析的子域名Name

应用代码如下

var hash = nameHashArray(["test","cc","bb","aa"]);//客户端计算好
resolve("http",hash,0)//合约解析 http://aa.bb.cc.test

or

var hash = nameHashArray(["test","cc","bb");//客户端计算好
resolve("http",hash,“aa")//合约解析 http://aa.bb.cc.test

返回类型为byte[],具体byte[]如何解读,由不同的协议定义,addr协议,byte[]就存的字符串。 协议约定另外撰文。

除了二级域名的解析,必须使用resolve(“http”,hash,0)的方式,其余的解析建议都使用resolve(“http”,hash,”aa”)的方式。

域名完整解析
resolveFull(string protocol,string[] nameArray)

解析域名,完整模式

  • protocol 协议类型
  • nameArray 要解析的域名数组

这种解析方式唯一的不同就是会逐级验证一下所有权是否和登记的一致,一般用resolve即可

返回类型同resolve

所有者接口

所有者接口全部为 owner_SetXXX(byte[] srcowner,byte[] nnshash,byte[] xxx)的形式。 xxx 均是scripthash。

返回值均为 一个byte array [0] 表示失败 [1] 表示成功

所有者接口均接受账户地址直接签名调用和智能合约所有者调用。

如果所有者是智能合约,那么所有者应该自己判断权限,不满足条件,不要发起对顶级域名合约的appcall

域名转让
owner\_SetOwner(byte[] srcowner,byte[] nnshash,byte[] newowner)

转让域名所有权,域名的所有者可以是一个账户地址,也可以是一个智能合约。

  • srcowner 仅在 所有者是账户地址时用来验证签名,他是地址的scripthash
  • nnshash 是要操作的域名namehash
  • newowner 是新的所有者的地址的scripthash
域名注册
owner\_SetRegister(byte[] srcowner,byte[] nnshash,byte[] newregister)

设置域名注册器合约(域名注册器为一个智能合约)

域名注册器参数形式必须也是0710,返回05

必须实现如下接口:

public static object Main(string method, object[] args)
{
    if (method == "getSubOwner")
        return getSubOwner((byte[])args[0], (string)args[1]);
    ...

getSubOwner(byte[] nnshash,string subdomain)

任何人可调用注册器的接口检查子域的所有者。

对于域名注册器的其他接口形式不做规范,官方提供的注册器会另外撰文说明。

用户自己实现的域名注册器,仅需实现getSubOwner接口

域名解析
owner_SetResolve(byte[] srcowner,byte[] nnshash,byte[] newresolver)

设置域名解析器合约(域名解析器为一个智能合约)

域名解析器参数形式必须也是0710,返回05

必须实现如下接口

public static byte[] Main(string method, object[] args)
{
    if (method == "resolve")
        return resolve((string)args[0], (byte[])args[1]);
    ...

resolve(string protocol,byte[] nnshash)

任何人可调用解析器接口进行解析

对于域名解析器的其它接口形式不做规范,官方提供的解析器会另外撰文说明。

用户自己实现的域名解析器,仅需实现resolve 接口

注册器接口

注册器接口由注册器智能合约进行调用,只有一个

register_SetSubdomainOwner(byte[] nnshash,string subdomain,byte[] newowner,BigInteger ttl)

注册一个子域名

  • nnshash 是要操作的域名namehash
  • subdomain 是要操作的子域名
  • newowner 是新的所有者的地址的scripthash
  • ttl 是域名过期时间(区块高度)

成功返回 [1] ,失败返回 [0]

所有者合约详解

所有者合约工作方式

所有者合约采用Appcall的形式调用顶级域名合约的owner_SetXXX 接口

[Appcall("dffbdd534a41dd4c56ba5ccba9dfaaf4f84e1362")]
static extern object rootCall(string method, object[] arr);

顶级域名合约会检查调用栈,取出调用它的合约和顶级域名合约管理的所有者进行比对。

所以只有所有者合约可以实现管理。

所有者合约的意义

用户可以用所有者合约实现复杂的合约所有权模式

比如:

  • 双人共有,双人签名操作。
  • 多人共有,投票操作。

注册器合约详解

注册器合约采用Appcall的形式调用顶级域名合约的register_SetSubdomainOwner接口

[Appcall("dffbdd534a41dd4c56ba5ccba9dfaaf4f84e1362")]
static extern object rootCall(string method, object[] arr);

顶级域名合约会检查调用栈,取出调用它的合约和顶级域名合约管理的注册器进行比对。

所以只有指定的注册器合约可以实现管理。

合约入口

注册器参数形式必须也是0710,返回05

public static object Main(string method, object[] args)
{
    if (method == "getSubOwner")
        return getSubOwner((byte[])args[0], (string)args[1]);
    if (method == "requestSubDomain")
        return requestSubDomain((byte[])args[0], (byte[])args[1], (string)args[2]);
    ...

查询拥有者

getSubOwner(byte[] nnshash,string subdomain)

此接口为注册器规范要求,必须实现,完整解析域名时会调用此接口验证权利

nnshash 为域名hash

subdomain 为子域名

返回 byte[] 所有者地址,或者空

FIFS域名申请

requestSubDomain(byte[] who,byte[] nnshash,string subdomain)

此接口为演示的先到先得注册器使用,用户调用注册器的这个接口申请域名

  • who 谁在申请
  • nnshash 申请哪个域名
  • subdomain 申请的子域名

解析器合约详解

1.解析器自己保存解析信息

2.顶级域名合约会以nep4的方式调用解析器的解析接口获取解析信息。

3.解析器设置解析数据时采用Appcall的形式调用顶级域名合约的getInfo接口来验证域名所有权

[Appcall("dffbdd534a41dd4c56ba5ccba9dfaaf4f84e1362")]
static extern object rootCall(string method, object[] arr);

任何合约都可以通过 appcall 顶级域名合约getInfo接口的方式来验证域名所有权

合约入口

注册器参数形式必须也是0710,返回05

public static byte[] Main(string method, object[] args)
{
    if (method == "resolve")
        return resolve((string)args[0], (byte[])args[1]);
    if (method == "setResolveData")
        return setResolveData((byte[])args[0], (byte[])args[1], (string)args[2], (string)args[3], (byte[])args[4]);
    ...

解析域名

resolve(string protocol,byte[] nnshash)

此接口为解析器规范要求,必须实现,完整解析域名时会调用此接口最终解析

  • protocol 协议类型
  • nnshash 要解析的域名

返回byte[] 解析数据

设置解析参数

setResolveData(byte[] owner,byte[] nnshash,string or int[0] subdomain,string protocol,byte[] data)

此接口为演示的标准解析器所有,所有者(目前还只支持账户地址所有者)可以调用此接口配置解析数据

  • owner 所有者
  • nnshash 设置哪个域名
  • subdomain 设置的子域名(可以传0,如果设置的就是域名解析,非子域名传0)
  • protocol 协议字符串
  • data 解析数据

返回[1]表示成功 或者[0]表示失败

投标竞拍域名注册方式详解

竞标机制

ENS采用密封竞价机制,通过混淆金来实现隐藏真实出价,但这种机制无法实现完全密封,真实出价不会超出公开的总出价。

密封出价的用户体验很差,用户需要记住一串密码,而且必须在短暂的揭标期向合约出示密码,否则无法找回资金。

NNS仍然会采用竞标的方式实现域名的初始注册分发,但是和ENS不同,我们采用透明的増价竞拍机制。

这样做的好处是,用户不需要记住密文和揭标期,只要竞标结束,最终结果就可以出现。 但是透明増价竞拍会有一个问题,如果竞拍期是确定的,那么没人愿意在一开始的时候就为域名出价,这样别人就可以在快结束的时候出比你稍多一点的钱来获得域名。

NNS logo

确定期

为了解决先手劣势的问题,我们在竞拍结束时间上引入了随机性,竞拍分两个阶段,第一阶段是确定期,例如3天,这一期间的所有出价都有效。

随机期

如果确定期最后一天有人出价,则进入两天的随机期,否则竞拍即在确定期结束。 在随机期里竞拍结束时间是不确定的,需要等到两天后的未来块的哈希值确定,根据区间大小,越靠后出价越可能落在竞拍结束时间之后从而无效, 所以你应当尽早给出你的心理价位。需要注意的是,如果这次的出价触发了结束,那么这次的出价并不会生效。

竞价结束

随机期结束后,利用未来块哈希来确定随机期里面的竞价结束块,统计从开标到此结束块的出价,就可以确定中标人。

这种竞拍方式的最终结果是,如果你觉得没人和你抢拍域名,那么你只需要在开标前两天出价,第三天确定期结束即可获得域名。

如果有竞争,那么竞争主要发生在确定期和随机期交替的两端。 通过在竞拍结束时间引入随机性,并且越靠后出价落在有效期可能性越小,避免了透明增加竞拍中的后手优势问题。

域名的使用和续约

域名如果被成功拍卖,那么拍卖的获胜者就享有一年的使用权。时间是从域名申请开拍的时间开始,365天后为到期时间。

到期前三个月内为续约期,域名的使用者可以申请续约延长一年的使用时间,延长后的到期时间为原到期时间加365天。目前续约不收取任何费用。

域名到期之后,原使用者可以继续使用。但其余人此时可以申请开拍这个域名,如果拍卖成功,原使用者会丧失使用权。

合约内时间计算

合约自开拍后一定有三天的自由竞价期,可能有两天的随机期结束期。拍得域名后有一年的使用期(从域名开拍的时间开始计算), 使用期前有三个月的续约期。

合约内的时间跨度都是用时间戳来计算,例如开拍某个域名的高度是1000000,对应的时间戳是1515777377。 那么这个域名的自由期就是时间戳小于1515777377+3*24*3600的块。使用期的就是小于1515777377+365*24*3600的块。

交易服务

交易服务支持域名登记员发布域名所有权转让邀约,支持固定价格转让和荷兰式减价拍卖方式转让。

可循环分配代币CGAS技术实现

为了实现NNS系统的良性发展,NNS引入了可循环的代币系统, 同时引入股权代币用以对系统收益的再分配提供证明。

  1. NNC股权证明代币
  2. CGAS NEP5代币合约。

NNC主要作用是在域名拍卖获得的收益进行再分配时提供股权证明。除此之外,当域名系统移植到其他公链(例如本体网络)时,持有NNC的用户依然可以在其他公链获取等比例的代币。 当官方开放域名二级交易市场时,NNC将作为支付代币进行域名的买卖。在未来,NNC的使用场景也会不断拓宽。

CGAS是为了方便将GAS用于应用合约中而引入的与GAS 1:1 绑定的NEP5资产。

重新分配机制

NNS在用户进行域名拍卖的时候会收取手续费,这笔手续费收益将依据NNS用户的NNC持有数量进行再分配,完全重新分配给NNC持有者。

领奖机制

在新的NNS系统设计中,为了保持CGAS合约的独立性,我们移除了奖池合约,转而使用中心化的方法来进行分红的计算和分发。用户将不在需要手动领取分红。

CGAS接口(仅述相比NEP5多出来的接口)

CGAS首先符合NEP5规范,NEP规范接口不再赘述

mintTokens

通过GAS换取等量的CGAS,通过InvokeContract,转账到CGAS合约账户,然后调用合约的mintTokens转等量的CGAS到自己的账户。需要签名。

refund

通过CGAS合约将CGAS兑换为等量GAS,由CGAS合约对用户进行身份校验然后构造合约交易将CGAS合约账户的GAS转到用户账户,同时扣取用户账户的CGAS。需要签名。

migrate

用于合约升级,当合约出现问题需要更新版本时使用。只有超级管理员有权限调用。

NNS开发者手册

NNS 开发者API接口

工程名称 接口名称 接口功能 业务逻辑
blockAPI getblockcount 获取块数量 1.查询mongodb,获取已入库块数量
invokescript 试运行合约(调用) 1.以输入的合约调用脚本调用脚本调用后端cli,invokescript方法并返回
sendrawtransaction 发送交易 1.以输入的签名后脚本调用cli sendrawtransaction方法
2.如果成功,调用用thinsdk,ThinNeo.Transaction.Deserialize().GetHash()获取txid并返回
walletapi getbidlistbyaddress 根据地址查询竞拍域名列表 1.查询获取地址参与竞拍的域名
2.根据域名查询最近一次开拍的数据记录
3.筛选出每个域名的当前状态
4.计算每个域名的竞拍状态/已过时间等信息
5.获取返回列表中结束域名的owner
getbiddetailbydomain 根据地址查询竞拍详情 1.查询域名竞拍历史
2.筛选出域名最近一次开拍的数据记录
3.累加每个阶段各个出价人的出价总和
getdomainstate 根据地址和场景id查询域名状态 1.查询域名竞拍详情
2.获取最大出价人记录
3.计算当前时刻自己出价
getbonushistbyaddress 获取指定地址的分红历史 1.查询分红通知表中from或to等于该地的记录
2.筛选所需字段直接返回
rechargeandtransfer 合并发送交易 1.根据传递过来的txhex1和txhex2顺序发送至核心
2.保存两笔交易发送和入链成功与否的状态标志
getrechargeandtransfer 查询第二笔交易是否成功 1.与上一个接口相对应
2.通过传递第一笔交易的txid查询第二笔交易成功与否状态
gettransbyaddress 根据地址查询交易记录 1.查询获取该地址交易记录列表
2.直接返回列表数据
getdomainbyaddress 获取指定地址拥有的域名 1.获取该地至拥有的域名列表
2.获取每个域名的解析映射地址和到期时间
3.过滤掉域名过期的且被他人再次使用的域名
hastx 根据交易id查询交易是否成功 1.查询库中是否有该笔交易
2.返回true/false
hascontract 根据交易id查询合约是否成功 1.查询该笔交易的内容
2.获取交易中包含合约信息
3.获取合约中displayName字段
4.解析displayName字段并返回该字段列表
searchdomainbyaddress 根据地址模糊查询竞拍域名列表 1.查询获取地址参与竞拍的域名并匹配指定前缀
2.根据域名查询最近一次开拍的数据记录
3.筛选出每个域名的当前状态
4.计算每个域名的竞拍状态/已过时间等信息
5.获取返回列表中结束域名的owner
scanapi getstatistics 获取统计信息 1.查询获取已使用域名个数
2.查询获取正在竞拍域名个数
3.奖金池和累计利息目前返回恒定值0
getauctingdomain 获取正在竞拍域名 1.分页查询获取状态为确定期和随机期的域名列表
2.获取正在竞拍域名总量
3.筛选所需字段返回
getaucteddomain 获取最有价值域名 1.分页查询状态为结束的域名列表
2.获取结束竞拍域名总量
3.筛选所需字段并按成交价倒序返回
getdomaininfo 获取域名信息 1.查询获取域名当前状态信息
2.区分正在竞拍和已经成交的域名信息字段
3.筛选所需字段返回
getbiddetailbydomain 获取域名竞拍详情 1.查询域名竞拍历史
2.筛选出域名最近一次开拍的数据记录
3.累加每个阶段各个出价人的出价总和
fuzzysearchasset 资产名称模糊查询 1.转换包含Antshare和AntCoin字符的资产名称前缀为neo
2.根据给定资产名称分页查询前缀查询utxo资产表和nep5资产表
3.转换返回资产中含antshare和antcoin的资产名称为neo
getrankbyasset 获取资产排行 1.分页查询资产列表
2.筛选返回所需字段
getrankbyassetcount 获取资产数量 1.查询获取资产种类数量

接口标准

变量类型

  1. 域名拍卖id hex256
  2. 用户地址 hex160
  3. 域名哈希 hex256
  4. 合约地址 hex160
  5. 用户资产额度 BigIngeger

变量/参数定义

常量
  1. CGAS 合约: DAPP_CGAS => hex160
  2. NNC合约: DAPP_NNC => hex160
  3. 竞拍注册器: DAPP_REG => hex160
  4. 先到先得注册器: DAPP_REG_FIFO => hex160
转账
  1. 转账源地址: who => hex160
  2. 转账目的地址: target => hex160
  3. 转账金额: amount => BigIngeger
域名拍卖
  1. 拍卖id :bid_id =>hex256
  2. 域名哈希:domain_hash => hex256
  3. 根域名哈希:root_hash =>hex256
  4. 子域名:sub_domain =>string
  5. 域名完全哈希:full_hash=>hex256

数据结构

NNS合约的对外统一接口有两个参数,一个是用于接收具体命令的,另一个是接收Object类型的参数列表的,所有合约的接口定义都如下:

public static object Main(string method, object[] args)

因此调用NNS合约的数据结构可描述为:

{
    appCall:合约地址
    method:合约方法
    params:参数列表
}

现阶段NNS在测试网已经部署完成,主网部署也即将进行,开发者可以根据本指导手册完成在自己开发的NEO钱包中对NNS的接入。

测试网NNS系统合约地址:

  1. 域名中心 **************** 0xd30348a37fb57b7a61f6637ecfc0da6b4eb08dd0
  2. 域名中心跳板 ************ 0x537758fbe85505801faa7d7d7b75b37686ad7e2d
  3. 标准解析器 ************** 0xd7bb680b4318f0823e968a5a067fc8bef35d13a1
  4. 先到先得注册器 ********** 0x4e02db8842aa400a4abd77e4bdf23c54ba4aa90e
  5. 拍卖注册器 ************** 0xd90d82bf64083312b0b7b8dc668d633cf56899ec
  6. CGAS ******************* 0xc7816d11287c08135f4e5f907af9e39754910ba3
  7. nnc ******************* 0xd8fa0cfdd54493dfc9e908b26ba165605363137b

先到先得注册器

先到先得注册器将只在测试网部署,采取先到先得的形式,用户注册后就可以直接使用,不需要支付手续费。

域名查询解析

我们在测试网中部署的顶级域名为 test 顶级域名,在用户注册域名时,需要获取顶级域名的哈希值,然后拼接上用户输入的域名,详情参考 domainhash。 获取域名注册信息的脚本构造如下:

var sb = new ThinNeo.ScriptBuilder();
sb.EmitParamJson([ "(bytes)" + domain.toHexString() ]);//第二个参数是个数组
sb.EmitPushString("getOwnerInfo");
sb.EmitAppCall(address);
appCall:DAPP_REG_FIFO
method:getOwnerInfo
params:[domain_hash]

交易类型:InvocationTransaction

签名:无

交易执行结果:

 {
      "jsonrpc": "2.0",
      "id": 1,
      "result": [{
              "script": "208563abfb9556bd12e5485bb873ea4860b6c047429e580a7bb36e9e2264c12f9f51c10c6765744f776e6572496e666f6750591a2f81a506786a39d9aeb4d7ee935a284f95",
              "state": "HALT, BREAK",
              "gas_consumed": "0.968",
              "stack": [{
                      "type": "Array",
                      "value": [{
                              "type": "ByteArray",
                              "value": "dcb4dc1afa7b11d9e465a0ce2c887d97c9e7233b"
                      }, {
                              "type": "ByteArray",
                              "value": ""
                      }, {
                              "type": "ByteArray",
                              "value": "6bcc17c5628de5fc05a80cd87add35f0f3f1b0ab"
                      }, {
                              "type": "ByteArray",
                              "value": "85fe115b"
                      }, {
                              "type": "ByteArray",
                              "value": "36630a8759e5000f88500017adfa5c8fe2be32ff"
                      }, {
                              "type": "ByteArray",
                              "value": "6a696e67687569"
                      }, {
                              "type": "ByteArray",
                              "value": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
                      }, {
                              "type": "ByteArray",
                              "value": ""
                      }]
              }]
      }]
}

关键数据路径如下:

域名所有者:owner = result.stack[0]

对请求结果的解析如下:

if (stack[ 0 ].type == “ByteArray”) {

info.owner = (stack[ 0 ].value as string).hexToBytes();

} if (stack[ 1 ].type == “ByteArray”) {

info.register = (stack[ 1 ].value as string).hexToBytes();

} if (stack[ 2 ].type == “ByteArray”) {

info.resolver = (stack[ 2 ].value as string).hexToBytes();

} if (stack[ 3 ].type == “Integer”) {

info.ttl = new Neo.BigInteger(stack[ 3 ].value as string).toString();

} if (stack[ 3 ].type = “ByteArray”) {

let bt = (stack[ 3 ].value as string).hexToBytes(); info.ttl = Neo.BigInteger.fromUint8ArrayAutoSign(bt.clone()).toString();

} if (stack[ 4 ].type = “ByteArray”) {

let parentOwner = (stack[ 5 ].value as string).hexToBytes();

} if (stack[ 5 ].type = “String”) {

let domainstr = stack[ 5 ].value as string;

} if (stack[ 6 ].type = “ByteArray”) {

let parentHash = (stack[ 6 ].value as string).hexToBytes();

} if (stack[ 7 ].type = “ByteArray”) {

let bt = (stack[ 7 ].value as string).hexToBytes(); let root = Neo.BigInteger.fromUint8ArrayAutoSign(bt);

} if (stack[ 7 ].type = “Integer”) {

let a = new Neo.BigInteger(stack[ 7 ].value as string);

}

其中address是域名中心的地址,domain就是用户域名的哈希结果。域名中心在解析到顶级域名为 test 时,会动态调用 test 注册器,请求响应域名的注册信息。

域名拍卖Id查询

域名注册

测试网中域名的注册过程相对简单,谁先注册域名谁就拥有这个域名的所有权。测试网注册域名首先需要获取 test 注册器的合约地址:

var sb = new ThinNeo.ScriptBuilder();
sb.EmitParamJson([ "(bytes)" + domain.toHexString() ]);//第二个参数是个数组
sb.EmitPushString("getOwnerInfo");
sb.EmitAppCall(address);

其中address是域名中心的地址,domain是 test 的哈希值。通过向域名管理中心发送 “getOwnerInfo” 指令并发送 test 的哈希值,就可以获取到 test 注册器的地址。

在获取到 test 注册器地址之后,就可以通过 test 注册器注册 test 域名:

sb.EmitParamJson([ "(addr)" + address, "(bytes)" + nnshash.toHexString(), "(str)" + doamin ]);
sb.EmitPushString("requestSubDomain");
sb.EmitAppCall(address);

这里的address是前一步获取到的 test 注册器的地址,注册的命令为”requestSubDomain”。

注册域名需要三个参数:

  • 第一个时域名本身
  • 第二个是上一级级域名的哈希值
  • 第三个是注册域名的用户的NEO账户地址

注意,在构造交易脚本是,参数倒序压栈。由于这个请求需要对用户的身份进行验证,所以本交易需要签名。

主网

主网的接入比测试网复杂许多,因为主网除了会引入拍卖机制外还有为了实现代币循环系统而引入的经济模型。

域名查询

主网的域名查询和测试网一样,都是直接向域名管理中心发送查询命令,然后传入需要查询的域名的明文+上一级域名的哈希值,域名中心通过动态向上一级域名请求域名信息来获取域名的注册详情。

详情参考 domaincheck

域名开拍

如果用户查询的域名没有被注册或者上一个所有者没有及时续费,那么用户就可以申请开拍该域名,域名开拍合约脚本格式:

appCall:DAPP_REG
method:wantBuy
params:[who,root_hash,sub_domain]

交易类型:InvocationTransaction

签名:用户签名

交易执行结果:

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": true
}

加价

域名在开拍成功之后,域名将会进入一个拍卖周期。在有效的周期时间内,所有想要得到这个域名的人都可以对这个域名进行出价和加价。

为了防止有人恶意竞拍,有效拍卖时间分为一个固定的拍卖时间和一个不固定的随机时间,域名的拍卖将在随机时间内任何时刻结束。

用户要对拍卖中的域名出价,首先需要获取该域名的拍卖id。获取域名拍卖id的交易构造如下:

appCall:DAPP_REG
method:getSellingStateByFullhash
params:[full_hash]

// var id = info3.value.subItem[0].subItem[0].AsHash256();

完全哈希,详情参考 NameHash算法详解

交易类型:InvocationTransaction 签名:无

交易执行结果:

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": [{
            "script": "20a9b961f8b37c39d969d764abff95435456ed0b5314d162edb85ba7c66e223f1951c11967657453656c6c696e675374617465427946756c6c6861736867f466384645d3e38445b8738bb7d9fa1a28665d50",
            "state": "HALT, BREAK",
            "gas_consumed": "0.843",
            "stack": [{
                    "type": "Array",
                    "value": [{
                            "type": "ByteArray",
                            "value": "8f0bfee402965aa7d718c1dc3108d9f20dd63295338938dd38ca802c0a9e23e2"
                    }, {
                            "type": "ByteArray",
                            "value": "1ed2b38c11c70aa02adedf9fe807482472daef00689af3eeb6141346ec3f3c70"
                    }, {
                            "type": "ByteArray",
                            "value": "6a696e67687569"
                    }, {
                            "type": "ByteArray",
                            "value": ""
                    }, {
                            "type": "ByteArray",
                            "value": "eb4817"
                    }, {
                            "type": "ByteArray",
                            "value": ""
                    }, {
                            "type": "ByteArray",
                            "value": ""
                    }, {
                            "type": "ByteArray",
                            "value": ""
                    }, {
                            "type": "ByteArray",
                            "value": ""
                    }]
            }]
      }]
}

结果中,我们需要的竞拍id的解析路径为 bid_id=result[‘stack’][‘value’][0][‘value’]

解析到域名的拍卖地址后,通过这个地址,用户就可以参与域名的竞拍。竞拍交易脚本构造如下:

appCall:DAPP_REG
method:addPrice
params:[
    who:竞拍人,
    bid_id:竞拍id,
    amount:出价
]

交易类型:InvocationTransaction 签名:用户签名

交易执行结果:

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": true
}

在每次加价成功之后,竞拍合约都会重新判断最高出价者,如果多个人都加到最高价,那么先出最高价的为最高出价人。

结束竞拍

在经过固定竞拍期和随机竞拍期之后,域名拍卖就会结束。拍卖结束之后用户将不能再进行出价。

参与竞拍的竞拍人可以调用接口结束竞拍,如果是域名的拍得人,可以领取域名所有权,其余的拍卖参与人,可以取回竞拍出价的90%,剩余10%作为拍卖手续费。

结束竞拍接口如下:

appCall:DAPP_REG
method:endSelling
params:[
    who:竞拍人,
    bid_id:竞拍id
]

交易类型:InvocationTransaction 签名:用户签名

交易执行结果:

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": true
}

参考文献

[1]. NEO White Paper: Superconducting Exchange. https://github.com/neo-project/ docs/blob/e01d268426a8b5f9b3676cfd03d0b8b83d7711a1/en-us/white-paper. md#highly-scalable-architecture-design, Accessed 2018.

[2]. NEO NEP-5: Token Standard. https://github.com/neo-project/proposals/blob/ master/nep-5.mediawiki, Accessed 2018.

[3]. ENS. https://github.com/ethereum/ens,Accessed 2018.

[4]. EIP137 - Ethereum Name Service. https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md,Accessed 2018.

[5]. EIP162 - Initial ENS Registrar Specification. https://github.com/ethereum/EIPs/issues/162,Accessed 2018.

总结

NNS项目是一个完全构建在NEO区块链上的一个智能合约协议层应用,是一个真正意义上的区块链落地应用。 NNS的所有服务都是由智能合约提供的,是分布式并且灵活可扩展的,不存在中心化风险。

NNS是一次NEO智能合约体系的大规模应用,为了实现解析器的灵活可扩展,我们应用了最新的NEP4动态调用,在经济模型中,我们会设计Vickrey拍卖合约和荷兰式拍卖合约, 为了实现系统费用更易重新分配,我们扩展了NEP5代币标准,增加了币天的概念,从而实现无需锁定代币就可以实现系统收入的重新分配,将应用代币和股权融合成一个代币。

域名服务能够提升区块链的易用性,并且具有丰富的使用场景,会形成围绕域名的生态系统。 未来,我们会和NEO生态的客户端合作,让所有的NEO钱包支持通过别名转账,我们也会探索一些新的使用场景,例如和宠物游戏合作,通过NNS为宠物起一个昵称。 未来,随着NEO生态的应用越来越多,NNS的域名也会越来越有价值。

Indices and tables