{"content":{"title":"Move共学-TASK8完成 MoveCTF Lets Move挑战","body":"## 需求\r\n\r\n- 完成 CLI 调用学习\r\n- 理解合约交互传值\r\n- 完成 Move CTF Lets Move\r\n\r\n## 一、任务指南\r\n\r\n- 合约部署地址: `0x097a3833b6b5c62ca6ad10f0509dffdadff7ce31e1d86e63e884a14860cedc0f`\r\n- Challenge Object: `0x19e76ca504c5a5fa5e214a45fca6c058171ba333f6da897b82731094504d5ab9`\r\n- random: `0x8`\r\n- github id:  填写自己的github id\r\n\r\n### 1.1 lets_move源码分析\r\n题目源码：\r\n[Move CTF Lets Move](https://github.com/move-cn/letsmove-ctf/tree/main/src/02_lets_move/lets_move)\r\n\r\n#### 1.1.1 常量与结构体定义\r\n - EPROOF `const EPROOF: u64 = 0;` 常量表示证明检查失败时的错误代码。\r\n - `Flag` 结构体\r\n```rust\r\n    // flag结构体\r\n    public struct Flag has copy, drop {\r\n        sender: address,              // 提交证明的地址\r\n        flag: bool,                        // 布尔值，说明挑战是否成功\r\n        ture_num: u64,                // 用于跟踪挑战成功的次数\r\n        github_id: String             // 表示提交证明的GitHub Id\r\n    }\r\n```\r\n\r\n - `Challenge` 结构体\r\n\r\n```rust\r\n    // Challenge 结构体\r\n    public struct Challenge has key {\r\n        id: UID,                            // 挑战的唯一标识符 (`UID`)\r\n        str: String,                       // 一个字符串，可能表示挑战的描述或名称\r\n        difficulity: u64,                // 挑战难度的级别\r\n        ture_num: u64                // 跟踪该挑战完成的次数\r\n    }\r\n```\r\n#### 1.2 函数 init\r\n```rust\r\n    // 初始化一个新的 `Challenge` 对象\r\n    // 使用 `object::new(ctx)` 创建唯一 ID，字符串为   `\"LetsMoveCTF\"`，难度为 `3`。\r\n    fun init(ctx: &mut TxContext) {\r\n        let flag_str = Challenge {\r\n            id: object::new(ctx),\r\n            str: string(b\"LetsMoveCTF\"),\r\n            difficulity: 3,\r\n            ture_num: 0,\r\n        };\r\n        share_object(flag_str);        // 共享这个对象\r\n    }\r\n```\r\n#### 1.3 函数 get_flag\r\n```rust\r\n    // get_flag 方法\r\n    entry fun get_flag(\r\n        proof: vector<u8>,    //  `u8` 类型的向量，表示某种加密证明（可能是挑战的解决方案）。\r\n        github_id: String,        // 提交证明的用户的 GitHub ID 字符串\r\n        challenge: &mut Challenge,   // 指向一个 `Challenge` 对象\r\n        rand: &Random,                     // 随机数\r\n        ctx: &mut TxContext               // 事务上下文，用于与 Sui 区块链进行交互。\r\n    ) {\r\n        // 将 `proof`、发送者的地址以及挑战对象合并成一个 `full_proof` 向量。\r\n        let mut full_proof: vector<u8> = vector::empty<u8>();\r\n        vector::append<u8>(&mut full_proof, proof);\r\n        vector::append<u8>(&mut full_proof, tx_context::sender(ctx).to_bytes());\r\n        vector::append<u8>(&mut full_proof, bcs::to_bytes(challenge));\r\n\r\n        // 对 `full_proof` 进行 SHA3-256 哈希运算。\r\n        let hash: vector<u8> = hash::sha3_256(full_proof);\r\n\r\n        // 循环遍历哈希值，计算 `prefix_sum` \r\n        let mut prefix_sum: u32 = 0;\r\n        let mut i: u64 = 0;\r\n        while (i < challenge.difficulity) {\r\n            prefix_sum = prefix_sum + (*vector::borrow(&hash, i) as u32);\r\n            i = i + 1;\r\n        };\r\n\r\n       // 目标是检查哈希的前 `difficulity` 字节的总和是否为 `0`\r\n        assert!(prefix_sum == 0, EPROOF);\r\n\r\n        // 检查通过，生成一个新的随机字符串,并更新挑战的状态。\r\n        challenge.str = getRandomString(rand, ctx);\r\n        challenge.ture_num = challenge.ture_num + 1;\r\n\r\n        // 通过 `event::emit` 发出事件，记录挑战完成的详细信息\r\n        event::emit(Flag {\r\n            sender: tx_context::sender(ctx),\r\n            flag: true,\r\n            ture_num: challenge.ture_num,\r\n            github_id\r\n        });\r\n    }\r\n```\r\n这个MoveCTF挑战的主要步骤\r\n①  将 `proof`、发送者的地址以及挑战对象合并成一个 `full_proof` 向量。\r\n②  对 `full_proof` 进行 SHA3-256 哈希运算。\r\n③  循环遍历哈希值，计算 `prefix_sum` \r\n④ 目标是检查哈希的前 `difficulity` 字节的总和是否为 `0`， 失败抛异常错误码\r\n⑤  检查通过，生成一个新的随机字符串,并更新挑战的状态。\r\n⑥  最后，通过 `event::emit` 发出事件，记录挑战完成的详细信息\r\n\r\n#### 1.4 函数 getRandomString\r\n使用随机生成器生成一个长度在4到 30之间的随机字符串。\r\n\r\n```rust\r\n    // 使用随机生成器生成一个长度在 `4` 到 `30` 之间的随机字符串。\r\n    fun getRandomString(rand: &Random, ctx: &mut TxContext): String {\r\n        let mut gen = random::new_generator(rand, ctx);\r\n\r\n        let mut str_len = random::generate_u8_in_range(&mut gen, 4, 30);\r\n\r\n        let mut rand: vector<u8> = b\"\";\r\n        while (str_len != 0) {\r\n            let rand_num = random::generate_u8_in_range(&mut gen, 34, 126);\r\n            vector::push_back(&mut rand, rand_num);\r\n            str_len = str_len - 1;\r\n        };\r\n\r\n        string(rand)\r\n    }\r\n}\r\n```\r\n### 1.2 解题的思路\r\n通过阅读源码，这个挑战是需要我们寻找寻找一个符合特定条件的“proof”（证明）。具体来说，它会生成一个 8 字节的随机 proof，并通过 SHA3-256 哈希算法计算出一个哈希值，然后检查哈希的前 `difficulty` 个字节的和是否为零。\r\n```rust\r\n        let mut full_proof: vector<u8> = vector::empty<u8>();\r\n        vector::append<u8>(&mut full_proof, proof);\r\n        vector::append<u8>(&mut full_proof, tx_context::sender(ctx).to_bytes());\r\n        vector::append<u8>(&mut full_proof, bcs::to_bytes(challenge));\r\n```\r\n代码中的 sender是明确的，我们需要找到bcs::to_bytes(challenge)的值和proof证明。\r\n那么如何获取挑战对象哈希序列化对象结构体的值？然后通过逆向分析计算出对应的 proof证明。\r\n#### 1.2.1 获取`Challenge`对象的序列化数组\r\n因为Challenge对象在测试网上，我们可以通过一个合约的函数，来获取challenge_bytes\r\n\r\n```rust\r\nmodule get_challenge::get_challenge {\r\n    use lets_move::lets_move::Challenge;\r\n    use sui::bcs;\r\n    use sui::event;\r\n\r\n    public struct ChallengeByte has copy, drop {\r\n        challenge_bytes: vector<u8>\r\n    }\r\n\r\n    public entry fun get_challenge(challenge: &Challenge) {\r\n        let challenge_bytes = bcs::to_bytes(challenge);\r\n        event::emit(ChallengeByte { challenge_bytes });\r\n    }\r\n}\r\n```\r\n1. 发布到测试环境\r\n命令：`sui client publish --gas-budget 100000000 --skip-fetch-latest-git-deps --skip-dependency-verification `\r\n```\r\nPS 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 \r\n[warn] Client/Server api version mismatch, client api version : 1.37.1, server api version : 1.38.2\r\nINCLUDING DEPENDENCY lets_move\r\nINCLUDING DEPENDENCY Sui\r\nINCLUDING DEPENDENCY MoveStdlib\r\nBUILDING get_challenge\r\nSkipping dependency verification\r\nTransaction Digest: CgRJQB6Arw68728a3YMmFBX7JuEJPq7KdyqhRfxMBmvA\r\n```\r\n2. sui 浏览器查询获取challenge_bytes\r\n通过交易摘要获取challenge_bytes的值：\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/12/x3V7DfXW676d7b472c9af.png)\r\n[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,\r\n     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]\r\n\r\n#### 1.2.2 计算proof证明\r\n```python\r\nimport hashlib\r\nimport random\r\n\r\n\r\ndef sha3_256(data: bytes) -> bytes:\r\n    \"\"\"计算 SHA3-256 哈希\"\"\"\r\n    return hashlib.sha3_256(data).digest()\r\n\r\n\r\ndef find_proof(sender: bytes, challenge: bytes, difficulty: int = 3) -> list:\r\n    \"\"\"寻找满足条件的 proof 并返回字节数组\"\"\"\r\n    while True:\r\n        # 随机生成 8 字节的 proof\r\n        proof = random.randbytes(8)\r\n\r\n        # 构建 full_proof = proof + sender + challenge\r\n        full_proof = proof + sender + challenge\r\n\r\n        # 计算 SHA3-256 哈希\r\n        hash_result = sha3_256(full_proof)\r\n\r\n        # 检查前 difficulty 个字节的和是否为 0\r\n        if sum(hash_result[:difficulty]) == 0:\r\n            print(f\"Proof found (byte array): {[b for b in proof]}\")\r\n            return [b for b in proof]\r\n\r\n\r\n# 将 sender 转换为字节数组（16 进制字符串转为 bytes）\r\nsender_hex = \"1546f533333b358a8edddd38a8e8967583883e82ca7de604d5bba15e20e493d2\"\r\nsender_bytes = bytes.fromhex(sender_hex)\r\n\r\n# 使用提供的 challenge_bytes\r\nchallenge_bytes = bytes(\r\n    [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,\r\n     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])\r\n\r\n# 计算 proof 并输出为字节数组\r\nproof = find_proof(sender_bytes, challenge_bytes)\r\nprint(f\"Calculated proof as byte array: {proof}\")\r\n\r\n```\r\n核心逻辑，`find_proof` 函数\r\n\r\n<!--StartFragment-->\r\n\r\n* 该函数尝试通过不断生成随机的 8 字节 `proof`，并与 `sender` 和 `challenge` 一起构建 `full_proof`，然后计算其 SHA3-256 哈希值。\r\n\r\n* 计算哈希后，它检查哈希结果前 `difficulty` 个字节的和是否为 0。如果条件满足（即 `sum(hash_result[:difficulty]) == 0`），表示找到了符合条件的 proof，函数将其返回。\r\n\r\n* `random.randbytes(8)` 用于生成一个随机的 8 字节 proof。\r\n\r\n<!--EndFragment-->\r\n\r\n\r\n<!--StartFragment-->\r\n#### 1.2.3 用Sui CLI执行get_flag函数\r\n用Cli命令调用get_flag函数，验证 proof证明是否正确。\r\n\r\n```rust\r\nPS 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\r\n[warn] Client/Server api version mismatch, client api version : 1.37.1, server api version : 1.38.2\r\nTransaction Digest: 2y7NhUaRJ3mJDenJSJPBTQWVfFkawsTBo6AdHLGPWAf4\r\n\r\n```\r\n\r\n## 二、总结\r\n1. **学习收获**：\r\n\r\n   * 掌握了Move合约的结构与核心特性。\r\n   * 深入理解了哈希函数与挑战验证逻辑。\r\n   * 学会通过BCS序列化与随机数生成进行智能合约交互。\r\n\r\n2. **解题关键**：\r\n\r\n   * 理解Challenge对象的序列化形式。\r\n   * 动态计算符合条件的proof。\r\n\r\n3. **适用场景**：\r\n\r\n   本次挑战为区块链开发中的密码学验证提供了实践经验，可应用于密码学协议验证、区块链挑战任务开发等领域。\r\n\r\n<!--EndFragment-->\r\n\r\n本次Move共学系列笔记\r\n\r\n[**Move共学-TASK4完成游戏的上链部署**](https://learnblockchain.cn/article/9899)\r\n\r\n[**Move共学-TASK5实现一个最简单的Swap**](https://learnblockchain.cn/article/9968)\r\n\r\n[**Move共学-TASK6用Sui SDK和Navi SDK完成一个自定义的PTB模块**](https://learnblockchain.cn/article/10066)\r\n\r\n[**Move共学-TASK7完成 MoveCTF Check in挑战**](https://learnblockchain.cn/article/10387)\r\n\r\n\r\n## 三、官方资源\r\n\r\n* **Sui 官方文档**：Sui 的官方网站提供了全面且权威的文档，涵盖了 Sui 区块链的基本概念、架构、Move 语言的语法、特性，以及在 Sui 上开发智能合约的详细指南等内容，这是深入理解 Sui Move 的基础资料，网址为<https://docs.sui.io/> 。\r\n* **MoveCTF 2024 官方网站**：由 MoveBit 主办、Sui 独家赞助的 MoveCTF 2024 竞赛网站，提供了竞赛的具体信息、规则、题目示例等，可通过<https://movectf2024.movebit.xyz/>访问\r\n\r\n**关注《HOH水分子》公众号，我们将持续分享和制作变成语言教程，让大家对编程产生化学反应。**\r\n\r\n<!--StartFragment-->\r\n\r\n![b4AQIzN06730e45415811.webp](https://img.learnblockchain.cn/attachments/2024/12/BNRovrdG674dcb50b8bdb.webp)\r\n\r\n<!--EndFragment-->"},"author":{"user":"https://learnblockchain.cn/people/23933","address":"0x98dEE54578D8c4D23bF90d09AD13e4DaEe7c1DB4"},"history":"bafkreidyn4njzjtklb3rwwakjoojogbtsdw5oi7q4taejtvfdkjj2qtzaq","timestamp":1735260002,"version":1}