参考资料缩略词DIY比特币测试网络启动服务bitcoindbitcoin-cli基础功能状态信息查看 UTXOset钱包信息交易列表节点信息区块高度按地址查询收款交易列表导出私钥查询余额未花费输出创建原始交易交易签名解码原始交易发送原始交易查询交易交易列表生成新地址查询地址信息转账OP_RETURN 携带数据2-3 多签智能坊合约开发获取基本信息获取地址列表获取账户信息生成测试地址、充值、激活地址生成测试地址充值查询余额激活地址确认地址已激活注册应用脚本智能合约脚本注册合约充值计算合约内容充值查询应用账户信息提现查看当前余额提现再次查看当前余额图示余额变化脚本出错处理智能坊综合操作DPoS钱包私钥节点综合以太坊初步探索启动 geth客户端连接RPCattach创建账户查看系统变量eth 全局变量personal 变量miner 变量基础操作查看节点信息查看当前区块高度查看矿工账户设置矿工账户开启挖矿停止挖矿查看系统所有用户创建新用户给用户取别名解锁账户检查账户余额单位转换转账查看交易信息查看排队的交易查看自己节点信息添加节点查看节点信息节点是否监听节点连接数量基础概念以太坊外部账户 EOA 与合约账户 CA合约编写 Solidity 合约脚本编译合约定义合约把合约代码部署上链查看交易细节合约命名执行合约 get 函数执行合约 set 函数执行合约 get 函数以太坊开发基础web3testrpctruffle以太坊 Truffle、TestRPC 开发基础启动 TestRPC 服务新建 Truffle 工程,并初始化开发、编译、部署合约测试合约以太坊第三方开放 API官方社区infura 开放 API以太坊智能合约编译、部署、调试编译部署调试以太坊区块浏览器与合约验证与发布合约读接口合约写接口查看合约相关交易查看合约相关事件以太坊智能合约: ERC20 Token编写合约接口函数事件函数在线编译部署合约执行合约在测试网络部署合约在测试网络一键发币通过 geth 发送 erc20以太坊智能合约安全:函数多签函数多签基础库权限控制合约应用合约运行过程以太坊智能合约安全:重入公共钱包合约三种转币函数回退函数攻击合约攻击原理攻击过程攻击过程演示扩展以太坊智能合约安全:访问控制函数可见性函数底层调用方式存在漏洞的合约存在的问题攻击合约攻击原理攻击过程以太坊智能合约安全:算术问题整数上溢存在问题的合约存在的问题攻击过程以太坊智能合约安全:构造函数失控构造函数失控示例合约预防措施真实世界的例子:Rubixi以太坊智能合约安全:未初始化的存储指针以太坊智能合约安全:从合约中提现transfer 转账失败send 转账失败预防技术EOS 入门启动 nodeos查看运行信息创建钱包查看钱包列表锁定解锁钱包创建密钥将密钥导入钱包查看钱包管理的密钥创建账户查看账户Libra 入门创建交易私有网络BTC密钥和地址钱包地址的生成私钥、公钥、钱包地址使用『私钥』对交易进行签名使用『公钥』对签名进行验证结论钱包非确定性钱包确定性钱包交易交易的输入输出交易输出交易输入交易费交易脚本和脚本语言图灵非完备性去中心化验证脚本构建(锁定与解锁)脚本执行堆栈P2PKH(Pay-to-Public-Key-Hash)P2PK(Pay-to-Public-Key)MS(Multiple Signatures)OP_RETURNP2SH(Pay-to-Script-Hash)比特币网络p2p 网络架构节点类型及分工区块链区块数据存储区块结构区块头区块标识符创世区块区块的连接Merkle 树挖矿与共识ETH智能合约何为智能合约?编程语言:Solidity运行环境:EVM合约的编译合约的部署geth如何部署运行Gas以太坊网络使用以太坊正式网络使用以太坊官网测试网络 Testnet使用私有链使用开发者网络(模式)使用模拟环境noncekeystore 文件与私钥keystore 文件私钥转 keystorekeystore 导出私钥使用 metamask 获取私钥使用 keythereum 模块获取私钥使用 web3 1.0 模块获取私钥使用 ethkey 工具获取私钥Solidity单位和全局变量以太币单位时间单位区块和交易属性错误处理EOSEOS 节点的收益分配分配的流程EOS 通胀率为5%新增 EOS 的用途出块节点和备用节点的收益总结FileCoin存储证明算法关键操作SealProof-of-Spacetime (PoSt)DSN网络(Decentralized Storage Network)网络参与角色去中心化交易市场存储账本(Ledger)的数据结构DSN网络的运行流程DSN网络提供的特性共识机制区块链技术定义分类商业价值技术架构核心技术共识机制 密码学原理 分布式存储 理论与技术点图灵完全base58重放攻击与保护DPoSDPoS + pBFT多重签名什么是多重签名多重签名的作用维基链的多重签名实现方式混币原理环签名同态加密零知识证明可验证随机函数(VRF)应用场景存证业务比特币数据记录以太坊数据记录维基链 WaykiChain 源码文件功能描述交易执行冷挖矿比特币 Bitcoind 源码最大连接限制种子节点、对等节点以太坊 geth 源码同步模式同步模式的定义默认同步模式同步模式的自动切换总结


参考资料

缩略词

缩略词定义/解释 
PoW工作量证明(Proof of Work) 
PoS权益证明(Proof of Stake) 
DPoS股份授权证明(Delegate Proof of Stake) 
PBFT实用拜占庭容错(Practical Byzantine Fault Tolerance) 
P2P点对点(Peer to Peer) 
DApp分布式应用(Decentralized Application) 

DIY

比特币测试网络

基础环境: Ubuntu 14.04.5 LTS/Bitcoin Code Daemon v0.16.99.0-7c32b41

启动服务

bitcoind
bitcoin-cli

API 接口:https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_calls_list

基础功能

状态信息
查看 UTXOset
钱包信息

可通过 https://testnet.manu.backend.hamburg/faucet 获取免费的测试币

交易列表
节点信息
区块高度
按地址查询收款交易列表
导出私钥
查询余额
未花费输出
创建原始交易

使用未花费输出 8e176938a3b43a27879b2d5362f546fbd8964ca7dd2b1d86f6bb612add5d8527 给地址 2N833q6qKVtqYQGeCkoxkhSqVBiLFfPNTvZ 转账 0.528 比特币,其中,fee = 0.529 - 0.528 = 0.001

交易签名
解码原始交易
发送原始交易
查询交易

此时,再次查询未花费输出

交易列表
生成新地址
查询地址信息
转账

此时,自动找出满足条件的未花费输出,并且设置小费,可通过查询交易信息验证

选择未花费输出 1.1 BTC,转账金额 0.1 BTC,消费 0.00003320 BTC,找零 0.99996680 BTC

综上:0.1 + 0.0.00003320 + 0.99996680 = 1.1

解锁脚本中,对于找零地址 2MvbfqkYTRfEurhJ9iP9hgbUGAL4Ed8ebdX,属于 P2SH;对于目标地址 mu2cVKoUsTSyUEJJMMbhNuGa6yVvMvXj6n,属于 P2PKH

OP_RETURN 携带数据

选择未花费输出 0.1

获取一个新的找零地址

创建原始交易,花费 0.1 BTC,找零 0.009 BTC,消费 0.0001 BTC,写入数据 hello world,对应十六进制 68656c6c6f20776f726c64

解码原始交易

导出未花费输出对应的私钥

用私钥给原始交易签名

将签名后的交易发送

查询交易信息

https://live.blockcypher.com/ 可以查询该交易信息,如下所示:

再次查询未花费输出

实际上,也可以直接选择一个未花费输出 X,将 X 全部当成小费构造一笔携带数据的交易,如下所示:

2-3 多签

多重签名 2-3,表示 3 个人拥有签名权,而 2 个人签名就可以支付这个账户里的比特币。本示例中,通过构造一笔 2-3 多签交易,利用 3 个私钥签名交易,后续需要前者中的 2 个私钥才能花费该未花费输出

查询未花费输出

生成 3 个新地址,并导出私钥、查看公钥

生成 2-3 多签地址 2MwgnLokB4WA9NLNd6yL36AvPHHVChDh5r3

解码赎回脚本

赎回脚本由如下几部分组成:52 21 02f396b11941706b0424499fbf679c3e5987a8d2bf5b47116ec60ceb4f71804d46 21 0256c0ec8ee73ba5fff9b12910880c12be9ff7d15f1f1b5d64685c3bcbdeb6734d 21 0269321ca40da82f1fb8da8932b25beb96b4a84e6af593e89ee64e4c3f867eb7b5 53 ae

查看上述地址中信息,以 mzzsUyocJXwrbQi13k1YVYuvUPKUL6H2Gz 为例

向多签地址 2MwgnLokB4WA9NLNd6yL36AvPHHVChDh5r3 转账

查询交易信息

解码原始交易

查看未花费输出

生成一个新地址

使用上述 2-3 多签未花费输出向地址 2NDYvTxmsRmyPw612KwBGCqLNsBRQSDpjXL 转账 0.5269 BTC

如果只是用一个私钥去签名交易(2-3 多签中至少需要 2 个有效私钥),失败信息如下

如果使用一个有效私钥,一个无效私钥(cVCHvKPezAr4gNCP5uLcDUcUP7kbydWAhV7CizA9V5QrNSKSRfXh 钱包中无此私钥)去签名交易,失败信息如下

使用两个有效私钥签名交易,成功信息如下

解码原始交易信息

解码锁定脚本

解码赎回脚本

发送原始交易

查看交易信息

多签地址 2MwgnLokB4WA9NLNd6yL36AvPHHVChDh5r3 包含两笔交易:

智能坊合约开发

获取基本信息

获取地址列表

获取账户信息

生成测试地址、充值、激活地址

生成测试地址
充值
查询余额

充值后,需要出块之后才能到账,可以通过查询交易信息确认是否到账,可以查询到包含上述交易 hash:

334f447dfd284b2a084560f57d3c4ad044cde1fc41470c68ffeae55aefe270fb

2ab57b070aa87aac85afaf21fd18e4e4211017f7104ab18980ccef287d750012

激活地址
确认地址已激活

账户余额计算:100000000000 - 100000 = 99999900000

根据字段 RegID 判断是否已激活:RegID 分别为 446-1、448-1,表示该地址已激活。

注册应用脚本

智能合约脚本

智能合约脚本实际为 lua 脚本,点此下载

注册合约

脚本注册 ID:117-1

通过 getappinfo() 接口,根据 regid 反向查询合约内容,字段 scriptContent 即为合约的十六进制显示

充值

合约中,包含总金额、自由余额、每月冻结金额。(初始)自由金额实时转移到合约接收方账户,每月冻结金额将逐步解冻,变成自有金额,并转移到合约接收方账户。

解冻逻辑:执行合约后,可以看到如下信息

当有新的区块加入时,检查当前区块高度是否大于等于合约中冻结金额的 nHeight,如果是,将该比冻结金额解冻,变成自有金额,并转移到合约接收方账户。

同时,可以借助于出块的速度,设置达到多少块高度某笔金额解冻,实现合约冻结解冻逻辑。

计算合约内容

合约内容 = 前缀 1 字节 0xff + 操作类型 1 字节 0x02 + 充值地址 34 字节whmD4M8Q8qbEx6R5gULbcb5ZkedbcRDGY1 + 充值总金额 8 字节 10000000000 + 自由金额 8 字节500000000 + 每月冻结金额 8 字节 500000000

  1. 合约前缀、操作类型转换为 16 进制

    前缀 0xff => ff

    操作类型 0x02 => 02

  2. 充值地址 whmD4M8Q8qbEx6R5gULbcb5ZkedbcRDGY1 => 77686d44344d3851387162457836523567554c626362355a6b656462635244475931

  3. 充值总金额 10000000000 => 利用计算器转成 16 进制为 2540be400,补齐 8 字节后为 00000002540be400,按照内存中逆序后为 00e40b5402000000

  4. 自由金额 500000000 => 利用计算器转成 16 进制为 1dcd6500,补齐 8 字节后为 000000001dcd6500,按照内存中逆序后为 0065cd1d00000000

  5. 每月冻结金额 500000000 => 利用计算器转成 16 进制为 1dcd6500,补齐 8 字节后为 000000001dcd6500,按照内存中逆序后为 0065cd1d00000000

  6. 将这些字段组合在一起,形成合约内容ff0277686d44344d3851387162457836523567554c626362355a6b65646263524447593100e40b54020000000065cd1d000000000065cd1d00000000

充值
查询应用账户信息

提现

查看当前余额
提现

将自由金额 500000000,提现到地址 whmD4M8Q8qbEx6R5gULbcb5ZkedbcRDGY1

合约内容 = 前缀 1 字节 0xff + 操作类型 1 字节 0x01 + 账户类型 1 字节 0x02 + 提现金额 8 字节 500000000

  1. 合约前缀、操作类型转换为 16 进制

    前缀 0xff => ff

    操作类型 0x02 => 01

  2. 账户类型 0x02 => 02

  3. 提现金额 500000000 => 利用计算器转成 16 进制为 1dcd6500,补齐 8 字节后为 000000001dcd6500,按照内存中逆序后为 0065cd1d00000000

  4. 将这些字段组合在一起,形成合约内容 ff01020065cd1d00000000

再次查看当前余额

1004.99900000 + 95.00000000 - 0.00100000 = 1099.99800000

备注:参数指定提取自由余额 5.00000000,实际上,一次性将所有自由余额 95.00000000 全部提取

图示余额变化

脚本出错处理

当应用账户余额为零时,提现操作将会失败,实现信息已 json 格式输出,具体如下所示:

智能坊综合操作

DPoS

创建投票原始交易不包含签名,需要经过签名之后,再提交交易(广播到网络),即,包含如下三个步骤

createdelegatetxraw -> sigstr -> submittx

钱包

私钥

删除私钥之后,如果尝试导出一个不存在的私钥,将提示如下错误

节点

给节点添加 127.0.0.1:7901 的节点

综合

以太坊初步探索

Ubuntu 14.04.5 LTS/geth v1.8.13-unstable/solc v0.4.24+commit.e67f0147.Linux.g++/golang v1.10.3

启动 geth

以太坊主网

默认主网,添加启动项 --testnet:测试网;--dev:开发者网络(初学者可以使用此模式)

以太坊私有网络

配置创世块配置文件 genesis.json

配置自己的创世块是为了区分公有链,同一个网络中,创世块必须是一样的,否则无法联通。

参数名称参数描述
mixhash与 nonce 配合用于挖矿
noncenonce 就是一个64 位随机数,用于挖矿
difficulty设置当前区块的难度,如果难度过大,cpu 挖矿就很难,这里设置较小难度
alloc用来预置账号以及账号的以太币数量,因为私有链挖矿比较容易,所以我们不需要预置有币的账号,需要的时候自己创建即可以。
coinbase矿工的账号,随便填
timestamp设置创世块的时间戳
parentHash上一个区块的 hash 值,因为是创世块,所以这个值是 0
extraData附加信息,随便填,可以填你的个性信息
gasLimit该值设置对 gas 的消耗总量限制,用来限制区块能包含的交易信息总和,因为我们是私有链,所以填最大。

初始化并启动私有网络

客户端连接

RPC
attach

创建账户

查看系统变量

eth 全局变量
personal 变量
miner 变量

基础操作

查看节点信息
查看当前区块高度
查看矿工账户
设置矿工账户
开启挖矿

默认第一个账户得到挖矿收益吗,2 为挖矿占用的 CPU 数量

停止挖矿

合并挖矿操作:miner.start(2); admin.sleepBlocks(1); miner.stop();

查看系统所有用户
创建新用户

创建成功后,会展示创建成功的地址,其中上面的表达式中,中间传入的 123456 为密码 。再次查看系统所有用户,可以看到共有两个用户。

给用户取别名

操作成功后,用户别名 user1user2 已经成功赋值

解锁账户

可设置解锁账户持续时间

为了安全起见,一般一个用户在创建的时候都处于锁定状态,输入密码进行解锁。

如果不解锁,直接对用户操作,例如,对用户转账,则报错如下:

检查账户余额
单位转换
转账

转账之后,账户余额仍然没变。此时,需要启动挖矿使交易加入到区块链中。

查看交易信息
查看排队的交易

网络原因,或者设置的 gasPrice 较低,提交的交易可能存在排队

查看自己节点信息
添加节点
查看节点信息
节点是否监听
节点连接数量

基础概念

以太坊外部账户 EOA 与合约账户 CA

在以太坊中,账户拥有4个字段:{nonce,balance,codeHash,StorageRoot}。一共分为2种账户:外部账户、合约账户。

判断一个是否为合约地址的方法如下:

合约

编写 Solidity 合约脚本
编译合约

利用 solc 编译合约,生成 ABI 接口和合约的二进制代码,也可利用 Remix 在线编译合约

定义合约
把合约代码部署上链

合约部署上链(以太坊测试网络)时,给构造函数传参 99,即赋初值 99

查看交易细节

根据交易 hash 查看交易细节

合约命名

交易信息中,contractAddress 表示合约地址

执行合约 get 函数
执行合约 set 函数
执行合约 get 函数

以太坊开发基础

基础环境:NPM v5.6.0/nodejs v8.11.3

web3

web3.js 是一个通过 RPC 调用 和本地以太坊节点进行通信的 js 库

web3.js 可以与任何暴露了 RPC 接口的以太坊节点连接。 web3 中提供了 eth 对象 web3.eth 来与以太坊区块链进行交互

web3 不同版本接口不兼容,根据需要安装对应版本

testrpc

ethereumjs-testrpc 库后续被重命名为 Ganache CLI

testrpc 不同于 gethgeth 是真正的以太坊环境,testrpc 是在本地使用内存模拟的一个以太坊环境,对于开发调试来说,更为方便快捷,当合约在 testrpc 中测试通过后,再部署到测试网络或主网络

运行 testrpc 之后自动创建 10 个账户,每个账户默认有 100 个以太币

可指定运行参数,例如设置 gaslimit、出块时间 testrpc --gasLimit 0x800000000 -b 2

truffle

Truffle 是最流行的开发框架,能够在本地编译、部署智能合约,使命是让开发更容易;Truffle 需要以太坊客户端支持,需要支持标准的 JSON RPC API

truffle 集成了 web3.js(可能使用较低版本的 web3.js,与最新版本的 web3.js 接口、语法可能存在不兼容)

可指定安装版本,如 npm install -g truffle@4.0.0

以太坊 Truffle、TestRPC 开发基础

基础环境:EthereumJS TestRPC v6.0.3 (ganache-core: 2.0.2)/Truffle v4.1.14 (core: 4.1.14)/Solidity v0.4.24 (solc-js)

启动 TestRPC 服务

新建 Truffle 工程,并初始化

在 Windows 下,删除 truffle.js,否则有 truffle-config.js 重定义问题

修改 truffle-config.js 文件,内容如下

开发、编译、部署合约

contracts 编写业务合约 MyStringStore.sol;在 migrations 添加 2_deploy_contracts.js

编译合约

Truffle 仅默认编译自上次编译后被修改过的文件,来减少不必要的编译。如果你想编译全部文件,可以使用 --compile-all 选项

部署合约

测试合约

在目录 test 编写测试用例文件 MyStringStore.js

执行测试用例

部署、测试合约过程中,可以查看 TestRPC 输出窗口

以太坊第三方开放 API

本地不部署节点,利用第三方开放 API 开发自身的业务

官方社区

详见 https://etherscan.io/apis,以太坊社区提供的无需授权的服务,但是请求频率限制比较严,5 Request/Second,且 API 只支持主网

首先,申请 Access Token

然后,调用对应的接口即可,如,获取事件日志

对应地,以太坊区块链浏览器信息如下

infura 开放 API

Infura provide secure, reliable, and scalable access to Ethereum APIs and IPFS gateways.

在 infura 注册个人账户,创建项目即可分配访问 API 相关的身份认证信息以及 Endpoint

通过 https://infura.io/docs 查看 API 文档。例如,获取测试网、主网区块高度

发送 API 请求时可以携带账户中的 Token,结合统计面板查看 API 调用情况,如下所示

以太坊智能合约编译、部署、调试

此处合约特指 Solidity

编译

部署

调试

此处特指 remixremix-ide 调试

以太坊区块浏览器与合约

此处特指以太坊测试网络区块浏览器 https://ropsten.etherscan.io

验证与发布

在以太坊测试网络 code 菜单选择 verify and publish,提交合约源码并验证

填写合约名字、编译器版本、是否开启优化选项,然后将合约代码粘贴(如果项目中合约包含多个文件,将多个文件内容整合即可)

再次回到以太坊测试网查看合约信息,可看到合约已通过验证的提示

合约读接口

Read Contract 菜单可以查看或调用合约读接口

合约写接口

Write Contract 菜单,借助于 Metamask 可以调用合约写接口

根据上述提示 Connect with Metamask,连接 Metamsk

连接 Metamask 之后,即可调用合约写接口

查看合约相关交易

查看合约相关事件

以太坊智能合约: ERC20 Token

编写合约

接口函数
事件函数

产生事件,从而可以被前端代码捕获到

SafeMath 定义了安全的加减乘除操作;ERC20 定义了符合 ERC20 的合约接口;StandardToken 合约继承自 ERC20,是 ERC20 的具体实现,点击下载

在线编译

https://remix.ethereum.org 新建文件 erc20_token.sol,粘贴上述代码,点击编译,生成结果如下

部署合约

点击右侧运行,选择 Standard Token

输入合约的构造函数的参数,分别为 12306,“Kevin's Token", 18, "KT",然后点击 Deploy

查看页面下方的控制台,查看详情,找到合约地址 0xef55bfac4228981e850936aaf042951f7b146e41

执行合约

复制合约地址,点击 At Address

执行合约的 totalSupply 函数

点击控制台,展开交易详情,如下所示

在测试网络部署合约

https://remix.ethereum.org 编译合约之后,点击 Detail,找到 WEB3DEPLOY

对 js 略作修改,赋初值,保存脚本为 erc20.js

本地运行 geth 节点 ./geth --datadir=./testnet --testnet --cache=2048 --rpc console,该节点已同步,同时启用了 rpc 服务。

运行部署脚本 node erc20.js,得到运行结果如下

合约地址:0x7f224de3c7276312ac8cca8b822ca2d1579d2197

交易 hash:0x4884439622697339381cc305a64ba6f11b5dc82cd0b75a1f13545a4a85f0f67c

https://ropsten.etherscan.io 查询交易信息

点击上图中合约地址链接(或直接根据合约地址查询),查询合约详细信息

可以点击 Code,填写合约名称、编译器版本、是否启用优化,提交合约源码,进行验证,验证同步哦之后,如下所示

Read Contract 可以对合约进行读操作,如下所示

在测试网络一键发币

前提条件:

代理服务脚本 点击下载,主要解析请求的代币初始化参数,发布合约成功后,返回合约地址、交易 hash

客户端可以通过 curl 访问,如下所示

通过 geth 发送 erc20

以太坊智能合约安全:函数多签

借助于 MSFun 提供的函数多签基础库,封装成权限控制合约 Auth,在应用合约 Demo 使用权限控制合约 Auth

函数多签基础库
权限控制合约
应用合约

应用合约中,添加函数多签限制的函数格式如下所示(参考上述合约中函数 modify

运行过程

应用合约中,函数 modify 调用添加了多签限制:需要两名具备开发者权限的账户签名,才能执行函数 modify 的核心功能:修改 value 的值

  1. 部署合约

  2. 使用具备开发者权限的账户 1 签名

  3. 使用具备开发者权限的账户 2 签名

使用账户 0xdd870fa1b7c4700f2bd7f44238821c26f7392148 部署合约 Auth,然后部署合约 Demo

查看 value 的初始值 100

若以账户0xdd870fa1b7c4700f2bd7f44238821c26f7392148 调用函数 modify 尝试修改 value,报错,提示账户费开发者

切换到具备开发者权限的账户 0xca35b7d915458ef540ade6068dfe2f44e8fa733c 调用函数 modify

虽然尝试修改 value 为 99,但是值仍然为 100

切换到具备开发者权限的账户 0x14723a09acff6d2a60dcdf7aa4aff308fddc160 调用函数 modify

此时,value 被修改成了 99

以太坊智能合约安全:重入

公共钱包合约

合约实现了一个类似公共钱包的代码,其中,balances 定义了一个下标为 [address] 的公共钱包,deposit 函数向钱包中调用者的位置存入相应的 value 值,withdraw 函数检查提币账户的余额与该合约资产是否大于参数 amount,之后向 to 地址发送相应 ether。

三种转币函数

Solidity 中 <address>.transfer()<address>.send()<address>.call.vale()() 都可以用于向某一地址发送 ether,他们的区别在于:

<address>.transfer()

<address>.send()

<address>.call.value()()

回退函数

A contract can have exactly one unnamed function. This function cannot have arguments and cannot return anything. It is executed on a call to the contract if none of the other functions match the given function identifier (or if no data was supplied at all).

fallback 函数在合约实例中表现形式即为一个不带参数没有返回值的匿名函数,如下所示

那么什么时候会执行 fallback 函数呢?

备注:一个没有定义 fallback 函数的合约,直接接收以太币(没有函数调用,即,使用 send 或 transfer 发送以太币)会抛出一个异常, 并返还以太币(在 Solidity v0.4.0 之前行为会有所不同)。所以如果你想让你的合约接收以太币,必须实现 fallback 函数。

攻击合约

攻击原理

著名导致以太坊硬分叉(ETH/ETC)的 The DAO 事件就跟重入漏洞有关,该事件导致 60 多万以太坊被盗。

前提条件:

  1. call.value()() 提供了足够的 gas

  2. 资产的修改在转币之后

  1. 攻击者部署 Attack 合约

  2. Attack 合约调用 IDMoney 合约的 deposit() 存入接口

  3. Attack 合约调用 IDMoney 合约的 withdarw() 取现接口,取现接口使用 call.value()() 触发 fallback()

  4. fallback() 接口中递归调用 withdraw(),从而不断从 IDMoney 合约提现以太坊

攻击过程

https://ethereum.github.io/browser-solidityhttps://remix.ethereum.org 编译合约

使用外部账户 0xca35b7d915458ef540ade6068dfe2f44e8fa733 部署 IDMoney 合约

得到 IDMoney 合约地址 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a

向外部账户 0xca35b7d915458ef540ade6068dfe2f44e8fa733c 向钱包存入 22 个以太坊,如下所示

点击 deposit 之后,查看交易信息

通过 balancaeof() 查看外部账户 0xca35b7d915458ef540ade6068dfe2f44e8fa733c 信息,合约中的余额为 22(0 --> 22),同时,账户余额为 77 (99 --> 77)

查看交易信息

 

WARNING: 切换到外部账户 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db 之后,才能部署合约 Attack

部署 Attack 合约

部署合约之后,得到 Attack 合约地址 0x8046085fb6806caa9b19a4cd7b3cd96374dd9573,同时可以查看到外部账户 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db 余额变为 97 以太坊

查看交易信息

调用 setVictim() 设置合约 IDMoney 地址,如下所示

查看交易信息

外部账户0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db 尝试发动攻击,调用 Attack 合约中的 startAttack(1000000000000000000),即,外部账户 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db 先向钱包转入 1 个以太坊,然后提取 0.5 个以太坊

查看交易信息如下

备注:交易信息部分截图如下,注意查看 logs,包含 46 条事件日志,第 1 条为 Attack 合约打印,剩下的 45 条为 IDMoney 合约打印。

1533927494535

调用 stopAttack() 停止攻击,销毁 Attack 合约,并将合约余额退回到外部账户0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db

查看交易信息

此时,外部账户 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db 的余额如下所示

计算思路:97.9 + 2 - 1 + 45 * 0.5 = 121.4,其中,97.9 为部署 Attack 合约之后的余额, 2 为转到 Attack 合约中的以太坊数量(合约销毁即退回原账户),1 为存入 IDMoney 合约的以太坊数量, 45 * 0.5 为递归提现的以太坊数量。

另外,对于锁定在 IDMoney 中的 0.5 以太坊,可以通过在 IDMoney 预留销毁合约接口,即可实现销毁合约退回到原账户,大致如下:

攻击过程演示

Alt Text

扩展

上述攻击合约中提现接口包含 balances[msg.sender] -= amount;,因为存在递归调用,存在数值溢出的问题。

如果在攻击合约中加入如下事件日志 withdrawLog2() 逻辑

在执行完攻击之后,可以在日志记录中查看到如下信息,从第 3 次开始,数值已经溢出,如下所示:

以太坊智能合约安全:访问控制

函数可见性

由于 Solidity 有两种函数调用(内部调用不会产生实际的 EVM 调用或称为“消息调用”,而外部调用则会产生一个 EVM 调用), 函数和状态变量有四种可见性类型。 函数可以指定为 externalpublicinternal 或者 private,默认情况下函数类型为 public。 对于状态变量,不能设置为 external ,默认是 internal

函数底层调用方式

在 Solidity 中,call 函数簇可以实现跨合约的函数调用功能,其中包括 calldelegatecallcallcode 三种方式。

在 Solidity 中 call 函数簇的调用模型:

其中,callcode 不建议使用,后续将会被去掉

这些函数提供了灵活的方式与合约进行交互,并且可以接受任何长度、任何类型的参数,其传入的参数会被填充至 32 字节最后拼接为一个字符串序列,由 EVM 解析执行。

在函数调用的过程中, Solidity 中的内置变量 msg 会随着调用的发起而改变,msg 保存了调用方的信息包括:调用发起的地址,交易金额,被调用函数字符序列等。

三种调用方式的异同点

calldelegatecall 的上下文环境对比如下所示

合约 A 以 call 方式调用外部合约 B 的 func() 函数,在外部合约 B 上下文执行完 func() 后继续返回 A 合约上下文继续执行;而当 A 以 delegatecall 方式调用时,相当于将外部合约 B 的 func() 代码复制过来(其函数中涉及的变量或函数都需要存在)在 A 上下文空间中执行。

存在漏洞的合约

存在的问题

合约 A 中,函数 infect 使用 delegatecall 调用另一合约的函数,如果攻击者部署一个精心构造的合约,并且将该地址传入函数 infect,能够将合约 A 中的 owner 修改为攻击者自己(msg.sender

攻击合约

攻击原理

delegatecall 执行环境为调用合约的上下文,即,合约 A 中的函数 infect 调用了合约 B 的函数 func,因为执行上下文是合约 A,所以能够修改合约 A 中的 ownervalue

攻击过程

使用账户 0xca35b7d915458ef540ade6068dfe2f44e8fa733c 部署合约 A,并查看 ownervalue 的值

使用账户 0xdd870fa1b7c4700f2bd7f44238821c26f7392148 部署合约 B,并查看 ownervalue 的值

执行合约 A 的函数 infect,参数为合约 B 的合约地址

然后,并查看合约 Aownervalue 的值

以太坊智能合约安全:算术问题

通常来说,在编程语言里算术问题导致的漏洞最多的就是整数溢出了,整数溢出又分为上溢和下溢。

整数上溢

整数溢出的原理其实很简单,这里以 8 位无符整型为例,8 位整型可表示的范围为 [0, 255]255 在内存中存储按位存储的形式如下:

img

8 位无符整数 255 在内存中占据了 8bit 位置,若再加上 1 整体会因为进位而导致整体翻转为 0,最后导致原有的 8bit 表示的整数变为 0.

如果是 8 位有符整型,其可表示的范围为 [-128, 127]127 在内存中存储按位存储的形式如下所示:

img

在这里因为高位作为了符号位,当 127 加上 1 时,由于进位符号位变为 1(负数),因为符号位已翻转为 1,通过还原此负数值,最终得到的 8 位有符整数为 -128

上面两个都是整数上溢的图例,同样整数下溢 (uint8)0-1=(uint8)255, (int8)(-128)-1=(int8)127

存在问题的合约

存在的问题

合约中函数 withdraw 的判断条件为 balances[msg.sender] - _amount > 0,当某个账户余额小于 _amount 时,由于下溢导致条件始终为真,导致将不属于该账户的余额提取

攻击过程

使用账户 0xca35b7d915458ef540ade6068dfe2f44e8fa733c 部署合约,并存入 66 以太坊

切换到账户 0xdd870fa1b7c4700f2bd7f44238821c26f7392148,查询合约中该账户余额为 0

然后执行合约函数 withdraw,尝试提取不属于自己的资产,如下所示

此时,账户 0xdd870fa1b7c4700f2bd7f44238821c26f7392148 余额变成了 122 以太坊(初始 100 以太坊 + 提取 22 以太坊)

以太坊智能合约安全:构造函数失控

构造函数失控

构造函数(Constructors)是特殊函数,在初始化合约时经常执行关键的权限任务。在 Solidity v0.4.22 以前,构造函数被定义为与所在合约同名的函数。因此,存在如下安全隐患:

进一步地,如果构造函数存在权限相关的任务,将导致权限的泄露。

示例合约

如下合约中,合约名称与“你以为的”构造函数名称不一致,加之,函数的默认可见性为 public,从而导致权限的泄露。

该合约储存以太坊,并只允许所有者通过调用 withdraw() 函数来取出所有 Ether。但由于构造函数的名称与合约名称不完全一致,这个合约会出问题。具体来说, ownerWallet 与 OwnerWallet 不相同。

因此,任何用户都可以调用 ownerWallet() 函数,将自己设置为所有者,然后通过调用 withdraw() 将合约中的所有以太坊都取出来。

预防措施

在 Solidity 0.4.22 版本的编译器中已经基本得到了解决。该版本引入了一个关键词 constructor 来指定构造函数,而不是要求函数的名称与合约名称匹配。建议使用这个关键词来指定构造函数,以防止上面显示的命名问题。

真实世界的例子:Rubixi

Rubixi 合约中的构造函数一开始叫做 DynamicPyramid ,但合约名称在部署之前已改为 Rubixi 。构造函数的名字没有改变,因此任何用户都可以成为 creator

以太坊智能合约安全:未初始化的存储指针

以太坊智能合约安全:从合约中提现

transfer 转账失败

Richest 合约中,任何人支付高于前一个人所支付的以太坊,即,成为最富有的人

存在的问题:攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest (可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。

详细的攻击过程如下:

send 转账失败

合约 Lotto 中,假设通过 setWinner 设置中奖者地址,正常情况下,任何人可以帮助中奖者兑奖 sendToWinner,并且取出剩余的少数以太坊。

存在的问题:合约 Lotto 中,给中奖者兑奖时,未检查 send 转账函数的返回值,即使 send 失败,仍然会将是否已兑奖的标识 payOut 置位,继而窃取剩余的大量的原来属于中奖者的以太坊。

详细的攻击过程如下:

使用账户 0xca35b7d915458ef540ade6068dfe2f44e8fa733c 部署合约 Lotto

此时兑奖失败,却标记为已兑奖

此时,账户余额变为 105 以太坊。

预防技术

EOS 入门

环境:Ubuntu 18.04/nodeos v1.4.3

启动 nodeos

默认生成的配置、数据目录位于 ~/.local/share/eosio/nodeos/,如下所示

监听端口如下所示

查看运行信息

创建钱包

钱包的密码为 PW5KVG15pS7McimPQz7J6jcx5Z1Bq3GJD5mZLok9QSScBMKPbi8f4

查看钱包列表

* 表示已解锁钱包

锁定解锁钱包

创建密钥

将密钥导入钱包

查看钱包管理的密钥

创建账户

cleos create account eosio token {public-OwnerKey} {public-ActiveKey} 执行创建新账户需要确保 public-OwnerKey 已经导入钱包并且具有权限。

在使用默认配置文件启动 nodeos,将内置了一个 owner 账户 eos,且对应的密钥对如下所示

因此,需要将此密钥对导入钱包,然后才可以利用账户 eos 授权创建新账户,否则报错,如下所示

Error 3090003: Provided keys, permissions, and delays do not satisfy declared authorizations Ensure that you have the related private keys inside your wallet and your wallet is unlocked.

查看账户

Libra 入门

创建交易

运行客户端并连接到测试网

创建两个账户

查看账户列表

挖矿并将奖励发放到相应账户(#0 表示第一个账户)

查询账户余额

查询账户序列号

账户之间转账(#0 转账 10 Libra 到 #1)

查询交易记录

上述转账需要异步查询,可以使用同步转账接口(交易提交成功才返回)

可以再次查询账户余额验证转账是否成功

私有网络

运行一个本地验证器节点,构成由单节点组成的私有网络

-p libra-swarm - causes cargo to run the libra-swarm package, which starts a local blockchain consisting of one node.

-s option starts a local client to connect to the local blockchain.

BTC

密钥和地址

一个比特币钱包中包含一系列的密钥对,每个密钥对包括一个私钥和一个公钥。私钥(k)是一个数字,通常是随机选出的。有了私钥,我们就可以使用椭圆曲线乘法这个单向加密函数产生一个公钥(K)。有了公钥(K),我们就可以使 用一个单向加密哈希函数生成比特币地址(A)。在本节中,我们将从生成私钥开始,讲述如何使用椭圆曲线运算将私 钥生成公钥,并最终由公钥生成比特币地址。

钱包地址的生成

img

  1. 首先使用随机数发生器生成一个『私钥』。一般来说这是一个256 bits 的数,拥有了这串数字就可以对相应『钱包地址』中的比特币进行操作,所以必须被安全地保存起来。

  2. 『私钥』经过 SECP256K1 算法处理生成了『公钥』。SECP256K1 是一种椭圆曲线算法,通过一个已知『私钥』时可以算得『公钥』,而『公钥』已知时却无法反向计算出『私钥』。这是保障比特币安全的算法基础

  3. 同 SHA256 一样,RIPEMD160 也是一种 Hash 算法,由『公钥』可以计算得到『公钥哈希』,而反过来是行不通的。

  4. 将一个字节的地址版本号连接到『公钥哈希』头部(对于比特币网络的 pubkey 地址,这一字节为“0”),然后对其进行两次 SHA256 运算,将结果的前 4 字节作为『公钥哈希』的校验值,连接在其尾部。

  5. 将上一步结果使用 BASE58 进行编码(比特币定制版本),就得到了『钱包地址』。

如下详细展示一个钱包地址的生成步骤

第一步,随机选取一个 32 字节的数、大小介于 1 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141 之间,作为私钥 18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 第二步,使用椭圆曲线加密算法(ECDSA-secp256k1)计算私钥所对应的非压缩公钥。 (共 65 字节, 1 字节 0x04, 32 字节为 x 坐标,32 字节为 y 坐标)

0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B 23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6 第三步,计算公钥的 SHA-256 哈希值 600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408 第四步,取上一步结果,计算 RIPEMD-160 哈希值 010966776006953D5567439E5E39F86A0D273BEE 第五步,取上一步结果,前面加入地址版本号(比特币主网版本号“0x00”) 00010966776006953D5567439E5E39F86A0D273BEE 第六步,取上一步结果,计算 SHA-256 哈希值 445C7A8007A93D8733188288BB320A8FE2DEBD2AE1B47F0F50BC10BAE845C094 第七步,取上一步结果,再计算一下 SHA-256 哈希值 D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30 第八步,取上一步结果的前 4 个字节(8 位十六进制) D61967F6 第九步,把这 4 个字节加在第五步的结果后面,作为校验(这就是比特币钱包地址的十六进制形态) 00010966776006953D5567439E5E39F86A0D273BEED61967F6 第十步,用 base58 表示法变换一下地址(这就是最常见的比特币钱包地址形态) 1M8DPUBQXsVUNnNiXw5oFdRciguXctWpUD

私钥、公钥、钱包地址

img

通过『私钥』可以得到上述计算过程中所有的值。 『公钥哈希』和『钱包地址』可以通过互逆运算进行转换,所以它们是等价的。

使用『私钥』对交易进行签名

img

比特币钱包间的转账是通过交易(Transaction)实现的。交易数据是由转出钱包『私钥』的所有者生成,也就是说有了『私钥』就可以花费该钱包的比特币余额。生成交易的过程如下:

  1. 交易的原始数据包括“转账数额”和“转入钱包地址”,但是仅有这些是不够的,因为无法证明交易的生成者对“转出钱包地址”余额有动用的权利。所以需要用『私钥』对原始数据进行签名。

  2. 生成“转出钱包公钥”,这一过程与生成『钱包地址』的第 2 步是一样的。

  3. 将“转出签名”和“转出公钥”添加到原始交易数据中,生成了正式的交易数据,这样它就可以被广播到比特币网络进行转账了。

使用『公钥』对签名进行验证

img

交易数据被广播到比特币网络后,节点会对这个交易数据进行检验,其中就包括对签名的校验。如果校验正确,那么这笔余额就成功地从“转出钱包”转移到“转入钱包”了。

结论

本文仅讨论标准 P2PKH(对公钥哈希的付款 ) 交易方式,P2SH 不在讨论范围内。

如果一个『钱包地址』从未曾发送余额到其他『钱包地址』,那么它的『公钥』是不会暴露在比特币网络上的。而公钥生成算法(SECP256K1)是不可逆的,即使『公钥』暴露,也很难对『私钥』的安全性造成影响(难易取决于『私钥』的生成算法)。

『私钥』用来生成『公钥』和『钱包地址』,也用来对交易进行签名。拥有了『私钥』就是拥有了对这个钱包余额的一切操作权力。

所以,保护『私钥』是所有比特币钱包应用最基本也是最重要的功能。

钱包

“钱包”一词在比特币中有多重含义。 广义上,钱包是一个应用程序,为用户提供交互界面。 钱包控制用户访问权限,管理密钥和地址,跟踪余额以及创建和签名交易。 狭义上,即从程序员的角度来看,“钱包”是指用于存储和管理用户密钥的数据结构。

非确定性钱包

非确定性钱包(nondeterministic wallet),其中每个密钥都是从随机数独立生成的。密钥彼此无关。这种钱包也被称为“Just a Bunch Of Keys(一堆密钥)”,简称 JBOK 钱包

确定性钱包

确定性钱包(deterministic wallet),其中所有的密钥都是从一个主密钥派生出来,这个主密钥即为种子(seed)。该类型钱包中所有密钥都相互关联,如果有原始种子,则可以再次生成全部密钥。确定性钱包中使用了许多不同的密钥推导方法。最常用的推导方法是使用树状结构,称为分级确定性钱包或 HD 钱包。

交易

比特币交易是比特币系统中最重要的部分。根据比特币系统的设计原理,系统中任何其他的部分都是为了确保比特币交易可以被生成、能在比特币网络中得以传播和通过验证,并最终添加入全球比特币交易总账簿(比特币区块链)。比特币交易的本质是数据结构,这些数据结构中含有比特币交易参与者价值转移的相关信息。比特币区块链是一本全球复式记账总账簿,每个比特币交易都是在比特币区块链上的一个公开记录。

五大标准脚本分别为 P2PKH、P2PK、MS(限15个密钥)、P2SH 和 OP_RETURN

通过 Bitcoin Core 解码的一笔标准的交易信息如下:

交易的输入输出

比特币交易中的基础构建单元是交易输出。 交易输出是比特币不可分割的基本组合,记录在区块上,并被整个网络识别为有效。 比特币完整节点跟踪所有可找到的和可使用的输出,称为 “未花费的交易输出”(unspent transaction outputs),即 UTXO。 所有 UTXO 的集合被称为 UTXO 集,目前有数百万个 UTXO。 当新的 UTXO被创建,UTXO 集就会变大,当 UTXO 被消耗时,UTXO 集会随着缩小。每一个交易都代表 UTXO 集的变化(状态转换) 。

一笔交易会消耗先前的已被记录(存在)的 UTXO,并创建新的 UTXO 以备未来的交易消耗。通过这种方式,一定数量的比特币价值在不同所有者之间转移,并在交易链中消耗和创建 UTXO。一笔比特币交易通过使用所有者的签名来解锁 UTXO,并通过使用新的所有者的比特币地址来锁定并创建 UTXO。

从交易的输出与输入链角度来看,有一个例外,即存在一种被称为“创币交易”(Coinbase Transaction)的特殊交易,它是每个区块中的第一笔交易,这种交易存在的原因是作为对挖矿的奖励,创造出全新的可花费比特币用来支付给“赢家”矿工。

交易输出

每一笔比特币交易都会创造输出,并被比特币账簿记录下来。除特例之外(见“数据输出操作符”(OP_RETURN)),几乎所有的输出都能创造一定数量的可用于支付的比特币,也就是 UTXO。这些 UTXO 被整个网络识别,所有者可在未来的交易中使用它们。

UTXO 在 UTXO 集(UTXOset)中被每一个全节点比特币客户端追踪。 新的交易从UTXO 集中消耗(花费)一个或多个输出。

尺寸字段说明
8 个字节总量用聪表示的比特币值(10^-8^ 比特币)
1–9 个字节(可变整数)锁定脚本尺寸用字节表示的后面的锁定脚本长度
变长锁定脚本一个定义了支付输出所需条件的脚本

交易输入

交易输入将 UTXO(通过引用)标记为将被消费,并通过解锁脚本提供所有权证明。

要构建一个交易,一个钱包从它控制的 UTXO 中选择足够的价值来执行被请求的付款。有时一个 UTXO 就足够,其他时候不止一个。对于将用于进行此付款的每个 UTXO,钱包将创建一个指向 UTXO 的输入,并使用解锁脚本解锁它。

尺寸字段说明
32 个字节交易指向交易包含的被花费的 UTXO 的哈希指针
4 个字节输出索引被花费的 UTXO 的索引号,第一个是 0
1–9 个字节(可变整数)解锁脚本尺寸用字节表示的后面的解锁脚本长度
变长解锁脚本一个达到 UTXO 锁定脚本中的条件的脚本
4 个字节序列号目前未被使用的交易替换功能,设成 0xFFFFFFFF

交易费

大多数交易包含交易费(矿工费),这是为了确保网络安全而给比特币矿工的一种补偿。费用本身也作为一个安全机制,使经济上不利于攻击者通过交易来淹没网络。

交易费作为矿工打包(挖矿)一笔交易到下一个区块中的一种激励;同时作为一种抑制因素,通过对每一笔交易收取小额费用来防止对系统的滥用。成功挖到某区块的矿工将得到该区内包含的矿工费, 并将该区块添加至区块链中。

交易费是基于交易的千字节规模来计算的,而不是比特币交易的价值。总的来说,交易费是根据比特币网络中的市场力量确定的。矿工会依据许多不同的标准对交易进行优先级排序,包括费用,他们甚至可能在某些特定情况下免费处理交易。但大多数情况下,交易费影响处理优先级,这意味着有足够费用的交易会更可能被打包进下一个挖出的区块中;反之交易费不足或者没有交易费的交易可能会被推迟,基于尽力而为的原则在几个区块之后被处理,甚至可能根本不被处理。交易费不是强制的,而且没有交易费的交易最终也可能会被处理,但是,交易费将提高处理优先级。

交易脚本和脚本语言

比特币交易验证并不基于静态模式而是通过脚本语言的执行来实现的。这种语言允许表达几乎无限的各种条件。这也是比特币作为一种“可编程的货币”所拥有的力量。

图灵非完备性

比特币脚本语言包含许多操作码,但都故意限定为一种重要的模式——除了有条件的流控制以外,没有循环或复杂流控制能力。这样就保证了脚本语言的图灵非完备性,这意味着脚本有限的复杂性和可预见的执行次数。脚本并不是一种通用语言,这些限制确保该语言不被用于创造无限循环或其它类型的逻辑炸弹,这样的炸弹可以植入在一笔交易中,引起针对比特币网络的“拒绝服务”攻击。记住,每一笔交易都会被网络中的全节点验证,受限制的语言能防止交易验证机制被作为一个漏洞而加以利用。

去中心化验证

比特币交易脚本语言是没有中心化主体的,没有任何中心主体能凌驾于脚本之上,也没有中心主体会在脚本被执行后对其进行保存。所以执行脚本所需信息都已包含在脚本中。可以预见的是,一个脚本能在任何系统上以相同的方式执行。如果您的系统验证了一个脚本,可以确信的是每一个比特币网络中的其他系统也将验证这个脚本,这意味着一个有效的交易对每个人而言都是有效的,而且每一个人都知道这一点。这种结果可预见性是比特币系统的一项至关重要的良性特征。

脚本构建(锁定与解锁)

比特币的交易验证引擎依赖于两类脚本来验证比特币交易:锁定脚本和解锁脚本。

锁定脚本是一个放置在输出上面的花费条件:它指定了今后花费这笔输出必须要满足的条件。 由于锁定脚本往往含有一个公钥或比特币地址(公钥哈希值),在历史上它曾被称为脚本公钥(scriptPubKey)。

解锁脚本是一个“解决”或满足被锁定脚本在一个输出上设定的花费条件的脚本,它将允许输出被消费。解锁脚本是每一笔比特币交易输入的一部分,而且往往含有一个由用户的比特币钱包(通过用户的私钥)生成的数字签名。由于解锁脚本常常包含一个数字签名,因此它曾被称作 ScriptSig。但并非所有解锁脚本都一定会包含签名。

每一个比特币验证节点会通过同时执行锁定和解锁脚本来验证一笔交易。每个输入都包含一个解锁脚本,并引用了之前存在的 UTXO。 验证软件将复制解锁脚本,检索输入所引用的 UTXO,并从该 UTXO 复制锁定脚本。 然后依次执行解锁和锁定脚本。 如果解锁脚本满足锁定脚本条件,则输入有效。 所有输入都是独立验证的,作为交易总体验证的一部分。

脚本执行堆栈

比特币的脚本语言被称为基于堆栈的语言,因为它使用一种被称为堆栈的数据结构。堆栈是一个非常简单的数据结构,可以被视为一叠卡片。栈允许两个操作:push 和pop(推送和弹出)。 Push(推送)在堆栈顶部添加一个项目。 Pop(弹出)从堆栈中删除最顶端的项。栈上的操作只能作用于栈最顶端项目。堆栈数据结构也被称为“后进先出”( Last-In-First-Out)或 “LIFO” 队列。

脚本语言通过从左到右处理每个项目来执行脚本。数字(数据常量)被推到堆栈上。操作码(Operators)从堆栈中推送或弹出一个或多个参数,对其进行操作,并可能将结果推送到堆栈上。例如,操作码 OP_ADD 将从堆栈中弹出两个项目,添加它们,并将结果的总和推送到堆栈上。

条件操作码(Conditional operators)对一个条件进行评估,产生一个 TRUE 或 FALSE 的布尔结果(boolean result)。例如, OP_EQUAL 从堆栈中弹出两个项目,如果它们相等,则推送为 TRUE(由数字1表示),否则推送为 FALSE(由数字0表示)。比特币交易脚本通常包含条件操作码,以便它们可以产生用来表示有效交易的 TRUE 结果。

P2PKH(Pay-to-Public-Key-Hash)

比特币网络处理的大多数交易花费的都是由“付款至公钥哈希”(或 P2PKH)脚本锁定的输出,这些输出都含有一个锁定脚本,将输入锁定为一个公钥哈希值,即我们常说的比特币地址。由 P2PKH 脚本锁定的输出可以通过提供一个公钥和由相应私钥创建的数字签名来解锁(使用)。

例如,我们可以再次回顾一下 Alice 向 Bob 咖啡馆支付的案例。Alice 下达了向 Bob 咖啡馆的比特币地址支付0.015 比特币的支付指令,该笔交易的输出内容为以下形式的锁定脚本:

OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG

脚本中的 Cafe Public Key Hash 即为咖啡馆的比特币地址,但该地址不是基于 Base58Check 编码。事实上,大多数比特币地址的公钥哈希值都显示为十六进制码,而不是大家所熟知的以1开头的基于 Bsase58Check 编码的比特币地址。

上述锁定脚本相应的解锁脚本是:

<Cafe Signature> <Cafe Public Key>

将两个脚本结合起来可以形成如下组合验证脚本:

<Cafe Signature> <Cafe Public Key> OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG

只有当解锁版脚本与锁定版脚本的设定条件相匹配时,执行组合有效脚本时才会显示结果为真(True)。即只有当解锁脚本得到了咖啡馆的有效签名,交易执行结果才会被通过(结果为真),该有效签名是从与公钥哈希相匹配的咖啡馆的私钥中所获取的。

主要验证两项,第一是 Public Key 是否能够转换成正确的地址,第二是 Signature 是否正确,也就是证明你是否是这个Public Key 的主人。

P2PK(Pay-to-Public-Key)

与 P2PKH 相比,P2PK 模式更为简单。与 P2PKH 模式含有公钥哈希的模式不同,在 P2PK 脚本模式中,公钥本身已经存储在锁定脚本中,而且代码长度也更短。P2PKH 是由 Satoshi 创建的,主要目的一方面为使比特币地址更简短,另一方面也使之更方便使用。P2PK 目前在 Coinbase 交易中最为常见。

P2PK 锁定版脚本形式如下:

<Public Key A> OP_CHECKSIG

用于解锁的脚本是一个简单签名:

<Signature from Private Key A>

经由交易验证软件确认的组合脚本为:

<Signature from Private Key A> <Public Key A> OP_CHECKSIG

该脚本只是 CHECKSIG 操作符的简单调用,该操作主要是为了验证签名是否正确,如果正确,则返回为真(True)。

根据上方的规则去运行就可以发现,此规则比 P2PKH 要简单的多,只有一步验证,少了上方的地址验证。其实,P2PKH 被创建主要目的一方面为使比特币地址更简短,使之更方便使用,核心内容还是 P2PK 的。

MS(Multiple Signatures)

多重签名脚本设置了这样一个条件,假如记录在脚本中的公钥个数为 N,则至少需提供其中的 M 个公钥才可以解锁。这也被称为 M-N 组合,其中,N 是记录在脚本中的公钥总个数,M 是使得多重签名生效的公钥数阀值(最少数目)。例如,对于一个 2-3 多重签名组合而言,存档公钥数为 3 个,至少同时使用其中 2 个或者 2 个以上的公钥时,才能生成激活交易的签名,通过验证后才可使用这笔资金。最初,标准多重签名脚本的最大存档公钥数被限定为 15 个,这意味着可采用 1-1 乃至15-15 的任意多重签名组合,或者组合的组合来激活交易。

通用的 M-N 多重签名锁定脚本形式为:

M <Public Key 1> <Public Key 2> ... <Public Key N> N OP_CHECKMULTISIG

其中,N 是存档公钥总数,M 是要求激活交易的最少公钥数。

2-3 多重签名条件:

2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG

上述锁定脚本可由含有签名和公钥的脚本予以解锁:

OP_0 <Signature B> <Signature C>

或者由 3 个存档公钥中的任意 2 个相一致的私钥签名组合予以解锁。

之所以要加上前缀 OP_0,是因为最早的 CHECKMULTISIG 在处理含有多个项目的过程中有个小漏洞,CHECKMULTISIG 会自动忽略这个前缀,它只是占位符而已。

两个脚本组合将形成一个验证脚本:

OP_0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG

当执行时,只有当未解锁版脚本与解锁脚本设置条件相匹配时,组合脚本才显示得到结果为真(True)。上述例子中相应的设置条件即为未解锁脚本是否含有与 3 个公钥中的任意 2 个相一致的私钥的有效签名。

OP_RETURN

比特币的分发和时间戳账户机制(也即区块链),其潜在运用将大大超越支付领域。许多开发者试图充分发挥交易脚本语言的安全性和可恢复性优势,将其运用于电子公证服务、证券认证和智能协议等领域。比特币脚本语言的早期运用主要包括在区块链上创造出交易输出。例如,为文件记录电子指纹,则任何人都可以通过该机制在特定的日期建立关于文档存在性的证明。

运用比特币区块链存储与比特币支付不相关数据的做法是一个有争议的话题。许多开发者认为其有滥用的嫌疑,因而试图予以阻止。另一些开发者则将之视为区块链技术强大功能的有力证明,从而试图给予大力支持。那些反对非支付相关应用的开发者认为这样做将引致“区块链膨胀”,因为所有的区块链节点都将以消耗磁盘存储空间为成本,负担存储此类数据的任务。更为严重的是,此类交易仅将比特币地址当作自由组合的 20 个字节而使用,进而会产生不能用于交易的 UTXO。因为比特币地址只是被当作数据使用,并不与私钥相匹配,所以会导致 UTXO 不能被用于交易,因而是一种伪支付行为。这样的做法将使得内存中的 UTXO 不断增加,而且这些不能被用于交易的数据同样也不能被移除,因此比特币节点将永久性地担负这些数据,这无疑是代价高昂的。

在 0.9 版的比特币核心客户端上,通过采用 OP_RETURN 操作符最终实现了妥协。OP_RETURN 允许开发者在交易输出上增加 40 字节的非交易数据。然后,与伪交易型的 UTXO 不同,OP_RETURN 创造了一种明确的可复查的非交易型输出,此类数据无需存储于 UTXO 集。OP_RETURN 输出被记录在区块链上,它们会消耗磁盘空间,也会导致区块链规模的增加,但它们不存储在 UTXO 集中,因此也不会使得 UTXO 内存膨胀,更不会以消耗代价高昂的内存为代价使全节点都不堪重负。

OP_RETURN 脚本的样式:

OP_RETURN <data>

“data”部分被限制为 40 字节,且多以哈希方式呈现,如 32 字节的 SHA256 算法输出。许多应用都在其前面加上前缀以辅助认定。例如,电子公正服务的证明材料采用 8 个字节的前缀“DOCPROOF”,在十六进制算法中,相应的 ASCII 码为44f4350524f4f46

请记住OP_RETURN 不涉及可用于支付的解锁脚本的特点,OP_RETURN 不能使用其输出中所锁定的资金,因此它也就没有必要记录在蕴含潜在成本的 UTXO 集中,所以 OP_RETURN 实际是没有成本的。OP_RETURN 常为一个金额为 0 的比特币输出,因为任何与该输出相对应的比特币都会永久消失。假如一笔 OP_RETURN 遇到脚本验证软件,它将立即导致验证脚本和标记交易的行为无效。如果你碰巧将 OP_RETURN 的输出作为另一笔交易的输入,则该交易是无效的。

一笔标准交易只能有一个 OP_RETURN 输出,但是单个 OP_RETURN 输出能与任意类型的输出交易进行组合。

P2SH(Pay-to-Script-Hash)

P2SH 地址是基于 Base58 编码的一个含有 20 个字节哈希的脚本,就像比特币地址是基于 Base58 编码的一个含有 20 个字节的公钥。由于 P2SH 地址采用 5 作为前缀,这导致基于 Base58 编码的地址以“3”开头。以“3”为前缀给予客户这是一种特殊类型的地址的暗示,该地址与一个脚本相对应而非与一个公钥相对应,但是它的效果与比特币地址支付别无二致。

P2SH 地址隐藏了所有的复杂性,因此,运用其进行支付的人将不会看到脚本。

与直接使用复杂脚本以锁定输出的方式相比,P2SH 具有以下特点:

P2SH 是 MS 多重签名的简化版本,如果使用 P2SH 进行和上方相同的 2-3 多重签名条件,步骤如下:

锁定脚本:

2 PK1 PK2 PK3 3 OP_CHECKMULTISIG

对锁定脚本,首先采用 SHA256 哈希算法,随后对其运用 RIPEMD160 算法,变成类似于 8ac1d7a2fa204a16dc984fa81cfdf86a2a4e1731 形式的 20 字节的脚本:

<lock scriptHash>

于是锁定脚本变为:

OP_HASH160 <lock scriptHash> OP_EQUAL

此锁定脚本要比原先使用 MS 的锁定脚本要简短的多,当接收方要使用此交易中的 UTXO 时,需要提交解锁脚本(这里又可称为赎回脚本):

<Sig1> <Sig2> <2 PK1 PK2 PK3 3 OP_CHECKMULTISIG>

两个脚本经由两步实现组合。首先,将赎回脚本与锁定脚本比对以确认其与哈希是否匹配:

<2 PK1 PK2 PK3 3 OP_CHECKMULTISIG> OP_HASH160 <redeem scriptHash> OP_EQUAL

假如赎回脚本与哈希匹配,解锁脚本会被执行以释放赎回脚本:

<Sig1> <Sig2> 2 PK1 PK2 PK3 3 OP_CHECKMULTISIG

验证过程分两步,首先验证的是接收方附上的赎回脚本是否符合发送方的锁定脚本,如果是,便执行该脚本,进行多重签名的验证。

P2SH 的特点是,将制作脚本的责任给了接收方,好处是可以暂缓节点存储的压力。

比特币网络

p2p 网络架构

节点类型及分工

区块链

区块数据存储

There are basically four pieces of data that are maintained:

So yes, everything but the block data itself is indeed redundant, as it can be rebuilt from it. But validation and other operations would become intolerably slow without them.

比特币程序从网络中接受数据后,会将数据以 .dat 的形式转储到磁盘上。一个块文件大约为 128 MB。每个块文件会有一个对应的撤销文件,比如文件 blocks/blk1234.datblocks/recv1234.dat 对应。

区块结构

区块是一种被包含在公开账簿(区块链)里的聚合了交易信息的容器数据结构。它由一个包含元数据的区块头和紧跟其后的构成区块主体的一长串交易组成。区块头是 80 字节,而平均每个交易至少是 250 字节,而且平均每个区块至少包含超过 500 个交易。因此,一个包含所有交易的完整区块比区块头的 1000 倍还要大。

大小字段描述
4 字节区块大小用字节表示的该字段之后的区块大小
80 字节区块头组成区块头的几个字段
1-9 (可变整数)交易计数器交易的数量
可变的交易记录在区块里的交易信息

区块头

区块头由三组区块元数据组成。首先是一组引用父区块哈希值的数据,这组元数据用于将该区块与区块链中前一区块相连接。第二组元数据,即难度、时间戳和 nonce,与挖矿竞争相关。第三组元数据是 merkle 树根(一种用来有效地总结区块中所有交易的数据结构)。

大小字段描述
4 字节版本版本号,用于跟踪软件/协议的更新
32 字节父区块哈希值引用区块链中父区块的哈希值
32 字节Merkle 根该区块中交易的 merkle 树根的哈希值
4 字节时间戳该区块产生的近似时间(精确到秒的 Unix 时间戳)
4 字节难度目标该区块工作量证明算法的难度目标
4 字节Nonce用于工作量证明算法的计数器

区块标识符

区块标识符:包括区块头哈希值和区块高度。

区块主标识符是它的加密哈希值,一个通过 SHA256 算法对区块头进行二次哈希计算而得到的数字指纹。产生的 32 字节哈希值被称为区块哈希值,但是更准确的名称是:区块头哈希值,因为只有区块头被用于计算。例如:000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 是第一个比特币区块的区块哈希值。区块哈希值可以唯一、明确地标识一个区块,并且任何节点通过简单地对区块头进行哈希计算都可以独立地获取该区块哈希值。

注意:区块哈希值实际上并不包含在区块的数据结构里,不管是该区块在网络上传输时,抑或是它作为区块链的一部分被存储在某节点的永久性存储设备上时。相反,区块哈希值是当该区块从网络被接收时由每个节点计算出来的。区块的哈希值可能会作为区块元数据的一部分被存储在一个独立的数据库表中,以便于索引和更快地从磁盘检索区块。

第二种识别区块的方式是通过该区块在区块链中的位置,即“区块高度(block height)”。第一个区块,其区块高度为 0,和之前哈希值 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 所引用的区块为同一个区块。因此,区块可以通过两种方式被识别:区块哈希值或者区块高度。每一个随后被存储在第一个区块之上的区块在区块链中都比前一区块“高”出一个位置,就像箱子一个接一个堆叠在其他箱子之上。

注意:区块高度也不是区块数据结构的一部分,它并不被存储在区块里。当节点接收来自比特币网络的区块时,会动态地识别该区块在区块链里的位置(区块高度)。区块高度也可作为元数据存储在一个索引数据库表中以便快速检索。

一个区块的区块哈希值总是能唯一地识别出一个特定区块。一个区块也总是有特定的区块高度。但是,一个特定的区块高度并不一定总是能唯一地识别出一个特定区块。更确切地说,两个或者更多数量的区块也许会为了区块链中的一个位置而竞争。

创世区块

区块链里的第一个区块创建于 2009 年,被称为创世区块。它是区块链里面所有区块的共同祖先,这意味着你从任一区块,循链向后回溯,最终都将到达创世区块。

因为创世区块被编入到比特币客户端软件里,所以每一个节点都始于至少包含一个区块的区块链,这能确保创世区块不会被改变。每一个节点都“知道”创世区块的哈希值、结构、被创建的时间和里面的一个交易。因此,每个节点都把该区块作为区块链的首区块,从而构建了一个安全的、可信的区块链的根。

区块的连接

区块通过引用父区块的区块头哈希值的方式,以链条的形式进行相连

比特币的完整节点保存了区块链从创世区块起的一个本地副本。随着新的区块的产生,该区块链的本地副本会不断地更新用于扩展这个链条。当一个节点从网络接收传入的区块时,它会验证这些区块,然后链接到现有的区块链上。为建立一个连接,一个节点将检查传入的区块头并寻找该区块的“父区块哈希值”。

Merkle 树

Merkle 树是一种哈希二叉树,它是一种用作快速归纳和校验大规模数据完整性的数据结构。这种二叉树包含加密哈希值。

在比特币网络中,Merkle 树被用来归纳一个区块中的所有交易,同时生成整个交易集合的数字指纹,且提供了一种校验区块是否存在某交易的高效途径。生成一棵完整的 Merkle 树需要递归地对哈希节点对进行哈希,并将新生成的哈希节点插入到 Merkle 树中,直到只剩一个哈希节点,该节点就是 Merkle 树的根。在比特币的 Merkle树中两次使用到了 SHA256 算法,因此其加密哈希算法也被称为 double-SHA256。

当 N 个数据元素经过加密后插入 Merkle 树时,你至多计算 2*log2(N) 次就能检查出任意某数据元素是否在该树中,这使得该数据结构非常高效。

挖矿与共识

ETH

以太坊的目的是基于脚本、竞争币和链上元协议(on-chain meta-protocol)概念进行整合和提高,使得开发者能够创建任意的基于共识的、可扩展的、标准化的、特性完备的、易于开发的和协同的应用。以太坊通过建立终极的抽象的基础层-内置有图灵完备编程语言的区块链-使得任何人都能够创建合约和去中心化应用,并在其中设立他们自由定义的所有权规则、交易方式和状态转换函数。域名币的主体框架只需要两行代码就可以实现,诸如货币和信誉系统等其它协议只需要不到二十行代码就可以实现。智能合约-包含价值而且只有满足某些条件才能打开的加密箱子-也能在我们的平台上创建,并且因为图灵完备性、价值知晓(value-awareness)、区块链知晓(blockchain-awareness)和多状态所增加的力量,而比比特币脚本所能提供的智能合约强大得多。

智能合约

何为智能合约?

以太坊上的程序称之为智能合约, 它是代码和数据(状态)的集合。

智能合约可以理解为在区块链上可以自动执行的(由消息驱动的)、以代码形式编写的合同(特殊的交易)。

编程语言:Solidity

智能合约编程语言有 Viper,Serpent,LLL 及 Bamboo,建议大家还是使用 Solidity。Serpent 官方已经不再推荐,建议 Serpent 的用户转换到 Viper,他们都是类 Python 语言。

智能合约的官方推荐的编程语言是 Solidity,文件扩展名以 .sol 结尾。 Solidity 语言和 JavaScript 很相似,用它来开发合约并编译成以太坊虚拟机字节代码。

运行环境:EVM

EVM(Ethereum Virtual Machine)以太坊虚拟机是以太坊中智能合约的运行环境。

合约的编译

以太坊虚拟机上运行的是合约的字节码形式,需要我们在部署之前先对合约进行编译,可以选择 Browser-Solidity Web IDE 或 solc 编译器。

合约的部署

在以太坊上开发应用时,常常要使用到以太坊客户端(钱包)。平时我们在开发中,一般不接触到客户端或钱包的概念,它是什么呢?

geth

geth是典型的开发以太坊时使用的客户端,基于 Go 语言开发。 geth 提供了一个交互式命令控制台,通过命令控制台中包含了以太坊的各种功能(API)。

相对于geth,mist 则是图形化操作界面的以太坊客户端。

如何部署

智能合约的部署是指把合约字节码发布到区块链上,并使用一个特定的地址来标示这个合约,这个地址称为合约账户。

以太坊中有两类账户:

合约部署就是将编译好的合约字节码通过外部账号发送交易的形式部署到以太坊区块链上(由实际矿工出块之后,才真正部署成功)。

运行

合约部署之后,当需要调用这个智能合约的方法时只需要向这个合约账户发送消息(交易)即可,通过消息触发后智能合约的代码就会在 EVM 中执行。

Gas

以太坊上用 Gas 机制来计费,Gas 也可以认为是一个工作量单位,智能合约越复杂(计算步骤的数量和类型,占用的内存等),用来完成运行就需要越多 Gas。 任何特定的合约所需的运行合约的 Gas 数量是固定的,由合约的复杂度决定。 而 Gas 价格由运行合约的人在提交运行合约请求的时候规定,以确定他愿意为这次交易愿意付出的费用:Gas 价格(用以太币计价) * Gas数量。

Gas 的目的是限制执行交易所需的工作量,同时为执行支付费用。当 EVM 执行交易时,Gas 将按照特定规则被逐渐消耗,无论执行到什么位置,一旦 Gas 被耗尽,将会触发 out of gas 异常。当前调用帧所做的所有状态修改都将被回滚, 如果执行结束还有 Gas 剩余,这些 Gas 将被返还给发送账户。

如果没有这个限制,就会有人写出无法停止(如:死循环)的合约来阻塞网络。

因此,实际上需要一个有以太币余额的外部账户,来发起一个交易(普通交易或部署、运行一个合约),运行时,矿工收取相应的工作量费用。

以太坊网络

使用以太坊正式网络

略。

使用以太坊官网测试网络 Testnet

测试网络中,我们可以很容易获得免费的以太币,缺点是需要发很长时间初始化节点。

使用私有链

创建自己的以太币私有测试网络,通常也称为私有链,我们可以用它来作为一个测试环境来开发、调试和测试智能合约。 通过 geth 很容易就可以创建一个属于自己的测试网络,以太币想挖多少挖多少,也免去了同步正式网络的整个区块链数据。

使用开发者网络(模式)

相比私有链,开发者网络(模式)下,会自动分配一个有大量余额的开发者账户给我们使用。

使用模拟环境

另一个创建测试网络的方法是使用 testrpc,testrpc 是在本地使用内存模拟的一个以太坊环境,对于开发调试来说,更方便快捷。而且 testrpc 可以在启动时帮我们创建 10 个存有资金的测试账户。进行合约开发时,可以在 testrpc 中测试通过后,再部署到 geth 节点中去。

nonce

为了防止交易重播,节点要求每笔交易必须有一个 nonce 数值。每一个账户从同一个节点发起交易时,这个 nonce 值从 0 开始计数,发送一笔 nonce 对应加 1。当前面的 nonce 处理完成之后才会处理后面的 nonce。注意这里的前提条件是相同的地址在相同的节点发送交易。

用同样的 nonce 值再次发送交易时,如果手续费高于原来的交易,那么第一笔交易将会被覆盖,如果手续费低于原来的交易就会发生异常:error: replacement transaction underpriced

如何保证 nonce 的可靠性?

keystore 文件与私钥

keystore 文件

节点目录 keystore 是用来存储你在这个区块链中创建的账户的密钥文件,例如,UTC--2018-07-12T06-48-30.819494813Z--91b678137f09c8b4f294a14e88c09276522618cf,格式为 json,内容如下

其中,各个字段信息如下

所以我们总结上面的过程,我们就知道该钥匙文件的使用过程为:

首先,你输入了密码 password,这个密码将作为 kdf 密钥生成函数的输入,来计算密文解密密钥

然后,将密文解密密钥ciphertext 密文连接并进行处理,和 mac 比较来确保密码是正确的。

最后,通过 cipher 对称函数用密文解密密钥ciphertext 密文解密,得到私钥

钥匙文件的文件名格式为 UTC。账号列出时是按字母顺序排列,但是由于时间戳格式,实际上它是按创建顺序排列。

所以,在整个过程中你的密码是惟一的输入,你的以太坊私钥是惟一的输出。所需的其他信息都可以在你的以太坊账户创建时生成的 keystore 文件中获得。

所以,一方面,要确保你的密码足够强,这样能保证即使有人得到了你的 keystore 文件,也无法获得你的私钥。另一方面, 要将钥匙备份文件备份保存,并且要牢记你自己的密码,如果你丢失了钥匙备份文件或者忘记了密码,那么你的币将丢失,密码是无法找回的。

私钥转 keystore

keystore 已经存放到了 --datadir 指定的目录中

keystore 导出私钥

使用 metamask 获取私钥

使用 keythereum 模块获取私钥
使用 web3 1.0 模块获取私钥
使用 ethkey 工具获取私钥

go-ethereum 自带的工具,需自行编译

Solidity

单位和全局变量

以太币单位

Ether 单位之间的换算就是在数字后边加上 weifinneyszaboether 来实现的,如果后面没有单位,缺省为 Wei。例如 2 ether == 2000 finney 的逻辑判断值为 true

时间单位

秒是缺省时间单位,在时间单位之间,数字后面带有 secondsminuteshoursdaysweeksyears 的可以进行换算,基本换算关系如下:

事实上,并非每年都是 365 天,由于闰秒的存在,甚至不是每天都是 24 小时。加上闰秒的不可预测性,只能借助外部系统来实现一个精确的日历库,例如 oracle

区块和交易属性

对于每一个外部函数调用,包括 msg.sendermsg.value 在内所有 msg 成员的值都会变化。这里包括对库函数的调用。

不要依赖 block.timestampnowblockhash 产生随机数,除非你知道自己在做什么。

时间戳和区块哈希在一定程度上都可能受到挖矿矿工影响。例如,挖矿社区中的恶意矿工可以用某个给定的哈希来运行赌场合约的 payout 函数,而如果他们没收到钱,还可以用一个不同的哈希重新尝试。

当前区块的时间戳必须严格大于最后一个区块的时间戳,但这里唯一能确保的只是它会是在权威链上的两个连续区块的时间戳之间的数值。

基于可扩展因素,区块哈希不是对所有区块都有效。你仅仅可以访问最近 256 个区块的哈希,其余的哈希均为零。

错误处理

EOS

EOS 节点的收益分配

分配的流程

每次有节点发起指令要领取奖励的时候,系统会计算:

简单概括就是,先计算各部分奖励池应该分配多少 EOS,即新注入多少 EOS;然后,根据规则,分配这部分新增的奖励,分配给对应的人节点。

EOS 通胀率为5%

EOS 是温和通胀设计的系统,每年增发的 EOS 比率为 5% 左右,按照总供应量10亿来计算,就是五千万 EOS。 而节点得到的奖励,只是通胀的一小部分,只是总 EOS 数量的 1%。

新增 EOS 的用途

实际上,EOS 的增发,是持续进行的,而并非是每年一次性新增加 EOS。每次 EOS 的节点发起领取奖励的命令,就会计算新增 EOS 的量。

通胀的 EOS,有两个用途:

Worker Proposal 这部分的奖励,现在已经开始累积,但是没有发放。

后续会部署新的智能合约到系统之中,经过持票人投票选出来认为对社区有益的 DAPP,来获得这部分奖金。

可以看得出来,EOS 设计之中对于生态的重视,Worker Proposal(可以称为:工作提案?)这部分每年足足有四千万左右的 EOS 奖励。(此处进行了简化,实际上随着 EOS 通胀,奖励的 EOS 数量会越来越多,比率为总流通量的4%)

出块节点和备用节点的收益

具体的节点奖励的这 1%,分为两部分:出块奖励(0.25%)和得票奖励(0.75%)。

出块奖励只是分配给出块的前 21 个节点,而得票奖励呢,则是用作所有节点(包括出块节点和备用节点)的奖励。

总结

FileCoin

存储证明算法

在分布式存储系统中,由于用户数据多存储在零散的存储商上,为了保证数据的完整性与可用性,都需要某种可靠的数据存储证明算法,而存储证明过程首先需要面临以下两个问题:

原来的分布式存储验证算法主要有Provable Data Possession (PDP)Proof-of-Retrievability (PoR),第一个主要保证存储商确实存储了用户的数据;第二个在前者的基础上还保证了用户数据可以被完整取回(不会出现存储商挟持数据不归还的情况)。

这两个证明算法的verify过程通常都需要由用户发起,这就需要用户程序经常保持在线;或者由一个可信的第三方机构来发起(如在Storj的方案中发起verify的角色由satellite来完成),这就造成对中心化机构的依赖;而且使用PDP与PoR的分布式网络存储方案还存在以下三个未解决的问题:

为解决以上三个问题,Filecoin提出了新的存储证明算法Proof-of-Replication (PoRep)Proof-of-Spacetime (PoSt),其中PoSt是在PoRep的基础上扩展的。以下简单描述下关键操作步骤:

关键操作Seal

Seal操作是保证整个PoRep算法安全性的关键,也是解决以上三个问题的关键;Seal操作使用伪随机排列PRP (pseudo-random permutation) 算法生成原始数据D的一个唯一乱序复本R,每次存储商存储的都是一份唯一的R而不是原始数据D,这就解决了第一个问题Sybil Attacks;而且Seal操作专门设计成非常费时的顺序计算(sequential operation),这个过程要比verify过程中的challenge-response通信要费时很多,通常在10x-100x倍率(可以调整时间难度系数τ ),使得Seal操作必须放在PoRep.Setup()中,而无法放在PoRep.Prove()操作中,如果存储商没有实际存储数据R而在PoRep.Prove()中使用Seal操作生成R,就会因为证明生成时间太长而很容易被验证为超时失败,这也同时解决了第二问题Outsourcing Attacks和第三个问题Generation Attacks

Proof-of-Spacetime (PoSt)

Proof-of-Spacetime(PoSt)在PoRep的基本上进行了扩展,用到了proof-chain的概念,proof-chain将多个时间间隔内PoRep.Prove操作生成的proof串联起来,每个proof都依赖于上一个proof的hash,从而保证整个链条不可被篡改。而Proof-of-Spacetime (PoSt)的时间证明还是依靠Seal操作的固有费时序列计算(slow sequential computation)特性;如果存储商没有实际存储数据,那在生成proof-chain的其中任何环节都会因为超时而被验证失败,使得已生成的proof-chain可以被认为是实际存储了相应的时间,而且只验证proof-chain可以使验证操作的频率大大降低;存储商可以使用PoSt生成一段时间的存储证明(proof-chain),而由Filecoin区块链的出块矿工在出块时对proof-chain进行验证操作,这样用户也无需进行验证操作。具体生成proof-chain证明的流程如下图:

DSN网络(Decentralized Storage Network)

所有运行Filecoin全节点的Storage Miner组成了整个DSN网络,并且共同管理着网络中所有空闲可用的存储空间,对存储证明proof进行验证和修复失效的存储。所有这些信息都记录在基于区块链的存储账本(Ledger)中。

网络参与角色

去中心化交易市场

整个网络中的所有存储需求与供给的汇合形成了两个去中心化自由交易市场,数据存储市场(Storage Market)和数据读取市场(Retrieval Market)。

存储账本(Ledger)的数据结构

DSN网络的运行流程

值得注意的是在各个数据存储与读取的交互流程中存在着很多AddOrders操作,而每个AddOrders操作都需要向区块链发送Transaction来使委托单(Order)被记录到委托单账本(Orderbooks) 中,另外包括存储商提交担保PledgeSector和提交证明ProveSector也涉及发送Transaction来记账,这样的去中心化交易效率有待考证。

DSN网络提供的特性

共识机制

Filecoin提出Useful Work Consensus的概念,即重新利用存储商(Storage Miner)原先用于计算存储证明(proof)而消耗掉的计算量来作为整个DSN网络共识的算力基础;并提出一个选举记账Miner的共识机制EC (Expected Consensus),存储商(Storage Miner)的投票权分配由AllocTable中的PoSt条目数量占总数的比例来决定,所以共识机制的安全来源于PoSt算法的安全性。

区块链技术

定义

区块链:由使用密码学原理使之连接和保证安全的块所组成的一个不断增长的数据记录列表。每一个区块包含一个前一个区块的密码学哈希值、一个时间戳,和交易数据。

分类

根据参与者的不同,可以分为公开(Public)链、联盟(Consortium)链和私有(Private)链。

公开链,顾名思义,任何人都可以参与使用和维护,典型的如比特币区块链,信息是完全公开的。

如果引入许可机制,包括私有链和联盟链两种。

私有链,则是集中管理者进行限制,只能得到内部少数人可以使用,信息不公开。

联盟链则介于两者之间,由若干组织一起合作维护一条区块链,该区块链的使用必须是有权限的管理,相关信息会得到保护,典型如银联组织。

商业价值

现代商业的典型模式为,交易方通过协商和执行合约,完成交易过程。区块链擅长的正是如何管理合约,确保合约的顺利执行。根据类别和应用场景不同,区块链所体现的特点和价值也不同。

从技术特点上,区块链一般被认为具有:

技术架构

  1. 数据层

数据层是最底层的技术,封装了底层区块数据的链式结构,以及数字签名、哈希函数和非对称加密技术等多种密码学算法和技术。主要实现了数据存储、账户和交易的实现与安全两个功能。

  1. 网络层

网络包括 P2P 网络机制、数据传播机制和数据验证机制等,主要实现网络节点的连接和通讯。

  1. 共识层

共识层主要封装网络节点的各类共识机制算法,实现全网所有节点对交易和数据达成一致,防范拜占庭攻击、女巫攻击和51% 攻击等共识攻击,其算法称为共识机制。比较常见的共识算法有工作量证明机制(PoW)、权益证明机制(PoS)、拜占庭容错算法(BFT)等。

  1. 激励层

激励层主要实现区块链代币的发行和分配机制,该层主要出现在公有链中,用以激励遵守规则参与记账的节点,惩罚不遵守规则的节点,促使整个系统朝着良性循环的方向发展。

  1. 合约层

合约层主要封装各类脚本、算法和智能合约,赋予账本可编程的特性。通过虚拟机的方式运行代码实现智能合约的功能,比如以太坊的以太坊虚拟机。

  1. 应用层

应用层封装了区块链的各种应用场景和案例,如搭建在以太坊上的各类去中心化的应用。

核心技术

区块链技术不是一个单项的技术,而是一个集成了多方面研究成果基础之上的综合性技术

系统。我们认为,其中有三项必不可缺的核心技术,分别是:共识机制、密码学原理和分布式

数据存储。

共识机制

所谓共识,是指多方参与的节点在预设规则下,通过多个节点交互对某些数据、行为或流程达成一致的过程。共识机制是指定义共识过程的算法、协议和规则。

区块链的共识机制具备“少数服从多数”以及“人人平等”的特点,其中“少数服从多数” 并不完全指节点个数,也可以是计算力、股权数或者其他的计算机可以比较的特征量。“人 人平等”是当节点满足条件时,所有节点都有权优先提出共识结果、直接被其他节点认同后并最后有可能成为最终共识结果。

密码学原理

在区块链中,信息的传播按照公钥、私钥这种非对称数字加密技术实现交易双方的互相信任。在具体实现过程中,通过公、私密钥对中的一个密钥对信息加密后,只有用另一个密钥才 能解开的过程。并且将其中一个秘钥公开后(即为公开的公钥),根据公开的公钥无法测算出另一个不公开的密钥(即为私钥)。

分布式存储

区块链中的分布式存储是参与的节点各自都有独立的、完整的数据存储。

跟传统的分布式存储有所不同,区块链的分布式存储的独特性主要体现在两个方面:一是区块链每个节点都按照块链式结构存储完整的数据,传统分布式存储一般是将数据按照一定的规则分成多份进行存储。二是区块链每个节点存储都是独立的、地位等同的,依靠共识机制保证存储的一致性,而传统分布式存储一般是通过中心节点往其他备份节点同步数据。数据节点 可以是不同的物理机器,也可以是云端不同的实例。

理论与技术点

图灵完全

图灵完全性通常指具有无限存储能力的通用物理机器或编程语言。简单来说,一切可计算的问题都能计算(给定足够的时间和存储),这样的虚拟机或者编程语言就叫图灵完备的。

base58

用于 bitcoin 中使用的一种独特的编码方式,主要用于产生 bitcoin 的钱包地址。相比 base64,base58 不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+"和"/"符号。 设计 base58 主要的目的是:

  1. 避免混淆。在某些字体下,数字 0 和字母大写 O,以及字母大写 I 和字母小写 l 会非常相似。

  2. 不使用"+"和"/"的原因是非字母或数字的字符串作为帐号较难被接受。

  3. 没有标点符号,通常不会被从中间分行。

  4. 大部分的软件支持双击选择整个字符串。

base58 与 base64 的差异:base64 编码中使用直接截取 3 个字符转 4 个字符的方法进行转换,而是采用我们数学上经常使用的进制转换方法——辗转相除法。

重放攻击与保护

重放攻击(Replay attack)分为链之间的重放攻击和链内重放攻击两种。

链之间的重放攻击主要是针对分叉链。我们先通过 BCC 的例子来讲解链之间的重放攻击。

BCC 是 BTC的一条分叉链,在未作特殊处理的情况下,两条链是一模一样的。

假设 Alice 在分叉前拥有 10 BTC 在地址:18mpPyy6Z1rUVnjrKTsznHxJ4Wku6VqrJV 上,那么分叉后她也将拥有 10 BCC 在同样的地址上。这时 Alice 将这 10 个BTC 转账给其好友 Bob 的地址:1GpysEyaGJE6Zu8mQ6eV1e1hvxZ3XY39ni 上。当 Bob 在 BTC 网络上收到这笔交易后,只要简单的将该交易信息广播至 BCC 网络上,Alice 的 10 个 BCC 也将自动转给了 Bob,于是 Bob 轻松地盗取了 Alice的 10 个 BCC。

以上例子就是重放攻击。因为两条链一模一样,所有的地址、签名、交易在两条链上都有效。未为重放攻击做特殊处理的分叉链也被叫做没有重放保护的分叉链,正如我们上面的例子看到的,这种情况是很危险的。因此绝大多数分叉链是做了重放保护的。那么如何做重放保护呢?

我们还是通过 BCC 的例子来看如何做重放保护。我们上面说了,两条分叉链的所有地址、签名和交易都是有效的。很显然我们需要让在 BTC 的上交易在 BCC 网络上变得无效,反之亦然。BCC 是做法是在做签名的时候,在 SIGHASH_TYPE 上增加了一个 FORK_ID 的位,并将自己的 FORK_ID 设置为 0x40。也就是说 BCC 网络使用了区别于 BTC 网络的SIGHASH_TYPE。因此 BCC 交易的签名结果在 BTC 网络上验证不通过,反之亦然。这样便实现的 BCC/BTC 网络的双向重放保护。

因此 BCC 分叉后普通用户不需要做什么特殊处理,相应的资产已经得到的内置的双向重放保护。但是本月(11月)中旬可能要进行的 2X 分叉则不然。因为即将要分叉的 B2X 和原先的 BTC(SegWit1x)都认为自己都正统的比特币,都不愿意在自己的签名算法上加上 FORK_ID 的参数。因此极有可能出现两条一模一样的分叉链,即没有任何的重放保护。即你在 B2X 网络上一笔转账(转给其他人的地址),可以轻松被重放的 BTC 网络上,从而偷走你的币,反之亦然。

这种情况下我们(普通用户)需要主动采取措施进行重放保护,这很重要,具体步骤如下:

  1. 下载 B2X 钱包,用你的助记词恢复出 B2X 分叉币

  2. 在 BTC 钱包和 B2X 钱包各自创建两个不同的新地址,如 BTC 钱包里创建地址 A,B2X 钱包里创建地址 B

  3. 将 BTC 钱包里的所有币转至地址 A,将 B2X 钱包里所有币转至地址 B

这样便完成了主动的重放攻击保护。因为你在不同网络里的币位于不同的地址上,以后交易都将采用不同的私钥签名,即使在签名算法、SIGHASH_TYPE 完全一致的情况下,在 BTC 里生效的交易将在 B2X 网络无法生效,因为其找不到对应的UTXO,反之亦然。

另外一种重放攻击是链内的重放攻击。这种攻击在比特币这样基于 UTXO 的网络里并不存在,而在以太坊这样基于账户系统的网络则非常普遍。因此以太坊实施了内置的重放保护措施,它是如何做的呢?

以太坊通过每个账户内置的 nonce 值来解决链内重放攻击的问题。nonce 值从 0 开始计数,每发出一个交易加 1。矿工在接受交易时比较交易里携带的 nonce 值和该账户当前 worldstate 中的 nonce 值,如果一致则接受交易,否则丢弃。

链内重放攻击相对容易解决,而链之间的重放攻击则需要分叉链主动做出保护,否则很容易使用户资产受到损失。没有哪个用户期望分叉的产生,然而如果分叉必须要产生,也请相应开发人员主动做出重放保护,而不是因为利益之争弃用户利益于不顾。作为普通用户则不能寄希望于相应分叉者们的良心发现,提前做好准备,保护好自己的资产。

DPoS

DPoS 共识算法中,区块链的正常运转依赖于受托人(Delegates),这些受托人是完全等价的。受托人的职责主要有:

  1. 提供一台服务器节点,保证节点的正常运行;

  2. 节点服务器收集网络里的交易;

  3. 节点验证交易,把交易打包到区块;

  4. 节点广播区块,其他节点验证后把区块添加到自己的数据库;

  5. 带领并促进区块链项目的发展;

受托人的节点服务器相当于比特币网络里的矿机,在完成本职工作的同时可以领取区块奖励和交易的手续费。

伪代码实现如下:

在每一轮循环里,系统会重新统计得票排名。在选出最高的 N 个受托人里,系统采用先打乱顺序,然后受托人依此生产区块。一轮区块生产完毕后进入下一个周期。

DPoS 和代议制民主以及董事会制度一样,都是一种精英制度

无独有偶,除了政治之外,作为经济主体的公司,尤其是最为重要的股份制有限公司,用的也都是现代企业的董事会制度,这项制度,和 DPOS 与代议民主框架也基本一致;本质上,DPoS 和代议制民主以及董事会制度一样,都是一种精英制度;但这种精英制度,受制于下面的民众,在美国,议员是被全民选举出来;在 DPoS 中,代币持有者至少有权决定见证人或者说矿工的身份;相比于变了味的 PoWPoS,这反而更加符合去中心化和平等的精神。

区块链世界,去中心化、速度、安全也只能选其二

这个世界上,有很多的不可能三角,或者说三元悖论;经济学里最为出名的,无疑是那个“一个国家不可能同时实现资本流动自由,货币政策的独立性和汇率的稳定性”的不可能三角;传统商业社会,质量好、速度快、安全是一个不可能三角——三者之间只能实现两个;区块链世界,去中心化、速度、安全也只能选其二。相应的,去中心化,被牺牲成了弱中心化,或是多中心化的模式。

共识机制里,DPoS 在相当一段长的时间内,无法被超越

去中心化只是手段,不是目的;不能为了去中心而去中心,如果一件事,半中心化甚至中心化可以做的更好,为什么一定要坚持完全的去中心化呢? 万物皆为取舍,DPoS 舍弃了部分的去中心化,换来的却是性能和安全几何倍数的提高;世界不断向前,人性与规律亘古不变,马太效应、二八定律、长尾理论这些模型,无论是传统世界,还是区块链世界,同样适用,比特币也好,区块链也好,去中心化并不会改变这些东西;传统世界, 全球 1% 的人已经拥有了 50% 的财富;区块链世界,40% 的比特币被掌握在约 1000 个人手中;DPoS 完美么? 当然不,但在人类进化到共产主义之前,在政治制度依旧以代议民主为核心,公司制度依旧以董事会为核心的当下,还想不出任何共识机制,能比 DPoS 更加先进,这种先进性,并非来源于技术,而是来源于我们人类本身;所以说共识机制里,DPoS 在相当一段长的时间内,可能无法超越。

两个缺陷

对于第一个问题,DPoS 没有很好的应对方案,只能寄希望于委托人拥有良好的服务器运维经验,如果他们稍微粗心,就会出现卡块、分叉的危险。 第二个问题,DPoS 主要采取的应对方案是对委托人随机排序和最长链同步的方法。

DPoS + pBFT

加入 pBFT 后,DPoS 算法的前半部分不变,即委托人名单的确定方式和排序算法不变。

变化的是后半部分,即区块的验证和持久化。 区块的验证,不再采用单一的签名验证,而是全节点投票的方式,每当新区块创造出来时,忠诚的节点并不会立即将其写入区块链,而是等待其他节点的投票。 当这个票数超过一定数量后,才进入执行阶段。

本算法假定错误节点数不超过 f 个,总结点数 n >= 3f + 1,那么系统可以通过满足以下两个条件来保证区块链的一致性

  1. 如果一个正确的节点接受并执行了一个 block,那么所有正确的节点都提交相同的 block

  2. 所有正确的节点要么落后于最长链,要么与最长链一致,不会出现高度相同但 block hash 不同的情况

算法流程如下:

  1. 当前时间片的锻造者将收集到的交易打包成 block 并进行广播(这一步与之前的算法一致)

  2. 收到 block 的委托人节点如果还没有收到过这个 block 并且验证合法后,就广播一个 prepare<h, d, s> 消息,其中 hblock 的高度,dblock 的摘要,s 是本节点签名

  3. 收到 prepare 消息后,节点开始在内存中累加消息数量,当收到超过 f+1 不同节点的 prepare 消息后,节点进入prepared 状态,之后会广播一个 commit<h, d, s> 消息

  4. 每个节点收到超过 2f + 1 个不同节点的 commit 消息后,就认为该区块已经达成一致,进入 committed 状态,并将其持久化到区块链数据库中

  5. 系统在在收到第一个高度为 hblock 时,启动一个定时器,当定时到期后,如果还没达成一致,就放弃本次共识。

需要说明的是,本算法与 pBFT 论文中的算法有所不同。一个是取消了 commit-local 的状态,另一个是没有视图变化的过程。因为 pBFT 最初提出来主要是面向的一般的 C-S 架构的服务系统,服务器对与客户端的每一个请求都要有所响应,但是在区块链系统中,区块的生成是可以延迟到下一个时间片的,而且这种延迟很常见,这跟视图变化本质上是一样的,每一个时间片相当于一个视图,slot = time_offset / interval,这个 slot 就相当于视图编号。

由于取消了视图变化,达成一次共识的性能也大幅度提升。假设系统中总共有 N 个节点,包括委托人节点和普通节点。系统的消息传播使用的 gossip 算法,一次广播需要传递的消息上限是 N^2,对应的时间开销为 O(logN)。假如普通节点只接收不转发,那么 N 可以降为委托人的节点总数 n,因为系统中委托人数量一定时期内保持不变,可以认为一次广播的时间开销为常数 t。确认一个 block 需要 3 轮广播,也就是总时间为 3tblock 消息大小设为 Bpreparecommit 的消息大小设为 b,那么总共的带宽消耗为 (B+2b)N^2

多重签名

什么是多重签名

多重签名(Multi Signature)指的是需要多个密钥来授权一个数字货币交易,它通常被用来界定对数字货币的所有权。传统的数字资产账户中,你的数字货币地址中,每一个地址都有一个对应的私钥,可以称为“单签名交易”。而多重签名地址,可以有多个相关联的私钥,你需要其中的多个才能完成一笔转账。

对于 M-N 多重签名,其中 1≤M ≤ N,就是由 N 个人分别持有 N 个私钥,至少需要其中 M 个人同意签名才可以动用某个“多签地址”的资金。实际上,你也可以设置成1/3,5/5,6/11,但是最常见的是2/3的组合,即,需要 3 个人中的至少 2 个人同意签名才能动用这个"多签地址"的资金。

多重签名的作用

如果采用单独的私钥,尽管以目前的密码学可以保证无法被暴力破解,但是这个私钥不保证会以其他方式(如黑客通过木马,自己不小心暴露等)暴露出去的话,那么对应的数字资产也同时暴露无遗。此时如果公钥是由多重签名方式生成,那么即便被盗取了其中一个私钥,盗取者也无法转移对应的数字资产。

资金监管:一笔钱需要多个人签名才能使用,任何一个人都无法直接动用资金。例如,由三个合伙人共有的资金账户,至少需要其中两人同意才能使用该资金账户的资金,防止任何一个人非法挪用资金。

电子商务:在买家与卖家的基础上,加入一个仲裁角色的"中介者",通过 2-3 多重签名模式,当买家与卖家发生纠纷时,仲裁者根据实际情况,确保资金公平地划分,保护交易中诚实的买家或卖家。

维基链的多重签名实现方式

维基链作为第三代公有链,拥有图灵完备的智能合约,因此,一方面,智能合约天然支持多重签名。另一方面,维基链将多重签名交易作为一种基础的交易类型实现,具有如下特点:

  1. 全面的 rpc 支持:例如,生成多签地址、查看多签地址脚本、创建多签交易、给多签交易签名等,进一步降低用户使用门槛

  2. 降低多签交易大小:借助于维基链特有的账户唯一对应的 RegID(注册 ID,能够唯一标识某一个维基链账户),序列化之后大小为 6 字节左右,相对于比特币多签脚本中公钥的长度 33 字节,极大地减少了交易结构的数据量。

维基链多重签名具有使用简单、实现高效的特点,通过对多重签名交易的支持,更好地满足用户与开发者的需求。

混币原理

就是割裂输入地址和输出地址之间的关系。在一个交易中,假如有很多人参与,其中包括大量输入和输出,这样会比较难在输入和输出中找出每个人的对应对,这样一来,输入与输出之间的联系被事实上割裂。多次混币、每次少量币,效果更好。

环签名

简化的类群签名。环中一个成员利用他的私钥和其他成员的公钥进行签名,但却不需要征得其他成员的允许,而验证者只知道签名来自这个环,但不知到谁是真正的签名者。这个方式解决了对签名者完全匿名的问题,允许一个成员代表一组人进行签名而不泄漏签名者的信息。

同态加密

是一种无需对加密数据进行提前解密就可以执行计算的方法。通过使用同态加密技术在区块链上存储数据可以达到一种完美的平衡,不会对区块链属性造成任何重大的改变。也就是说,区块链仍旧是公有区块链,但区块链上的数据将会被加密。

零知识证明

是一种在无需泄露数据本身情况下证明某些数据运算的一种零知识证明,允许两方(证明者和验证者)来证明某个提议是真实的,而且无需泄露除了它是真实的之外的任何信息。在密码学货币和区块链中,这通常是指交易信息数据。

可验证随机函数(VRF)

随机预言机(Random Oracle,RO),就是可以通过任意的一个输入,获得一个随机数输出。可验证随机函数比随机预言机多了一个非交互的零知识证明,可以用来该随机数输出的正确性,表明这个随机数的确是某个人生成的。

先说 RO,有两个特征:

  1. 对于不同的 Input,Output 的值是随机的,并且均匀分布在值域范围内

  2. 对于相同的 Input,它得到的 Output 一定是相同的

举例说明一下,假设某公链网络用普通的 RO 选节点,有可能是这样的情况:假设全网有 100 个节点,我想生成下一轮一个节点谁打包,我以某一轮的轮次作为输入,然后随机输出的值必须要是在 1-100 之间的自然数(因为网络中只有 100 个节点)。这就每一轮都选出了一个打包节点的人。

这里的问题是,由于输入对应的输出肯定是相同的,而输入是公开的,就使得每一轮的抽签结果变得可以被预知,攻击者可以尝试控制这个过程或者攻击特定的节点。可是如果输入不公开的话,我们要怎么保证这个输入结果没有问题呢?VRF 就用到了零知识证明,让结果“可验证”。

VRF 的方式是,让各个节点自己抽签,如果抽中了之后,大家可以很容易地验证这个结果确实是你生成的。具体过程有可能是这样的:假设现在是 round 10(第 10 轮),节点们可能会轮流抽签,以节点自己的私钥 + 一个全网都知道的随机数(比如是这轮的轮次 10)作为输入,生成了一个随机数(0-100);设置一个条件:100 个节点轮流抽签,谁先抽出来的随机数大于 10,就是这一轮的打包者。假设 5 号节点抽到了 11,可是只有 5 号知道其他人不知道,因此他在广播这个随机的同时还需要广播一个零知识证明。通过零知识证明,全网只需要通过 5 号的公钥就可以验证,接受 5 号为这轮打包者。普通 RO 在私钥 + 零知识证明的作用下,抽签过程就可以在本地进行、不公开私钥同时又可以全网验证。

可验证随机函数一共包含四个函数:

  1. 生成密钥,生成一个公钥私钥对

  2. 生成随机数输出

  3. 计算零知识证明

  4. 验证随机数输出

应用场景

区块链属于一种去中心化的记录技术。参与到系统上的节点,可能不属于同一组织,彼此互不信任;区块链数据由所有节点共同维护,每个参与维护节点都能复制获得一份完整记录的拷贝。跟传统的数据库技术相比,其特点应该包括:

更进一步的,还可以将智能合约跟区块链结合到一起,让其提供除了交易功能外更灵活的合约功能,执行更为复杂的操作(实际上,比特币区块链已经支持简单的脚本计算)。这样扩展之后的区块链,已经超越了单纯数据记录的功能了,实际上带有“普适计算”的意味了。

定位功能智能合约一致性权限类型性能代表
公信的数字货币记账功能不带有或较弱PoW公有链较低比特币
公信的交易处理智能合约图灵完备PoW、PoS公有链受限以太坊
带权限的交易处理商业处理多种语言,图灵完备多种,可插拔支持联盟链可扩展Hyperledger

存证业务

联盟链和公有链结合的存证业务

存证业务的流程相当明晰,首先是双方基于电子签名技术,在电子合同上签约,第三方存证平台提供一个服务,将合同文件进行哈希后,将哈希值妥善存储即可。

但这里存在一个问题,那就是第三方平台本身的信用。第三方平台出于某种利益,是否会更改合同的哈希值,一旦更改,该合同的有效性就会受到质疑,从而引发纠纷。

为了解决这个问题,公证通(Factom)等公司选择将文件的哈希值存入比特币的区块链中,由于比特币区块链的不可更改性,即便是公证通这家存证公司本身都无法篡改其哈希值,从而在根本上保障了合同真伪校验的有效性。

但是,使用公有链依然具有局限性,一是公有链的确认速度很慢,二是公有链的交易手续费会越来越高,导致业务成本高。

因此,我建议将联盟链置于用户数据和公有链之间。如下图所示,是保全网的方案,采用了 Factom 的技术,而联盟链公司可以将中间的锚定链替换为自身的联盟链,从而提高用户存证的确认速度,并且多个数据最终做一次哈希,可大大降低比特币网络的交易次数,从而降低成本。

img

联盟链的作用和公有链的作用

最终必然有一个疑问,既然底层最终锚定了公有链,那么联盟链的作用在哪里。我认为联盟链的作用有三个,一是,如果某个机构希望在本地拥有其文件的哈希数据,那么只需要布置一个联盟链的节点即可。二是,联盟链可以大大提高存证的确认速度,同时联盟链未来可以支持更高的存证并发数。三是,即便最终想要切断联盟链和公有链的联系,有一个联盟链在中间层做保障,也是非常方便的一件事。

当联盟链中的节点足够多之后,其存证的公信力也将愈发强大,但在联盟链推动的初期,若没有底层公有链的信用加成,前期推动的难度非常大,要花费极多的口舌去进行市场教育,这也是联盟链公司一个不可忽视的机会成本。

电子合同及合同存证的巨大价值

人类社会之所以得到发展,“契约精神”起到了不可磨灭的作用,但是持续了几千年的纸质“契约”,纸质合同,已经难以跟上这个飞速发展的互联网世界。

设想一个人从小到大的成长过程,要接触各种各样的合同:入学有合同、银行开户有合同、买房有合同、买车有合同,说得夸张一些,可谓是“凡事皆契约”,“一切皆合同”。甚至我让同事帮我买一杯奶茶,欠个15块钱,本质上也是一个口头契约。但如果所有的合同都是纸质的,谁又能保存和检索如此多的纸质合同呢?

对企业来说更是如此,仅仅是员工的聘用合同,这一类的合同,就需要整个HR部门花费巨大的力气保存和检索,万一丢失纸质合同,又会引发一系列的纷争。所以,这是一个亟待解决的重要问题。

电子合同的网上签约、第三方存管、合同真伪查询、一站式的合同模板提供、合同规范性的校验,这些都是一个大大的蓝海市场,切不可错过。

存证业务的进一步拓展

电子合同仅仅是存证业务中的一种而已,之所以选择从合同的存证切入市场,是因为这个场景确实在现阶段成为了企业和个人的一个痛点。

未来,除了合同以外的任何电子证据都可以存储在区块链存证平台中,例如人的身份证明,或者票据、学历证明、工作履历等等。

因此,区块链可以成为整个社会信用体系得以构建的底层基础设施。

比特币数据记录

落地应用:docproof

比特币的去中心特点和时间戳账本机制,即区块链技术,其潜在运用将大大超越支付领域。许多开发者试图充分发挥交易脚本语言的安全性和可恢复性优势,将其运用于电子公证服务、证券认证和智能合约等领域。很多早期的开发者利用比特币这种能将交易数据放到区块链上的技术进行了很多尝试,例如,为文件记录电子指纹,则任何人都可以通过该机制在特定的日期建立关于文档存在性的证明。

运用比特币的区块链技术存储与比特币支付不相关数据的做法是一个有争议的话题。许多开发者认为其有滥用的嫌疑,因而试图予以阻止。另一些开发者则将之视为区块链技术强大功能的有力证明,从而试图给予大力支持。那些反对非支付相关应用的开发者认为这样做将引致“区块链膨胀”,因为所有的区块链节点都将以消耗磁盘存储空间为成本,负担存储此类 数据的任务。

更为严重的是,此类交易仅将比特币地址当作自由组合的 20 个字节而使用,进而会产生不能用于交易的 UTXO。因为比特币地址只是被当作数据使用,并不与私钥相匹配,所以会导致 UTXO 不能被用于交易,因而是一种伪支付行为。因此,这些交易永远不会被花费,所以永远不会从 UTXO 集中删除,并导致 UTXO 数据库的大小永远增加或“膨胀”。

在 0.9 版的比特币核心客户端上,通过采用 OP_RETURN 操作符最终实现了妥协。OP_RETURN 允许开发者在交易输出上增加 80 字节的非交易数据。然后,与伪交易型的 UTXO 不同,OP_RETURN 创造了一种明确的可复查的非交易型输出,此类数据无需存储于 UTXO 集。OP_RETURN 输出被记录在区块链上,它们会消耗磁盘空间,也会导致区块链规模的增加,但它们不存储在 UTXO 集中,因此也不会使得 UTXO 内存膨胀,更不会以消耗代价高昂的内存为代价使全节点都不堪重负。OP_RETURN 脚本的样式:

“data”部分被限制为 80 字节,且多以哈希方式呈现,如 32 字节的 SHA256 算法输出。许多应用都在其前面加上前缀以辅助认定。例如,电子公正服务的证明材料采用 8 个字节的前缀“DOCPROOF”,在十六进制算法中,相应的 ASCII 码为 44 4f 43 50 52 4f 4f 46。

请记住 OP_RETURN 不涉及可用于支付的解锁脚本的特点, OP_RETURN 不能使用其输出中所锁定的资金,因此它也就没有必要记录在蕴含潜在成本的 UTXO 集中,所以 RETURN 实际是没有成本的。

OP_RETURN 常为一个金额为 0 的比特币输出, 因为任何与该输出相对应的比特币都会永久消失。假如一笔 OP_RETURN 被作为一笔交易的输入,脚本验证引擎将会阻止验证脚本的执行,将标记交易为无效。如果你碰巧将 OP_RETURN 的输出作为另一笔交易的输入,则该交易是无效的。

举例

交易 0xb17a027a8f7ae0db4ddbaa58927d0f254e97fce63b7e57e8e50957d3dad2e66e

交易 0xe89e09ac184e1a175ce748775b3e63686cb1e5fe948365236aac3b3aef3fedd0

如下,两个交易输出脚本只包含数据(十六进制),解码分别对应

Yuki will you marry me ? Tetsu.

Yes I will. Yuki.

以太坊数据记录

以太坊在交易中数据字段中添加待记录的数据,如下所示

维基链 WaykiChain 源码

文件功能描述

目录/源码简单功能说明
jsonJSON和对象互转处理
leveldbLevel DB 数据库交互
rpc节点RPC交互接口实现
vmLua虚拟机执行核心逻辑和智能合约交互接口实现
wallet管理地址公私钥,创建原始交易,管理未确认交易
addrman.cpp地址管理器,确保防止地址广播产生的女巫攻击和对硬盘、内存的滥用
alert.cpp提供版本兼容性警告信息
allocators.cpp多线程处理内存分配
arith_uint256.cppuint256 类型实现
base58.cppbase58编码实现
bignum.h大整型数字实现
bloom.cppBloom过滤算法实现
chainparams.cpp公链参数设置
clientversion.h版本信息设置
coind-cli.cpp客户端程序入口
coind.cpp服务器后台程序和客户端程序交换入口
configuration.cpp配置信息
core.cpp基础类:块头部信息,块信息
crypter.cpp钱包加密
cuiserver.cppcoind程序的字符界面处理
database.cpp状态数据库的交换部分(ORM):账号和合约数据、交易数据类存储
hash.cppsha256两次hash
init.cppcoind的启动和关闭处理
key.cpp公私钥生成、签名、验证
keystore.cpp统一账户(含普通账户和矿工)的公私钥对,已经密钥存储文件
leveldbwrapper.cppLevelDB的访问封装类
limitedmap.h前 N 个最大值元素的映射表类
main.cpp区块链数据交换协议实现主要程序
miner.cppDPoS 协议实现和挖矿实现
mruset.hMRU 集合类实现
net.cpp网络节点发现和连接和数据传送
netbase.cpp网络处理基础类
noui.cpp无界面消息处理
protocol.cppP2P数据交换协议
random.cpp伪随机实现
SafeInt3.hpp安全整型
serialize.h序列化和反序列化处理
sync.cpp多线程同步处理
syncdata.cppcheckpoint数据签名和验签
syncdatadb.cppcheckpoint数据的存储和加载
threadsafety.h线程安全的一些宏定义
tinyformat.h类型安全的格式打印函数
tx.cpp交易类型,账户类、各种交易类、投票
txdb.cpp交易和合约数据存储
txmempool.cpp交易内存池管理
ui_interface.h界面信号处理
uint256.cpp哈希ID类型
util.cpp基础工具类
version.cpp版本

交易执行

提交交易 -> 添加到内存池 -> 矿工打包交易 -> 广播块 -> 收到新块并验证

  1. 在第 N 块提交交易,添加到内存池,执行一次

  2. 出第 N+1 块之后,rescan 内存池,重新执行内存池未确认的交易

  3. 出块 N+2,将交易打包,执行一次

  4. 将块 N+2 连接到最长链,执行一次

冷挖矿

概念

在保证矿工能够用自己私钥签名区块正常出块外,同时保证矿工账户资金的安全(即使节点被攻破,无法盗取矿工的账户资金)

实现

如何开启冷挖矿

方法一:矿工密钥在节点钱包的前提下,执行 dropminerkeys rpc 即可

方法二:在节点只包含新钱包(无矿工账户密钥)的前提下,只需要执行 importprivkey <miner_private_key> 导入矿工账户的挖矿密钥即可

验证冷挖矿安全性

如果要验证冷挖矿特性,可以按照如下步骤:

  1. getnewaddr true(生成两组密钥对,分别是用来转账的主私钥、用来给区块签名的挖矿私钥),假设生成地址

  2. submitaccountregistertx

    激活账户

  3. 给上述账户投票,使其票数进入 top11 成为矿工节点;同时给该账户转点钱(后面需要用到)

  4. 先保存账户两组私钥 dumpprivkey

  5. 从钱包删除两组私钥 dropprivkey

  6. 导入矿工私钥 importprivkey <miner_private_key>

  7. 通过 getchainstate 100|grep <address 对应的 regid>,预期结果能够正常出块

  8. 通过 submitsendtx

    <another_addresss> 尝试给别人转账,预期结果不能转账

  9. 导入主私钥(用于转账),再次尝试 8,预期结果能够转账

备注:通过 dropminerkeys 也可以保证矿工账户的安全。

比特币 Bitcoind 源码

源码版本:基于 v0.9.5

最大连接限制

MAX_OUTBOUND_CONNECTIONS 默认值为 8

nMaxConnections 默认值为 125

同时,可以自定义参数覆盖 nMaxConnections,如下所示

最大连接数取值 min(MAX_OUTBOUND_CONNECTIONS, nMaxConnections)

种子节点、对等节点

其中-dnsseed 和 -seednode 是与种子节点相关的,而 -addnode 和 -connect 是与对等节点相关的。

-dnsseed 和 -seednode 的区别 -dnsseed 用于控制是否启用内置的 DNS 种子节点,-seednode 选项用于指定特定的种子节点。因此,除了使用内置的 DNS种子节点外(比如 DNS 种子节点全部瘫痪,或者关闭了 -dnsseed 选项),还可以使用 -seednode 选项指定种子节点。

-addnode 和 -connect 的区别 -addnode 指定的对等节点在与你的节点连接上后,会告知你的节点所有与它相连的其他对等节点信息,另外还会将你的节点的信息告知与其相连的其他对等节点,这样它们也可以与你的节点建立连接。 -connect 指定的对等节点在与你的节点连接上后,并不会做上述工作。 因此,如果您位于防火墙后,或者其他原因无法找到节点,则可以使用 -addnode 添加一些节点。 如果您想保护隐私,可以使用 -connect 连接到哪些您可以信任的节点。 如果您在一个局域网内运行了多个节点,您不需要让它们建立许多连接,您只需使用 -connect 让它们统一连接到一个已端口转发并拥有多个连接的节点。

以太坊 geth 源码

源码版本:基于 release 1.8 分支

同步模式

同步模式的定义

在文件 eth/downloader/modes.go 定义了同步模式,主要包含如下三种同步模式 full/fast/light,详见定义

SyncMode 类型定义 type SyncMode int,实际数据类型为 int,三种同步模式的差异如下:

同步模式特点备注
FullSync同步完整的区块信息全量同步
FastSync快速同步 header(至最新高度 X),然后同步(最新高度 X 之后的)完整的
区块信息
快速同步
LightSync只同步 header,同步完成之后终止轻量同步

默认同步模式

在 cmd/utils/flags.go 文件内定义了项目启动时的基本配置参数

然后,查看 eth.DefaulConfig 结构中关于 SyncMode 的定义,如下所示

因此,默认的同步模式为 FastSync,即,快速同步。

同步模式的自动切换

然后,根据实际同步情况,可能调整同步模式,在 eth/handler.go 中方法 NewProtocolManager 有如下代码逻辑

解读:即使启动节点使用默认同步模式或者通过指定 FastSync 快速同步模式,节点启动时,检查当前区块高度,如果已经同步好一部分数据时,此高度大于 0,程序自动将同步模式切换成 FullSync 全量同步模式,并打印警告信息,如下所示

总结