草庐IT

Merkle树的生成和SPV验证信息的策略

white and white 2023-07-20 原文

最近项目需要用到区块链来对某些信息进行快速校验,且由于每天的信息量都是很大的,因此直接使用区块链来保存信息是不可行的,故使用Merkle树的叶子节点先对传入的信息进行一次hash加密,紧接着利用结点两两hash构造Merkle树得到Merkle Root 最后与前区块hash,时间戳等信息一起再hash加密得到当前区块信息。

当数据被攻击篡改:

Merkle树构造算法:
设置Merkle树的基本架构,包括左右孩子,数据域,是否叶子几点和对应的信息主键

public class MerKleTreeNode {
    private MerKleTreeNode lChild;
    private MerKleTreeNode rChild;
    private String data;
    private Integer isLeaf;
    private Integer infoId;
}

先对叶子节点进行构造hash

for(Map<String,String> map : list){
            MerKleTreeNode node = new MerKleTreeNode(null,null,BlockHashAlgoUtils.encodeDataBySHA_256(map),1,-1);
            res.add(node);
}

由于Merkle二叉树的话可以利用他两两hash的缘故,构造一颗满二叉树,再利用满二叉树的特性(序号编排特点,树高公式,每层节点数公式)等等一切与满二叉树的特点,可以达到快速的存取MerkleTreeNode。既然要构造满二叉树,我们可以先利用叶子节点,通过求出一个数,设该数为 XX必须大于叶子节点数,且是2的幂次数(1,2,4,8…),且X不能太大,不然造成空间浪费,必须越接近叶子节点总数越好。

        if((size & (size -1))!=0){
            int n = size;
            n |= n >>> 1;
            n |= n >>> 2;
            n |= n >>> 4;
            n |= n >>> 8;
            n |= n >>> 16;
            n = n >= MAXIMUM ? MAXIMUM : n + 1;
            //计算差距
            int distance = n - size;
            for(int i =size-distance;i < size; i++){
                //复制对称数据,凑齐merkle树所有节点
                data.add(data.get(i));
            }
            size = n;
        }

上图例子如下:
init: 0001101011011001
>>>1: 0001101010000100 | 0000110101000010 = 000111111000110
````````(无符号右移类推)
====>>> 0001111111111111
res = 0001111111111111 + 1 = 0010000000000000 = 2^14

利用高位右移的特性,其实就是把传入进来的数通过无符号右移把他所有的位都变为1,最后在通过一次+1 ,既可以在原数的下一个最高位上得到一个1,且其余的位置因为2进制的缘故变为了0,这下就可以得到一个准确地 X

接着就是计算一些高度啊,序号,以及每层拥有的节点树
获取size的二进制形式,它的长度就是merkle树的高度,也就是该二进制的长度

  int height = Integer.toBinaryString(size).length();
	int idxStart = (total +1) >> 1;
	int total = (int)(Math.pow(2,height)-1);

最后构造Merkle树
idxStart作为计算每一层节点(从左数起)第一个叶子节点的序号(根节点从1开始编排)

while(height-1>0){
                /**
                 * 总节点数 2^n-1
                 * 每一层的最左节点序号
                 */
                total = (int)(Math.pow(2,height-1)-1);
                idxStart = (total +1) >> 1;
                /**
                 * 逐层构建,new temp(list) 作为新一层的数据赋值到data(list)上,循环
                 */
                ArrayList<MerKleTreeNode> temp = new ArrayList<>();
                for(int j = 0; j < size ; j+=2){
                    MerKleTreeNode lChild = data.get(j);
                    MerKleTreeNode rChild = data.get(j+1);
                    String hash = BlockHashAlgoUtils.encodeDataBySHA_256(lChild.getData() + rChild.getData());
                    MerKleTreeNode node = new MerKleTreeNode(lChild, rChild, hash,0,-1);
                    temp.add(node);
                    //输出非叶节点的序号
                    System.out.println(idxStart++);
                }
            //temp 下个循环进行新建回收,赋值data
            data = temp;
            height--;
            //size长度每次减半
            size = size >> 1;
        }

接下来是SPV验证,由于数组存储不方便展示,顾我把数据先行存放在数据库中,利用index字段存储数组的下标。
进行SPV查询,最重要的就是需要先获取SPV验证路径(即需要对两两hash的另一半作为固定的因素,如下图黑圈所示),还是根据满二叉树的序号特点,通过计算下标
如图所示,很明显,左节点都是偶数,右节点是奇数,根结点是1,可以利用这个特性,根据传入消息所在的index快速得到所有的spv验证路径节点的index

 while(idx>1){
                /**
                 * if 是偶数左节点,取他的右兄弟节点
                 * else 取左节点
                 * 完成后向父节点移动
                 */
                if((idx&1) == 0){
                    list.add(idx+1);
                }else{
                    list.add(idx-1);
                }
                idx = idx >> 1;
            }
            /**
             * 1也要加入进去
             */
            list.add(idx);
            //根据集合在list集合中的数据去数据库中查询所有的节点
         		........(查库操作)
         }

接着就是再一次构造hash分支重新得到MerkleRoot了,与已经上传到区块链上的merkleROOT进行比对,既可以判断是否又被修改信息。

if(CollectionUtils.isEmpty(checkProofs)){
            return res;
        }
        /**
         * 从集合中取出一个,任意一个所属的区块和链都应相同
         */
        Integer blockIndex = checkProofs.get(0).getBlockIndex();
        MerkleNode merkleRoot = getMerkleRoot(blockIndex);
        if(Objects.isNull(merkleRoot)){
            return res;
        }
        /**
         * 与区块链上的block比对Merkle Root
         */
        Blockchain blockchain = blockchainMapper.selectByPrimaryKey(blockIndex);
        if(!blockchain.getBlockMerkle().equals(merkleRoot.getHash())){
            return res;
        }
        /**
         *  与merkle树上的验证路径比对
         *  mapper降序处理
         *  通过奇偶判断左右孩子
         *  跑到最后一层即可,不用继续计算
         */
        Info info = infoMapper.selectByPrimaryKey(infoId);
        String hash = BlockHashAlgoUtils.encodeDataBySHA_256(info.toString());
        for(int i = checkProofs.size()-1;i>0;i--){
            MerkleNode node2 = checkProofs.get(i);
            if((node2.getMerkleNodeIndex()&1)==0){
                hash = BlockHashAlgoUtils.encodeDataBySHA_256(node2.getHash()+hash);
            }else{
                hash = BlockHashAlgoUtils.encodeDataBySHA_256(hash + node2.getHash());
            }
        }
        /**
         * 新生成的与merkle root再次比对
         */
        if(!blockchain.getBlockMerkle().equals(hash)){
            return res;
        }

总的来说,采用Merkle + SPV的好处就是当需要验证海量信息中某个信息是否遭到修改,可以无需得知所有节点的信息,只需要验证某个小分支的节点即可,所需要的节点只需要log(n+1)个,当数量级很大的时候,节省的空间是很可观的,用户也无需承担那么大的代价,他们只需要保留区块链的头部数据即可。
余下就是区块链的特点了,由于MerkleRoot保存在区块链中,所以对应的信息可基本可以保障他的可靠性(仍有可能发生hash碰撞的因素,看利用的hash算法)

有关Merkle树的生成和SPV验证信息的策略的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  3. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  4. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  5. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  6. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  7. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  8. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  9. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  10. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

随机推荐