高效、廉价地存储IPFS哈希

2019-07-30 16:07:38

最近在一个新项目中,我遇到了在以太坊区块链上存储IPFS哈希(尤其是CIDv1类型)的需求,这种存储方式既方便用户,又高效。

  Alexandre Trottier RTrades:最近在一个新项目中,我遇到了在以太坊区块链上存储IPFS哈希(尤其是CIDv1类型)的需求,这种存储方式既方便用户,又高效。

  为什么选择CIDV1?cidv0使用sha2–256,其输出大小不适合单个Bytes32存储槽。正常情况下,每当人们需要在智能合约中存储IPFS哈希值时,它几乎总是存储在bytes32存储变量中的IPFS CID的散列,或者IPFS哈希本身存储在字符串存储变量中。与CIDv1合作不会受此影响。

  虽然这两种方法都没有什么问题,但它们都不是最优的。仅仅为了适应一个存储槽而哈希会大大增加使用的难度,只有在出于安全原因需要这样做时才是值得的。将IPFS哈希存储在字符串存储变量中通常非常昂贵,而且也不是最优的。


  这里的技巧是能够适应尽可能少,但完全占用的存储槽(高效),同时易于使用(可用)和使用最少gas(廉价)。这在理论上听起来很简单,因为你只需要找到一个哈希函数,它占用一个字节32存储变量,或者两个字节32存储变量,但是由于IPFS使用multiformats(或者更恰当地说,multihash),这并不像听起来那么简单。为了解决这个问题,我们需要讨论一下multihash。

  Multihash是multiformat规范的子集,允许我们创建自描述哈希值。

  考虑到上面的内容,即使你能够找到输出为32字节的哈希函数,multihash也会在其上再添加4个字节,总共36个字节。这意味着我们需要将它存储在一个字节存储变量中,该变量有一个单独的word overhead。


  解决方案

  这就引出了一个问题,如果我们能找到一个哈希函数,当它以多哈希格式占用一个单字节32存储槽时,我们就可以使用它了!不完全是,唯一做到这一点的multihash是blake2b-136,由于安全风险,go-ipfs节点在默认情况下不接受…

  经过几个小时的不同实验(摸索地尝试multihashes),我终于找到了一个哈希函数,在multihash中输出为64字节时,这意味着我们可以将其存储在恰好两个bytes32存储槽中,完全填充两个槽而不浪费任何空间。 multihash是blake2b-328。所以要使用它,我们需要做的就是获取64字节的输出,将其分为两部分,这样我们就可以很好地进行下去了!


  举例

  简而言之,我将演示在尝试几种不同的组合和存储两个字节32存储变量的方法之后找到的最理想的解决方案。

  第一步是哈希的两个部分分别定义:hashpart1和hashpart2。为了在这里存储IPFS哈希值,我们需要取blake2b-328 multihash的64字节输出,将其分成两半,将每一半存储在bytes32类型的2个元素数组中,并将其传递给函数updatehash。

  现在,无论何时我们想要使用这些数据,我们所要做的就是调用getHash,它将以字节类型返回完整的哈希。如果你在移动电话的dapp中使用它,那么你需要做的就是将它转换成字符串,在golang中,字符串是string(returnedbytes)并且你的ipfs哈希是纯文本的!

  这真的节省gas吗,还是我浪费了你的时间?


  空间消耗

  为了测试空间消耗,我编写了下面这个测试合约来测试空间消耗。为了获得空间成本,我会查看交易收据上累计空间使用量的字段。测试是在我的笔记本电脑上的一个本地私人POA链上进行的,时间为1秒,使用geth 1.8.27-stable。

  函数updateLink、updateLinkHash和updateLinkParts用于测试不同方式在两个字节32存储槽中存储数据的空间成本。函数setcid用于测试存储IPFS哈希的空间成本。函数setcidstring用于测试存储ipfs哈希的纯文本(aka string)版本所产生的空间成本。

  起初,你可能会考虑到setCID的空间成本,然后开始认为我只是在浪费你宝贵的时间。然而,我们需要考虑的事实是,实际上不仅仅是IPFS哈希,它是IPFS哈希的哈希。因此,虽然这可能是高效的空间,但在智能合约之外消费并不容易,而在其他智能合约内消费则最糟糕,因为:

  我们需要将哈希的纯文本副本存储在智能合约(存储)可访问的某个位置。

  我们需要从存储中读取纯文本数据,哈希它,然后比较两个哈希值。


最新推荐