Smart Escrow — Secret based fund locking Contracts on Ethereum

Lucas Henning
3 min readApr 2, 2021

Atomicity is one of the most desirable attributes of marketplace transactions in distributed systems. It is important that both parties — buyer and seller — fulfill their obligations. In an environment like this where every participant is a potential fraudster, an atomic relationship between blockchain and real-world transactions becomes more important than ever.

Disclaimer: This article describes a concept that I’ve used in Solidity beginner classes to explain secret based locking on Ethereum. I wrote this in 2018 and I’m publishing this now for educational purposes only. For the sake of simplicity, the code in this article purposefully omits sanity checks and security mechanisms. It should never be used for anything in production. Never use any of this with real funds!

Motivation

Let’s consider a simple scenario in which a buyer purchases a good from a seller. The participants don’t know each other and the lack of trust prevents the buyer from paying upfront and the seller from blindly sending over the goods. Due to this mistrust, the deal can never happen.

Adding a secret based locking mechanism

Let’s add one more step and improve the situation with an escrow smart contract. Instead of sending the funds to the seller directly, the buyer sends his money to an escrow smart contract (Solidity):

function lockFunds(address _to, bytes32 _secret) public payable {
sender = msg.sender;
to = _to;
secret = _secret;
amount = msg.value;
}

The lockFunds() function accepts a recipient’s address and a hashed secret (the keccak256 hash of a randomly generated nonce). In order to unlock funds, somebody who knows the secret needs to call unlockFunds() .

function unlockFunds(bytes32 _secret) public {
if (keccak256(_secret) == secret) {
to.send(this.balance);
}
}

Mission accomplished: With these simple steps we already improved the situation in two ways:

  1. The buyer doesn’t have to send funds before he receives the goods.
  2. The seller can verify that funds are held in the escrow before sending out goods.

However, there’s one problem left: The buyer is the only one who knows the secret. What if the buyer receives the goods but never calls unlockFunds() ? We need to improve the situation in favor of the seller.

Adding a second secret and security deposits

To protect the seller, it’s a good idea to add a security deposit for the buyer. This deposit can be added to the lockFunds() function above. Let’s suppose the value is equal to the value of the purchase, so that the buyer needs to pay 2x the amount of the purchase (1x to eventually pay the seller, 1x as a security deposit).

Now, we need to add a second secret that is created by the seller. The hashed secret is written to the blockchain while the plaintext is pegged to the good and transferred over an independent channel (by mail).

function addSellerSecret(_hashedSecret) public {
sellerSecret = _hashedSecret;
}

To unlock the funds, the buyer needs to know the hash of both secrets. The unlockFunds() function sends the amount to the seller and the security deposit back to the buyer.

function unlockFunds(bytes32 _secret, bytes32 _sellerSecret) public {
if ( keccak256(_secret) == secret &&
keccak256(_sellerSecret) == sellerSecret )
{
to.send(amount / 2);
msg.sender.send(amount / 2);
}
}

Mission accomplished: We successfully incentivized the buyer to call unlockFunds() as he wants to get his security deposit back.

So far so good, but what prevents the seller from not sending any goods? The buyer would end up with his funds being locked up forever in a smart contract.

Making fraud expensive for the seller

We need to incentivize the seller to actually send out the good. Once again, a monetary security deposit is the easiest way. We can add another security deposit to the second secret. Only if the buyer reveals all secrets, the seller is paid the amount and both deposits are paid back (one to the buyer, one to the seller).

function addSellerSecret(_hashedSecret) public payable {
require(msg.value == amount / 2);
sellerSecret = _hashedSecret;
}

Mission accomplished: We successfully incentivized the seller to send out the goods as he wants to get his security deposit back.

Conclusion

We have solved the following problems:

  1. The buyer is incentivized to pay in order to get his security deposit back.
  2. The seller is incentivized to send out the goods in order to get his security deposit back.
  3. Neither party can achieve a benefit by not complying.

This is the foundation for many secret based locking mechanisms on Ethereum. Understanding this concept will help you get started with more advanced locking strategies.

--

--