从密码学看盲拍合约:智能合约的隐私与安全新革命!
// 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);
}
}