使用ipfsapi与IPFS交互的全面指南
在分布式存储和内容寻址的领域中,IPFS(InterPlanetary File System)是一个强大的工具。本文将深入探讨如何使用ipfsapi与IPFS进行交互,包括数据序列化、哈希处理、编码转换以及文件和目录的添加等操作。
1. 数据序列化与反序列化
原始数据
b'I am a good unicorn.\n'
首先会被
unixfs
模块的
Data
包装,然后再被
merkledag
模块的
PBNode
包装。在脚本中,我们需要先使用
PBNode
对序列化的数据进行反序列化,然后再使用
Data
对结果进行反序列化。
2. 多哈希(Multihash)
IPFS 使用多哈希(Multihash)对数据进行哈希处理。多哈希不仅输出哈希结果,还会输出所使用的哈希函数、该哈希函数的输出长度以及哈希输出。
2.1 多哈希示例
假设我们要哈希的数据是
b'i love you'
,选择
sha256
作为哈希函数:
from hashlib import sha256
print(sha256(b'i love you').hexdigest())
输出结果为:
'1c5863cd55b5a4413fd59f054af57ba3c75c0698b3851d70f99b8de2d5c7338f'
哈希输出的长度为 64 个字符,由于十六进制格式的数字每个占两个字符,所以实际长度为 32(64 / 2)。我们需要的十六进制版本的 32 是
0x20
或 20。
在多哈希支持的哈希函数表中,
sha256
对应的编号是 12。我们将它们组合起来:
12 + 20 + 1c5863cd55b5a4413fd59f054af57ba3c75c0698b3851d70f99b8de2d5c7338f
也可以写成:
12201c5863cd55b5a4413fd59f054af57ba3c75c0698b3851d70f99b8de2d5c7338f
如果使用
sha1
哈希函数:
from hashlib import sha1
print(sha1(b'i love you').hexdigest())
输出结果为:
'bb7b1901d99e8b26bb91d2debdb7d7f24b3158cf'
哈希输出长度为 40 个字符,实际长度为 20(40 / 2),十六进制版本为
0x14
或 14。
sha1
在哈希函数表中的编号是
0x11
或 11,组合后的输出为:
11 + 14 + bb7b1901d99e8b26bb91d2debdb7d7f24b3158cf
即:
1114bb7b1901d99e8b26bb91d2debdb7d7f24b3158cf
2.2 为什么使用多哈希
传统的哈希函数(如
sha1
、
sha256
或
keccak256
)可能会被破解,即有人可能在合理的时间内找到两个不同的输入但具有相同的哈希输出。这对于数据完整性检查非常危险。
例如,我们发送一个治疗癌症的秘密文档,为确保其未被篡改,会对文档进行哈希并广播哈希结果。但如果敌人创建了一个不同的文档(如创建病毒的指南),却具有相同的哈希输出,那么接收者可能会误执行该文件。
当哈希函数被破解时,程序员需要升级系统,但通常会遇到困难,因为他们对哈希函数的输出长度有假设。而使用多哈希,升级过程会更简单,因为哈希函数和输出长度都嵌入在多哈希的输出中,我们不再需要对哈希输出长度进行假设。
可以通过以下代码进行实验:
(ipfs-venv) $ pip install pymultihash
(ipfs-venv) $ python
import multihash
the_universal_hash = multihash.digest(b'i love you', 'sha1')
print(the_universal_hash.verify(b'i love you'))
当发现
sha1
哈希函数被破解时,只需将
'sha1'
替换为
'sha2_256'
:
the_universal_hash = multihash.digest(b'i love you', 'sha2_256')
print(the_universal_hash.verify(b'i love you'))
3. Base58 编码
Base58 是 Base64 的修改版本,通常用于将二进制数据编码为 ASCII 字符串。
3.1 Base64 编码示例
import base64
print(base64.b64encode(b'i love you'))
输出结果为:
b'aSBsb3ZlIHlvdQ=='
Base64 编码常用于将二进制数据(如图像文件)编码,以便在不支持二进制数据的环境中传输,如电子邮件。
3.2 Base58 编码示例
Base58 编码去除了打印时容易混淆的字符(如 0、O、I 和 l)以及
+
和
/
字符。它最初由中本聪设计用于编码大整数,例如比特币地址。
安装
base58
库并进行实验:
import base58
print(base58.b58encode(b'i love you'))
输出结果为:
b'6uZUjTpoUEryQ8'
使用 Base58 可以创建一个长十六进制字符串,方便我们用肉眼检查和验证。
4. 结合 Protobuf、多哈希和 Base58
现在我们已经了解了 Protobuf、多哈希和 Base58,可以解释
b'I am a good unicorn.\n'
文件内容如何转换为
'QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR'
。
4.1 数据序列化
b'I am a good unicorn.\n'
数据被包装在 IPFS 节点中,并使用 Protobuf 序列化为
b'\n\x1b\x08\x02\x12\x15I am a good unicorn.\n\x18\x15'
。
创建
serialize_unicorn.py
脚本:
import unixfs_pb2
import merkledag_pb2
precious_data = b'I am a good unicorn.\n'
unicorn = unixfs_pb2.Data()
unicorn.Type = unixfs_pb2.Data.File
unicorn.Data = precious_data
unicorn.filesize = len(precious_data)
serialized_unicorn_node = unicorn.SerializeToString()
outer_node = merkledag_pb2.PBNode()
outer_node.Data = serialized_unicorn_node
print(outer_node.SerializeToString())
运行脚本:
(ipfs-venv) $ python serialize_unicorn.py
输出结果为:
b'\n\x1b\x08\x02\x12\x15I am a good unicorn.\n\x18\x15'
4.2 哈希处理
使用
sha256
对 Protobuf 序列化的数据进行哈希处理:
import hashlib
print(hashlib.sha256(b'\n\x1b\x08\x02\x12\x15I am a good unicorn.\n\x18\x15').hexdigest())
输出结果为:
'912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536'
sha256
在多哈希表中的编号是 12,哈希输出长度为 32(十六进制为
0x20
)。将它们连接起来:
12 + 20 + 912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536
即:
1220912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536
4.3 Base58 编码
由于
b58encode()
方法只接受字节对象,我们需要先将十六进制字符串转换为字节对象:
import codecs
byte_data = codecs.decode('1220912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536', 'hex')
import base58
print(base58.b58encode(byte_data))
输出结果为:
b'QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR'
5. ipfsapi API 操作
我们可以使用 ipfsapi API 与 IPFS 进行交互,包括添加文件、列出块以及重建文件等操作。
5.1 添加大文件
从 Unsplash 下载一个至少 1MB 的图像文件,例如
milada-vigerova-1284157-unsplash.jpg
,并将其放在与 IPFS Python 脚本相同的目录中。
创建
add_image_file.py
脚本:
import ipfsapi
c = ipfsapi.connect()
result = c.add('milada-vigerova-1284157-unsplash.jpg')
print(result)
运行脚本:
(ipfs-venv) $ python add_image_file.py
输出结果为:
{'Name': 'milada-vigerova-1284157-unsplash.jpg', 'Hash': 'QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM', 'Size': '2604826'}
5.2 列出块
创建
list_blocks.py
脚本:
import ipfsapi
import pprint
c = ipfsapi.connect()
blocks = c.ls('QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM')
pp = pprint.PrettyPrinter(indent=2)
pp.pprint(blocks)
运行脚本:
(ipfs-venv) $ python list_blocks.py
输出结果为包含多个块信息的字典。
大文件在 IPFS 中会被分割成多个块,每个块的大小通常为 262,158 字节(最后一个块除外)。IPFS 使用 Merkle 树计算文件内容的根哈希。
5.3 反向工程 IPFS 块
可以使用以下命令获取文件内容的 IPFS 块并保存为二进制文件:
$ ipfs block get QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM > block.raw
然后使用
protoc
编译器解码二进制文件:
$ protoc --decode_raw < block.raw
解码时,如果没有
.proto
文件,需要猜测某些块中数字(如 1、2、3 和 4)的含义。因此,在解码序列化数据之前,最好先获取
.proto
文件。
5.4 从块重建原始文件
创建
construct_image_from_blocks.py
脚本:
import ipfsapi
c = ipfsapi.connect()
images_bytes = []
blocks = c.ls('QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM')
for block in blocks['Objects'][0]['Links']:
bytes = c.cat(block['Hash'])
images_bytes.append(bytes)
images = b''.join(images_bytes)
with open('image_from_blocks.jpg', 'wb') as f:
f.write(images)
运行脚本后,打开
image_from_blocks.jpg
文件,即可查看原始图像。
5.5 添加目录
创建一个名为
mysite
的目录,在其中创建一个
img
目录,并将
cat.jpg
图像文件放入
img
目录。在
img
目录旁边创建一个
index.html
文件和一个
README.md
文件。
index.html
文件内容如下:
<html>
<head>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
</head>
<body>
<img src="img/cat.jpg" class="rounded-circle" />
</body>
</html>
README.md
文件内容如下:
This is Readme file.
创建
add_directory.py
脚本:
import ipfsapi
import pprint
c = ipfsapi.connect()
result = c.add('mysite', True)
pp = pprint.PrettyPrinter(indent=2)
pp.pprint(result)
运行脚本:
(ipfs-venv) $ python add_directory.py
输出结果为包含目录中每个文件和目录的哈希信息的列表。
可以通过以下 URL 在浏览器中打开网站:
http://localhost:8080/ipfs/QmZamPcNnfZjjTkoyrYjYMEA8pp29KmpmkuSvkicSGiZDp/
也可以使用另一个网关访问:
https://ipfs.io/ipfs/QmZamPcNnfZjjTkoyrYjYMEA8pp29KmpmkuSvkicSGiZDp/
6. 星际命名系统(IPNS)
有时候,我们希望使用相同的链接发布动态文件,即哈希链接在不同时间生成不同的内容。这时可以使用星际命名系统(IPNS)。
6.1 创建星座预测文件
创建两个星座预测文件
horoscope1.txt
和
horoscope2.txt
,内容分别为:
You will meet the love of your life today!
You need to be careful when going outside!
创建
add_horoscope_predictions.py
脚本:
import ipfsapi
c = ipfsapi.connect()
result = c.add('horoscope1.txt')
print(result)
result = c.add('horoscope2.txt')
print(result)
运行脚本后,记录输出中的两个哈希值。
6.2 列出密钥
创建
keys_list.py
脚本:
import ipfsapi
c = ipfsapi.connect()
print(c.key_list())
运行脚本:
(ipfs-venv) $ python keys_list.py
输出结果为包含密钥信息的字典。
6.3 发布星座预测
创建
publish_horoscope1.py
脚本:
import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][0]['Id']
c.name_publish('QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR')
result = c.cat('/ipns/' + peer_id)
print(result)
运行脚本可能需要一些时间,因为在 IPNS 中发布文件有点慢。
创建
publish_horoscope2.py
脚本:
import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][0]['Id']
c.name_publish('Qme1FUeEhA1myqQ8C1sCSXo4dDJzZApGD6StE26S72ZqyU')
result = c.cat('/ipns/' + peer_id)
print(result)
运行脚本会得到与之前不同的结果,尽管 IPNS 路径相同。
6.4 生成新的 IPNS 路径
创建
generate_another_key.py
脚本:
import ipfsapi
c = ipfsapi.connect()
print(c.key_list())
c.key_gen('another_key', 'rsa')
print(c.key_list())
运行脚本后,会得到一个新的密钥和对应的 IPNS 路径。
创建
publish_horoscope1_in_another_ipns.py
脚本:
import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][1]['Id']
c.name_publish('QmTG4eE6ruUDhSKxqwofJXXqDFAmNzQiGdo4Z7WvVdLZuS', key='another_key')
result = c.cat('/ipns/' + peer_id)
print(result)
通过以上步骤,我们可以全面了解如何使用 ipfsapi 与 IPFS 进行交互,包括数据处理、文件和目录的添加以及动态内容的发布。这些操作可以帮助我们更好地利用 IPFS 的分布式存储和内容寻址特性。
使用ipfsapi与IPFS交互的全面指南
7. 操作流程总结
为了更清晰地展示使用 ipfsapi 与 IPFS 进行交互的过程,我们将前面的操作步骤总结如下:
7.1 数据处理流程
| 步骤 | 操作 | 代码示例 |
|---|---|---|
| 1 | 数据序列化 |
创建
serialize_unicorn.py
脚本并运行
|
import unixfs_pb2
import merkledag_pb2
precious_data = b'I am a good unicorn.\n'
unicorn = unixfs_pb2.Data()
unicorn.Type = unixfs_pb2.Data.File
unicorn.Data = precious_data
unicorn.filesize = len(precious_data)
serialized_unicorn_node = unicorn.SerializeToString()
outer_node = merkledag_pb2.PBNode()
outer_node.Data = serialized_unicorn_node
print(outer_node.SerializeToString())
| 2 | 哈希处理 | 使用
sha256
对序列化数据进行哈希 |
import hashlib
print(hashlib.sha256(b'\n\x1b\x08\x02\x12\x15I am a good unicorn.\n\x18\x15').hexdigest())
| 3 | Base58 编码 | 将哈希结果转换为 Base58 编码 |
import codecs
byte_data = codecs.decode('1220912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536', 'hex')
import base58
print(base58.b58encode(byte_data))
7.2 文件和目录操作流程
| 步骤 | 操作 | 代码示例 |
|---|---|---|
| 1 | 添加大文件 |
创建
add_image_file.py
脚本并运行
|
import ipfsapi
c = ipfsapi.connect()
result = c.add('milada-vigerova-1284157-unsplash.jpg')
print(result)
| 2 | 列出块 | 创建
list_blocks.py
脚本并运行 |
import ipfsapi
import pprint
c = ipfsapi.connect()
blocks = c.ls('QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM')
pp = pprint.PrettyPrinter(indent=2)
pp.pprint(blocks)
| 3 | 反向工程 IPFS 块 | 使用命令获取并解码块 |
$ ipfs block get QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM > block.raw
$ protoc --decode_raw < block.raw
| 4 | 从块重建原始文件 | 创建
construct_image_from_blocks.py
脚本并运行 |
import ipfsapi
c = ipfsapi.connect()
images_bytes = []
blocks = c.ls('QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM')
for block in blocks['Objects'][0]['Links']:
bytes = c.cat(block['Hash'])
images_bytes.append(bytes)
images = b''.join(images_bytes)
with open('image_from_blocks.jpg', 'wb') as f:
f.write(images)
| 5 | 添加目录 | 创建
add_directory.py
脚本并运行 |
import ipfsapi
import pprint
c = ipfsapi.connect()
result = c.add('mysite', True)
pp = pprint.PrettyPrinter(indent=2)
pp.pprint(result)
7.3 IPNS 操作流程
| 步骤 | 操作 | 代码示例 |
|---|---|---|
| 1 | 创建星座预测文件 |
创建
horoscope1.txt
和
horoscope2.txt
文件
|
| 2 | 添加文件 |
创建
add_horoscope_predictions.py
脚本并运行
|
import ipfsapi
c = ipfsapi.connect()
result = c.add('horoscope1.txt')
print(result)
result = c.add('horoscope2.txt')
print(result)
| 3 | 列出密钥 | 创建
keys_list.py
脚本并运行 |
import ipfsapi
c = ipfsapi.connect()
print(c.key_list())
| 4 | 发布星座预测 | 创建
publish_horoscope1.py
和
publish_horoscope2.py
脚本并运行 |
# publish_horoscope1.py
import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][0]['Id']
c.name_publish('QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR')
result = c.cat('/ipns/' + peer_id)
print(result)
# publish_horoscope2.py
import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][0]['Id']
c.name_publish('Qme1FUeEhA1myqQ8C1sCSXo4dDJzZApGD6StE26S72ZqyU')
result = c.cat('/ipns/' + peer_id)
print(result)
| 5 | 生成新的 IPNS 路径 | 创建
generate_another_key.py
脚本并运行 |
import ipfsapi
c = ipfsapi.connect()
print(c.key_list())
c.key_gen('another_key', 'rsa')
print(c.key_list())
| 6 | 在新 IPNS 路径发布内容 | 创建
publish_horoscope1_in_another_ipns.py
脚本并运行 |
import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][1]['Id']
c.name_publish('QmTG4eE6ruUDhSKxqwofJXXqDFAmNzQiGdo4Z7WvVdLZuS', key='another_key')
result = c.cat('/ipns/' + peer_id)
print(result)
8. 操作流程图
下面是使用 mermaid 绘制的操作流程图,展示了使用 ipfsapi 与 IPFS 交互的主要流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(数据处理):::process
B --> B1(数据序列化):::process
B --> B2(哈希处理):::process
B --> B3(Base58 编码):::process
B1 --> B2
B2 --> B3
A --> C(文件和目录操作):::process
C --> C1(添加大文件):::process
C --> C2(列出块):::process
C --> C3(反向工程 IPFS 块):::process
C --> C4(从块重建原始文件):::process
C --> C5(添加目录):::process
C1 --> C2
C2 --> C3
C3 --> C4
C5 --> C4
A --> D(IPNS 操作):::process
D --> D1(创建星座预测文件):::process
D --> D2(添加文件):::process
D --> D3(列出密钥):::process
D --> D4(发布星座预测):::process
D --> D5(生成新的 IPNS 路径):::process
D --> D6(在新 IPNS 路径发布内容):::process
D1 --> D2
D2 --> D3
D3 --> D4
D4 --> D5
D5 --> D6
B3 --> C1
C4 --> D1
D6 --> E([结束]):::startend
9. 注意事项和常见问题
在使用 ipfsapi 与 IPFS 进行交互时,有一些注意事项和常见问题需要我们关注:
9.1 哈希函数的选择
虽然多哈希可以简化哈希函数的升级过程,但在选择哈希函数时,仍然需要考虑其安全性和性能。例如,
sha1
已经被证明是不安全的,建议使用更安全的哈希函数,如
sha256
。
9.2 大文件处理
大文件在 IPFS 中会被分割成多个块,每个块的大小可以配置。在处理大文件时,需要确保有足够的存储空间和网络带宽。
9.3 解码序列化数据
在使用
protoc
编译器解码序列化数据时,如果没有
.proto
文件,会增加解码的难度。因此,建议在解码之前先获取
.proto
文件。
9.4 IPNS 发布速度
在 IPNS 中发布文件可能会比较慢,需要耐心等待。同时,要注意 IPNS 路径的管理,避免混淆。
10. 总结
通过本文的介绍,我们详细了解了如何使用 ipfsapi 与 IPFS 进行交互。从数据的序列化、哈希处理和 Base58 编码,到文件和目录的添加、块的管理以及动态内容的发布,每个环节都有其重要性和操作要点。
使用 IPFS 的分布式存储和内容寻址特性,可以帮助我们更好地管理和共享数据。同时,IPNS 为我们提供了发布动态内容的能力,使得我们可以使用相同的链接在不同时间发布不同的内容。
在实际应用中,我们可以根据具体需求选择合适的操作流程和方法,同时注意避免常见问题,以充分发挥 IPFS 的优势。希望本文能够帮助你更好地掌握使用 ipfsapi 与 IPFS 进行交互的技巧。
超级会员免费看
1114

被折叠的 条评论
为什么被折叠?



