您正在查看: Ethereum 分类下的文章

详解 MPC 和智能合约钱包的优缺点与面临的挑战

概述

智能合约钱包与多方计算 (MPC) 协议,从长远来看并不是竞争关系,而是互补关系。

自我托管一直被誉为管理加密资产的最佳实践。FTX 和 Celsius 的崩溃是一长串事件中的最新一起,这些事件提醒业内 「非彼之钥,则非彼之币」,引得人们纷纷奔向非托管钱包。在 FTX 事件曝光后,Safe 获得了 8 亿美元以上的净流入,Ledger 在短时间内连续经历了多个历史新高的销售额,Trezor 销售额飙升 300%,ZenGo 在一夜之间实现了三位数增长,存款达到历史最高水平,所有这些都发生在同一周内。

然而,大量用户仍然愿意承担托管风险,以换取较低的成本和易用性。在非托管钱包基础设施成为保护和管理资产阻力最小的途径之前,我们还有很长的路要走。

幸运的是,现在有一个蓬勃发展的钱包生态系统,为个人、DAO 和机构提供了更多的选择。加密不再只涉及安全存储,它还包括在新经济中使用资产。但是,不断增加的攻击面和漏洞,再加上日益丰富的功能,使得钱包需要既能够抵御攻击,同时又能支持日常业务和个人使用。

与所有的设计决策一样,这是针对给定用例的多个考虑因素的优化问题,也是钱包解决方案和密钥管理实践的能力,它们需要务实地平衡目标用户的集体需求:

  • 个人需要无缝的用户体验、低费用、与 dApp 交互的灵活性。
  • DAO 需要透明的金库管理、生态系统治理参与。
  • 机构希望通过链不可知性、可审计性和机构级安全性来外包责任。

有两类替代密钥管理解决方案取得了重大进展:智能合约钱包 ( 包括多重签名钱包 ) 和多方计算 (MPC) 协议。

本文涵盖:

  • 钱包中要考虑的属性;
  • 传统、MPC 和智能合约钱包的概述;
  • 钱包生态系统的持续挑战;
  • 当前钱包解决方案的权衡总结,以及钱包基础设施前景展望。

钱包中需要考虑的属性

  • 安全
    从简单攻击到复杂攻击的保护程度。「良好的密钥管理」需要选择一系列解决方案,其加入和运营成本与链上活动的性质和风险金额相匹配。
  • 成本
    创建帐户、管理访问和执行交易的成本有多高。
  • 用户体验和灵活性。
    访问控制管理、开销策略、限制和权限的粒度。
  • 可恢复性
    在受到威胁或造成损失的情况下,有能力恢复资产和访问权。
  • 可扩展性
    可以为核心产品带来新功能,以及能够建造出综合的产品和服务生态系统。
  • 隐私
    地址可以轻松链接到个人。

传统 (HD) 钱包

传统钱包使用助记词和分层确定性 (HD) 结构来派生私钥、对应的公钥和链上地址。这些钱包允许用户生成用于签署交易的私钥,并使用助记词恢复所有密钥。

到目前为止,传统钱包一直是用户保管资产的工具,也是他们与区块链应用程序交互的主要入口。像 MetaMask 这样的浏览器扩展程序和像 Rainbow 这样的移动应用程序已经为这个生态系统吸引了数百万用户。想要降低风险的用户可以选择 Ledger 和 Trezor 等硬件钱包,它们可以离线保护私钥,从而提供更好的安全性。

虽然业界已经做出了巨大的集体努力来告知用户保持助记词和密钥安全的重要性,但这个单点故障仍然是广泛采用的一个重要障碍。如果私钥丢失,除了失去所有资产外,用户还必须手动跟踪多个地址、代币批准,并因必须为新地址提供资金而损害隐私。

今天,不可撤销的字符串不仅可以让一个人的毕生积蓄全部被「访问」,而且越来越多地趋势是将用户在线身份的链上历史联系起来。获取私钥访问权的动机就是这么大,以至于黑客们,每个人都投入无限的资源,进行越来越有创意的攻击。现在,仅仅依靠用户已经不够了——我们需要完全消除这个单点故障。

多方计算 (MPC) 钱包和智能合约钱包帮助我们实现这一目标,并且已经有一个由机构、个人和 DAO 等采用的关于这两类产品和服务的生态系统。虽然这两种类型的钱包都消除了单点故障,但它们有一些基本的技术差异,导致了不同的折衷方案。

MPC 钱包

广义上讲,多方计算 (MPC) 使一组互不信任的各方能够根据他们的输入共同计算一个函数,同时保持这些输入的私密性。在密码学中,这对于保存用于解密数据或生成数字签名的私钥特别有用。

MPC 钱包通过使用阈值签名方案 (TSS) 消除了单点故障。在这个范式下,我们创建并分发私钥的一部分,这样就没有一个人或机器能够完全控制私钥——这个过程被称为分布式密钥生成 (DKG)。然后,我们可以通过合并部分,并且在不暴露各方之间的部分的情况下共同生成公钥。


为了对消息和交易进行签名,每一方都要输入秘密共享部分与公共输入 ( 要签名的消息 ),以生成数字签名。从那里,任何知道公钥的人 ( 即验证者节点 ) 都应该能够验证和验证签名。由于密钥部分是被组合的,签名是在链下生成的,因此从 MPC 钱包生成的交易与传统的私钥钱包的交易没有区别。

这为 MPC 钱包用户保护了一定程度的隐私。对于那些希望将其签名方案和签名者活动置于公众视线之外的组织来说,这个特性是开箱即用的,因为这些过程发生在链下。这样,组织就可以保留参与签名的内部日志,而不对外公开。

Private Key Rotation 是另一种 MPC 协议,它将秘密共享部分作为输入,并输出一组新的秘密共享部分。旧的秘密共享部分可以被删除并替换为新的共享部分,新的共享部分可以以相同的方式使用,而无需更改相应的公钥和地址。

MPC 钱包的优势

  • 无单点故障
    一个完整的私钥在任何时候都不会集中在一台设备上。也没有助记词。
  • 可调整的签名方案
    批准固定人数可以随着个人和组织需求的变化而修改,同时保持相同的地址。组织可以动态调整签名方案,而不必每次都通知交易对手一个新地址。
  • 粒度访问控制
    机构用户可以为一个策略分配无限数量的交易审批者,并分配能够准确反映组织角色和安全措施 ( 时间锁、MFA、欺诈监控 ) 的权限。个人可以通过 MPC 钱包即服务 (wallet-as-a-service) 选择半托管路线,第三方持有其中一个关键共享部分。
  • 更低的交易和回收成本
    MPC 钱包在区块链上表示为单个地址,其 gas 费用与常规私钥地址相同。这对于每天进行数百个交易的用户 ( 例如在 B2C 用例中 ) 来说非常重要。丢失的密钥共享部分也可以进行链下回收。
  • 区块链不可知论者
    密钥生成和签名依赖于链下的纯密码学。将兼容性扩展到新的区块链很简单,因为钱包只需要能够使用该链识别的算法生成签名。

MPC 钱包的缺点

  • 链下问责制
    签署授权政策和批准固定人数是在链下管理的,因此这些自定义规则仍然容易出现中心化问题。密钥共享仍然是加密秘密,应该像处理整个私钥一样处理。链下规则和签名阻碍了透明度,需要更严格的运营审计。
  • 与许多用户采用的大多数传统钱包不兼容 ( 没有助记词,没有完整的私钥存储在单个设备上
    MPC 算法也没有标准化,也没有得到机构级安全设备 ( 如 iPhone SEP 和 HSM) 的原生支持。
  • 大多是孤立的定制产品
    许多 MPC 库和解决方案都不是开源的,因此,如果出现问题,生态系统很难独立审计和集成它们。

基于 MPC 的解决方案主要针对机构客户,如基金、交易所和托管人。像 Fireblocks 和 Qredo 这样的 MPC 技术提供商,允许他们的客户为不同类型的交易定义自己的工作流,使他们能够保持合规和安全。然而,散户投资者的基础仍然依赖于独立的研究和私人密钥钱包。Web3Auth 最近发布了一个 MPC SDK,用户可以使用他们的 iCloud 或电子邮件作为备份。像 Entropy 这样的去中心化托管协议正在为消费者和 DAO 构建开源工具,以便他们能够在线存储资产。

MPC 中值得注意的发展:可编程密钥对

Lit 是一个去中心化协议,它将密钥共享存储在 Lit 网络节点上。公钥 / 私钥对由 PKP( 可编程密钥对 )NFT 表示,其所有者是密钥对的唯一控制者。然后,PKP 所有者可以触发网络聚合密钥共享,以解密文件或在满足任意定义的条件时代表他们签名。

这对去中心化访问控制、资产管理和链上自动化交互具有很大的意义。通过向 Lit Action( 部署到 IPFS 的不可变代码 ) 授予签名特权,PKP 可以用作 MPC 或去中心化云钱包,使用任何可用 javascript 表示的身份验证方法。

铸造 PKP NFT 是基于 MPC 的分布式密钥生成过程,它使 NFT 所有者成为 PKP 的根所有者。因此,转移这个 NFT 相当于交易私钥,这实际上打破了「灵魂绑定」代币 (SBT) 的概念,因为 SBT 是与特定的所有者绑定,现在是钱包本身可以安全地交易,因此,「钱包绑定代币」可能是更合适的名称。

智能合约钱包

以太坊目前有两种账户类型:

  • 外部拥有帐户 (EOA)——由私钥控制
  • 智能合约帐户——由代码控制

智能合约钱包 (「智能钱包」) 是一种行为类似于钱包的智能合约,即一个允许用户管理资金、进行 web3 登录和与 dApp 交互的界面。与私钥钱包不同的是,智能钱包的创建需要初始成本,因为智能合约需要部署在链上。

多重签名钱包是智能合约钱包,它需要 M-of-N 密钥的签名才能执行交易。MPC 只创建单个签名,而不管参与的密钥共享的数量,多重签名使用由不同私钥生成的不同签名对交易进行签名。这使得它与现有的私钥钱包兼容,并位于 Ledger 或 MetaMask 等传统钱包地址之上的一层。

像 Safe 这样的智能合约账户标准为资产管理产品和服务的生态系统提供了一个基础层。功能是通过模块添加的,它允许用户定义管理密钥逻辑、支出限制、重复交易、帐户自动化、分层访问等等。目前最多产的一组 Safe 模块是由 Zodiac 团队构建的。

智能合约钱包的优势

  • 无单点故障
    执行交易需要多个签名。
  • 可编程访问控制
    用户可以定义不同的政策,设置时间锁、支出限制、自动化。
  • 可以实现交易批处理以节省成本。
  • 可扩展
    由于智能合约的可组合性,钱包开发人员可以创建一个模块生态系统,用户可以选择将这些模块添加到他们的钱包中,为 NFT 借贷框架、DAO 投票模块和非托管资产管理服务等新功能创建一个应用程序商店。
  • 可编程恢复
    钱包可以提供几种选择,将资金回收到智能合约本身。
  • 链上问责制
    链上签名授权策略和聚合可以明确使用哪些密钥对交易进行签名,从而使操作更加透明和直接,以便在出现错误的情况下审计谁参与了交易。
  • 支持迁移到其他签名方案
    智能合约钱包可以将其签名方案改为更简单、更省 gas 或抗量子的方案。他们还可以在 iOS 和 Android 设备上使用 ( 将手机变成硬件钱包 ),或启用 Ed25519,允许使用 iOS 生物识别和网络认证。
  • 开源
    任何人都可以审计智能钱包的实现和功能扩展,从而通过生态系统的方式解决漏洞和添加新功能。

智能合约钱包的缺点

  • 更高的费用
    智能钱包的费用比普通的单地址交易要高,因为需要验证多个签名。添加 / 删除所有者和更改阈值等操作也需要链上交易。
  • 没有得到普遍支持
    虽然智能钱包可以部署在相同地址的任何 EVM 链上,但它们需要在非 EVM 链上定制实现。
  • 恢复成本更高
    虽然恢复逻辑是可编程的,但需要支付链上费用来执行它。
  • 与不可升级的合约不兼容
    尽管 EIP -1271 允许应用程序代表合约钱包进行签名,但它仍然没有得到普遍支持,并且不能添加到不可升级的合约中。

智能合约钱包中值得注意的发展:帐户抽象

智能钱包在生态系统范围内,在努力完全摆脱 EOA 和私钥(也称为帐户抽象)的过程中发挥着至关重要的作用。在这种范式下,所有账户都是智能合约,它们有自己的逻辑来规定什么是有效的交易,允许用户根据自己的特定需求定制账户。

自 2016 年以来一直在讨论帐户抽象,但生态系统在对解决方案上的协调方面一直进展缓慢。L2 已经极大地加快了其意识和采用,例如 StarkWare 已经将所有 Starknet 账户本地化为智能钱包,zkSync 2.0 也将与 AA 一起推出。

在以太坊上,存在多个 EIP 来完成路线图上的里程碑,使帐户抽象成为现实。

  • EIP-4337:将签名验证、gas 支付和重放保护从核心协议移出到 EVM 中,让用户能够使用包含任意验证逻辑的智能钱包,而不是将 EOA 作为他们的主要帐户,同时也无需任何共识层更改。这个 EIP 引入了一个 UserOperations 内存池,它与现有的内存池并行存在。捆绑器 ( 验证者、MEV 搜索者或应用程序本身 ) 从 UserOperations 池获取交易,将它们转发给区块链并支付费用。在这里,启动钱包本身不支付 gas 费用,但应用程序可以通过收费订阅模式为用户聚合。
  • EIP-3074:允许 EOA 将控制权委托给合约,让现有的 EOA 发送由第三方支付的操作。
  • EIP-5003:将现有的 EOA 升级为合约,并允许其从 ECDSA 迁移到更高效或抗量子签名方案。

钱包开发生态系统面临的挑战

技术漏洞

Parity Multisig 黑客攻击和最近的 Rabby Swap 攻击表明,如果实现有缺陷,即使是最好的存储资金的概念方法也没有什么意义。我们可以预见,智能合约账户的标准将会出现

社交攻击层面

任何技术解决方案的优点仍然不能消除社会层面的风险。损失 6 亿美元的 Ronin Bridge 漏洞不是由于任何技术缺陷,而是针对 Sky Mavis 一名员工的社会工程攻击,使攻击者能够访问验证者密钥。除了决定使用哪个钱包来管理资产之外,组织还需要确保这个关键系统的每个「组件」在社交和技术层是真正独立的。

安全和迁移成本

从一个帐户迁移到另一个帐户既不有趣也不便宜。尽管目前市场上有强大的钱包替代品,但用户迁移现有的 EOA 是有实际成本的:交易费用、关闭 / 打开 DeFi 头寸、收入影响、用户错误、时间和精力。

操作安全

自我保管对于今天的大多数用户来说是一个可怕的前景,因为提高是需要有意识的努力的,这可能是一项艰巨的任务。大多数交易数据是不可读的 ( 尽管这一点正在改变 ),错误是不可逆转的。就像加密教育一样,这个问题不能由一个团队单独解决,需要工具和用户体验模式。

结论


尽管 MPC 和智能钱包有着共同的「这个 vs 那个」框架,但从长远来看它们并不是竞争关系,而是互补关系。MPC 在密钥生成和管理级别提供了共享安全性,而智能合约为功能和应用程序开发带来了可扩展性和生态系统方法。例如:

  • MPC 可以通过将一个或多个私钥分割成多个部分来增强现有的多重签名方案。如果三个人被用来保护一个 2 / 3 的多重签名,这三个用户中的每个人都可以使用 MPC 细分他们的个人私钥,并将他们的 MPC 密钥部分存储在独立的机器上。
  • 社区或 DAO 可以是拥有 PKP NFT 的多重签名的签名者,该 NFT 管理去中心化的云钱包,可用于自动投资或 DEX 交互。

今年,中心化实体的不计后果的行为在许多方面削弱了加密货币,它们侵蚀了行业的信任,最重要的是失去了用户的资金。本文重点介绍的技术和项目为每个人都可以参与去中心化经济,同时不用将命运掌握在少数人手中的未来铺平了道路。

原文

https://medium.com/1kxnetwork/wallets-91c7c3457578

如何在solidity中开始使用无gas元交易

元交易,也被称为 "无gas" 交易,是一种允许用户与智能合约交互而无需自己支付gas的方式。这对于需要用户进行频繁或小额交易的应用来说特别有用,因为Gas费的成本会迅速增加。在这篇博文中,我们将讨论如何在Solidity中开始实现无gas元交易,Solidity是用于在以太坊区块链上编写智能合约的编程语言。

什么是元交易?

在以太坊网络中,每次用户想与智能合约交互时,他们必须向网络支付一笔费用(以Gas形式),以便执行交易。这种费用对于激励矿工将交易纳入区块链并确保网络保持去中心化和安全是必要的。

然而,这种模式对于需要用户进行频繁或小额交易的应用来说是有局限性的,因为Gas费用的成本会迅速增加,并成为用户进入的障碍。元交易提供了一种解决方法,允许用户与智能合约交互,而不必自己支付加Gas费。

在元交易中,用户的交易实际上是由另一个账户执行的,该账户代表他们支付Gas费。这个账户被称为 "relayer",它可以是一个合约或普通的以太坊账户。中继者从用户那里收到交易,用自己的私钥签名,然后将其提交给网络进行开采。用户的交易基本上被包裹在支付Gas费用的第二笔交易中,允许用户与合约交互,而无需自己支付Gas费用。

在 Solidity 中实现无gas元交易

为了在Solidity智能合约中实现无gas元交易,我们需要做以下工作。

  1. 创建一个函数,允许中继者代表用户执行交易。
  2. 检查中继者是否被授权代表用户执行交易。
  3. 验证用户交易的签名以确保其真实性。
  4. 执行用户的交易,并使用中继者的账户支付Gas费。

让我们更详细地了解一下这些步骤中的每一个。

1. 为中继者创建一个函数来执行交易

首先,我们需要在我们的智能合约中创建一个函数,允许中继者代表用户执行交易。这个函数应该接受以下参数。

  • _user: 想执行交易的用户的地址。
  • _data: 用户的交易数据,编码为字节数组。这通常是用户想调用的函数的签名和参数,使用abi.encode()函数进行编码。
  • _signature: 用户交易的签名,使用eth_signTypedData()函数生成。

下面是这个函数在Solidity中的一个例子。

function execute(address _user, bytes _data, bytes _signature) public {
  // TODO: Add code to verify the relayer and signature
  // TODO: Add code to execute the user's transaction
}

2. 检查中继者是否被授权

接下来,我们需要检查中继器是否被授权代表用户执行交易。这对于防止恶意行为者代表其他用户提交任意交易非常重要。

做到这一点的一个方法是让用户明确授权中继器代表他们执行交易。这可以通过在智能合约中添加一个映射来实现,该映射存储了每个用户的授权中继者。然后execute()函数可以检查这个映射,以验证调用者是否被授权代表用户执行交易。

下面是一个例子,说明这种映射和验证在Solidity中可能是怎样的。

mapping(address => address[]) public authorizedRelayers;

function execute(address _user, bytes _data, bytes _signature) public {
  // Check that the caller is authorized to execute transactions on behalf of the user
  require(authorizedRelayers[_user].contains(msg.sender), "Unauthorized relayer");

  // TODO: Add code to verify the signature
  // TODO: Add code to execute the user's transaction
}

在这个例子中,authorizedRelayers映射被用来为每个用户存储一个授权中继者数组。然后execute()函数检查调用者(msg.sender)是否在该用户的授权中继者数组中,然后再继续执行。

3. 验证签名

接下来,我们需要验证用户交易的签名,以确保它是真实的。这对于防止恶意行为者提交实际上并非由用户签名的交易非常重要。

为了验证签名,我们可以使用ecrecover()函数,该函数将签名、交易数据和链ID作为输入,并返回签署该交易的地址。然后我们可以将这个地址与传递给execute()函数的_user参数进行比较,以确保它们相匹配。

下面是这个签名验证在Solidity中可能出现的例子。

function execute(address _user, bytes _data, bytes _signature) public {
  // Check that the caller is authorized to execute transactions on behalf of the user
  require(authorizedRelayers[_user].contains(msg.sender), "Unauthorized relayer");

  // Verify the signature
  bytes32 hash = keccak256(abi.encodePacked(chainId, _data));
  address signer = ecrecover(hash, sig.v, sig.r, sig.s);
  require(signer == _user, "Invalid signature");

  // TODO: Add code to execute the user's transaction
}

4. 执行用户的交易

最后,我们需要执行用户的交易,用中继者的账户支付Gas费。要做到这一点,我们可以使用delegatecall()函数,它允许我们用当前合约的调用者和参数调用另一个合约的函数。

下面是一个在Solidity中可能出现的例子。

function execute(address _user, bytes _data, bytes _signature) public {
  // Check that the caller is authorized to execute transactions on behalf of the user
  require(authorizedRelayers[_user].contains(msg.sender), "Unauthorized relayer");

  // Verify the signature
  bytes32 hash = keccak256(abi.encodePacked(chainId, _data));
  address signer = ecrecover(hash, sig.v, sig.r, sig.s);
  require(signer == _user, "Invalid signature");

  // Execute the user's transaction
  // The relayer's account is used to pay for the gas fees
  delegatecall(_data);
}

在这个例子中,delegatecall()函数被用来执行用户的交易,使用relayer的账户来支付Gas费。_data参数包含用户交易的函数签名和参数,被传递给delegatecall()函数作为调用的合约和参数。

总结

在这篇博文中,我们讨论了如何在Solidity中实现无gas元交易,Solidity是用于在以太坊区块链上编写智能合约的编程语言。我们走过了以下步骤:创建一个允许中转者代表用户执行交易的函数,验证中转者是否被授权,签名是否真实,以及执行用户的交易,同时使用中转者的账户支付Gas费用。

元交易对于需要用户进行频繁或小额交易的应用来说是一个有用的工具,因为它们允许用户与智能合约交互,而不必自己支付Gas费。通过遵循这篇博文中概述的步骤,你可以在你自己的 Solidity 智能合约中实现无gas元交易。
Source:https://coinsbench.com/how-to-get-started-with-gasless-meta-transactions-in-solidity-90e6d18f758
转载自:https://learnblockchain.cn/article/5347

EIP2612: 通过链下签名授权实现更少 Gas 的 ERC20代币

解锁消耗到了大量的 gas

每个人都在谈论 “无gas” 的以太坊交易,因为没有人喜欢支付gas费用。 但是以太坊网络的运行正是因为交易是付费的。 那么,你怎么才能“无gas”交易呢? 这是什么法术?
在本文中,我将展示如何使用 “无 gas” 交易背后的模式。 你会发现,尽管以太坊没有免费的午餐之类的东西,但是你可以通过有趣的方式改变 gas 成本。
通过运用本文中的知识,你的用户将节省大量 gas,享受更好的用户体验,甚至可以在你的智能合约中构建新颖的委派模式。
可是等等! 还有更多! 为方便起见,我将所需的所有工具都放在了此存储库中。 因此,现在你实现 “无 gas” 代币的障碍就突然降低了很多。
让我们开始吧。

背景

我不得不承认,即使我知道如何在智能合约中实现“无 gas”交易,但对于使它们成为可能的密码学我也知之甚少。 那对我来说不是障碍,所以对你也不应该是。

据我所知,私钥用于签署发送给以太坊的交易,一些密码学魔术用于将我(签名者)识别为msg.sender。 这支撑了以太坊中所有访问控制。

“无 gas” 交易背后的法宝是,我可以使用我的私钥和要执行的智能合约交易进行签名。

签名是在链下进行的,而无需花费任何 gas。 然后,我可以将此签名交给其他人,以他们的名义代表我执行交易。

签名函数通常就是常规合约方法,但会使用其他签名参数进行扩展。 例如,在dai.sol中,我们有授权(approve)函数:

function approve(address usr, uint wad) external returns (bool)

我们还具有permit许可函数,该功能与approve函数相同,但是将签名作为参数。

function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external

不用担心所有这些额外的参数,我们将介绍它们。 你需要注意的是这两个函数都使用allowance映射执行的操作:

function approve(address usr, uint wad) external returns (bool)
{
  allowance[msg.sender][usr] = wad;
  …
}

function permit(
  address holder, address spender,
  uint256 nonce, uint256 expiry, bool allowed,
  uint8 v, bytes32 r, bytes32 s
) external {
  …
  allowance[holder][spender] = wad;
  …
}

如果使用approve,则允许spender最多使用wad个代币。

如果你给某人提供有效的签名,则该人可以调用permit以允许spender 使用你的代币。

因此,基本上,“无 gas”交易背后的模式是制作可以提供给某人的签名,以便他们可以安全地执行特殊交易。 这就像授予某人执行函数的权限。

这是一种授权模式。

标准

如果你像我一样,那么你要做的第一件事就是深入研究代码。 我立即注意到此注释:

// — — EIP712 niceties — -

有了这个,我钻进了兔子洞,却无望地迷路了。 现在,我已经理解了,我可以用简单的方式来解释它。

EIP712描述了如何以通用方式构建函数签名。 其他EIP描述了如何将EIP712应用。 例如,EIP2612描述了如何使用EIP712的签名应用于permit函数,其功能应与ERC20代币中的approve功能相同。

如果你只想实现之前提到的签名功能,例如将签名批准添加到自己的MetaCoin,则可以阅读EIP2612 ,你甚至可以继承实现过的合约,并减轻生活压力。

在本文中,我们将研究dai.sol中“无 gas”交易的实现。 这将使事情变得清晰。 dai.sol实现发生在EIP2612之前,会略有不同。 那不会有问题。

签名组成

EIP712签名的早期实现可以在dai.sol源码中找到 。 它允许Dai持有人通过计算链下签名并将其提供给支出者(spender)来批准转账交易,而不是自己调用approve函数。
它包含下面几个部分:

  1. 一个 DOMAIN_SEPARATOR .
  2. 一个 PERMIT_TYPEHASH .
  3. 一个 nonces 变量.
  4. 一个 permit 函数.

这是DOMAIN_SEPARATOR,和相关变量:

string  public constant name     = "Dai Stablecoin";
string  public constant version  = "1";
bytes32 public DOMAIN_SEPARATOR;
constructor(uint256 chainId_) public {
  ...
  DOMAIN_SEPARATOR = keccak256(abi.encode(
    keccak256(
      "EIP712Domain(string name,string version," + 
      "uint256 chainId,address verifyingContract)"
    ),
    keccak256(bytes(name)),
    keccak256(bytes(version)),
    chainId_,
    address(this)
  ));
}

DOMAIN_SEPARATOR只不过是唯一标识智能合约的哈希。 它是由EIP712域(EIP712Domain)的字符串,包含代币合约的名称,版本,所在的chainId以及合约部署的地址构成。

所有这些信息都在构造函数上进行hash 运算赋值到DOMAIN_SEPARATOR变量中,该变量在创建线下签名时由持有人使用,并且在执行permit时需要匹配。 这样可以确保签名仅对一个合约有效。

这是PERMIT_TYPEHASH:

PERMIT_TYPEHASH 是函数名称(大写开头)和所有参数(包括类型和名称)的哈希。 目的是清楚地标志签名的函数。

签名将在permit函数中处理,如果使用的PERMIT_TYPEHASH不是该特定函数的签名,它将回退交易。 这样可以确保仅将签名用于预期的功能。

然后是nonces映射:

mapping (address => uint) public nonces;

该映射记录了特定持有人已使用了多少次签名。 创建签名时,需要包含一个nonces值。 执行permit时,所包含的nonce 值必须与该持有人到目前为止使用的签名数完全匹配。 这样可以确保每个签名仅使用一次。

所有这三个条件,即PERMIT_TYPEHASH,DOMAIN_SEPARATOR和nonce,确保每个签名仅用于预期的合约,预期的函数,并且仅使用一次。

现在,让我们看看如何在智能合约中处理签名。

permit 函数

permit是dai.sol里实现的函数,允许使用签名来修改持有人的 allowance对spender授权的数量。

// --- 通过签名授权 ---
function permit(
  address holder, address spender,
  uint256 nonce, uint256 expiry, bool allowed,
  uint8 v, bytes32 r, bytes32 s
) external;

如你所见,permit有很多参数。 它们是计算签名所需的所有参数,加上签名本身就是v, r和s。

你需要用参数创建签名似乎很愚蠢,但是你确实需要。 因为仅能从签名中恢复签名的地址。 我们将使用所有参数和恢复的地址来确保签名有效。

首先,我们使用确保安全性所需的所有参数来计算digest。 作为签名创建的一部分,holder将需要在链下计算出完全相同的digest:

bytes32 digest =
  keccak256(abi.encodePacked(
    "\x19\x01",
    DOMAIN_SEPARATOR,
    keccak256(abi.encode(
      PERMIT_TYPEHASH,
      holder,
      spender,
      nonce,
      expiry,
      allowed
    ))
  ));

使用ecrecover和v,r,s签名,我们可以恢复地址。 如果它是holder的地址,我们知道所有参数都匹配DOMAIN_SEPARATOR,PERMIT_TYPEHASH,nonce,holder,spender,expiry和allowed。 哪怕是任何一点内容没匹配,则签名被拒绝:

require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");

请注意这里。 签名中有许多参数,其中一些参数有点模糊,例如chainId (它是 DOMAIN_SEPARATOR 的一部分)。 它们中的任何一个不匹配都会导致签名被拒绝,并带有完全相同的错误提示,这让链下调试签名很困难。

现在我们知道 holder 批准了这个函数调用。 接下来,我们将证明签名没有被滥用。 我们检查当前时间是否在 expiry(过期)之前,这保证了仅在特定时间内许可有效。

require(expiry == 0 || now <= expiry, "Dai/permit-expired");

我们还会检查签名中的 nonce ,以便每个签名只能使用一次。

require(nonce == nonces[holder]++, "Dai/invalid-nonce");

这些检查都通过了! dai.sol使spender可以使用的holder的代币数量设置为最大值(即allowance设置为最大),并触发一个事件,仅此而已。

uint wad = allowed ? uint(-1) : 0;
allowance[holder][spender] = wad;
emit Approval(holder, spender, wad);

dai.sol合约对 allowance 使用的是二分法设置(译者注:要么是最大,要么是 0), 在代码库,有更传统的方法。

创建链下签名

创建签名也许需要通过一些实践才可以掌握它。 我们将分三步复制智能合约中permit的功能:

  1. 生成 DOMAIN_SEPARATOR
  2. 生成 digest
  3. 创建交易签名

以下函数将生成 DOMAIN_SEPARATOR。 它与dai.sol构造函数中的代码相同,但在JavaScript中实现,并使用ethers.js的keccak256,defaultAbiCoder和toUtfBytes,它需要代币名称和部署地址,以及chainId。 假定代币版本为“1”。

以下函数将为特定的permit调用生成digest。 注意,holder,spender,nonce 和 expiry作为参数传递。 为了清楚起见,它还传递了一个 approve.allowed 参数,尽管你可以将其始终设置为 true,否则签名将被拒绝。从刚刚dai.sol复制PERMIT_TYPEHASH。

一旦我们有了digest,对其进行签名就相对容易了。 我们从[digest]中删除0x前缀后,使用ethereumjs-util中的ecsign。 请注意,我们需要用户私钥才能执行此操作。

在代码中,我们将按以下方式调用这些函数:

请注意,对permit的调用需要重用用于创建digest的所有参数。 只有在这种情况下,签名才有效。

还要注意的是,此代码段中仅有的两个交易是由user2调用的。 user1是holder,是创建digest并签名的用户。 但是,user1并没有花费任何gas。

user1将签名提供给user2,后者使用它来执行user1授权的 permit 和transferFrom。

从 user1的角度来看,这是一次“无 gas”交易, 他没有花一分钱。

结论

本文介绍了如何使用“无Gas”交易,阐明了“无Gas”实际上意味着将Gas成本转移给其他人。 为此,我们需要一个智能合约中的功能,该功能可以处理预先签署的交易,并且需要进行大量的数据检验以确保一切安全。

但是,使用此模式有很多好处,因此,它被广泛使用。 签名允许将交易 gas 成本从用户转移到服务提供商,从而在许多情况下消除了相当大的障碍。 它还允许实现更高级的委派模式,通常会对UX进行相当大的改进。

已为您提供入门代码库,请使用它。
本翻译由 Cell Network 赞助支持。

转载自:https://learnblockchain.cn/article/1496

Uniswap Permit2 - 高效、一致和安全的授权

Uniswap Labs发布了两个新的智能合约Permit2和UniversalRouter, Permit2 确实可以让链上交易体验上一层楼。

前几天,Uniswap Labs发布了两个新的智能合约 Permit2 和 Universal Router :

  1. Permit2 允许代币授权在不同的应用程序中共享和管理,创造一个更统一、更具成本效益、更安全的用户体验。
  2. Universal Router 将ERC20和NFT兑换统一到一个单一的兑换路由器。与Permit2整合后,用户可以在一次兑换交易中兑换多个代币和NFT,同时节省Gas费。

Uniswap最初构思Permit2和Universal Router是为了改进Uniswap自己的产品,优化Gas成本,简化用户交易流程,并加强安全性。在构思的过程中,Unswap觉得其他应用可以从整合这些合约中大大受益。Uniswap 本身致力于建设公共基础设施,因此设计了这些合约,提供整个开发者生态系统使用,包括广泛的文档、SDK。

Permit2是一个代币授权合约,可以在不同的智能合约中安全地共享和管理代币授权。随着越来越多的项目与Permit2集成,可以在所有应用程序中对代币授权进行标准化。反过来,Permit2将通过降低交易成本来改善用户体验,同时提高智能合约的安全性。

典型授权模式

以下是EIP-20中定义的典型代币授权(Approve)方法图示:

  1. Alice在一个ERC20上调用approve(),向一个合约授予支出授权。
  2. Alice在合约上调用一个交互函数,该函数又在ERC20代币合约上调用transferFrom(),转账她的代币。

显然,这种模式是可行的(它无处不在),并且最终可以相当灵活,因为协议通常会最终不间断地长期访问用户的代币。但它有两个众所周知的现实世界的问题。

  • 糟糕的用户体验, 用户必须对他们打算使用的每个代币授权给每个新的协议,这导致了混乱的用户体验,同时这几乎总是一个单独的交易,浪费了Gas和时间。
  • 糟糕的安全性,应用程序通常要求用户授权最大限额,以避免重复上述用户体验问题。这意味着,如果协议被利用,每个用户授权协议使用的代币都有可能被直接从他们的钱包里拿走(因为应用程序可以无限期地访问钱包的整个代币余额)。

授权签名(EIP-2612)模型

EIP-2612 对代币的授权进行了迭代。用户可以通过在他们的交易中附加一个授权签名(Permit)信息来与应用合约交互,而不需要事先授权。
让我们看看ERC20的EIP-2612扩展所启用的方法,它通常是这样的:

  1. Alice签署一个链外的 "permit(签名授权)" 信息,表示她希望授予一个合约一个(EIP-2612)代币的使用权。
  2. Alice提交签署的消息,作为她与所述合约交互的一部分。
  3. 合约调用代币上的 "permit()" 方法,它会使用签名授权信息和签名,同时授予合约一个授权。
  4. 合约现在有了授权,所以它可以在代币上调用transferFrom(),转账由Alice持有的代币。

这解决了典型ERC20 授权方法的两个问题:

  • 用户永远不需要提交一个单独的approve()交易。
  • 不再有悬空授权的必要之恶,因为许可消息授予的是即时授权,通常会立即花费。因此也可以选择一个更合理的授权额度,更重要的是,在签名授权消息可以被使用代币的时间上有一个到期时间。

虽然EIP-2612使代币授权更加安全,但在EIP-2612之前推出的代币并不支持签名授权功能,而且并非所有较新的代币都采用该功能,这就是悲催的现实。因此大多数时候,这种方法不可行。

关于EIP-2612, 我那个登链社区上还有一些文章探讨,可参考这里

Permit2模式

最后,让我们深入探讨Permit2的方法,Permit2 结合了这两种模式,将EIP-2612的用户体验和安全优势扩展到也涵盖了普通的ERC20代币!
为了说明Permit2的革命性,在一个常见的场景中,协议需要转账 Alice持有的代币。

  1. Alice在一个ERC20上调用approve(),典型的方式为的Permit2合约授予一个无限的授权。
  2. Alice签署一个链下 permit2 消息,该消息表明协议合约被允许代表她转账代币。
  3. Alice在协议合约上调用一个交互函数,将签署的 permit2 消息作为参数传入。
  4. 协议合约在Permit2合约上调用 permitTransferFrom(),而Permit2合约又使用其授权(在1中授予)在ERC20合约上调用 "transferFrom()",转账Alice持有的代币。

要求用户首先授予一个明确的授权交易,这似乎是一种倒退。但是,用户不是直接授予协议,而是将其授予规范的Permit2合约。这意味着,如果用户之前已经这样做了,比如说与另一个集成了Permit2的协议进行交互,那么其他每一个协议都可以跳过这个步骤。

这太棒了。

协议不会直接调用ERC20代币上的transferFrom()来执行转账,而是调用规范的Permit2合约上的permitTransferFrom()。Permit2 位于协议和ERC20代币之间,跟踪和验证permit2消息,然后最终使用其授权直接在ERC20上执行transferFrom()调用。这种间接性使得Permit2可以将类似于EIP-2612的好处扩展到每一个现有的ERC20代币上。

同时,像EIP-2612 签名授权信息一样,Permit2 信息也会过期,以限制漏洞的攻击窗口。

集成 Permit2

对于集成Permit2的前端来说,它需要获取一个用户签名,并将其传递到交易中。这些签名签署的Permit2消息结构(PermitTransferFrom)必须符合EIP-712标准(社区有一些相关文章),使用这里这里定义的Permit2域和类型散列。请注意,EIP-712 Permit2对象的 spender 字段需要被设置为将要消费它的合约地址。

智能合约的整合实际上是相当容易的! 任何需要转账用户持有的代币的函数只需要接受任何的许可信息细节和相应的EIP-712用户签名。为了实际转账代币,我们将在规范的Permit2合约上调用permitTransferFrom()。该函数的声明为:

function permitTransferFrom(
    PermitTransferFrom calldata permit,
    SignatureTransferDetails calldata transferDetails,
    address owner,
    bytes calldata signature
) external;

这个函数的参数是:

  • permit - permit2 消息的详情, 有下面的信息。
    • permitted 一个TokenPermissions结构,有以下字段:
      • token - 要转账的代币的地址。
      • amount - 此签名信息可转移的最大金额。
    • nonce - 一个独特的数字,用来防止重用签名许可。一旦签名许可被使用,任何使用该nonce的其他签名许可将无效。
    • deadline - 该签名许可有效的截止时间。
  • transferDetails - 一个包含转账接收人和转账金额的结构,可以小于用户签名的金额。
  • owner - 签署许可的人,也是持有代币。通常,在简单的使用场景中,调用者和用户是同一个人,这应该被设置为调用者(msg.sender)。但在更奇特的集成中,你可能需要更复杂的检查。
  • signature - permit2信息对应的EIP-712签名,由owner签名。如果从签名验证中还原的地址与 owner 不一致,调用将失败。

注意,PermitTransferFrom结构不包括签名信息 EIP-712 typehash 定义中的spender字段。在处理过程中,它将被填入我们的合约地址(permitTransferFrom()的直接调用者)。这就是为什么用户签署的EIP-712对象的spender字段必须是这个合约的地址。

高级集成

前面涵盖了 Permit2 提供的基本功能,但你还可以用它做更多的事情!

  • 自定义见证数据 - 你可以将自定义数据附加到permit2的信息中,这意味着Permit2的签名验证也将扩展到验证这些数据。
  • 批量转账 - 一个用于执行多个转账的批量 permit2 消息,由一个签名来保证。
  • Smart Nonces - 在底层,nonces实际上被写成存储槽中的位字段,并以上面的248位为索引。你可以通过仔细选择重用存储槽的nonce值来节省大量的Gas。
  • 回调签名 - Permit2支持EIP-1271回调签名,它允许智能合约也签署permit2消息。
  • Permit2 Allowances - 对于需要更多灵活性的协议,Permit2支持一个更传统的授权模型,得到了过期时间的额外好处。

The Demo

这里提供的示例代码是一个简单的金库,用户可以使用Permit2将ERC20代币存入其中,随后可以提取。因为它是多用户的,它需要启动转账,以便可靠地记入哪个账户拥有哪个余额。通常情况下,这需要给金库合约授予授权,然后让金库对代币本身执行transferFrom(),但Permit2让我们跳过了这个麻烦!

Test用例部署了一个本地的、字节码的主网Permit2合约的分叉,以测试金库的一个实例。EIP-712 Hash 和签名生成也是用solidity/foundry编写的,但通常应该在链外用你选择的语言在前端或后端执行。

参考资源

转载自:https://learnblockchain.cn/article/5161