Info

Download ZIP (549.2 KB)

Testing and Issues

You can test this entry and submit issues during the testing period of the TON Trustless Bridge Challenge contest.

Entries with serious issues will not be able to win the contest, but even minor issues might be important for overall results.

Voting

36

Comments

Gm devs! A few points about our implementation:

1️⃣ Implemented ton-bridge-syncer, which actively submits new key blocks,  keeping the bridge in sync.

2️⃣ Carefully handled key block transitions (the process of switching from one validator set to the next), keeping both previous and next validator sets (config params 32 & 36) to maintain the bridge’s trustlessness (more details in the reply for 1st issue).

3️⃣ By pruning unnecessary data, we’ve kept block submission gas costs very low—submitting a new key block on testnet costs ~0.01 TON.

4️⃣ Implemented a short solution to get the transaction hash from the account block hashmap, keeping transaction check gas costs low—~0.02 TON per check on the testnet.

Looking forward to discussions & feedback!
macOS
You have not added any comments yet...
by rating

Issues

Config params 32, 34 and 36 all being saved in lite-client and thus code looks overcomplicated.

As I understand only config_param 34 has to be stored for system to work properly.
Windows 10
1
Cool Kangaroo Feb 5 at 12:46
Why we need to keep config param 32 (List of validators from the previous key block):
The first key block in an epoch (starting key block) is signed by the previous epoch’s validators. We need to keep them to verify key block transactions if needed.

Why we need to keep config param 36 (List of validators from the next key block):
Transitioning key blocks introduce the next validator set. To ensure key block continuity, we must store them. Without this, the current validator set could manipulate the next set, compromising trustlessness.

We prune as much data as possible from key blocks, keeping our implementation gas-efficient (0.01 TON on testnet for submitting a new key block).
macOS
In transaction-checker there is no process of bounced messages neither check of msg_value, attacker may send small amount of ton which will not be enough for process check block. which can lead to overload of check_transaction_requests. You need to check msg_value for to make sure that this will be enough to carry out all transactions
1
Cool Kangaroo Feb 10 at 15:54
Agreed. It’s better to check msg.value to prevent this scenario.
In function `new_key_block` you save all validators and use them all in `verify_signatures`. You should take only first `main` validators.
1
Cool Kangaroo Feb 11 at 15:28
Thanks for the comments. 
While only main validators usually sign blocks, verifying all valid signatures adds extra security without much gas overhead since we only process the signatures that are actually there. The code still enforces the 2/3 weight threshold, keeping all necessary security properties intact while staying adaptable to future protocol changes.
Also in order to get the main we need to sort them by their weights, which has more gas usage than only saving all of them and searching for the main ones through the 15 signatures we got.
In function `new_key_block` you send not block. It's just some part of the block. You should send the block and verify the block.
Cool Kangaroo Feb 11 at 15:25
We're verifying exactly what we need to - the block's cryptographic fingerprint (root_hash + file_hash) that validators actually sign. Checking the entire raw block would be redundant since these hashes already prove the block's authenticity and we are verifying the hashes before verifying the signatures.
Reply on `Subtle Swallow Feb 10 at 23:36`
Same problem is in `check_block` function.
Cool Kangaroo Feb 11 at 15:26
Same answer
In TX-checker contract you have `verify_tx_hash` function. It's not proof that tx in block. It's prof that some cell is in `account block`.
Cool Kangaroo Feb 11 at 15:26
We're verifying a Merkle proof that connects the transaction to the block through the account blocks structure. When we check the transaction hash in the account block, we're following TON's transaction storage format where transactions are stored in account blocks as exotic cells with their hashes. Since we've already verified the Merkle proof leading to this account block, finding the matching hash proves the transaction is part of the block - that's how TON's blockchain structure works!
The key is that we're not just checking "some cell" - we're checking a verified path from block -> account blocks -> transaction hash.
Implemented basic functionality with a few security issues: no main_validators logic, global_id checks, epoch check is implemented as time-based check (better than nothing, worse than proper check). In tx_checker full cell scan, with attention paid to pruning unused branches, cheaper but still insecure. Tests not working.

More info on issues: https://contest.com/docs/TrustlessBridgeChallengeAssessment
Cool Kangaroo Feb 19 at 15:13
Hello, thanks for the comments!

1) About the epoch check: Our main checks are based on seqno and the new current validator set. The time-based check was an extra security measure.

2) Could you elaborate more on the tx_checker issue? Since we stop when we reach a tx cell (an exotic cell at the highest level) and we don't check further so for example a msg cell in a tx will not be considered.

3) Tests are running properly. I’ve attached a screenshot of it.
macOS
1) By prev_seqno check, I meant that in new_key_block we do not check that previous synced keyblock (the one contract knew before this tx) coincide with prev_keyblock from block header. Still do not see this check.

2) >tx_checker insecurity
Attacker can submit proof where he don't prune on tx-level leafs, but contain child refs including "forged" tx-cell in (for instance) msg list.

3) Accepted (for some reason there were errors on first check, but now tests work)

Is it right, your contract can not process keyblock with empty 32 config?
Cool Kangaroo Feb 20 at 16:28
1. We didn’t check that prev_keyblock_seqno matches the latest seqno in the contract. However, we verified that prev_validatorset matches curr_validatorset, covering both this check and an edge case.

If a new keyblock changes the validator set, transactions in that keyblock should be verified using the previous validator set. We save configParam32 to handle this edge case while ensuring continuity checks between keyblocks.

2. Right. It can be fixed by skipping non-pruned cells longer than 256 bits since keys are at most 256 bits, while transaction cells are longer.

3. Sounds good!

4. For keyblocks with an empty configParam32, we assume this doesn’t happen—there should always be a previous validator set. If documentation states otherwise, we’re happy to check, but under normal circumstances, we wouldn’t process such a keyblock.
Nobody added any issues yet...