以太坊dapp采用的一个摩擦点是用户必须支付一笔支付天然气(交易(txn)费)才能让他们的交易记录在区块链上。 例如,我有一个简单的投票dapp ,让任何人都可以为候选人投票,并将选票存储在区块链中。 想要在区块链上记录投票的用户必须支付交易/燃气费。 这并不理想,因为作为一个dapp所有者,您希望您的应用程序用户拥有以太网来支付燃气费用,因为他们所要做的只是执行与转账无关的简单操作。 但是如果交易需要在区块链上执行,除了支付费用之外别无选择。 如果用户有安全地执行交易的方式(如我们的例子中为候选人投票),并让其他人(可能是合同拥有者)在区块链上记录交易并自行付款?
感谢John Backus发来的这条推文,我只有足够的信息来帮助我为我的投票dapp实现这样的解决方案。
我想分享有关我如何为我的简单dapp实施此解决方案的详细信息,以便更多人可以在他们自己的dapps中采用这种技术,并且可以有所改进。 这篇文章涵盖以下内容:
- 公钥密码学和数字签名的高级概述,这是理解这个解决方案的关键。
- 解决方案细节和新的应用程序流程。
- 实施细节(前端js和Solidity合同代码)。
- 讨论潜在的问题和增强。
数字签名
要使这个解决方案合理化,您需要对数字签名如何在加密中起作用有一个基本的了解。 如果您知道公钥密码术,请随意跳过本节。 我将尽力在非常高的层次上解释公钥/私钥和数字签名的概念,但我强烈建议详细学习 - 维基百科是一个很好的开始 。
公钥密码术是一个密码系统,你有两个密钥 - 公钥(Pu)和私钥(Pr)。 你把你的公钥交给整个世界,把私钥留给自己。 例如:您的以太坊地址是一个公钥(它实际上是从公钥导出的,但是对于这个练习,我们只是将其视为公钥),并将您的私钥存储在浏览器或手机/计算机上。 如你所知,有人向你发送以太网,他们只需要知道你的公共(帐户)地址。 但是,只有您可以访问您拥有的资金,因为您是唯一知道您私钥的人。
公钥加密算法具有算法,可让您使用一对密钥对消息进行加密,解密,签名和验证。
让我们通过一个例子来看看签名和验证消息的含义。 假设用户Kim有一对公钥/私钥
Pu =“0x44ac12c1e3dfd8edaf83b6f65918229d5279a6f5”
Pr =“dbc226043e390cf39280e5edfd418d7ad61931c76509270867d300f110c46506”
为了签署消息,Kim执行输出字母数字字符串的功能符号(“投票给爱丽丝”,Pr)
签名= 0x9127112de0033555c7f6508d963d484965a953844dfcff092712102c236467a25af57edc53b63880ea39af8ce7334f6d77a8206e805305e7c6ad919d12bfae5c1b
这是Kim用她的私人密钥Pr签名的消息“投票给爱丽丝”的数字签名。
现在,任何人都可以通过执行验证功能验证该消息“投票给爱丽丝”,由Kim执行验证(“投票给爱丽丝”,签名),其输出“0x44ac12c1e3dfd8edaf83b6f65918229d5279a6f5”。 如果你注意到,那输出是Kim的公钥Pu(记住,每个人都知道它是Kim的公钥),这意味着这个消息肯定是由Kim签署的。 如果篡改签名或消息(通过更改一个字符),验证算法会输出完全不同的公钥,并且您将知道该消息被篡改,因为公钥与Pu不同。
解决方案详情
如果你理解数字签名,解决方案是非常微不足道的。 让我们看看如何在我们的投票应用程序中使用它,以免用户在不损害投票的情况下支付燃气费。 您可以在下面看到dapp的所有用户以及他们执行的操作。
![](https://cdn-images-1.medium.com/max/1200/1*UdOleYl7ot02KidjG_Kd8A.png)
- 选民表示他们打算通过使用他们的私钥签署消息来投票给候选人。 他们不会将他们的交易提交给区块链,所以不会支付任何费用。 上图中的消息队列只是存储所有投票详情的脱链位置。
- 任何愿意支付txn费用的人(通常是合同所有者)都需要签名,候选人姓名和选民的账户地址,并将其提交至区块链。
- 智能合约使用验证功能基于候选人姓名和签名来派生公钥(以太坊账户地址)。 如果派生的公钥与签名该消息的用户的地址相匹配,则它记录投票或否则交易失败。
实施细节
现在让我们来看看实际的实现以及所有部分如何组合在一起。
第1步:签署消息
第一步是以选民的身份签署消息。 我们将使用eth_signTypedData函数来签署我们的消息。 这个功能已经在Metamask中实现,这使得签名消息变得非常简单。 你可以在这里找到关于这个建议的更多细节和讨论: https : //github.com/ethereum/EIPs/pull/712 。 您可以在下面找到代码以签署消息。
一个非常重要的要注意的是,内部eth_signTypedData散列消息,散列消息是签名。 您可以在这里参考typedSignatureHash函数以获取有关散列的更多细节。
第2步:将已签署的投票提交给区块链
由于这只是一个演示应用程序,因此我们不会在任何地方存储签名和其他细节。 它在消息签名后直接显示在页面上。 任何人都可以采取这些细节并提交给区块链。 以下是向区块链提交投票的代码:
第3步:验证智能合约中的投票详情
我们现在在智能合约中验证提交的投票信息是否有效,然后记录投票结果。
Zeppelin有一个名为ECRecovery的方便库,我们可以使用它来验证签名的消息。 voteForCandidate函数验证签名的消息(恢复功能),并在验证成功时更新投票计数。
如果你还记得,我刚才提到eth_signTypedData在签名之前散列消息(“为Alice投票”)? solidity recover函数没有关于eth_signTypedData中使用的散列函数的任何知识,因此它不能验证消息“为Alice投票”。 它必须生成消息“投票给爱丽丝”的散列,然后验证它。 我们不是在合约内部生成哈希,而是事先对所有消息进行哈希处理,并在构造函数中传递它,这样在验证时很容易查找。 生成哈希的代码位于下面的迁移文件中
这就是获得新应用程序所需的所有代码!
我创建了一个快速演示来展示这个应用程序如何工作
整个工作代码在这里: https : //github.com/maheshmurthy/ethereum_voting_dapp/tree/master/chapter4
演示应用程序位于: https : //www.zastrin.com/voting-dapp-without-paying-gas.html
可能需要解决的问题
使用这种技术构建真正的dapp时需要考虑几个问题。 其中一些列在下面:
- 签名邮件存储在哪里? 您可以使用某种排队系统来存储这些消息。
- 什么是保证签名的信息最终被提交给区块链?
- 将所有消息的散列存储在区块链中并不理想,那么最好的解决方案是什么?
如果您对如何解决这些问题有所想法,或者如果您发现此解决方案中存在任何缺陷,请发表评论!
注意:显然,eth_signTypedData API仍然不稳定,只能通过metamask实现。 如果您打算在mainnet / production中使用这种技术,请小心这一点。
进一步阅读
https://en.wikipedia.org/wiki/Public-key_cryptography
https://en.wikipedia.org/wiki/Digital_signature
https://github.com/danfinlay/js-eth-personal-sign-examples/
https://danfinlay.github.io/js-eth-personal-sign-examples/
https://github.com/ethereum/EIPs/pull/712
感谢Chris Whinfrey和Febin John James审阅本文的草稿。
https://medium.com/blockchannel/how-to-save-your-ethereum-dapp-users-from-paying-gas-for-transactions-cfc665891ab4