Demystifying Payment Channels

An in depth tutorial on how to create your own using Ethereum

Patrick Guay
The Dark Side
Published in
16 min readNov 9, 2018

--

A payment channel is a technique to allow users to make multiple Bitcoin or Ethereum transactions without committing all of the transactions to the blockchain[1].

Only two transactions need to be sent to the blockchain, but an unlimited number of payments can be made between the participants. A large volume of transactions between two parties can be reduced to the only two that actually matter: the opening and closing of the payment channel.

Rationale

Why are payment channels necessary? Taking the example of Ethereum, we know that we can transfer ether and tokens by sending between each other by sending transactions to the Ethereum network. Why are payment channels a better solution?

  1. Ethereum is slow. We can calculate the current maximum transaction per second (tps) that Ethereum can handle. Assuming a block gas limit of 8,000,000 (at the time of writing) and a simple ether transaction costing 21000 gas units, we can calculate how many transactions fit in a block by taking their quotient: 8000000 / 21000 =~ 380. This means that at most, a single Ethereum block can hold 380 transactions. With a block time of around 15 seconds, we calculate the tps: 381/15 =~ 25. 25 tps is the best case scenario, assuming that all the transactions are simple ether transfers. Notice that here we are not counting on "function calls", contract creation transactions and general transactions with a data field. By counting those in, we see and effective average tps of about 15 transactions per second.
  2. Micropayments are expensive. A simple transaction costs from about one cent to multiple dollars. This makes sending low value transactions (micropayments) infeasible. This is not taking into account token transfers which usually cost about double that.
  3. Storage optimization. Blockchain full nodes store the entire history of the blockchain since the genesis block. Currently, running an Ethereum full node requires storing 115 gigabytes of data on your computer. As more users join the network and more transactions are being created, the storage requirement increases at an accelerating pace. Payment channels help reduce this acceleration by reducing the total amount of transactions between two parties to only a few.
  4. Bandwidth requirements. Every transaction sent to an Ethereum node gets dispersed to other nodes using the gossip protocol. Now this is sustainable if you don’t have that many transactions flowing through the network but as more people join the network, this becomes a true bottleneck. This also requires a higher amount of RAM, and requires miners/full nodes to have better bandwidth.
  5. Privacy. It is extremely easy to track all transactions made from a specific address. Payment channels actually allow a certain amount of privacy between the two parties. As we will see, a person can “pay” another by simply signing a message and sending it to the other party. This means that normal encryption paradigms can be used. The two parties can decide to encrypt their communications and even encrypt the contents of the message with the other party’s PGP key. The only information that gets revealed (in the best case scenario) are the two parties involved in the channel, how much money they put in as a security deposit (initial channel state) and how much money they are getting out (final channel state).

Payment channel designs

In its essence, a payment channel is an exchange of valid signed transactions (we will call them messages) that dictate the distribution of ether/tokens between the two people in the payment channel. In this article, we will be using two fictional characters, Alice and Bob as our examples. We are assuming that Alice is hiring Bob to do some work.

There are multiple types of payment channels that one can implement, let’s explore a few of them in detail.

Simplest payment channel (no smart contract)

This example will be very brief and is simply here to get us accustomed to the concepts. Alice enters into an agreement with Bob that claims that she will be sending him a valid signed Ethereum transaction (messages) but tells Bob not to send it to the network right away, since she promises to send more when Bob finishes a job. These messages are actual valid transactions sending real Ether to Bob. As Bob does more work, Alice is supposed to send him a new signed transaction with a higher ether value and the same nonce. It’s important that the transaction has the same nonce, or else Bob could easily take all of the transactions that were sent to him and send them all at once to the network, thus getting paid a the sum of all the valid transactions.

Bob, at any time, can decide to send this transaction to the network to get the full payout. This solution is flawed, since Alice can simply send Bob a valid signed message and then remove all the Ether from her account before Bob can claim his payment. A large amount of trust is needed in this solution, which renders it useless.

Unidirectional payment channel

A unidirectional payment channel means that the transfer of Ether goes only from one party to another (here from Alice to Bob) and the value can only increase.

To create such a channel, we can create a smart contract that locks Alice’s funds for a specific amount of time. There is a great article of such a smart contract written by Matthew Di Ferrante. In a very succinct way, Di Ferrante explains to us the mechanism of this unidirectional channel and provides an example of a simple Solidity contract.

To explain it simply, Alice locks the total amount of Ether she is willing to pay to Bob. To effectively “pay” Bob, she needs to craft a message (again signed using her private key) that instructs the smart contract to pay Bob a certain value taken from the locked up funds put up by Alice.

For example, such a message could simply be a value (uint256) hashed and signed using a private key. Then, in our smart contract, we would be able to compare this value with its hash and also validate that the signature is valid.

With this message in hand, Bob can verify that the message is valid and would lead to him getting the right value of Ether transferred to him.

This is important to understand: Bob can cash out at any time as long as he has a valid message, although it would be more beneficial for Bob to keep the channel open, as he supposedly would get more money if Alice sends him more payments.

This solution is almost perfect but one flaw persists: it can lead to Alice’s funds being locked forever. Remember, it is Alice that locked in a certain amount of ether into the channel. In such a scenario, it is very possible that Bob decides never to close the channel. It could be that Bob is doing this maliciously or simply that Bob is not available/online anymore.

To solve this edge case, Di Ferrante introduces a general timeout to the channel. Assuming the channel was never closed and times out, Alice would get the totality of ether that she sent. This ensures that Bob is incentivized to close the channel before it times out.

Now this solution only works for monotonically increasing unidirectional payment channels, meaning that Alice is sending ether to Bob and the next message will need to be of higher value than the last (or else Bob can pick and choose which message has the highest value for him and close the channel).

Unidirectional payment channel with a challenge period

In another great and more hands on article written by Alex Miller @asmiller1989, we see a slightly more complex logic.

In his V2 version of the payment channel, he introduces a few improvements:

  • Nonces: for every new message add a counter (nonce) to the signed message. The message with the higher nonce is the latest message.
  • Challenge period: A period of time where any party (and anyone that has the data) can challenge the message used when the channel was closed by sending in another valid message with a higher nonce. In a successful challenge, the last valid message is overwritten by the one sent in the challenge.

Bidirectional payment channels

Bidirectional payment channels are very similar to unidirectional payment channels with the main difference being that you need both participants to lock in a certain amount of ether. The message used now directs how the smart contract should split the ether between Alice and Bob. For example, let us assume Alice and Bob have contributed 1 ether each in the payment channel, for a grand total of 2 ether. When Alice wants to pay Bob 0.5 ether, Alice sends a message that divides the 2 ether in between them, i.e. 0.5 ether to herself and 1.5 to bob.

This channel needs to be able to be closed by both parties, but again it should have a challenge period and nonce relating to what is the actual latest message.

Technical example

A semi-bidirectional payment channel

Why semi-bidirectional? I’ve decided to simplify this example so only Alice needs to deposit ether into the channel, but the amount sent to bob does not need to monotonically increase, it can also decrease. You can think of it as Alice being a customer and Bob being the merchant. Alice will be paying bob most of the time, but Bob can actually reimburse Alice on some expenses (if a product is defective, for example).

We will be creating a simple payment channel smart contract that will achieve these properties. To make it simple, let’s call it SinglePaymentChannel.sol. If you want the full solution, please find it here.

In our example, Alice deploys our smart contract. She calls the OpenChannel function where she declares the other party as Bob and she deposits a total maximal sum that the contract holds.

For simplicity, we won't allow Alice or Bob to add more funds into the contract after the channel is open.

OpenChannel function, initializing our payment channel between Alice and Bob

For simplicity of this example, we have hardcoded the challenge time period to last 15 minutes and the total timeout of the channel to last 1 day. Also, the design of this payment channel is such that you need to redeploy a brand new contract to start a new payment channel. Of course this is not scalable and not the best practice, this is only for to simplify the example.

We have now opened a payment channel between Bob and Alice. Alice has locked in all of the funds available in this payment channel. If Alice wants to effectively pay bob, all she has to do is to sign a message of intent saying that she will be sending Bob a certain amount of ether.

In our solution, the message that needs to be signed includes three pieces of information:

  • the address of the payment channel contract
  • the value that Alice is willing to send to Bob
  • the nonce of the message (simple message counter)

Why is the contract address needed? Remember that we will be signing a message with our private key. This message needs to be unique and simply signing the value and the nonce together means that an attacker can reuse this exact signature coming from you, in another payment channel. Adding the contract address here makes it so it is truly (or almost) a one time use signature.

To create a valid message using these three values and either Alice’s or Bob’s private key, we need to bring this message down to 32 bytes by using a cryptographic hash function, here it will be keccak256. To create a valid 32 byte string to sign, we concatenate the the contract address, value and nonce together and take the keccak256 hash. This gives us a 32 byte digest which we will call proof, with which we can sign using a private key.

To make this easier to understand, I’ve created tests in javascript for our contract (These are NOT exhaustive tests, it is simply used to show the flow of interaction with our contract). The createSig function shows how to create such a signature.

createSig creates the a proof, hashes it and signs it using a private key

Alice now signs such a message with her private key and sends it to Bob. She needs to send the signature and the three other values: address, value and nonce to Bob. Bob can now verify this message by concatenating the three arguments, hashing them together and verifying that the signature really came from Alice. Bob now does the same, signs the same message and sends it to Alice. Both of them now have the same proof signed by the other party. At this point one can assume that the payment has officially occurred.

Remember, “sending” the transaction can be done using encrypted channels, email, SMS, even a printed out piece of paper!

At anytime now, either of them are able to close the channel. All that either party needs to do is to call the Close function on the smart contract with both valid signatures and their respective values (nonce and value). From there, the smart contract will verify both signatures, update the latest proof sent to it and start the challenge period.

CloseChannel function. Takes in the proof, both signatures and the value and nonce used.

In this example I’ve set a 15 minute “challenge period” where the opponent (or actually anyone) with the two valid signatures will be able to change the outcome of the payment. As multiple messages get sent between Bob and Alice, it’d be trivial for any of them to send in the message that makes them the most money. This where the nonce comes in.

By ensuring that the nonce goes up every single time (this can be enforced server/client side, discarding any new message that does not have a higher nonce), and enforcing the fact the the highest nonce wins on the smart contract, one cannot cheat and send in an old signature that makes them the most money as the other one will always be able to overwrite it with the true latest signature.

Furthermore, we cannot only let one of the parties be able to sign the message as it would mean that it would be able to create as many messages with higher nonces as it wants. To ensure this is impossible, I’ve made it so one needs to provide the other party’s signature with a higher nonce for it to be considered valid.

Challenge function. Can only be called during the challenge period and needs the valid signature of the other party in the channel.

After a successful challenge, the latest proof will be overwritten with the one sent. This ensures that Bob (or Alice) agree on the final outcome of the channel.

After the challenge period is done, either party call the FinalizeChannel function and get paid.

FinalizeChannel function. After the challenge period is done, transfers the funds based on the last valid payment proof.

Finally we have to cover one last edge case, the fact that Bob could be unresponsive or decide to never sign a single message from Alice. This would lock Alice’s funds forever. Thus, to counter that, we add a timeout mechanism so Alice can recover her funds if the channel never gets closed.

TimeoutClose function. If the channel is never closed and times out, Alice can recover her funds

CLI tool

To make this a bit more concrete, I’ve created a small CLI tool in Golang exemplifying our example with Alice and Bob.

Getting Started

To get started using this CLI tool, you will need ganache or ganache-cli. Ganache is an amazing continuation of the testrpc project from Truffle that gives you a test blockchain to work with locally.

By default, the payment-channel tool will be looking for Ganache on port 7545, so when starting up ganache, ensure to tell it what port to use:

Make sure to copy the mnemonic phrase, we will need this for our tool.

If you are on linux, an executable file is already created for you. All you need to do to run the project is ./payment-channel.

If not on linux, you will also need to have a version of golang superior to 1.8. Then in the repository, run go get -v ./... to fetch all the dependencies. Then run go build which will create an executable binary for you called payment-channel.

Usage

This small CLI tool simulates a payment channel between Alice and Bob.

First we need initialize our storage, run:

Where [mnemonic] is replaced by the mnemonic given by ganache (in between double quotes as one string i.e “lamp … umbrella”)

This will initialize our storage, and derive the public/private keys from the mnemonic that we will use.

Now we need to deploy the contract to the ganache testnet network.

With the contract deployed, we can go ahead and open a payment channel between Alice and Bob.

Here we just opened a channel with a value of 1 ether (1*10^18 Wei) sent by Alice.

We can exchange as many signed messages between Bob and Alice. We need to give it the value we want to exchange, and the nonce of the transaction

For simplicity, this command creates a signature from both parties (Alice and Bob) automatically. We can see the latest signatures from both parties outputted to STDOUT.

One can create as many signatures as they want with an ever increasing nonce. Those signatures will be appended to an array of all signatures, all they have to do is the same operation but with a higher nonce. To view all the signatures (and data saved), you can lookup the storage.json file.

Now let’s create a message where Bob receives more ether

Now for the interesting bits. We can decide to close the payment channel with an earlier signature that benefits us. Let’s close our payment channel with the first signature that we have where Bob is getting 0.1 Ether and Alice keeps 0.9 of it all.

Here we close the payment channel with the first signature we created, with nonce 1. This definitely benefits Alice, as she doesn’t have to pay the extra ether. At this point, the challenge period has started.

Now Bob notices that Alice is trying to cheat her, Bob will challenge Alice and send the latest transaction he received from her. Keep in mind, this message has a higher nonce.

If the message is valid, it will overwrite Alice’s assertion that she was supposed to only send 0.1 ether, now she will need to send 0.15 ether in total.

Now we need to finalize the payment channel but we are still in the challenge period. To do so we will do a little bit of magic. We will artificially increase the blockchain time (we can do this on testrpc and ganache) by calling our special timewarp command.

Because our challenge period is only 15 minutes, increasing the time by 1000 seconds is plenty. We can now call our finalize command:

At that point, the funds will be distributed to Bob and Alice.

Payment Networks

Now what if we connected multiple bi-directional payment channels together and somehow made it so that anyone connected to such a network can interact with another person in that network? This is what payment networks aim to achieve (most notable in Ethereum is Raiden).

How is this done? Essentially you let users relay the payment. Imagine we have Alice, Bob and Carol. Imagine there is a payment channel between Alice and Bob and Bob and Carol. For Alice to be able to pay Carol, she would need to relay that information to Bob and ask Bob to pay Carol. Now this relies on trusting Bob, which is not a good way to go forward.

So to pay Carol, Alice first creates a secret, concatenates the value with it and hashes it. Alice then creates a message saying that she is paying Bob a certain value if he reveals this secret (she sends in the value and the hash of the secret). Alice tells Carol what the secret is using an encrypted channel that Bob cannot snoop in. Bob now creates a payment to Carol with the same conditions as Alice’s. To get paid, Carol needs to reveal the secret to Bob. Bob now has the secret and can then reveal it to Alice.

In this way, you can have a multi-hop network where anyone who participates in this network is able to transact with anyone else in that network.

Problems with Payment Channels

Payment channels at first glance seem like the ultimate Layer 2 scalability solution. We can ensure almost instant finality of transactions, transfer micro-payments to another party at incredible speeds and extremely low cost. But larger problems persist.

UI/UX

As you can see, creating/opening/closing/challenging/finalizing a payment channel is by no means easy. One needs to always verify at what state the payment channel is in, whether we are close to the timeout or not and close it with the latest signature. This makes it a headache when we think about the end user, but thankfully a lot of these tasks can be automated by software.

Availability

Participating in a payment channel needs just that, participation. One needs to be connected most of the time and be able to track what is happening. It’s great that we have a timeout but that still means that Alice cannot move her ether for that period of time, which is not only an inconvenience, it slows down the velocity of money.

Security

Participation and constant signing usually mean having “hot wallets” that are used regularly and more vulnerable than “cold wallets”. This makes it very risky to open payment channels that handle large amounts of money.

Velocity of money

Payment channels require participants to lock funds for a predetermined amount of time (this can also be extended as needed). Now this potentially reduces the velocity of money and also practically means that a regular user won’t want to lock up a very large amount of funds for a long time.

Backup

Backing up channel signatures is imperative. If one loses all their signatures, and somehow the other party knows this, the other party can use it to their own benefit. This leads to some interesting and dangerous exploits opportunities.

Generalized state channels

In this article we looked at a payment channel using ether only. Of course this would also work with ERC20 Tokens (Miller has a great example), or even ERC721 Non Fungible Tokens (NFTs).

In fact, these channels can be generalized to any state transition that we want. Although non-trivial to implement, we can make it so before we enter into a payment channel, we agree on what state transitions are allowed and send signed messages between parties on such transitions. This is what Counterfactual, Spankchain and others are trying to achieve.

Conclusion

In this article we’ve explored a few possible design for payment channels and done a technical deep dive on an implementation of a bi-directional payment channel. If there is one thing that I want you to take away are the emergent properties that this Layer 2 solution achieves. Under optimal circumstances, a payment channel would require at most 2–3 transactions (open/close) and be able to reduce a very large amount of transactions that actually hit the blockchain. It is not a perfect solution though, as it means that both parties need to be available and online. Nonetheless, having quasi-instant finality while paying with cryptocurrencies is extremely attractive, and already we see implementations of it to pay for coffee.

Special Thanks!

Thanks to Matthew Di Ferrante and Alex Miller for their well written articles!

I’ll be doing more articles of this sort, next one being about Plasma! If you’d like a deeper technical dive just like this one on a blockchain technology or concept, please let me know in the comments!

https://altcoinmagazinemastermindevent.eventbrite.com

Before moving on, make sure to press follow, leave a clap or 46, share today’s highlight and if you missed the last article, click here.

Read about the Altcoin Magazine Mastermind Event here.

Follow us on Twitter, InvestFeed, Facebook, Instagram, LinkedIn, and join our Discord and Telegram.

The purpose of ALTCOIN MAGAZINE is to educate the world on crypto and to bring it to the hands and the minds of the masses. This article was written and composed by Patrick Guay on ALTCOIN MAGAZINE.

--

--

Patrick Guay
The Dark Side

Full stack, golang, js, solidity. I believe blockchain is a vector of social good. Never stop learning, just buidl.