本文描述了MoveCTF “Lets move” 挑战和提交加密证明求解的过程,为区块链密码学验证提供实践经验,助参与者深刻理解哈希函数与验证逻辑,掌握 BCS 序列化和随机数生成在智能合约交互中的运用,提升区块链开发能力。
0x097a3833b6b5c62ca6ad10f0509dffdadff7ce31e1d86e63e884a14860cedc0f
0x19e76ca504c5a5fa5e214a45fca6c058171ba333f6da897b82731094504d5ab9
0x8
题目源码: Move CTF Lets Move
EPROOF const EPROOF: u64 = 0;
常量表示证明检查失败时的错误代码。
Flag
结构体
// flag结构体
public struct Flag has copy, drop {
sender: address, // 提交证明的地址
flag: bool, // 布尔值,说明挑战是否成功
ture_num: u64, // 用于跟踪挑战成功的次数
github_id: String // 表示提交证明的GitHub Id
}
Challenge
结构体
// Challenge 结构体
public struct Challenge has key {
id: UID, // 挑战的唯一标识符 (`UID`)
str: String, // 一个字符串,可能表示挑战的描述或名称
difficulity: u64, // 挑战难度的级别
ture_num: u64 // 跟踪该挑战完成的次数
}
// 初始化一个新的 `Challenge` 对象
// 使用 `object::new(ctx)` 创建唯一 ID,字符串为 `"LetsMoveCTF"`,难度为 `3`。
fun init(ctx: &mut TxContext) {
let flag_str = Challenge {
id: object::new(ctx),
str: string(b"LetsMoveCTF"),
difficulity: 3,
ture_num: 0,
};
share_object(flag_str); // 共享这个对象
}
// get_flag 方法
entry fun get_flag(
proof: vector<u8>, // `u8` 类型的向量,表示某种加密证明(可能是挑战的解决方案)。
github_id: String, // 提交证明的用户的 GitHub ID 字符串
challenge: &mut Challenge, // 指向一个 `Challenge` 对象
rand: &Random, // 随机数
ctx: &mut TxContext // 事务上下文,用于与 Sui 区块链进行交互。
) {
// 将 `proof`、发送者的地址以及挑战对象合并成一个 `full_proof` 向量。
let mut full_proof: vector<u8> = vector::empty<u8>();
vector::append<u8>(&mut full_proof, proof);
vector::append<u8>(&mut full_proof, tx_context::sender(ctx).to_bytes());
vector::append<u8>(&mut full_proof, bcs::to_bytes(challenge));
// 对 `full_proof` 进行 SHA3-256 哈希运算。
let hash: vector<u8> = hash::sha3_256(full_proof);
// 循环遍历哈希值,计算 `prefix_sum`
let mut prefix_sum: u32 = 0;
let mut i: u64 = 0;
while (i < challenge.difficulity) {
prefix_sum = prefix_sum + (*vector::borrow(&hash, i) as u32);
i = i + 1;
};
// 目标是检查哈希的前 `difficulity` 字节的总和是否为 `0`
assert!(prefix_sum == 0, EPROOF);
// 检查通过,生成一个新的随机字符串,并更新挑战的状态。
challenge.str = getRandomString(rand, ctx);
challenge.ture_num = challenge.ture_num + 1;
// 通过 `event::emit` 发出事件,记录挑战完成的详细信息
event::emit(Flag {
sender: tx_context::sender(ctx),
flag: true,
ture_num: challenge.ture_num,
github_id
});
}
这个MoveCTF挑战的主要步骤
① 将 proof
、发送者的地址以及挑战对象合并成一个 full_proof
向量。
② 对 full_proof
进行 SHA3-256 哈希运算。
③ 循环遍历哈希值,计算 prefix_sum
④ 目标是检查哈希的前 difficulity
字节的总和是否为 0
, 失败抛异常错误码
⑤ 检查通过,生成一个新的随机字符串,并更新挑战的状态。
⑥ 最后,通过 event::emit
发出事件,记录挑战完成的详细信息
使用随机生成器生成一个长度在4到 30之间的随机字符串。
// 使用随机生成器生成一个长度在 `4` 到 `30` 之间的随机字符串。
fun getRandomString(rand: &Random, ctx: &mut TxContext): String {
let mut gen = random::new_generator(rand, ctx);
let mut str_len = random::generate_u8_in_range(&mut gen, 4, 30);
let mut rand: vector<u8> = b"";
while (str_len != 0) {
let rand_num = random::generate_u8_in_range(&mut gen, 34, 126);
vector::push_back(&mut rand, rand_num);
str_len = str_len - 1;
};
string(rand)
}
}
通过阅读源码,这个挑战是需要我们寻找寻找一个符合特定条件的“proof”(证明)。具体来说,它会生成一个 8 字节的随机 proof,并通过 SHA3-256 哈希算法计算出一个哈希值,然后检查哈希的前 difficulty
个字节的和是否为零。
let mut full_proof: vector<u8> = vector::empty<u8>();
vector::append<u8>(&mut full_proof, proof);
vector::append<u8>(&mut full_proof, tx_context::sender(ctx).to_bytes());
vector::append<u8>(&mut full_proof, bcs::to_bytes(challenge));
代码中的 sender是明确的,我们需要找到bcs::to_bytes(challenge)的值和proof证明。 那么如何获取挑战对象哈希序列化对象结构体的值?然后通过逆向分析计算出对应的 proof证明。
Challenge
对象的序列化数组因为Challenge对象在测试网上,我们可以通过一个合约的函数,来获取challenge_bytes
module get_challenge::get_challenge {
use lets_move::lets_move::Challenge;
use sui::bcs;
use sui::event;
public struct ChallengeByte has copy, drop {
challenge_bytes: vector<u8>
}
public entry fun get_challenge(challenge: &Challenge) {
let challenge_bytes = bcs::to_bytes(challenge);
event::emit(ChallengeByte { challenge_bytes });
}
}
sui client publish --gas-budget 100000000 --skip-fetch-latest-git-deps --skip-dependency-verification
PS D:\data\web3\letsmove-ctf\src\02_lets_move\get_challenge> sui client publish --gas-budget 100000000 --skip-fetch-latest-git-deps --skip-dependency-verification
[warn] Client/Server api version mismatch, client api version : 1.37.1, server api version : 1.38.2
INCLUDING DEPENDENCY lets_move
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING get_challenge
Skipping dependency verification
Transaction Digest: CgRJQB6Arw68728a3YMmFBX7JuEJPq7KdyqhRfxMBmvA
[25, 231, 108, 165, 4, 197, 165, 250, 94, 33, 74, 69, 252, 166, 192, 88, 23, 27, 163, 51, 246, 218, 137, 123, 130, 115, 16, 148, 80, 77, 90, 185, 5, 99, 89, 97, 92, 126, 3, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0]
import hashlib
import random
def sha3_256(data: bytes) -> bytes:
"""计算 SHA3-256 哈希"""
return hashlib.sha3_256(data).digest()
def find_proof(sender: bytes, challenge: bytes, difficulty: int = 3) -> list:
"""寻找满足条件的 proof 并返回字节数组"""
while True:
# 随机生成 8 字节的 proof
proof = random.randbytes(8)
# 构建 full_proof = proof + sender + challenge
full_proof = proof + sender + challenge
# 计算 SHA3-256 哈希
hash_result = sha3_256(full_proof)
# 检查前 difficulty 个字节的和是否为 0
if sum(hash_result[:difficulty]) == 0:
print(f"Proof found (byte array): {[b for b in proof]}")
return [b for b in proof]
# 将 sender 转换为字节数组(16 进制字符串转为 bytes)
sender_hex = "1546f533333b358a8edddd38a8e8967583883e82ca7de604d5bba15e20e493d2"
sender_bytes = bytes.fromhex(sender_hex)
# 使用提供的 challenge_bytes
challenge_bytes = bytes(
[25, 231, 108, 165, 4, 197, 165, 250, 94, 33, 74, 69, 252, 166, 192, 88, 23, 27, 163, 51, 246, 218, 137, 123, 130,
115, 16, 148, 80, 77, 90, 185, 5, 99, 89, 97, 92, 126, 3, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0])
# 计算 proof 并输出为字节数组
proof = find_proof(sender_bytes, challenge_bytes)
print(f"Calculated proof as byte array: {proof}")
核心逻辑,find_proof
函数
<!--StartFragment-->
该函数尝试通过不断生成随机的 8 字节 proof
,并与 sender
和 challenge
一起构建 full_proof
,然后计算其 SHA3-256 哈希值。
计算哈希后,它检查哈希结果前 difficulty
个字节的和是否为 0。如果条件满足(即 sum(hash_result[:difficulty]) == 0
),表示找到了符合条件的 proof,函数将其返回。
random.randbytes(8)
用于生成一个随机的 8 字节 proof。
<!--EndFragment-->
<!--StartFragment-->
用Cli命令调用get_flag函数,验证 proof证明是否正确。
PS D:\data\web3\letsmove> sui client call --package 0x097a3833b6b5c62ca6ad10f0509dffdadff7ce31e1d86e63e884a14860cedc0f --module lets_move --function get_flag --args '[56, 171, 212, 27, 54, 49, 33, 217]' LeonDev1024 0x19e76ca504c5a5fa5e214a45fca6c058171ba333f6da897b82731094504d5ab9 0x8 --gas-budget 100000000
[warn] Client/Server api version mismatch, client api version : 1.37.1, server api version : 1.38.2
Transaction Digest: 2y7NhUaRJ3mJDenJSJPBTQWVfFkawsTBo6AdHLGPWAf4
学习收获:
解题关键:
适用场景:
本次挑战为区块链开发中的密码学验证提供了实践经验,可应用于密码学协议验证、区块链挑战任务开发等领域。
<!--EndFragment-->
本次Move共学系列笔记
Move共学-TASK6用Sui SDK和Navi SDK完成一个自定义的PTB模块
Move共学-TASK7完成 MoveCTF Check in挑战
关注《HOH水分子》公众号,我们将持续分享和制作变成语言教程,让大家对编程产生化学反应。
<!--StartFragment-->
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!