当前位置: 首页 > 科技观察

如何使用DNS搭建零成本的区块链数据库

时间:2023-03-18 15:14:56 科技观察

本文转载自微信公众号《区块链研究实验室》,作者连三丰。转载本文请联系区块链研究实验室公众号。区块链不仅仅是一个流行语。它也不限于加密货币和比特币。凭借其创造透明度和公平性的能力,这项技术正在彻底改变各个领域。应用范围从跟踪系统到保护数据,再到实施在线投票系统。它可以帮助实施反洗钱跟踪系统,或者简单地跟踪您在商店购买的产品的来源。正如信息科学中经常发生的那样,许多区块链平台管理所有的复杂性,使我们能够像简单的数据库一样简单地保存数据。在本文中,我想实现一个区块链数据库以了解此类解决方案的关键要素。而且,为了让它更具挑战性,我将在不使用任何数据库或服务器的情况下完成它。该解决方案使您可以轻松拥有可以验证和安全存储的不可变数据。这篇文章的结构如下:什么是区块链数据库以及如何使用它如何仅使用DNS服务实现区块链什么是区块链数据库以及如何使用像往常一样,我们可以从维基百科的定义开始:“一个区块链,[...],是一个不断增长的记录集合,称为块,使用链接进行加密。每个块包含前一个块的加密散列,这是一个时间戳[...]。根据设计,A区区块链可以抵抗其数据的修改。这是因为一旦记录下来,任何给定块中的数据都无法在不更改所有后续块的情况下进行追溯更改。”为了用作分布式账本,区块链通常由对等网络管理。网络共同遵守节点间通信和验证新区块的协议。换句话说,区块链的主要特征是:通过将一个记录连接到前一个记录来存储数据就是这样做的,所以你不能在不使所有数据乱序的情况下更改记录将数据存储在分布式数据库中那么,你如何创建它?我在想什么节点链或多或少是一个链表,其中每个块都有一个不可变的哈希值。一旦完成,您所需要的只是一个安全的分布式数据库来存储您的数据。什么是古老的分布式数据库?嗯,每个人都有一个分布式数据库,但没人知道!我说的是DNS。是的,它是分布式的,它存储数据。每个人都有一个DNS服务。我意识到这不是预期用途,但让我们来玩一玩。协议的工作流程是受信任的权威机构写data到DNS。每条记录都有一个唯一的密钥,它是内容的哈希值。这意味着,通过更改数据,您将更改ID,并且指向该ID的所有子项都将不一致。此外,DNS协议是分布式的,因此许多数据副本在许多服务器之间共享,这意味着您的一个DNS将离线,而另一个继续为数据提供服务。还要考虑DNS被广泛缓存,这使您的通信性能良好(永远不会成为不可变数据缓存的问题)。系统使用所有公司已有的DNS作为存储,因此没有额外的成本。DNS本身就是一个分布式数据库。现在我们已经定义了存储数据的位置,我们只需要了解如何存储数据。下一步是定义一个通信协议,使各方都能发挥自己的作用。下图显示了流程。DNS区块链工作流程。在上图中,我们有:在DNS上发布的推力实体。这是写作的关键——别人可以写记录,但他们不能理解。消费者,即推力生产者,读取数据数据,可以是任意JSON数据。您可以选择将其公开或私有。如何实施现在我们知道如何去做,并且我们有启动该工具的工具,我们只需要使用源代码。为了使用DNS实现区块链,我们必须面对一些重要问题:DNS限制——DNS不是为存储大数据而设计的。我们想用TXT记录,但是只有254个字符。如果我们要存储一个大的JSON对象,这是一个很大的限制。安全性——即使我们想公开我们的数据,DNS使用的UDP协议也是有问题的。它没有加密,也没有像HTTPS协议中那样的证书机制来驱动授权。数据是按设计公开的——这可能是个问题。所有这些方面都有一个解决方案,正如您将看到的,它很容易实施。事实上,通过使用密码学和独创性,我们会找到解决上述所有问题的巧妙方法。让我们看看它是如何工作的。创建沙盒环境第一步是创建一个我们想要玩的沙盒环境。我们需要的只是一个带有API系统的本地DNS服务器。我们通过创建一个托管此文件的docker-compose文件来做到这一点。我使用了一个VisualStudio项目,我在其中创建了一个我们将用于验证数据的Web应用程序、一个将成为我们核心的库和一个测试项目。结果如下:通过运行docker-composeup的DNS区块链项目,全部启动并准备好进行测试。对于DNS部分,我使用了非常轻量级且具有HTTPAPI的DNS。它使用以下配置运行:version:'3.4'services:blockchaindns.web:image:${DOCKER_REGISTRY-}blockchaindnswebbuild:context:.dockerfile:BlockChainDNS.Web/Dockerfiledns:image:tgpfeiffer/shaman-dnscommand:shaman--server--tokenxxx--api-listen0.0.0.0:1632--dns-listen0.0.0.0:53-ltrace--insecureports:-1632:1632-53:53/udp其中xxx是你想要使用的身份验证令牌,DNS已配置为接受来自所有主机(0.0.0.0:port)的请求。使用docker-composeup运行它后,您可以使用控制台对其进行测试:#createarecordinShamanDNScurl--location--requestPOST'localhost:1632/records'\--header'Content-Type:application/json'\--data-raw'{“domain”:“test.myfakedomain.it.”,“records”:[{“ttl”:60,“class”:“IN”,“type”:“A”,“address”:“127.0.0.1"}]}'#testtherecordnslookuptest.myfakedomain.it127.0.0.1#output#Server:UnKnown#Address:127.0.0.1#Responsefromserver:#Nome:test.myfakedomain.it#Address:127.0.0.1现在我们有了使用本地DNS,我们可以创建一个可以通过API管理DNS记录的客户端。DNS客户端第二步是包装要在应用程序中使用的DNS客户端功能。我这里想做的是以后有能力改变DNS服务,所以我创建了一个接口和一个类来实现它。以下代码片段显示了接口:publicinterfaceIDNSClient{TaskGetRecord(stringhost,stringzone);TaskAddRecord(DNSEntryentry);ActionInit{get;set;}}如您所见,客户端实现执行HTTP调用存储记录。您可以在本文末尾的GitHub项目中找到完整的类实现。我们只为萨满实施了本地提供商,但很容易将其扩展为支持大多数现代托管提供商上的任何商业DNS。区块链服务现在我们有了执行部分,是时候实现业务逻辑了。所有工作都在客户端完成,客户端会计算要存储的数据并调用DNS客户端方法来持久化记录。服务层由两部分组成:BlockChainNode:节点的表示BlockChainService:实现逻辑的服务让我们详细看看这些类是如何工作的。区块链节点这是一个具有JObject属性的简单类,用户可以在其中存储任何数据。它计算键控哈希数据。数据包含历史记录,它是指向父项的链接。仅更改数据的字节会更改密钥,这会使后面的节点不一致。以下代码显示了此类中最重要的部分。publicclassBlockChainNode{publicBlockChainNode(){this.History.CollectionChanged+=History_CollectionChanged;}privatevoidHistory_CollectionChanged(objectsender,System.Collections.Specialized.NotifyCollectionChangedEventArgse){this.Data["_history"]=JArray.FromObject(this.HistoryJObject_Object);}私有();publicJObjectData{get{return_data;}set{_data=value;History_CollectionChanged(null,null);}}publicstringHash{get{returnGetHash(this.ToBase64());}}publicObservableCollectionHistory{get;set;}=newObservableCollection();//FirsttolastpublicstringToBase64(){varcontent=UnicodeEncoding.Unicode.GetBytes(Data.ToString(Formatting.None));returnConvert.ToBase64String(content);}publicstaticstringGetHash(stringtext){using(varmd5=MD5.Create()){returnBase32.ToBase32String(md5.ComputeHash(UTF8Encoding.UTF8.GetBytes(text))).ToLower();}}}这段代码最相关的部分是:数据对象:用户可以存储的地方data历史记录的JSON对象:与数据同步的可观察列表(“历史记录”中的任何更改都会更改_history节点,反之亦然)哈希:根据数据文本表示的MD5计算的哈希。结果以Base32算法编码-类似于Base64,但仅使用四个字节且仅包含小写字符。这是因为DNS不区分大小写,使用广泛使用的Base64编码会产生不一致的数据。现在我们有了模型,我们必须继续下一步:服务实现的业务逻辑。区块链服务区块链服务实现保存、读取和验证记录的方法。困难的部分是绕过DNS服务器记录长度的255个字符限制。解决方案是以Base64对内容进行编码,然后使用命名约定将其拆分为单独记录中的块。密钥用作URL的一部分。因此,对于项目mykey.domain.dom,我们将有0.mykey.domain.dom、1.mykey.domain.dom等。下一段代码显示了节能方法。privateintWriteDNFSragmentedText(stringbaseUrl,stringvalue,intsize){vartokens=Tokenize(value,size).ToList();inti=0;foreach(vartokenintokens){WriteDNSRecord($"{i}.{baseUrl}","TXT",token);i++;}returni;}privatevoidWriteDNSRecord(stringdomain,stringtype,stringvalue){this.client.AddRecord(newDNSEntry(){Domain=domain,Type=type,Value=value});}从前面的片段可以看出调用WriteDNFSragmentedText,输入文本被拆分,数据保存在许多DNS条目中。读取数据则相反。我尝试获取子记录0、1、2等等,直到有数据为止。一旦我收集了所有Base64块,这个过程就是连接它们、解码并获得纯JSON。privatestringReadDNFSragmentedText(stringdomain){Listfragments=newList();for(inti=0;i<1000;i++){varfragmentUrl=$"{i}.{domain}";varresult=ReadDNSTxtResult(fragmentUrl);if(result==null)break;//otherwiseparentdomainvaluewillbeaddedfragments.Add(result);}returnstring.Join("",fragments);}privatestringReadDNSTxtResult(stringfragmentUrl){if(!fragmentUrl.EndsWith(".")){fragmentUrl=fragmentUrl+".";}varresult=lookup.QueryAsync(fragmentUrl,QueryType.TXT).Result;if(result!=null&&!result.HasError&&result.Answers?.Count>0){varresultDomain=result.Answers.FirstOrDefault().DomainName.Value;if(resultDomain==fragmentUrl){returnresult.Answers.TxtRecords().FirstOrDefault()?.EscapedText.FirstOrDefault();}}returnnull;}客户端可以方便的验证获取到的数据是否为加密密钥和是有效的,因为客户端可以获取数据,对其进行哈希处理并比较结果。此外,客户端可以递归验证以检查所有父节点是否为真。这就是验证过程所做的。它由下一部分代码表示:publicListValidate(JObjectdata,stringkey,intdb,stringdomain,byte[]privateKey,stringexpectedKey=null){varerrors=newList();//ValidateBase:Coherencebetweeendataandvalues.varcomputed=this.获取(键、数据库、域、私钥);如果(键!=计算哈希){errors.Add(“Keymismatch”);}ValidateHierarchy(键、数据库、域、私钥、referrors);returnerrors;}privateListValidateHierarchy(stringkey,intdb,stringdomain,byte[]privateKey,refListerrors){varcomputed=this.Get(key,db,domain,privateKey);if(computed==null)returnnewList();如果(computed.History.Count>0){varhierarchy=ValidateHierarchy(computed.History.Last(),db,domain,privateKey,referrors);if(hierarchy.Count!=computed.History.Count-1){错误。添加($"{computed.Hash}:historycountnotmatchwithlookup");}else{for(inti=0;i