There are two contexts for talking about confidentiality on the blockchain. When it comes to public and permissionless blockchains, there are projects like Monero and Zcash where privacy is the outcome of anonymous transactions. Transactions in Ethereum, the biggest smart contract blockchain, can be traced and transaction payload is readable.
The openness of Ethereum is excellent from the ideological standpoint, attracts many developers and allows for implementing mechanisms (ICOs, escrows, all sorts of gambling games, collectibles, etc.) in a transparent manner that wasn’t possible before. The flip side is that Ethereum doesn’t work for many businesses that require a high level of confidentiality or have to comply with data privacy regulations.
Private and permission blockchains like Hyperledger Fabric try to address these business requirements allowing the organization to manage who can participate in the network and what data they can access.
Hyperledger Fabric
Hyperledger Fabric is a modular framework for building hierarchical and permission blockchain networks capable of running the chaincode. The chaincode in Hyperledger Fabric is an installed and initialized program that runs on the blockchain, an equivalent of a smart contract in Ethereum.
Applications running on Hyperledger Fabric are upgradeable and since version 1.2 they can also save and read from the private data storage. The other feature that allows programmers to secure data is an application-level solution in the form of attribute-based access control (ABAC). Private data and ABAC together give enough flexibility to model a non-trivial business process without revealing confidential information.
Ledger in Hyperledger Fabric consists of the current world state (database) and transaction log (blockchain). Assets are represented by a collection of key-value pairs. Changes are recorded as transactions on a channel ledger. Assets can be in binary or JSON format. World state is maintained so reading data doesn’t involve traversing the entire blockchain. Each peer can recreate the world state from the transaction log.
Chaincode
To understand how to incorporate private data and ABAC into your smart contract let’s implement a simple use case that involves storing medicine prescription:
- The doctor can create a new prescription for the patient
- The doctor can see the prescription that he issued
- The doctor cannot see the prescription that he didn’t issue
- The patient can see his prescription
- The patient cannot see the prescription that doesn’t belong to him
- The patient can reveal his prescription to the pharmacy
- The pharmacy can see only prescriptions that the pharmacy filled
Access to the private data is configurable on the organization-level. Doctors and patients access ledger using peers that are members of the first organization (Org1). Doctors can issue new prescriptions and patients can access them. These two rules will have to be programmed in the chaincode as private collections config doesn’t allow for specifying such action-based rules. The pharmacy should maintain their own set of prescriptions for patients (Org2). That’s the minimal configuration for private collections to meet those requirements:
[
{
"name": "pharmacyPrescriptions",
"policy": "OR('Org1MSP.member', 'Org2MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 3,
"blockToLive": 0
},
{
"name": "healthcarePrescriptions",
"policy": "OR('Org1MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 3,
"blockToLive": 0
}
]
We will have to specify a path to that file later when we instantiate the chaincode.
peer chaincode instantiate -C mychannel -n mycc -v 1.0 -c '{"Args":[""]}' -P "AND('Org1MSP.peer','Org2MSP.peer')" --collections-config /path/to/collections_config.json
I use hyperledger/fabric-samples/first-network as the foundation for my network setup.
Start with generating the required certificates, genesis block, and docker compose file for configuration with two Fabric CA containers, one per each organization. Fabric CA is a certificate authority for Hyperledger Fabric.
Specify newly generated docker compose file and bring up the network.
./byfn.sh generate
./byfn.sh up -f docker-compose-e2e.yaml
On the application level, we can restrict data access using custom attributes. Certificates issued by the Fabric CA can contain custom attributes that we will use for authorization.
fabric_ca_client.register({
enrollmentID: "user1",
affiliation: "org1.department1",
role: "client",
attrs: [{ name: "role", value: "PAT0" }]
}, admin_user);
For more information on how to issue the certificate with Fabric CA, check out hyperledger/fabric-samples/fabcar example.
Chaincode is written in Go and uses cid library that might not be available in your container. Make sure to have fabric available in your $GOPATH and install two additional dependencies.
go get -u github.com/golang/protobuf
go get -u github.com/pkg/errors
Let’s start with defining the prescription struct, which we will use for storing prescription information. We can use the same prescription type for both pharmacyPrescriptions and healthcarePrescriptions collections.
type Prescription struct {
Patient string `json:"patient"`
Doctor string `json:"doctor"`
Content string `json:"content"`
Expires string `json:"expires"`
FilledBy string `json:"filled_by"`
}
Our smart contract has to handle invoking of four functions. The best practice is to have a separate method for state initialization. Let’s add one prescription to the healthcarePrescriptions private data for testing purposes. In the main call, we only start the chaincode.
type SmartContract struct {
}
func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) peer.Response {
return shim.Success(nil)
}
func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fn, args := stub.GetFunctionAndParameters()
if fn == "initLedger" {
return s.initLedger(stub)
} else if fn == "addPrescription" {
return s.addPrescription(stub, args)
} else if fn == "getPrescription" {
return s.getPrescription(stub, args)
} else if fn == "transferPrescription" {
return s.transferPrescription(stub, args)
}
return shim.Error("Invalid function name.")
}
func (s *SmartContract) initLedger(stub shim.ChaincodeStubInterface) peer.Response {
prescriptions := []Prescription{
Prescription{Patient: "PAT0", Doctor: "DOC0", Expires: "2018-07-17 14:01:52"},
}
for i, prescription := range prescriptions {
prescriptionAsBytes, _ := json.Marshal(prescription)
stub.PutPrivateData("healthcarePrescriptions", "PRE"+strconv.Itoa(i), prescriptionAsBytes)
}
return shim.Success(nil)
}
func main() {
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error starting SmartContract chaincode: %s", err)
}
}
Prescriptions stored in the healthcarePrescriptions collection should be only readable by patient owning the prescription and the doctor that issued the prescription. We know who is who as each user of the system should identify himself by using the certificate issued by Fabric CA with a role attribute. We respond with “Prescription not found” error also when the user is unauthorized to see the prescription.
Pharmacies store only prescriptions that they filled in, in a separate private collection. We don’t check their specific identifier.
func (s *SmartContract) getPrescription(stub shim.ChaincodeStubInterface, args []string) peer.Response {
// Check arguments
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
// Get prescription
role, err := s.getRole(stub)
if err != nil {
return shim.Error(err.Error())
}
key := "PRE" + args[0]
var prescriptionBytes []byte
if strings.HasPrefix(role, "PAT") || strings.HasPrefix(role, "DOC") {
// When patient or doctor
prescriptionBytes, err = stub.GetPrivateData("healthcarePrescriptions", key)
if prescriptionBytes != nil {
prescription := Prescription{}
json.Unmarshal(prescriptionBytes, &prescription)
if prescription.Patient == role || prescription.Doctor == role {
return shim.Success(prescriptionBytes)
}
}
} else if strings.HasPrefix(role, "PHR") {
// When pharmacy
prescriptionBytes, err = stub.GetPrivateData("pharmacyPrescriptions", key)
if prescriptionBytes != nil {
return shim.Success(prescriptionBytes)
}
} else {
// When other
return shim.Error("Only patients, doctors and pharmacies can access prescriptions")
}
return shim.Error("Prescription not found")
}
func (s *SmartContract) getRole(stub shim.ChaincodeStubInterface) (string, error) {
role, ok, err := cid.GetAttributeValue(stub, "role")
if err != nil {
return "", err
}
if !ok {
return "", errors.New("role attribute is missing")
}
return role, nil
}
Doctors can add prescriptions specifying patient’s identifier. We use doctor’s role attribute to reference him in the Prescription.
The last feature to implement is to allow the patient to transfer the prescription to the pharmacy upon filling the prescription. The patient can use the chaincode that writes to the pharmacyPrescriptions private collection.
func (s *SmartContract) transferPrescription(stub shim.ChaincodeStubInterface, args []string) peer.Response {
// Check arguments
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
role, err := s.getRole(stub)
if err != nil {
return shim.Error(err.Error())
}
if strings.HasPrefix(role, "PAT") {
return shim.Error("Only patients can transfer prescriptions")
}
// Get prescription
key := "PRE" + args[0]
prescriptionBytes, err := stub.GetPrivateData("healthcarePrescriptions", key)
if err != nil {
return shim.Error(err.Error())
}
if prescriptionBytes == nil {
return shim.Error("Prescription not found")
}
prescription := Prescription{}
json.Unmarshal(prescriptionBytes, &prescription)
// Check permissions
if prescription.Patient != role {
return shim.Error("Prescription not found")
}
// Set FilledBy
prescription.FilledBy = args[1]
prescriptionBytes, _ = json.Marshal(prescription)
err = stub.PutPrivateData("healthcarePrescriptions", key, prescriptionBytes)
if err != nil {
return shim.Error(err.Error())
}
// Save pharmacy prescription
err = stub.PutPrivateData("pharmacyPrescriptions", key, prescriptionBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
We could consider changing this implementation so it involves the pharmacy allowing the patient to transfer the prescriptions. It adds very little to how we work with ABAC or private data, so I decided to skip it.
You can find the complete implementation here: prescriptions.go.
Client
To interact with the Hyperledger Fabric, we can use fabric-client or fabric-ca-client SDKs. To test the implementation, you can start with scripts from hyperledger/fabric-samples/fabcar example. Some modifications are needed as our network uses TLS encryption, and fabcar doesn’t.
In query.js change
var peer = fabric_client.newPeer('grpc://localhost:7051');
to
const serverCert = fs.readFileSync('./crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/tlscacerts/tlsca.org1.example.com-cert.pem', 'utf8');
const peer = fabric_client.newPeer('grpcs://localhost:7051', { pem: serverCert, 'ssl-target-name-override': 'peer0.org1.example.com' });
In invoke.js change
var peer = fabric_client.newPeer('grpc://localhost:7051');
var order = fabric_client.newOrderer('grpc://localhost:7050');
to
const serverCert = fs.readFileSync('../crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/tlscacerts/tlsca.org1.example.com-cert.pem', 'utf8');
const ordererCert = fs.readFileSync('../crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem', 'utf8');
and change
let event_hub = fabric_client.newEventHub();
event_hub.setPeerAddr('grpc://localhost:7053');
to
let event_hub = channel.newChannelEventHub(peer);
and change
console.log('The transaction has been committed on peer ' + event_hub._ep._endpoint.addr);
to
console.log('The transaction has been committed on peer ' + event_hub.getPeerAddr());
Now you should be able to query the chaincode and make transactions.
Hyperledger Composer
If you feel like Hyperledger Fabric is a just bare bones, you might want to look at Hyperledger Composer. Hyperledger Composer is a set of tools that provide higher-level abstraction over the Hyperledger Fabric. It allows to model a business network that consists of assets and participants, runs JavaScript to execute a query or a transaction and provides easy-to-use REST API with different authorization schemes.
Hyperledger Composer is built on top of the Hyperledger Fabric v1.1 and doesn’t support the newest features. Lack of support for private data is a limiting factor for applying Hyperledger Composer where lack of confidentiality can be a problem. Nonetheless, developer experience of using Hyperledger Composer is much better than setting up and using Hyperledger Fabric. The project is under active development, and I’m looking forward to trying new version that comes with a driver for Hyperledger Fabric v1.2.
Conclusion
At Tooploox we incorporate blockchain into applications taking into consideration their long-term impact on the product. Currently, Hyperledger Fabric is the best fit for implementations where there are other reasons than transparency for using blockchain like compliance, building trust between business parties or streamlining processes.
On the other hand, when you would like to provide your users with the ability to easily participate in the network, collaborate and trade, you will be better off with Ethereum. There are multiple standards driven by the community so we can make your product compatible with the ever-growing ecosystem of decentralized applications.
This article has been originaly posted on Tooploox's blog: Hyperledger Fabric: Confidentiality on the Blockchain
Photo by Andrew Neel on Unsplash.