互联网计算机的响应如何被认证为真实的
作为一个与互联网计算机区块链交互的用户,你怎么能相信你得到的回应?换句话说,您如何确认您真的在与互联网计算机交谈?
让我们从它在理想化世界中的样子开始,想象一下,您正在与完全在可信环境(例如,您自己的计算机或公司的网络)中运行的服务进行交互。就本博文而言,我们正在寻找的服务可能是演出门票的预订机构。
因此,您与该服务互动并为即将到来的节目预订门票。只要服务和网络值得信赖,您就可以放心并确信您确实为演出预订了 5A 座位。实际上,您可能正在通过互联网与服务交谈,这是一个充满恶意方的大而危险的地方。
现在有一个问题:如果您和您正在与之交谈的服务之间的某个人想要操纵您的请求和相应的响应怎么办?他们可能会给出错误的回应,例如错误的座位,或者通知您实际上并没有在您预订机票时预订。相反,他们可能会告诉您您购买了机票,而实际上您并没有购买。
这正是这里要解决的问题,在互联网上,解决方案是已知的,它基于公钥加密,该服务有一个密钥,这是上图中右侧的蓝色键。当它响应您的请求时,它会使用密钥来签署该响应,签名由上图中的蓝丝带表示。
作为用户,您知道服务对应的公钥。使用该公钥,您可以验证响应上的签名。您可以检查响应是否确实来自该服务,并知道您确实已经预订了机票,这是您通过互联网使用服务时的传统体验。
谈到互联网计算机,情况相似但略有不同。您正在与之交互的服务并不是一个拥有自己密钥的全栈服务器,而只是运行在容器智能合约中的代码(即运行在互联网计算机上的应用程序),并且它不会自己做密码学。相反,它由所谓的子网执行,子网是共同运行容器的节点集合,在本例中,它是预订机构。
整个子网可以创建一个签名,就像我们之前看到的那样。即使有多个节点组成子网,其中一些节点可能是恶意的,但只有在绝大多数节点同意签名的情况下,它才能创建签名。这意味着即使少数节点是恶意的,它们也无法创建虚假签名。因此,当从具有子网有效签名的互联网计算机接收到签名响应时,即使某些节点是恶意的,用户也知道这是约定的响应。
这些签名称为阈值签名,有趣的是,这些签名的创建成本有点高,节点需要协作和通信以创建这些阈值签名之一。因此,当每秒可能有数千个请求时,就无法对每个单独的响应进行签名。
为了解决这个问题,响应没有单独签名,网络所做的是将所有响应记录在将被称为本博客文章目的的子网“文档”中,其中列出了所有响应。
正如您在此处看到的,在这些响应中的某处,注意到当本示例中的用户 Kim 要求服务预订演出门票时,Kim 的预订成功且座位为 5A,还有对 Larry 和其他用户的回应。
理论上可以做的是整个“文档”,它具有子网的这个阈值签名密钥的签名,可以被获取并发送给用户,用户可以验证他们的响应是否在该文档中。这并不理想,因为所有响应都记录在此文档中,因此它会很大。此外,对于 Larry 对完全不相关的服务提出的请求的响应,Kim 真的没有任何业务。
可以做的是编辑文档,删除 Kim 不应该看到的部分,并且只包含与 Kim 相关的部分,签名将仍然有效。这意味着 Kim 现在可以查看此文档并验证对她的请求的响应是她获得了一张 5A 座位的机票并且签名是正确的,编辑向 Kim 隐藏了她不应该看到的信息,从而使文档更小,其效果原理上类似于文档压缩或图像压缩。因此,通过这种方式,Kim 会得到合理规模的响应,只向 Kim 传达 Kim 需要知道的信息。
但是有一个点需要考虑,正在与之通信的这个子网称为子网 5,这意味着有多个子网。事实上,互联网计算机是一个无限可扩展的区块链,其子网数量不断增加。在上图中,Kim 知道与子网 5 对应的公钥。
由于子网太多,如果所有用户都需要与所有子网对应的所有公钥,那将是不理想的。这个想法是,Kim 实际上只有一个公钥,一旦 Kim 信任这个公钥,它是少量数据,应该足以验证来自互联网计算机的所有数据。
这样做的方法是,互联网计算机的这个公钥(在上面幻灯片中以橙色表示)恰好是一个特定子网的公钥,称为根子网。根子网有点像任何其他子网:它有节点,其中一些可能是恶意的,而大多数是诚实的,根子网的区别在于用户知道它的公钥。
该根子网还在其文档中列出了互联网计算机的所有子网,正如您所看到的,它列出了互联网计算机上的子网 5,并且它有蓝色键。
除了对用户的响应之外,子网还可以包括该文档的编辑副本,这称为委托,因为它是将信任从根子网委托给子网 5。现在用户可以查看此文档并找出用户与之通话的子网(子网 5)的公钥,以及用户会在那里找到蓝色钥匙。这样就解决了用户不需要知道每个子网的公钥,而只知道一个子网的公钥的问题。
这就是子网代表互联网计算机验证对所谓更新调用的响应的方式,更新调用是用户访问互联网计算机、通过共识、包含在区块中等的请求。它们可以更改容器服务的状态,这对于预订节目当然很重要,因为您希望预订容器记录节目已被预订,最终您会得到响应并且响应得到认证,这意味着用户可以按照我刚刚概述的方式验证内容。
整个认证过程对容器中的应用程序代码以及客户端的应用程序代码完全透明,然而,更新调用很慢,因为它们必须通过共识等等,这大约需要两秒钟左右。
延迟最小化是还提供查询调用的原因,查询调用是对不改变容器状态的方法的调用。因为它们不会改变状态,所以没有必要将它们按确定性顺序排列或在所有需要新状态的节点上执行它们。一个查询调用可以由一个节点响应,而不是整个子网。
这意味着查询调用可以非常快 —— 几毫秒而不是几秒,但是您可能已经注意到,现在出现了一个问题。我已经说过整个子网是值得信赖的,只有整个子网才能执行此蓝色签名,但子网上的单个节点可能是恶意的。
当那个可能是恶意的单节点响应你的查询调用时,你怎么知道响应是正确的?单个节点无法使用蓝色密钥对响应进行签名,因为单个节点无法单独签名。那么,您如何在进行查询调用的同时仍能从互联网计算机获得经过身份验证的响应?
这可以使用称为认证变量的功能来实现,一个重要的因素是,这需要容器的合作。为了验证更新调用,容器代码和应用程序开发人员无需执行任何特殊操作,它开箱即用,为了保护这些查询调用并使用经过认证的变量来保护它们,现在需要容器提供帮助。
这就是它的样子,当用户进行预订时 —— 请注意,我们现在正在查看用户想要预订演出的请求 —— 容器正在系统的特殊区域中记录 Kim 已预订座位 5A 的状态,它基本上是在告诉系统,“请记住,Kim 已经预订了 5A 号座位”,现在这与容器的正常状态分开存储。
事实上,它被写入同一个文档中,该文档记录了之前看到的所有响应。请记住,该文档是由整个子网使用此蓝丝带签名的。
这有助于查询调用发生时,假设一天后,Kim 忘记了她的座位号,想要使用查询电话询问容器她的座位是什么。查询调用是一个非常好的用例,因为只需检查您拥有的座位不需要更新调用。
在处理查询时,容器可以要求系统提供子网文档的编辑副本,其中除了容器本身要求系统记住的信息(即,Kim 的座位为 5A)之外的所有内容都已编辑。现在,容器可以在对 Kim 的响应中包含所谓的证书(即,编辑后的文档)。
现在 Kim 实际上可以检查容器的响应(“你有 5A 位”)是否正确,当然,这意味着检查签名,但有现有的代码,它与检查对更新调用的响应的有效性的机制相同。
Kim 还需要检查信息是否太旧,因为也许攻击者给了 Kim 一个有效的响应,但它仅在一周前有效,而且情况已经发生了变化。所以有一些事情是用户(特别是用户端的客户端代码)必须检查的,但是当它们被检查时,可以看到响应是正确的。
由于保持系统简单的限制,出现了一个小的复杂性,在设计像互联网计算机这样的系统时,总是需要权衡取舍。系统是否变得更复杂一些并且它的使用(即,从容器的角度来看)更方便,或者它是否只提供了最低限度的功能并将一些负担放在容器以便可以保持系统简单,因此也安全和易于管理?
在这种情况下,容器只允许使用 32 字节的存储空间来使用我刚刚概述的功能。现在 32 字节可能足以说 Kim 已经预订了 5A 的座位,但是一旦超过几个人预订座位,那么 32 字节就已经不够了,但这不是根本问题。可以用来克服这个问题的技巧与之前我们在几张幻灯片中使用的技巧几乎相同,当时我们正在研究子网如何对多个响应使用一个签名。
同样,正在使用这些可编辑文档之一,所以这里可以做的是,当 Kim 预订节目时,容器将 Kim 拥有 5A 座位的信息存储在自己的文档中,它还包含有关其他人在节目中预订座位的其他信息,然后它计算文档的加密哈希,它唯一标识文档的内容,并要求子网存储和验证加密哈希。
当 Kim 现在向服务询问她拥有哪个席位时,该服务可以使用来自子网的文档进行响应,其中包括加密哈希等信息,然后使用容器数据的编辑副本进行响应,以验证你有座位 5A 是真的正确。和以前一样,需要这个小委托,它将蓝色签名连接到 Kim 拥有的橙色密钥。
有了所有这些东西,就可以从用户获得的响应到用户已经拥有的密钥来追踪信任链,用户可以从容器和左下角检查该响应是否包含在文档的编辑副本中。它可以重新计算该文档的根哈希或加密哈希,并检查它是否包含在来自子网的编辑文档中。它可以检查该文档上的蓝色签名,并确保这确实是由授权中该子网的相应密钥签名的,即右下角从根上网络的编辑文档。
该文件是使用橙色密钥签署的,用户可以根据用户拥有的公钥检查签名,这就是系统如何设法验证所传递的响应,甚至从可能是恶意的单个节点计算它,将它们传递给用户,并让用户仍然验证响应是否正确,仅使用一条信息:用户需要事先拥有的信任根。
让我们更仔细地看看这些具有这一有趣特性的文档,即信息可以被编辑并且签名仍然可以被验证。这里使用的机制是一种著名的加密工具,称为 Merkle(默克尔)树,为避免混淆,请注意本博文中的“可编辑”一词是一个类比,它不是指技术意义上的可编辑签名。
这些实际上只是 Merkle 数据结构,这并不是什么新鲜事,我想稍微解释一下这些 Merkle 树是如何工作的,以及如何在这里使用它们。我最初使用的类比是一个文档,但另一个类比在这里更有效:硬盘驱动器的文件系统,其中有文件夹和名称列表。
在这些文件夹中,有更多的文件夹,以及包含数据的文件。这是我在谈论这些树时想要牢记的概念模型,它们现在是树形的,子网维护其中一棵树,这就是为什么它被称为状态树,具有各种信息,它具有对用户请求的响应。
它也有认证查询调用时查看的认证数据,然后是一堆其他信息,例如当前时间和保持整个互联网计算机运行所必需的大量内部数据,例如跨网消息传递。
所以这棵树现在是树形的,然后在这张图片中,我把它画下来,因为在计算机科学中,树由于某种奇怪的原因向下生长,树中的数据有这些角落的东西,这就是容器存储的认证数据所在的位置,在左上角,有创建这棵树的时间。现在我们要定义一个加密哈希,它唯一地标识整个树,包括所有数据、节点上的所有表及其形状。
作为朝着这个方向迈出的一个小技术步骤,这棵树需要成为一个二叉树。如您所见,这棵树分叉了多次,如果这棵树实际上是二叉树,那就更好了。因此,每个节点都有一个或两个子节点,您可以轻松地采用分支并将其替换为多个二元分支,现在这些是幻灯片上的小菱形以获得该属性。
接下来需要做的事情是定义如何计算该树的加密哈希,这可以通过自下而上的方法来完成。对于有数据的叶子,可以直接使用数据的哈希,比如 SHA-256,然后是这些标签 —— 例如,左下角的认证数据,在那里,该子树的哈希是标签的哈希和下面所有内容的哈希。
对于二进制节点,做了类似的事情,这些二元节点之一的哈希是左右子树的哈希。这样就定义了一个规则,从下到上定义了一个哈希,根有一个哈希,这样,一个很小的 32 字节数字现在足以识别整棵树。
如果该哈希已签名,则该签名可用于验证树中的所有内容。接下来,需要进行编辑。假设你想暴露给 Kim,容器 abcde-fgh 的认证数据恰好是 CAFFEE,也许我们还想表明当前时间是我们拥有它们的时间。
然后可以删除与这两条信息无关的所有子树,但是,被删除的子树的哈希值(由上面的小橙色注释表示)也必须记住,为什么必须这样做?好吧,如果您现在将修剪后的树提供给用户,包括修剪某些地方的所有哈希,则用户可以像以前一样从下到上重新计算各个节点的所有哈希,因此也用于根节点。
然后可以获取该根哈希的蓝色签名,即根节点的哈希,并且用户可以验证签名。这就是用户可以验证签名并签署整棵树的方式,即使用户没有整棵树,所以这就是在子网中使用的内容,可以在其中编辑信息,但它在更多地方使用 —— 例如,根子网也使用状态树,但它具有有关哪些子网的附加信息存在以及公钥是什么。
然后在预订代理的示例中,查询调用得到认证,容器本身可能具有这些 Merkle 结构之一,以包含有关预订的座位的所有信息,然后可以插入到一个小的哈希后面系统作为认证数据。
这是 Merkle 树如何工作的总结,它是一个非常通用的工具,在互联网计算机中使用它可以得到很好的效果。
最后,我想向您介绍作为容器开发人员或应用程序开发人员在互联网计算机上必须采取的步骤,如果您想从经过认证的数据中获益来保护您的查询调用,这需要容器的一些帮助,并且您必须进行一些开发。
首先,您必须考虑需要保护的查询类型,并考虑哪种 Merkleized 数据结构适合它。我已经向您展示了树,并且树对于键值查找或类似于文件系统的所有内容都非常有用。如果你有更复杂的东西,需要聚合或选择一些数据,也许你需要一些不同的东西,这实际上取决于您的应用程序。
在一个简单的情况下,像这样的树会起作用。然后,您必须将该树作为正常容器状态的一部分进行维护。因此,这是在互联网计算机上运行的程序的主内存中运行的,每当有更新调用更改需要在那里反映的状态时,您必须更新该 Merkle 结构。您必须重新计算标识该结构中所有数据的根哈希,并将其作为容器的认证数据写入系统。
在处理查询方法时,您必须计算对查询方法的响应,然后您必须查看您的容器内 Merkle 结构并编辑您不需要的所有内容。基本上,您计算有时称为“见证”的东西,它证明您实际上正在提供的响应包含在您之前存储的哈希中。
您还必须要求系统向您提供该证书,即编辑过的文档,它表明您之前在上次更新调用期间编写的哈希值正是您所期望的。然后您将响应、容器内 Merkle 结构的编辑部分发送给用户,并证明根哈希是数据结构的根哈希。
关于客户端代码,您应该为您的服务编写一个专用客户端,因为您必须执行一些特定于服务器的事情。首先,您检查系统证书是否正确,否则恶意节点可能会给您一个旧的结果,并让您相信一周前可能是真的,但现在已不再是真的。
然后,您必须检查这确实是您正在与之通信的容器的数据。否则,攻击者可能会在该子网上安装另一个具有类似状态的容器,例如一个虚假的预订机构,它拥有您拥有座位 7D 而不是座位 5A 的信息,并基于此构建响应。
因此,您必须检查这是否是您实际与之交谈的容器。然后,您可以查看从容器收到的经过编辑的 Merkle 树中特定于应用程序的数据结构。您重新计算其根哈希值,并将其与系统数据中的哈希值进行比较。
最后,您必须检查特定于应用程序的数据中的内容是否与您的查询参数和响应相匹配,两者都很重要。您必须记住查询的参数以及响应。当您作为客户端代码的一部分完成所有这些检查后,您就可以信任响应,即使它来自可能不可信的节点。
对于更新调用,如前所述,您不必担心任何这些。因此,如果这太复杂,或者您的查询可能不符合 Merkle 结构支持的格式,您仍然可以选择使用查询调用并降低安全保证,或者您可以使用更新代码并实时性能略有下降。
我们正在研究使查询调用更安全的方法,以便即使在您作为开发人员不做任何特殊事情的情况下,您也可以获得更多保证和一些针对恶意节点的保护。目前正在开发中,敬请期待更多信息。
在此之前,如果您想了解更多有关我所介绍的技术细节的信息,您可以在互联网计算机接口规范的“系统状态树”、“认证”和“认证数据”部分下找到它们:
https://smartcontracts.org/docs/interface-spec/index.html
如果您有任何其他问题,我们很乐意在开发者论坛上为您提供帮助,以支持您在互联网计算机上构建创新应用程序。
开始在 smartcontracts.org 上构建并加入我们的开发者社区 forum.dfinity.org。
作者:Joachim Breitner
翻译:Catherine
- 往 期 推 荐 -
下一代 AMM 成功完成由 Polychain Capital 的 Beacon Fund 领投的一轮融资
MetaSports Basketball 将 NFT 收藏变成了可玩的角色
长按关注 DFINITY 微信公众号
随时答疑解惑
*添加小助手微信 comiocn 进交流社群