Blockchain Integration in CoreExchange

MultiSig Wallets In CoreExchange  (BTC, LTC, DOGE)

CoreExchange uses multi-signature transactions for all cryptocurrencies that support them, using hierarchical deterministic (HD) keys. Multi-signature transactions in the CoreExchange system are designed to use a 2 of 3 signature approach (requiring at least two valid signatures to claim a transaction output).

  • Two HD keys can be generated by the CoreExchange system (on separate machines, each having access to one master key exclusively). One of these services provides the signatures created with the customer HD key, the other one provides the signatures created with the CoreExchange HD key.
  • One HD key can only be generated externally. That master key is not known/stored anywhere in the CoreExchange system. It is only available to an escrow provider (external independent legal identity) which in case of a dispute between CoreExchange and the customer would be able to provide the second required signature for claiming transaction outputs either on behalf of the customer or CoreExchange.
  • This makes sure that neither CoreExchange alone nor the customer alone can claim transaction outputs without either the consent of each other (the usual case – an authenticated user triggering a transaction in the CoreExchange system) or the approval of the escrow provider (either CoreExchange or the customer proving to the escrow provider that the transaction is legally valid without the consent of the other party).
  • (Derived) keys are never made available on machines running cryptocurrency nodes (which interact with the blockchain network). Instead, transactions are prepared by one software service and transmitted inside the internal network to two signing services which add the required signatures. The final 2-of-3 multi-signature transaction is then submitted to one cryptocoin daemon running in the CoreExchange system that is connected to the blockchain network.

The CoreExchange system requires to run...

1. At least 2 full nodes for each supported cryptocurrency, connected to the corresponding blockchain networks.

Access to their RPC interfaces is restricted on the IP level so that only services on machines in the internal (private) network can connect. These nodes make sure that CoreExchange’s view of the blockchain is in sync with the view of all other nodes in the blockchain network that are currently known and reachable.

This service is notified from the coin daemons (the full nodes) about ongoing transactions in the blockchain network. All incoming transactions are monitored, and those affecting addresses which belong to the CoreExchange system are filtered and processed (new unspent outputs are credited to the corresponding accounts, spent transaction outputs debited from them). This way all transactions originating outside of CoreExchange affecting addresses belonging to accounts inside CoreExchange are processed.

The service also provides an internal software API for triggering the creation of multi-signature transactions reflecting transfers originating inside the CoreExchange system (customer to customer trading; customer purchases of crypto coins from CoreExchange; etc). This API is used by the wallet service which does the bookkeeping of all CoreExchange accounts. The wallet service itself runs on a separate machine in the internal network.

The usual way a transaction originating inside the CoreExchange system is created looks like this:

  • the wallet service triggers the creation of a transaction by specifying the sender, amount and recipient
  • the transaction service creates an unsigned raw transaction by collecting the appropriate unspent outputs
  • the raw transaction is sent to the signing services which add their signatures
  • the signed raw transaction is submitted to one of the full nodes

2. Two separate signing services, running on different machines, for signing

multi-signature transactions with the appropriate HD keys

3. One machine running the wallet service

4. (Optional) A blockchain explorer service for each supported cryptocurrency.

All services are run on hardened Linux systems and protected by load balancer and both network firewall equipment as well as additional iptables firewall rules on each machine.

Ethereum Smart Contracts in CoreExchange

CoreExchange currently uses two different kinds of Ethereum smart contracts:

  1. A simple “proxy” contract is deployed per customer (on demand).
    Its purpose is mainly to simplify the detection of incoming and outgoing transactions by means of transaction events.
  2. One “multi-sig” contract ist deployed for system-wide use.

The Contract Owner Account

The CoreExchange system currently uses one dedicated Ethereum account that creates and owns all smart contracts. You need to make sure that this account always has sufficient balance to

  • Deploy contracts for customers on demand (SimpleProxy contracts)
  • Provide enough gas for invoking smart contract methods
  • This contract is not used for regular Ether transfers but only for doing contract calls

The SimpleProxy Smart Contract

The main purpose of this smart contract is to simplify the detection of incoming and outgoing Ethereum transactions to/from customer Ethereum accounts. Regular Ethereum accounts/addresses do not support an easy detection of those due to the account and balance model of the Ethereum blockchain.

// SimpleProxy.sol
/**
 * A simple proxy contract.
 *
 * The creator is set in the constructor, and only the creator can issue a payout.
 * Incoming payments emit a Funding event.
 */
contract SimpleProxy {
    address creator;
    event Funding(address indexed sender, uint indexed value);
    function SimpleProxy() public {
        creator = msg.sender;
    }
    function () public payable {
        Funding(msg.sender, msg.value);
    }
    function payout (address destination, uint value) public {
        require(msg.sender == creator);
        destination.transfer(value);
    }
}

Incoming transactions on a SimpleProxy contract result in a Funding event which is used to detect the incoming payment sender and amount during block/transaction processing.

Blocks/transactions are also scanned for contract call data with a payload matching the payout contract call for detecting outgoing payments.

An incoming payment on such a contract is credited to the internal CoreExchange wallet account that is linked to it by a database relation. This happens during block/transaction processing.

An outgoing transaction can only be made by a successful invocation of the payout method, which requires the credentials of the system contract owner account. The data payload (input) of the contract call is used to identify such invocations.

For both kinds of transaction, the block depth is used to update the internal confirmation count up to a configurable threshold (currently 12), after which the transaction is considered complete.

The SimpleMultiSig Smart contract

One multi-sig contract is used in CoreExchange which is primarily meant to enhance the security of Ether deposited in CoreExchange.

A batch job in the CoreExchange system monitors the balances of the SimpleProxy contracts (deployed per customer account) and may transfer the Ether deposited on those to the SimpleMultiSig contract if they exceed a configurable threshold.

The SimpleMultiSig execution function responsible for triggering an outgoing payment requires

  • The contract calling account to be the CoreExchange contract owner account
  • Two separate pre-computed signatures (provided by the two distinct signing services)


The signature scheme (and the way the signers’ addresses are recovered from the signatures) follow the ERC191 Signed Data standard. (The requirement that the caller must be the owner of the contract was added to mitigate possible replay attacks).

As a consequence, a successful transfer of Ether from the SimpleMultiSig contract requires three separate credentials

  • The credentials of the contract owner account
  • The credentials of each of its (two) signer accounts
// SimpleMultiSig.sol
pragma solidity ^0.4.18;
contract SimpleMultiSig {
 uint public nonce;                  // (only) mutable state
 mapping (address => bool) isOwner;  // immutable state
 address[3] public ownersArr;        // immutable state
 address creator;                    // immutable state
 // Note that owners must be strictly increasing
 function SimpleMultiSig(address[3] owners_) public {
   address lastAdd = address(0);
   for (uint i=0; i<3; i++) {
     require(owners_[i] > lastAdd);
     isOwner[owners_[i]] = true;
     lastAdd = owners_[i];
   }
   ownersArr = owners_;
   creator = msg.sender;
 }
// Note that addresses recovered from signatures must be strictly increasing
 function execute(uint8[2] sigV, bytes32[2] sigR, bytes32[2] sigS, 
                  address destination, uint value) public {
  require(msg.sender == creator);
   // Follows ERC191 signature scheme: 
   // https://github.com/ethereum/EIPs/issues/191
   bytes32 txHash = keccak256(byte(0x19), byte(0), this, destination, 
                              value, '', nonce);
   address lastAdd = address(0); // cannot have address(0) as an owner
   for (uint i = 0; i < 2; i++) {
       address recovered = ecrecover(txHash, sigV[i], sigR[i], sigS[i]);
       require(recovered > lastAdd);
       require(isOwner[recovered]);
       lastAdd = recovered;
   }
   // If we make it here all signatures are accounted for
   nonce = nonce + 1;
   destination.transfer(value);
 }
 function () payable public {}
}

Configuration Concerns

The configuration value for the threshold of what is deemed a “large” amount of Ether can be used to change the behavior for outgoing Ether transactions.

Outgoing payments below the threshold will result in a SimpleProxy smart contract call (if an account with sufficient balance is present), with a fallback to a SimpleMultiSig smart contract call (if an account with sufficient balance cannot be found).

Setting the threshold to a very high value will result in all outgoing payments being made by means of SimpleMultiSig smart contract calls.

Rationale

Ideally, some kind of multi-sig contract would be used for all transactions. The well-known multi-sig contract implementations mainly focus multi-owned contracts, and have the following drawbacks (simplified here for sake of brevity to the 2-of-3 multi-sig case):

  • You need to keep sufficient balance for providing transaction gas on all accounts that participate in a multi-sig transaction - in the case of exchange using HD accounts for its customers this requires balance on at least 2 accounts per customer
  • You need to deploy a multi-sig contract per customer, which is rather expensive (typically around 0.04 ETH per account)
  • The gas cost for outgoing transactions is relatively high (around 0.004 ETH per transaction)

The implementation we have chosen has the following advantages:

  • Only one multi-sig contract is used throughout the exchange
  • Deploying a SimpleProxy contract has a cost of around 0.0036 ETH per customer
  • Outgoing payments from a SimpleProxy contract have a gas cost of around 0.00003 ETH per transaction

Deployment Considerations

CoreExchange requires the parity daemon for interacting with the Ethereum blockchain.

The implementation ensures that the parity daemon never has access to any account secret key (this includes the contract owner account and all signer accounts).