创建文章

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Article Thumbnail

“签约”智能合约:安全的高级智能合约设计

我们已经详细阐述了怎样编写智能合约,细致描述了交易的结构。今天我们来深入了解交易头中默默嵌套的 Lease 字段暗藏的宝藏功能。我们将探索这一可选字段的具体功能,探讨何时要选择在智能合约中加入该字段,然后看一些示例代码和应用场景。

简介

Lease 字段是在 Algorand 2.0 中引入的,可用于保障偏好长期的专有交易执行(如定期付款)的安全,缓解费用波动,实现长期智能合约。我们会在本文中探索其中一些概念。从技术上讲,包含 Lease 值([32] 字节)的已确认交易将在验证节点上保留 { Sender : Lease } 数据对,直到其 LastValid 过期。这会创建一个“锁”,阻止任何未来交易在过期前使用同一 { Sender : Lease } 对。

典型的一次性付款资产“发送”交易生存时间很短,未必能从使用 Lease 值中获益,但如果在某些智能合约设计中没有定义这个值,可能会导致账户易受拒绝服务攻击。我们来看看为什么要考虑使用 Lease 字段,以及哪些时候绝对应该使用这一字段。

信息

快速回顾 Algorand 交易和智能合约验证。每个交易都包含一个,其中定义了交易类型,包含验证期间使用的必需字段和可选字段。FirstValidLastValid 是两个必需字段,定义交易被网络验证的范围(至多 1000 轮),超出即丢弃。通常,这相当于 MainNet 上至多 70 分钟的“有效窗口”。很多智能合约场景包含“目标”有效窗口计算,也在其验证逻辑中增设了特定的 Lease 值。这几项结合在一起,智能合约就能用于经常性的定期付款交易、密钥管理及其他场景。

Hello World

我们应该都很熟悉演示 Alice 发送几个 Algo 币给 Bob 的“hello world”样例了。这就是个生存期很短的交易,不太可能在正常网络条件下从指定 Lease 值中获益。使用 goal:

$ goal clerk send –from $ALICE –to $BOB –amount $AMOUNT

在正常网络条件下,该交易会在下一轮确认(约 5 秒)。Bob 从 Alice 处收到钱,就没有后顾之忧了。

但是,现在我们假设网络拥塞,费用高于正常水平,Alice 希望尽量减少费用支出,同时确保网络仅确认一笔给 Bob 的付款交易。Alice 可以构造一系列给 Bob 的交易,每笔都定义相同的 LeaseFirstValidLastValid 值,但增加 Fee 数额,然后广播到网络上。

# Define transaction fields
$ LEASE_VALUE=$(echo "Lease value (at most 32-bytes)" | xxd -p | base64)
$ FIRST_VALID=$(goal node status | grep "Last committed block:" | awk '{ print $4 }')
$ VALID_ROUNDS=1000
$ LAST_VALID=$(($FIRST_VALID+$VALID_ROUNDS))
$ FEE=1000

# Create the initial signed transaction and write it out to a file
$ goal clerk send –-from $ALICE –-to $BOB –-amount $AMOUNT \
–-lease $LEASE_VALUE --firstvalid $FIRST_VALID –-lastvalid $LAST_VALID \
–-fee $FEE –-out $FEE.stxn --sign

上面的代码中,Alice 定义了交易中使用的各项值。$LEASE_VALUE 必须是 base64 编码,且不能超过 32 字节(常以哈希值填充)。$FIRST_VALID 值从网络获取,而 $VALID_ROUNDS(此处设为最大)用于计算 $LAST_VALID$FEE 最初设为最小值,将是后续交易中唯一被修改的值。

Alice 现在通过 goal clerk rawsend –-filename 1000.stxn 广播初始交易,但由于网络拥塞和高昂的费用,goal 将持续等待确认,直至触及 $LAST_VALID。在验证窗口内,Alice 可构造更多几乎相同的交易(只是费用升高),并将这些交易同时广播出去。

# Redefine ONLY the FEE value
$ FEE=$(($FEE+1000))

# Broadcast additional signed transaction
$ goal clerk send –-from $ALICE –-to $BOB –-amount $AMOUNT \
                                    –-lease $LEASE_VALUE --firstvalid $FIRST_VALID –-lastvalid $LAST_VALID \
–-fee $FEE

每个后续交易 Alice 都会持续增加 $FEE 值。在某个时点,其中一笔交易将获批,很可能是当时费用最高的那笔,此时 { $ALICE : $LEASE_VALUE } 对上“锁”,直至触及 $LAST_VALID。Alice 可以确保之前提交的未决交易均不会通过验证。Bob 只收到一次付款。

潜在缺陷

这是个相当简单的场景,正常网络条件下不太可能发生。接下来,我们揭示 Alice 需要防范的几个安全隐患。一旦广播了初始交易,Alice 就必须确保所有后续交易采用完全相同的 FirstValidLastValidLease 值。注意第二笔交易中仅 Fee 递增了,确保其他值保持不变。如果 Alice 执行初始代码块两次,$FIRST_VALID 值会随即时查询网络而更新,因而延长评估 $LEASE_VALUE 的验证窗口。

与之类似,如果 $LEASE_VALUE 在静态验证窗口内被改变,可能会有多笔交易得到确认。谨记,“锁”是 { Sender : Lease } 的互斥;改变其中任何一个都会创建新锁。

验证窗口过期后,Alice 就可以在任何新交易中自由重用 $LEASE_VALUE 了。这是定期付款的常见做法。

智能合约设计

下面列出几个利用 Lease 字段的智能合约用例:代理密钥注册、定期付款和动态费用。在智能合约中纳入 Lease 评估主要出于两个设计考虑:费用最小化和定期交易。这两个概念可以在一个智能合约中结合起来,但我们留给读者去实现,并在开发者论坛上分享实现结果。

代理密钥注册

代理密钥注册演示阐释了利用 LogicSig 或“代理”脚本进行定期密钥轮换的模板的功能和用法。参数部分解释模板预期的值。这种情形中重要的是分别用于评估 LeaseFirstValidLastValidTMPL_LEASE``、``TMPL_PERIODTMPL_DURTMPL_LEASE([32] 字节)必须是 base64 编码,且在使用此 LogicSig 的所有交易中保持不变。TMPL_PERIOD 定义新验证窗口开放前需通过的轮数。TMPL_DUR 定义验证窗口保持开放的时长(至多 1000 轮)。此用例中 Lease 有益的原因在于,可以防止在一个验证窗口中批准多笔交易,避免额外的费用支出抽空账户。

定期付款

定期付款教程使用 PyTeal,为读者演示如何创建定期付款代理签名智能合约 (LogicSig)。这在模板字段上与代理密钥注册非常类似,但验证交易类型是付款而非密钥注册。tmpl_amt 指定所有交易的固定数额,所以 Lease 字段就用于确保每个验证窗口内仅一笔交易可能获批。这种智能合约使用固定费用,如果网络在任意验证窗口内要求更高的费用,接收方就容易遭到拒绝服务。下一个例子解决这一问题。

动态费用

动态费用教程使用 PyTeal,基于此模板演示。此用例旨在创建原子交易组,以使发送方能够得到与发送资金相关的所有费用补偿。因为未来费用定价的不确定性,这一点对发送方很有利。此 LogicSig 使用 Lease 抵御重放攻击,与上述代理密钥注册类似。

总结

Lease 字段可有力保障账户免遭重放攻击,令智能合约适用于执行经常性的定期交易。我们有很多资源可供设计安全智能合约时参考,也有很多 SDK 可以辅助实现。