Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOT-179: Verify proof using SP1 contracts #7

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
[submodule "crates/cli/contracts/lib/openzeppelin-contracts"]
path = crates/cli/contracts/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "crates/cli/contracts/lib/sp1-contracts"]
path = crates/cli/contracts/lib/sp1-contracts
url = https://github.com/succinctlabs/sp1-contracts
11 changes: 4 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
[workspace]
resolver = "2"
members = ["crates/cli", "crates/prover-sdk"]
members = ["crates/cli", "crates/prover-sdk", "crates/ecies"]
exclude = ["crates/sp1-prover"]

[workspace.dependencies]
aligned-sdk = { git = "https://github.com/yetanotherco/aligned_layer", tag = "v0.10.2" }
aligned-sp1-prover = { path = "crates/sp1-prover" }
anyhow = "1.0.90"
auction-sp1-prover = { path = "crates/sp1-prover" }
bincode = "1.3.3"
bytes = "1.7.2"
chrono = "0.4.38"
Expand All @@ -15,10 +14,8 @@ config = "0.14.0"
curl = "0.4.46"
dialoguer = "0.11.0"
dotenv = "0.15.0"
ecies = { version = "0.2.6", default-features = false, features = [
"pure",
"std",
] }
ecies = { path = "crates/ecies" }
eth-keystore = "0.5.0"
ethers = { tag = "v2.0.15-fix-reconnections", features = [
"ws",
"rustls",
Expand Down
11 changes: 3 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,11 @@ update-abi:
taplo-fmt:
taplo format --config taplo/taplo.toml

test-submit-proof:
cd crates/sp1-prover && make elf-commit
RUST_BACKTRACE=1 cargo test --release --color=always --lib tests::test_submit_proof \
test-find-winner:
cd crates/sp1-prover && make gen-key && make elf-vk
RUST_BACKTRACE=1 cargo test --release --color=always --lib tests::test_find_winner \
--no-fail-fast --manifest-path crates/prover-sdk/Cargo.toml -- --exact -Z unstable-options --show-output --nocapture

test-prove:
cd crates/sp1-prover && make gen-key && make elf-commit
cargo test --release --color=always --lib tests::test_sp1_prover \
--no-fail-fast --manifest-path crates/prover-sdk/Cargo.toml -- --exact -Z unstable-options --show-output

test-mint:
cargo test --color=always --lib tests::test_auction::test::test_mint \
--no-fail-fast --manifest-path crates/cli/Cargo.toml -- --exact -Z unstable-options --show-output --nocapture
Expand Down
4 changes: 1 addition & 3 deletions config.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
contract_address = "0x666fdd363f32e3b0efac35739aa9e2ee52a4b50e"
contract_address = "0x00b4d7aebd75f698cce5d3070efb3f0ac6c3dc3a"
[chain]
rpc_url = "https://ethereum-holesky-rpc.publicnode.com"
network = "holesky"
aligned_batcher_url = "wss://batcher.alignedlayer.com"
4 changes: 2 additions & 2 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ version = "0.1.0"
edition = "2021"

[dependencies]
aligned-sdk = { workspace = true }
aligned-sp1-prover = { workspace = true }
anyhow = { workspace = true }
auction-sp1-prover = { workspace = true }
bincode = { workspace = true }
bytes = { workspace = true }
chrono = { workspace = true }
Expand All @@ -15,6 +14,7 @@ config = { workspace = true }
curl = { workspace = true }
dotenv = { workspace = true }
ecies = { workspace = true }
eth-keystore = { workspace = true }
ethers = { workspace = true }
futures-util = { workspace = true }
glob = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/assets/ZkAuction.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/cli/contracts/lib/forge-std
1 change: 1 addition & 0 deletions crates/cli/contracts/lib/sp1-contracts
Submodule sp1-contracts added at e8a2f7
191 changes: 107 additions & 84 deletions crates/cli/contracts/src/ZkAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {ISP1Verifier} from "lib/sp1-contracts/contracts/src/ISP1Verifier.sol";

contract ZkAuction is IERC721Receiver {
using SafeERC20 for IERC20;

// data for verifying batch inclusion
error InvalidElf(bytes32 submittedElf);
bytes32 public constant ELF_COMMITMENT = 0x1751d5d4ef537625091e623bbfd7cc457b0503daa18d06b59d5d0f13d38fdb5f;
address public constant ALIGNED_SERVICE_MANAGER = 0x58F280BeBE9B34c9939C3C39e0890C81f163B623;
address public constant ALIGNED_PAYMENT_SERVICE_ADDR = 0x815aeCA64a974297942D2Bbf034ABEe22a38A003;
// Data for verifying proof
bytes32 public constant VERIFICATION_KEY = 0x0072d1c573b7b587cf19f5ebb986a1409c35bd63ec98735746a778038dc1ab9d;
ISP1Verifier public constant SP1_VERIFIER = ISP1Verifier(0x3B6041173B80E77f038f3F2C0f9744f04837185e);

struct Auction {
address owner; // Owner of the auction
Expand Down Expand Up @@ -55,16 +54,23 @@ contract ZkAuction is IERC721Receiver {

// Events
event AuctionCreated(uint256 indexed auctionId, address indexed owner);
event NewBid(uint256 indexed auctionId, address indexed bidder, bytes encryptedPrice);
event AuctionEnded(uint256 indexed auctionId, address indexed winner, uint128 price);
event NewBid(
uint256 indexed auctionId,
address indexed bidder,
bytes encryptedPrice
);
event AuctionEnded(
uint256 indexed auctionId,
address indexed winner,
uint128 price
);

modifier onlyOwner(uint256 auctionId) {
Auction storage auction = auctions[auctionId];
require(msg.sender == auction.owner, "You are not the owner");
_;
}


// Function to create a new auction
/**
* @notice Creates a new auction with specific parameters.
Expand All @@ -84,16 +90,27 @@ contract ZkAuction is IERC721Receiver {
require(_duration > 0, "Duration must be greater than zero");

IERC721 nftContract = IERC721(_nftContract);
require(nftContract.ownerOf(_tokenId) == msg.sender, "You must own the NFT to auction it");
require(nftContract.getApproved(_tokenId) == address(this), "You need approve the NFT to contract");
require(
nftContract.ownerOf(_tokenId) == msg.sender,
"You must own the NFT to auction it"
);
require(
nftContract.getApproved(_tokenId) == address(this),
"You need approve the NFT to contract"
);

// Create auction
auctionCount++;
Auction storage newAuction = auctions[auctionCount];

newAuction.owner = msg.sender;
newAuction.encryptionKey = _encryptionKey;
newAuction.asset = Asset(_assetName, _assetDescription, _nftContract, _tokenId);
newAuction.asset = Asset(
_assetName,
_assetDescription,
_nftContract,
_tokenId
);
newAuction.depositPrice = _depositPrice;
newAuction.endTime = block.timestamp + _duration; // Set auction end time
newAuction.ended = false;
Expand Down Expand Up @@ -123,9 +140,15 @@ contract ZkAuction is IERC721Receiver {
// Update the state to indicate that the user has deposited
hasDeposited[auctionId][msg.sender] = true;
// Bid
auction.bids.push(Bid({bidder: msg.sender, encryptedPrice: _encryptedPrice}));
auction.bids.push(
Bid({bidder: msg.sender, encryptedPrice: _encryptedPrice})
);

auction.token.safeTransferFrom(msg.sender, address(this), auction.depositPrice);
auction.token.safeTransferFrom(
msg.sender,
address(this),
auction.depositPrice
);
emit NewBid(auctionId, msg.sender, _encryptedPrice);
}

Expand All @@ -134,7 +157,10 @@ contract ZkAuction is IERC721Receiver {
* @dev Uses auctionId to get list bidders.
*/
function getBids(uint256 auctionId) public view returns (Bid[] memory) {
require(block.timestamp >= auctions[auctionId].endTime, "Auction has not ended yet");
require(
block.timestamp >= auctions[auctionId].endTime,
"Auction has not ended yet"
);
require(!auctions[auctionId].ended, "Auction has ended");
return auctions[auctionId].bids;
}
Expand All @@ -143,27 +169,49 @@ contract ZkAuction is IERC721Receiver {
* @notice Reveals the winner after the auction ends.
* @dev Uses a ZK-proof to reveal the highest valid bid.
*/
function finalizeAuction(uint256 auctionId, Winner memory _winner, bytes memory proof) public onlyOwner(auctionId) {
function finalizeAuction(
uint256 auctionId,
Winner memory _winner,
bytes calldata publicInput,
bytes memory proof
) public onlyOwner(auctionId) {
Auction storage auction = auctions[auctionId];
require(auction.owner == msg.sender, "You need owner of auction");
require(block.timestamp >= auctions[auctionId].endTime, "Auction has not ended yet");
require(
block.timestamp >= auctions[auctionId].endTime,
"Auction has not ended yet"
);
require(!auction.ended, "Auction has ended");
_verifyProof(_winner, auctionId, proof);
require(_winner.price <= auction.depositPrice, "Winner has more bid price than deposit price");
_verifyProof(_winner, auctionId, publicInput, proof);
require(
_winner.price <= auction.depositPrice,
"Winner has more bid price than deposit price"
);
// Set winner and status auction
auction.winner = _winner;
auction.ended = true;
// Send nft
IERC721 nftContract = IERC721(auction.asset.nftContract);
nftContract.safeTransferFrom(address(this), auction.winner.winner, auction.asset.tokenId);
nftContract.safeTransferFrom(
address(this),
auction.winner.winner,
auction.asset.tokenId
);
// Refund token
if (auction.depositPrice > auction.winner.price) {
auction.token.safeTransfer(auction.winner.winner, auction.depositPrice - auction.winner.price);
auction.token.safeTransfer(
auction.winner.winner,
auction.depositPrice - auction.winner.price
);
}
// Withdraw token
auction.token.safeTransfer(msg.sender, auction.winner.price);

emit AuctionEnded(auctionId, auction.winner.winner, auction.winner.price);
emit AuctionEnded(
auctionId,
auction.winner.winner,
auction.winner.price
);
}

function withdraw(uint256 auctionId) public {
Expand All @@ -177,93 +225,68 @@ contract ZkAuction is IERC721Receiver {
function _verifyProof(
Winner memory winner,
uint256 auctionId,
bytes memory verifiedProofData
bytes calldata publicInput,
bytes memory proof
) internal view {
(
bytes memory publicInput,
bytes32 proofCommitment,
bytes32 pubInputCommitment,
bytes32 provingSystemAuxDataCommitment,
bytes20 proofGeneratorAddr,
bytes32 batchMerkleRoot,
bytes memory merkleProof,
uint256 verificationDataBatchIndex
) = abi.decode(verifiedProofData, (bytes, bytes32, bytes32, bytes32, bytes20, bytes32, bytes, uint256));
if (ELF_COMMITMENT != provingSystemAuxDataCommitment) {
revert InvalidElf(provingSystemAuxDataCommitment);
}
require(
address(proofGeneratorAddr) == msg.sender,
"proofGeneratorAddr does not match"
);
require(
pubInputCommitment == keccak256(publicInput),
"Invalid public input"
);
bytes32 auctionHash,
address winnerAddr,
uint128 winnerPrice
) = decodePublicInput(publicInput);

(bytes32 auctionHash, address winner_addr, uint128 winner_price) = decodePublicInput(publicInput);

require(winner_addr == winner.winner, "Winner in proof does not match");
require(winner_price == winner.price, "Winner in proof does not match");
require(calculateAuctionHash(auctionId) == auctionHash, "Auction hash does not match");

(
bool callWasSuccessful,
bytes memory proofIsIncluded
) = ALIGNED_SERVICE_MANAGER.staticcall(
abi.encodeWithSignature(
"verifyBatchInclusion(bytes32,bytes32,bytes32,bytes20,bytes32,bytes,uint256,address)",
proofCommitment,
pubInputCommitment,
provingSystemAuxDataCommitment,
proofGeneratorAddr,
batchMerkleRoot,
merkleProof,
verificationDataBatchIndex,
ALIGNED_PAYMENT_SERVICE_ADDR
)
require(winnerAddr == winner.winner, "Winner address in proof does not match");
require(winnerPrice == winner.price, "Winner price in proof does not match");
require(
calculateAuctionHash(auctionId) == auctionHash,
"Auction hash does not match"
);

require(callWasSuccessful, "static_call failed");

bool proofIsIncludedBool = abi.decode(proofIsIncluded, (bool));
require(proofIsIncludedBool, "proof not included in batch");
}

function calculateAuctionHash(uint256 auctionId) view internal returns (bytes32) {
Bid[] memory bids = auctions[auctionId].bids;
bytes memory hashInput = abi.encodePacked(auctionId);
for (uint256 i = 0; i < bids.length; ++i) {
hashInput = abi.encodePacked(hashInput, bids[i].bidder, bids[i].encryptedPrice);
}
return keccak256(hashInput);
SP1_VERIFIER.verifyProof(VERIFICATION_KEY, publicInput, proof);
}

function decodePublicInput(bytes memory data) internal pure returns (bytes32 auctionHash, address winner_addr, uint128 winner_price) {
function decodePublicInput(bytes memory data) internal pure returns (
bytes32 auctionHash,
address winnerAddr,
uint128 winnerPrice
) {
auctionHash = bytes32(slice(data, 0, 32));
winner_addr = address(bytes20(slice(data, 32 + 8, 20)));
winner_price = uint128(bytes16(reverse(slice(data, 32 + 8 + 20, 16))));
winnerAddr = address(bytes20(slice(data, 32 + 8, 20)));
winnerPrice = uint128(bytes16(reverse(slice(data, 32 + 8 + 20, 16))));
}

function slice(bytes memory data, uint256 start, uint256 length) internal pure returns (bytes memory) {
function slice(
bytes memory data,
uint256 start,
uint256 length
) internal pure returns (bytes memory) {
require(start + length <= data.length, "Slice out of bounds");

bytes memory result = new bytes(length);
for (uint256 i = 0; i < length; i++) {
result[i] = data[start + i];
}

return result;
}

function reverse(bytes memory data) public pure returns (bytes memory) {
function reverse(bytes memory data) internal pure returns (bytes memory) {
bytes memory result = new bytes(data.length);
for (uint256 i = 0; i < data.length; i++) {
result[i] = data[data.length - 1 - i];
}
return result;
}

function calculateAuctionHash(uint256 auctionId) internal view returns (bytes32) {
Bid[] memory bids = auctions[auctionId].bids;
bytes memory hashInput = abi.encodePacked(auctionId);
for (uint256 i = 0; i < bids.length; ++i) {
hashInput = abi.encodePacked(
hashInput,
bids[i].bidder,
bids[i].encryptedPrice
);
}
return keccak256(hashInput);
}

function onERC721Received(
address operator,
address from,
Expand Down
Loading