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

如何与以太坊智能合约交互?

背景

智能合约部署在区块链上,它们是包含一些逻辑的代码片段,由EVM执行,将以太坊区块链变成一种世界分布式计算机。

智能合约可以被链下用户/程序调用,向区块链提交交易。链上的合约之间也可以通过调用对方的方法进行交互(不过是在链下调用之后,智能合约不能 "主动触发" 调用)。

智能合约的交互必须遵循ABI规范,这是一套规则和定义,用于规范以太坊生态系统中的智能合约通信。

在这篇博客中,我将以简化的方式介绍,根据ABI规范,必须如何提交数据给区块链,以触发智能合约。然后,我将谈论用不同方法从链下和链上调用智能合约函数。

介绍

在我们开始之前,我将简单地说明我将在这篇博客中使用哪些工具。

对于链下实体,我将使用web3 javascript库(web3.js),因为它封装了JSON-RPC协议,这是用于与区块链通信的实际协议。, 也可以使用其他相同目的的库库,如:ethers.js,不过语法可能会有所不同。

对于链上智能合约,我将使用solidity语言,但任何其他EVM兼容的编程语言也可以。

ABI规范

ABI规范指出了在调用智能合约函数时如何构建发送到 "交易"中作为 "data" 的字节数据。字节数据包含2个主要部分:

  • 函数选择器:前4个字节。它们表明被调用的智能合约的确切函数。函数选择器是通过计算函数签名的哈希值(keccak256)获得的(函数名称及参数类型 "func1(bool,uint256,address) "),然后简单地提取其前4个字节。可能会出现一些函数碰撞,因为我们只是使用4个字节,但可能性非常小...
  • 参数编码:从第5个字节开始,我们必须按照函数签名中指定的顺序添加作为输入参数传递的编码参数。有两种类型的参数,静态参数(值数据类型,如bool,unit256,...)和动态参数(引用数据类型,如数组,...)。静态参数需要32个字节(在方法签名所指示的位置),它们包含参数的值(必要时用0填充)。动态参数则以不同的方式编码。先预留32个字节(在方法签名所指示的位置)表示实际包含参数值的位置(作为从编码的参数部分开始计算的字节偏移),在指定的位置上,前32个字节表示参数的长度(它包含多少个值),然后列出实际的值。

示例 1

  • 函数: baz(uint32 val, bool check) 返回 bool
  • 函数签名 : baz(uint32,bool)
  • 调用 : baz(69, true) 时,ABI 规范编码数据为:

  • 橙色字节 = 函数选择器,keccak256("baz(uint32,bool)")的前4个字节。
  • 蓝色字节 = 第一个编码参数,它是一个静态参数,值为 "69"(0x....45)。
  • 红色字节 = 第二个编码参数,它是一个值为 "true"的静态参数(0x....01)。

示例 2

  • 函数 : sam(bytes name, bool check, uint256[] ids)
  • 函数签名 : sam(byte,bool,uint256[])
  • 调用 : sam("dave", true, [1,2,3]) 时,ABI 规范编码数据为:

  • 橙色字节=函数选择器, keccak256("sam(bytes,bool,uint256[])") 的前4个字节。
  • 蓝色字节=第一个编码参数,它是一个动态的参数,首先表明它的位置(字节0x60)。然后在位置0x60上,第一个字节表示长度(0x.....04 = 4字节,因为数据类型是字节),第二个字节表示参数的实际值: "dave" (0x646176650.........0)。
  • 红色字节=第二个编码参数,它是一个静态参数,值为 "true"(0x....01)。
  • 绿色字节=第三个编码参数,它是一个动态参数,首先指示它的位置(字节0xa0)。然后在位置0xa0上,第一个字节表示长度(0x....03 = 3个字,因为它是一个uint256数组),然后三个字节表示值: "1"、"2"、"3"(0x.....01, 0x....02, 0x......03)。

链下到链上的通信

你有一个前端或后端应用程序,需要与一些以太坊智能合约交互。我将使用javascript的web3.js库,它将处理JSON-RPC协议,也会生成必须提交给区块链的符合abi规范的字节串。

有两种可能的情况,你要么有智能合约JSON ABI,要么没有。

有智能合约JSON ABI

智能合约JSON ABI是一个JSON文件,在你构建智能合约时由solidity编译器生成。编译器实际上会生成两个文件:

  • ByteCode: 将被部署在区块链上的操作码(EVM操作)和来自 "构造函数 "函数的操作码(如果存在的话),在部署智能合约时只执行一次,为字节格式。
  • JSON ABI: 一个json数组,包含与你的智能合约相关的 public 和 external函数事件和错误的列表。每个函数、事件和错误都是数组中的一个json对象,它们包含所有必要的信息,以便链下实体与合约交互。

JSON ABI对象包含以下信息:

函数对象:

  • Type(类型) : 表示函数的类型,选项有 "function"(用于常规函数)、"receive"、"fallback" 和 "constructor"(用于特殊以太坊函数)。
  • Name :函数名称。
  • Inputs(参数) :包含每个函数输入参数的名称、类型和组件的对象数组。
  • Outputs(返回值) : 就像输入参数一样,但对于函数的输出参数。
  • 状态可变性 :函数的可变性:选项是 "view"(只从区块链中读取),"pure"(既不写也不从区块链中读取),"nonpayable"(不能接收以太币)和 "payable"(可接收以太币)。

事件对象:

  • Type : 总是 "事件" 。
  • Name :事件名称 。
  • Inputs : 包含每个事件参数的数组,其名称、类型、组件和(是否有)索引。
  • Anonymous : 如果事件被声明为匿名,则为真。

错误对象:

  • Type(类型) : 总是 "error"
  • Name(名称) :错误名称
  • Inputs : 包含每个错误参数的对象数组,其名称、类型和组件。

为了从你的链下应用程序与智能合约进行交互,首先需要导入JSON Abi文件,然后提供JSON Abi和指向智能合约的地址来实例化一个的对象。从那一刻起,你可以像对待其他对象一样直接调用合约的方法。

智能合约的调用将以异步方式完成:

// Reference the smart contract
const SmartContract= require(“SmartContract”);

// Retrieve the JSON ABI and address
const SmartContractAbi = SmartContract.abi;
const SmartContractAddress = "0x......"

// Instantiate an object that "encapsulates" the smart contract
const SmartContractObject = new web3.eth.Contract(SmartContractAbi, SmartContractAddress);

// Now you are ready to interact with the smart contract. Functions // invocations will return promises.
SmartContractObject.methods.func1(…).send({from: …, …}).on(…);
SmartContractObject.methods.func2(…).call({from: …}).on(…);

没有智能合约的JSON ABI

如果你没有JSON ABI,你仍然可以与智能合约进行交互,但这将是一个有点麻烦和烦人的过程。
你将不得不自己从方法定义(json格式)、希望提交的输入参数中创建区块链交易,并将其直接发送到智能合约地址。
你可以提交一个 "send"交易(将改变区块链状态的实际交易)或一个 "call" 交易(从以太坊的角度看不是一个实际的交易,因为它将只读取数据)。
交易将以异步方式提交:

// Define the Transaction Data
const TransactionData = web3.eth.abi.encodeFunctionCall({
    name: 'myMethod',
    type: 'function',
    inputs: [{
        type: 'uint256',
        name: 'myNumber'
    },{
        type: 'string',
        name: 'myString'
    }]
}, ['2345675643', 'Hello!%']);

// Now you can either send a transaction or make a call. In both 
// cases you will be dealing with Promises
web3.eth.sendTransaction({from: …, to: …, data: TransactionData, …}).on(…);
web3.eth.call({from: …, to: …, data: TransactionData, …}).on(…);

链上到链上的通信

你正在实现一个智能合约,想从你的代码中调用另一个合约的函数。可以使用 solidity 编程语言,它提供了一些内置的函数,来生成符合 abi 规范的字节串。

就像链下到链上的情况一样,有两种可能的情况,你要么有智能合约接口,要么没有。

有智能合约接口

如果你有你想调用的智能合约的接口,solidity将为你做大部分的工作。

你只需要将接口导入到智能合约文件,实例化一个接口类型的对象,并传递智能合约地址,你就可以开始了。就可以像其他对象一样调用合约的方法了。

// Import the interface and define the contract object using the 
// interface as a data type
import "IContract.sol";
IContract Contract;

// Instantiate the contract with its address
address contractAddress = 0x.......;
Contract = IContract(contractAddress);

// Invoke the contract's methods as defined by the interface
Contract.func1(....);

没有智能合约接口

如果你没有合约接口,那么你将不得不构建整个消息。
你将需要合约地址,方法签名(方法名称和输入参数类型用逗号分隔)和你希望提交的参数(也用逗号分隔)。

// Contract Address and function signature
address contractAddress = 0x.......;
string memory Method = “func1(uint256,bool)”;

// Define the abi compliant data
bytes memory AbiData = abi.encodeWithSignature(Method, 345223, true);

// Send the message
(bool success, bytes memory data) = contractAddress.call(AbiData);

警告

需要注意的是,不管你与智能合约的交互方式如何,如果你使用的智能合约地址是错误的,你仍然可以提交交易,没有任何的检查。如果智能合约确实有一个与你的调用相匹配的函数,它将被执行,如果没有,那么交易可能失败,也可能成功,如果智能合约有一个 "fallback()" 函数......重点是,后果可能是意想不到的,而且可能是无法检测的,这就是为什么你必须确定你向哪个合约发送交易,始终确保合约地址是正确的。

参考原文: https://medium.com/coinmonks/ethereum-smart-contracts-how-to-communicate-with-them-abi-specification-web3-solidity-db056218b251
转载:https://learnblockchain.cn/article/5090

详解 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