The present audit report has been prepared by Pruvendo at 05/11/23
The present audit is conducted against
tvm-preprocessed-wallet contract with the code equals to the
commit aebadc7ba6ff8fc7f99c98b22ec2197fd7e94038
.
The contract represents a simple TVM-based (applicable for such blockchains as TON or Everscale) wallet, with the basic functionality and strongly optimized in terms of gas spending.
The audit result is POSITIVE and the contract is recommended for moving into production.
While the present project is just an audit, without formal verificattion of the code, the following light technologies are being used:
- the whole workflow is manually analyzed step-by-step
- the possibility of typical attacks is analyzed
- manual check for optimized gas usage
The following analysis is based of TVM Specification created by Nickolay Durov at 03/23/2020/ as well as Fift documentation. According to the code the ASM library is used.
The initial value of stack is described here and assumes five elements put on stack. Only two of them are used and latter are ignored and so skipped from the further consideration.
The valuable stack elements are:
- selector - boolean:
- FALSE - for internal messages
- TRUE - for external messages
- msg - slice that represents message body according to the Chapter A.11.9 of the TVM documentation mentioned above as well as in section 3.1.7 of Blockchain documentation
The message body has the following parameters:
- data:
- sign - at least 512 bits representing signature
- ref:
- data - cell representing the rest of the message
- data:
- until - 64-bit value representing time until the message is valid
- num - 16-bit value representing the message sequence number
- ref:
- actions - the cell representing the code to be executed
- data:
- data - cell representing the rest of the message
Persistent data is a cell that contains two bit sequences with no references:
- key - 256-bit sequence representing the private key of the contract owner
- cur - the current value sequence number (16 bits) that is expected in the next message
Instruction | Comments | S0 | S1 | S2 | S3 | S4 | S5 | S6 | S7 | S8 | S9 |
---|---|---|---|---|---|---|---|---|---|---|---|
SETCP0 | Ensures the set of instructions corresponds to ones provided in the document mentioned above | selector | msg | ||||||||
IFNOTRET | Only external messages allowed | msg(ext) | |||||||||
LDREF | Load signature | sign | data | ||||||||
SWAP | data | sign | |||||||||
DUP | data | data | sign | ||||||||
HASHCU | Computes hash of the data | hash(data) | data | sign | |||||||
SWAP | data | hash(data) | sign | ||||||||
CTOS | data(slice) | hash(data) | sign | ||||||||
LDU 64 | Loads time until the message is valid | data(-until) | until | hash(data) | sign | ||||||
LDU 16 | Loads message sequence number | data(-until-num) | num | until | hash(data) | sign | |||||
PLDREF | Preloads (substitutes) the remnants of data slice with the actions reference | actions | num | until | hash(data) | sign | |||||
PUSH c4 | Loads persistents | c4 | actions | num | until | hash(data) | sign | ||||
CTOS | c4(sliced) | actions | num | until | hash(data) | sign | |||||
LDU 256 | Loads key value | c4(-key) | key | actions | num | until | hash(data) | sign | |||
PLDU 16 | Preloads cur value | cur | key | actions | num | until | hash(data) | sign | |||
DUP | cur | cur | key | actions | num | until | hash(data) | sign | |||
INC | Increments current sequence number | cur+1 | cur | key | actions | num | until | hash(data) | sign | ||
PUSHPOW2 16 | Pushes 65536 | 65536 | cur+1 | cur | key | actions | num | until | hash(data) | sign | |
MOD | (cur+1)%216 | cur | key | actions | num | until | hash(data) | sign | |||
PUSH s2 | key | (cur+1)%216 | cur | key | actions | num | until | hash(data) | sign | ||
NEWC | empty builder | key | (cur+1)%216 | cur | key | actions | num | until | hash(data) | sign | |
STU 256 | Stores key | builder(key) | (cur+1)%216 | cur | key | actions | num | until | hash(data) | sign | |
STU 16 | Stores new cur | builder(key, (cur+1)%216) | cur | key | actions | num | until | hash(data) | sign | ||
ENDC | cell(key, (cur+1)%216) | cur | key | actions | num | until | hash(data) | sign | |||
POP c4 | Update persistents with new cur value | cur | key | actions | num | until | hash(data) | sign | |||
XCHG3 s4 s3 s0 | cur | num | until | key | actions | hash(data) | sign | ||||
XCHG s4 s6 | cur | num | until | key | sign | hash(data) | actions | ||||
EQUAL | Checks if the message number is correct | cur==num | until | key | sign | hash(data) | actions | ||||
THROWIFNOT 33 | Throws an exception in case of incorrect message number | until | key | sign | hash(data) | actions | |||||
NOW | now | until | key | sign | hash(data) | actions | |||||
GEQ | Checks if the message is still valid | now ≥ until | key | sign | hash(data) | actions | |||||
THROWIFNOT 34 | Throws an exception if the is expired | key | sign | hash(data) | actions | ||||||
CHKSIGNU | Checks if the signature is correct | signature correct? | actions | ||||||||
THROWIFNOT 35 | Throws an exception if the signature is incorrect | actions | |||||||||
ACCEPT | Accepts spending gas from the receiving contract | actions | |||||||||
POP c5 | Stores output actions |
The detailed workflow analysis provided above demonstrates that the contract works correctly and provides the comprehensive checks for all the input parameteres, including the security ones.
The most common attacks for the wallet contracts are:
- Violation of security
- Replay attack
- Exception after ACCEPT
The contract provides the correct process of checking the signature of the sender thus eliminating this risk.
The contract provides a defence against replay protection that is based on exact equivalence of the expected message counter (incremented at each message) and the message number provided.
Such a defence:
- prevents a replay attack
- elimitates the risk of counter overflow by using modulo wrapped increment (with modulo equals to 216)
- at the same time the modulo is big enough to avoid the risk of having two non-expired messages with the same number
Exceptions after ACCEPT may lead to uncontrolled gas spending from the contract balance due to the repeatable calls of public methods by the attacker.
In the present contract the developer filtered out all the potentially malicious calles before invoking the ACCEPT primitive thus eliminating the possibility of the attack being discussed.
The developers chose to use almost pure TVM Assembler (Fift environment is just a boilerplate that keeps the final code intacted) that let them to apply some exotic and efficent primitives such as XCHG3 or PUSHPOW2 . All the doubtful cases (such as using PUSHPOW2 together with MOD instead of PUSH and AND ) were analyzed and the auditors came to conclusion that developers chose the optimal approach in all the cases were considered.
No issues found throughout the present audit.
As a result of the audit the auditors recommend the contract to be deployed into production. The outcome is POSITIVE.