如何在IPFS 上存储签名和加密的数据
在IPFS上存储经过身份验证和加密的数据是许多Web3应用程序的核心构建块,但是迄今为止,还没有标准化的方式来编码这种类型的数据。
没有标准,许多开发人员被迫为其签名和加密的数据创建自定义格式。通过将数据存储到IPFS的特定实现中,这已经阻碍了IPFS中存储的信息的开放性和互操作性。验证数据的另一种方法是将数据放入IPFS,并将数据的CID放入区块链(如以太坊)的智能合约中。从本质上讲,这是在数据之上添加签名并将签名记录持久保存在区块链上的昂贵方法。
随着EIP-2844的引入,该标准允许钱包支持一些新的方法来基于DID和dag-joseIPLD编解码器对数据进行签名和解密,我们现在可以简单地将经过身份验证和加密的数据直接放入IPFS。
什么是DID和JOSE?
DID是用于分散标识符的W3C标准。
具体内容可以参考我们上一篇文章:Astral 构建新世界。本文仅在这里简单介绍,DID指定了从字符串标识符(例如did:3:bafy...)到包含用于签名验证和密钥交换的公共密钥的DID文档的一般方法。在大多数DID方法中,出于安全原因旋转键时可以更新文档。
JOSE是IETF(Internet Engineering Task Force)国际互联网工程任务组的标准,代表JSON对象签名和加密,几乎可以解释其含义。该标准有两个主要原语:JWS(JSON Web签名)和JWE(JSON Web加密)。这两种格式都允许多个参与者:在JWS中,有效负载上可以有一个或多个签名,而在JWE中,加密明文可以有一个或多个接收者。
使用dag-jose和EIP2844进行构建
当我们使用dag-jose和EIP-2844作为基本构建模块来构建Ceramic时,我们创建了一些底层工具,这些工具使我们可以更轻松地使用这些技术。
js-3id-did-provider是使用3ID作为DID方法的EIP-2844的实现。它可以单独用作DID提供程序,也可以在3ID Connect库中更方便地使用。3ID Connect允许用户使用其以太坊钱包(即将支持更多区块链)来访问DID Provider。
key-did-provider-ed25519是使用Key DID方法的EIP-2844的实现。它是同时支持签名和加密的最简单的DID提供程序。
js-did是一个库,允许开发人员以DID的形式表示用户。这是我们在本教程中将要看到的主要界面。它使我们能够与当前经过身份验证的用户签署数据,将数据加密给任何用户(DID),以及与当前经过身份验证的用户解密数据。
IPFS中的签名数据
通过使用dag-jose IPLD编解码器,我们可以创建链接和签名的数据结构。这是通过创建包含指向其他数据链接的JSON Web签名(JWS)来完成的。dag-jose编解码器解决的主要问题之一是,传统上将JWS的有效负载编码为:base64url这意味着,如果其中包含IPLD链接,则您将无法遍历这些链接。
相反,我们对DagJWS所做的是将有效负载强制为CID的字节。然后,编解码器将有效负载转换为CID实例,并将其设置link为DagJWS的属性。这使我们可以轻松地遍历生成的DAG。
设置具有dag-jose支持的IPFS
由于dag-jose是新的IPLD编解码器,因此默认情况下它尚未包含在js-ipfs中。它还实现了新的IPLD编解码器API,js-ipfs尚不支持该API。因此,在创建IPFS实例时,需要执行以下操作:
import IPFS from 'ipfs'import dagJose from 'dag-jose'import multiformats from 'multiformats/basics'import legacy from 'multiformats/legacy'multiformats.multicodec.add(dagJose)const dagJoseFormat = legacy(multiformats, dagJose.name)const ipfs = await Ipfs.create({ ipld: { formats: [dagJoseFormat] } })
确保安装正确的多格式版本:
$ npm i multiformats@3.0.3
设置一个DID实例
在下面的示例设置中,我们使用key-did-provider-ed25519。如果您选择从上方使用网络,则将在后台使用3ID Connect和js-3id-did-provider。
import { DID } from 'dids'import { Ed25519Provider } from 'key-did-provider-ed25519'import KeyResolver from '@ceramicnetwork/key-did-resolver'import { randomBytes } from '@stablelib/random'// generate a seed, used as a secret for the DIDconst seed = randomBytes(32)// create did instanceconst provider = new Ed25519Provider(seed)const did = new DID({ provider, resolver: KeyResolver.getResolver() })await did.authenticate()window.did = didconsole.log('Connected with DID:', did.id)
创建一个签名的数据结构
现在我们可以开始签名并将数据添加到IPFS!首先,让我们创建一个简单的函数,该函数接受有效负载,使用did.createDagJWS方法对其进行签名,然后将结果数据添加到IPFS。
正如我们在下面的代码中看到的那样,我们从该方法中获得了两个对象:jwsDagJWS本身和linkedBlock编码有效载荷的原始字节。后台发生的事情是使用dag-cbor对有效负载进行编码,此后,将已编码有效负载的CID用作created的有效负载jws。我们可以通过访问DagJWS实例上的有效负载CID jws.link。
async function addSignedObject(payload) { // sign the payload as dag-jose const { jws, linkedBlock } = await did.createDagJWS(payload) // put the JWS into the ipfs dag const jwsCid = await ipfs.dag.put(jws, { format: 'dag-jose', hashAlg: 'sha2-256' }) // put the payload into the ipfs dag await ipfs.block.put(linkedBlock, { cid: jws.link }) return jwsCid}
使用此功能,让我们创建第一个签名的数据对象:
// Create our first signed objectconst cid1 = await addSignedObject({ hello: 'world' })// Log the DagJWS:console.log((await ipfs.dag.get(cid1)).value)// > {// > payload: "AXESIHhRlyKdyLsRUpRdpY4jSPfiee7e0GzCynNtDoeYWLUB",// > signatures: [{// > signature: "h7bHmTaBGza_QlFRI9LBfgB3Nw0m7hLzwMm4nLvcR3n9sHKRoCrY0soWnDbmuG7jfVgx4rYkjJohDuMNgbTpEQ",// > protected: "eyJraWQiOiJkaWQ6MzpiYWdjcWNlcmFza3hxeng0N2l2b2tqcW9md295dXliMjN0aWFlcGRyYXpxNXJsem4yaHg3a215YWN6d29hP3ZlcnNpb24taWQ9MCNrV01YTU1xazVXc290UW0iLCJhbGciOiJFUzI1NksifQ"// > }],// > link: CID(bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae)// > }// Log the payload:ipfs.dag.get(cid1, { path: '/link' }).then(b => console.log(b.value))// > { hello: 'world' }// Create another signed object that links to the previous oneconst cid2 = addSignedObject({ hello: 'getting the hang of this', prev: cid1 })// Log the new payload:ipfs.dag.get(cid2, { path: '/link' }).then(b => console.log(b.value))// > {// > hello: 'getting the hang of this'// > prev: CID(bagcqcerappi42sb4uyrjkhhakqvkiaibkl4pfnwpyt53xkmsbkns4y33ljzq)// > }// Log the old payload:ipfs.dag.get(cid2, { path: '/link/prev/link' }).then(b => console.log(b.value))// > { hello: 'world' }
请注意,由于有效负载将由您的DID签名,因此CID和JWS的值对您而言将有所不同。
验证签名的数据结构
验证JWS非常简单。只需检索JWS对象并将其传递给verifyJWS方法。如果签名无效,则此函数将引发错误。如果签名有效,它将返回用于签名JWS的DID(带有密钥片段)。
const jws1 = await ipfs.dag.get(cid1)const jws2 = await ipfs.dag.get(cid2)const signingDID1 = await did.verifyJWS(jws1)await did.verifyJWS(jws2)
IPFS中的加密数据
IPFS中的签名数据是一个难题,但也许更有趣的是加密数据。通过使用dag-jose和EIP-2844,我们可以将数据加密为一个或多个DID,并将其直接存储在IPFS中。下面,我们演示如何使用js-did库提供的便捷工具来执行此操作。
加密IPLD数据
有一种简单的方法可以创建DagJWE对象,该对象被加密为一个或多个DID createDagJWE。此方法接受IPLD对象(可能还包括CID链接的JSON对象)和DID数组。
它将解析DID以检索在其DID文档中找到的公共加密密钥,并创建一个已加密到这些密钥的JWE。首先,让我们创建一个辅助函数,该函数创建一个JWE并将其放入IPFS。
async function addEncryptedObject(cleartext, dids) { const jwe = await did.createDagJWE(cleartext, dids) return ipfs.dag.put(jwe, { format: 'dag-jose', hashAlg: 'sha2-256' })}
一旦有了此功能,我们就可以创建一些加密的对象。在下面的示例中,我们首先创建一个简单的加密对象,然后创建一个链接到前一个对象的附加加密对象。
const cid3 = await addEncryptedObject({ hello: 'secret' }, [did.id])const cid4 = await addEncryptedObject({ hello: 'cool!', prev: cid3 }, [did.id])
请注意,在上面的示例中,我们用于[did.id]()将数据加密为当前已验证的DID。我们当然也可以将数据加密为未经本地身份验证的用户(例如另一个用户)的DID!
解密IPLD数据
从IPFS检索数据后,我们将只获得加密的JWE。这意味着我们需要在获取数据后解密数据。由于我们已经创建了相互链接的对象,因此让我们创建一个函数来检索这些对象并递归解密它们。
async function followSecretPath(cid) { const jwe = (await ipfs.dag.get(cid)).value const cleartext = await did.decryptDagJWE(jwe) console.log(cleartext) if (cleartext.prev) { followSecretPath(cleartext.prev) }}
上面的函数是一个示例,仅记录解密的对象。我们可以使用它来查看这些对象的内容。
// Retrieve a single objectfollowSecretPath(cid3)// > { hello: 'secret' }// Retrive multiple linked objectsfollowSecretPath(cid4)// > { hello: 'cool!', path: CID(bagcqceraqittnizulygv6qldqgezp3siy2o5vpg66n7wms3vhffvyc7pu7ba) }// > { hello: 'secret' }
Scan QR code with WeChat