Proposal for a backup RTGS using blockchain technology part 2: hiding transaction data
Last October I wrote about a blockchain Real-time Gross Settlement (RTGS) and now I am ready to write some more about what I have been researching to improve the proposal. One of the main problems of the first version was the publicity of all transactions between banks because the smart contract hid the balances, but exposed everything else.
As the balances are not verified by the contract logic, the transaction data does not need to be stored in clear text. So a new version of the BlockchainRTGS smart contract was developed to store only encrypted transaction data. All decrypting and data processing is done offline.
The transaction cryptography uses symmetric and asymmetric cryptography. A unique symmetric key is generated by the Central Bank for each pair of banks participating in the network. For example, if there are n banks participating in the blockchain RTGS system, (n.(n-1))/2 symmetric keys must be used.
The contract stores each banks´s certificate and the symmetric keys asymmetrically encrypted twice: One using bank A´s public key and the another using bank B´s. So each can decrypt its transaction key with its private key and read all transactions that it is part of.
All pretty basic and functional, but two things bothered me: the forward secrecy absence and the offline overdraft protection. The first drawback means that, as every network participant has access to a lot of historical encrypted data, they can read all past transactions in case of key leaks by mismanagement, hacks or cryptanalysis. The second problem derives from the impossibility for the smart contract to automatically block transactions from senders that do not have enough money to honor them. That´s why a transaction log and Central Bank transaction blocking system was implemented on the first post, but here we are again relying on a central party. As explained in the original post, the RTGS system still operates relying on honest behavior incentive. Not the best solution, but, at least, it is an an option to total financial system freeze.
JPMorgan Chase Quorum: A new hope
When I learned about JPMorgan´s Quorum I got pretty excited that it would solve this problem. Maybe the recipient bank could even be sure that the sender had enough balance without the need of a Central Bank node present on the network. (Spoiler alert: AFAIK, for now it can´t.)
Quorum is an Ethereum fork that implements transaction and contract privacy in a distributed ledger. It adds two main features to the Ethereum protocol: a new consensus model based on voting mechanism (QuorumChain) and a new peer-to-peer private message exchange (Constellation).
I used the provided Vagrant example (7 nodes) to develop the prototype of the BlockchainRTGS running on JPMorgan Quorum: the 5nodesRTGS. This environment starts 5 instances of quorum nodes and provides scripts to get everything up and running.
Working with Quorum is very similar to working with with Ethereum, are some minor changes and additional commands in the command line. The first thing I had to understand was how private transactions work.
Different from Ethereum, Quorum can use Constellation to send messages directly to defined recipients in the parameter “privateFor” represented as public keys, creating encrypted private transactions. If a private transaction creates a contract, it is created as a private contract. Private transactions and contracts obviously are not part of the consensus process as the data stored on different nodes are simply not the same. Quorum creates a separated Merkle Patricia trie to store the private contracts states.
Getting everything running
The test environment is available as a new Quorum example at this github fork. The README file explains how to get the network up and running. The two smart contracts developed are available in the contracts.sol file: bankContract and regulatorTransactionList. bankContract is deployed privately and stores the sensitive data and regulatorTransactionList is deployed as a public contract and provides the public transaction log and bank data.
Instead of sharing a unique smart contract that stores all banks information as the previous BlockchainRTGS, it is mandatory to create a private contract instance for each participating bank on all nodes when using Quorum. In my test, the chosen nodes are:
- Node 1: Bank 1;
- Node 2: Bank 2;
- Node 3: Bank 3;
- Node 4: Regulator;
- Node 5: Observer.
After contract deployment detailed in the README, the regulator node can populate the contracts with private data. For example: The balance variable (initially zero) of bank A was set using a message created by the regulator node destined (privateFor) only to banks 1´s node. After this message, the balance variable (and the contract state, obviously) in the same smart contract address has different states between nodes: Bank 1´s contract on bank 2´s node still has balance zero. That´s why private contracts cannot be part of the consensus rule.
Let me illustrate with some output after initial deployment:
Output from Node 1 (bank 1):
Output from Node 2 (bank 2):
Output from Node 3 (bank 3):
Output from Node 4 (regulator):
The private consensus absence
I must confess: I took a while to change my blockchain trained brain to deal with different states of the same contract. Countless times I coded a smart contract logic just to remember that I could not use the private data on the wrong node. I finally realized that I still need a Regulator node to act as an accountant to the participants and to stop any transaction that would cause an overdraft. This unfortunately is a big problem to my totally decentralized Quorum RTGS goal.
Nevertheless, I kept on programming. Quorum could still be a step forward to the last version where the encrypted data was available to all nodes. (Spoiler alert: It can!)
Enter the bug parade
I slowly realized that I needed support variables to inform the smart contract logic about where this specific instance of the smart contract is hosted. Without them, it was impossible to take correct actions.
My mistake was to think think about inter-contract interactions as inter-node interactions. In my initial reasoning, a local smart contract was invoking another bank´s contract in the bank´s node, but in reality, every smart contract is processed locally invoking local instances of other banks contracts as in all Ethereum nodes. The only node where all data is available is the regulator node. I really struggled to get the grip of this kind of logic inverse to blockchain´s full data availability and transparency, but finally achieved a working version it after a lot of awful crazy bugs.
That said, the final smart contract has support variables that guide the processing logic like isRegulatorNode and thisBankContract that are privately set by the Central Bank node. These variables store information about the node this contract is hosted in like if it is in the Central Bank node and the local bank node contract address, respectively.
The last problem was how to create an unique transaction ID when there was no global data store. So I added a public support smart contract to store global information, but public contracts can only be read by private contracts, not written. This is makes sense as a private update on a public contract would break consensus as private contracts can behave differently on different nodes.
Long story short: I blatantly copied the public blockchains solution: The transaction ID is a hash derived from the transaction data. Still, two transactions with the same sender, recipient and value would generate the same hash. To add some randomness to the equation, I included the block timestamp to the hash calculation. This did not solve the problem completely because a fast stream of equal transactions included in the same block produce the same hash. I tried to add msg.data to the hash input with the same result because the transaction nonce is not included in this global variable. Without any better solution, I added an offchain generated random parameter to the sendValue function that is used in the hash calculation. If it is really random, transaction IDs collision should be REALLY improbable.
As explained, the private bankContract can´t update the public regulatorTransactionList contract, so this must be done by the sender. Transactions not present in the public transaction log cannot be confirmed, even by the regulator. During the development of this part public-private interaction, I bumped into a Quorum private-public contract interaction bug, but the very helpful Quorum community pointed to a quick workaround in no time.
Transaction confirmation was kept mainly unchanged from the last post, the offline balance is achieved by the regulator node inspecting every transaction and blocking the overdraft ones during the confirmation time, shown in the figure below.
Using the public transaction log, I am sure that the regulator can see all the transactions and, if it was not blocked after the confirmation time (10 minutes in this example), one of two scenarios had taken place:
1- The sender has enough funds.
2- The sender does not have enough funds AND the regulator is offline.
On either case, the recipient can accept the funds because they follow the rules of the blockchain RTGS. If the regulator cannot tolerate any overdraft transaction, he would have disabled the bank´s self-confirmation function, but in this case there is no point in using a distributed blockchain solution after all. If it is enabled, the regulator opted for resiliency instead of strict compliance.
I believe that this prototype is a small step towards the completely distributed blockchain RTGS. Using the privacy features of JPMorgan Quorum, the system does not rely anymore in offline cryptography of transaction and balance data of the smart contract, so no more encrypted private data floating around. All data stored in Quorum private smart contracts is obviously private between whoever received the private transaction. The drawback is the lost of consensus about the contract state, so no automatic positive balance enforcement was achieved, Quorum blockchain RTGS still relies on an active regulator node to block overdraft transactions as before. It is not the perfect solution I want yet, but a better solution than before nevertheless.
Stop the presses!
As I was writing this article, great news have been published: Quorum will support zero-knowledge proofs! Maybe this witchery…, er, technology is the key to my problem. It makes a lot of sense: I need to prove that a bank has enough funds but I cannot show how much money it has.
Well, I can only say there is at least one guy that is eagerly waiting for this update.