英文:
BLUR airdrop claim explained - embedding a proof into a transaction
问题
最近,我看到Blur有一个依赖于Twitter的空投活动。基本上,你需要发推文,然后就可以选择领取代币。我去分析了这段代码,但对我来说有点复杂。
这是领取函数:
function claim(
address account,
uint256 amount,
bytes32[] memory proof
) external {
bytes32 leaf = keccak256(abi.encodePacked(account, amount));
require(!claimed[leaf], "Airdrop already claimed");
MerkleVerifier._verifyProof(leaf, merkleRoot, proof);
claimed[leaf] = true;
require(IERC20(token).transfer(account, amount), "Transfer failed");
emit Claimed(account, amount);
}
这是Merkle验证器:
pragma solidity 0.8.17;
/**
* @title MerkleVerifier
* @dev 用于Merkle树计算的实用函数
*/
library MerkleVerifier {
error InvalidProof();
/**
* @dev 验证Merkle证明
* @param leaf 叶子节点
* @param root 根节点
* @param proof 证明
*/
function _verifyProof(
bytes32 leaf,
bytes32 root,
bytes32[] memory proof
) public pure {
bytes32 computedRoot = _computeRoot(leaf, proof);
if (computedRoot != root) {
revert InvalidProof();
}
}
/**
* @dev 计算Merkle根节点
* @param leaf 叶子节点
* @param proof 证明
*/
function _computeRoot(
bytes32 leaf,
bytes32[] memory proof
) public pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
computedHash = _hashPair(computedHash, proofElement);
}
return computedHash;
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(
bytes32 a,
bytes32 b
) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
我的理论是,每次用户发推文时,网站会识别这一行为,并嵌入一个特殊的证明(proof)到交易中,然后将该证明与金额和地址一起计算,最后与根证明(root proof)进行验证(唯一的owner函数)。我不明白的是,为什么这个系统无法被欺骗?我们不能只是复制其中一个证明到交易中,从而避免Twitter验证吗?
最重要的是:这个证明是如何创建的?合同如何跟踪Twitter(据我所知,没有Oracle)?
英文:
Recently, I saw that Blur had a twitter dependent airdrop. So basically you had to tweet and then you would get the option to claim tokens. I went to analyze the code, but it is overwhelming for me.
This is the claim function:
function claim(
address account,
uint256 amount,
bytes32[] memory proof
) external {
bytes32 leaf = keccak256(abi.encodePacked(account, amount));
require(!claimed[leaf], "Airdrop already claimed");
MerkleVerifier._verifyProof(leaf, merkleRoot, proof);
claimed[leaf] = true;
require(IERC20(token).transfer(account, amount), "Transfer failed");
emit Claimed(account, amount);
}
This is the Merklee Verifier:
pragma solidity 0.8.17;
/**
* @title MerkleVerifier
* @dev Utility functions for Merkle tree computations
*/
library MerkleVerifier {
error InvalidProof();
/**
* @dev Verify the merkle proof
* @param leaf leaf
* @param root root
* @param proof proof
*/
function _verifyProof(
bytes32 leaf,
bytes32 root,
bytes32[] memory proof
) public pure {
bytes32 computedRoot = _computeRoot(leaf, proof);
if (computedRoot != root) {
revert InvalidProof();
}
}
/**
* @dev Compute the merkle root
* @param leaf leaf
* @param proof proof
*/
function _computeRoot(
bytes32 leaf,
bytes32[] memory proof
) public pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
computedHash = _hashPair(computedHash, proofElement);
}
return computedHash;
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(
bytes32 a,
bytes32 b
) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
My theory is that each time a user tweets, the website recognizes this and embeds a special proof in the transaction, which is then computed with the amount and address and finally verified with the root proof(the only owner function). What I don't understand is why can't this system be cheated? Can't we just copy one of the proofs into the transaction and avoid the twitter verfication?
And most importantly: How is the proof created? How can the contract follow twitter(there is no oracle to my knowledge)?
答案1
得分: 3
为什么这个系统不能被欺骗?难道我们不能只是复制一个证明到交易中,避免 Twitter 验证吗?
在这种特定情况下,如果你传递别人的 proof
和你的 account
,声明将失败。
bytes32 leaf = keccak256(abi.encodePacked(account, amount));
对于每个输入,得到的 leaf
值都是不同的。因此,一个合格的地址会得到叶子值 0x12
,而你的地址会得到叶子值 0x34
。
现在,_verifyProof()
函数验证了 leaf
和 proof
的组合是否导致了预期的 root
。
bytes32 computedHash = leaf;
// ...
computedHash = _hashPair(computedHash, proofElement);
由于你的 leaf
与合格者的不同(尽管 proof
是相同的),这个组合会导致一个不同的 root
值,因此此条件将导致回滚:
if (computedRoot != root) {
revert InvalidProof();
}
如果你传递了他们的 proof
和他们的 account
,那么你只是将空投声明到他们的地址 - 而不是你的地址:
// 将代币发送到 `account` - 而不是 `msg.sender`
IERC20(token).transfer(account, amount)
英文:
> why can't this system be cheated? Can't we just copy one of the proofs into the transaction and avoid the twitter verfication?
In this specific case, if you pass someone else's proof
and your account
, the claim fails.
bytes32 leaf = keccak256(abi.encodePacked(account, amount));
The resulting leaf
value is different for each input. So an eligible address would result in leaf 0x12
while your address would result in leaf value 0x34
.
Now, the _verifyProof()
function validates if the combination of leaf
and proof
leads to the expected root
.
bytes32 computedHash = leaf;
// ...
computedHash = _hashPair(computedHash, proofElement);
Since your leaf
is different from the eligible (while the proof
is the same), the combination leads to a different root
value, and this condition reverts:
if (computedRoot != root) {
revert InvalidProof();
}
And if you pass their proof
and their account
as well, then you simply claim the airdrop to their address - not to yours:
// sends tokens to `account` - not to `msg.sender`
IERC20(token).transfer(account, amount)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论