Move共学-TASK8完成 MoveCTF Lets Move挑战
需求
- 完成 CLI 调用学习
- 理解合约交互传值
- 完成 Move CTF Lets Move
一、任务指南
- 合约部署地址:
0x097a3833b6b5c62ca6ad10f0509dffdadff7ce31e1d86e63e884a14860cedc0f
- Challenge Object:
0x19e76ca504c5a5fa5e214a45fca6c058171ba333f6da897b82731094504d5ab9
- random:
0x8
- github id: 填写自己的github id
1.1 lets_move源码分析
题目源码: Move CTF Lets Move
1.1.1 常量与结构体定义
-
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 // 跟踪该挑战完成的次数
}
1.2 函数 init
// 初始化一个新的 `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); // 共享这个对象
}
1.3 函数 get_flag
// 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
发出事件,记录挑战完成的详细信息
1.4 函数 getRandomString
使用随机生成器生成一个长度在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)
}
}
1.2 解题的思路
通过阅读源码,这个挑战是需要我们寻找寻找一个符合特定条件的“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证明。
1.2.1 获取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
- sui 浏览器查询获取challenge_bytes 通过交易摘要获取challenge_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]
1.2.2 计算proof证明
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-->
1.2.3 用Sui CLI执行get_flag函数
用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
二、总结
-
学习收获:
- 掌握了Move合约的结构与核心特性。
- 深入理解了哈希函数与挑战验证逻辑。
- 学会通过BCS序列化与随机数生成进行智能合约交互。
-
解题关键:
- 理解Challenge对象的序列化形式。
- 动态计算符合条件的proof。
-
适用场景:
本次挑战为区块链开发中的密码学验证提供了实践经验,可应用于密码学协议验证、区块链挑战任务开发等领域。
<!--EndFragment-->
本次Move共学系列笔记
Move共学-TASK4完成游戏的上链部署
Move共学-TASK5实现一个最简单的Swap
Move共学-TASK6用Sui SDK和Navi SDK完成一个自定义的PTB模块
Move共学-TASK7完成 MoveCTF Check in挑战
三、官方资源
- Sui 官方文档:Sui 的官方网站提供了全面且权威的文档,涵盖了 Sui 区块链的基本概念、架构、Move 语言的语法、特性,以及在 Sui 上开发智能合约的详细指南等内容,这是深入理解 Sui Move 的基础资料,网址为<https://docs.sui.io/> 。
- MoveCTF 2024 官方网站:由 MoveBit 主办、Sui 独家赞助的 MoveCTF 2024 竞赛网站,提供了竞赛的具体信息、规则、题目示例等,可通过<https://movectf2024.movebit.xyz/>访问
关注《HOH水分子》公众号,我们将持续分享和制作变成语言教程,让大家对编程产生化学反应。
<!--StartFragment-->
<!--EndFragment-->
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。