Designing decentralized apps for the Ethereum Blockchain

Applications created to run on the Blockchain are called Dapps (Decentralized Apps). They run in the open (public visibility) and thus any bad intent or execution is visible to everybody.

Dapps are built using Smart Contracts. Very simply put, the Blockchain is like a global computer which will run a smart contract exactly as implemented. Once the smart contract is deployed, and someone calls it, it will certainly be executed. This certainty is what makes developing smart contracts challenging.

In the traditional software development mindset, there is always an option to upgrade and fix bugs. It is not so in the world of smart contracts. A section on upgrading contracts below provides a partial workaround to this. It is important to ensure that a smart contract is ready to be deployed. Some organisations have a bounty program to iron out any flaws, particularly security flaws, on the Ethereum Testnet.

On Ethereum, smart contracts are typically implemented using the Solidity programming language. Snippets of code written in solidity are used in this article to illustrate design considerations.

Let us explore the following aspects that need consideration while designing Dapps:

Decentralization v/s transparency

If a Smart Contract is deployed on the Blockchain, the network of computers which form the Blockchain will execute the Smart Contract. There is no single entity which hosts the computer or computers for executing the Smart Contract. This is the basis for calling such applications as Decentralized Applications. From a technical perspective, it is decentralized.

However, from a commercial, legal and administrative perspective, this is not enough to qualify as “decentralized”.

Owned contracts

An owned contract is so common that it can even be found in the github repository of solidity for reuse. There are many genuine usecases where owned contracts are inevitable.

https://github.com/ethereum/solidity/blob/develop/std/owned.sol

The link above implements an owned contract. Some functions in an owned contract can be invoked only by the owner. A modifier called onlyowner is provided, which typically gets used as follows:

contract SomeUnrealControlledVotingContract is owned {
...
...
    function allowVotingRightsTo(address _add) 
             public onlyowner returns (bool) {
      // some check which limits rival party's
      // loyalists to a certain number
...
...
    }
}

Although the smart contract runs on the so called decentralized blockchain, it is controlled by the owner. In the above example, the owner can influence the voting outcome by allowing only a few voters of the rival party. This is not decentralization in the administrative sense.

This is an extreme example. However, a huge number of smart contracts are deployed that put controls into the hands of a few. Users and businesses must be aware and review the smart contracts in order to avoid lock-ins.

Many Dapps are thus “transparent”, as they run on the Blockchain, but not necessarily “decentralized”.

Incentive / Disincentive

While designing applications for the social scale, it is important to provide an incentive for good behaviour and disincentive for bad behaviour on the blockchain. In the traditional software development community, it is very common to use “reputation points” or “stars” as the incentive / disincentive. This may not be enough for Dapps on the Blockchain and must be understood why.

A typical way to implement a “reputation system” is to begin with some “default” reputation. When a new vendor enters the market, there is no history associated with the vendor. Therefore an average reputation is attributed to this vendor. Over time as the vendor works hard and with honesty, (s)he accrues higher reputation.

Alternatively, the vendor could cheat and the cheated counterparty could reduce the vendor’s reputation points. The vendor might cheat quite a few and in the process possibly accrued lot of crypto-currency. As the vendor’s reputation points fall below a certain threshold, the vendor will stop receiving more business.

At this point, the vendor can easily create a new identity and automatically the vendor’s reputation effectively “increases” to the average new beginner’s reputation!

Relying only on reputation as a deterrent may not work for certain Dapps, as creating new identity is very cheap on the Blockchain.

A similar problem exists for credit rating systems built on the Blockchain.

Either onboarding a new identity must be costly enough or cheating / breaching contracts must prove costly. Appropriate design techniques must be used.

Privacy, anonymity and personas

Storing names, postal addresses and contact numbers on the Blockchain is not a good idea. People with malicious intent will be able to see these things.

Data can be stored in the smart contracts as “state” variables. The state is available to any node on the Blockchain that can run smart contracts. It is synchronized by the consensus mechanism of the Blockchain.

Obviously storing any private information in the state is unpalatable. However, that is not the only place where data is stored or propagated on the Blockchain. Consider the following “potential” sources of privacy breaches on the Ethereum Virtual Machine:

  1. State Variables
  2. Transactions
  3. Function arguments
  4. Emitted Events

While designing smart contracts, make sure that no personally identifiable information is used as function parameters, state variables.

A user must only be identified by an address.

Any message, function argument, or emitted Event that needs extra information about the user must use some form of indirection.

The Ethereum blockchain’s protocol is well publicised. There are many implementations of the Ethereum client in different languages. It is possible for a reasonably good programmer / hacker to dump all the state, transactions and event logs by simply modifying some existing Ethereum client’s code.

Anonymity

To use a Dapp on Ethereum, users need an account. The account has the user’s Ether (crypto-currency). The account’s address is sent as a part of the transaction (msg.sender in Solidity) whenever a smart contract is called. This is visible to everybody.

sidenoteIn Ethereum there are two kinds of accounts: Externally owned accounts (User’s account), Contract account. Even the contract address can have Ether. One may find interesting details here (ethereum accounts).

tx.origin and msg.sender both provide the account address. However, tx.origin can only point to a user’s account (externally owned accounts). When a smart contract calls another smart contract using a transaction, the msg.sender is set to the callee contract’s address. However, the tx.origin remains the User’s address, which originated the call.

Consider developing a marketplace Dapp, where some suppliers and some consumers come together. If all of them use the same address everytime, everybody can eventually know who is transacting with whom and how frequently. Even the business / economic dynamics would be compromised.

Principles used in cryptographic protocols can be helpful here. Basically use different addresses for different interactions. I would not like one of my suppliers to know if I am also dealing with another supplier and how frequently. And therefore I will use different accounts / addresses for dealing with different suppliers. Even the suppliers would not like to publicize how their business is doing.

In the ideal world I would not like anybody unconcerned to know how many deals were executed through my address even with any particular supplier. It is nobody’s business to know such things.

Personas

Many social network Dapps are being attempted by enterprising individuals. The fallout of “centralisation” of user data has been unpleasant in the recent past. There can be many different social network Dapps for different purposes, very much like LinkedIn, Facebook or Twitter. Each of these Dapps would maintain a persona for a user. The user would be willing to share some public information and keep a separate store / channel for private information.

If the same address (account) is used to create different personas on different Dapps, all of them can be linked. Remember the Ethereum client’s code can be modified to dump data by a reasonably good programmer / hacker. This again is a breach of privacy. The reason for creating separate personas on the Blockchain is that they should not be linked. Otherwise just one persona would suffice. It is akin to having a common account for Facebook, LinkedIn and Twitter.

This puts personal security at risk for individuals, especially since their worth is out there in the open.

User education is a must for such Dapps which maintain personas on a social network Dapp.

Security and the adversarial environment

Blockchain applications run in the open, and hence are subject to all sorts of attacks. This is what we call the adversarial environment. The usual attack techniques, be it; social engineering or delivering malicious scripts that steal private keys or passwords, are all applicable in this context. However, there is more that attackers can do in case of Dapps. The attackers on the Blockchain are highly motivated, and unusually skilled, as there is a huge money involved after all!

The attack surface

Ethereum client

Ethereum clients are nodes on the blockchain, which validate transactions, run smart contracts etc. There have been attacks on Ethereum clients in the past. These will continue, and Ethereum clients will continue to evolve and build stronger security. Being aware of security alerts is very important, as it can affect your own “wallets”.

Some links that highlight the nature of security issues:

  1. https://blog.ethereum.org/category/security/
  2. https://paritytech.io/security-alert-2/

Wallets

Just as the ethereum client, even wallets have had security attacks in past. However, a more concerning trend is that Dapp creators want to manage wallets for users.

In a zeal to make the user experience as seamless as possible, some applications allow for login through username/password and maintain the wallets. This from a blockchain developer’s perspective is not decentralized, however, it provides users with a well known way of interacting with the blockchain, and puts the onus of security on the Website developers. To me; this is a big mistake. However, it is very common especially with exchanges and some Dapps. Social engineering, phishing and other forms of attacks are very common in such cases.

The alternative is to put the onus of security on the user. The user maintains wallets and makes sure that its private keys are not compromised. User education is very important in this case.

Metamask provides a browser plugin that addresses the usability issue, and does not store the private keys on their servers.

Dapp source code

Security flaws in the source code are very common. A few best practices need to be followed. The following link is a good resource:

http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html

The price of not following the best practices is high. The DAO hack is explained below with the help of a naive smart contract code. The remediation is also prescribed.

Defense

It is important to keep abreast with security recommendations and known attacks.

However, merely trying to build secure Dapps is not enough. A realization that things can still go wrong will take you a long way, and protect you from huge losses.

Emergency Controls

Typical front office applications in the finance industry use what are called Emergency Controls. These allow disabling functionality or providing manual intervention in case of run-away situations. Similarly developers could provide a way to:

  1. Completely halt the Dapp
  2. Disable a certain functionality
  3. Stop accepting new clients (addresses)

It completely depends upon the nature of the Dapp. However, a well thought through emergency control system will serve as a good defense / recovery mechanism.

Limits and Validations

Attackers will use various techniques to game the system. Limit checks and alerts can help in defending against such attacks.

  1. Apply various limits like:
    1. transaction value
    2. transaction volume
    3. transaction frequency
  2. Alerts through Events

An off the chain system which listens to the events can be designed to detect possible anomalies.

Checks and Balances

A successful attack remains undetected.

A system of checks and balances will help in detecting possible attacks. In accounting, a double entry system was designed to catch possible errors or fraud. Similarly an economic system like the Dapps need a way to verify that nothing wrong has happened.

A periodic, automatic “audit” of the tokens, ether and other functional data will certain help detect possible attacks.

Testing assumptions

Validating technical assumptions is often easier compared to validating assumptions about how people or organisations will behave.

Assumptions are the biggest loopholes in any contract / smart contract, and hence must be listed clearly whenever you start a project. Sometimes behavioural assumptions are hidden in the source code. One such assumption is anyone who is to receive money is willing to receive it.

Let us consider a hypothetical scenario, in which a company has its sales conducted through third parties. A smart contract that awards sales commissions is created. As soon as a deal is struck, some money is transferred by the client, to the company. Instantaneously, a commission fee is paid out to the third-party sales agent.

//
//  BAD CODE!
//  DO NOT USE!
//
function strikeDeal(uint deal_id, uint price)
  public
  ... // modifier used for checking sender's credentials
  ... // modifier used for checking deal state
 {
  // some validation code for price
  // compute seller_co_keeps_value
  // compute commission_value

  // get the address of seller_co
  // get the address of sales_agent
  seller_co.transfer(seller_co_keeps_value);
  sales_agent.transfer(commission_value);
 }

The above code must not be used, it assumes that both seller_co and sales_agent will want to receive money, and not block the deal.

Our hypothetical sales agent has very less at stake compared to the seller, and hence potentially exploitable by a competitor. If the sales_agent is compromised, and decides to block the deal, the agent can simply block this transfer. To block the transfer, the sales agent can just throw an exception from the fallback function of his address.

sidenoteWhenever someone tries to transfer money to an address, the address’s fallback function is invoked. It is important to note, that if an exception occurs while executing a smart contract function, the complete state is rolled back. Only the gas consumed for executing the transaction is used up.

Obviously the withdraw pattern should have been used. In the withdraw pattern, the dues are stored in a state variable in the smart contract. The entity which was supposed to receive money, initiates the transfer. By doing this, the ability to block the “strikeDeal” transaction above is eliminated.

If the sales_agent does not want the money, he may simply leave it, but will not be in a position to block the deal. The sample code below shows how it could be achieved.

//
// BETTER THAN BEFORE
// USE THIS
//
 function strikeDeal(unit deal_id, uint price)
 public
 ... // modifier used for checking sender's credentials
 ... // modifier used for checking deal state
 {
  // some validation code for price
  // compute seller_co_keeps_value
  // compute commission_value

  set_withdrawable_deal_amounts(deal_id, 
                                seller_co,
                                seller_co_keeps_value,
                                commission_value);
  // emit an event so that seller_co can be notified.
 }

function withdrawForDeal(uint deal_id)
 public
 ... // various modifiers
 {
  // various checks
  // get values and addresses for the deal in concern
  // check sender has some withdrawable

  // set withdrawn amount     !!important step!!
  //                          !!explained in the dao hack!!

  if (msg.sender == seller_co) {
   sender.transfer(seller_co_gets_value);
   set_withdrawable_commission(sales_agent,
                               deal_id,
                               commission_value);
   // emit an event so that sales agent can be notified
  }
  else if (msg.sender == sales_agent) {
   sales_agent.transfer(commission_value);
  }
 }

Upgrading contracts

Whenever a newer version of a smart contract is deployed, it gets a different address. The old address can still be used by an attacker or someone who is unaware of the updated smart contract. To avoid usage of the old version, it can perform a selfdestruct. The Ether at old version address can be transferred to the new address. However, all the data stored in it will be lost. This is clearly not acceptable.

Data, Business Logic and the Smart Contract Registry

Separating data from business logic is certainly the most important step in trying to design upgradable smart contracts. One of the ways to separate data from business logic is to create a library. Although the library construct in Solidity provides a good way to “reuse” code, it does not provide a way to “upgrade” to a newer version.

Low level calls like call, delegatecall provide a workaround for this problem. The developer of the smart contract needs to separate the data and business logic as two separate smart contracts. We will keep the data in PrimarySmartContract. This PrimarySmartContract is what the callers use. The BusinessLogicVersion0 is where the business logic would recide. Along with the data, the address of BusinessLogicVersion0 will be stored in PrimarySmartContract. Whenever a new BusinessLogicVersion1 is created, the PrimarySmartContract can be updated with the new address.

When the PrimarySmartContract is invoked, it delegates the call to BusinessLogicVersion1 by looking up the current version address in its state variables.

This very simple mechanism can be effective only if BusinessLogicVersions are data compatible. In short this workaround does not work if you want to upgrade the data storage as well.

Complex upgrading mechanisms

In order to make things absolutely upgradeable, the smart contract design is sometimes made complex. Solutions are proposed to include a contract “registry” as a separate smart contract, and getting it to route the call (delegatecall).

Having more smart contract interactions is fraught with security risk. While writing secure Dapps, simplicity and reuse are your best bets. A sensible approach must be used, and unnecessary complexity must be avoided where it is not needed.

The DAO hack

The DAO hack led to a hard fork on ethereum, and most importantly made it evident that Ethereum is not truely decentralized. The attack was of the nature of a recursive call attack.

Basically a smart contract which invokes any other smart contract hands over the control to the other contract. The other contract could call the previous contract again. Lets look at the following code:

contract TheNaiveContract {
  // a good smart contract developer uses the
  // withdraw pattern.
  function withdrawBalance() public payable {
    // get the user's balance amount
    amount = userBalance[msg.sender];
    // try to send it
    if (!(msg.sender.call.value(amount)())) { throw; }
    // successful send hence set the balance to 0
    userBalance[msg.sender] = 0;
  }
}

This innocent code is extremely vulnerable. The msg.sender.call.value(amount)() causes a fallback function to be invoked in the sender’s smart contract. An attacker makes use of this and calls withdrawBalance again in the fallback function. The fallback function can easily be implemented as:

function () {
  ...
  TheNaiveContract naiveGuy;
  // if (already_called_multiple_times_leave_some_change)
  //   return
  naiveGuy.withdrawBalance();
}

This is a recursive call to TheNaiveContract. The fallback function implemented by the attacker will invoke TheNaiveContract which tries to send money to the caller (msg.sender.call.value(amount)()). The second call still sees the same amount due, and tries to send the amount again. This has the potential to empty the TheNaiveContract’s Ether.

A simple remedy is to immediately set the userBalance to ‘0’ and then try to transfer the Ether. This is called the Checks-Effects-Interactions pattern.

The remedy:

contract TheNotSoNaiveContract {
  function withdrawBalance() {
    amount = userBalance[msg.sender];
    userBalance[msg.sender] = 0; // recursive call will only get 0
    if (!(msg.sender.call.value(amount)())) { throw; }
  }
}

Conclusion

Designing decentralized apps is fun, however, it has many different considerations compared to the usual software design. A sincere appreciation for the fact that your apps will be running in an adversarial environment, will humble anybody as there is a “lot” at stake, sometimes even millions of dollars worth value.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s