CertiK reviewed MAP Protocol (Map Protocol - Near) on 28 Nov 2022: 44 findings, 1 critical; 40 marked fixed, 4 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 1 |
| High | 2 | 0 | 0 |
| Medium | 0 | 0 | 3 |
| Low | 2 | 0 | 36 |
The BLS signature scheme requires a cryptographic hash function that maps arbitrary data to a point in one of the two groups of the chosen elliptic curve. Such a function is also required to respect the properties of the Random Oracle Model. The `hash_to_g1` function at the pointed location maps arbitrary data to a point in the $G1$ group. Its implementation first performs a `sha256` on the input data and then ec-multiplies the resulting string with the generator of the $G1$ group. This is a known insecure manner to implement an hash-to-point function. In fact, the presence of the generator of the $G1$ group inside such expression allows, given a key pair (sk, PK) and a legitimate signature `S` of a message $m$ created by the user holding `sk`, an attacker to forge a signature `S'` of any arbitrary message `m'` such that `verify(S', m', PK) = true` having the sole knowledge of $S$ and $m$. In respect to the audited logic, this vulnerability allows an attacker to forge the aggregated signature carried by validated blocks. Indeed, each valid block, that is public, includes a signature and the date that the signature is supposed to authenticate. This means that any attacker, including malicious Map Relay Chain validator or Maintainers, can let the light client be updated with any fake block containing arbitrary data.
In Near, when a transaction causes a cross-contract call that requires an attached deposit, and the execution of the generated `Promise` fails, the attached deposit is not automatically refunded to the transaction caller. In the `MapCrossChainService`, a call to `transfer_in()` generates a `Promise` to verify the provided proof on the `MapLightClient`. The result of this `Promise` is handled in the `finish_verify_proof` that refunds the signer if the proof validation was not successful.\ In the case in which the supplied proof was correct, execution proceeds with the `process_transfer_in()` handler. However, since `process_transfer_in()` is also checking validity of other parameters, in the case in which any of the operation of `process_transfer_in()` fails, there is not any refund logic to give back the attached deposit to the `transfer_in` initiator. In practice, an example scenario would be the following. We know that the `order_id` of the event is 32 bytes and the price per 1 byte of storage is currently 1e-05 NEAR, so the storage deposit for `order_id` is at least 0.00032 NEAR. Let’s assume that Alice on the Map Relay Chain transfers 100 USDC to Bob on Near. Alice locks 100 USDC on the Map Relay Chain. Then, the `Messenger` will relay the event to the `MapCrossChainService` calling the `transfer_in()` and paying a storage deposit of 0.0002 NEAR in order to unlock 100 USDC to Bob.\ `transfer_in()` will create a `Promise` to asynchronously make a cross-contract call to verify the proof on the `MapLightClient` contract. Anyway, the `transfer_in` call will terminate and the 0.0002 NEAR deposit is transferred to the `MapCrossChainServices` contract account.\ Once the `verify_proof_data` `Promise` resolves with a successful outcome, the `process_transfer_in()` function would be called and the assertion on line 356 on the required deposit will fail because 0.0002 NEAR are not enough for recording the `order id` which needs 0.00032 NEAR.\ The execution of the `finish_verify_proof` fails but the storage deposit of 0.0002 NEAR remains in the `MapCrossChainService` account and the `transfer_in` initiator, i.e. the `Messenger`, can not redeem them in any way.
Calls to methods on smart contracts in Near are made through the serialization of method name and parameter in the JSON encoding format which assumes Javascript types. In particular, in Javascript numeric types are represented through the `number` type. In the case of integer parameters, the Javascript `number` type can only represent numbers on 53 bits, while parameters at the pointed locations express native Rust types `u64` and `u128`. Because of this mismatch in the maximum integers length, such parameters can incur in an overflow when serialized through the JSON encoding. The issue also affects returned values, even if returned in a `struct` like the case of `TransferOutEvent`.
The pseudocode in [EIP-3068](https://eips.ethereum.org/EIPS/eip-3068), reference for the `base_to_g1` function, uses constants that can be precomputed for efficiency. `constant2 = -3` is precomputed in function `hash_const_2()`. However the hardcoded value corresponds to `sqrt(-3)` instead of `-3` (inside the finite field over `p` hardcoded in `prime()`). Since cryptography algorithms are the result of dedicated security demonstrations, implementation should carefully follow the reference algorithm.
There is a `TODO` statement in the code. Such statements are used to identify incomplete functionality.
The pointed line initializes a struct field that was present in a previous development iteration. This prevents unit tests from compiling and running. The following integration tests (17 out of 24) fail their execution: - test_add_validator - test_get_header_height - test_remove_validator - test_update_block_header - test_update_block_header_38000 - test_update_block_header_93000 - test_update_block_header_bad_epoch - test_update_validator_for_20_epochs - test_update_validator_for_21_epochs - test_validator_remove_01 - test_validator_remove_02 - test_validator_remove_add - test_verify_proof_after_add_remove_validators_001 - test_verify_proof_after_add_remove_validators_002 - test_verify_proof_in_diff_epochs - test_verify_proof_multi_receipt - test_verify_proof_single_receipt
The functions `hash()` and `hash_without_seal()` from file `header.rs` are almost identical. The shared functionality can be placed into a separate `internal` or `private` function to improve the code's readability and to detect errors over subsequent development iterations.
The linked lines contain commented code with no explanation on its purpose.
The current error message does not give the accurate information to describe the error condition.\ According to the logic, the `cur` amount, representing the deposit attached to the `transfer_in` function not necessary to the `MapCrossChainService` contract, should be `ret_deposit` instead of the total deposit `cur_deposit`.
The comment above the function `deserialize()` from `serialization/bytes` states that the function will "Deserialize string into T" but the return type of the function is `Integer`.
The local variable `validators` from the function `verify_proof_data()` from mapclient's `lib.rs` has an inconsistent naming. The name suggests that the variable will hold a collection of addresses or properties of the validators, but the variable contains the data of an `epoch_record`.
Some data structures can answer `.is_empty()` much faster than calculating their length. In a smart contract code `.is_empty()` executes more cheaply and, in some contexts, makes the intent clearer than a manual comparison. For more information, we invite the team to check this [reference](https://rust-lang.github.io/rust-clippy/master/index.html#len_zero).
Access Keys authorization in Near is based on a comma separated method list. Such a list in the `multisig` package is declared in the `MULTISIG_METHOD_NAMES` constant. However there is a mismatch in the methods name, since the method `add_request_and_confirm` is declared, in the `MULTISIG_METHOD_NAMES` as `add_and_confirm_request`. This denies the added Access Keys to call such method.
Full Access Keys have full control on the operations that can be performed on and on behalf of an account. This includes accounts holding a smart contract. Comment in `lib.rs`#263 correctly states that adding a full access key to the `multisig` contract would void its utility. However, the adopted solution does not guarantee that such condition can not take place. In fact the cited wallet UI check can be easily circumvented by directly interacting with the on-chain contract. Despite of the fact that any modification must anyway undergo the `multisig` itself approval, the issue can be completely avoided adding the check mentioned in the comment as an on-chain assertion, so that the condition can be ensured independently of off-chain components.
The **assert** macro can be used to check conditions and panic if some are not met. When this happens at least an error message should be provided such that the failure condition can be properly managed, logged or/and reported to the caller.
In Near, off-chain applications can be authorized to execute transactions on behalf of an account creating contract and method specific access keys. If on one hand, this patterns eases the user interaction with an application requiring only a single initial signature, it also poses in the off-chain application or component trust in its correct behavior. It is a good practice to not fully delegate to the off-chain component the execution of all the possible user interactions with a certain smart-contract, but to require an explicit user signature for sensitive operations. This practice in enforced requiring, in the sensitive operation, the transfer of `1 yoctoNear`, which is the minimum transferrable quantity, that constrict the transaction containing the operation to be signed by a full access key belonging to the caller account. In fact, full access key are not generally granted to off-chain components, so an explicit user signature is required. The following methods has sensitive effects on the smart contract logic and/or on the caller account, but no full access key check is enforced as a countermeasure against malicious of malfunctioning applications: - `MCSToken::set_metadata` - `MCSToken::mint` - `MCSToken::burn` - `MCSToken::upgrade_self` - `MultiSigContract::confirm`
When initializing a new epoch record on the `init` function of the `MapLightClient`, there are no validations for the parameters being added. Some problems that may arise from lack of input validation are: - Setting the threshold value to zero, which implies that the vote of a single validator will be enough. - Setting the threshold to a value greater than the number of validators, would cause the system to never reach consensus. - Adding multiple times the same addresses as validators. - Adding addresses with a weight of 0.
The function `update_block_header()` lacks a `log!` event. An event should be emitted for significant transactions in order ease the tracking of important changes or events on the contract data.
The expressions highlighted borrow reference with operator `&`, which will be dereferenced by the compiler immediately.\ For more information, we invite the team to check this [reference](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow).
`collect` causes the allocation of a new data structure, when this allocation may not be needed.\ For more information, we invite the team to check this [reference](https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect).
The `#[near_bindgen]` annotation generates code to serialize the contract state at the end of a function call when the `mut` keyword precedes the `self` parameter. The highlighted parts of the code don't need a mutable reference, so the `mut` keyword is not needed and the serialization of the state can be avoided.
The function `set_metadata()` of `MapCrossChainService` contract attaches some deposit to a cross-contract call to `MCSToken::set_metadata()` that is not annotated as `#[payable]`.
The pointed `if-else` statements contain redundant instructions for different cases. The first condition checks if `key_index` is equal to zero while the `else` statement should capture every other case. However, both conditions result in the same instructor executing. The only different cases that could happen according to the code is that the node length is either lower than 32 or not.
File: map-cross-chain-service/src/prover.rs:49:27\ It's not necessary to use `clone` on type `[u8; 20]` which implements the `Copy` trait. --- File: map-cross-chain-service/src/prover.rs:80:31\ It's not necessary to use `to_string()` as this value is dropped without further use. --- File: map-cross-chain-service/src/prover.rs:88:54\ It's not necessary to use `clone` on type `[u8; 20]` which implements the `Copy` trait. --- File: map-cross-chain-service/src/lib.rs:385:25\ It's not necessary to use `clone` as this value is dropped without further use. --- File: map-cross-chain-service/src/lib.rs:399:41\ It's not necessary to use `clone` cloned value is neither consumed nor mutated. --- File:map-cross-chain-service/src/lib.rs:403:40\ It's not necessary to use `clone` as this value is dropped without further use. --- File: map-cross-chain-service/src/lib.rs:376:51\ It's not necessary to clone the reference. --- File: map-cross-chain-service/src/lib.rs:429:45\ It's not necessary to clone the reference. --- File: map-cross-chain-service/src/lib.rs:815:42\ It's not necessary to use `clone` as this value is dropped without further use. For more information, we invite the team to check this [clone_on_copy](https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy) as well as [redundant_clone](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone).
The closure usage at the pointed location is redundant.\ For more information, we invite the team to check this [reference](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure)
The pointed functions are used for testing purposes only.
There are typos in the code: - "receit_proof" should be "receipt_proof". - "unkown" should be "unknown". - "is_quorum_default_wight" should be "is_quorum_default_weight". - "Deafult" should be "Default". - "typ" should be "type", given that the name is also a keyword in rust, it can be changed to `msg_type`. - "anther" should be "another" We also found a typo in your [documentation](https://www.maplabs.io/blog/nomad-bridge-hack-explained/), "Certik" should be "CertiK".
The linked error message doesn't contain the requirement which was not satisfied.
A $x >= y + 1$ can be simplified to $x > y$ when both $x$ and $y$ are integers.\ For more information, we invite the team to check this [reference](https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one).
`assert_self` is not required to prevent execution in methods not declared as `pub`. Moreover the following function are called from points in the code where the `assert_self` is already executed. - record_order_id() - remove_order_id()
The pointed `let` binding can be omitted since it is immediately returned. For more information, we invite the team to check this [reference](https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return).
The import statement for `Serializer` on `types/proof.rs` has redundant braces which can be removed.
According to the `near_withdraw` implementation of [w-near](https://github.com/near/core-contracts/blob/a11e6d4c97/w-near/src/w_near.rs#L38) in Near core contracts, the deposited one yoctoNear will be refunded to caller synchronously with the withdraw operation.\ It's unnecessary to minus one yoctoNear from the remaining deposit.
1. The data types of the argument `locker_address` and of the field `address` defined within `LogEntry` are the same. They actually are both arrays of 20 elements of type u8. 2. The data types of the local mutable variable `result` at L98 and of the type alias `Hash` are the same. They actually are both arrays of 32 elements of type u8. In summary, the `into()` function used to do data type conversion operations are not needed.
In Rust, `unwrap` is used to handle the correct return value or `panic!`, so no further error handling mechanism will be adopted.\ Here using `if let` or `match` is more idiomatic.
The highlighted `return` statement is redundant. For more information, we invite the team to check this [reference](https://rust-lang.github.io/rust-clippy/master/index.html#needless_return).
The `eth-types` workspace under folder `mcs/near` is not used in the whole project. In the `MCSToken` contract, the trait `ExtMapCrossChainService` is defined but never used. Using the macros `impl_admin_controlled` to append admin controlled feature, especially the `check_not_paused`, to `MCSToken` contract. However, that feature is not used. The linked private constants can only be accessed within the `MCSToken` contract, however, the usage of those constants cannot be found.
The code contains unused imports.
The function `is_quorum()` isn't public nor used elsewhere in the code. This represents a dead code that can be removed.
It is not recommended to use `unwrap_or` followed by a call to the `default` function since the default value would be generated independently if it is needed or not, allocating unnecessary memory.\ For more information, we invite the team to check this [reference](https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call).
The report names a repository or files. Open the report to see its scope.
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us