Zhuang's Diary

言之有物,持之以恒

一句话概述:以太坊上没有隐私数据

private 声明帮不了你

string private secret;

如上,将变量设置为private只是确保仅通过特定的 function 才能够更改变量的值。但就数据的隐私性“private”而言,这是没用的。基本上,以太坊smart contract上的所有内容都在(所有)节点(即所有矿工和参与者)的硬盘上,私有变量只是为了使阅读不方便,但并不意味着不可能。它不足以保护您的敏感、隐私信息。如下图所示。
而且,smart contract 的代码是透明的,private 的变量声明是很容易被以太坊用户识别的。

1
2
3
4
5
6
0x5 - length of the string
0x68 - h (ASCII code 104)
0x65 - e
0x6c - l
0x6c - l
0x6f - o

private data 上链的方式如下,但是都觉得是隔靴搔痒:

1.hash 上链,作为存证的证明

2.密文上链。

3.时间锁 Time-locked secrets

4.EIP1024

请参考:How to secure Sensitive data on an Ethereum Smart contract?

让我来 step by step 做介绍:

1.编写关于 bid case 的 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/// This zksnark is to be used to verify that an operator has choosen
/// the lowest bid that was actually submitted by an approved supplier was choosen
/// by the operator for the work. For this proof of concept, we will structure the
/// proof to use 3 suppliers only.

/// Inputs for the SNARK

/// PUBLIC FIELDS
/// input_of_py: field[3] public keys for each supplier - should be EDDSA public keys
/// * A: Curve point. Public part of the key used to create S.

/// PRIVATE FIELDS
/// input_of_py: field[3] actual bid from each supplier
/// input_of_py: Requires per each EDDSA signatures per supplier
/// * R: Curve point. Hidden version of the per-message nonce.
/// * S: Field element. Signature to be verified.
/// input_of_py: a secret to aide in building out the merkel tree of the results

/// * context: Curve parameters used to create S. Tells you which curve was used.
/// The context can be generated from the babyjubjub curve.

/// Returns for the SNARK
/// return: a merkel tree including...
/// 1. private key of choosen supplier
/// 2. secret
/// 3. A: Curve point. Public part of the key used to create S.

// In the vectors, the supplier order should be kept the same to compare/access (i.e. supplier one has first bid, supplier two second bid, etc.)

//For signatures
import "ecc/babyjubjubParams.code" as context
import "signatures/verifyEddsa.code" as verifyEddsa
//For unpacking bids correctly
import "utils/pack/unpack128.code" as unpack128
import "utils/pack/pack128.code" as pack128
import "utils/pack/nonStrictUnpack256.code" as unpack256
//For unpacking signatures correctly
import "utils/pack/pack128.code" as pack128
//For merkle tree - changed to padded
import "hashes/sha256/512bitPadded.code" as sha256

def main(private field[6] R, \
field[6] A, \
field[3] S, \
private field secret, \
private field bidone, \
private field bidtwo, \
private field bidthree) -> (field[2]):

//Since each message is constructed from the bids, no way to input_of_py a faulty bid

//Reconstruct messages:
//Note: Field element can be a value between 0 and p (p upper limit: 2^254)
//Makes an assumption that every bid will be 64 bit number
//Message in the signature should be 512 bits

field[128] bidoneunpacked = unpack128(bidone)
field[512] bidonemessage = [...[0; 384], ...bidoneunpacked]
field[128] bidtwounpacked = unpack128(bidtwo)
field[512] bidtwomessage = [...[0; 384], ...bidtwounpacked]
field[128] bidthreeunpacked = unpack128(bidthree)
field[512] bidthreemessage = [...[0; 384], ...bidthreeunpacked]

context = context()

// Supplier 1
1 == verifyEddsa(R[0..2], S[0], A[0..2], bidonemessage[0..256], bidonemessage[256..512], context)
// Supplier 2
1 == verifyEddsa(R[2..4], S[1], A[2..4], bidtwomessage[0..256], bidtwomessage[256..512], context)
// Supplier 3
1 == verifyEddsa(R[4..6], S[2], A[4..6], bidthreemessage[0..256], bidthreemessage[256..512], context)

//Lowest bid should correllate with supplier
//To Do: write the simplest implementation to simplify, each of the below would be a range proof
field[3] lowestbidder = [...[0; 3]] //Save supplier and the lowest bid
lowestbidder = if bidone < bidtwo && bidone < bidthree then [A[0], A[1], bidone] else [...[0; 3]] fi
lowestbidder = if bidtwo < bidone && bidtwo < bidthree then [A[2], A[3], bidtwo] else [...[0; 3]] fi
lowestbidder = if bidthree < bidone && bidthree < bidtwo then [A[4], A[5], bidthree] else [...[0; 3]] fi

//Now we have the lowest bid, supplier sig verified, and secret --> ready for the merkle tree
//512bitpadded - same merkle tree as ethereum
//Leaf One: lowest bid + secret
//Makes an assumption that every secret will be 64 bit number
field lowest = lowestbidder[2]
field[256] lowestbidunpacked = unpack256(lowest)
field[256] secretunpacked = unpack256(secret)
field[256] leafone = sha256(lowestbidunpacked, secretunpacked)

//Leaf Two: Curve point A: A[0] + A[1]
//We know that A[0] and A[1] will be 64 bit numbers, need to unpack to 256
field[256] a0unpacked = unpack256(A[0])
field[256] a1unpacked = unpack256(A[1])
field[256] leaftwo = sha256(a0unpacked, a1unpacked)

//Build the root of leaf one + leaf two
field[256] root = sha256(leafone, leaftwo)

//Before returning, compress back to field element root - split off 128 bits
res0 = pack128(root[..128])
res1 = pack128(root[128..])

return [res0, res1]

2.编译

zokrates compile -i verifybid.code

生成 out 和 out.code 文件。其中 out 文件为 102M,out.code 文件为 60M,文件 size 较大。

3.设置

zokrates setup

生成 proving.key 和 verification.key 文件。其中 proving.key 为 121M,文件较大。

4.执行程序

zokrates compute-witness -a 10055980565918579776429468085502320403896861311334950973306171539247124720876 9872212025624827705538064352986283580914984702567748676648365783032194062540 15914440638590293656936968551196129378835493705090190454426890248467449623197 7164401624892807842686423552737135338148312648059491863415011813391162897156 17656893400745400645892949581714190787095935883004706460374622673779099869831 13238137875306891162631161684759202257598937454005976664682588856632564854848 14897476871502190904409029696666322856887678969656209656241038339251270171395 16668832459046858928951622951481252834155254151733002984053501254009901876174 14897476871502190904409029696666322856887678969656209656241038339251270171395 16668832459046858928951622951481252834155254151733002984053501254009901876174 14897476871502190904409029696666322856887678969656209656241038339251270171395 16668832459046858928951622951481252834155254151733002984053501254009901876174 6460329147541296150725376868570071708599740324087744825213520062979428542760 8063340131639693693865282091868641282847827816145781069349300386706996098818 21548706238088528999687556989620717323700520797702367873886065001951944889761 45 500 400 300

-a 后面为执行程序的参数,即对应着verifybid.code中的入参 private field[6] R, field[6] A, field[3] S, private field secret, private field bidone, private field bidtwo, private field bidthree

其中的 500,400,300 即为 3 方的报价,此处可在前序的智能合约中编写为下面的逻辑:

party one,输入 500,执行 smart contract,

party two,输入 400,执行 smart contract,

party three,输入 300,执行smart contract,

smart contract 收集到法定报价数量之后,verifier 端收到 event,执行 step5 和 step6。

其余参数内容来自inputs_of_command_paras

5.生成证明

zokrates generate-proof

生成 witness 文件。其中 witness 文件为 82M,文件较大。

6.导出 solidity verifier

zokrates export-verifier

生成Verifier.sol

7.编写合约

编写Bid_operator.sol合约,引用 verifyTx 函数。

8.使用 truffle 部署合约

2_deploy_Verifier.js3_deploy_Bid_operator.js 作为配置文件。

9.测试

test/bid_operator.js 中的a, b, c 和 input 均来自于proof.json

回头来总结一下:

1.本次的 bid case 的目的为验证operator选择了最低的 bid 报价,并且验证了该 bid 为 supplier 所提供;

不能证明 supplier one,two,three 之间互相看不到其他 supplier 的 bid。

2.zokrates默认的证明算法为 G16根据 zokrates 的说明,在使用了签名和 ethereum address 作为proofs 证明的情况下,G16 是安全的。并且 G16 无需引用libsnark,生成 witness 和 proof 的速度大大加快,使用配置文件的 size 大大降低。

Zokrates

ZoKrates是以太坊上zkSNARKs的工具箱。它可以帮助您在DApp中使用可验证的计算,从高级语言的程序规范到生成计算证明,以及在Solidity中验证这些证明。

闲话少叙,开搞

1.编写 .code 文件

.code 文件是证明的逻辑内容,函数的返回内容为 field、field[n]。

2.1 compile,编译 .code 文件,Zokrates内部生成数字电路,没有指定文件名称的话,默认生成 ./out.code

./zokrates compile -i root.code

2.2 perform the setup phase,进行安装。为 ./out.code 中的已编译程序生成可信设置。在./proving.key和./verifying.key处创建一个证明密钥和一个验证密钥。这些密钥源自随机源,通常称为“有毒废物”。任何有权访问随机源的人都可以生成假证明,该证明将由协议后的验证者接受。

./zokrates setup

2.3 execute the program,执行证明。计算在./out.code中找到的已编译程序的witness和程序的参数。witness是变量的有效分配,包括计算结果。-a 后面跟着程序的参数,用空格分隔。
在./witness创建一个witness文件

./zokrates compute-witness -a 337 113569

2.4 generate a proof of computation,生成 proof(证明)。使用./proving.key中的证明密钥,生成./out.code的证明,从而得到./witness。

./zokrates generate-proof

2.5 export a solidity verifier,使用./verifying.key上的验证密钥,生成一个Solidity合约,其中包含生成的验证密钥和公共函数,以验证./out.code上已编译程序的解决方案。
在./verifier.sol被创建。

./zokrates export-verifier

3.通过验证者合同,可以检查此证明。例如,使用web3,调用将如下所示:

1
Verifier.at(<verifier contract address>).verifyTx(A, A_p, B, B_p, C, C_p, H, K, [...publicInputs, ...outputs])

A, A_p, B, B_p, C, C_p, H, K的值,以及publicInputs和 outputs 均来自于步骤2.4中生成的proof.json文件。

发行

ERC 代币的发行已经标准化,最好采用 openzeppelin 的模板,并搭配 solidity5.x 版本和 truffle 工具使用,效果更佳。

1
2
3
4
5
6
7
8
9
10
// ERC 721
pragma solidity ^0.5.0;

import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol';
import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Mintable.sol';

contract MyNFT is ERC721Full, ERC721Mintable {
constructor() ERC721Full("MyNFT", "MNFT") public {
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// ERC 20
pragma solidity ^0.5.0;

import 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol';
import 'openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol';
import 'openzeppelin-solidity/contracts/ownership/Ownable.sol';

contract MyERC20 is ERC20, ERC20Detailed, Ownable {
constructor(uint256 initialSupply) ERC20Detailed("MyToken", "MT", 18) public {
_mint(msg.sender, initialSupply);
}
}

Freeze 功能 (openzeppelin-contracts v3.2.0之后)

也叫做 pause 或者 close,具体就是停止 Token 相关的 transfer(转账)功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// ERC 20
pragma solidity ^0.5.0;

import 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol';
import 'openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol';
import 'openzeppelin-solidity/contracts/ownership/Ownable.sol';

contract MyERC20 is ERC20, ERC20Detailed, Ownable {
bool public closed = false;

event Close();
event Open();

constructor(uint256 initialSupply) ERC20Detailed("MyToken", "MT", 18) public {
_mint(msg.sender, initialSupply);
}

// ------------------------------------------------------------------------
// Don't accept ETH
// ------------------------------------------------------------------------
function() external payable {
revert("Don't accept ETH");
}

function transfer(address _to, uint _value) whenOpen public returns (bool) {
return super.transfer(_to, _value);
}

function transferFrom(address _from, address _to, uint _value) whenOpen public returns (bool) {
return super.transferFrom(_from, _to, _value);
}

function close() onlyOwner whenOpen public {
closed = true;
emit Close();
}

function open() onlyOwner whenClosed public {
closed = false;
emit Open();
}

modifier whenOpen() {
require(!closed);
_;
}

modifier whenClosed() {
require(closed);
_;
}
}

openzeppelin-contracts v3.2.0版本中的实现代码如下 ERC721Pausable

1
2
3
contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Burnable, ERC721Pausable {
......
}

MultiSigWallet 多方签名

gnosis 的 multisignature 功能最为完善。已经有多个生产项目采用此工具。目前此工具合约的 solidity 版本为0.4.15,项目语言版本交旧,但没有发生过安全问题。

首先 ERC20 合约的初始化总金额地址需要给到 MultiSigWallet 的合约地址,由 MultiSigWallet 合约完成多方签名确认后,ERC20 代币从 MultiSigWallet 合约的地址发送到接收人的账户地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// ERC 20
pragma solidity ^0.5.0;

import 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol';
import 'openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol';
import 'openzeppelin-solidity/contracts/ownership/Ownable.sol';

contract MyERC20 is ERC20, ERC20Detailed, Ownable {
bool public closed = false;

event Close();
event Open();

constructor(uint256 initialSupply) ERC20Detailed("MyToken", "MT", 18) public {
_mint(msg.sender, initialSupply);
}

// ------------------------------------------------------------------------
// Do accept ETH, deposit token in contract address
// ------------------------------------------------------------------------
function() external payable {}

function transfer(address _to, uint _value) whenOpen public returns (bool) {
return super.transfer(_to, _value);
}

function transferFrom(address _from, address _to, uint _value) whenOpen public returns (bool) {
return super.transferFrom(_from, _to, _value);
}

function close() onlyOwner whenOpen public {
closed = true;
emit Close();
}

function open() onlyOwner whenClosed public {
closed = false;
emit Open();
}

modifier whenOpen() {
require(!closed);
_;
}

modifier whenClosed() {
require(closed);
_;
}
}

Live website 地址在这里,以下操作均为https://wallet.gnosis.pm/上的截图。

1.增加钱包。如下图,完成钱包增加配置

2.点击 Wallets Name,即点击’Lcoin’,进入钱包的详细页面,如下图,进入后需要手动添加 Tokens Name,其后点击 Withdraw,最后经过 2 位 Required confirmations 后,Executed 状态变为 Yes

注意,此处,由 MultiSigWallet 合约完成多方签名确认后,ERC20 代币从 MultiSigWallet 合约的地址发送到接收人的账户地址(a825),MultiSigWallet 合约执行 transfer 给到接收人的账户地址(a825),所以 MultiSigWallet 合约的地址上需要有足够的 Eth 作为 gas

即使用 MultiSigWallet 转账时,花费的 gas 是 2 位 Required confirmations 和一次 transfer 的总和

3.如下图,虽然在 MultiSigWallet中可以选择到调用 ERC20 合约中的 close 函数,但是 close 函数并不受 MultiSigWallet 的控制,由onlyOwner,即 msg.sender 单独控制并操作

4.如下图,具体交易的内容在 MultiSigWallet,并配合etherscan 和 MyEtherWallet 中的结果

关联内容 ==> https://willzhuang.github.io/2020/09/07/ERC721%E5%AE%9E%E8%B7%B5/实践

1. 智能合约是可以删除的

删除智能合约的 EVM 字节码为 SELFDESTRUCT (之前称为 SUICIDE)。该操作系统会提供 gas 退款,激励用户删除存储状态的方式释放资源。删除智能合约并不会清楚合约之前历史的交易记录,区块链本身并不可改变。

2. Solidity 目前的版本号码是Version 0.5.9

其中主版本号是 0,它表示任何东西都有可能修改。次版本号是 5,在 Solidity 实际编码中,次版本号视为主版本号。9 为补丁号,实际编码中视为次版本号。当然建议开发者使用最新的版本。

3. ABI,Application Binary Interface,应用程序二进制接口

ABI 定义了数据结构和函数如何在机器指令中被访问。ABI 是向机器指令层面编码和解码并传送数据的主要方式。与 API 不同。ABI 对智能合约进行编码,具体是对 EVM 的调用和从交易获取数据的调用进行编码。例如,钱包软件调用 withdraw 函数时,需要通过 ABI 知道,该调用需要一个 uint256 类型的变量,变量名称为 withdraw_amount。然后钱包软件就会提示用户输入该参数,接着创建一个以太坊交易,调用合约的 withdraw 函数。

4. 智能合约设计中的安全设计模式

4.1 重入:典型案例为DAO。
  • 尽可能地使用内置的 transfer 函数想外部合约发送以太币。因为 transfer 仅会给外部调用2300gas,所以不足以支持目标地址或者合约再次调用其他合约,也就不足以重入发送以太币的合约。避免使用 send 或者 call。

  • 确保状态变量的修改都发生在合约发送以太币之前。即按照”检查-生效-交互”模式。

  • 引入互斥锁,也就是增加一个状态变量在代码执行中锁定合约,避免重入的调用。请参照智能合约退款方式一文

4.2 溢出:典型案例为美团币被盗。

使用 openzeppelin 的 safemath 做计算防范溢出。

4.3 以太币余额逻辑陷阱:典型案例为游戏应用中的存 ether,中奖 ether漏洞。
  • 避免使用 this.balance 的具体数值,它可能会被操纵。

  • 如果需要判断以太币的具体充值数额,可以使用一个自定义的变量在 payable 的数字中记录数额的变动。需要保证这个变量不会被 selfdestruct 调用强制发送的以太币所影响。

4.4 尽量避免使用 DELEGATECALL,尽量使用 library。保持库合约是无状态的,不会selfdestruct自我销毁的。典型案例 Parity 多重签名钱包的第二次共计漏洞。
4.5 保持正确的可见性,public/private/internal,不建议省略,因为省略即为 public。
4.6 区块链上的随机性目前还是一个难题。使用过往或者最近的变量都是灾难性的。

随机数必须来自区块链外部。或者,由像”提交-揭示”这样的模式在节点之间来实现。或者通过更改一组参与者之间的信任模型来实现。例如:randao , oraclize

4.7 参数攻击

向智能合约传递参数时,这些参数需要依照 ABI 规范进行编码。不过,发送的实际数据长度小于标准的参数编码长度也是可以的。在这种情况下,EVM 会在数据末尾补 0 使得数据长度达到要求。这可能会引起实际的转账以太币数量增长 10 倍,100倍甚至更高。防范方法是外部应用程序在把输入参数发送到区块链之前都应该对它们进行校验。由于数据填充发生在尾部,所以在合约中仔细考虑参数的顺序也可以在一定程度上减轻共计的危害。

4.8 Tx.Origin

智能合约中不应该使用Tx.Origin来进行验证授权。攻击者可以将共计合约的地址伪装成权限拥有人的地址,这样就可以通过验证,操作后续的(提款)逻辑。

Don’t roll your own crypto!