An Implementation Example of Notarization in Hyperledger Fabric

KC Tam
12 min readMar 28, 2019

--

Overview

Notarization is always considered as a good use case in Distributed Ledger Technology (DLT). It is largely due to the nature of data immutability, transaction audibility and decentralization in DLTs.

As mentioned in my previous works (link), there are different types of notarization. This work is an application about proof-of-ownership: to show a document owned by someone is recorded in ledger using digital signature technology.

Theoretically this type of application can run on any DLT setup, be it permissionless and permissioned one. In fact I have developed one in Ethereum Rinkeby testnet. However in this article the application is built on top of Hyperledger Fabric, a permissioned blockchain platform. I will talk about these two platforms later.

This article provides some in-depth discussion on the chaincode and client applications. All the codes and demonstration flow are provided for easy hands-on practice.

Design

Ownership of a Document by Digital Signature

As said, the mechanism of proving ownership is through digital signature in public key cryptography. Each individual is given a key pair, designated as a private key and a public key. These are mathematically related. Anything encrypted with private key can only be decrypted by the corresponding public key. In digital signature use, the document-to-be-validated is first “hashed” to a fixed length (called hash or digest) and encrypted with one’s private key. The process is called signing, and the result is called one’s signature on that document-to-be-validated.

For anyone who wishes to verify the signature, the document-to-be-validated, the signature, and the public key of the owner is needed. The signature is first decrypted with the public key. The result is compared to the hash of document-to-be-validated. If identical, anyone can conclude that the document is signed by someone who owns the private key, corresponding to the public key being used.

There is a small caveat here: I simply equate “one signs a document” to “one owns a document”. It may be possible in some cases but not in others. In real life further process may be needed to enhance the ownership. Again, for sake of demonstration, this is well assumed.

Identity in Ethereum and Hyperledger Fabric

Theoretically this type of application and the digital signature can be done almost in all types of blockchain platform. In fact I also have developed this similar application on Ethereum Rinkeby almost a year ago (and it is still running).

However, being a permissionless blockchain, Ethereum has no implicit way to link the holding of private/public key to one’s identity. I am taking a tedious approach by adding a layer of Public Key Infrastructure (PKI), using X509 certificate to identify individual, and document signing and verification is done on X509 certificate instead of native Ethereum network.

Hyperledger Fabric as a permissioned blockchain requires anything (and anyone) is identified before using the network. And good enough they are using X509 certificates for identification. Therefore, the same set of certificate is used for both signing transaction in fabric network, and signing document for application.

Using permissioned blockchain like Hyperledger Fabric brings in both good sides and bad sides. On one side the identify mechanism is already there, simplifying the design. On the other hand, one has to build the fabric network and after that maintain it, and the level of distribution is far lower than a permissionless blockchain network like Ethereum. Nevertheless, both cases rely on centralized element (the Certificate Authority, CA) and therefore trust-level wise, they are the same.

User enrollment to CA. Private key is kept secret while certificate is made public.
Use X509 to validate the ownership (signature) of a document

Overall Flow

So the design of application looks like this. Application user is first registered and enrolled into the CA. After enrollment the user is given a signing key (i.e. private key) and the certificate, which contains user’s identity, user’s public key, and CA’s signature on the certificate. This information is stored in user’s wallet.

When a user (originator) needs to record the ownership of a document, the application computes the document hash, and the signature using user’s private key. These two pieces of information is submitted to the fabric network. The ledger in fabric network will record the signature and timestamp of submission, with the document hash as key.

When anyone needs to verify that ownership of that document, the information needed is the document itself and the originator’s certificate. The application will compute the hash of the document and retrieve the record from the ledger based on the hash. If found, the signature and the timestamp is retrieved. Then the application performs the validation by using the given signature and public key in the certificate. After validation, one can be sure that this document has been signed by the originator, and the record is found in the ledger.

The validation fails when one is using someone’s else certificate (not the originator) or that document has not been recorded before.

Application Deep Dive

Application in Hyperledger Fabric is mainly composed of two portions: the chaincode (the “smart contract” being executed in endorsing peers in the fabric network), and the client applications (the client portion interacting with the chaincode using software development kit (SDK). Here we will take a look on both.

Chaincode

The chaincode is responsible for interacting with the ledger. In our example, we largely adapt the chaincode of fabcar example inside fabric-samples. Here I just highlight those relevant portions in the chaincode.

Data Structure in Ledger

As the purpose of our application is to keep the signature and timestamp of a document submitted by a user, we use a rather simple data structure to keep this.

type DocRecord struct {
Signature string `json:"signature"`
Time string `json:"time"`
}

Chaincode Functions

Following the best practice, we do not define the Init(). And inside Invoke(), we define two functions: queryDocRecord() and createDocRecord().

func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
}
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {// Retrieve the requested Smart Contract function and arguments
function, args := APIstub.GetFunctionAndParameters()
// Route to the appropriate handler function to interact with the ledger appropriately
if function == "queryDocRecord" {
return s.queryDocRecord(APIstub, args)
} else if function == "createDocRecord" {
return s.createDocRecord(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}

In createDocRecord(), we are expecting two parameters, the hash of a document and the document signature by the owner (the computation of hash and signature are outside the chaincode). When recorded in the ledger, the hash is the key, while the document signature and timestamp is the value of this key.

func (s *SmartContract) createDocRecord(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
var docrecord = DocRecord{Signature: args[1], Time: time.Now().Format(time.RFC3339)}
docrecordAsBytes, _ := json.Marshal(docrecord)
APIstub.PutState(args[0], docrecordAsBytes)
return shim.Success(nil)
}

In queryDocRecord(), we are expecting one parameter, which is the hash of a document. With this hash as key, the record in the ledger is retrieved.

func (s *SmartContract) queryDocRecord(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
docrecordAsBytes, _ := APIstub.GetState(args[0])if docrecordAsBytes == nil {
return shim.Error("Document not found: " + args[0])
}
return shim.Success(docrecordAsBytes)
}

Client Application

Now it is the client application. We totally have four client applications for this application.

enrollAdmin.js

This one is directly taken from fabcar example without modification.

The purpose is to enrol an admin user to the Fabric CA of Basic Network. This admin will later register user for our demo.

After enrollment, the admin (and other users as well) will have a directory created inside docrec/wallet directory. Inside the wallet there are signing key and X509 certificate issued by Fabric CA. They will be used on other client applications.

If admin already exists in the docrec/wallet, no further action is needed.

Meanwhile, per the Basic Network setup, the Fabric CA will have all the things “hardcoded”. As long as we do not modify the crypto detail on Basic Network, we can enrol the admin once, and use the admin wallet for the whole demo.

$ node enrollAdmin.js

registerUser.js

The application is mainly adapted from fabcar example with modification. The original fabcar example creates a wallet for ‘user1’ (hardcoded). Since we need multiple users, we will add a username when using this application.

The purpose is to register a user by admin, and then a user can enroll the Fabric CA, that is, collect the signing key and X509 certificate. Like admin user, each user will have its own wallet, stored under docrec/wallet directory.

Similarly, as long as Basic Network setup is not modified, we can generate once these users and use for demo any time after.

$ node registerUser.js <username>// examples
$ node registerUser.js alice
$ node registerUser.js bob

addDocByFile.js

The purpose of this application is to record the document in this notarization application.

The input parameters are

  • user: From the wallet of user user’s signing key is retrieved. This signing key is to generate siganture on the hash of the file specified.
  • filename: The file to be recorded. The hash (SHA-256) is computed.

After computation, it invokes chaincode function createDocRecord(), which requires the information of

  • hash of file
  • signature of the hash by user
$ node addDocByFile.js <user> <filename>// example
$ node addDocByFile.js alice alicefile
$ node addDocByFile.js bob bobfile

validateDocByFile.js

The purpose of this application is to validate the document in this notarization application. The validation can help to prove that,

  • the document has been recorded in the ledger before (otherwise, document not found is responded)
  • the signature is correct and created by the certificate provided (otherwise, signature validation fails)
  • the certificate provided is a valid certificate by a CA
  • the time when the record is created

The input parameters are

  • user: Here user can be anyone with a valid wallet. Consider validation can be done by anyone, not just the document owner or admin. However, we still need user as Hyperledger Fabric as a permissioned blockchain requires identity.
  • filename: The file to be validated. The hash (SHA-256) is computed from the file.
  • usercert: The X509 certificate of the file owner. One’s certificate should be public available and the subject is the identify of the certificate owner.
$ node addDocByFile.js <any_user> <filename> <usercert>// example
$ node validateDocByFile.js alice alicefile alicecert
$ node validateDocByFile.js admin bobfile bobcert

Codes

Here are the codes of chaincode and several client applications.

chaincode
enrollAdmin.js is adapted from fabric-samples/fabcar
registerUser.js is adapted from fabric-samples/fabcar

Demonstration

The demo setup is built on Basic Network from fabric-samples. It is a single-organization single-peer setup with one order and one Fabric CA. Commands are issued through CLI. It is good enough to show how the application works.

On this Basic Network a channel mychannel is created, and the peer joins the channel. After that the chaincode docrec.go is installed and instantiated.

After that client applications are used to interact with the chaincode.

The demonstration setup

Step 1: Create Project Directory

Create a project directory under fabric-samples. (The demo code contains relative directory structure. Therefore make sure it is on the right directory.)

# inside fabric-samples
$ mkdir docrec && cd docrec

Step 2: Load the Required Packages

Besides Fabric SDK, we also needs handling X509 certificate and cryptography.

# inside fabric-samples/docrec
$ npm init -y
$ npm install fabric-ca-client fabric-network crypto-js jsrsasign -S

Step 3: Chaincode

For demo purpose, we place the chaincode under fabric-samples/chaincode directory. This directory is accessible in the running containers of basic-network.

# inside fabric-samples/chaincode
$ mkdir docrec && cd docrec
$ <put the docrec.go file here>

After we should be able to see the chaincode file in this directory.

Step 4: Client Application

There are total four client applications to be used. We simply put them in fabric-samples/docrec directory.

# inside fabric-samples/docrec
$ <put the four files enrollAdmin.js, registerUser.js, addDocByFile.js and validateDocByFile.js>

After we should be able to see the four client application files in this directory.

Step 5: Bring Up Basic Network

Bring up Basic Network and check if all five containers are up and running. After that the Basic Network is up and running with a channel mychannel is created and the peer node joins the channel.

# inside fabric-samples/basic-network
$ ./start.sh && docker-compose -f docker-compose.yml up -d cli
$ docker ps

Check all five containers are up and running.

Step 6: Install and Instantiate docrec Chaincode

After Basic Network is up and running, we can work on the chaincode installation and instantiation.

# any directory
$ docker exec cli peer chaincode install -n docrec -v 1.0 -p "github.com/docrec"
$ docker exec cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n docrec -v 1.0 -c '{"Args":[]}' -P "OR ('Org1MSP.member')"

Step 7: Enrol admin as Admin (if not yet)

Use enrollAdmin.js to enrol an admin. If admin is in docrec/wallet, it is already enrolled and it will be used later.

# inside fabric-samples/docrec
$ node enrollAdmin.js
$ ls wallet

Step 8: Register Users alice and bob, and Enrol them as Users (if not yet)

Use registerUser.js to enrol users alice and bob. If they are already in the docrec/wallet, they are already enrolled and can be used later.

# inside fabric-samples/docrec
$ node registerUser.js alice
$ node registerUser.js bob
$ ls wallet

Step 9: Extract Certificate from alice and bob

We later needs the certificate from alice and bob for signature validation. The certificate can be found inside their wallets. The directory is docrec/wallets/alice/alice.

# inside fabric-samples/docrec
$ cat wallet/alice/alice
$ cat wallet/bob/bob

Use any editor to extract them and keep them into two files: alicecert and bobcert.

Here is how alicecert looks like.

This is just for alicecert. Do the same for bobcert from bob’s wallet.

Step 10: Create two files for demonstration

Create two files for demonstration.

# inside fabric-sample/docrec
$ echo "This is a test file for alice" > alicefile
$ echo "This is another test file for bob" > bobfile

Step 11: Add Files in the Notarization Application

Now we have all files ready for demonstration. We will use addDocByFile.js to add alicefile by alice, and bobfile by bob.

# inside fabric-sample/docrec
$ node addDocByFile.js alice alicefile
$ node addDocByFile.js bob bobfile

From the response we see the transaction is successfully submitted.

Step 12: Validate Files in the Notarization Application

We can use validateDocByFile.js to validate files with the certificates.

We first validate alicefile with alicecert, and bobfile with bobcert. Both should work. Meanwhile, we need to specify the user as someone issues this query, but it is not relevant in the validation process. As an example, we just use alice as the query issuer.

# inside fabric-sample/docrec
$ node validateDocByFile.js alice alicefile alicecert
$ node validateDocByFile.js alice bobfile bobcert

In both case we see positive result (true).

Next we validate with wrong certificate.

# inside fabric-sample/docrec
$ node validateDocByFile.js alice alicefile bobcert
$ node validateDocByFile.js alice bobfile alicecert

And we see negative result.

Meanwhile, some detail of certificate provided is given in each validation. We can further consider whether this certificate is acceptable or not (e.g. whether it is from a trusted issuer, whether the subject names are correct, whether the certificate has expired, etc.)

Finally we validate a file that is not submitted before.

# inside fabric-sample/docrec
$ echo "File not recorded" > nofile
$ node validateDocByFile.js alice nofile alicecert

Document not found in this query.

Step 13: Teardown the Demonstration

After demonstration, we will tear down the whole Basic Network setup. The scripts stop and remove containers and image created during the demonstration.

Note all file record in the ledger is removed. The wallets are still there, and next time when perform demonstration, they don’t need created again (i.e. Step 7 and 8 can be skipped).

Closing

In this exercise we have built a notarization application for recording the ownership of documents on a fabric network. Although the application is demonstrated in Basic Network, it should work fine with a proper setup of a fabric network.

This setup is for demonstration purpose and therefore the applications are not optimized and no user interface is developed. Nevertheless, it shows how to leverage the identity management with X509 for individual user as ownership of any document. It can be further evolved to become a useable application for a larger population, such as a user interface to register and enrol the CA and collect the certificate in their own wallet, select their wallet for signing document, etc.

--

--

KC Tam
KC Tam

Responses (3)