区块链技术基础知识–SHA256

简介

SHA256是安全散列算法SHA(Secure Hash Algorithm)系列算法之一,其摘要长度为256bits,即32个字节,故称SHA256。 SHA256是SHA-2下细分出的一种算法。SHA-2的名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,由美国国家安全局 (NSA) 设计,美国国家标准与技术研究院(NIST) 发布的一系列密码散列函数,属于SHA算法之一,是SHA-1的后继者。SHA-2下又可再分为六个不同的算法标准。包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。这些变体除了生成摘要的长度 、循环运行的次数等一些微小差异外,算法的基本结构是一致的。

回到SHA256上,说白了,它就是一个哈希函数。哈希函数,又称散列算法,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(或哈希值)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。

对于任意长度(按bit计算)的消息,SHA256都会产生一个32个字节长度数据,称作消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据是否发生改变,即验证其完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。

SHA算法有如下特性:

  1. 不可以从消息摘要中复原信息;

  2. 两个不同的消息不会产生同样的消息摘要。

专业术语

首先我们来了解几个专业术语

位(Bit),字节(Byte)和字(Word)

SHA始终把消息当成一个位(bit)字符串来处理。本文中,一个“字”(Word)是32位,而一个“字节”(Byte)是8位。比如,字符串“abc”可以被转换成一个位字符串:01100001 01100010 01100011。它也可以被表示成16进制字符串:0x616263.

补位

SHA算法中信息必须进行补位,以使其长度在对512取模以后的余数是448。也就是说,(补位后的消息长度)Q2 = 448。即使长度已经满足对512取模后余数是448,补位也必须要进行。

补位是这样进行的:先补一个1,然后再补0,直到长度满足对512取模后余数是448。总而言之,补位是至少补一位,最多补511位。以信息“abc”为例显示补位的过程。   

原始信息:01100001 01100010 01100011

补位第一步:0110000101100010 01100011 1

首先补一个“1”

补位第二步:0110000101100010 01100011 10…..0

然后补423个“0”

我们可以把最后补位完成后的数据用16进制写成下面的样子

61626380 0000000000000000 00000000

00000000 0000000000000000 00000000

00000000 0000000000000000 00000000

00000000 00000000

现在,补位完成后的数据的长度余数就是448了,我们可以进行下一步操作。

补长度

所谓的补长度是将原始数据的长度补到已经进行了补位操作的消息后面。通常用一个64位的数据来表示原始消息的长度。如果消息长度不大于2^64,那么第一个字就是0。在进行了补长度的操作以后,整个消息就变成下面这样了(16进制格式)

61626380 0000000000000000 00000000

00000000 0000000000000000 00000000

00000000 0000000000000000 00000000

00000000 0000000000000000 00000018

SHA256算法描述

为了更好的理解SHA256的原理,这里首先将算法中可以单独抽出的模块,包括常量的初始化、信息预处理、使用到的逻辑运算分别进行介绍,甩开这些理解上的障碍后,一起来探索SHA256算法的主体部分,即消息摘要是如何计算的。

常量

SHA256算法中用到了8个哈希初值以及64个哈希常量

其中,SHA256算法的8个哈希初值如下:

h0 := 0x6a09e667
h1 := 0xbb67ae85
h2 := 0x3c6ef372
h3 := 0xa54ff53a
h4 := 0x510e527f
h5 := 0x9b05688c
h6 := 0x1f83d9ab
h7 := 0x5be0cd19

这些初值是对自然数中前8个质数(2,3,5,7,11,13,17,19)的平方根的小数部分取前32bit而来

举个例子来说,$ \sqrt{2} $小数部分约为0.414213562373095048,

而0.414213562373095048≈6∗16 −1 +a∗16 −2 +0∗16 −3 +…

于是,质数2的平方根的小数部分取前32bit就对应出了0x6a09e667

在SHA256算法中,用到的64个常量如下:

428a2f98 71374491 b5c0fbcf e9b5dba5
3956c25b 59f111f1 923f82a4 ab1c5ed5
d807aa98 12835b01 243185be 550c7dc3
72be5d74 80deb1fe 9bdc06a7 c19bf174
e49b69c1 efbe4786 0fc19dc6 240ca1cc
2de92c6f 4a7484aa 5cb0a9dc 76f988da
983e5152 a831c66d b00327c8 bf597fc7
c6e00bf3 d5a79147 06ca6351 14292967
27b70a85 2e1b2138 4d2c6dfc 53380d13
650a7354 766a0abb 81c2c92e 92722c85
a2bfe8a1 a81a664b c24b8b70 c76c51a3
d192e819 d6990624 f40e3585 106aa070
19a4c116 1e376c08 2748774c 34b0bcb5
391c0cb3 4ed8aa4a 5b9cca4f 682e6ff3
748f82ee 78a5636f 84c87814 8cc70208
90befffa a4506ceb bef9a3f7 c67178f2

和8个哈希初值类似,这些常量是对自然数中前64个质数(2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97…)的立方根的小数部分取前32bit而来。

信息预处理

SHA256算法中的预处理就是在想要Hash的消息后面补充需要的信息,使整个消息满足指定的结构。

信息的预处理分为两个步骤:附加填充比特和附加长度

即我们上面讲的补位和补长度。

在完成了信息预处理之后,我们来看看sha256中的逻辑运算

逻辑运算

SHA256散列函数中涉及的操作全部是逻辑的位运算

包括如下的逻辑函数:

CH(x, y, z) = (x AND y) XOR ( (NOT x) AND z)  
MAJ( x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z)  
BSIG0(x) = ROTR^2(x) XOR ROTR^13(x) XOR ROTR^22(x)  
BSIG1(x) = ROTR^6(x) XOR ROTR^11(x) XOR ROTR^25(x)  
SSIG0(x) = ROTR^7(x) XOR ROTR^18(x) XOR SHR^3(x)  
SSIG1(x) = ROTR^17(x) XOR ROTR^19(x) XOR SHR^10(x) 

其中 x、y、z皆为32bit的字。 ROTR^2(x)是对x进行循环右移2位。

计算消息摘要

基本思想:就是将消息分成N个512bit的数据块,哈希初值H(0)经过第一个数据块得到H(1),H(1)经过第二个数据块得到H(2),……,依次处理,最后得到H(N),然后将H(N)的8个32bit连接成256bit消息摘要

首先:将消息分解成512-bit大小的块

如上图所示,假设消息M可以被分解为n个块,于是整个算法需要做的就是完成n次迭代,

n次迭代的结果就是最终的哈希值,即256bit的数字摘要。

一个256-bit的摘要的初始值H0,经过第一个数据块进行运算,得到H1,即完成了第一次迭代

H1经过第二个数据块得到H2,……,依次处理,最后得到Hn,Hn即为最终的256-bit消息摘要

将每次迭代进行的映射用$ Map(H_{i-1}) = H_{i} $表示,于是迭代可以更形象的展示为:

图中256-bit的Hi被描述8个小块,这是因为SHA256算法中的最小运算单元称为“字”(Word),一个字是32位。

此外,第一次迭代中,映射的初值设置为前面介绍的8个哈希初值,如下图所示:

下面开始介绍每一次迭代的内容,即映射$ Map(H_{i-1}) = H_{i} $的具体算法:

第一步,构造64个字(word)

对于每一块,将块分解为16个32-bit的big-endian的字,记为w[0], …, w[15]

也就是说,前16个字直接由消息的第i个块分解得到

其余的字由如下迭代公式得到:

Wt=σ1(Wt−2)+Wt−7+σ0(Wt−15)+Wt−16

第二步, 进行64次循环

映射 $ Map(H_{i-1}) = H_{i} $ 包含了64次加密循环

即进行64次加密循环即可完成一次迭代

每次加密循环可以由下图描述:

图中,ABCDEFGH这8个字(word)在按照一定的规则进行更新,其中

深蓝色方块是事先定义好的非线性逻辑函数,上文已经做过铺垫

红色田字方块代表 mod $ 2^{32} $ addition,即将两个数字加在一起,如果结果大于$ 2^{32} ,你必须除以 ,你必须除以,你必须除以 2^{32} $并找到余数。

ABCDEFGH一开始的初始值分别为$ H_{i-1}(0),H_{i-1}(1),…,H_{i-1}(7) $

Kt是第t个密钥,对应我们上文提到的64个常量

Wt是本区块产生第t个word。原消息被切成固定长度512-bit的区块,对每一个区块,产生64个word,通过重复运行循环n次对ABCDEFGH这八个字循环加密。

最后一次循环所产生的八个字合起来即是第i个块对应到的散列字符串$ H_{i} $

由此便完成了SHA256算法的所有介绍

深入EVM:EVM工作机制的深层分析(终极版)

本文翻译自英文原文: Getting Deep Into EVM: How Ethereum Works Backstage

本文是“深入区块链”系列文章之一,意在深入介绍以太坊等区块链的内部工作机制。LinkChain博客前期曾翻译并分享了该系列文章的其中一篇“四十种智能合约支持平台(完全版)”,其它文章如下:

Getting Deep Into Geth: Why Syncing Ethereum Node Is Slow

Getting Deep Into Ethereum: How Data Is Stored In Ethereum?

本文将详细深入介绍EVM的核心机制,涉及智能合约建立机制、消息调用机制,并介绍存储、内存、CALLDATA和栈(STACK)这四类数据管理机制。

要理解本文内容,读者最好具有基础的以太坊知识。如果有必要,推荐阅读下面这篇博客:

5 resources to get started with ethereum

文中使用的所有例子和演示,均开源提供在该代码库中,读者可以克隆代码、运行npm install,进而查看运行结果。

EVM概览

在深入了解并通过代码例子查看EVM的工作机制之前,我们先阐述EVM最适用于以太坊之处,以及EVM的组成。在看到下面给出的复杂结构图时,请不要心生恐惧。一旦读完本文,你自然会理解这些它们。

下图给出了EVM是如何匹配以太坊运行的:

下图给出了EVM的基本架构:

下图给出了EVM各组成部分间的相互作用机制,由此实现了以太坊的神奇功能。

至此,我们对EVM有了一个整体上的了解。下面,我们将深入介绍各部分在以太坊运行中的重要作用。

以太坊合约(Ethereum Contracts)

基本概念

智能合约就是一种计算机程序。以太坊合约可以称为运行在EVM上的智能合约。EVM是一种“沙盒”(Sandbox)运行时,它为智能合约在以太坊中的运行提供了完全独立的环境。这也意味着,运行在EVM中的每个智能合约不能访问网络,也不能访问运行EVM主机上的任何进程。

我们知道,以太坊具有两种类型的账户,合约账户(contract account)和外部账户(external account)。每个账户由一个地址唯一标识,所有账户共享同一地址空间。EVM可处理的地址长度为160比特。

每个账户由余额(balance)、nonce、字节码(bytecode)和存储数据(即存储,storage)组成。以太坊的两类账户间存在着一些差异之处。例如,外部账户的字节码和存储为空,而合约账户中存储了字节码和整个状态树的默克尔根哈希值。此外,外部地址对应一个私钥,而合约账户则没有。对于合约账户,除了对每个以太坊交易做正常的加密认证外,所有其余动作均由其所持有的字节码控制。

创建合约

合约是以交易的形式创建的。交易中的接收者地址为空,数据域则包含了要创建合约的编译后字节码(需注意,一个合约可以创建另一个合约)。下面给出一个例子。打开练习一的目录,其中可以看到一个名为“MyContract”的合约。该合约的代码如下:

pragma solidity ^0.4.21;
contract MyContract {
  event Log(address addr);
function MyContract() public {
    emit Log(this);
  }
function add(uint256 a, uint256 b) public pure returns (uint256) {
    return a + b;
  }
}

运行命令truffle develop,以开发模式打开一个Truffle终端。一旦终端启动完成,使用下面命令在MyContract中部署实例:

truffle(develop)> compile
truffle(develop)> sender = web3.eth.accounts[0]
truffle(develop)> opts = { from: sender, to: null, data: MyContract.bytecode, gas: 4600000 }
truffle(develop)> txHash = web3.eth.sendTransaction(opts)

下面检查合约是否成功部署。运行如下代码:

truffle(develop)> receipt = web3.eth.getTransactionReceipt(txHash)
truffle(develop)> myContract = new MyContract(receipt.contractAddress)
truffle(develop)> myContract.add(10, 2)
{ [String: ‘12’] s: 1, e: 1, c: [ 12 ] }

我们深入查看一下上面的工作。当一个新的合约部署到以太坊区块链上时,首先完成的是创建合约对应的账户¹。在上例中我以看到,构造函数(constructor)中记录了合约的地址。为进一步确认,可查看receipt.logs[0].data中保存的就是合约生成的32个字节的地址,receipt.logs[0].topics中保持的是“Log(address)”字符串的Keccak-256哈希(即SHA3)。 K

调用合约中函数时,后台的运行结构图

下一步,随交易发送的数据将以字节码方式执行。它初始化存储中的状态变量,并确定所创建的合约体。该过程在合约的全生命周期中仅执行一次。初始化的代码并非存储在合约中,它实际上生成了要存储的字节码作为其返回值。谨记,一旦合约账户创建完成,就无法更改合约的代码²。

鉴于初始化过程返回了合约体中将要存储的字节码,因此从构造函数的逻辑无法访问此代码,这点具有意义的。下面以练习一中的Impossible合约为例:

contract Impossible {
  function Impossible() public {
    this.test();
  }
function test() public pure returns(uint256) {
    return 2;
  }
}

如果我们尝试编译该合约,那么就会得到警告,指出在构造函数中引用了 this (即“referencing this within the constructor function”)。该警告并不影响编译的继续进行。但是一旦部署新实例,就会出现终止执行并还原状态(revert)。这是因为运行尚未存储的代码是毫无意义的³。另一方面,因为账户已经创建,我们可以访问合约的地址,但是其中并未存储任何代码。

此外,代码执行还会产生另一种情况,例如更改了存储、创建更多账户、做更多的消息调用等。下面以AnotherContract合约代码为例:

contract AnotherContract {
  MyContract public myContract;
  function AnotherContract() public {
    myContract = new MyContract();
  }
}

在Truffle终端中运行下面命令,查看合约的运行情况:

truffle(develop)> compile
truffle(develop)> sender = web3.eth.accounts[0]
truffle(develop)> opts = { from: sender, to: null, data: AnotherContract.bytecode, gas: 4600000 }
truffle(develop)> txHash = web3.eth.sendTransaction(opts)
truffle(develop)> receipt = web3.eth.getTransactionReceipt(txHash)
truffle(develop)> anotherContract = AnotherContract.at(receipt.contractAddress)
truffle(develop)> anotherContract.myContract().then(a => myContractAddress = a)
truffle(develop)> myContract = MyContract.at(myContractAddress)
truffle(develop)> myContract.add(10, 2)
{ [String: ‘12’] s: 1, e: 1, c: [ 12 ] }

另一方面,鉴于Solidity结构最终也将编译为指令码,因此合约也可以使用CREATE指令码(opcode)创建。上面介绍的两种合约创建方式的工作机制相同。

下面,我们将介绍消息调用的工作机制。

消息调用(Message Call)

合约间可以通过消息调用实现相互调用。一个Solidity合约在每次调用另一个合约的函数时,就会生成一次消息调用。每次调用具有发送者、接受者、二进制内容(payload)、值以及GAS数量。限制消息调用的深度为不大于1024层。

Solidity为地址类型提供了原生调用方法,工作如下:

address.call.gas(gas).value(value)(data)

其中,gas是要传递的GAS数量,address是调用的地址,value是要传递的以太币wei数,data是要发送的二进制内容。谨记,valuegas是可选参数,使用应谨慎。因为默认情况下,低层调用将会将发送者几乎全部剩余的GAS发送出去。

图 GAS消费结构图

从上图可见,合约可以确定每次调用中要传递的GAS数量。每次调用都会因为“GAS耗尽”(OOG,out-of-gas)异常而终止执行。为避免出现安全问题,调用中至少会保留发送者GAS数量的1/64。这使得发送者可以处理调用的OOG异常、完成执行而不会耗尽GAS,进而也不会触发异常。

产生异常的结构图

下面以练习二中的Caller合约为例:

contract Implementation {
  event ImplementationLog(uint256 gas);
function() public payable {
    emit ImplementationLog(gasleft());
    assert(false);
  }
}
contract Caller {
  event CallerLog(uint256 gas);
  Implementation public implementation;

  function Caller() public {
    implementation = new Implementation();
  }
function () public payable {
    emit CallerLog(gasleft());
    implementation.call.gas(gasleft()).value(msg.value)(msg.data);
    emit CallerLog(gasleft());
  }
}

其中,Caller合约只有一个回调函数,实现所有接收到的调用重定向到Implementation实例。该实例只是通过每个接收到的调用上的assert(false)抛出,调用将消费所有提供的GAS,进而在传递调用给Implementation之前和之后,将GAS数量记录到Caller中。下面启动一个Truffle终端运行如下命令,查看运行情况:

truffle(develop)> compile
truffle(develop)> Caller.new().then(i => caller = i)
truffle(develop)> opts = { gas: 4600000 }
truffle(develop)> caller.sendTransaction(opts).then(r => result = r)
truffle(develop)> logs = result.receipt.logs
truffle(develop)> parseInt(logs[0].data) //4578955
truffle(develop)> parseInt(logs[1].data) //71495

如结果所示,71495大体上构成了4578955的第64个部分。该例子清晰地验证了,代码处理了内部调用抛出的OOG异常。

Solidity还提供了call操作码,支持在内联汇编(inline assembly)中管理调用:

call(g, a, v, in, insize, out, outsize)

其中,g是要传递的GAS数量,a是被调用地址,v是要传递的以太币wei数,in指定了保存调用数据的insize字节的内存地址,outoutsize指定了返回数据的内存存储地址。汇编调用与函数二者的唯一不同之处在于,汇编调用支持我们处理返回数据,而函数只会返回1或0指示函数处理成功与否。

EVM支持一类特殊的消息调用变体,称为“delegatecall”。同上,Solidity在提供内联汇编版本的同时,还提供了内建的地址方法。二者的不同之处在于,对于低层调用,目标代码在调用合约的上下文内执行,而msg.sendermsg.value并非如此。⁴

更好地理解delegatecall的工作机制,我们对下面的例子进行分析。首先给出Greeter合约的代码:

contract Greeter {
  event Thanks(address sender, uint256 value);
function thanks() public payable {
    emit Thanks(msg.sender, msg.value);
  }
}

如上,Greeter合约只定义了一个thanks函数,发出一个承载了msg.valuemsg.sender数据的事件。在Truffle终端中使用如下命令运行该方法:

truffle(develop)> compile
truffle(develop)> someone = web3.eth.accounts[0]
truffle(develop)> ETH_2 = new web3.BigNumber(‘2e18’)
truffle(develop)> Greeter.new().then(i => greeter = i)
truffle(develop)> opts = { from: someone, value: ETH_2 }
truffle(develop)> greeter.thanks(opts).then(tx => log = tx.logs[0])
truffle(develop)> log.event                     //Thanks
truffle(develop)> log.args.sender === someone   //true
truffle(develop)> log.args.value.eq(ETH_2)      //true

运行结果确认了该函数的功能。注意Wallet合约的代码:

contract Wallet {
  Greeter internal greeter;

  function Wallet() public {
    greeter = new Greeter();
  }

  function () public payable {
    bytes4 methodId = Greeter(0).thanks.selector;
    require(greeter.delegatecall(methodId));
  }
}

该合约只定义了一个回调函数,通过delegatecall执行Greeter#thanks方法。下面通过Wallet合约调用Greeter#thanks合约,在Truffle终端查看运行情况:

truffle(develop)> Wallet.new().then(i => wallet = i)
truffle(develop)> wallet.sendTransaction(opts).then(r => tx = r)
truffle(develop)> logs = tx.receipt.logs
truffle(develop)> SolidityEvent = require(‘web3/lib/web3/event.js’)
truffle(develop)> Thanks = Object.values(Greeter.events)[0]
truffle(develop)> event = new SolidityEvent(null, Thanks, 0)
truffle(develop)> log = event.decode(logs[0])
truffle(develop)> log.event                    // Thanks
truffle(develop)> log.args.sender === someone  // true
truffle(develop)> log.args.value.eq(ETH_2)     // true

从结果中可以看到,delegatecall函数保持了msg.valuemsg.sender

这意味着合约可以在运行时从不同的地址动态地加载代码。存储、当前地址和余额依然指向调用合约,只有代码是取自于被调用地址。这意味着可在Solidity中实现“软件库”⁵。

关于delegatecalls,我们还需要了解一件事情。如上所述,被调用合约的存储是可以被所执行代码访问的。下面查看Calculator合约的代码:

contract ResultStorage {
  uint256 public result;
}
contract Calculator is ResultStorage {
  Product internal product;
  Addition internal addition;
function Calculator() public {
    product = new Product();
    addition = new Addition();
  }

  function add(uint256 x) public {
    bytes4 methodId = Addition(0).calculate.selector;
    require(addition.delegatecall(methodId, x));
  }
function mul(uint256 x) public {
    bytes4 methodId = Product(0).calculate.selector;
    require(product.delegatecall(methodId, x));
  }
}
contract Addition is ResultStorage {
  function calculate(uint256 x) public returns (uint256) {
    uint256 temp = result + x;
    assert(temp >= result);
    result = temp;
    return result;
  }
}
contract Product is ResultStorage {
  function calculate(uint256 x) public returns (uint256) {
    if (x == 0) result = 0;
    else {
      uint256 temp = result * x;
      assert(temp / result == x);
      result = temp;
    }
    return result;
  }
}

其中,Calculator合约只有两个函数,即addproductCalculator合约并不知道如何执行相加或相乘运算,而是将相应的调用分别代理(delegate)给AdditionProduct合约。所有这些合约共享相同的状态变量结果,并存储每次计算的结果。下面在Turffle终端运行命令,查看运行情况:

truffle(develop)> Calculator.new().then(i => calculator = i)
truffle(develop)> calculator.addition().then(a => additionAddress=a)
truffle(develop)> addition = Addition.at(additionAddress)
truffle(develop)> calculator.product().then(a => productAddress = a)
truffle(develop)> product = Product.at(productAddress)
truffle(develop)> calculator.add(5)
truffle(develop)> calculator.result().then(r => r.toString()) // 5
truffle(develop)> addition.result().then(r => r.toString())   // 0
truffle(develop)> product.result().then(r => r.toString())    // 0
truffle(develop)> calculator.mul(2)
truffle(develop)> calculator.result().then(r => r.toString()) // 10
truffle(develop)> addition.result().then(r => r.toString())   // 0
truffle(develop)> product.result().then(r => r.toString())    // 0

可以确认,我们使用了Calculator合约的存储。此外,所执行的代码存储在Product合约和Addition合约中。

对于call函数,同样存在Solidity汇编操作码版本的delegatecall。下面给出Delegator合约的代码,注意其中对delegatecall的调用方式:

contract Implementation {
  event ImplementationLog(uint256 gas);
function() public payable {
    emit ImplementationLog(gasleft());
    assert(false);
  }
}
contract Delegator {
  event DelegatorLog(uint256 gas);
  Implementation public implementation;
function Delegator() public {
    implementation = new Implementation();
  }
function () public payable {
    emit DelegatorLog(gasleft());
    address _impl = implementation;
assembly {
     let ptr := mload(0x40)
     calldatacopy(ptr, 0, calldatasize)
     let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
    }

    emit DelegatorLog(gasleft());
  }
}

这里,我们使用了内联汇编去执行delegatecall。你可能注意到,其中没有值参数,这是因为msg.value不会发生更改。你可能会有疑问,为什么这里我们加载的是0x40地址?calldatacopycalldatasize是什么?我们将在本系列的下一篇文章中详细介绍。下面,我们可以在Truffle终端中运行同上的命令,验证合约的行为。

再次强调,重要的是要清楚理解delegatecall的工作机制。每此触发的调用将被从当前合约发送,而非代理调用的合约。此外,所执行代码可以读写调用者合约的存储。如果合约并未正确地实现,甚至是存在非常小的错误,都可能导致上百万美元的损失。下文列出了以太坊历史上一些最严重的错误:

HackPedia: 16 Solidity Hacks/Vulnerabilities, their Fixes and Real World Examples

该文列举了Solidity被破解和漏洞的完全列表、修复情况,以及一些真实世界的破解实例。

数据管理(Data Management)

EVM根据不同的应用场景,采用不同的方式管理不同类型的数据。除了合约代码之外,合约所管理的数据大体可分为四类:栈(Stack)、调用数据(calldata)、内存和存储。

栈(tack)

EVM本身就是一种栈机器。也就是说,EVM的操作并非基于注册函数,而是基于虚拟栈。栈的最大规模是1024,其中栈条目(item)的大小是256比特。事实上,EVM是一种256比特的字(word)机器,这种设计便于Keccak256哈希模式和椭圆曲线密码(elliptic-curve)的计算。下图给出了大部分操作码输入参数的来源。

EVM提供了多种操作码,用于直接操作栈。其中包括:

  • POP:从栈中移除条目。
  • PUSHn:将后面n个字节条目置于栈中,n的大小介于1到32之间。
  • DUPn:复制第n个栈条目,n的大小介于1到32之间。
  • SWAPn:交换栈中第一个和第n个条目的位置,n的大小介于1到32之间。

调用数据(Calldata)

调用数据是只读的字节地址编码空间,用于存储交易或调用的数据参数。不同于栈,要使用调用数据,必须要准确地指定字节偏移量和要读取的字节数。

调用数据的EVM操作码包括:

  • CALLDATASIZE:指定交易数据的规模。
  • CALLDATALOAD:加载32字节的交易数据到栈中。
  • CALLDATACOPY:拷贝交易数据的指定字节数到内存中。

Solidity为上述操作码提供了内联编译版本,分别是calldatasizecalldataloadcalldatacopy。其中,calldatacopy需要指定三个参数(t, f, s),将f地址处的调用数据拷贝s个字节到t地址。此外,Solidity支持通过msg.data访问调用数据。

你可能注意到,我们在以前文章的一些例子中使用了部分操作码。下面,我们再看一下delegatecall的内联汇编代码块:

assembly {
  let ptr := mload(0x40)
  calldatacopy(ptr, 0, calldatasize)
  let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
}

要将调用代理给_impl地址,我们必须提交msg.data。鉴于delegatecall操作码在内存中操作数据,我们首先需要将调用数据拷贝到内存中。这里,我们使用calldatacopy,将所有的调用数据拷贝到指定内存指针处。注意,我们使用了calldatasize

下面,我们看一下另一个使用调用数据的例子。在练习三的目录中,可以看到Calldata合约的代码如下:

contract Calldata {
function add(uint256 _a, uint256 _b) public view 
  returns (uint256 result) 
  {
    assembly {
      let a := mload(0x40)
      let b := add(a, 32)
      calldatacopy(a, 4, 32)
      calldatacopy(b, add(4, 32), 32)
      result := add(mload(a), mload(b))
    }
  }
}

上面的代码将返回由参数传递而来的两个数字的相加运算结果。注意,这里我们再一次加载了从0x40处读取的内存指针,原因将在本文稍后给出解释。我们在变量a中存储内存指针,并在变量b中存储a的32个字节之后的位置。然后我们使用calldatacopy将首个参数存储在a中。你应该注意到,数据是从调用数据的第四个位置处拷贝的,而非首个位置处。这是因为调用数据的头四个字节保存了被调用函数的签名,这里是bytes4(keccak256("add(uint256,uint256)"))。这是EVM用于识别调用时需要的函数。然后,我们存储b中第二个参数,拷贝调用数据随后32个字节。最后,我们只需要计算加载在内存中两个值的和。

在Truffle终端中运行下面命令,测试运行结果:

truffle(develop)> compile
truffle(develop)> Calldata.new().then(i => calldata = i)
truffle(develop)> calldata.add(1, 6).then(r => r.toString())    // 7

内存(Memory)

内存是一种易失性、字节可寻址的读写空间,用于在合约执行期间存储数据,主要是将参数传递给内部函数。鉴于内存是易失性区域,因此在每次消息调用开始,都要执行清除内存操作。所有位置的最初定义为零。对于调用数据,内存可采用字节级别寻址,但一次只能读取32字节。

一旦一个字写入了一块以前从未使用的内存,我们就称之为内存“扩展”了。除了内存写入需要一定代价外,内存扩展也是有代价的,前724个字节的扩展代价是线性的,之后的扩展代价呈二次方增长。

EVM提供了三个操作内存区域的操作码:

  • MLOAD:将字从内存加载到栈中。
  • MSTORE:将字保存到内存中。
  • MSTORE8:将一个字节保存到内存中。

Solidity同样对这些操作码提供了相应的内联汇编版本。

关于内存,我们还需要了解另一个关键点。Solidity总是在位置“0x40”处存储空闲内存指针,即对存储器中第一个未使用的字的引用。这就是为什么我们在操作内联汇编时需要加载这个字。这是因为头64字节内存是为EVM保留的,这样可以确保不会覆盖Solidity内部使用的内存。例如,在上面给出的delegatecall例子中,我们加载此指针,存储给定的调用数据,实现数据转发。这是因为内联汇编操作码delegatecall需要从内存中获取其负载。

此外,如果查看Solidity编译器的字节码输出,那么我们就会发现所有字节码是以0x6060604052…开始的,这表示了:

PUSH1   :  EVM操作码0x60
0x60    :  自由内存指针。
PUSH1   :  EVM操作码0x60。
0x40    :  自由内存指针的内存位置。
MSTORE  :  EVM操作码0x52。

在汇编层级操作内存必须要谨慎,因为存在覆盖保留区域的风险。

存储(Storage)

存储是一种持久的、字可寻址的读写空间,是合约存储其中持久信息的地方。不同于内存,存储是持久性区域,只能使用字作为地址。它是2²⁵⁶个槽的键值映射,其中每个槽32字节。除了合约自身的存储,合约既不能读取也不能写入其它任何存储。所有位置最初定义为零。

在EVM的所有操作中,将数据保存到存储是需GAS数量最高的操作之一。这笔费用并非一成不变的。将存储槽从零值修改为非零值需要2万个GAS。存储相同的非零值或将非零值设置为零时需要5千个GAS。但是,对于后一种应用场景,即将非零值设置为零时,会提供15000个GAS的返还款。

EVM提供了两个操作存储的操作:

  • SLOAD:将字从存储加载到栈中。
  • SSTORE:将字保存到存储。

同样,Solidity内联编译也支持这些操作码。

Solidity自动将合约中每个已定义的状态变量映射到存储的相应插槽中。映射策略非常简单:固定大小的变量(即除映射和动态数组之外的所有变量)从存储的位置0开始连续布局。

对于动态数组,p槽位存储数据长度,数组数据将由p哈希(即keccak256(p))确定槽位数。

对于映射,不使用槽位,对应于键k的值由keccak256(k,p)定位。谨记,keccak256 的参数(kp)总是填充为32字节。

为解释其中的工作机制,我们分析下面给出代码例子。在练习三合约目录中,提供了Storage合约的代码如下:

contract Storage {
uint256 public number;
  address public account;
  uint256[] private array;
  mapping(uint256 => uint256) private map;
function Storage() public {
    number = 2;
    account = this;
    array.push(10);
    array.push(100);
    map[1] = 9;
    map[2] = 10;
  }
}

打开一个Truffle终端,测试合约的存储结构。首先,编译并创建一个新的合约实例: Now, let’s open a truffle console to test its storage structure. First, we will compile and create a new contract instance:

truffle(develop)> compile
truffle(develop)> Storage.new().then(i => storage = i)

确保地址0保存数值2,地址1保存了合约的地址:

truffle(develop)> web3.eth.getStorageAt(storage.address, 0)  // 0x02
truffle(develop)> web3.eth.getStorageAt(storage.address, 1)  // 0x..

检查存储位置2保存了数组长度:

truffle(develop)> web3.eth.getStorageAt(storage.address, 2)  // 0x02

最后,检查存储位置3是未使用的,映射值的存储方式如我们上面所介绍:

truffle(develop)> web3.eth.getStorageAt(storage.address, 3) 
// 0x00
truffle(develop)> mapIndex = ‘0000000000000000000000000000000000000000000000000000000000000003’
truffle(develop)> firstKey = ‘0000000000000000000000000000000000000000000000000000000000000001’
truffle(develop)> firstPosition = web3.sha3(firstKey + mapIndex, { encoding: ‘hex’ })
truffle(develop)> web3.eth.getStorageAt(storage.address, firstPosition)
// 0x09
truffle(develop)> secondKey = ‘0000000000000000000000000000000000000000000000000000000000000002’
truffle(develop)> secondPosition = web3.sha3(secondKey + mapIndex, { encoding: ‘hex’ })
truffle(develop)> web3.eth.getStorageAt(storage.address, secondPosition)
// 0x0A

很好,上面演示了Solidity存储策略,正如我们所理解的!要了解更多Solidity是如何映射状态变量到存储中,可参阅官方文档.

希望本文有助于大家更好地理解EVM在以太坊架构中的功能。

¹ 以太坊黄皮书中提出,“新账户地址定义为:对仅包含发送者和帐户nounce的结构做RLP编码,对所得到编码求Keccak哈希值,取该哈希值最右边开始的160位。”

² zeppelin_os的支柱之一就是合同的可升级性。 Zeppelin一直在探索实施这一目标的不同策略。 在这里阅读更多相关信息。

³ Solidity在调外部函数前,会先验证该函数的地址中是否具有字节码。否则,终止函数执行并还原状态。

作者简介

Vaibhav Saini是一家由MIT Cambridge 创新中心孵化的初创企业TowardsBlockchain的联合创始人。Saini也是一名高级区块链开发人员,具有Ethereum、Quorum、EOS、Nano、Hashgraph、IOTA等多种区块链平台的开发经验。他目前是德里印度理工学院(IIT Delhi)的一名大二学生。

比特币数据结构解析

区块

区块结构

{
  "version": 1,
  "merkleroot": "10f072e631081ad6bcddeabb90bc34d787fe7d7116fe0298ff26c50c5e21bfea",
  "tx": [
    "10f072e631081ad6bcddeabb90bc34d787fe7d7116fe0298ff26c50c5e21bfea"
  ],
  "time": 1233046715,
   "nonce": 2999858432,
  "bits": "1d00ffff",
  "difficulty": 1,
  "previousblockhash": "00000000a1496d802a4a4074590ec34074b76a8ea6b81c1c9ad4192d3c2ea226",
}

区块hex解析

block: 00000000dfd5d65c9d8561b4b8f60a63018fe3933ecb131fb37f905f87da951a
hex:

0100000026a22e3c2d19d49a1c1cb8a68e6ab77440c30e5974404a2a806d49a100000000eabf215e0cc526ff9802fe16717dfe87d734bc90bbeaddbcd61a0831e672f010bbcc7e49ffff001d0035ceb20101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d029b01ffffffff0100f2052a0100000043410413e59d83b9055a14263cf4d953dc11fdc767be418325ef592150f16f944030d64a31105e2b5977118f4f3fa67b903b8ccc05b09770a791adfa061511f16e549cac00000000

hex解析:

版本号:01000000
前继block:26a22e3c2d19d49a1c1cb8a68e6ab77440c30e5974404a2a806d49a100000000(默认32个字节需要网络字节序转主机字节序:00000000a1496d802a4a4074590ec34074b76a8ea6b81c1c9ad4192d3c2ea226)

merkle根:eabf215e0cc526ff9802fe16717dfe87d734bc90bbeaddbcd61a0831e672f010(默认32个字节需要网络字节序转主机字节序:10f072e631081ad6bcddeabb90bc34d787fe7d7116fe0298ff26c50c5e21bfea)

时间:bbcc7e49(默认32个字节需要网络字节序转主机字节序:497eccbb,1233046715)

bits:ffff001d(难度目标,默认32个字节需要网络字节序转主机字节序:1d00ffff)

nounce:0035ceb2(默认32个字节需要网络字节序转主机字节序:b2ce3500,2999858432)

交易数量:01

交易body:01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d029b01ffffffff0100f2052a0100000043410413e59d83b9055a14263cf4d953dc11fdc767be418325ef592150f16f944030d64a31105e2b5977118f4f3fa67b903b8ccc05b09770a791adfa061511f16e549cac00000000

交易

交易结构

交易包含:

{
    version:0x01,
    inCount:1,
    ins:[
        {
            "txid":"74c1a6dd6e88f73035143f8fc7420b5c395d28300a70bb35b943f7f2eddc656d",
            "index":0,
            "scriptSig":"493046022100b687c4436277190953466b3e4406484e89a4a4b9dbefea68cf5979f74a8ef5b1022100d32539ffb88736f3f9445fa6dd484b443ebb31af1471ee65071c7414e3ec007b014104f9804cfb86fb17441a6562b07c4ee8f012bdb2da5be022032e4b87100350ccc7c0f4d47078b06c9d22b0ec10bdce4c590e0d01aed618987a6caa8c94d74ee6dc",
            "sequence":ffffffff
        }
    ],
    outCount:2,
    outs:[
        {
            "value" : 0.01,
            "scriptPubKey":"410403c344438944b1ec413f7530aaa6130dd13562249d07d53ba96d8ac4f59832d05c837e36efd9533a6adf1920465fed2a4553fb357844f2e41329603c320753f4ac"
        }
        {
            "value" : 49.91,
            "scriptPubKey":"4104f9804cfb86fb17441a6562b07c4ee8f012bdb2da5be022032e4b87100350ccc7c0f4d47078b06c9d22b0ec10bdce4c590e0d01aed618987a6caa8c94d74ee6dcac"
        }
    ],
    nLockTime:000000
}

交易hex解析

交易: 131f68261e28a80c3300b048c4c51f3ca4745653ba7ad6b20cc9188322818f25

交易hex:

01000000016d65dcedf2f743b935bb700a30285d395c0b42c78f3f143530f7886edda6c174000000008c493046022100b687c4436277190953466b3e4406484e89a4a4b9dbefea68cf5979f74a8ef5b1022100d32539ffb88736f3f9445fa6dd484b443ebb31af1471ee65071c7414e3ec007b014104f9804cfb86fb17441a6562b07c4ee8f012bdb2da5be022032e4b87100350ccc7c0f4d47078b06c9d22b0ec10bdce4c590e0d01aed618987a6caa8c94d74ee6dcffffffff0240420f000000000043410403c344438944b1ec413f7530aaa6130dd13562249d07d53ba96d8ac4f59832d05c837e36efd9533a6adf1920465fed2a4553fb357844f2e41329603c320753f4acc0aff62901000000434104f9804cfb86fb17441a6562b07c4ee8f012bdb2da5be022032e4b87100350ccc7c0f4d47078b06c9d22b0ec10bdce4c590e0d01aed618987a6caa8c94d74ee6dcac00000000

hex解析

版本号:01000000
vin数量:01
vin交易id:6d65dcedf2f743b935bb700a30285d395c0b42c78f3f143530f7886edda6c174
(txid,默认32个字节需要网络字节序转主机字节序 74c1a6dd6e88f73035143f8fc7420b5c395d28300a70bb35b943f7f2eddc656d)
vin索引:00000000(代表交易(74c1a6dd6e88f73035143f8fc7420b5c395d28300a70bb35b943f7f2eddc656d)的第几个输出)
签名长度:8c(140个字节,280个hex码)
vin的签名:493046022100b687c4436277190953466b3e4406484e89a4a4b9dbefea68cf5979f74a8ef5b1022100d32539ffb88736f3f9445fa6dd484b443ebb31af1471ee65071c7414e3ec007b014104f9804cfb86fb17441a6562b07c4ee8f012bdb2da5be022032e4b87100350ccc7c0f4d47078b06c9d22b0ec10bdce4c590e0d01aed618987a6caa8c94d74ee6dc

// 140字节长度的签名,含有两个部分:签名+公钥
{
    签名长度:49(73个字节,146个hex码)
    签名:3046022100b687c4436277190953466b3e4406484e89a4a4b9dbefea68cf5979f74a8ef5b1022100d32539ffb88736f3f9445fa6dd484b443ebb31af1471ee65071c7414e3ec007b01
    公钥长度:41(65个字节,130个hex码)
    公钥:04f9804cfb86fb17441a6562b07c4ee8f012bdb2da5be022032e4b87100350ccc7c0f4d47078b06c9d22b0ec10bdce4c590e0d01aed618987a6caa8c94d74ee6dc
}
sequence:ffffffff

vout数量:02
vout1金额:40420f0000000000(默认8个字节需要网络字节序转主机字节序,00000000000f4240->1000000->0.01btc)
vout1输出地址长度:43(67个字节,134个hex码)
vout1输出地址:410403c344438944b1ec413f7530aaa6130dd13562249d07d53ba96d8ac4f59832d05c837e36efd9533a6adf1920465fed2a4553fb357844f2e41329603c320753f4ac
vout2金额:c0aff62901000000
vout2输出地址长度:43(67个字节,134个hex码)
vout2输出地址:4104f9804cfb86fb17441a6562b07c4ee8f012bdb2da5be022032e4b87100350ccc7c0f4d47078b06c9d22b0ec10bdce4c590e0d01aed618987a6caa8c94d74ee6dcac
锁定时间:0000000

学术界与区块链的关联概述

摘要: 在业界看来,学术界可以解决区块链目前在可扩展性、隐私和互操作性等方面存在的局限性。本文概要介绍了当前学术界的区块链研究人员和学者,并简述了一些学术界正在构建或已推出的区块链项目。

作者: Wesley Graham

正文:

面对区块链,学术界的趋势如何?大学应该如何参与?已经构建了哪些系统?

观察要点

  • 区块链的区域性中心已在湾区(伯克利和斯坦福大学)和一些国际区域(色列和新加坡)范围内初具雏形。
  • 计算平台受到了大学教授和投资者们的重点青睐。
  • 隐私和可扩展性依然是区块链的主要关注点。

学术界的重要性所在

撰稿人: Wesley GrahamRonen Kirsh

当前绝大多数的主要区块链架构,在很大程度上受限于可扩展性、隐私和互操作性等方面上存在的技术局限。不少投资者认为,学术界是解决这些局限的力量之源。因为正是这些躬耕于学术界的专家学者们,最早提出并引领了加密方法与分布式系统的发展。

出于此考虑,本文意在让读者初步了解当前最具天赋区块链研究人员、学者和思想领袖,并概要介绍一些正在构建的系统、一些引领变革的大学,以及预测下一波贡献将会出现之处。

如何定义“学术”项目?

我们认为,区块链学术项目的特征是:
* 对当前区块链生态系统中存在的一个或多个痛点做出了实质性的贡献。
* 具体工作是由大学研究生和教授牵头完成。
* 项目已成功地实现首轮融资。

学术项目通常是围绕着一些由教授和研究人员牵头的项目开展的,其中还可能包括了一些积极参与的博士生,以及其它一些在此专业领域颇有建树的人士。本文所指的学术项目,还额外考虑了那些使用去中心化账本技术构建并受到资金会和其它投资者投资的项目。

学术生态系统:目前已构建的项目

如上图所列,当前已有不少由学术界牵头并具有一定影响的项目,它们大体可归为“支付架构”类(例如,Liquidity Networks)、“协议”类(隐私保护技术)和“平台”类(数字货币或智能合约解决方案)。这些区块链解决方案主要针对可扩展性和隐私领域。当然,还有其它一些知名项目,意在为存储、安全和分布式共识领域提供解决方案。

智能合约平台是当前学术生态系统的主要活跃投资领域之一。例如,Dfinity (融资6100万美元)、Zilliqa (融资2200万美元)和Spacemesh](https://spacemesh.io/)等项目,就因为项目的融资金额而受到媒体的重点关注。

智能合约投资的增长,可以看成是解决以太坊可扩展性和安全性问题的延续,意在寻求更适合于大规模采纳的替代平台。详细信息,推荐阅读这篇由闪电网(Lightning Network)开发者@StopAndDecrypt撰写的优秀文章

例如,今年早期成功融资4500万美元的Oasis Labs项目,就是瞄准于改进以太坊中存在的一些不足,目标是“解决目前阻碍区块链采纳的一些性能、安全、隐私限制方面的智能合约平台问题”。该项目结合了“智能飞地”(smart enclaves)技术和高性能智能合约执行,为企业解决隐私问题和可扩展性问题提供了独到的解决方案,吸引了来自区块链社区的更广泛关注。

除了智能合约平台,一些研究还在“基础设施层”等关键领域取得了显著成果。例如,适用于实际应用的底层存储实现、支付技术和隐私工具等。

例如,去中心化网络项目Filecoin在去年得到了创纪录的2.57亿美元融资,因此上了头条。此外,研究和实现zkSTARK的Starkware项目团队,引领了对EthereumZCash等现有项目的研发方向。

教授团队:项目出自于哪些大学?

大多数学术驱动型项目均出自于大学教授团队,这些教授多供职于一些传统的“有名望”大学和研发中心,例如康奈尔大学(Thunder CoreSpacemeshIC3)、麻省理工学院(AlgorandEnigmaZerocoin)、新加坡国立大学(ZilliqaKyber Network)和牛津大学(IOHK)等。

特别值得一提的是,湾区在最近十二个月中取得了巨大的发展。伯克利(Oasis LabsZCashStarkwareCoda等项目的创始人出自该学校)和斯坦福(StellarFilecoinBloomCognito等项目创始人出自该学校)等学校在该领域确立了相当大的影响力。

近期,以色列的研究机构也推出了一些项目,主要包括来自ZCashStarkwarePhantom等项目的研究者工作。其中,Spacemesh项目是由以色列荷兹利亚跨学科研究中心(IDC Herzliya)的研究人员牵头,DAG Labs由耶路撒冷希伯来大学(Hebrew University of Jerusalem)的教职人员牵头。通过引入新的共识算法(Spacemesh提出的Proof of Space Time)和数据结构(DAG Labs提出的有向无环图),这些项目提出了独到的可扩展性解决方案。

值得关注的是,一些天使投资者、风险投资公司和对冲基金都开始积极地投资该领域。它们作为早期投资者加入了Dekrypt Capital,参与到我们上面列出的部分项目中。

学术界的推波助澜

展望未来,我们非常期待学术界在构建未来区块链基础设施方面所发挥的作用。

最先进的工具要交付给数十亿用户使用,支撑这些工具的底层技术必须尽可能地接近完善。在培育下一批区块链先行者中,应将学术界需面对的严格公众审查和修订视为最有效的筛选因素之一。

一旦学术界已经开始完善技术并“扩大规模”,就应该鼓励创始人走出校园并寻求企业界参与。我们相信,如果学术界愿意让拥有经验丰富的企业家参与进来,并寻求经验证可靠的早期投资者帮助,这将比那些并不具备这些条件的项目具有更强大的竞争优势。Dekrypt Capital投资公司致力于为学术团队提供积极的支持,目前为止已得到了多个源自校园的项目的认可。

总结

我们看到,业界对层出不穷的学术驱动型项目的兴趣与日俱增。我们也相信,更多可用的基础设施将对未来的应用提供支撑。

本文给出的图片可作为深入探讨学术界在区块链领域作用的一个出发点。我们也将根据其它一些新推出项目的建议做相应的修改。如有必要,可在评论中提供学校和项目信息!!。

特别感谢Jon AllenHoward Wu及Dekrypt Capital团队的其他成员,他们为本文提供了有价值的观察和素材。

作者简介

Wesley Graham是伯克利区块链团队的咨询主管。他之前曾在埃克森美孚和DoC领导区块链项目,目前专注于区块链先知(oracles)和费用市场(Fee Markets)相关的研究。

Dekrypt Capital投资公司简介

Dekrypt Capital是一家专注于区块链基础设施、隐私保护技术和早期风险投资的业界领先投资公司。企业为投资、指导和加速团队开发提供新颖的解决方案,意在解决影响去中心化协议和应用的可扩展性、隐私和互操作性的核心问题。

免责声明:本文并不构成投资建议,也不提供任何购买或出售所涉及公司的证券、代币或其他权益的建议或募捐。Dekrypt Capital对文中所涉及的部分公司和项目有着直接或间接的利益关联。

文中表达的所有观点均为作者自身的观点,不代表作者所属的任何实体的观点。

查看英文原文: An Overview of Academia & Blockchain

三种国产区块链账户模型的比较

区块链系统中一般需要存储两类信息,即交易日志和用户数据。简而言之可以理解为,用户数据保存了账户余额,交易日志保存了账户余额的变化情况。交易日志的存储是由去中心化账本实现的,而用户数据的存储则依赖于区块链的账户模型(account model)。

一、UTXO和Account模型

目前主流的账户模型基本上可分为两类,一类是使用比特币的UTXO,另一类是基于以太坊的账户模型。两者的对比可检索到大量公开的文档,不再详述。这里只阐述其中的一个关键点,就是从交易性能对比两类账户的实现。

UTXO无需维护账户状态(即无状态)并可极大并行,适于提高交易性能。但它并不是一种通用数据结构,而是一种针对交易业务而高度定制的数据结构,对于运行(图灵完备的)智能合约存在大量局限性,这也正是Gavin Wood另辟蹊径提出Account模型的原因。其次,UTXO的每次初始化需要全部的历史交易日志,这种模式在可预见的将来完全不适用于具有大量流水的大型交易系统。一些链考虑引用快照机制降低维护全部历史交易的代价,但是这增加了链的维护代价,主要是防止双花等账户安全性需要重新考虑。

以太坊的账户(Account)模型(包括外部和合约账户)的状态保存在节点中,即MPT方式存储。只需nonce++即可实现交易,可编程性好。虽然账户模型具有更好的设计,易于编程实现,但是出于性能考虑,一些链考虑用函数式编程语言实现基于对象设计的账户模型(正如后面介绍的比原链)。

混合两者的基本考虑就是,既有UTXO的可并行性,又具有账户模型的数字资产发行和管理功能。

二、对比区块链系统

针对用户的关注,我们择取了三种代表性国产区块链系统。它的基本信息为

2.1 比原链(Bytom blockchain)

定位为一种专用于支持多资产管理的公链,意在解决原子资产的不可复制性、资产映射的合规性和资产在链上链下的高效流通等问题。

2.2 BUMO

公链,宣称为“面向全行业的基础公链,可实现商业级的区块链部署”,“在保证系统稳定安全的前提下,能够达到2000TPS”。

2.3 智品链(Zeepin Chain)

主要应用是解决文创产品的版权交易问题。已与NEO建立生态战略伙伴关系,开展多项战略合作。

三、比原链

3.1 项目要点:

  • 账户同时管理多种资产。
  • 结合DPoS和BFT提出一种新的共识算法BFT,宣称实现了较高的交易吞吐量,具备了可扩展性和安全性。
  • 提出了一种支持异构区块链跨链操作的交易架构——互联链(InterChain),在实现智能合约和 Dapp 跨链功能的基础上,提供了良好的隐私保护机制(基于 zkSNARK零知识证明算法)。
  • 良好的开发者和用户社区。

3.2 账户模型

账户对多种资产的高性能管理是比原链的一个卖点。

比原链的账本在设计上属于合约层的概念。为支持多资产管理,每个账户在账本层对应一组BUTXO(比原链的UTXO数据结构),账户下所有BUTXO资产数目总和对应该账户的资产。比原链维护一个全局的BUTXO池,每一种资产都有相对应的一种树型结构的轻量级世界状态,每个区块维护交易哈希和BUTXO哈希(Merkel树根)。每一种资产在执行交易时,会实例化相对应的轻量级虚拟机执行,支持多种不相关的资产并行运行。

四、BUMO

4.1 项目要点:

  • 创新的共识算法:(据白皮书介绍)BUMO 提出一种新的共识算法 BCP(BUMO Consensus Protocol),能够实现较高的交易吞吐量、可扩展性和安全。 BCP 算法是一种动态记账人选举的新算法,每个用户将会被分配权重优先级,优先级可以以用户收益中的权益来衡量,以此为基础形成一种基于用户权重的记账人节点选取机制。
    • 相比于 PoW ,可有效提高交易吞吐量并避免分叉;
    • 相比于 BFT,不需要一套固定的服务器组同时能够抵抗女巫攻击;
    • 相比于PoS,避免恶意领导者单独形成一个分支,降低影响;
    • 相比于 Ripple /Stellar共识算法,无需用户配置复杂的信任策略。
  • 高性能:(据白皮书介绍)正是这种共识机制让BUMO达到快速、好用、安全、稳定的效果,这也使得BUMO的TPS水平到2000笔每秒,超过1500笔每秒的商用水平,其交易确认时间也是秒级。

4.2 账户模型

基于以太坊账户模型,支持图灵完备智能合约。BUMO的账户设计如图,其中也支持多种资产,保存为账户状态。

账本数据也使用MPT存储。定义了多种账户操作。从白皮书看,BUMO账户设计上与以太坊基本一致。

五、智品链

5.1 项目要点:

  • 从该项目的公开资料看,应该是基于NEO的一个实现,尽管NEO是使用C#编写的。

5.2 账户模型

白皮书中未提供更多细节。从对智能合约支持等特性看,应该是采用了以太坊账户模型。

六、汇总对比表

总结一下,下表给出了三种国产链的基本对比。主要参考各自的白皮书和Github代码库。

对比项 比原链 BUMO 智品链
开源
项目活跃程度 十分活跃,贡献者最多,star最多 活跃,有一定数量贡献者和Star  不活跃 
开发语言 golang C golang
账户模型 基于UTXO的BUTXO 以太坊账户模型 以太坊账户模型
共识机制 考虑对矿机ASIC优化的POW,并引入Tensority算法 称为“BCP-NG”,据白皮书称有大量创新,应该是DPoS的一种变体。 称为“GBFT-POS”,POS机制的变体
智能合约 提出了equity智能合约语言和BVM虚拟机 图灵完备的区块链只能合约开发环境,据称支持多种语言开发。
跨链 XRelay,支持对指定“侧链”的跨链交易 支持主链-子链的双层多链结构,支持多个不同主链连接起来,可扩展支持同构和异构的主链。 未见提及
社区 发展很好 一般

Whiteblock指出EOS是一种分布式数据库而非区块链

摘要: 近期,区块链测试方Whiteblock发布了一份测评报告,其中的内容对EOS极为不利,并将EOS称为一种伪装成区块链的“分布式同构数据库”。Whiteblock对EOS协议的多个方面进行了剖析,得出的结论认为EOS在安全性上存在着严重的缺陷,而且网络性能要远低于其宣称所能达到的性能。就此,加密货币新闻网站CCN(CryptoCoinsNews)独家专访了Whiteblock的CTO。

正文:

近期,区块链测试与研究企业Whiteblock Inc.发布了一份测评报告,其中的内容对EOS极为不利,并将EOS称为一种伪装成区块链的“分布式同构数据库”。在这篇名为《对EOS架构、性能和经济性的分析》(“EOS: An Architectural, Performance and Economic Analysis”)的报告中,Whiteblock对EOS协议的多个方面进行了剖析,得出的结论认为EOS在安全性上存在着严重的缺陷,而且网络性能要远低于其宣称所能达到的性能。

惊人的发现

根据这份由Whiteblock研究团队的Brent Xu、Dhruv Luthra、Zak Cole和Nate Blakely等人撰写的报告,被冠以“以太坊终结者”的EOS网络在安全性和协议上存在着多处令人震惊的失败,并会对多种由EOS网络提议的应用造成致命性的破坏。

EOS于今年九月上线以来的两个月时间中,Whiteblock团队持续测试了EOS网络的交易通量是否能达到其所宣称的性能。此外,测试中还包括了EOS对不利网络条件的响应情况、如何应对可变交易率和规模、平均交易完成时间,对分区的容忍度及容错能力。测试结果远非令人满意。

Whiteblock在11月2日就EOS测试而发布的新闻稿中直言不讳地指出:

“EOS并非一种区块链,而是一种分布式同构数据库管理系统。其中最显著的差异在于,EOS的交易并非加密认证的。从本质上看,EOS代币和RAM市场就是一种云服务,网络以黑箱方式向用户提供计算资源承诺,用户通过授信访问。由于EOS在区块生成者的计算能力上缺乏透明度,因此EOS并未提供问责(accountability)机制。”

Whiteblock在“切实可行”的网络条件下测定了EOS的实际通量,结果严重低于EOS市场宣传材料中所宣称的通量。此外,EOS网络中缺失拜占庭容错机制,并存在重复性共识失败这样的基本安全问题。

据CCN今年六月的报道,EOS在其网络主版本发布之后一周内,就因一起由区块生成者导致的事故而陷入争议,该事故导致很多人对EOS网络去中心化的程度产生质疑(译注:指今年七月EOS因私钥生成工具存在安全隐患,创建的私钥被黑客发现漏洞并实施了“彩虹”攻击,导致账户的数字资产被盗,造成了上千万数字资产的损失)。Whiteblock的测试报告确证了人们担忧问题的存在,这将严重地影响到EOS的价值。

Whiteblock对EOS的整体网络给出了结论性意见:

“我们的研究结果表明,EOS所宣称的性能是不准确的。EOS系统是构建在不正确模型的基础之上,这种模型并非真正的去中心化。”

CCN对Whiteblock CTO的独家专访

在测试报告发布后,CCN采访了Whiteblock CTO Zak Cole,独家详细解读了这份关于EOS社区和区块链生态系统的报告内容。

CCN: 你们团队给出的研究结论称EOS交易未经加密验证,因而应称为一种分布式同构数据库而非区块链。这对于EOS生态系统意味着什么?这是否会显著地改变EOS承诺实现的目标(即成为以太坊杀手)。EOS投资者和用户应对此感到担心吗?

Cole: 我希望我们的研究结果能为社区提供健康的讨论基础,而不是挑起竞争对手之间的某种政治战争。我认为,要建立可实现EOS初衷的简明路线图,需要对EOS生态系统的长期目标进行评估。鉴于以太坊和EOS两者间存在着本质上的差别,对比这两个系统是完全没有意义的。前者是一种由加密认证支持的去中心化P2P网络,而后者则是一种优化的分布式数据库,功能上更类似于一些通用云计算平台提供的IaaS服务产品。

Whiteblock是区块链的拥趸,而并非EOS或以太坊等某个特定系统的拥趸。我们研究的目的并非是要证明各系统间孰优孰劣,而是要为社区在建立高性能和功能系统过程中提供可参考的、客观并科学的分析。Whiteblock团队还将为在下周在旧金山召开的EOS黑客马拉松(Hackathon)提供指导。我们的唯一目标是协助构建这样的桥梁,将区块链技术从一种边缘的科学领域,过渡到一种可提供实际应用并塑造未来去中心化世界的可行解决方案。这正是我们推出Whiteblock测试框架的原因所在。

社区所需要的开发工具,应该可以提供透明并客观的性能数据。这些性能数据将甄别事实情况和营销用语,进而掌握这些正在构建中的系统的功能。EOS的最根本性问题在于,它当前无法提供并且在短期内也无法提供其所宣称的通量。EOS根本无法支撑其所提出的数十亿美元愿景。其中尚需再做大量的工作,我希望EOS团队能够兑现所有的承诺。无论如何,它都是对分布式计算的一种非常有意义的实验性探索。

EOS投资者和用户则并不必过分担心,除非他们希望通过投机性下注的方式从游离于监管之外的新兴技术市场中获利。

CCN:你们的研究还指出,EOS的实际通量要显著地低于其所宣称能达到的指标。在外行人看来,这对用户和DApp开发者意味着什么?

Cole: 在选定最适合构建DApp的平台时,开发人员应首先对自身优先考虑的事项做出评估。如果想要尝试实现去中心化P2P交易逻辑的功能,那么就应确保所选定的系统事实上能够提供执行该操作所需的功能。如果想要实现高交易通量,那么就应该考虑使用Shopify或Stripe等的现有的支付网关会存在哪些问题。当然,如果传统的客户-服务器架构同样适用,坚持选用此也无可厚非。

另一项需注意的重要事情是,EOS并非真正地无需交易费用。相反,交易成本会转移给DApp开发人员,使得运行这些DApp的成本非常高昂。我们在很多软件系统中已经看到,这将会创建类似于苹果的App Store这样的应用市场,并且用户最终支付的费用可能会远高于预期。我不知道大家是否还注意到,随着延迟和用户数量的增加,成功处理的交易显著地下降了。这不仅仅是因为通量,而是还有一些更重要的因素在起作用。

CCN:是否EOS从本质上看就会给用户带来安全风险,或者说EOS的上述缺点是可修复的?

Cole: 我认为,EOS系统当前的确存在一些固有的安全漏洞。对于如何确保区块生成者的正常行为方式上,EOS并没有有效地实现博弈论,或采用一些额外的算法机制,也无法保证用户当前存储的资产可供未来使用或访问。EOS共识模型的所有价值都基于区块生成者的能力,而这些区块生成者是由代币持有者投票选出的,并代表他们执行功能。一旦无法阻止区块生成者本身出于自己的利益考虑而做出投票,那么这种共识是否还存在意义?即便存在,EOS也没有提供可以管治区块生成者行为的函数、加密、计算或其他手段。这个问题是非常明显的,完全不需要通过我们为期三个月的研究项目才能发现。

话虽如此,但这些缺点是可以修复的。但如果修复了上述问题,EOS可能会变得与Dash或Syscoin等许多主节点系统(masternode)毫无二致。

CCN:该研究受到了ConsenSys的资助,那么其中是否存在着一些利益冲突? [CCN编者注:ConsenSys是一家对ETH应用做了大量投资的以太坊开发工作室]

Cole: ,我们的研究还得到了除ConsenSys之外大约二十多家机构的资助。例如,Bitshares的联合创始人及Dan Larimer的前合伙人Bo Shen也提供了资助,但Bitshares的大部分技术均是基于EOS的。ConsenSys也资助了一部分研究计划,这并不影响科学的过程,尽管这应被视为争议性问题。同样,我们对以太坊也开展了相同的测试,并指出了以太坊的一些不足之处。以太坊社区接纳了我们的研究结果,并支持我们进一步开展研究。目前,我们已经与数十种区块链系统开展了合作。我们的测试目的并非是要指出某个系统的优点,这不是选美比赛。为了建立更有效和更高性能的系统,我们应该做到客观和透明,找出系统存在的弱点,帮助系统在进一步设计过程中考虑到这些问题并作出优化措施。如果EOS社区对此类性质的测试和观察选择抵触的态度,那么其整个生态系统都将注定会失败,并且肯定永远无法实现其所宣称的规模。

我们的研究报告是公开提供的,其中也指出了以太坊在安全和性能上存在的多个显著问题。

查看英文原文: Interview: Researcher Argues EOS isn’t a Blockchain — Just a Distributed Database

ChainSQL:基于数据库操作日志的区块链数据查询

总所周知,区块链是一种去中心化链接数据结构,本质上设计用于防止数据篡改。但如果用户想要查询存储在区块链中的数据,实现非常繁琐并且效率不高。中科院深圳先进技术研究院的一项研究ChainSQL意图解决此问题。项目的论文“A Blockchain Database Application Platform”于今年九月发布在预发表网站ArXiv上。项目的网站是http://www.chainsql.net/,可以从官方网站下载白皮书,开源地址是https://github.com/ChainSQL。目前已有66颗星。

简介

ChainSQL考虑将分布式数据库与区块链结合在一起。数据库技术经过四十多年的发展,是提供快速查询处理的主要手段,但是在实现有效数据查询的同时,数据可靠性问题并非数据库设计中的主要关注点。ChainSQL为区块链提供了良好设计的数据结构,实现了去中心和、分布式、防篡改、可审计、多活的数据存储,并提供了数据层面的灾难恢复机制。下面我们基于论文介绍ChainSQL的关键特性实现,具体特性可参考ChainSQL白皮书。

ChainSQL是一种基于区块链的日志数据库,它组合了区块链技术和分布式数据库的特性。ChainSQL主要针对两种用例,一种是将企业应用与数据库系统结合在一起的多活数据库中间件,另一种是连接数据库生产阶段和灾难恢复节点的数据库灾难恢复中间件。这两类中间件用来都是用来连接企业应用与底层数据库。ChainSQLde主要亮点包括:一、提供了访问个人用户数据的认证功能,提高了数据的安全性。二、交易存储在区块链中,而实际数据存储在数据库中。三、多对一的灾难恢复架构,支持单一备份中心为多个生产站点提供备份。四、易于与MySQL、Oracle、DB2等现有数据库集成,并使用API编程。五、数据库操作历史存储在区块链中,支持审计。这些亮点使得ChainSQL不仅提供传统的数据库实例,而且提供区块链的安全性。

结构

从结构上看,ChainSQL由区块链网络、分布式数据库和用户组成。基本结构如下:
1. ChainSQL使用区块链网络Ripple。Ripple支持快速发布和验证交易,它采用一种称为UNL(Unique Node List)的方式避免了区块生成中使用过量的计算。
2. 在区块链节点基础上配置了分布式数据库,数据库使用区块链同步,并使用了区块链的read操作。
and facilitates quick database like blockchain ‘read’ operations.
3. 提供了三种查询方式:直接查询区块链网络、创建用于快速存取的数据库区块链节点,以及结合二者的查询。示意如图一所示。


图一 ChainSQL提供的三种查询方式示意图

ChainSQL的主要组件包括:

  • 应用接口(Application Interface):访问区块链需要通过API,API提供了应用的接口,使得对用户而言区块链的交易命令类似于数据库操作。ChainSQL API支持多种编程语言,实现了灵活性和可应用性。

  • 数据库操作(Database Operation):数据库操作是在实时环境中执行的。区块链网络将交易数据直接传递给相应的数据库,并由数据库处理。共识机制用于验证交易的有效性,实现为:在ULN模式中指定了验证交易的一组区块链网络节点。交易一旦通过验证,就将发送到区块链网络实现共识,进而写入到数据库中。一旦共识失败,数据库操作回滚。整个过程可在数秒内完成,因此从用户角度看是近实时完成的。

  • 数据库恢复(Database Recovery):在区块链网络中,一个节点配置了用于恢复的数据库,该数据库中存储了区块链中的交易,或者是重建数据库的操作过程。区块链网络支持阶段为存储区块链网络全部交易的完全记录节点,或是存储部分交易的部分记录节点。

用例

ChainSQL的启动运行情况如图二所示。这里给出两个基本用例。


图二 ChainSQL启动过程示意图

多活数据库中间件

多活数据库是一种连接企业应用和底层数据库的中间件。底层数据库可以是任何传统关系型数据库,以及NoSQL数据库。区块链维护了操作日志,其中记录了数据库中所有的数据定义和操作,是不可修改的。操作日志可用于重新生成数据库,因此可用于数据库审计。用户应用调用ChainSQL API获取适用于区块链网络的交易数据,并将交易数据发送给网络节点。网络节点在将交易数据发送给网络中其它节点之前,会对新到来的数据做认证并验证。网络节点通过对以交易形式提交的数据达成共识而打包形成区块。如果达成共识,网络中的每个节点将以区块形式保存同一数据。每个节点配置了一个数据库,节点将数据发送给数据库,并同步数据库操作。一旦节点出现故障,用户可无缝地切换到另一个节点。该机制保障了零恢复时间和实时多活数据库部署。在恢复时,故障节点根据最近的监测点恢复。多活数据库中的一个关注点是数据在网络中传输的安全性。ChainSQL提供了对称和非对称加密机制,用户可选择适当的安全机制。多活数据库的另一个主要关注点是可扩展性。新节点可以自动地从已有节点获取操作日志,并参与到构建共识和同步数据写操作中。

多灾难恢复中间件

多灾难恢复是一种连接企业应用中数据库生产节点和灾难恢复节点的中间件。如上所述,在数据库中,用户操作记录在操作日志中。在灾难恢复时,第一步是实现区块链网络的共识,该步骤可保证所有操作日志数据的一致性。一旦生成了新的区块,指定的备份节点将读取区块并将其发送给灾难恢复中心。恢复中心使用交易数据执行数据库备份。这样,如果在生产环境中有节点故障,用户可切换到恢复中心,备份节点提升为生产节点。恢复中心在10秒内可提供操作日志,对故障生产节点重新执行基于用户设定恢复点的恢复操作。

结论

结合区块链和数据库,提供了可靠的数据管理和恢复机制,这使得ChainSQL在金融和供应链等具有数据可靠性要求的领域。当前开源的ChainSQL提供了一个基础平台,可进一步研究数据分析能力、数据分片技术、验证技术等。

区块链技术基础知识–加密与哈希

区块链的世界中我们经常能够听到加密、哈希、签名等字眼,那么他们之间到底有什么区别和关联呢,下面我们来一一解读。

基本概念

加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。

哈希(Hash)是将目标文本转换成具有相同长度的、不可逆的杂凑字符串。(或叫做消息摘要)

加密和哈希的区别

首先,加密、哈希和数字签名都属于加密学的范畴。我们这里所说的加密和哈希指的是加密/解密算法和哈希算法。

哈希算法往往被设计成生成具有相同长度的文本,而加密算法生成的文本长度与明文本身的长度有关

例如,设我们有两段文本:“bitcoin”和“eth”。两者使用某种哈希算法得到的结果分别为:“140864078AECA1C7C35B4BEB33C53C34”和“8B36E9207C24C76E6719268E49201D94”,而使用某种加密算法的到的结果分别为“Njdsptpgu”和“Hpp”。可以看到,哈希的结果具有相同的长度,而加密的结果则长度不同。实际上,如果使用相同的哈希算法,不论你的输入有多么长,得到的结果长度是一个常数,而加密算法往往与明文的长度成正比。

哈希算法是不可逆的,而加密算法是可逆的

这里的不可逆有两层含义, 一是“给定一个哈希结果R,没有方法将E转换成原目标文本S”, 二是“给定哈希结果R,即使知道一段文本S的哈希结果为R,也不能断言当初的目标文本就是S”。其实稍微想想就知道,哈希是不可能可逆的,因为如果可逆,那么哈希就是世界上最强悍的压缩方式了——能将任意大小的文件压缩成固定大小。

加密则不同,给定加密后的密文R,存在一种方法可以将R确定的转换为加密前的明文S。

常见的加密算法

对称加密

指加密和解密使用相同密钥的加密算法。

常见的对称加密算法有: DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6和AES

非对称加密

指加密和解密使用不同密钥的加密算法,也称为公私钥加密。

常见的非对称加密算法:RSA、ECC(椭圆加密)、Diffie-Hellman、El Gamal

常见的哈希算法

常见的哈希算法有MD4、MD5、SHA1、SHA256和SHA512 等

数字签名技术

数字签名技术是使用非对称加密算法来验证数据发送者的方式。

简单举例如下:

发送报文时

发送方首先用接收者的公钥,对数据进行加密。

然后再将加密后的数据使用发送者的私钥进行加密(签名动作)

然后将数据发送给接受者

接收报文时

接收者接收到报文以后首先尝试使用发送者的公钥进行解密(签名验证)

如果解密失败则这个数据不是发送者发过来的,直接抛弃

如果解密成功,再使用接受者的私钥对数据进行解密,这样就可以看到明文了。

数据完整性校验

数据签名技术可以保证数据源没有问题,数据加解密可以保证数据被窃听者获取也能防止窃听者知道数据的内容,要做到数据的安全传输,还需要确定收到的数据没有经过窃听者的篡改,这就涉及到数据的完整性校验。

数据完整性校验一般使用哈希算法和密钥对数据进行哈希得到数据的一个哈希值,然后将该哈希值和数据一块发送给对方,对方收到数据之后,对数据使用相同的哈希算法和密钥进行哈希得到哈希值,如果得到的哈希值和对方发过来的相同,那么就说明数据没有经过篡改。

基于区块链的安全数据库交易日志管理

大家在看区块链相关技术时,难免会产生一个疑问;“传统技术都能解决的问题,我们为什么还需要区块链”。

对此问题,很多业内专家都从很宏观的角度给出了解读。我们注意到,一些研究从“区块链技术作为传统技术有益和必要的补充”的角度,考虑了分布式系统、数据库、FinTech等领域。下面,我们将对其中一些代表性研究做出分析。

首先,我们看的是收录在2018年Dependable Computing Conference上的一篇论文,“A Prototype Evaluation of a Tamper-resistant High Performance Blockchain-based Transaction Log for a Distributed Database”,由罗马大学和南安普顿大学的研究人员合作完成。

确保数据完整性是很多现代业务的基础需要。在传统数据库中,交易历史以重做日志文件的形式记录。一旦出现问题时,数据库使用重做日志恢复交易。但是,重做日志的安全性一般由操作系统保证,易于被恶意篡改和伪造,进而破坏数据的整体完整性。区块链提供了强大的数据完整性保证,但是其在性能上的局限性限制了它在现实中的广泛应用。在这项研究中,研究者提出了一种多层区块链架构,用于维护分布式数据库的重做日志。具体而言,第一层为一种高速区块链,实现共识算法,并将安全相关机制导向基于PoW的第二层区块链。研究进而提出了使用拜占庭容错共识和DHT(分布式哈希表)改进可用性和可扩展性上的一些考虑。

基本架构

该研究提出了一种称为2LBC(2-layer blockchain architecture)的架构,如图一所示。其中,第一层实现为私有(permissioned)链,第二层实现为公开链。第一层使用基于领导者轮换机制的高速共识算法,即将时间分成多轮,各位矿工根据固定的公平策略在每轮上轮流坐庄,担任领导者。交易顺序由领导者确定,交易的账本中记录的是重做日志的具体内容。和其它区块链一样,交易一旦经由各方使用非对称加密算法签名,将存储在各方的账本副本中。在典型的联盟应用中,区块链各方用户称为联盟成员。这样,每个联盟成员向2LBC提供一位矿工。该矿工是网络中的一个代表性节点,保持数据库和账本的副本,并维护区块链的完整性。第二层实现为使用PoW确保完整性的公链。第一层区块链的哈希周期性地通过导向管理器(图一中的Anchoring Manager)存储在第二次区块链中。PoW确保了哈希的不可变性。如果有恶意矿工试图修改账本中的重做日志,将导致第二层中存储的哈希不一致。


图一 用于联盟应用的2LBC架构

具体实现中,论文使用Java实现了第一层区块链。矿工加入P2P网络,通过由客户操作、序列号或是时间戳等工作负载组成的交易进行交流,并由矿工的签名认证。矿工可以相互间直接通过JavaRMI通信,也可以使用JGroup (http://jgroups.org/)通过广播机制通信。客户在操作数据库时,通过JavaRMI向所有矿工发送两种操作

  • set(k,v),将值v指定给键k,返回值为布尔值,表示客户是否确认。
  • get(k),通过返回交易,获取键k所存储的值。

共识的实现需要针对2LBC做一定修改。对于给定set(k,v)操作,矿工必须达成共识,才能向客户给出确认。共识基于三阶段提交协议(3-PC)。客户向所有矿工广播一个set操作,矿工使用Verify Manager验证set操作的正确性,并在通过验证后将该set操作添加到队列中。一旦当前指定的领导者提出和set相关的交易,其它矿工从自身队列中移走该set操作,并广播自己签名,构建相应验证。当验证包含了所有的签名,所有矿工将交易提交到自身的账本副本中,并触发对数据库副本的更新操作。对于get操作,每位矿工向客户符合最近set操作相关的交易。客户可以从最近交易中获取值v,并使用矿工的签名验证其正确性。一旦当前领导者坐庄的时间到,它以一种特殊交易的方式触发领导者轮换事件。退位的领导者在第一层区块链上计算SHA-1哈希,并将公正交易发送给第二层上的指定验证智能合约,智能合约使用以太坊实现。

实验采用6个节点的虚拟机网络。对于set操作,图二(a)和(b)分别给出了通量和延迟情况。从结果看,通量要显著高于以太坊,延迟显著低于以太坊。整体共识算法需要矿工对每个操作交换所有签名,因此在240交易/秒左右出现不稳定的情况。此外,领导者轮换对于队列操作引入了一定的开销,比平时高50%左右。图二(c)和(d)分别给出了get操作的通量和延迟情况。get操作并不受领导者轮换的影响。


图二 对2LBC架构中set和get操作的通量和延迟的实验评估情况

性能改进

由上可见,基本2LBC架构的主要问题在于可用性和可扩展性。具体而言,由于共识上的考虑,在架构中添加节点,并不会直接提升架构的整体性能。

为解决可用性上的局限性,初步考虑基于PBFT实现拜占庭容错算法。一般情况下,为容错f个矿工,需要3f+1可靠的矿工。这可以降低验证可信矿工的代价。在提交交易时,可以考虑等待至少f+1可信签名才能提交一个交易。为解决可扩展性上的局限性,初步考虑对私有(联盟)链实现数据分片。这里可以引入基于DHT的账本,其中每个矿工基于键空间分区,每个键具有一个可配置的副本因子实现容错。

结论

2LBC作为一种双层架构的区块链,在保证高性能、高安全性的同时,提供了去中心化环境中的强数据完整性保证。此外,进一步结合拜占庭容错和DHT数据分片,可以改进架构的可用性和可扩展性。该架构适用于与传统数据库应用结合,为重做日志提供完整性,进而提高数据库的安全性。