Hashed Timelock Agreements, or Contracts, have emerged as an important concept in the cryptocurrency space in order to perform transactions across ledgers – and I feel could be a valid mechanism to handle the issue of performing verifiable cross-channel transactions in Hyperledger in some use cases. The basic concept of a Hashed Timelock Agreement (HTLA) is that it allows for a conditional transaction (which I have deemed a ‘proposal’) with a cryptographic challenge which ensures it can only be completed by a pre-defined party. This can be chained through multiple intermediaries, which can enable two organisations who do not share a channel to interact, and for transactions to be confirmed across channels.
If you want to jump straight into the implementation, the proof of concept code is available here.
Aside: In order to understand how ‘hashlocking’ of a proposal works, it is helpful to understand some concepts around hashing. In this explanation, we will discuss passing around a hash and validating it with a pre-image. The pre-image is the initial data that was run through a one-way hash algorithm to generate the hash output. The important concept is that it is effectively impossible to determine the pre-image from the hash, but determining whether a pre-image matches a hash is trivial. As such, we can freely pass the hash around, and the person who initially generated it is able to confirm their identity by presenting the pre-image.
As an example of this, assume that we have several parties, Alice (A), Bob (B) and Charlie (C). Alice wants to perform a transaction which will require both Bob and Charlie to do something, and will only sign off on the task once both Bob and Charlie have completed it. However, for data privacy reasons, Alice and Charlie do not share a channel and do not deal with each other directly; they do however both have channels in which Bob is a member, e.g.
In order to facilitate this, Charlie generates a random string, then hashes it, and shares the hashing algorithm and resulting hash with Alice out-of-band (alternatively, they could use a dedicated channel for this purpose, but this risks channel creep). Alice can then create a conditional proposal in her shared channel with Bob, which will only be confirmed once the pre-image (i.e. Charlie’s random string) is provided.
Bob is then able to initiate a transaction with Charlie, which includes an agreement that once that interaction is satisfactorily concluded, Charlie will provide the pre-image of the hash shared with Alice, such that Bob is able to confirm Alice’s proposal. Importantly, Alice is therefore able to confirm that Charlie was involved in the sign-off, despite this occurring in a different channel.
The flow is something like the following (where all communication between Alice and Bob is in the Alice/Bob channel, and all communication between Bob and Charlie is in the Bob/Charlie channel):
Some simple proof of concept code for this is available here, using the concept of ‘abstract proposals’ which obviously should be replaced with something more meaningful in a concrete use case. This proof of concept is also heavily dependent upon chaincode events, which can be used to inform each participant that there is a transaction on which they need to act. The reason for this is the ‘Timelock’ feature of these contracts, which is highly desirable in the cryptocurrency space (since typically the proposal is ‘I agree to pay you x tokens, on the condition that you can prove you have paid Charlie x tokens’, and the tokens are locked until the transaction is resolved, so expiring it is important if anyone disagrees). This ‘timelock’ feature implies that proposals should be invalidated after a certain elapsed time, if they haven’t been confirmed by the presentation of the pre-image.
If you have much experience with Hyperledger Fabric, you have probably run into issues around handling time and timestamps in smart contracts, because of the classic problem of chaincode being executed at endorsement time, and not at transaction commit time, as well as the fact that the GetTxTimestamp shim method is based upon the client’s reported time. As such, I have left the timelocking completely out of the proof of concept code, and I am firing an event which can be listened for by a client which is responsible for invoking the ‘invalidateProposal’ method. If you didn’t require high precision time locking, i.e. of the order of minutes or longer, you could probably write this into the smart contract and compare the system time against the proposal creation time.
This implementation does push some responsibility to clients who are listening for events, in order to handle invalidating expired proposals, and for Bob, who straddles both channels to replay proposals between them, but I suspect that this is likely inevitable when you have multi-party transactions, and there is no particular reason why this needs to be automated and event-driven. In longer lived multi-party transactions, a user could ‘check’ for new activities or updates, then take the appropriate action to progress the transaction, or some sort of middle-ground, in which events trigger the creation of human tasks in other systems.
Hashed Timelock Agreements have some interesting attributes in multi-party operations across channels in Hyperledger, and provides options to obtain better transaction atomicity in cross-channel operations than using the InvokeChaincode shim method. It also allows for verifiability, without leaking data across channels, due to the nature of one-way-hashes. In our example, Alice can confirm that Charlie and Bob completed a transaction, without revealing anything about the nature of that transaction, as it occurred in a separate channel.