如何在 IPFS 中重新创建多重哈希的哈希摘要
How recreate a hash digest of a multihash in IPFS
假设我像这样向 IPFS 添加数据:
$ echo Hello World | ipfs add
这会给我 QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
- 一个 CID,它是 Base58 编码的 Multihash。
将其转换为 Base16,告诉我 IPFS 添加的哈希摘要是 SHA2-256 哈希:
12 - 20 - 74410577111096cd817a3faed78630f2245636beded412d3b212a2e09ba593ca
<hash-type> - <hash-length> - <hash-digest>
我知道 IPFS 不只是对数据进行哈希处理,而是先将其序列化为 Unixfs protobuf,然后将其放入 dag 中。
我想揭开如何到达 74410577111096cd817a3faed78630f2245636beded412d3b212a2e09ba593ca
的神秘面纱,但我不太确定如何获得已创建的 dag,该 dag 包含带有数据的 Unixfs protobuf。
例如,我可以将序列化的原始数据写入磁盘并使用 protobuf 解码器对其进行检查:
$ ipfs block get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u > /tmp/block.raw
$ protoc --decode_raw < /tmp/block.raw
这将以可读格式给我序列化数据:
1 {
1: 2
2: "Hello World\n"
3: 12
}
然而,通过 SHA-256 的管道仍然给我一个不同的哈希值,这是有道理的,因为 IPFS 将 protobuf 放在一个 dag 中并对其进行多重哈希处理。
$ protoc --decode_raw < /tmp/block.raw | shasum -a 256
所以我决定弄清楚如何获得那个 dag 节点,自己对其进行哈希处理以获得我正在寻找的哈希值。
我希望使用 ipfs dag get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
会给我一个可以解码的多重哈希,但结果是 returns 一些我不知道如何检查的其他数据哈希:
$ ipfs dag get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
$ {"data":"CAISDEhlbGxvIFdvcmxkChgM","links":[]}
关于如何从此处解码 data
的任何想法?
更新
data
是原始数据的 Base64 表示:https://github.com/ipfs/go-ipfs/issues/4115
我不确定那个编码是什么,但你可以在 js-ipfs 中像这样解组 dag 数据字段:
const IPFS = require('ipfs')
const Unixfs = require('ipfs-unixfs')
const ipfs = new IPFS
ipfs.dag.get('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u', (err, d) => {
console.log(Unixfs.unmarshal(d.value.data).data.toString()))
// prints Hello World
})
您要查找的散列是 ipfs block get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
输出的散列。 IPFS 对编码值进行哈希处理。
代替运行宁:
protoc --decode_raw < /tmp/block.raw | shasum -a 256
就运行:
shasum -a 256 < /tmp/block.raw
but it turns out it returns some other data hash that I don't know how to inspect
那是因为我们目前在 protobuf 内部使用 protobuf。外部 protobuf 的结构为 {Data: DATA, Links: [{Name: ..., Size: ..., Hash: ...}]}
.
在:
1 {
1: 2
2: "Hello World\n"
3: 12
}
1 { ... }
部分是外层protobuf的Data字段。但是,protoc --decode_raw *recursively* decodes this object so it decodes the
Data` 字段为:
- 字段 1(数据类型):2(文件)
- 字段 2(数据):"Hello World\n"
- 字段 3(文件大小):12(字节)
对于上下文,相关的 protobuf 定义是:
外部:
// An IPFS MerkleDAG Link
message PBLink {
// multihash of the target object
optional bytes Hash = 1;
// utf string name. should be unique per object
optional string Name = 2;
// cumulative size of target object
optional uint64 Tsize = 3;
}
// An IPFS MerkleDAG Node
message PBNode {
// refs to other objects
repeated PBLink Links = 2;
// opaque user data
optional bytes Data = 1;
}
内部:
message Data {
enum DataType {
Raw = 0;
Directory = 1;
File = 2;
Metadata = 3;
Symlink = 4;
HAMTShard = 5;
}
required DataType Type = 1;
optional bytes Data = 2;
optional uint64 filesize = 3;
repeated uint64 blocksizes = 4;
optional uint64 hashType = 5;
optional uint64 fanout = 6;
}
message Metadata {
optional string MimeType = 1;
}
根据 Steven 的回答,使用 protobuf 是可行的。这是我的方法的完整代码。
ipfs.proto
syntax = "proto3";
message PBNode {
bytes Data = 1;
}
message PBLink {
bytes Hash = 1;
string Name = 2;
uint64 Tsize = 3;
}
message Data {
enum DataType {
Raw = 0;
Directory = 1;
File = 2;
Metadata = 3;
Symlink = 4;
HAMTShard = 5;
}
DataType Type = 1;
bytes Data = 2;
}
cid.js
const mh = require('multihashes');
const axios = require('axios');
const crypto = require('crypto');
const protobuf = require("protobufjs");
const IPFS = protobuf.loadSync('./ipfs.proto').lookupType('PBNode');
class CID {
/**
* convert IPFS multihash to sha2-256 hash string
* @param {string} multihash
* @param {boolean} prefix
* @returns {string} sha2-256 hash string starting with 0x
*/
static toHash(multihash, prefix = false) {
return prefix ? '0x' : ''
+ mh.decode(mh.fromB58String(multihash)).digest.toString('hex')
}
/**
* convert sha2-256 hash string to IPFS multihash
* @param {string} str
* @returns {string} IPFS multihash starting with Qm
*/
static fromHash(str) {
str = str.startsWith('0x') ? str.slice(2) : str;
return mh.toB58String(mh.encode(Buffer.from(str, 'hex'), 'sha2-256'))
}
/**
* hash the buffer and get the SHA256 result compatible with IPFS multihash
* @param {Buffer} buf
* @returns {string}
*/
static hash(buf) {
const r = IPFS.encode({
Data: {
Type: 2,
Data: buf,
filesize: buf.length
}
}).finish();
return crypto.createHash('sha256').update(r).digest('hex');
}
}
async function ipfsGet(cid) {
const x = await axios.get(`http://your.address.xxx/ipfs/${cid}`, {
responseType: 'arraybuffer'
});
return Buffer.from(x.data);
}
const r = "QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN";
const hashFromCID = CID.toHash(r);
console.log(hashFromCID);
ipfsGet(r).then(buf => {
const hashCalculated = CID.hash(buf);
console.log(hashCalculated);
console.log(hashCalculated === hashFromCID);
console.log(CID.fromHash(hashCalculated) === r)
});
module.exports = CID;
假设我像这样向 IPFS 添加数据:
$ echo Hello World | ipfs add
这会给我 QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
- 一个 CID,它是 Base58 编码的 Multihash。
将其转换为 Base16,告诉我 IPFS 添加的哈希摘要是 SHA2-256 哈希:
12 - 20 - 74410577111096cd817a3faed78630f2245636beded412d3b212a2e09ba593ca
<hash-type> - <hash-length> - <hash-digest>
我知道 IPFS 不只是对数据进行哈希处理,而是先将其序列化为 Unixfs protobuf,然后将其放入 dag 中。
我想揭开如何到达 74410577111096cd817a3faed78630f2245636beded412d3b212a2e09ba593ca
的神秘面纱,但我不太确定如何获得已创建的 dag,该 dag 包含带有数据的 Unixfs protobuf。
例如,我可以将序列化的原始数据写入磁盘并使用 protobuf 解码器对其进行检查:
$ ipfs block get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u > /tmp/block.raw
$ protoc --decode_raw < /tmp/block.raw
这将以可读格式给我序列化数据:
1 {
1: 2
2: "Hello World\n"
3: 12
}
然而,通过 SHA-256 的管道仍然给我一个不同的哈希值,这是有道理的,因为 IPFS 将 protobuf 放在一个 dag 中并对其进行多重哈希处理。
$ protoc --decode_raw < /tmp/block.raw | shasum -a 256
所以我决定弄清楚如何获得那个 dag 节点,自己对其进行哈希处理以获得我正在寻找的哈希值。
我希望使用 ipfs dag get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
会给我一个可以解码的多重哈希,但结果是 returns 一些我不知道如何检查的其他数据哈希:
$ ipfs dag get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
$ {"data":"CAISDEhlbGxvIFdvcmxkChgM","links":[]}
关于如何从此处解码 data
的任何想法?
更新
data
是原始数据的 Base64 表示:https://github.com/ipfs/go-ipfs/issues/4115
我不确定那个编码是什么,但你可以在 js-ipfs 中像这样解组 dag 数据字段:
const IPFS = require('ipfs')
const Unixfs = require('ipfs-unixfs')
const ipfs = new IPFS
ipfs.dag.get('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u', (err, d) => {
console.log(Unixfs.unmarshal(d.value.data).data.toString()))
// prints Hello World
})
您要查找的散列是 ipfs block get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
输出的散列。 IPFS 对编码值进行哈希处理。
代替运行宁:
protoc --decode_raw < /tmp/block.raw | shasum -a 256
就运行:
shasum -a 256 < /tmp/block.raw
but it turns out it returns some other data hash that I don't know how to inspect
那是因为我们目前在 protobuf 内部使用 protobuf。外部 protobuf 的结构为 {Data: DATA, Links: [{Name: ..., Size: ..., Hash: ...}]}
.
在:
1 {
1: 2
2: "Hello World\n"
3: 12
}
1 { ... }
部分是外层protobuf的Data字段。但是,protoc --decode_raw *recursively* decodes this object so it decodes the
Data` 字段为:
- 字段 1(数据类型):2(文件)
- 字段 2(数据):"Hello World\n"
- 字段 3(文件大小):12(字节)
对于上下文,相关的 protobuf 定义是:
外部:
// An IPFS MerkleDAG Link
message PBLink {
// multihash of the target object
optional bytes Hash = 1;
// utf string name. should be unique per object
optional string Name = 2;
// cumulative size of target object
optional uint64 Tsize = 3;
}
// An IPFS MerkleDAG Node
message PBNode {
// refs to other objects
repeated PBLink Links = 2;
// opaque user data
optional bytes Data = 1;
}
内部:
message Data {
enum DataType {
Raw = 0;
Directory = 1;
File = 2;
Metadata = 3;
Symlink = 4;
HAMTShard = 5;
}
required DataType Type = 1;
optional bytes Data = 2;
optional uint64 filesize = 3;
repeated uint64 blocksizes = 4;
optional uint64 hashType = 5;
optional uint64 fanout = 6;
}
message Metadata {
optional string MimeType = 1;
}
根据 Steven 的回答,使用 protobuf 是可行的。这是我的方法的完整代码。
ipfs.proto
syntax = "proto3";
message PBNode {
bytes Data = 1;
}
message PBLink {
bytes Hash = 1;
string Name = 2;
uint64 Tsize = 3;
}
message Data {
enum DataType {
Raw = 0;
Directory = 1;
File = 2;
Metadata = 3;
Symlink = 4;
HAMTShard = 5;
}
DataType Type = 1;
bytes Data = 2;
}
cid.js
const mh = require('multihashes');
const axios = require('axios');
const crypto = require('crypto');
const protobuf = require("protobufjs");
const IPFS = protobuf.loadSync('./ipfs.proto').lookupType('PBNode');
class CID {
/**
* convert IPFS multihash to sha2-256 hash string
* @param {string} multihash
* @param {boolean} prefix
* @returns {string} sha2-256 hash string starting with 0x
*/
static toHash(multihash, prefix = false) {
return prefix ? '0x' : ''
+ mh.decode(mh.fromB58String(multihash)).digest.toString('hex')
}
/**
* convert sha2-256 hash string to IPFS multihash
* @param {string} str
* @returns {string} IPFS multihash starting with Qm
*/
static fromHash(str) {
str = str.startsWith('0x') ? str.slice(2) : str;
return mh.toB58String(mh.encode(Buffer.from(str, 'hex'), 'sha2-256'))
}
/**
* hash the buffer and get the SHA256 result compatible with IPFS multihash
* @param {Buffer} buf
* @returns {string}
*/
static hash(buf) {
const r = IPFS.encode({
Data: {
Type: 2,
Data: buf,
filesize: buf.length
}
}).finish();
return crypto.createHash('sha256').update(r).digest('hex');
}
}
async function ipfsGet(cid) {
const x = await axios.get(`http://your.address.xxx/ipfs/${cid}`, {
responseType: 'arraybuffer'
});
return Buffer.from(x.data);
}
const r = "QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN";
const hashFromCID = CID.toHash(r);
console.log(hashFromCID);
ipfsGet(r).then(buf => {
const hashCalculated = CID.hash(buf);
console.log(hashCalculated);
console.log(hashCalculated === hashFromCID);
console.log(CID.fromHash(hashCalculated) === r)
});
module.exports = CID;