Skip to content

Commit

Permalink
Merge pull request #789 from jes16jupyter/main
Browse files Browse the repository at this point in the history
[Improvement]: Add some information on several chapters
  • Loading branch information
AmazingAng authored Oct 19, 2024
2 parents b423ea2 + 01758f2 commit 8a70e4f
Show file tree
Hide file tree
Showing 7 changed files with 26 additions and 6 deletions.
4 changes: 2 additions & 2 deletions 05_DataStorage/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ tags:

## 数据位置

Solidity数据存储位置有三类:`storage``memory``calldata`。不同存储位置的`gas`成本不同。`storage`类型的数据存在链上,类似计算机的硬盘,消耗`gas`多;`memory``calldata`类型的临时存在内存里,消耗`gas`少。大致用法:
Solidity数据存储位置有三类:`storage``memory``calldata`。不同存储位置的`gas`成本不同。`storage`类型的数据存在链上,类似计算机的硬盘,消耗`gas`多;`memory``calldata`类型的临时存在内存里,消耗`gas`少。整体消耗`gas`从多到少依次为:`storage` > `memory` > `calldata`大致用法:

1. `storage`:合约里的状态变量默认都是`storage`,存储在链上。

Expand Down Expand Up @@ -68,7 +68,7 @@ function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
![5-2.png](./img/5-2.png)
- `memory`赋值给`memory`,会创建引用,改变新变量会影响原变量。

- 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方
- 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方。这有时会涉及到开发中的问题,比如从`storage`中读取数据,赋值给`memory`,然后修改`memory`的数据,但如果没有将`memory`的数据赋值回`storage`,那么`storage`的数据是不会改变的。

## 变量的作用域

Expand Down
2 changes: 2 additions & 0 deletions 12_Event/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ keccak256("Transfer(address,address,uint256)")

`indexed`标记的参数可以理解为检索事件的索引“键”,方便之后搜索。每个 `indexed` 参数的大小为固定的256比特,如果参数太大了(比如字符串),就会自动计算哈希存储在主题中。

这里其实会引入一个新的问题,根据Solidity的[官方文档](https://docs.soliditylang.org/en/v0.8.27/abi-spec.html#encoding-of-indexed-event-parameters), 对于非值类型的参数(如arrays, bytes, strings), Solidity不会直接存储,而是会将`Keccak-256`哈希存储在主题中,从而导致数据信息的丢失。这对于某些依赖于链上事件的DAPP(跨链,用户注册等等)来说,可能会导致事件检索困难,需要解析哈希值。

### 数据 `data`

事件中不带 `indexed`的参数会被存储在 `data` 部分中,可以理解为事件的“值”。`data` 部分的变量不能被直接检索,但可以存储任意大小的数据。因此一般 `data` 部分可以用来存储复杂的数据结构,例如数组和字符串等等,因为这些数据超过了256比特,即使存储在事件的 `topics` 部分中,也是以哈希的方式存储。另外,`data` 部分的变量在存储上消耗的gas相比于 `topics` 更少。
Expand Down
2 changes: 1 addition & 1 deletion 27_ABIEncode/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function encode() public view returns(bytes memory result) {

### `abi.encodePacked`

将给定参数根据其所需最低空间编码。它类似 `abi.encode`,但是会把其中填充的很多`0`省略。比如,只用1字节来编码`uint8`类型。当你想省空间,并且不与合约交互的时候,可以使用`abi.encodePacked`,例如算一些数据的`hash`时。
将给定参数根据其所需最低空间编码。它类似 `abi.encode`,但是会把其中填充的很多`0`省略。比如,只用1字节来编码`uint8`类型。当你想省空间,并且不与合约交互的时候,可以使用`abi.encodePacked`,例如算一些数据的`hash`时。需要注意,`abi.encodePacked`因为不会做填充,所以不同的输入在拼接后可能会产生相同的编码结果,导致冲突,这也带来了潜在的安全风险。

```solidity
function encodePacked() public view returns(bytes memory result) {
Expand Down
5 changes: 4 additions & 1 deletion 37_Signature/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ print(f"签名:{signed_message['signature'].hex()}")

为了验证签名,验证者需要拥有`消息``签名`,和签名使用的`公钥`。我们能验证签名的原因是只有`私钥`的持有者才能够针对交易生成这样的签名,而别人不能。

**4. 通过签名和消息恢复公钥:**`签名`是由数学算法生成的。这里我们使用的是`rsv签名``签名`中包含`r, s, v`三个值的信息。而后,我们可以通过`r, s, v``以太坊签名消息`来求得`公钥`。下面的`recoverSigner()`函数实现了上述步骤,它利用`以太坊签名消息 _msgHash``签名 _signature`恢复`公钥`(使用了简单的内联汇编):
**4. 通过签名和消息恢复公钥:**`签名`是由数学算法生成的。这里我们使用的是`rsv签名``签名`中包含`r, s, v`三个值的信息,长度分别为32 bytes,32 bytes,1 byte。而后,我们可以通过`r, s, v``以太坊签名消息`来求得`公钥`。下面的`recoverSigner()`函数实现了上述步骤,它利用`以太坊签名消息 _msgHash``签名 _signature`恢复`公钥`(使用了简单的内联汇编):

```solidity
// @dev 从_msgHash和签名_signature中恢复signer地址
Expand Down Expand Up @@ -171,6 +171,9 @@ print(f"签名:{signed_message['signature'].hex()}")
_msgHash:0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b
_signature:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c
```

需要注意的是,这里需要对输入参数`_signature`的长度进行检查,确保其长度为65bytes,否则会产生签名重放问题。具体问题可以参考[BlazCTF中的Cyber Cartel](https://github.com/DeFiHackLabs/blazctf-2024-writeup/blob/main/writeup/cyber-cartel.md).

![通过签名和消息恢复公钥](./img/37-8.png)

**5. 对比公钥并验证签名:** 接下来,我们只需要比对恢复的`公钥`与签名者公钥`_signer`是否相等:若相等,则签名有效;否则,签名无效:
Expand Down
2 changes: 2 additions & 0 deletions 51_ERC4626/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ contract ERC4626 is ERC20, IERC4626 {
}
```

当然,本文中的`ERC4626`合约仅是为了教学演示使用,在实际使用时,还需要考虑如`Inflation Attack`, `Rounding Direction`等问题。在生产中,建议使用`openzeppelin`的具体实现。

## `Remix`演示

**注意:** 以下运行示例使用了remix中第二个账户,即`0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2`, 来部署合约, 调用合约方法.
Expand Down
2 changes: 2 additions & 0 deletions S01_ReentrancyAttack/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ contract Attack {
4. 调用`Bank`合约的`getBalance()`函数,发现余额已被提空。
5. 调用`Attack`合约的`getBalance()`函数,可以看到余额变为`21 ETH`,重入攻击成功。

当然,不仅仅`ETH`转账会触发重入攻击,`ERC721``ERC1155``safeTransfer()``safeTransferFrom()`安全转账函数,还有`ERC777``callback`函数,都可能会引发重入攻击。所以这更多的是一个宏观上的设计问题,而不仅仅局限于ETH转账本身。

## 预防办法

目前主要有两种办法来预防可能的重入攻击漏洞: 检查-影响-交互模式(checks-effect-interaction)和重入锁。
Expand Down
15 changes: 13 additions & 2 deletions S06_SignatureReplay/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,21 @@ contract SigReplay is ERC20 {
}
```

3. 对于由用户输入`signature`的场景,需要检验`signature`的长度,确保其长度为`65bytes`,否则也会产生签名重放问题。

```solidity
function mint(address to, uint amount, bytes memory signature) public {
require(signature.length == 65, "Invalid signature length");
...
}
```

## 总结

这一讲,我们介绍了智能合约中的签名重放漏洞,并介绍了两个预防方法
这一讲,我们介绍了智能合约中的签名重放漏洞,并介绍了三个预防方法

1. 将使用过的签名记录下来,防止二次使用。

2.`nonce``chainid` 包含到签名消息中。
2.`nonce``chainid` 包含到签名消息中。

3. 对于由用户输入`signature`的场景,需要检验`signature`的长度,确保其长度为`65bytes`,否则也会产生签名重放问题。

0 comments on commit 8a70e4f

Please sign in to comment.