链上随机性
在 Move 中生成伪随机值与其他语言中的解决方案类似。 Move 函数可以创建 RandomGenerator 的新实例,并使用它生成不同类型的随机值,例如 generate_u128(&mut generator), generate_u8_in_range(&mut generator, 1, 6):
entry fun roll_dice(r: &Random, ctx: &mut TxContext): Dice {
let generator = new_generator(r, ctx); // generator is a PRG
Dice { value: random::generate_u8_in_range(&mut generator, 1, 6) }
}
Random 有一个保留地址 0x8 。请参阅 random.move 了解用于访问 Bfc 上随机性的 Move API。
尽管 Random 是共享对象,但可变操作无法访问它,并且任何尝试修改它的事务都会失败。
访问随机数只是设计安全应用程序的一部分,您还应该仔细注意如何使用随机性。要安全地访问随机性:
- 将您的函数定义为 (private)
entry。 - 推荐使用局部函数
RandomGenerator生成随机性。 - 确保你的函数的“不推荐的路径”不会比“推荐的路径”收取更多的gas。
使用非公开 entry 函数
虽然组合对于智能合约来说非常强大,但它为攻击使用随机性的函数打开了大门。例如考虑下一个模块:
module games::dice {
...
struct GuessedCorrectly has drop { ... };
/// If you guess correctly the output you get a GuessedCorrectly object.
public fun play_dice(guess: u8, fee: Coin<BFC>, r: &Random, ctx: &mut TxContext): Option<GuessedCorrectly> {
// Pay for the turn
assert!(coin::value(&fee) == 1000000, EInvalidAmount);
transfer::public_transfer(fee, CREATOR_ADDRESS);
// Roll the dice
let generator = new_generator(r, ctx);
if (guess == random::generate_u8_in_range(&mut generator, 1, 6)) {
option::some(GuessedCorrectly {})
} else {
option::none()
}
}
...
}
攻击者可以部署以下功能:
public fun attack(guess: u8, r: &Random, ctx: &mut TxContext): GuessedCorrectly {
let output = dice::play_dice(guess, r, ctx);
option::extract(output) // reverts the transaction if roll_dice returns option::none()
}
攻击者现在可以通过猜测调用 attack ,并且如果猜测不正确,则始终恢复费用转移。
为了防止本示例中的组合攻击,请将 play_dice 定义为私有 entry 函数,以便其他模块的函数无法调用它,例如:
entry fun play_dice(guess: u8, fee: Coin<BFC>, r: &Random, ctx: &mut TxContext): Option<GuessedCorrectly> {
...
}
Move 编译器通过拒绝以 Random 作为参数的 public 函数来强制执行此行为。
可编程事务块(PTB)限制
即使 play_dice 被定义为私有 entry 函数,与之前描述的攻击类似的攻击也涉及 PTB。例如,考虑前面定义的 entry play_dice(guess: u8, fee: Coin<BFC>, r: &Random, ctx: &mut TxContext): Option<GuessedCorrectly> { … } 函数,攻击者可以发布该函数:
public fun attack(output: Option<GuessedCorrectly>): GuessedCorrectly {
option::extract(output)
}
并发送带有命令 play_dice(...), attack(Result(0)) 的 PTB,其中 Result(0) 是第一个命令的输出。和以前一样,攻击利用了 PTB 的原子性质,如果猜测不正确,总是会恢复整个交易,而无需支付费用。发送多笔交易可以重复攻击,每笔交易都以不同的随机性执行,如果猜测不正确则恢复。
为了防止基于 PTB 的组合攻击,Sui 会拒绝在使用 Random 或 MergeCoins 命令的 PTB 作为输入。
实例化 RandomGenerator
RandomGenerator 只要是由使用模块创建的,就是安全的。如果作为参数传递,调用者可能能够预测该 RandomGenerator 实例的输出(例如,通过调用 bcs::to_bytes(&generator) 并解析其内部状态)。
Move 编译器通过拒绝以 RandomGenerator 作为参数的 public 函数来强制执行此行为。