IPFS文件地址链上存储

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

IPFS 是什么

IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。
内容可寻址:通过文件内容生成唯一哈希值来标识文件,而不是通过文件保存位置来标识。相同内容的文件在系统中只会存在一份,节约存储空间
版本化:可追溯文件修改历史
点对点超媒体:P2P 保存各种各样类型的数据
可以把 IPFS 想象成所有文件数据是在同一个 BitTorrent 群并且通过同一个 Git 仓库存取。
总之,它集一些成功系统(分布式哈希表、BitTorrent、Git、自认证文件系统)的优势于一身,是一套很厉害的文件存取系统。

区块链和IPFS的结合

大家都知道区块链的数据是可以永久保存的,但是如果在区块链上存储大量的数据是非常昂贵的。如果结合IPFS,我们可以在IPFS以文件的形式存储数据并把文件的地址保存在区块链上,这样就可以做到大量数据的永久存储并可以有效追踪。当然再结合区块链上的智能合约就可以发挥更大的想象力了。

IPFS地址分析

为了在链上存储IPFS的文件地址,我们对IPFS的文件地址做一下简单的分析。由于IPFS的数据块大小为256字节,因此如果文件大小超过此大小会被拆分为多个数据块。多个数据块要涉及到Merkle DAG(Directed Acyclic Graph) 默克有向无环图,相对复杂一点,后续的文章再做分析。这里我们简单分析一下单数据区块的情况。
IPFS的源码分离出部分代码,可以生成文件的IPFS地址

 

function ipfsHash(filePath) {
    var buffer = fs.readFileSync(filePath);
    const unixFs = new Unixfs('file', buffer);
    DAGNode.create(unixFs.marshal(), (err, dagNode) => {
        let json = dagNode.toJSON();
        console.log("File:0x" + buffer.toString('hex'));
        console.log("UnixFs:0x" + unixFs.marshal().toString('hex'));
        console.log("Header+UnixFS:0x" + dagNode.serialized.toString('hex'));
        console.log("Multihash:0x" + dagNode.multihash.toString('hex'));
        console.log("Address:" + json.multihash);
        console.log("---------------------------------------------------------------------");
    });
}
ipfsHash('/Users/Kirn/Documents/Workspace/Dawn/ethereum/assets/test.txt');

File:0x310a320a330a340a350a360a370a0a
UnixFs:0x0802120f310a320a330a340a350a360a370a0a180f
Header+UnixFS:0x0a150802120f310a320a330a340a350a360a370a0a180f
Multihash:0x1220a1001394f749d9a0c5f27761b2f08e9432ce215dad6f01dbe26e468857169cbb
Address:QmZB8R7T5xvKJDUJ6pXtUym6frQx1r6bQPcwquR1rtGHL6

为了能更清楚的了解IPFS的地址构成我们把整个构造过程拆解一下

 

function customHash(filePath) {
    // 读取文件Buffer
    var buffer = fs.readFileSync(filePath);

    // 转为Unix File System
    const unixFs = new Unixfs('file', buffer).marshal();

    // 添加tag
    let tag = Buffer.from([10])
    
    // 添加File Size
    let size = Buffer.from([unixFs.length]);
    var newBuffer = Buffer.concat([tag, size, unixFs]);

    // sha2-256
    let sha256 = crypto.createHash('sha256').update(newBuffer).digest();

    // multihash
    let multihash = multihashes.encode(sha256, 'sha2-256');

    // base58
    let base58 = bs58.encode(multihash).toString('hex');
    console.log("File:0x" + buffer.toString('hex'));
    console.log("UnixFs:0x" + unixFs.toString('hex'));
    console.log("Header+UnixFS:0x" + newBuffer.toString('hex'));
    console.log("Sha256:0x" + sha256.toString('hex'));
    console.log("Multihash:0x" + multihash.toString('hex'));
    console.log("Address:" + base58);
    console.log("---------------------------------------------------------------------");
}
customHash('/Users/Kirn/Documents/Workspace/Dawn/ethereum/assets/test.txt');

File:0x310a320a330a340a350a360a370a0a
UnixFs:0x0802120f310a320a330a340a350a360a370a0a180f
Header+UnixFS:0x0a150802120f310a320a330a340a350a360a370a0a180f
Sha256:0xa1001394f749d9a0c5f27761b2f08e9432ce215dad6f01dbe26e468857169cbb
Multihash:0x1220a1001394f749d9a0c5f27761b2f08e9432ce215dad6f01dbe26e468857169cbb
Address:QmZB8R7T5xvKJDUJ6pXtUym6frQx1r6bQPcwquR1rtGHL6

大致可以分解为以下步骤

  • 读取文件数据为Buffer
  • 把文件数据转为Unix文件格式
  • 数据流头部增加Metadata数据
    • tag: 0x0a=10(此处也不知为何,后续再做研究)
    • 文件大小
  • sha2-256编码
  • 转为multihash格式,目前IPFS采用的是32位sha2-256编码,因此数据头部需要增加0x1220,0x12代表sha256,0x20=32代表hash位数
  • Base58编码

IPFS链上存储方案

针对于上面对IPFS地址的分析,我们可以在链上采取两种存取方案

  • 存储方案一
    以string的形式直接存储IPFS地址,优点:简单明了,读取和存储都很方便,缺点:占用空间大,gas消耗可能会比较大
  • 存储方案二
    以bytes32的形式只存储IPFS地址的sha256之后的结果,优点:占用空间少,gas消耗较少,缺点:读取和存储相对比较麻烦

写一个简单的合约测试一下

 

pragma solidity ^0.4.21;

contract IPFSAddress {
    mapping(address => bytes32) public bytesIpfs;
    mapping (address=>string) public stringIpfs;
    
    // save as bytes32
    function saveBytes(bytes32 ipfs) public {
        bytesIpfs[msg.sender] = ipfs;
    }
    
    // save as string 
    function saveString(string ipfs) public {
        stringIpfs[msg.sender] = ipfs;
    }
}

string存储交易回执

 

QmZB8R7T5xvKJDUJ6pXtUym6frQx1r6bQPcwquR1rtGHL6
{
  blockHash: "0x1385ab689055d504d98b675da4803708be856368e8dd2799a917b1153d5712e2",
  blockNumber: 185768,
  contractAddress: null,
  cumulativeGasUsed: 85962,
  from: "0x262bab6a90aa1741390c4a3ec58855c81d9728e1",
  gasUsed: 85962,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  root: "0xc37b7485014a3fc7ff943c4f753b77e649526633d490ca815ff2abd385699c88",
  to: "0xb6093ecf6a2ae6b94bb2e45186da0f2bcfa315a5",
  transactionHash: "0xfe6162aceeb211ace9b2c689135778ec28c1c5d778785f20a0a246220b18cfa4",
  transactionIndex: 0
}

bytes32存储交易回执

 

0xa1001394f749d9a0c5f27761b2f08e9432ce215dad6f01dbe26e468857169cbb
{
  blockHash: "0xf7f47ef6d77773b54fe143c933d67c49ee0694851bb7fa7cafb2b594d14c1d0e",
  blockNumber: 185766,
  contractAddress: null,
  cumulativeGasUsed: 43595,
  from: "0x262bab6a90aa1741390c4a3ec58855c81d9728e1",
  gasUsed: 43595,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  root: "0x476961f34dbbfc86c47a2f4935a5840b23fee03578994c56625ca83af75acf7f",
  to: "0xb6093ecf6a2ae6b94bb2e45186da0f2bcfa315a5",
  transactionHash: "0x0a8b740c94deae4df8df76852d03f74461843235e0c05b78f31c8f06ee2f81a3",
  transactionIndex: 0
}

对比两次链上交易的结果:

  • string存储消耗:gasUsed: 85962
  • bytes32存储消耗:gasUsed: 43595

bytes32存储差不多是string存储gas消耗的一半,算是一个较优的存储方案,当然前提是multihash采用的hash算法不变的情况下。因为IPFS的地址采用了multihash,在sha256算法不安全的情况下可以随时更换其他hash算法而不需要更改设计方案。

解析链上IPFS地址

为了方便查询链上的IPFS地址,可以把base58编码的算法在合约里实现一下,这里用library实现。

 

library IPFSLib {
    bytes constant ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

    /**
     * @dev Base58 encoding
     * @param _source Bytes data
     * @return Encoded bytes data
     */
    function base58Address(bytes _source) internal pure returns (bytes) {
        uint8[] memory digits = new uint8[](_source.length * 136/100 + 1);
        digits[0] = 0;
        uint8 digitlength = 1;
        for (uint i = 0; i < _source.length; ++i) {
            uint carry = uint8(_source[i]);
            for (uint j = 0; j<digitlength; ++j) {
                carry += uint(digits[j]) * 256;
                digits[j] = uint8(carry % 58);
                carry = carry / 58;
            }
            
            while (carry > 0) {
                digits[digitlength] = uint8(carry % 58);
                digitlength++;
                carry = carry / 58;
            }
        }
        return toAlphabet(reverse(truncate(digits, digitlength)));
    }

    /**
     * @dev Truncate `_array` by `_length`
     * @param _array The source array
     * @param _length The target length of the `_array`
     * @return The truncated array 
     */
    function truncate(uint8[] _array, uint8 _length) internal pure returns (uint8[]) {
        uint8[] memory output = new uint8[](_length);
        for (uint i = 0; i < _length; i++) {
            output[i] = _array[i];
        }
        return output;
    }
    
    /**
     * @dev Reverse `_input` array 
     * @param _input The source array 
     * @return The reversed array 
     */
    function reverse(uint8[] _input) internal pure returns (uint8[]) {
        uint8[] memory output = new uint8[](_input.length);
        for (uint i = 0; i < _input.length; i++) {
            output[i] = _input[_input.length - 1 - i];
        }
        return output;
    }

    /**
     * @dev Convert the indices to alphabet
     * @param _indices The indices of alphabet
     * @return The alphabets
     */
    function toAlphabet(uint8[] _indices) internal pure returns (bytes) {
        bytes memory output = new bytes(_indices.length);
        for (uint i = 0; i < _indices.length; i++) {
            output[i] = ALPHABET[_indices[i]];
        }
        return output;
    }

    /**
     * @dev Convert bytes32 to bytes
     * @param _input The source bytes32
     * @return The bytes
     */
    function toBytes(bytes32 _input) internal pure returns (bytes) {
        bytes memory output = new bytes(32);
        for (uint8 i = 0; i < 32; i++) {
            output[i] = _input[i];
        }
        return output;
    }

    /**
     * @dev Concat two bytes to one
     * @param _byteArray The first bytes
     * @param _byteArray2 The second bytes
     * @return The concated bytes
     */
    function concat(bytes _byteArray, bytes _byteArray2) internal pure returns (bytes) {
        bytes memory returnArray = new bytes(_byteArray.length + _byteArray2.length);
        for (uint16 i = 0; i < _byteArray.length; i++) {
            returnArray[i] = _byteArray[i];
        }
        for (i; i < (_byteArray.length + _byteArray2.length); i++) {
            returnArray[i] = _byteArray2[i - _byteArray.length];
        }
        return returnArray;
    }
}

 

contract IPFSAddress {
    using IPFSLib for bytes;
    using IPFSLib for bytes32;
    mapping(address => bytes32) public bytesIpfs;
    mapping (address=>string) public stringIpfs;
    
    function saveBytes(bytes32 ipfs) public {
        bytesIpfs[msg.sender] = ipfs;
    }
    
    function saveString(string ipfs) public {
        stringIpfs[msg.sender] = ipfs;
    }
    
    function ipfsAddress() external view returns (string) { 
        bytes memory prefix = new bytes(2);
        prefix[0] = 0x12;
        prefix[1] = 0x20;
        bytes memory value = prefix.concat(bytesIpfs[msg.sender].toBytes());
        bytes memory ipfsBytes = value.base58Address();
        return string(ipfsBytes);
    }
}

合约已部署Ropsten,可作参考

https://ropsten.etherscan.io/address/0x0581d89b0b4edf171a199937b1d16a1033ba7538



作者:Kirn
链接:https://www.jianshu.com/p/d16797e8de59
来源:简书

基于Fabric+IPFS大规模数据上方案 基于Fabric+IPFS大规模数据上方案 第一章 系统综述   加粗样式区块是创造信任的机器,但是数据存储与读取的效率十分低下,两者不可兼得的情况下,一种新的方式,既弥补区块的效率,又能利用其“信任”与“不可篡改”特性。该方案使用区块+分布式存储。   Fabric简介、适用场景:Hyperledger Fabric的出现是对传统区块模型的一种革新,在某种程度上允许创建授权和非授权的区... 阅读详情

相关推荐

IPFS真的优于Http吗?首份IPFS存储性能测评报告出炉

IPFS协议诞生以来,从技术的角度分析,行业内一直认为其存储性能优于传统的HTTP协议,那么事实上是否真的如此呢?理论上的优势在实践上是否也存在呢,星际鑫航技术团队为揭秘IPFS真实的存储性能,进行了深度性能测试,本文为 IPFS 存储性能测试报告的核心内容摘录。 文章导读: 阅读须知,测评如何进行。 本地存储、局域网存储,无网络因素下性能测试 公网环境中,IPFS协议与七牛云性能对比 结论陈述...

XJXH_IPFS的博客 3266

第133篇 IPFS 介绍

本文简单介绍 IPFS

wonderBlock的博客 758

linux 安装ipfs —— IPFS环境配置

ipfs web3 区块

不受天磨非好汉,不遭人妒是庸才——着实着迷゛ 5668

IPFS综述

1. IPFS工作原理 IPFS是一个P2P存储网络。内容可通过位于世界任何地方的对等点访问,这些对等点可能会传递信息、存储信息或两者兼而有之。IPFS知道如何使用它的内容地址(CID)来找到相关的内容,而不是基于它的位置。IPFS有三个基本原则: 1)通过内容寻址(内容标识符)进行唯一标识; 2)通过有向无环图接内容(文件的内容); 3)通过分布式哈希表发现(多地址)内容; 这三个原则相互建立,以实现IPFS生态系统。让我们从内容寻址和内容的唯一标识开始。 1.1内容寻址 IPFS使用内容

zhinen丶的博客 1万+

4.1 IPFS初识

IPFS  简介:IPFS(InterPlanetary File System)是一个点对点的分布式超媒体分发协议 IPFS工作原理  每个文件及其中的所有块都被赋予一个称为加密散列的唯一指纹 IPFS通过网络删除重复的具有相同哈希值的文件,通过计算可以判断哪些文件是冗余的、重复的。并跟踪每个文件的版本历史记录。 每个网络节点只存储他感兴趣的内容,以及一些索引信息,有助于弄清楚...

尹成的技术博客 716

读懂IPFS

文章目录IPFS项目介绍IPFS是什么IPFS的产生背景及应用IPFS和区块有什么区别?IPFS生态IPFS与Filecoin的关系 IPFS项目介绍 IPFS介绍:星际文件系统IPFS(Inter-Planetary File System)是一个面向全球的、点对点的分布式版本文件系统,目标是为了补充(甚至是取代)目前统治互联网的超文本传输协议(HTTP),将所有具有相同文件系统的计算设备连接...

夏洛的克 743

区块 存储可扩展方法

区块网络本质上是一个P2P网络,区块在矿工节点计算出正确的nonce值后,由矿工节点广播至整个区块网络,其余节点收到区块验证通过后将新区块接至自己存储的区块之后。区块系统通过高冗余存储机制保证区块数据安全,现在或未来都将可能遇到存储可扩展性问题。扩容方法下扩容存储,如下图所示,区块体中数据内容从原区块体转移到存储系统,区块体中仅存储指向这些数据的“指针”和其他非数据信息,以解决区块存储可扩展性问题。1基于DHT的存储基于DHT的存储,如下图所示,原始数据经SHA-256计算后

软件工程小施同学 的专栏 3690

IPFS和以太坊存储数据

如何将JSON文件存储IPFS上,并使用Oraclize访问智能合约中的数据呢? 以太坊是一个成熟的区块,使开发人员能够创建智能合约,在区块上执行的程序可以由交易触发。人们经常将区块称为数据库,但使用区块作为数据存储非常昂贵。 以目前的价格(530美元,4gwei)在以太坊上存储250GB将花费你106,000,000美元。一般来说,我们可以忍受高成本因为我们: 不会在以太坊区块上保...

mongo_node的专栏 3389

干货|原来IPFS是这样存储文件的

概述 IPFS - InterPlanetary File System 星际文件系统,是一个点对点的分布式文件存储系统,IPFS的愿景是构建一个全世界的分布式网络,用来替代传统中心化的服务器模式,所有的IPFS节点组成一个分布式网络,每个节点都可以存储文件,用户可以从IPFS构建的网络中以DHT(Distributed Hash Table,分布式哈希表) 的方式获取文件,从而实现了新一代的完全去中心化的网络,旨在取代现有的万维网。IPFS功能很丰富,包括DHT组网,文件存储,Bitswap文件交换等功能

Hyperchain的博客 1958

集成IPFS上签名验证与JWT权限控制

IPFS(InterPlanetary File System)是一种基于区块技术的去中心化文件存储协议,它通过将文件分割成多个小块,并在全球范围内的节点间分发这些块来实现去中心化存储IPFS的优势在于它能降低存储成本,提高访问速度,并且在去中心化的背景下,保障数据的隐私和安全。IPFS的工作原理非常简单。每一个存储IPFS上的文件都会被分割成多个数据块,每个数据块都会被哈希并存储在多个节点上。每个节点都有一份文件的副本,用户通过内容的哈希值来访问文件。

kooly_2010的博客 1807

IPFS系列04-IPFS 存储性能测试

本文是对 IPFS 存储性能测试的一个报告。 Note: 本次测试使用的都是单台服务器,主要测试 IPFS 读写性能,以及哪些因素会影响 IPFS存储效率。 统计方式:每组操作执行5次,然后取平均值 1. 本地测试 考虑到硬件对存储性能的影响,我们这里分别使用了两台不同配置的台式机进行了测试。 配置一 CPU: i5-4160-4Core 主频 3.2G 内存: 8GB 硬盘:普通家用硬盘 1GB 操作系统:Ubuntu 18.04-LTS 测试结果 网络环境 文件大小 写入时间(ms)

yangjian8801的专栏 4243

ipfs数据存储原理

文章目录前言ipfs repo规范介绍ipfs配置初始化ipfs配置文件config和存储相关的配置项存储相关package源码解析DatastoreConfig实现了Batching接口的存储Datastore interfacePinnerCoreAPI方法调用一览repo/blockstore初始化类图调用流程 前言 从规范、配置到源码详细介绍ipfs数据存储ipfs repo规范介绍 repo是IPFS节点的存储仓库,它是IPFS节点实际存储数据的子系统。所有IPFS对象都存储在一个仓库中(类似

KK下山去买菜 4337

50、IPFS:区块存储解决方案(上)

本文探讨了如何利用 IPFS 作为区块存储解决方案,以解决区块在大规模数据存储中的局限性。文章详细介绍了 IPFS 的架构和协议栈,包括其各个子协议的功能,并分析了区块IPFS 融合的优势、应用场景以及潜在应用,例如医疗保健、物联网数据处理和学习管理系统。此外,文章还总结了区块IPFS 融合的特点,如减少区块大小、确保数据不可变性以及提高系统同步效率。最后,提出了未来需要进一步评估该融合方案在实际应用中的有效性。

ios99的博客 148

IPFS搭建

为什么80%的码农都做不了架构师?>>> ...

weixin_33896069的博客 623

以太坊DApp如何用IPFS存储并调用数据

正在构建的一个Dapp。Dapp包括一些用户数据,如电子邮件、姓名和个人图片等。我想将用户数据内容存储IPFS中,通过一个JSON对象,并用IPFS hash处理过。我怎样才能把这个数据和一个特定的用户联系起来呢?也就是说在与DAPP的交互时将用户与IPF中存储的数据关联起来。要使用用户密码和某种类型的密码来完成的吗? 例如,用户A对使用dapp感兴趣,然后提供了他或她的电子邮件、姓名和一些个...

mongo_node的专栏 3290

IPFS存储一致性难题?IPFS-Cluster帮你解决

引 言 星际文件系统(InterPlanetary File System,缩写IPFS)是一个旨在创建持久且分布式存储和共享文件的网络传输协议。它是一种内容可寻址的点对点超媒体分发协议。在IPFS网络中的节点将构成一个分布式文件系统。 在IPFS网络中,文件是拆分后存储在不同节点的,每个节点存储的内容并不相同,当我们使用IPFS私有网络来作为系统的文件系统时就存在存储一致性问题,如单个节点的故障导致存储的文件不可用。 IPFS-Cluster项目很好地解决了私有IPFS网络数据可用性问题,IPFS-Cl

Hyperchain的博客 934

java ipfs文件存储_原来IPFS是这样存储文件的

概述CID在介绍IPFS存储文件的远离之前,先介绍一个重要的标识——CID(Content-ID),CID是IPFS中用来表示内容的标识,可以用来表示一个文件,也可以用来表示一个文件块。如下所示,CID是一个字符串,它主要由Version、Codec和Multihash三部分构成,Version目前分为v0和v1版本,v0版本的CID可以由V0Builder生成,v0版本的CID以Qm字符串开头,...

weixin_29034963的博客 956

如何将NFT上传到IPFS服务器中?如何存储NFT数据?

IPFS是一种点对点分布式文件系统,主要用于无法存储在区块上的数据。与更标准的文件共享协议 HTTP 不同,IPFS 具有特殊的属性,使其成为与区块协同工作的去中心化数据模型的理想选择。主要的特点有:1.分布式 - IPFS 由多个 IPFS 节点组成,因此没有一个实体可以控制网络上的共享内容。2.不变性 - IPFS 上共享的内容不能更改或篡改。3.可寻址性 - IPFS 上共享的内容使用唯一地址来定位网络上的内容。

Apathyb7的博客 1476

Java连接ipfs存储_干货|原来IPFS是这样存储文件的

本文作者:BitXMesh概述IPFS - InterPlanetary File System 星际文件系统,是一个点对点的分布式文件存储系统,IPFS的愿景是构建一个全世界的分布式网络,用来替代传统中心化的服务器模式,所有的IPFS节点组成一个分布式网络,每个节点都可以存储文件,用户可以从IPFS构建的网络中以DHT(Distributed Hash Table,分布式哈希表) 的方式获取文件...

weixin_32631179的博客 774
上一篇: blockchain实践错误及解决方法
下一篇: tensorflow keras重复运行结果不一致问题
for_ge
博客等级 码龄9年 10粉丝 9原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值