Skip to content

Commit

Permalink
chore: minor edits
Browse files Browse the repository at this point in the history
  • Loading branch information
novusnota committed Nov 27, 2024
1 parent 0a1b13d commit a717d5a
Showing 1 changed file with 52 additions and 52 deletions.
104 changes: 52 additions & 52 deletions docs/src/content/docs/book/security-best-practices.mdx
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
---
title: Security Best Practices
title: Security best practices
description: "Several anti-patterns and potential attack vectors, as well as best practices, that Tact smart contract developers should be aware of"
---

[//]: # (✅❌)

In Tact smart contracts there are various anti-patterns and potential attacks developers need to be cautious of. These can impact the security, efficiency, and correctness of the contracts. Below, are covered both common anti-patterns and attacks specific to Tact smart contracts.
There are several anti-patterns and potential attack vectors that Tact smart contract developers should be aware of. These can affect the security, efficiency, and correctness of the contracts. Below we discuss the do's and don'ts specific to writing and maintaining secure Tact smart contracts.

This article is based on the following resources. For a deeper understanding, refer to
For a deeper understanding, refer to the following resources:

* [Smart contracts guidelines](https://docs.ton.org/v3/guidelines/smart-contracts/guidelines)
* [Secure Smart Contract Programming](https://docs.ton.org/v3/guidelines/smart-contracts/security/secure-programming)
* [FunC Security Best Practices](https://github.com/slowmist/Toncoin-Smart-Contract-Security-Best-Practices/blob/main/README.md)
* [Smart contracts guidelines in TON Docs](https://docs.ton.org/v3/guidelines/smart-contracts/guidelines)
* [Secure Smart Contract Programming in TON Docs](https://docs.ton.org/v3/guidelines/smart-contracts/security/secure-programming)
* [FunC Security Best Practices in GitHub repo](https://github.com/slowmist/Toncoin-Smart-Contract-Security-Best-Practices/blob/main/README.md)

## Send sensitive data on chain
## Send sensitive data on-chain

The whole smart contract computation is transparent and if you had some confidential values in runtime these could be retrieved with a simple emulation.
The entire smart contract computation is transparent, and if you had some confidential values at run-time, they could be retrieved with a simple emulation.

##### Do's ✅

Do not send sensitive data on chain.
Do **not** send or store sensitive data on-chain.

##### Don'ts ❌

Expand All @@ -40,23 +41,20 @@ contract Test with Deployable {
}
```


## Misuse of signed integers

Unsigned integers are safer because they prevent most errors by design, while signed integers can lead to unpredictable consequences if not used carefully. Therefore, signed integers should only be used when absolutely necessary.
Unsigned integers are safer because they prevent most errors by design, while signed integers can have unpredictable consequences if not used carefully. Therefore, signed integers should be used only when absolutely necessary.

##### Do's ✅

Prefer using unsigned integers unless signed integers are specifically required.
Prefer to use unsigned integers unless signed integers are required.

##### Don'ts ❌

The following is an example of incorrect use of a signed integer. In the `Vote` message, the `votes` type is `int32`. This can lead to fraud when the attacker sends the negative number of votes instead of positive one.
The following is an example of the incorrect use of a signed integer. In the `Vote{:tact}` [Message][message], the type of the `votes` field is `Int as int32{:tact}`, which is a $32$-bit singed integer. This can lead to spoofing if the attacker sends the negative number of votes instead of a positive one.

```tact
message Vote {
votes: Int as int32;
}
message Vote { votes: Int as int32 }
contract Sample {
votes: Int as uint32 = 0;
Expand All @@ -69,11 +67,11 @@ contract Sample {

## Invalid throw values

Exit codes $0$ and $1$ indicate normal TVM execution of the compute phase of the transaction. Execution can be unexpectedly aborted by calling [`throw(){:tact}`](/ref/core-debug#throw) (or [similar functions](/ref/core-debug)) directly with exit codes $0$ and $1$, which can make debugging very difficult since this aborted execution would be considered normal.
[Exit codes](/book/exit-codes) $0$ and $1$ indicate normal execution of the compute phase of the transaction. Execution can be unexpectedly aborted by calling a [`throw(){:tact}`](/ref/core-debug#throw) or [similar functions](/ref/core-debug) directly with exit codes $0$ and $1$. This can make debugging very difficult since such aborted execution would be indistinguishable from a normal one.

##### Do's ✅

Prefer using `require` to validate some conditions.
Prefer to use the [`require(){:tact}`](/ref/core-debug#require) function to state expectations.

```tact
require(isDataValid(msg.data), "Invalid data!");
Expand All @@ -90,22 +88,24 @@ throw(1);

## Insecure random numbers

Generating truly secure random numbers in TON is challenging. The [`random()`](/ref/core-random#random) function is pseudo-random and depends on [logical time](https://docs.ton.org/develop/smart-contracts/guidelines/message-delivery-guarantees#what-is-a-logical-time). A hacker can predict random by brute-forcing the logical time in the current block.
Generating truly secure random numbers in TON is challenging. The [`random()`](/ref/core-random#random) function is pseudo-random and depends on [logical time](https://docs.ton.org/develop/smart-contracts/guidelines/message-delivery-guarantees#what-is-a-logical-time). A hacker can predict the randomized number by [brute-forcing](https://en.wikipedia.org/wiki/Brute-force_attack) the logical time in the current block.

##### Do's ✅

- For critical applications **avoid relying solely on on-chain solutions**.
- Use [`random()`](/ref/core-random#random) with randomized logical time to enhance security by making predictions harder for attackers without Validator Node access. However, note that it is **still not entirely foolproof**.
- Consider using the **commit-and-disclose scheme**:
* For critical applications **avoid relying solely on on-chain solutions**.

* Use [`random(){:tact}`](/ref/core-random#random) with randomized logical time to enhance security by making predictions harder for attackers without access to a validator node. Note, however, that it is still **not entirely foolproof**.

* Consider using the **commit-and-disclose scheme**:
1. Participants generate random numbers off-chain and send their hashes to the contract.
2. Once all hashes are received, participants disclose their original numbers.
3. Combine the disclosed numbers (e.g., summing them) to produce a secure random value.

For more details, refer to the [Secure Random number generation](https://docs.ton.org/v3/guidelines/smart-contracts/security/random-number-generation).
For more details, refer to the [Secure Random number generation page in TON Docs](https://docs.ton.org/v3/guidelines/smart-contracts/security/random-number-generation).

##### Don'ts ❌

Don't rely on random function.
Don't rely on the [`random(){:tact}`](/ref/core-random#random) function.

```tact
if (random(1, 10) == 7) {
Expand All @@ -115,11 +115,9 @@ if (random(1, 10) == 7) {

Don't use randomization in `external_message` receivers, as it remains vulnerable even with randomizing logical time.


## Optimized message handling

String parsing from human-friendly formats into machine-readable binary structures should be handled **off-chain**.
This approach ensures that only optimized and compact messages are sent to the blockchain, minimizing computational and storage costs while avoiding unnecessary gas overhead.
String parsing from human-friendly formats into machine-readable binary structures should be done **off-chain**. This approach ensures that only optimized and compact messages are sent to the blockchain, minimizing computational and storage costs while avoiding unnecessary gas overhead.

##### Do's ✅

Expand All @@ -137,7 +135,7 @@ receive(msg: Sample) {

##### Don'ts ❌

Avoid parsing strings from human-readable formats into binary structures **on-chain**, as it increases computational overhead and gas costs.
Avoid parsing strings from human-readable formats into binary structures **on-chain**, as this increases computational overhead and gas costs.

```tact
message Sample {
Expand All @@ -152,8 +150,7 @@ receive(msg: Sample) {

## Gas limitation

Be careful with the `Out of gas error`. It cannot be handled, so try to precalculate the gas consumption for each receiver using tests, if possible.
This will help not to waste extra gas, because the transaction will fail anyway.
Be careful with the `Out of gas error`. It cannot be handled, so try to pre-calculate the gas consumption for each receiver [using tests](/book/debug#tests) whenever possible. This will help to avoid wasting extra gas because the transaction will fail anyway.

##### Do's ✅

Expand All @@ -173,11 +170,11 @@ contract Sample2 {

## Identity validation

Always validate identity of the sender if needed. This may be achieved through [`Ownable{:tact}`](/ref/stdlib-ownable) trait or using state init validation. You can read more about [Jetton validation](/cookbook/jettons#accepting-jetton-transfer) and [NFT validation](/cookbook/nfts#accepting-nft-ownership-assignment).
Always validate the identity of the sender if your contract logic revolves around trusted senders. This can be done using the [`Ownable{:tact}`](/ref/stdlib-ownable) trait or using [state init](/book/expressions#initof) validation. You can read more about [Jetton validation](/cookbook/jettons#accepting-jetton-transfer) and [NFT validation](/cookbook/nfts#accepting-nft-ownership-assignment).

##### Do's ✅

Use [`Ownable{:tact}`](/ref/stdlib-ownable) trait.
Use the [`Ownable{:tact}`](/ref/stdlib-ownable) trait.

```tact
import "@stdlib/ownable";
Expand Down Expand Up @@ -223,12 +220,14 @@ contract Example with Deployable {

## Replay protection

Replay protection is a security mechanism to prevent an attacker from reusing a previous message. More about replay protection may be found [here](https://docs.ton.org/develop/smart-contracts/guidelines/external-messages).
Replay protection is a security mechanism that prevents an attacker from reusing a previous message. More information about replay protection can be found on the [External messages page in TON Docs](https://docs.ton.org/develop/smart-contracts/guidelines/external-messages).

##### Do's ✅

Always include and validate a unique identifier, like `seqno`, to differentiate messages. Update the identifier after successful processing to prevent duplicates.
Alternatively, you can implement replay protection, as done in [Highload V3](https://github.com/ton-blockchain/highload-wallet-contract-v3/blob/main/contracts/highload-wallet-v3.func#L60), which is not based on seqno.
To differentiate messages, always include and validate a unique identifier, such as `seqno`. Update the identifier after successful processing to avoid duplicates.

Alternatively, you can implement a replay protection similar to the one in the [highload v3 wallet](https://github.com/ton-blockchain/highload-wallet-contract-v3/blob/main/contracts/highload-wallet-v3.func#L60), which is not based on `seqno`.

```tact
message Msg {
newMessage: Cell;
Expand Down Expand Up @@ -263,7 +262,7 @@ contract Sample {

##### Don'ts ❌

Do not rely on signature validation without incorporating a sequence number. Messages without replay protection can be resent by attackers, as nothing distinguishes a valid original message from a replayed one.
Do not rely on signature verification without the inclusion of a sequence number. Messages without replay protection can be resent by attackers because there is nothing to distinguish a valid original message from a replayed one.

```tact
message Msg {
Expand All @@ -288,16 +287,15 @@ contract Sample {

## Race condition of messages

A message cascade can be processed over many blocks. Assume that while one message flow is running, an attacker can initiate a second one in parallel.
That is, if a property was checked at the beginning (e.g. whether the user has enough tokens), do not assume that at the third stage in the same contract they will still satisfy this property.
A message cascade can be processed over many blocks. Assume that while one message flow is running, an attacker can initiate a second message flow in parallel. That is, if a property was checked at the beginning, such as whether the user has enough tokens, do not assume that it will still be satisfied at the third stage in the same contract.

## Handle/Send bounced messages

Send messages with bounce flag. Bounced messages means that other contract execution failed. You may want to deal with it by rolling back contract's state.
Send messages with the bounce flag set to `true{:tact}`, which is a default for the [`send(){:tact}`](/ref/core-common#send) function. Messages bounce when the execution of a contract has failed. You may want to deal with this by rolling back the state of the contract by wrapping code in [`try...catch{:tact}`](/book/statements#try-catch) statements and some additional processing depending on your logic.

##### Do's ✅

Handle bounced messages via [bounced message receiver](/book/bounced/#bounced-message-receiver) to correctly react to failed messages.
Handle bounced messages via a [bounced message receiver](/book/bounced/#bounced-message-receiver) to correctly react to failed messages.

```tact
contract JettonDefaultWallet {
Expand Down Expand Up @@ -346,25 +344,23 @@ contract JettonDefaultWallet {

## Transaction and phases

The computational phase executes the code of smart contracts and only then the actions are performed (sending messages, code modification, changing libraries, and others) and state of contract is updated.
That's mean if compute phase fails [registers](https://docs.ton.org/v3/documentation/tvm/tvm-overview#control-registers) `c4` (“persistent data”) and `c5` (“actions”) won't be updated. But it is possibly to manually save their state via [`commit(){:tact}`](/ref/core-advanced/#commit).
From [Sending messages page](/book/send#outbound-message-processing) of the Book:

> Each transaction on TON Blockchain consists of multiple phases. Outbound messages are evaluated in compute phase, but are **not** sent in that phase. Instead, they're queued in order of appearance for the action phase, where all actions listed in compute phase, like outbound messages or reserve requests, are executed.
Hence, if the compute phase fails, [registers](https://docs.ton.org/v3/documentation/tvm/tvm-overview#control-registers) `c4` (persistent data) and `c5` (actions) won't be updated. However, it is possible to manually save their state using [`commit(){:tact}`](/ref/core-advanced/#commit) function.

## Return gas excesses carefully

If excess gas is not returned to the sender, the funds will accumulate in your contracts over time.
In principle, nothing terrible, this is just suboptimal practice. You can add a function for raking out excesses,
but popular contracts like TON Jetton still return to the sender with the message `op::excesses`.
If excess gas is not returned to the sender, the funds will accumulate in your contracts over time. Nothing terrible in principle, just a suboptimal practice. You can add a function to rake out excess, but popular contracts like TON Jetton still return to the sender with the [Message][message] with `0xd53276db` opcode.

##### Do's ✅

Return excesses using message with `op::excesses`.
Return excesses using a [Message][message] with `0xd53276db` opcode.

```tact
message Vote {
votes: Int as int32;
}
message(0xd53276db) Excesses {}
message Vote { votes: Int as int32; }
contract Sample {
votes: Int as uint32 = 0;
Expand Down Expand Up @@ -402,10 +398,11 @@ contract Sample {
}
```


## Pulling data from other contract

Contracts in the blockchain can reside in separate shards, processed by other set of validators, meaning that one contract cannot pull data (call getter) from other contracts. Thus, any communication is asynchronous and done by sending messages.
Contracts in the blockchain can reside in separate shards, processed by other set of validators, meaning that one contract cannot pull data from other contracts. That is, no contract can call a [getter function](/book/functions#getter-functions)) from other contracts.

Thus, any on-chain communication is asynchronous and done by sending and receiving messages.

##### Do's ✅

Expand Down Expand Up @@ -448,3 +445,6 @@ contract AnotherContract {
}
}
```

[struct]: /book/structs-and-messages#structs
[message]: /book/structs-and-messages#messages

0 comments on commit a717d5a

Please sign in to comment.