Skip to content

Developing Confidential Smart Contracts

Developing Confidential Smart Contracts

You can develop a confidential smart contract in much the same way as you would develop a non-confidential smart contract, with a few important differences. This doc describes how to develop and deploy confidential smart contracts.

Defining Confidential State

When deployed confidentially, all of the contract's internal state remains encrypted for its lifetime, being decryptable only by the smart contract when running in a secure environment.

How do I define Confidential State?

In Solidity, state can be marked private using the private modifier. For example, consider the following statement:

bool private mySecretFlag;

Given such a definition, any attempt to access this value through a transaction will fail, ensuring the value cannot be viewed by anyone.

Note

In Ethereum and other platforms without confidentiality, the private modifier states that the value should not be accessible through transactions. This does not provide confidentiality. The value of the field is still stored in plaintext on the blockchain, allowing any node/worker to easily read it.

In Rust, all data is private unless made available through a getter method. For example:

// The initial value of myVariable is publicly viewable in the bytecode
// However if the contract modifies its value, those values are kept private
static myVariable: H256 = H256([2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);

// The value is encrypted into storage
fn write_value(owner: &Address) -> U256 {
    owasm_ethereum::write(key, value);
}

Releasing Confidential State

If you want to release confidential state from your smart contract to external users there are several ways to do so.

Approach 1: Public fields

If your smart contract wants to allow anybody to read confidential state (over a secure channel), you can use the public or external modifier on the field. For example, consider the following statement:

bool public myPublicFlag;

Given such a definition, any user can access this field by calling the implicit getter method inserted by the Solidity compiler.

Warning

Solidity allows fields without an explicit visibility modifier. Such fields are treated as public and will have public getter methods automatically generated for them. You must always explicitly add the private modifier to fields that should be secret.

Approach 2: Public methods

A more flexible method for releasing confidential information is through the use of smart contract methods.

As mentioned earlier, the return value of transactions can be decrypted (only) by the caller. This allows smart contracts to release confidential state by returning the data through a public method. Such methods can include validation checks to ensure the information is revealed only to authorized users or under specific conditions.

For example, consider the following code from the secret ballot contract:

mapping (bytes32 => uint256) private votesReceived;
bool public votingEnded;

function totalVotesFor(bytes32 candidate) view public returns (uint16) {
  require(validCandidate(candidate));  // Abort the call if not in candidate list
  require(votingEnded);  // Abort the call if voting has not ended.
  return votesReceived[candidate];
}

Here, the field votesReceived is marked private so it cannot be accessed directly. Instead, the only way to access this value is by calling the totalVotesFor method. The require(votingEnded) statement ensures this method will only reveal the votesReceived data after the election has finished (i.e. when votingEnded = true). If a user tries to call this method before voting has ended, the call returns an error and the caller learns nothing about the secret data.

Access control patterns for private data

The totalVotesFor method above is an example of access control, which in this context means selectively releasing secret information to authorized parties under specific conditions.

Here we provide some simple design patterns for access control in confidential smart contracts.

State-based access control

contract SecretBallot {

  mapping (bytes32 => uint16) private votesReceived;
  bool public votingEnded;
  address public  ballotCreator;

  /// saves the msg.sender as ballotCreator into storage upon deploying
  /// the contract.
  constructor(bytes32[] _candidateNames) public {
    ballotCreator = msg.sender;
    candidateNames = _candidateNames;
  }

  /// ends the voting period. Can only be called by the creator of the ballot.
  function endVoting() public returns (bool) {
    require(msg.sender == ballotCreator);
    votingEnded = true;
    return true;
  }

  /// returns the number of votes issued for the given candidate, if
  /// and only if the voting period has ended.
  function totalVotesFor(bytes32 candidate) view external returns (uint16) {
    require(validCandidate(candidate));
    require(votingEnded);
    return votesReceived[candidate];
  }

  // ... (code omitted for space)
}

This is the pattern used in the previous section. Only the creator of the ballot can set the end of the voting period, and so this smart contract only reveals the results of the SecretBallot's votesReceived data when the ballot is done collecting results.

Event-based access control

Another way of doing access control is by using events. Suppose we wanted to change the totalVotesFor method so that only the ballotCreator can read the votes at any time. Then we can change our code to:

contract SecretBallot {

  event TotalVotesReceived(uint16);

  mapping (bytes32 => uint16) private votesReceived;
  address public  ballotCreator;

  constructor(bytes32[] _candidateNames) public {
    ballotCreator = msg.sender;
    candidateNames = _candidateNames;
  }

  /// Emits a TotalVotesReceived event if and only if the transaction comes
  /// from the ballot creator.
  function totalVotesFor(bytes32 candidate) view external returns (uint16) {
    require(msg.sender == ballotCreator);
    emit TotalVotesReceived(votesReceived[candidate]);
  }

  // ... (code omitted for space)
}

Because events can only be decrypted by the user who caused them to be generated, this code ensures the totalVotesFor method will reveal information only to ballotCreator. If anyone else tries to call this method, the event will not be emitted.

Caller-based access control (experimental)

Warning

Caller-based based accesss control is an experimental feature currently in progress (see below for explanation). Do not use this approach to protect sensitive data at this time.

A more ergonomic form of access control can be done with msg.sender, which contains the address of the user that issued the transaction.

contract SecretBallot {

  mapping (bytes32 => uint16) private votesReceived;
  bool public votingEnded;
  address public  ballotCreator;

  constructor(bytes32[] _candidateNames) public {
    ballotCreator = msg.sender;
    candidateNames = _candidateNames;
  }

  /// returns the number of votes issued for the given candidate if and only if
  /// the method is called by the ballotCreator.
  function totalVotesFor(bytes32 candidate) view external returns (uint16) {
    require(msg.sender == ballotCreator);
    return votesReceived[candidate];
  }
}

Notice how the votesReceived data is returned if and only if the sender of the transaction is the ballotCreator.

Why this is an experimental feature.

In standard Web3, transactions executed via eth_call do not authenticate msg.sender (note that transactions executed via eth_sendTransaction are authenticated because they are signed by the user's wallet).

This is not an issue for non-confidential platforms like Ethereum, since eth_call supports only read-only transactions that do not transfer value or modify state, hence there are no consequences of a spoofed caller.

This is problematic, however, in the context of confidential smart contracts because if msg.sender is spoofed, the code above might release secret information to someone who is not authorized to view it.

Our Web3c API offers a confidential analogue of eth_call named confidential_call_enc for issuing read-only transactions on confidential smart contracts. Currently confidential_call_enc performs end-to-end encryption but does not authenticate the caller (in order to preserve compatibility with eth_call semantics, confidential_call_enc transactions are not signed by the user's wallet). For this reason you should consider caller-based access control an experimental feature until we add a mechanism to authenticate callers in confidential_call_enc.

Note the first two access control methods are still secure even with this limitation. In the first case, a read-only transaction to totalVotesFor will return nothing if votingEnded = false, and only the ballot creator can modify this flag through a signed transaction call to endVoting.

In the second example, the emit statement will have no effect for read-only transactions, since only signed transactions can generate events.

Function modifiers

We can achieve the same behavior using Solidity's function modifiers, which were created precisely for this type of access control. Take, for example, the previous msg.sender based access control. The example becomes:

contract SecretBallot {

  mapping (bytes32 => uint16) private votesReceived;
  bool public votingEnded;
  address public  ballotCreator;

  constructor(bytes32[] _candidateNames) public {
    ballotCreator = msg.sender;
    candidateNames = _candidateNames;
  }

  modifier onlyByBallotCreator() {
    require(msg.sender == ballotCreator);
    // "_;" will be replaced by the actual function body when the modifier is used.
    _;
  }

  /// returns the number of votes issued for the given candidate if and only if
  /// the method is called by the ballotCreator.
  function totalVotesFor(bytes32 candidate) view external onlyByBallotCreator returns (uint16) {
    return votesReceived[candidate];
  }
}

Upcoming Features

The following features are not yet available but will be supported in a future release.

  • Cross-contract calls: This release of confidential contracts does not expose an interface for making calls between confidential contracts, or to non-confidential contracts. Since each contract runs in an environment protected by its own key, cross contract calls involve an additional interface for providing mutual authentication between two secured environments. We are working on adding this support.

  • Access control in read-only calls: As mentioned earlier, this release of confidential smart contracts does not authenticate the sender field in confidential_call_enc (our confidential version of the web3c RPC eth_call), which means you cannot do access control on private data inside your contract for these read-only calls. We are working on supporting this design pattern in future versions. There are many other ways to do access control for private data in this release. For more information on design patterns to protect your smart contract data, see the Access Control section above.

Next steps

To learn how to interact with confidential contracts with a client, see the section on web3c.js.

To see an end-to-end example of a confidential smart contract, check out our tutorial on building a secret ballot DApp.