前面几篇文章中,我们学习了自定义许可权限,以及如何抵御彩虹攻击。其实,自定义许可权限还有一个非常重要的能力,多重签名。本文就来介绍一下多重签名的用法。
知识回顾
回忆一下我们前两篇文章中提到的bob和carl,我们为他们各创建了一个钱包,分别是bobwallet
和carlwallet
,他们各自的key就存储在里面。
后来,我们把tester
的trans
许可授权给了bob和carl,也就是说,bob和carl可以使用tester的trans
许可权限对交易进行签名。
注意,这里不是tester账户所有的交易都可以用trans
许可权限签名的。
所谓交易,就是action消息的事务。转账
交易实际上就是,向eosio.token
合约发送一个transfer
的action消息;eosio.token
合约对transfer
action进行处理,就完成了转账,我们之前专门介绍过eosio.token
合约的源码,相信你应该对这一机制非常了解。
回到之前的话题,什么样的交易才能用trans
许可进行签名呢?答案是,那些与trans
许可所关联的action交易,才可以用trans
许可权限签名。
这里说的“用trans
许可权限签名”又是什么意思呢?在这里例子中,就是用bob和carl的私钥进行签名了。如果用通用的语言解释,就是用trans
许可所关联的key、或者所关联的账户的key,对交易进行签名。
我们之前也看到了,一个许可权限可以关联多个key,也可以关联多个账户,每个key和账户可以设置不同的权重。
上一篇中,我们给owner权限关联了两个key,每个key的权重都是1,threshold设置为2,这样,只有同时拥有这两个key的人才能够使用owner许可;从而增加了黑客彩虹攻击的难度,即便黑客破解了其中一个key,也无法使用owner许可。
多重签名的场景
本文将解决这样的一个场景问题:tester把自己账户的trans
许可权限给了bob和carl,把他们各自的weight
都设置为2,threshold
也是2,从而bob和carl中任何一个人都可以转出tester的资产。不过呢,tester名下资产比较多,他担心这样不安全,所以他想把trans
许可改成,只有bob和carl同时同意签名转账交易,交易才能成功。
我们先看下之前设置的trans
许可情况:
cleos get account tester
permissions:
owner 2: 1 EOS7ufpQne6oXmanxvdoadmPkmfCT9rmtncML1tLRb7emgMrHgMBL, 1 EOS8UTyktNUn4afScvYqMJ9JumGA27X7qmwgYpscwDoEHeFVYjDgb
active 1: 1 EOS83sN8bfKGk3jTBezN41UN7LfXSVFa1w3YQcGApE67J26t3HLcr
trans 2: 2 bob@active, 2 carl@owner,
vote 4: 3 EOS7LVbDSU3hMewm8F2sxXUNDFPCUmqNvpYJe4TCg58GJ9UW8bo3M, 1 EOS83sN8bfKGk3jTBezN41UN7LfXSVFa1w3YQcGApE67J26t3HLcr
可以看到我们设置的trans
许可授权给了bob@active
和 carl@owner
,他们的权重(weight
)都是2,阀值(threshold
)也是2 。
现在我们把它改为:授权给bob@active
和 carl@active
,并且他们的权重都是1,threshold是2。这样只有他们俩的权重加在一起才等于threshold
,也就是说,只有他们两个都签名了,trans
许可才能生效。
修改方法,和之前的类似:
cleos set account permission tester trans '{"threshold": 2, "accounts":[
{
"permission":
{"actor":"bob","permission":"active"},
"weight":1
},
{"permission":
{"actor":"carl","permission":"active"},
"weight":1}
]}' active -p tester@active
执行成功后,我们确认一下tester的trans
许可:
~ cleos get account tester
permissions:
owner 2: 1 EOS7ufpQne6oXmanxvdoadmPkmfCT9rmtncML1tLRb7emgMrHgMBL, 1 EOS8UTyktNUn4afScvYqMJ9JumGA27X7qmwgYpscwDoEHeFVYjDgb
active 1: 1 EOS83sN8bfKGk3jTBezN41UN7LfXSVFa1w3YQcGApE67J26t3HLcr
trans 2: 1 bob@active, 1 carl@active,
vote 4: 3 EOS7LVbDSU3hMewm8F2sxXUNDFPCUmqNvpYJe4TCg58GJ9UW8bo3M, 1 EOS83sN8bfKGk3jTBezN41UN7LfXSVFa1w3YQcGApE67J26t3HLcr
可以看到和预想的一样。
试验一下
我们试试看,如果只用bob签名,看能否使用trans
许可转账。
我们锁住其他的钱包,只解锁bobwallet
,然后用trans
许可向eosio.token
发送 transfer
action。
~ cleos wallet lock_all
Locked All Wallets
~ cleos wallet unlock -n bobwallet
password: Unlocked: bobwallet
~ cleos push action eosio.token transfer \
'[ "tester", "user", "0.5000 SYS", "some money" ]' -p tester@trans
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.
你看到了,提示权限不足。如果我们打开carlwallet
再试试呢?
~ cleos wallet unlock -n carlwallet
password: Unlocked: carlwallet
~ cleos push action eosio.token transfer \
'[ "tester", "user", "0.5000 SYS", "some money" ]' -p tester@trans
executed transaction: ad643a390d6439c285d39c1587902e8de2f3a25de02d551da0fac3c051b3d8ca 152 bytes 3623 us
# eosio.token <= eosio.token::transfer {"from":"tester","to":"user","quantity":"0.5000 SYS","memo":"some money"}
# tester <= eosio.token::transfer {"from":"tester","to":"user","quantity":"0.5000 SYS","memo":"some money"}
# user <= eosio.token::transfer {"from":"tester","to":"user","quantity":"0.5000 SYS","memo":"some money"}
成功了,对吧。但这不符合我们的场景,我们的场景是说,不存在一个人同时有bob和carl的钱包,我们不能是bob和carl的合体,对吧。
那我们怎么实现bob和carl同时对这个交易签名呢?这就是多重签名(multisig)的作用了。
部署多重签名合约
在开始之前,我们需要在我们自己的测试节点上部署eosio.msig
合约(在主网和jungle测试网络上,已经部署好了,所以,如果你使用主网或者jungle测试网络,可以直接使用这个合约)。
部署合约的方法也非常简单,和我们之前部署eosio.token
类似,本着一账户一个合约的原则,我们先创建eosio.msig
账户,然后再部署eosio.msig
合约。
创建eosio.msig
账号:
~ cleos create account eosio eosio.msig \
EOS83sN8bfKGk3jTBezN41UN7LfXSVFa1w3YQcGApE67J26t3HLcr \
EOS83sN8bfKGk3jTBezN41UN7LfXSVFa1w3YQcGApE67J26t3HLcr
部署eosio.msig
合约,我们在编译完成eos源码之后,合约目标文件会自动生成,在eos源码目录的build/contracts/eosio.msig
下面。
~ cd ~/eos/build/contracts
~ cleos set contract eosio.msig ./eosio.msig -p eosio.msig@active
Reading WAST/WASM from ./eosio.msig/eosio.msig.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: d121e31ae3e0152b6431f4de1eb724d2c836f21b7c669ead1ea31d18a4345326 8864 bytes 19510 us
# eosio <= eosio::setcode {"account":"eosio.msig","vmtype":0,"vmversion":0,"code":"0061736d010000000198011760017f0060047f7e7e7...
# eosio <= eosio::setabi {"account":"eosio.msig","abi":"0e656f73696f3a3a6162692f312e30030c6163636f756e745f6e616d65046e616d650...
部署成功!
多重签名
我们想模拟的情况是,bob和carl是独立的个体,他们互不知道对方的key。而我们还要实现他们两个对同一个交易进行签名,这怎么办呢?
多重签名给了我们一个流程:
- 首先有一个转账发起人,假设是bob吧,它提出一个
transfer
交易的提案:
➜ ~ cleos wallet lock_all
Locked All Wallets
➜ ~ cleos wallet unlock -n bobwallet
password: Unlocked: bobwallet
➜ ~ cleos multisig propose payuser '[{"actor": "bob", "permission": "active"},{"actor": "carl", "permission": "active"}]' '[{"actor": "tester", "permission": "trans"}]' eosio.token transfer '{"from":"tester", "to":"user", "quantity":"0.0005 SYS", "memo":"pay user some money"}' -p bob@active
executed transaction: 507346a3a782f0fa82cbb5b7e3a93d8a86caac0b2d273dd9b29cc6795a431a7e 248 bytes 2442 us
# eosio.msig <= eosio.msig::propose {"proposer":"bob","proposal_name":"payuser","requested":[{"actor":"bob","permission":"active"},{"act...
我们先把所有的钱包都锁住,只打开bobwallet
,这样可以完全模拟发起人是bob的情形。目前为止,bob发起提案成功。
发起提案的命令格式是这样的:
cleos multisig propose <proposal-name> < requested_permissions> <trx_permissions> <contract> <action> <data> -p accountname@permission
proposal-name: 就是提案的名字了, 它与发起人账户一起唯一标识这个提案,本例中,我们的提案名是
payuser
requested_permission: 代表,要执行本提案中的交易所需要的各个参与方的权限
trx_permissions: 代表本提案中的交易在什么许可下执行。本例中,该许可,是tester
账户的trans
许可。
contract 与 action: 本提案中的交易执行的是哪个合约的哪个action
data: 是对应的action所需要的参数
-p accountname@permission: 是发起当前提案的许可权限
本例中,bob是发起人,同时该提案也需要bob的active权限;其实发起人也可以不是所发起提案的权限的参与方,也就是说,任何一个人都可以发起任何形式的提案,这种情况我就不再这里演示了。
- bob把提案的名字告诉carl,carl可以审查一下这个提案的内容:
~ cleos multisig review bob payuser
{
"proposal_name": "payuser",
"packed_transaction": "c9b17f5b000000000000000000000100a6823403ea3055000000572d3ccdcd01000000005c95b1ca00000000003ccdcd34000000005c95b1ca00000000007015d60500000000000000045359530000000013706179207573657220736f6d65206d6f6e657900",
"transaction": {
"expiration": "2018-08-24T07:20:41",
"ref_block_num": 0,
"ref_block_prefix": 0,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "eosio.token",
"name": "transfer",
"authorization": [{
"actor": "tester",
"permission": "trans"
}
],
"data": {
"from": "tester",
"to": "user",
"quantity": "0.0005 SYS",
"memo": "pay user some money"
},
"hex_data": "000000005c95b1ca00000000007015d60500000000000000045359530000000013706179207573657220736f6d65206d6f6e6579"
}
],
"transaction_extensions": []
}
}
这个命令的格式是:
cleos multisig review <proposer> <proposal-name>
proposer: 提案发起人,本例中是bob
proposal-name: 提案的名字,本例中是payuser
你发现了,这个命令不需要权限,也就是说,任何人只要知道发起人和提案名,就可以查看该提案的内容。
carl从这个提案中,看到要从tester账户中转出0.0005 SYS 到 user账户
,carl觉得该提案没问题,就同意了,于是他做了下面这个操作。
签署提案
为了模拟carl签署提案的过程,我们先锁住所有的钱包,只打开carlwallet:
~ cleos wallet lock_all
Locked All Wallets
~ cleos wallet unlock -n carlwallet
password: Unlocked: carlwallet
~ cleos multisig approve bob payuser '{"actor":"carl", "permission":"active"}' -p carl@active
executed transaction: 4440f20256399c8f11f5365e2d514d7f05ef0bca25914973266ca7ea75a3e813 128 bytes 3972 us
# eosio.msig <= eosio.msig::approve {"proposer":"bob","proposal_name":"payuser","level":{"actor":"carl","permission":"active"}}
可以看到我们签署成功。这个命令的格式是:
cleos multisig approve <proposer> <proposal-name> <provided-permission> -p accountname@permission
proposer与proposal-name: 提案发起人,和提案的名字
provided-permission:本次签署所提供的权限
-p accountname@permission: 代表本次签署交易以什么许可执行,这个许可必须有权力提供provided-permission的权限
bob签署提案,并执行
bob用同样的方法签署:
~ cleos wallet lock_all
Locked All Wallets
~ cleos wallet unlock -n bobwallet
password: Unlocked: bobwallet
~ cleos multisig approve bob payuser '{"actor":"bob", "permission":"active"}' -p bob@active
executed transaction: b850d7c3a6674e03112f40f8c91c79cd9cd496c8a9900782fb4e4ff007d06fac 128 bytes 5158 us
# eosio.msig <= eosio.msig::approve {"proposer":"bob","proposal_name":"payuser","level":{"actor":"bob","permission":"active"}}
我们也是在只有bobwallet解锁的情况下签署的。下面我以bob的身份来执行这个多签合约:
~ cleos multisig exec bob payuser -p bob@active
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.
这个命令的比较简单,就不多解释。你应该发现,我这里执行失败了,我查下nodeos
的log,发现了这样的内容:
3090003 unsatisfied_authorization: Provided keys, permissions, and delays do not satisfy declared authorizations
transaction declares authority '{"actor":"tester","permission":"trans"}', but does not have signatures for it under a provided delay of 0 ms, provided permissions [{"actor":"eosio.msig","permission":"eosio.code"}], and provided keys []
{"auth":{"actor":"tester","permission":"trans"},"provided_delay":0,"provided_permissions":[{"actor":"eosio.msig","permission":"eosio.code"}],"provided_keys":[],"delay_max_limit_ms":3888000000}
这是啥意思呢?意思就是说multisig
没有权限执行tester@trans
许可,这是自然的。如果随便部署一个合约都能执行别的账户的许可,这就是天大的安全问题。所以我们需要给这个multisig
一个系统权限。注意,我们现在用的自测的nodeos节点,部署的时候没有给它系统权限,主网和测试网络上的eosio.multisig
合约都已经拥有了系统权限。
我们执行这样一个命令就好了:
cleos push action eosio setpriv '["eosio.msig", 1]' -p eosio
这个命令的意思是说,给eosio合约发个setpriv
action,参数是 '["eosio.msig", 1]' ,以后我们分析系统合约的时候,再详细看下它的代码实现。
好,现在我们再去执行这个多重签名提案:
~ cleos multisig exec bob payuser -p bob@active
executed transaction: 689fa2757f35c1f1a6d6fcb600fda3eda2a3abf11d7f2bf8fdf2565942e0ab57 160 bytes 6062 us
# eosio.msig <= eosio.msig::exec {"proposer":"bob","proposal_name":"payuser","executer":"bob"}
这次很顺利就成功了。
注意:
- 本例中,我们是以bob发起的提案,bob本身也是该提案的签署方,其实这个提案谁发起都无所谓,与是不是该提案的签署方没有任何关系。
- 本例中,过程是这样的:bob发起提案,carl签署,然后bob再签署,然后bob执行。实际上,bob和carl的签署顺序对结果没有任何影响,而且不一定非要bob来执行,carl也可以执行,甚至其他的人,一个不相干的人,只要提案被签署通过,任何人都可以执行。
- 本例中,
bob@active
和carl@active
的权重都是1,而threshold是2,如果提案仅有他们中的一人签署,那么该提案是无法执行的;我这里没有演示这种情况,你可以自行试验一下。
多重签名的其他命令
多重签名还有两个命令:
- 撤销签署
cleos multisig unapprove bob payuser '{"actor":"carl", "permission":"active"}' -p carl@active
这个命令与签署命令的形式类似。撤销签署,只有在提案执行前才有意义。如果已经执行,就撤销不掉。
另外,只有签署人自己才能撤销自己的签署。
- 取消提案
cleos multisig cancel bob payuser -p bob@active
这个命令也比较简单,不过有两点需要注意:
- 只有提案发起人本人可以撤销提案
- 在提案发起后到提案执行前这段时间里,发起人都可以撤销提案;也就是说,即便提案已经被签署了,只要还没有执行,就可以被撤销。
好了,今天挺多内容,就到这里。
简介:不羁,一名程序员;专研EOS技术,玩转EOS智能合约开发。
微信公众号:know_it_well
知识星球地址:https://t.zsxq.com/QvbuzFM