本文介绍了使用Zama的fhEVM构建链上保密单价拍卖的方案,用于代币销售。文章重点介绍了Palra提交的获胜方案,该方案在保密性和可扩展性之间取得了平衡,通过使用Fenwick树数据结构和部分隐藏(加密数量,价格可见)来实现。该方案使用Zama的fhEVM的编程隐私功能,允许参与者提交保密的竞标,从而实现更公平的代币销售。
Zama 的 fhEVM 的可编程隐私功能对于金融和代币应用尤其有用,例如代币销售拍卖,其中参与者的决策受到其他人的影响。保密拍卖是 Zama 赏金计划第 7 季的重点。
本季的赏金挑战开发者构建一个 Solidity 智能合约系统,用于启动单一价格拍卖 (SPA),采用密封投标的方式出售一定数量的可替代资产。
我们收到了许多高质量的提交,选择获胜者并非易事。但经过仔细评估,有三个项目脱颖而出:
这篇文章重点介绍了社区成员 Palra 提交的获奖作品,该作品在保密性和可扩展性之间找到了平衡。
该赏金挑战开发者构建一个单一价格拍卖,采用密封投标出售一定数量的可替代资产。参与者提交保密投标,指定他们想要购买的代币数量和他们愿意支付的价格。
结算价格由出售的最后一个代币决定——它是中标中的最低价格。中标者是在可用代币限制内出价最高的人,所有中标均以相同的价格结算。
例如,假设有 10 个资产待售,有 4 个竞标者:
结算价格为 42 USDC。竞标者 A 获得 4 个代币,而竞标者 C 获得 6 个代币(在要求的 8 个代币中)。两者均支付每个代币 42 USDC。拍卖组织者收取 420 USDC (10 * 42)。
如果拍卖没有售罄,开发者必须定义一个解决方案,例如退款或以最低价格执行。除了处理边缘情况外,主要的技术挑战是计算结算价格。在 Solidity 中使用 TFHE 对保密投标进行排序和排序在计算上是昂贵的,并且受到 gas 限制(即,区块大小限制)的约束。
大多数提交都优先考虑保密性,但这通常以大型拍卖中的可扩展性为代价。Palra 提交的获奖作品在这两者之间取得了有趣的平衡。
获奖作品引入了两个关键的设计选择:(1)使用专用数据结构存储投标,以及(2)选择部分隐藏 - 加密数量,同时保持价格可见。
该提交使用了 Fenwick 树,这是一种非常适合 EVM 的数据结构。它支持三个关键操作——询问、更新和搜索——每个操作的复杂度均为 O(log n),其中 n 是竞标者的数量。这使得它在拍卖中非常有效,同时避免了代价高昂的初始化。该实现利用了其中两个操作:
function update(Storage storage _this, uint16 atKey, euint128 quantity) internal {
if (atKey == 0) revert FT_InvalidPriceRange();
_this.largestIndex = TFHE.select(
TFHE.eq(quantity, 0),
_this.largestIndex,
TFHE.select(TFHE.gt(atKey, _this.largestIndex), TFHE.asEuint16(atKey), _this.largestIndex)
);
TFHE.allowThis(_this.largestIndex);
uint16 index = atKey;
while (index >= atKey) {
_this.tree[index] = TFHE.add(_this.tree[index], quantity);
TFHE.allowThis(_this.tree[index]);
unchecked {
index += lsb(index);
}
}
_this.tree[0] = TFHE.add(_this.tree[0], quantity);
TFHE.allowThis(_this.tree[0]);
}
要更深入地了解此功能的工作原理,请查看我们的文档。
在计算结算价格时,如果在树中未找到拍卖的总代币,系统将默认为注册的最低价格。每个投标的添加还会单独跟踪 Fenwick 树中的最低投标价。
获奖作品引入了一个自定义 Solidity 库,HalfEncryptedFenwickTree。这种数据结构通过避免在结算拍卖时进行完全排序来提高可扩展性。值得注意的是,该库用途广泛,可以重新用于其他用例。
该提交还包含来自 Zama 的 fhevm-contracts 存储库的关键构建块,包括:
拍卖创建和初始化
首先,拍卖所有者部署一个具有以下参数的合约:
constructor(
address _auctioneer,
IConfidentialERC20 _auctionToken,
uint64 _auctionTokenSupply,
IConfidentialERC20 _baseToken,
uint256 _auctionEnd,
uint64 _minPrice,
uint64 _maxPrice
) Ownable(_auctioneer) EncryptedErrors(uint8(type(ErrorCodes).max))
创建拍卖后,所有者(组织者)必须存入拍卖的代币。这会触发对网关的请求,网关会验证存入的金额是否足以开始拍卖。
主动竞标阶段。
用户通过指定价格(以明文形式)和数量(加密形式)来提交投标。由于价格保持公开,因此确保拍卖保密性需要提交多个投标,包括0 值投标——加密数量为 0 的投标。
function bid(
uint64 _price,
einput _encryptedQuantity,
bytes calldata _inputProof
) external onlyState(AuctionState.Active) nonReentrant {
例如,竞标者 A 可能会以 10 USDC、50 USDC、100 USDC 和 1000 USDC 提交投标,所有这些都具有加密的数量。只有 100 USDC 的投标才会有非零数量。观察者会看到可能的价格,但不知道哪个投标是真实的。
该合约还包括错误处理,并在记录加密金额之前验证投标数量是否已正确转移。
拍卖结算。
拍卖结算阶段确定结算价格。
虽然将值插入 HalfEncryptedFenwickTree 不需要解密,但结算是一个迭代过程,涉及索引加密数量。在树上执行二进制搜索,根据加密值向左或向右分支。
结算依赖于异步解密方案,该方案的工作方式如下:
function stepWithdrawalDecryption()
external
onlyState(AuctionState.WithdrawalPending)
checkTimeLockTag(TL_TAG_COMPUTE_SETTLEMENT_STEP)
{
uint256[] memory cts = new uint256[](1);
bytes4 selector;
if (TFHE.isInitialized(_searchIterator.foundIdx)) {
cts[0] = Gateway.toUint256(_searchIterator.foundIdx);
selector = this.callbackWithdrawalDecryptionFinal.selector;
} else {
cts[0] = Gateway.toUint256(_searchIterator.idx);
selector = this.callbackWithdrawalDecryptionStep.selector;
}
Gateway.requestDecryption(cts, selector, 0, block.timestamp + CALLBACK_MAX_DURATION, false);
_startTimeLockForDuration(TL_TAG_COMPUTE_SETTLEMENT_STEP, CALLBACK_MAX_DURATION);
}
认领阶段。
认领过程是拍卖的最后阶段。
虽然未经审计,但此实现提出了一种在 EVM 约束内进行密封拍卖的有前途的方法,利用 Zama 的 fhEVM 实现保密性。你可以在以下位置查看本赛季获奖赏金的完整实现:GitHub – palra/zama-bounty-confidential-auction。如果你希望了解代码的架构设计,请查看用户指南文档。请记住,智能合约存在风险,此提交是外部贡献者的概念证明。
- 原文链接: zama.ai/post/onchain-con...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!