Skip to content

Commit a8e2ef2

Browse files
authored
Merge pull request #12800 from ethereum/april-zh-import
chore: April zh import
2 parents 787bd87 + 42a52a1 commit a8e2ef2

File tree

15 files changed

+3053
-190
lines changed
  • public/content/translations/zh/developers/tutorials
    • all-you-can-cache
    • eip-1271-smart-contract-signatures
    • erc-721-vyper-annotated-code
    • erc20-annotated-code
    • erc20-with-safety-rails
    • getting-started-with-ethereum-development-using-alchemy
    • guide-to-smart-contract-security-tools
    • hello-world-smart-contract-fullstack
    • how-to-mock-solidity-contracts-for-testing
    • kickstart-your-dapp-frontend-development-wth-create-eth-app
    • logging-events-smart-contracts
    • merkle-proofs-for-offline-data-integrity
    • secure-development-workflow
    • sending-transactions-using-web3-and-alchemy
    • waffle-test-simple-smart-contract

15 files changed

+3053
-190
lines changed

public/content/translations/zh/developers/tutorials/all-you-can-cache/index.md

Lines changed: 867 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
title: "EIP-1271:签署和验证智能合约签名"
3+
description: 基于 EIP-1271 的智能合约签名生成与验证概述。 我们还介绍了 Safe(原 Gnosis Safe)中使用的 EIP-1271 实现,以此为智能合约开发者提供一个可参考的具体例子。
4+
author: Nathan H. Leung
5+
lang: zh
6+
tags:
7+
- "eip-1271"
8+
- "智能合约"
9+
- "验证"
10+
- "签名"
11+
skill: intermediate
12+
published: 2023-01-12
13+
---
14+
15+
[EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) 标准允许智能合约验证签名。
16+
17+
在本教程中,我们概述了数字签名、EIP-1271 的背景,以及 [Safe](https://safe.global/)(原 Gnosis Safe)使用的 EIP-1271 的具体实现。 总之,这可以作为在你自己的合约中实现 EIP-1271 的起点。
18+
19+
## 什么是签名?
20+
21+
在这种情况下,签名(更准确地说“数字签名”)是一条信息加上某种证明,该证明表明信息来自某个特定的人/发件人/地址。
22+
23+
例如,数字签名可能如下所示:
24+
25+
1. 信息:“我想用我的以太坊钱包登录这个网站”
26+
2. 签名者:我的地址是 `0x000…`
27+
3. 证明:这里有一些证明,证明我,`0x000…`,确实创建了这整条信息(通常会加密)。
28+
29+
值得注意的是,数字签名包括“信息”和“签名”两部分。
30+
31+
为什么? 例如,如果你给我一份合同让我签字,然后我剪掉了签名页,只把我的签名还给你,而没有合同的其他部分,那么这份合同就无效。
32+
33+
同样,如果没有相关信息,数字签名也将没有任何意义!
34+
35+
## 为什么会有 EIP-1271?
36+
37+
要在基于以太坊的区块链上创建数字签名,通常需要一个别人不知道的秘密私钥。 这样,你的签名才是你的(如果其他人不知道密钥,则无法创建相同的签名)。
38+
39+
你的以太坊帐户(即你的外部帐户/EOA)有一个与之关联的私钥,这是通常在网站或去中心化应用程序要求你签名时使用的私钥(例如“使用以太坊登录”)。
40+
41+
应用程序可以在[不知道你的私钥](https://en.wikipedia.org/wiki/Public-key_cryptography)的情况下[验证你使用 ethers.js 等第三方库创建的签名](https://docs.alchemy.com/docs/how-to-verify-a-message-signature-on-ethereum),并且确认_你_是那个创建签名的人。
42+
43+
> 事实上,由于外部帐户数字签名使用公钥加密,可以在**链下**生成和验证! 这就是无燃料去中心化自治组织投票的工作原理—并非在链上提交投票,而是使用加密库在链下创建和验证数字签名。
44+
45+
虽然外部帐户帐户有私钥,但智能合约帐户没有任何类型的私钥或密钥(因此“使用以太坊登录”等自然不适用于智能合约帐户)。
46+
47+
EIP-1271 旨在解决:如果智能合约没有可以合并到签名中的密匙,我们如何判断智能合约签名是有效的?
48+
49+
## EIP-1271 是如何工作的?
50+
51+
智能合约没有可用于签署信息的私钥。 那么,我们如何辨别签名的真伪呢?
52+
53+
有一种想法是,我们可以直接_询问_智能合约签名是否真实!
54+
55+
EIP-1271 所做的就是将“询问”智能合约给定签名是否有效这一想法标准化。
56+
57+
实现 EIP-1271 的合约必须有一个名为 `isValidSignature` 的函数,该函数接收信息和签名。 然后,合约可以运行一些验证逻辑(规范并没有在此强制执行任何特定内容),然后返回一个表示签名是否有效的值。
58+
59+
如果 `isValidSignature` 返回表示签名有效的结果,这就相当于合约在说“是的,我承认这个签名和信息!”
60+
61+
### 接口
62+
63+
以下是 EIP-1271 规范中的确切接口(我们将在下文中讨论 `_hash` 参数,但现在请将其视为正在验证的信息):
64+
65+
```jsx
66+
pragma solidity ^0.5.0;
67+
68+
contract ERC1271 {
69+
70+
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
71+
bytes4 constant internal MAGICVALUE = 0x1626ba7e;
72+
73+
/**
74+
* @dev Should return whether the signature provided is valid for the provided hash
75+
* @param _hash Hash of the data to be signed
76+
* @param _signature Signature byte array associated with _hash
77+
*
78+
* MUST return the bytes4 magic value 0x1626ba7e when function passes.
79+
* MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
80+
* MUST allow external calls
81+
*/
82+
function isValidSignature(
83+
bytes32 _hash,
84+
bytes memory _signature)
85+
public
86+
view
87+
returns (bytes4 magicValue);
88+
}
89+
```
90+
91+
## EIP-1271 实现示例:安全
92+
93+
合约可通过多种方式实现 `isValidSignature`—规范本身对具体实现没有做出太多要求。
94+
95+
实现 EIP-1271 的一个代表性合约是 Safe(原 Gnosis Safe)。
96+
97+
在 Safe 的代码中,[实现了 ](https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol) `isValidSignature`,从而使签名可通过以下[两种方式](https://ethereum.stackexchange.com/questions/122635/signing-messages-as-a-gnosis-safe-eip1271-support)创建和验证:
98+
99+
1. 链上消息
100+
1. 创建:一个安全的所有者创建一个新的安全交易来“签名”一则消息,并将消息作为数据传入交易中。 一旦足够的所有者对交易进行签名,使签名数量达到多签名阈值后,交易就会被广播并运行。 在交易中,会调用一个安全函数,将消息添加到“已批准”消息列表中。
101+
2. 验证:调用 Safe 合约的 `isValidSignature` 函数,并传入待验证的消息作为消息参数,传入[一个空值作为签名参数](https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol#L32)(即 `0x`)。 Safe 合约会看到签名参数为空,将不会对签名进行密码验证,而是直接检查消息是否在“已批准”消息列表中。
102+
2. 链下信息:
103+
1. 创建:安全的所有者在链下创建一条消息,然后让其他安全的所有者分别对消息进行签名,直到有足够的签名来达到多签名批准阈值。
104+
2. 验证:调用 `isValidSignature`。 在消息参数中,传入要验证的消息。 在签名参数中,传入每个安全所有者的签名,所有签名都是背靠背连接在一起的。 Safe 合约会检查是否有足够签名以达到阈值,**并**检查每个签名的有效性。 如果是,它将返回一个表示签名验证成功的值。
105+
106+
## `_hash` 参数到底是什么? 为什么不传递整条消息?
107+
108+
你可能已经发现,[EIP-1271 接口](https://eips.ethereum.org/EIPS/eip-1271)中的 `isValidSignature` 函数并不接收消息本身,而是使用 `_hash` 参数。 这意味着我们只需将消息的 32 个字节长的哈希值(一般通过 keccak256)传入 `isValidSignature`,而不是传入任意长度的完整消息。
109+
110+
calldata 的每一个字节(即传入智能合约函数的函数参数数据)都会[花费 16 个单位燃料(若为空字节,则会花费 4 上单位燃料)](https://eips.ethereum.org/EIPS/eip-2028),因此在消息较长时,这可以节省很多的燃料费。
111+
112+
### 先前的 EIP-1271 规范
113+
114+
现有 EIP-1271 规范中的 `isValidSignature` 函数,其第一个参数为 `bytes` 类型(任意长度,而不是固定长度的 `bytes32`),并且参数名为 `message`。 这是 EIP-1271 标准的一个[旧版本](https://github.com/safe-global/safe-contracts/issues/391#issuecomment-1075427206)。
115+
116+
## 如何在我的合约中实现 EIP-1271?
117+
118+
这是一个非常开放式的规范。 Safe 的实现有一些不错的思路:
119+
120+
- 你可以认为合约“所有者”的外部帐户签名是有效的。
121+
- 你可以存储已批准消息的列表,并且只认为这些消息是有效的。
122+
123+
最终,都将由作为合约开发者的你来决定!
124+
125+
## 结论
126+
127+
[EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) 是一个允许智能合约验证签名的通用标准。 它为智能合约打开了一扇门,使其行为更像外部帐户(例如,为“使用以太坊登录”提供一种与智能合约协同工作的方式),而且它可通过多种方式实现(例如 Safe 有一个有用且有趣的实现方式值得考虑)。

public/content/translations/zh/developers/tutorials/erc-721-vyper-annotated-code/index.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ from vyper.interfaces import ERC721
3333
implements: ERC721
3434
```
3535

36-
ERC-721 接口内置在 Vyper 语言中。 [您可以点击此处查看代码定义](https://github.com/vyperlang/vyper/blob/master/vyper/builtin_interfaces/ERC721.py) 接口定义是用 Python 而不是 Vyper 编写的,因为接口不仅在区块链内使用, 而且在外部客户端向区块链发送交易时也使用,而客户端可能 是用 Python 编写的。
36+
ERC-721 接口内置在 Vyper 语言中。 [你可以点击此处查看代码定义](https://github.com/vyperlang/vyper/blob/master/vyper/builtin_interfaces/ERC721.py) 接口定义是用 Python 而不是 Vyper 编写的,因为接口不仅在区块链内使用, 而且在外部客户端向区块链发送交易时也使用,而客户端可能 是用 Python 编写的。
3737

3838
第一行导入接口,第二行指定我们在这里执行它。
3939

@@ -47,7 +47,7 @@ interface ERC721Receiver:
4747

4848
ERC-721 支持两类转账:
4949

50-
- `transferFrom`,让发送者指定任何目的地地址并让发送者 承担转账责任。 这意味着您可以转账到一个无效的地址,在这种情况下,NFT 将永远丢失。
50+
- `transferFrom`,让发送者指定任何目的地地址并让发送者 承担转账责任。 这意味着你可以转账到一个无效的地址,在这种情况下,NFT 将永远丢失。
5151
- `safeTransferFrom`,检查目的地址是否是合约。 如果是,ERC-721 合约 将会询问接收合约是否要接收这笔 NFT 转账。
5252

5353
接收合约必须执行 `ERC721Receiver` 才能回应 `safeTransferFrom` 请求。
@@ -112,7 +112,7 @@ event Approval:
112112
tokenId: indexed(uint256)
113113
```
114114

115-
ERC-721 批准与 ERC-20 限额类似。 特定地址只允许转移特定 代币。 这就形成了一种合约在接受代币时作出回应的机制。 合约不能侦听 事件,所以如果您只是把代币转移给合约,它们不会“知道”这笔转账。 因此,代币所有者 首先提交批准,然后向合约发送请求:“我批准你转移 代币 X,请执行......”。
115+
ERC-721 批准与 ERC-20 限额类似。 特定地址只允许转移特定 代币。 这就形成了一种合约在接受代币时作出回应的机制。 合约不能侦听 事件,所以如果你只是把代币转移给合约,它们不会“知道”这笔转账。 因此,代币所有者 首先提交批准,然后向合约发送请求:“我批准你转移 代币 X,请执行......”。
116116

117117
这是一种设计选择,使 ERC-721 标准与 ERC-20 标准类似。 由于 ERC-721 代币 为非同质化代币,合约还可以通过查看代币的所有权来确定 它得到了一个特定代币。
118118

@@ -129,7 +129,7 @@ event ApprovalForAll:
129129
approved: bool
130130
```
131131

132-
有时候,拥有一个能够管理某个帐户所有特定类型代币(由一个特定合约 管理的所有代币)*运营者*是很有用的,这类似于委托书。 例如,我可能想把这样一种权力赋予一个合约,即 检查我是否已经 6 个月没有联系它了,如果属实,就会把我的资产分配给我的继承者(如果他们中一人要求这样做,合约在没有 被交易调用时什么都做不了)。 在 ERC-20 中,我们只需给继承合约提供一个高限额即可。 但这对 ERC-721 不起作用,因为代币是非同质化的。 这是对应的。
132+
有时候,拥有一个能够管理某个帐户所有特定类型代币(由一个特定合约 管理的所有代币)的_运营者_是很有用的,这类似于委托书。 例如,我可能想把这样一种权力赋予一个合约,即 检查我是否已经 6 个月没有联系它了,如果属实,就会把我的资产分配给我的继承者(如果他们中一人要求这样做,合约在没有 被交易调用时什么都做不了)。 在 ERC-20 中,我们只需给继承合约提供一个高限额即可。 但这对 ERC-721 不起作用,因为代币是非同质化的。 这是对应的。
133133

134134
`approved` 值表示事件是等待批准,还是等待撤回批准。
135135

@@ -161,7 +161,7 @@ ownerToNFTokenCount: HashMap[address, uint256]
161161
ownerToOperators: HashMap[address, HashMap[address, bool]]
162162
```
163163

164-
一个帐户可能有多个运营者。 仅有 `HashMap` 不足以跟踪它们,因为每个键都会生成单一值。 然而,可以将 `HashMap[address, bool]` 作为值。 默认情况下,每个地址的都值是 `False`,这意味着它 不是运营者。 您可以根据需要将值设置为 `True`
164+
一个帐户可能有多个运营者。 仅有 `HashMap` 不足以跟踪它们,因为每个键都会生成单一值。 然而,可以将 `HashMap[address, bool]` 作为值。 默认情况下,每个地址的都值是 `False`,这意味着它 不是运营者。 你可以根据需要将值设置为 `True`
165165

166166
```python
167167
# @dev Address of minter, who can mint a token
@@ -202,7 +202,7 @@ def __init__():
202202
"""
203203
```
204204

205-
在 Python 和 Vyper 中,通过指定多行字符串(以 `"""` 起始和结束),您还可以创建注释,但不能以任何方式使用它。 这些注释也可以包括 [NatSpec](https://vyper.readthedocs.io/en/latest/natspec.html) 注释。
205+
在 Python 和 Vyper 中,通过指定多行字符串(以 `"""` 起始和结束),你还可以创建注释,但不能以任何方式使用它。 这些注释也可以包括 [NatSpec](https://vyper.readthedocs.io/en/latest/natspec.html) 注释。
206206

207207
```python
208208
self.supportedInterfaces[ERC165_INTERFACE_ID] = True
@@ -221,7 +221,7 @@ def __init__():
221221
@external
222222
```
223223

224-
函数定义前面以 (`@`) 开头的这些关键词称为*修饰符*。 它们 规定能够调用函数的环境。
224+
函数定义前面以 (`@`) 开头的这些关键词称为_修改器_。 它们 规定能够调用函数的环境。
225225

226226
- `@view` 指定此函数为 view 函数。
227227
- `@external` 指定该特定函数可以由交易及其它合约调用。
@@ -295,7 +295,7 @@ def getApproved(_tokenId: uint256) -> address:
295295
return self.idToApprovals[_tokenId]
296296
```
297297

298-
注意,`getApproved` *可以*返回零。 如果代币有效,则返回 `self.idToApprovals[_tokenId]`。 如果没有批准者,该值为 0。
298+
注意,`getApproved` _可以_返回零。 如果代币有效,则返回 `self.idToApprovals[_tokenId]`。 如果没有批准者,该值为 0。
299299

300300
```python
301301
@view
@@ -323,7 +323,7 @@ def isApprovedForAll(_owner: address, _operator: address) -> bool:
323323
@internal
324324
```
325325

326-
修饰符 `@internal` 表示该函数只能由 同一合约内的其他函数访问。 按照惯例,这些函数名称也以下划线 (`_`) 开头。
326+
修改器 `@internal` 表示该函数只能由 同一合约内的其他函数访问。 按照惯例,这些函数名称也以下划线 (`_`) 开头。
327327

328328
```python
329329
def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
@@ -347,7 +347,7 @@ def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
347347
2. 该地址经批准可以使用该代币
348348
3. 该地址是代表代币所有者的运营者
349349

350-
上面的函数可以是一个视图函数,因为它并不改变状态。 为了降低运营成本,任何*可以* 成为视图函数的函数都*应该*成为视图函数
350+
上面的函数可以是一个视图函数,因为它并不改变状态。 为了降低运营成本,任何_可以_ 成为视图函数的函数都_应该_成为视图函数
351351

352352
```python
353353
@internal
@@ -451,7 +451,7 @@ def transferFrom(_from: address, _to: address, _tokenId: uint256):
451451
self._transferFrom(_from, _to, _tokenId, msg.sender)
452452
```
453453

454-
此函数允许您向任意地址转账。 除非该地址是用户或是知道如何转移代币的 合约,否则您转移的任何代币都将卡在该地址中变得毫无用处
454+
此函数允许你向任意地址转账。 除非该地址是用户或是知道如何转移代币的 合约,否则你转移的任何代币都将卡在该地址中变得毫无用处
455455

456456
```python
457457
@external
@@ -485,7 +485,7 @@ def safeTransferFrom(
485485
if _to.is_contract: # check if `_to` is a contract address
486486
```
487487

488-
首先检查地址是否为合约(如果有代码)。 如果不是,假定它是一个用户 地址,并且该用户能够使用或转移代币。 但不要让该地址 给您一种虚假的安全感。 如果您将代币转移到一个没有人知道私钥的地址,即使使用了 `safeTransferFrom`,也可能损失代币。
488+
首先检查地址是否为合约(如果有代码)。 如果不是,假定它是一个用户 地址,并且该用户能够使用或转移代币。 但不要让该地址 给你一种虚假的安全感。 如果你将代币转移到一个没有人知道私钥的地址,即使使用了 `safeTransferFrom`,也可能损失代币。
489489

490490
```python
491491
returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
@@ -518,7 +518,7 @@ def approve(_approved: address, _tokenId: uint256):
518518
assert _approved != owner
519519
```
520520

521-
根据惯例,如果您不想要批准者,可以指定零地址而不是您自己
521+
根据惯例,如果你不想要批准者,可以指定零地址而不是你自己
522522

523523
```python
524524
# Check requirements
@@ -527,7 +527,7 @@ def approve(_approved: address, _tokenId: uint256):
527527
assert (senderIsOwner or senderIsApprovedForAll)
528528
```
529529

530-
要设置批准,您可以是所有者,也可以是所有者授权的运营者。
530+
要设置批准,你可以是所有者,也可以是所有者授权的运营者。
531531

532532
```python
533533
# Set the approval
@@ -578,7 +578,7 @@ def mint(_to: address, _tokenId: uint256) -> bool:
578578
assert msg.sender == self.minter
579579
```
580580

581-
只有铸币者(创建 ERC-721 合约的帐户)可以铸造新代币。 如果我们将来想改变铸币者的 身份,这可能会成为一个问题。 在生产合约 中,您可能需要一个函数,允许 铸币者将铸币者特权转让给其他人。
581+
只有铸币者(创建 ERC-721 合约的帐户)可以铸造新代币。 如果我们将来想改变铸币者的 身份,这可能会成为一个问题。 在生产合约 中,你可能需要一个函数,允许 铸币者将铸币者特权转让给其他人。
582582

583583
```python
584584
# Throws if `_to` is zero address
@@ -616,17 +616,17 @@ def burn(_tokenId: uint256):
616616

617617
# 使用此合约 {#using-contract}
618618

619-
与 Solidity 相比,Vyper 中没有继承。 这种有意而为之的设计选择,是为了使代码 更清晰,从而更容易受保护。 因此,要创建您自己的 Vyper ERC-721 合约,您可以 利用[此合约](https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy),并修改 它以实现想要的业务逻辑。
619+
与 Solidity 相比,Vyper 中没有继承。 这种有意而为之的设计选择,是为了使代码 更清晰,从而更容易受保护。 因此,要创建你自己的 Vyper ERC-721 合约,你可以 利用[此合约](https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy),并修改 它以实现想要的业务逻辑。
620620

621621
# 总结 {#conclusion}
622622

623623
回顾一下,下面是此合约中最重要的几点:
624624

625625
- 要通过安全转账方式接收 ERC-721 代币,合约必须实现 `ERC721Receiver` 接口。
626-
- 即使使用了安全转账方式,如果您将代币发送到私钥未知 的地址,代币仍然会被卡住。
626+
- 即使使用了安全转账方式,如果你将代币发送到私钥未知 的地址,代币仍然会被卡住。
627627
- 当操作出现问题时,最好 `revert` 该调用,而不是只返回 失败值。
628628
- 有了所有者,ERC-721 代币才存在。
629-
- 有三种经过授权的 NFT 转账方式。 您可以是所有者,可以针对特定代币获得批准, 或者可以是所有者全部代币的运营者。
629+
- 有三种经过授权的 NFT 转账方式。 你可以是所有者,可以针对特定代币获得批准, 或者可以是所有者全部代币的运营者。
630630
- 过去的事件只在区块链之外可见。 区块链中运行的代码无法看到它们。
631631

632632
现在去实现安全的 Vyper 合约吧。

0 commit comments

Comments
 (0)