文章目录
- 前言
- 一、什么是盲拍合约?
- 二、盲拍合约的优势
- 1.时间压力的缓解
- 2.绑定与秘密的挑战
- 三、盲拍合约的工作原理
- 1.提交盲出价
- 2.披露出价
- 3.结束拍卖
- 4.退款机制
- 四、代码示例
- 总结
前言
随着区块链技术的发展,智能合约在各种场景中的应用越来越广泛。盲拍合约作为一种新兴的智能合约形式,利用密码学原理为参与者提供了隐私保护和安全保障。这种合约不仅增强了竞拍的公平性,还消除了时间压力,让参与者能够在更为放松的环境中进行投标。本文将深入探讨盲拍合约的定义、优势、工作原理以及代码实现,旨在为读者提供一个全面的理解。
一、什么是盲拍合约?
盲拍合约是一种智能合约,允许参与者在不公开其出价的情况下进行竞拍。参与者提交一个“盲出价”,其中包含出价金额、一个虚假的标记以及一个秘密值。只有在竞拍结束时,参与者才能披露这些信息,从而验证他们的出价。
二、盲拍合约的优势
盲拍合约的好处在于,参与者在投标结束前不会感受到时间压力。在透明的计算平台上进行秘密竞拍听起来似乎矛盾,但密码学的应用使这一切成为可能。
1.时间压力的缓解
在投标期间,投标人实际上并没有发送真实出价,而只是发送出价的哈希版本。由于几乎不可能找到两个(足够长的)值,其哈希值相等,投标人可以通过这种方式提交出价。投标结束后,投标人必须公开他们的出价,合约会检查披露的出价是否与之前提交的哈希值相同。
2.绑定与秘密的挑战
另一个挑战是如何使拍卖同时做到绑定与秘密。唯一能阻止投标者在赢得拍卖后不付款的方式是让她将钱连同出价一起发送。但由于以太坊中资金转移不可隐藏,任何人都可以看到转移的资金。
合约通过接受任何大于当前最高出价的值来解决这个问题。虽然在披露阶段才进行检查,有些出价可能是无效的,但这也是故意的。投标人可以通过设置几个高或低的无效出价来迷惑竞争对手。
三、盲拍合约的工作原理
1.提交盲出价
参与者通过 bid 函数提交盲出价,计算方式为:
在这里,value
是实际出价金额,fake
是一个布尔值,用于隐藏真实出价,secret
是一个32字节的秘密字符串,用于防止加密前过于简单而导致容易暴力破解的情况。此计算使得盲出价在未披露前无法被识别
2.披露出价
在竞拍结束后,参与者使用 reveal
函数披露出价。只有正确披露的出价会被验证:
如果出价有效且未标记为假,合约会将其视为有效出价并处理
3.结束拍卖
在竞拍结束后,auctionEnd
函数将确定最高出价并将其转移给部署合约时设置的三个参数受益人:
4.退款机制
对于无效出价或低于最高出价的出价,合约会将存入的保证金退还给参与者。这通过 pendingReturns
映射来实现
四、代码示例
以下是完整的盲拍合约代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address payable public beneficiary;
uint public biddingEnd;
uint public revealEnd;
bool public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
// 可以取回的之前的出价
mapping(address => uint) public pendingReturns;
event AuctionEnded(address winner, uint highestBid);
// 定义错误
error TooEarly(uint currentTime, uint endTime);
error TooLate(uint currentTime, uint endTime);
error AuctionAlreadyEnded();
/// 使用 modifier 可以更便捷的校验函数的入参。
/// `onlyBefore` 会被用于后面的 `bid` 函数:
/// 新的函数体是由 modifier 本身的函数体,并用原函数体替换 `_;` 语句来组成的。
// modifier onlyBefore(uint _time) { require(block.timestamp < _time); _; }
// modifier onlyAfter(uint _time) { require(block.timestamp > _time); _; }
modifier onlyBefore(uint _time) {
if (block.timestamp >= _time) revert TooLate(block.timestamp, _time);
_;
}
modifier onlyAfter(uint _time) {
if (block.timestamp <= _time) revert TooEarly(block.timestamp, _time);
_;
}
constructor(
uint _biddingTime,
uint _revealTime,
address payable _beneficiary
) {
beneficiary = _beneficiary;
biddingEnd = block.timestamp + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}
/// 可以通过 `_blindedBid` = keccak256(value, fake, secret)
/// 设置一个秘密竞拍。
/// 只有在出价披露阶段被正确披露,已发送的以太币才会被退还。
/// 如果与出价一起发送的以太币至少为 “value” 且 “fake” 不为真,则出价有效。
/// 将 “fake” 设置为 true ,然后发送满足订金金额但又不与出价相同的金额是隐藏实际出价的方法。
/// 同一个地址可以放置多个出价。
// function bid(bytes32 _blindedBid)
// external
// payable
// onlyBefore(biddingEnd)
// {
// bids[msg.sender].push(Bid({
// blindedBid: _blindedBid,
// deposit: msg.value
// }));
// }
function bid(uint value, bool fake, bytes32 secret)
external
payable
onlyBefore(biddingEnd)
{
// 计算 blindedBid 内部使用,仅供存储或其他用途
bytes32 blindedBid = keccak256(abi.encodePacked(value, fake, secret));
bids[msg.sender].push(Bid({
blindedBid: blindedBid,
deposit: msg.value
}));
}
/// 披露你的秘密竞拍出价。
/// 对于所有正确披露的无效出价以及除最高出价以外的所有出价,你都将获得退款。
function reveal(
uint[] memory _values,
bool[] memory _fake,
bytes32[] memory _secret
)
external
payable
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(_values.length == length, "Mismatched values length");
require(_fake.length == length, "Mismatched fake flags length");
require(_secret.length == length, "Mismatched secrets length");
uint refund;
for (uint i = 0; i < length; i++) {
Bid storage bidInfo = bids[msg.sender][i];
(uint value, bool fake, bytes32 secret) =
(_values[i], _fake[i], _secret[i]);
if (bidInfo.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
// 出价未能正确披露
// 不返还订金
continue;
}
refund += bidInfo.deposit;
if (!fake && bidInfo.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// 使发送者不可能再次认领同一笔订金
bidInfo.blindedBid = bytes32(0);
}
// Cast msg.sender to address payable
address payable sender = payable(msg.sender);
sender.transfer(refund);
}
// 这是一个 "internal" 函数, 意味着它只能在本合约(或继承合约)内被调用
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// 返还之前的最高出价
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
/// 取回出价(当该出价已被超越)
function withdraw() public payable {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// 这里很重要,首先要设零值。
// 因为,作为接收调用的一部分,
// 接收者可以在 `transfer` 返回之前重新调用该函数。(可查看上面关于‘条件 -> 影响 -> 交互’的标注)
pendingReturns[msg.sender] = 0;
// Cast msg.sender to address payable
address payable sender = payable(msg.sender);
sender.transfer(amount);
}
}
/// 结束拍卖,并把最高的出价发送给受益人
function auctionEnd()
public
payable
onlyAfter(revealEnd)
{
// require(!ended);
if (ended) revert AuctionAlreadyEnded();
emit AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
}
总结
通过本文,我们详细介绍了盲拍合约的定义、优势、工作原理及其代码实现。盲拍合约利用密码学原理为参与者提供隐私保护,减轻时间压力,并确保出价的绑定与秘密。我们讲解了参与者如何提交盲出价、披露出价、结束拍卖及退款机制。希望这篇文章能帮助你深入理解盲拍合约及其在区块链中的应用。如果你有任何疑问或建议,欢迎在评论区留言讨论🌹