andrarchy cross-posted this post in Koinos last year


[koinos proposals] Improve security in koinos

in #koinoslast year (edited)

Hello everyone, here is a proposal to add a new system call in the core of koinos blockchain called get_contract_metadata. This system call is needed to improve the security of the tokens and NFTs.

Before digging into the details, let's take a look to Ethereum, how they handle the authorizations, and then let's come back to koinos and see the differences.

Ethereum

Let's say you call the contrat A. This contract calls the contract B. This one the contract C. And this final contract tries to make a transfer of tokens from your account to another address. In summary is A -> B -> C -> transfer wEth. What is the process to authorize this transaction? In ethereum there are 2 addresses:

  • tx.origin: This is the external owned account (EOA), that is, the user that signs the transaction.
  • msg.sender: This is the address that triggers the transfer. It could be a contract account or again the tx.origin. In our example it is the Contract C, the address that triggered the transfer.

The ERC20 token contract only allows to move tokens from the msg.sender account when using the transfer function. That is, the transfer is done only if you make the transfer directly. But if you are tx.origin and the contract in the middle is msg.sender then your funds are safe because tx.origin is not involved in this process.

But then, how the contract C can actually move the tokens from the user? the answer is the transferFrom function. In order to add more options to make transfers in ethereum, they introduced "allowances". So first, you have to allow to a specific contract to spend your tokens. Then this contract can call the transferFrom function to actually move the tokens.

This allowance has to be set in a previous transaction.

Koinos

Koinos introduced a new model for authorisations. When a transfer of koin is called, the contract does the following process:

If the caller of the koin contract is the owner of the tokens then the transaction is accepted. This is more or less similar to the msg.sender of ethereum, but there is a difference: The caller of koinos is just for contracts, not external accounts. This means that if the user signs a transaction to call the koin contract directly, he is not considered as caller. The caller is empty in this case.

But then how it works? When the caller is not authorized, the koin contract calls the checkAuthority(from) system call, where from is the account that owns the tokens.

This system call works in this way:

  • If from is a smart contract, then the chain will call it to ask if it authorizes the transfer. Then the transfer is accepted only if this contract replies YES. The advantage is that this contract could contain any logic to determine if the transaction is accepted or not.
  • If from is not a smart contract, then the chain will check if the transaction was signed by from. So, this is like the equivalent of tx.origin in ethereum.

This last point has a risk, because this means that a contract in the middle can request to make a transfer and it will be accepted because the user signed the transaction in the first place.

Please note that in ethereum, the tx.origin is not involved in the transaction to not affect his assets. However, in koinos it is taken into account and this can put in risk the assets of the user. Here is a summary of the different scenarios:

ActionEthereumKoinos
User calls the token contract directlyUser is msg.sender. Transfer acceptedUser is not caller. User signed the transaction. Transfer accepted
User has a contract and this one calls the token contractUser is msg.sender. Transfer acceptedUser is caller. Transfer accepted
User calls contract C and this one calls the token contractUser is not msg.sender. Transfer accepted only if the user defined an allowance for C in a previous txUser is not caller. User signed transaction. Transfer accepted ⚠️
User has a contract, calls contract C and this one calls the token contractUser is not msg.sender. Transfer accepted only if the user defined an allowance for C in a previous txUser is not caller. Transfer accepted only if the contract of the user allows the operation.

How to improve the security in koinos?

The principal problem is the involvement of the tx.origin in the authorization. In other words, the problem is that the signature of the user is accepted without taking into account who is the caller.
The solution is to do something similar to Ethereum (defining allowances), and at the same time trying to keep the good parts of the authorization model of koinos. So the proposal for the token contracts is this one:

// transfer from A to B

if (caller == A) {
  // equivalent of msg.sender
  isAuthorized = true;
} else if (caller has allowance) {
  // equivalent of transferFrom
  isAuthorized = true;
} else if (A has a contract) {
  // novel authority function of koinos.
  // no equivalent in ethereum
  isAuthorized = ask_contract_A();
} else if (no caller && tx signer == A) {
  // equivalent of msg.sender
  isAuthorized = true;
} else {
  isAuthorized = false;
}

This logic can not be implemented with the current tools in koinos because the checkAuthority function is the unique one that can check if the user has a contract, but its problem is that it doesn't take into account the caller when checking the signature.

Koinos proposal - Part 1 - get_contract_metadata

Create a new system call to get the contract metadata of a specific address. Then it can be used to know if an account has a smart contract and or not.

Roamin already created the code for this change. You can check it in this pull request: https://github.com/koinos/koinos-contracts-as/pull/100

Koinos proposal - Part 2 - update token contracts

Once the first part is done, then we can rewrite the token contracts in order to introduce the new authorization logic described above.

As you can notice, the checkAuthority system call is not used anymore. Instead of that, we define an internal function in the contract to handle this authorization, and it calls the new get_contract_metadata system call to know if the user has a contract.

I already created a token contract for this change. You can check the implementation here https://github.com/joticajulian/koinos-contracts-as/blob/main/contracts/token/assembly/Token.ts

It still needs the adaptation of the new system call, but it already has the allowances. Koin DX is already using this token contract as reference.

The idea is to use it as standard, and update the KOIN and VHP contracts using this logic.

Polls to vote

These proposals can be voted here:

Edit: I forgot that the authorize function can only be called in kernel mode, and not by normal contracts. This means that the part where the token contract calls the authorize function of the user is not applicable. Then it is better to update the check authority system call and use it in the token contracts. The update consist in protect the user by allowing the signature ONLY if the caller is empty.

With regarding to the get contract metadata system call it is not useful anymore for the token contracts. However, it can be useful for other use cases.