Skip to content

Private Transaction Chains

Some users may wish to transact with the protection of the blockchain without publicly sharing the details of the transactions. To provide transaction privacy, we introduce the concept of private transaction chains. With private transaction chains, participants on the public blockchain will be able to transact with each other privately, while maintaining the immutability and auditability of the public blockchain.

Example Use Case

Consider a use case between a non-custodial asset manager (hedge fund), a custodian (bank), and a client (asset owner).

In a non-blockchain approach, the hedge fund might be granted access to the client’s bank account through a secure API to execute trades on the client’s behalf.

In a blockchain approach, each participant would run a node with all transaction data synced between them. However, each party should only have access to the data of their consented counterparty. The hedge fund might want to received notifications upon execution of a successful trade, but they should only be given access to this information when it is from one of their clients. To accomplish privacy at a granular level (by user, user-type, or transaction-type, etc), a trade contract might include methods which are marked "private" which will only allow the public keys that exist in a permissions.json file to gain access to the data within private contract methods.

Solving this problem is only possible when we store partial information on-chain. Each piece of private data will only have its transaction hash broadcast to the network at large. So, trades might be executed out-of-band between two private parties, while the transaction hash of that transaction would be broadcast to the entire network. At any point in the future, either party can search the ledger history for that transaction hash and verify (1) its occurrence, and (2) its payload, which would be reconstructed from the out-of-band data that the party has already exchanged off-chain.

Create New Private Chain

We can begin by creating a new private blockchain.

Each member is identified by an eNode URL, which is how the member node is identified by their peers on the network.

createChain() {

  fetch('http://localhost/bloc/v2.2/chain' {
      method: 'POST',
      body: {
        "args": {
          "addRule": "AUTO_APPROVE",
          "removeRule": "TWO_VOTES_IN",
          "terminateRule": "MAJORITY_RULES"
        },
        "balances": [
          {
            "address": "5815b9975001135697b5739956b9a6c87f1c575c",
            "balance": 20000000000000000000
          },
          {
            "address": "93fdd1d21502c4f87295771253f5b71d897d911c",
            "balance": 999999
          }
        ],
        "label": "my chain",
        "members": [
          {
            "address": "5815b9975001135697b5739956b9a6c87f1c575c",
            "enode": "enode://<public-key>@<ip>:<port>?discport=<port>"
          },
          {
            "address": "93fdd1d21502c4f87295771253f5b71d897d911c",
            "enode": "enode://<public-key>@<ip>:<port>?discport=<port>"
          }
        ],
        "src": "contract Governance { enum Rule { AUTO_APPROVE, TWO_VOTES_IN, MAJORITY_RULES } Rule addRule; Rule removeRule; Rule terminateRule; event MemberAdded (address member, string enode); event MemberRemoved (address member); event ChainTerminated(); struct MemberVotes { address member; uint votes; } MemberVotes[] addVotes; MemberVotes[] removeVotes; uint terminateVotes; function voteToAdd(address m, string e) { MemberAdded(m,e); } function voteToRemove(address m) { MemberRemoved(m); } function voteToTerminate() { terminateVotes++; if (satisfiesRule(terminateRule, terminateVotes)) { ChainTerminated(); } } function satisfiesRule(Rule rule, uint votes) returns (bool) { if (rule == Rule.AUTO_APPROVE) { return true; } else if (rule == Rule.TWO_VOTES_IN) { return votes >= 2; } else { return true; } } }"
      };
  });
const label = 'My chain label';
const members = [
    {
      "address": "5815b9975001135697b5739956b9a6c87f1c575c",
      "enode": "enode://<public-key>@<ip>:<port>?discport=<port>"
    },
    {
      "address": "93fdd1d21502c4f87295771253f5b71d897d911c",
      "enode": "enode://<public-key>@<ip>:<port>?discport=<port>"
    }
];
const balances = [
           { address: "00000000000000000000000000deadbeef"
           , balance: 1000000000000000000000
           },
           { address: "0000000000000000000000000012345678"
           , balance: 0
           }];
const src = "contract Governance { enum Rule { AUTO_APPROVE, TWO_VOTES_IN, MAJORITY_RULES } Rule addRule; Rule removeRule; Rule terminateRule; event MemberAdded (address member, string enode); event MemberRemoved (address member); event ChainTerminated(); struct MemberVotes { address member; uint votes; } MemberVotes[] addVotes; MemberVotes[] removeVotes; uint terminateVotes; function voteToAdd(address m, string e) { MemberAdded(m,e); } function voteToRemove(address m) { MemberRemoved(m); } function voteToTerminate() { terminateVotes++; if (satisfiesRule(terminateRule, terminateVotes)) { ChainTerminated(); } } function satisfiesRule(Rule rule, uint votes) returns (bool) { if (rule == Rule.AUTO_APPROVE) { return true; } else if (rule == Rule.TWO_VOTES_IN) { return votes >= 2; } else { return true; } } }";
const args = {
  addRule:'AUTO_APPROVE',
  removeRule:'TWO_VOTES_IN',
  terminateRule:'MAJORITY_RULES'
  };

yield rest.createChain(
  label, members, balances, src, args
  );

POST chain returns a chain ID to identify our new private blockchain:

{
  "chainId": "02468101214"
}

Governance Contract

You'll notice that we use a governance.sol contract to create a new private chain.

  • Rule: Sets the voting process for a private chain. New members can be added automatically (AutoApprove), democratically (MajorityRules), or semi-democratically (TwoIn).
  • Event: Generates an event in the EVM logs when a new member is added or removed from the private chain.
  • Mapping: Defines a hash table mapping of user addresses to the number of votes cast by each user.
contract Governance {
  enum Rule { AutoApprove, TwoIn, MajorityRules }
  Rule addRule;
  Rule removeRule;

  event MemberAdded (address member, string enode);
  event MemberRemoved (address member);

  mapping (address => uint) addVotes;
  mapping (address => uint) removeVotes;

  function voteToAdd(address m, string e) {
    uint votes = addVotes[m] + 1;
    if (satisfiesRule(addRule, votes)) {
      MemberAdded(m,e);
      addVotes[m] = 0;
    }
    else {
      addVotes[m] = votes;
    } 
  }

  function voteToRemove(address m) {
    uint votes = removeVotes[m] + 1;
    if (satisfiesRule(removeRule, votes)) {
      MemberRemoved(m);
      removeVotes[m] = 0;
    }
    else {
      removeVotes[m] = votes;
    }
  }

  function satisfiesRule(Rule rule, uint votes) private returns (bool) {
    if (rule == Rule.AutoApprove) {
      return true;
    }
    else if (rule == Rule.TwoIn) {
      return votes >= 2;
    } else {
      return false;
    }
  }
}

Get Private Chain Details

To get information about our private chain, we can run GET /chain:

curl -X GET "http://localhost/strato-api/eth/v1.2/chain" 
    -H  "accept: application/json;charset=utf-8"
yield rest.getChainInfo(chainId);

Which returns a list of chain members, member balances, and governance rules:

{
  "balances": [
    {
      "address": "5815b9975001135697b5739956b9a6c87f1c575c",
      "balance": 20000000000000000000
    },
    {
      "address": "93fdd1d21502c4f87295771253f5b71d897d911c",
      "balance": 999999
    }
  ],
  "label": "my chain",
  "members": [
    {
      "address": "5815b9975001135697b5739956b9a6c87f1c575c",
      "enode": "enode://<public-key>@<ip>:<port>?discport=<port>"
    },
    {
      "address": "93fdd1d21502c4f87295771253f5b71d897d911c",
      "enode": "enode://<public-key>@<ip>:<port>?discport=<port>"
    }
}

Next Steps

To initialize your new chain with existing data, visit:

To learn about network governance, visit: