入门以太坊与Solidity安全

入门以太坊与Solidity安全

前言

本文持续更新,为笔者学习DApp开发/合约审计方向的入门笔记。

笔者在行文前完成了CryptoZombies 的基础内容,对区块链相关基础概念仅有浅尝辄止的了解,参考书籍《精通以太坊:开发智能合约和去中心化应用》进行学习。

测试网络与简单的Faucet 合约

在Chrome 应用商店中安装钱包MetaMask,注册第一个以太坊账户,切记保存好由12个英文单词组成的助记词,它们代表了你的账户的唯一私钥。

完成注册后,即可切换到测试网络,此时以太坊的 Rinkeby、Ropsten 和 Kovan 测试网络已被弃用[1],我们使用Goerli 网络,并获取一定的测试以太币(无价值)

获取测试币

获取Goerli 测试币有三种方式:

  • 直接在Goerli Testnet Faucet 申请,但小水管基本上一直处于堵塞状态,无果
  • 通过推特申请,tweet 你的钱包地址,然后在Faucet 上申请,条件是推特账号必须注册时间足够长且有一定数量的基础tweets,无果
  • Goerli PoW Faucet 通过浏览器在线挖矿获得,根据电脑性能不同,一般一小时以内即可得到1 以太的测试币,这种方式最为有效。

第三种方式需要注意的是,一定不要挂代理,挂代理会警告并降低你的收益。挖矿速度只取决于你的CPU性能及核心数(线程数),我的笔记本是I5 1240p(性能稍低于12500h,高于11代标压笔记本I7),16个大小核线程拉满后大约有600的速度,1小时即可得到约1.5个测试币。


METAMASK钱包


实现一个Faucet 智能合约

Faucet: 向任何提出申请的钱包地址发送测试以太币,并且会被定期充满测试币。

使用Web端IDE Remix 进行开发、编译与部署。


remix 开发


// 声明Solidity版本,需要注意的是书中代码已不适用于新版Solidity
pragma solidity ^0.4.22;
// 声明一个合约,类似其他语言中的Class
contract Faucet {
// 声明一个转账方法
    function withdraw(uint withdraw_amount) public {
        // 调用这个方法的条件是传入参数值必须小于10e12,不满足则报错
        require(withdraw_amount<=10e12);
        // 向调用合约的账户地址转入参数值个wei
        msg.sender.transfer(withdraw_amount);
    }
    // 默认回调函数
    function() public payable {}
}

编写好并配置完成后即可进行编译,编译成功的以太坊主机字节码如下:

{
    "linkReferences": {},
    "object": "608060405234801561001057600080fd5b5060ef8061001f6000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d146041575b005b348015604c57600080fd5b50606960048036038101908080359060200190929190505050606b565b005b60648111151515607a57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f1935050505015801560bf573d6000803e3d6000fd5b50505600a165627a7a723058208b1b909e6b1319fe21cc49339518323a3e697a284d131cb31c0e693f3105be7e0029",
    "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0xEF DUP1 PUSH2 0x1F PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH1 0x3F JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0x2E1A7D4D EQ PUSH1 0x41 JUMPI JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH1 0x4C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x69 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH1 0x6B JUMP JUMPDEST STOP JUMPDEST PUSH1 0x64 DUP2 GT ISZERO ISZERO ISZERO PUSH1 0x7A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH2 0x8FC DUP3 SWAP1 DUP2 ISZERO MUL SWAP1 PUSH1 0x40 MLOAD PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 DUP9 DUP9 CALL SWAP4 POP POP POP POP ISZERO DUP1 ISZERO PUSH1 0xBF JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 DUP12 SHL SWAP1 SWAP15 PUSH12 0x1319FE21CC49339518323A3E PUSH10 0x7A284D131CB31C0E693F BALANCE SDIV 0xbe PUSH31 0x290000000000000000000000000000000000000000000000000000000000 ",
    "sourceMap": "26:203:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;26:203:0;;;;;;;"
}

成功后可进行部署


remix 部署


环境选择Metamask(即Injected Web3),连接成功并授权后自动获取账号信息

配置完毕即可进行部署至Goerli 测试以太网络,部署合约需要收取Gas


钱包交易


部署成功后,可通过Metamask 向我们的Faucet 合约转账,而后在Remix IDE中选择调用withdraw函数,在Etherscan 中即可查询到我们的交易与合约调用过程,注意,每次调用都需要收取Gas。


Etherscan


如此,我们就完成了一次在去中心化的世界计算机上的Hello World。

以太坊协议&密码学基础

以太坊是一个开源项目,以太坊客户端是其协议规范的实现,不同的客户端可以在相同的以太坊网络上实现操作和交互。

拥有多个独立开发的客户端已被证明是防御网络攻击的绝佳方法,特定客户端实现策略的漏洞只会影响到那些要修补漏洞的开发者,其他客户端仍可以保证以太坊网络几乎不受影响的继续运行。

以太坊协议有六种主要实现,分别由不同语言编写:

  • Parity,Rust
  • Get,Go
  • cpp-ethereum,C++
  • pyethereum,Python
  • Mantis,Scala
  • Harmony,Java(不是鸿蒙)

关于以太坊节点

大量在地理上分散与独立运行的全功能节点是以太坊的重要组成部分,确保了以太坊网络的持续健康、弹性与放审查机制(去中心化),然而,运行一个全功能节点需要消耗大量的硬件资源与网络带宽,现如今(截止2022.10.9 19:10分),每隔12.1s即会诞生一个72.413KB大小的新区块,以太坊区块数据量已经达到了345.17GB,为了实时同步全功能节点的区块数据,需要能够进行高速、频繁的IO操作的大容量固态硬盘与高速网络带宽,且所需硬件资源在可见的增长,故有远程调用以太坊的客户端与公共测试网络、本地区块链模拟器可供开发者选择。

全功能节点

全功能节点的重要意义是帮助其他新加入网络的节点获取运行所需的区块数据,为用户提供独立和权威的交易合约验证。

事实上,个人PC不适合充当以太坊的全功能节点,远程客户端最为合适,Metamask就是运行于Web浏览器的远程客户端。

公共测试网络

如Goerli 测试网络节点,数据量比全功能节点小的多,数据同步时间也更快,使用测试以太币没有成本,弊端是无法在测试网络上检测安全性。

本地区块链模拟器

可以使用Ganache(testrpc)来创建单一实例的本地区块链(私有链)

以太坊中的密码学

以太坊的技术基石,主要是以密钥和地址的形式来控制资金所有权的公钥密码学PKC

基础部分略去吧,可以参考我博客中其他有关公钥密码学与数字签名的文章/笔记

  • 公钥密码
  • 基于公钥密码的数字签名
  • 椭圆曲线密码学
  • 哈希消息摘要
  • 密钥生成

椭圆曲线密码学[2]

每个以太坊公钥都是椭圆曲线上的一个点,也就是说,每个公钥都是一组一组x、y坐标,这个坐标正好满足椭圆曲线方程,公钥由两个经过私钥单向计算得来的数字组合在一起,通过私钥计算得到公钥是非常容易的,但是很难通过公钥算出其私钥(陷门函数)。

椭圆曲线密码学是基于离散对数问题的公钥密码学,与之相对的RSA算法则是基于大整数分解的困难问题


椭圆曲线


以太坊使用与比特币相同的椭圆曲线算法,由NIST规定,称为secp256k1

该椭圆曲线由以下函数定义的特定椭圆曲线生成 y^2 = (x^3+7)\quad over (F_p) 上面是有限域形式的函数表达,F_p 表示位于素数阶p的有限域中,其中 p = 2^{256}-2^{32}-2^9-2^8-2^7-2^6-2^4-1 是一个非常大的素数,同时该椭圆曲线函数还可以表示为 y^2=(x^3+7)(mod\quad p) 下图为secp256k1椭圆曲线部分点的在有限域内的分布


secp256k1


然后,我们开始进行有限域上的计算准备,首先定义“0”为椭圆曲线上的一个无穷远点,然后定义加法为两点之间连线与椭圆曲线的唯一交点,乘法即为连续的加法。完成这些工作后,有限域的代数计算就映射到了椭圆曲线的几何问题上

生成公钥

以太坊的公钥是通过对私钥使用椭圆曲线的乘法运算得来的,而该有限域上的乘法运算基本上是不可逆的: K=k*G 其中K是公钥,k是256位私钥,G称为生成点,对于secp256k1来说,G的具体值为 G=(5506626302227734366957871889516853432\\\\6250603453777594175500187360389116729240,\\\\ 326705100207588169780830851305070431\\\\84471273380659243275938904335757337482424) 经过计算得到的K点坐标,经过序列化EC公共编码后,即为以太坊所使用的公钥。

以太坊中的哈希函数

哈希函数是一个可以将任意长度数据映射成固定长度数据的数学函数。

著名的哈希函数如MD5,SHA256等等,相信大家应该都很熟悉了,本文不再介绍其原理。在零信任的以太坊中,相比于加密算法,单向哈希函数才是真正的主力。

Keccak-256

本应成为SHA-3标准的Keccak-256在经过NIST调整后,被披露的文档指出NIST可能受到了NSA的影响在其中加入了后门,故以太坊基金会决定使用原生的Keccak-256算法而不是NIST修改后的SHA-3算法。

注意区分Keccak256与FIPS-202 SHA-3

使用Keccak-256算法对公钥进行计算即可得到该私钥所对应的公开以太坊地址

钱包

存储和管理用户密钥的系统,基于以太坊去中心化应用DApp的入口。

钱包中仅保存了密钥,以太币或其他代币都保存在以太坊区块链上,因此检查钱包里的钱无需通过主人的钱包,钱包相当于包含了一堆透明玻璃柜的钥匙的钥匙圈。

  • 非确定性钱包,每一个私钥都是通过不同的随机数相互独立地生成的,称为JBOK钱包,只是个钥匙圈。
  • 确定性钱包,所有的密钥都是从一个主密钥衍生而来,主密钥即为种子密钥,如果密钥的派生方法为树形结构,称为层级式确定性钱包,简称HD钱包。此处涉及密码工程中密钥管理的知识。

层级式确定性钱包

层级确定性钱包(HD Wallet)


一个私钥生成若干子密钥,每一个子密钥都可以推导出一系列孙子密钥。

好处如下:

  • 树形结构可以用来表示组织结构的含义,例如一组密钥专门用于收款,另一组专门用于付款
  • 可以创建一系列公钥而不需要经过私钥,此时HD钱包可以放在不安全的服务器上,无需操作私钥。

助记词

将私钥映射为一串无规律的单词

根据BIP-39标准,可将128bit的随机密钥映射为2048个英语单词中的12个单词。

交易

交易是由外部账户发出的经过签名的消息,通过以太坊的网络传播,由矿工记录在区块链上。以太坊是一个全局单体状态机,交易是唯一能让这台状态机向前推进并改变状态的事务,合约由交易触发。

交易的结构

一串打包在一起的二进制数据,包含:

nonce
// 随机序列编号,由创建该交易的外部账号提高,防范重放攻击
gas price
// 交易发起方愿意支付的gas价格
gas limit
// 交易发起方愿意支付的最大gas数量
recipient
// 目标以太坊地址
value
// 发送给目标地址的以太币数量
data
// 附在交易中的可变长度数据
v,r,s
// 椭圆曲线数字签名三个部分

不包含交易发起方的地址,因为其地址可由椭圆曲线数字签名v,r,s计算出,不包含在交易数据包之内,从而节省区块链数据空间。

数量随机数 nonce

nonce(number of once):一个数值,等于这个地址发出的交易数量,当这个地址与合约关联时,是这个地址所创建的合约数量。

交易数量随机数是通过计算发送方地址已确认的交易数量动态得出

  • 包含交易创建顺序中的可用性特征
  • 保护交易重复的重要特征

gas

gas 是以太坊的燃料,并不是以太币,跟以太币之间存在汇率关系。
以太坊用gas来控制交易对资源的使用,确保避免Dos攻击或过度消耗资源的交易。

gasPrice字段是交易方设定的针对gas的汇率,单位为wei/gas

gasPrice越高,交易被确认的速度越快(类似于加价越高,滴滴司机来的越快),优先级较低的交易可以支付比平均值低的gas费用,确认的时间会更长。

以太坊允许零gas的交易,如果区块有多余空间,那么这样的交易也会被打包到区块链上,但这一天有可能永远也不会到来。

在web3接口中查询最近几个区块的价格中值

web3.eth.getGasPrice(console.log)

gasLimit表示交易方愿意支付的最大gas数量,之所以是最大,是因为交易所需要的gas并非固定的。

验证交易的第一个步骤就是确保发起交易的账户中有足够的以太币支付对应的gas费用 wei_{need} = gasPrice \times gas

value与data

一笔交易数据包可以同时包括value与data,也可以都没有或只有其中一个。

  • 只包含value的交易是支付操作
  • 只包含data的交易是合约调用操作
  • 都不包含的交易可能只是为了浪费gas

特殊的交易——合约创建

在区块链上创建新合约的交易,注册交易的目的地址为零地址,这个地址永远不能用来支付以太币或触发交易,是一个带有特殊含义的地址。

注册合约只需要在data字段中包含经过编译的合约字节码,也可以选择在value中包含以太币作为该合约的起始金额。

以太坊虚拟机 EVM

根据自己的情况,先跳过书中对Solidity智能合约的介绍,先行学习基础

以太坊虚拟机简称EVM,是以太坊协议和具体操作的核心,类似于Java虚拟机JVM的概念。
  • EVM是一个准图灵完备的状态机,即EVM可以解决你输入的任何问题。出于对计算所需资源的限制,引入了gas的概念作为停止条件。

EVM基于栈的架构,拥有程序代码存储区ROM,加载了智能合约字节码;一个内容可变的内存RAM;一个永久储存的空间Storage,作为以太坊状态的一部分。

EVM没有系统接口,也没有硬件支持,是完全虚拟化的。


EVM的架构


EVM 指令集(字节码)

EVM指令也是由标准机器码指令组成的,有:

  • 算术、位运算逻辑操作 ADDMULSHA3AND
  • 执行上下文查询
  • 栈、内存、存储访问POPMLOAD
  • 处理流程操作STOPPC
  • 日志、跳转等

以太坊的状态

EVM的任务是基于以太坊协议,根据智能合约代码的执行来进行合法的状态转换,从而更新以太坊的状态,这是外部用户(EOA)通过创建、接受交易和对交易进行排序打包来引起状态转换所描述的。
  1. 一次引起智能合约代码执行的交易
  2. 基于交易+当前区块信息创建EVM实例,将智能合约字节码加载入EVM的ROM
  3. 加载合约账户所对应的存储数据,设定gas为交易发送者支付的gas总量(gasLimit)
  4. 设置相关环境变量,执行智能合约
  5. 若gas减少到0,则抛出异常OOG(out of gas),立即停止执行。
  6. 执行成功,同步EVM主机与该实例的数据,包括合约存储数据的更改、新合约、以太币余额的变化。

可将EVM的执行类比为将以太坊即时的世界状态映射到一个沙盒(EVM实例)中,若执行失败,则丢弃该沙盒中的状态,若执行成功结束,将EVM实际的状态更新至该沙盒的状态

智能合约自己也可以产生交易,一个合约也可以调用另外一个合约,故而EVM的执行是一个可以递归的过程。

gas 计量

我们无法仅通过对程序的观察确定它所需要的资源,有可能会出现需要无限资源才能解决的问题,造成EVM的堵塞(图灵完备机的共同安全问题),即所谓的停机问题。为了解决该问题,以太坊引入gas作为计算的开销,并在同一时间存在一个最大gas的限制,终止过度消耗资源的交易。

在EVM的交易执行成功结束后,执行实际消耗的gas费用会被作为交易费支付给矿工,剩余gas将退还给发送者。 交易费 = 实际消耗的gas\times gasPrice\\\\ 剩余gas = gasLimit - 实际消耗的gas\\\\ 返还以太币 = 剩余gas\times gasPrice 同时,每个区块的交易总量和gas数量被限定,倘若一个矿工尝试包含一个gas数量超过区块限制的交易,将返回transaction exceeds block gas limit的警告,停止交易,gas限制由矿工共同决定。

负gas开销

以太坊鼓励删除无用合约和使用过的存储变量storage,在执行结束后会返还对gas使用的消耗

  • 删除一个合约返还24000gas
  • 将一个非零存储地址置0返还15000gas
为了避免通过返还机制牟利,最大gas返还数量被设定为交易总共消耗gas的一半(向下舍去)。

共识机制[3]

当涉及到像以太坊这样的区块链(本质上是分布式数据库)时,网络节点必须能够就系统的当前状态达成一致。 这是通过协商一致机制实现的。

拜占庭容错BFT[4]&工作量证明PoW

拜占庭容错是分布式计算机网络在信息不完善或网络组件出现故障的情况下保持有效共识的容错能力,基于拜占庭将军问题。


Byzantine Fault Tolerance: A Blockchain Fundamental - Phemex Academy


在比特币出现之前,主要通过一组封闭节点(领导者)实现BFT,该方法称为pBFT

比特币创始人实现了一种称为中本聪共识(Nakamoto Consenus)的方法,通过引入工作量证明、块选择、时间戳服务器与激励结构的概念,实现了零信任共识,为彻底去中心化网络的实现提供了可能。

而在以太坊中,先前使用的共识算法为基于PoW的Ethash(已弃用),实现流程如下(改编自以太坊文档)

首先以太坊的交易被处理为区块。 每个区块都具备以下内容:

  • 区块的难度,例如:3,324,092,183,262,715
  • 混合哈希(mixHash),例如:0x44bca881b07a6a09f83b130798072441705d9a665c5ac8bdf2f39a3cdf3bee29
  • nonce--例如:0xd3ee432b4fb3d26b

区块数据与工作量证明直接关联。 矿工准备好一个区块数据,需要进行PoW工作量计算时,实际上是在暴力破解一道数学题。 已知的区块RLP数据和数据集,再结合需要求解的变量 Nonce,进行数据合成计算。 需要使得计算结果能够符合区块难度系数要求。如果不符合要求,则继续使用 Nonce++ 继续计算, 直到符合要求。


Ethash算法计算流程


其中缓存和数据集大小随时间线性增长,为了使Ethash具备抗ASIC性,从而避免PoW挖矿中的中心化问题

权益证明 PoS

当前以太坊已进入2.0阶段,共识算法转为权益证明PoS

权益证明是区块链用来达成分步式共识的一种共识机制。 在工作量证明共识机制中,矿工通过消耗能源来证明他们拥有资本以应对风险。 而在权益证明共识机制中,验证者通过将以太币资本质押到以太坊上的智能合约中证明其选择的权威性,如果验证者表现出不诚实或懒惰,就可以销毁这些以太币。 之后,验证者负责检查在网络上传播的新区块是否有效,并创建和传播新区块。

简要地讲,以太坊上一组验证者轮流提议和投票下一个区块,每个验证者投票的权重取决于其存款(即权益)的大小。 PoS 的显著优势包括安全性、降低中心化风险和能源利用效率。

V神关于PoS的介绍-2017


Solidity&DApp开发

待更新

智能合约安全

待更新

参考

  1. ^Due to the protocol changes of Ethereum: Rinkeby, Ropsten and Kovan test networks may not work as reliably and will be deprecated soon https://blog.ethereum.org/2022/06/21/testnet-deprecation
  2. ^ECC与secp256k1 https://www.wanghaoyi.com/crypto-ecc-secp256k1.html
  3. ^共识机制 https://ethereum.org/en/developers/docs/consensus-mechanisms/
  4. ^BFT https://blockonomi.com/nakamoto-consensus/BFT
编辑于 2022-10-14 21:47