美文网首页
IOTA 基石 - Transaction pow

IOTA 基石 - Transaction pow

作者: 萝卜头4lbt | 来源:发表于2019-06-18 14:09 被阅读0次

    一、概要

        proof of work(pow) 作为区块链技术中最早的共识算法,并应用与比特币、以太坊等区块链项目之中;但pow最早是用于系统抵挡拒绝服务攻击和网络爬虫,后来也被广泛使用于反垃圾邮件,其设计理念是,一个正常用户写一封邮件是需要一定的时间,而发送垃圾邮件者是无法接受这个等待的时间,如果pow系统能够使垃圾邮件发送者需要更多的时间来发送邮件,就可以增大他们的攻击成本,从而起到抵挡攻击的作用。同样,在iota 中,pow 并不是用于共识之中,而是作为交易流程中重要的一环,防止垃圾交易,以及防止女巫攻击,因此,我们来深入IOTA 中pow 实现。

    二、详细介绍&解析

        随着比特币成功后,pow为人们熟知,基于HASH的pow算法常被人误解为是pow的代名词,然而,hash只是pow采用一种算法而已,你可以使用大部分需要迭代运算的算法(如卷积求导、大质数分解这些复杂的运算)实现POW,其实稍微调整或修改一下pow算法,就有可能诞生一种山寨币,然后大肆宣传欺骗小白,了解原理后就知道这并没有什么卵用。

        一方面,由于区块链体系中,包括iota,都是使用hash作为pow 的实现算法,因此,本文还是使用hash 作为pow的其原理分析。另一方面,正如上述所说,不同系统使用的实现算法不一,如果没有具体公布其设计思路,很难深入其hash 算法的实现思路,因此,本文在分析IOTA的pow算法时,不会从数学角度以及核心的hash 实现来深入分析,只从工程学的使用即设计来分析。

    2.1pow 原理

        正如上述介绍,工作量证明最常采用的方式是hash散列函数,因此,我们来详细介绍基于hash 函数的工作量证明是如何工作的。这里,我们作出以下设定:

    • 这里使用的hash 函数为SHA256;
    • 需要被证明的内容用String content表示;
    • 工作量难度系数为16。

    基于上述假定,我们通过以下代码来看看什么是工作量证明:

    public int searchCounter(String content) {
         int counter = 1;
         while(true) {
                byte[] hashReuslt = SHA256(content + counter)
                
                // 转成16进制字符串
                String hexHash = toHex(hashReuslt);
    
                //难度系数为16,即hexHash 前4个数字为0;
                if (prefix.equals("0000")) {
                  // find the counter,完成工作量证明
                  break;
                }
                counter++;
         }
         return counter;
    }
    

        我们来解析上述代码。首先,在代码中,定义一个初始值为1计数器counter,代表运算次数,然后在一个while 循环内,通过hash函数SHA256 对 需要证明的内容 以及 计数器的拼接值,求其hash值,而hash 函数SHA256 返回的是长度为256二进制数值,即长度为 256/8 = 32的byte数组,这里用hashReuslt表示。然而,为例方便展示,在通过hashReuslt转成16进制字符串,我们知道,一个16进制数值需要用4个二进制位表示,因此,如果 对应难度系数为16意味着前16个二进制位都为0,那么,使用16进制来表示的话,只需前4个 hexHash的字符都为0即可,来判断是否结束运算,如果符合条件,则结束运算;否则,继续counter 自增一,重复上述运算,直到符合条件为止。
        当运算方,完成工作量运算后,即找到这样一个符合条件的counter,运算方会使用String workContent = content + counter的方式发给验证方,而验证方只要通过以下简单验证:

    public boolean verify(workContent) {
        byte[] hashReuslt = SHA256(content + counter)
        // 转成16进制字符串
        String hexHash = toHex(hashReuslt);
        if (hexHash.startsWith("0000")) {
            return true;
        }
        return false;
    }
    

    据上述实现,只需一次SHA256 运算,即可快速判断运算方所证明的内容是否经工作量证明。

        假设我们要处理数据 Hello World(即String content = "Helle World"),那么,至少需要107105 次运算,即counter = 107105,才可以使SHA256("Helle World107105") 的结果 ,其前16个二进制数字为0,这里为了方便运算,使用16进制表示其结果:

    0000BFE6AF4232F78B0C8EBA37A6BA6C17B9B8671473B0B82305880BE077EDD9

        最后,我们在来定性分析其工作量,由于散列函数SHA256是基本均匀分布的,因此,对于我们生成的每一个内容hash摘要来说,对应的的哈希值在每一位上出现0和1的概率应该是相同的,假设,我们设定难度值为16,那么,从概率分布来说,我们平均只需2^16次运算,即可得到答案。在假设每次SHA256运算所需时间位 t 秒,那么,平均来说,一次工作量证明所需时间位t * 2^16
        基于hash 函数的特性,用户几乎无法从h(content)反推回content,因此借由该特征,让用户进行大量的穷举运算,就可以达成工作量证明。


    2.2IOTA pow 使用

        在IOTA中,其pow用于防止垃圾交易以及女巫攻击的,具体的使用方式与【2.1pow 原理】所讲解的基于hash实现的pow基本一致,并且也是通过n个连续零值来调节难度,只不过,iota中所使用的hash函数使其自创的hash 函数CURL,这个已在《IOTA 基石 - Sponge 算法详解》详细分析。

        iota将相关的pow所依赖的算法都封装在PearlDiver类,并通过以下

    public synchronized boolean search(final byte[] transactionTrits, final int minWeightMagnitude,
                                           int numberOfThreads)
    

    来执行pow运算。这里我门先来详细解析以下入参:

    • transactionTrits,该pow 算法是专门针对长度位8019 byte字节数组的transaction序列化 实体,当然,这里的每一个byte 都是平衡三进制数值,并且,transactionTrits字节数组中,最后243 位是用来存储nonce,该nonce值用于难度结果校验的,并在方法search(...)内部赋值。
    • minWeightMagnitude,该参数为难度系数值,用于控制pow运算量;
    • numberOfThreads,并发线程数,用于并发查找nonce得的线程数量。

    因此,运算方只要通过以下调用,则可以完成pow 运算(难度系数为9,线程数为1):

    pearlDiver.search(myTrits, 9, 1);
    

    而校验方,则通过:

    
    boolean verify(byte[] myTrits, int minWeightMagnitude) {
    
        byte[] hashTrits = new byte[243];
    
        Sponge curl = new Curl(SpongeFactory.Mode.CURLP81);
    
        //hash的入参为经pow运算的myTrits,with nonce
        curl.absorb(myTrits, 0, myTrits.length);
        //将myTrits 的hash结果写至hashTrits
        curl.squeeze(hashTrits, 0, Curl.HASH_LENGTH);
    
        // 求hashTrits  末尾处开始,连续为0的 个数
        int zeros = 0;
        while (index-- > 0 && hashTrits[index] == 0) {
          zeros++;
        }
        return zeros >= minWeightMagnitude
    }
    

    上述校验与【2.1pow 原理】所讲解的校验流程基本一致,只不过iota 所校验的连续0个数放在了尾部 。

    到这里,IOTA pow 使用分析完毕


    2.3IOTA IOTA pow 分析

         这里,我们在来简单分析一下pearlDiver.search(...)的实现:

    
        public synchronized boolean search(final byte[] transactionTrits, final int minWeightMagnitude,
                                           int numberOfThreads) {
            //检验入参transactionTrits length of with nonce  = 8019
            //  0 <=minWeightMagnitude< 243 (length(nonce) = 243)
            validateParameters(transactionTrits, minWeightMagnitude);
            synchronized (syncObj) {
                state = State.RUNNING;
            }
            
            // 729 = 3 *  243
            final long[] midStateLow = new long[729];
            final long[] midStateHigh = new long[729];
            // 通过transactionTrits 初始化状态midStateLow 以及 midStateHigh 
            initializeMidCurlStates(transactionTrits, midStateLow, midStateHigh);
      
            if (numberOfThreads <= 0) {
                // 获取cpu核数
                int available = Runtime.getRuntime().availableProcessors();
                numberOfThreads = Math.max(1, Math.floorDiv(available * 8, 10));
            }
            List<Thread> workers = new ArrayList<>(numberOfThreads);
            while (numberOfThreads-- > 0) {
                long[] midStateCopyLow = midStateLow.clone();
                long[] midStateCopyHigh = midStateHigh.clone();
                // 并行找nonce
                Runnable runnable = getRunnable(numberOfThreads, transactionTrits, minWeightMagnitude, midStateCopyLow, midStateCopyHigh);
                Thread worker = new Thread(runnable);
                workers.add(worker);
                worker.setName(this + ":worker-" + numberOfThreads);
                worker.setDaemon(true);
                worker.start(); // 启动任务
            }
            for (Thread worker : workers) {
                try {
                    worker.join(); // 等待所有的线程返回
                } catch (InterruptedException e) {
                    synchronized (syncObj) {
                        state = State.CANCELLED;
                    }
                }
            }
            return state == State.COMPLETED;
        }
    
    

    上述代码段为执行pow 准备代码段,包括像hash 转换所需要依赖的 高低位状态midStateCopyLowmidStateCopyHigh初始化(这里的初始化是iota定制的,感兴趣的同学可以自行阅读),然后通过多并行的方式取查找符合条件的nonce,我们接着深入具体的任务查找任务构造 getRunnable(...):

       private Runnable getRunnable(final int threadIndex, final byte[] transactionTrits, final int minWeightMagnitude,
                                    final long[] midStateCopyLow, final long[] midStateCopyHigh) {
           return () -> {
               for (int i = 0; i < threadIndex; i++) {
                   increment(midStateCopyLow, midStateCopyHigh, 162 + 243 / 9,
                       162 + (243 / 9) * 2);
               }
    
               final long[] stateLow = new long[243 * 3];
               final long[] stateHigh = new long[243 * 3];
    
               final long[] scratchpadLow = new long[243 * 3];
               final long[] scratchpadHigh = new long[243 * 3];
    
               final int maskStartIndex = 243 - minWeightMagnitude;
               long mask = 0;
    
               // do pow, 查找符合条件的nonce
               while (state == State.RUNNING && mask == 0) {
    
                   increment(midStateCopyLow, midStateCopyHigh, 162 + (CURL_HASH_LENGTH / 9) * 2,
                       CURL_HASH_LENGTH);
                   //将midStateCopyLow 拷贝至stateLow
                   // midStateCopyHigh 拷贝至stateHigh
                   copy(midStateCopyLow, midStateCopyHigh, stateLow, stateHigh);
                   //转换
                   transform(stateLow, stateHigh, scratchpadLow, scratchpadHigh); 
    
                   mask = HIGH_BITS;
                   for (int i = maskStartIndex; i < CURL_HASH_LENGTH && mask != 0; i++) {
                       mask &= ~(stateLow[i] ^ stateHigh[i]);
                   }
               }
               // 当state != State.RUNNING  时,说明别的线程已经找到符合条件的nonce
               // 当mask!=0时,说明当前线程找到符合条件的nonce
     
               if (mask != 0) {
                   synchronized (syncObj) { // 加锁,确保只有一个线程可以填写nonce
                       if (state == State.RUNNING) {
                           // 设置当前状态为完成
                           state = State.COMPLETED;
                           long outMask = 1;
                           while ((outMask & mask) == 0) {
                               outMask <<= 1;
                           }
                           // 填充nonce值
                           for (int i = 0; i < CURL_HASH_LENGTH; i++) {
                               transactionTrits[TRANSACTION_LENGTH - CURL_HASH_LENGTH + i] =
                                   (midStateCopyLow[i] & outMask) == 0 ? 1
                                       : (midStateCopyHigh[i] & outMask) == 0 ? (byte) -1 : (byte) 0;
                           }
                       }
                   }
               }
           };
       }
    
       // hash 转换
       private static void transform(final long[] stateLow, final long[] stateHigh,
                                     final long[] scratchpadLow, final long[] scratchpadHigh) {
    
           for (int round = 0; round < Curl.NUMBER_OF_ROUNDSP81; round++) {
               copy(stateLow, stateHigh, scratchpadLow, scratchpadHigh);
    
               int scratchpadIndex = 0;
               for (int stateIndex = 0; stateIndex < CURL_STATE_LENGTH; stateIndex++) {
                   final long alpha = scratchpadLow[scratchpadIndex];
                   final long beta = scratchpadHigh[scratchpadIndex];
                   if (scratchpadIndex < 365) {
                       scratchpadIndex += 364;
                   } else {
                       scratchpadIndex += -365;
                   }
                   final long gamma = scratchpadHigh[scratchpadIndex];
                   final long delta = (alpha | (~gamma)) & (scratchpadLow[scratchpadIndex] ^ beta);
    
                   stateLow[stateIndex] = ~delta;
                   stateHigh[stateIndex] = (alpha ^ gamma) | delta;
               }
           }
       }
    

    上述代码段的核心是对midStateCopyLow 以及midStateCopyHigh 进行(代换-置换网络)的算法进行hash转换,直到找到符合条件的nonce 值为止,最后在通过midStateCopyLow 以及midStateCopyHigh对transactionTrits 的末尾243位进行填充。对于详细的转换方式,这里不再深入。

        到这里,transaction pow 分析完毕。

    相关文章

      网友评论

          本文标题:IOTA 基石 - Transaction pow

          本文链接:https://www.haomeiwen.com/subject/dvqufctx.html