How LocalCryptos' Non-Custodial Bitcoin Escrow Works

In November 2019, LocalCryptos launched Bitcoin escrows. We copied our Ethereum-based concept and translated the code to a Bitcoin-compatible script. In this article, we’ll explain how it works.

This article assumes you understand Bitcoin to a technical degree. We’ll try to break down simple concepts, but if you don’t know what the term “UTXO” means, it might be difficult to follow.

This setup is non-custodial from start to finish. LocalCryptos’ servers only store encrypted blobs which it can’t differentiate from gibberish. Our servers don’t generate any transactions described here—it’s all your browser’s JavaScript.

Our roots in Ethereum

We were LocalEthereum before we renamed the platform to LocalCryptos. LocalEthereum was the world’s first peer-to-peer trading platform for Ethereum. It was also the most popular P2P fiat-to-crypto ramp behind PaxFul and LocalBitcoins.

LocalEthereum wasn’t a simple LocalBitcoins clone. Although the user journey was similar, LocalEthereum was a beast when you popped the hood. It was the only platform with end-to-end encrypted messages and a non-custodial web wallet. It was also the first to introduce a “non-custodial escrow”, which sounds like an oxymoron. Let us explain what that means:

Instead of holding crypto in guarantee in a wallet on its server, it used an Ethereum smart contract. Ether (ETH) would flow from the seller to into the smart contract, and from the smart contract to the buyer. The smart contract set the rules of the escrow in stone ahead of time in a decentralised manner. By using a smart contract rather than a custodian, the escrow was intermediary-free.

Now we’ve built the same thing for Bitcoin. Except that Bitcoin doesn’t have smart contracts—it has Scripts. Scripts are like smart contracts in some ways, but very different in others.

What’s in a Bitcoin transaction?

Bitcoin transactions contain inputs and outputs. Each output contains a recipient address—where you are sending the Bitcoin—and how much the address will get. Inputs point to previous outputs—coins sent to you that you haven’t yet spent.

Bitcoin “addresses” are a strange concept to unpack. In a way, they’re not a part of the protocol—they’re not mentioned in Satoshi Nakamoto’s white paper.


There are different types of Bitcoin addresses. A Bitcoin address, when decoded, contains two parts: a version identifier, and a payload. The version field is at the beginning, and it tells your Bitcoin wallet the type of address it’s dealing with. The usage of the payload depends on the version.

An address that begins with a 1 is a Pay-to-Public-Key-Hash (P2PKH) address, the most common address version. An address that begins with a 3 is a Pay-to-Script-Hash (P2SH) address. You might see other address prefixes too, such as a native SegWit address. For the sake of this article, only P2SH and P2PKH addresses are relevant to understand.

Address standards describe how to create puzzles, and how to create solutions to unlock them. The puzzle solution allows the recipient to use an output as an input to another transaction.

Alice sends Bob some dough the old way

In a P2PKH address, the payload following the version is a hash of the recipient’s public key. The hash function used is “hash160”, which translates to RIPEMD160(SHA256(thing)).

When Alice wants to send a payment to Bob, she creates a puzzle named a ScriptPubKey that only Bob can solve. The puzzle becomes an output of her Bitcoin transaction.

For Bob to spend this output, he needs to use it as an input in another Bitcoin transaction. To do that, he needs to craft a valid ScriptSig corresponding to the ScriptPubKey.

Any P2PKH ScriptPubKey always follows this template:


And a corresponding P2PKH ScriptSig is in the format of:

<Signature> <PubKey>


An output’s ScriptPubKey is a piece of code. It’s in a restricted stack-based programming language unique to Bitcoin, named “Script”.

A ScriptSig is also a piece of code, but by Bitcoin’s consensus rules it can only contain “push data”. In simple words, it can’t do anything except to add arbitrary data to the stack.

In this P2PKH example, Bob’s ScriptSig is adding two things to the stack:

  • PubKey— Bob’s own public key. Alice was only given a hash of Bob’s public key, not the real deal.
  • Signature — A signature of a hash of the transaction Bob has generated, including all the outputs, created using the same PubKey. (Bitcoin signatures are a bit more complex than that—too complex to describe here.)

Checking that ScriptSig is right

Before a miner can include this transaction in a block, they will confirm that ScriptSig is correct for the ScriptPubKey.

The miner adds ScriptPubKey to the end of ScriptSig, and executes the code. After the last line of code is ran, the stack must end with the top item being true (non-zero). This is all it takes to verify a ScriptSig.


As you can see, the stack ends with “true”. If you want to learn more about P2PKH and why it’s popular, there are a ton of resources on the web. Search Google for “P2PKH” to find out more.

What is a P2SH address?

Pay-to-Script-Hash addresses move the responsibility of supplying the payment conditions to the spender.

Remember when I said a moment ago that the spender’s ScriptSig can’t include non-data code? Forget that for a moment—P2SH changes everything.

Instead of writing spending conditions inside ScriptPubKey, that code goes inside a RedeemScript. Yes, that’s a new term—in loose non-technical words, it refers to the “real” puzzle for the output. When you send coins to a P2SH address, the ScriptPubKey contains a hash of the spending coniditons code. It looks like:

OP_HASH160 <RedeemScriptHash> OP_EQUAL

A spender of a P2SH input provides two things in their ScriptSig:

  • RedeemScript — The spending conditions of the payment. It’s easy to think of this as “the real ScriptPubKey”.
  • Signature — The push data to combine with the RedeemScript. In plain terms, “the real ScriptSig”.

Miners will first verify that the hash of RedeemScript matches the hash in ScriptPubKey. If it does, it will unpack the RedeemScript, put Signature above it, and execute.

What is a P2WSH address?

LocalCryptos uses Pay-to-Witness-Script-Hash (P2WSH) addresses for escrows instead of P2SH. To be precise, we use a P2WSH wrapped inside of a P2SH (i.e. P2SH-P2WSH).

The difference between a P2WSH and a P2SH is that in a P2WSH, the old RedeemScript goes inside a new “Witness Script” field.

If you haven’t learnt about SegWit, please pretend LocalCryptos’ escrow uses standard P2SH. It will make as much sense. Using Segregated Witness improves the escrow’s efficiency and cost, and nothing more.

Why not use multi-signature?

Off the bat, there’s a good chance you’re assuming LocalCryptos uses multi-signature addresses. You won’t find OP_CHECKMULTISIG in the code.

In a multi-signature script, each signer is a party to the spending transaction. In our model, LocalCryptos never has to sign any part of the transaction—even in a payment dispute.

In true peer-to-peer fashion, we are never a party to any transaction. Because we’re not a party, it’s impossible for us to impose spending conditions on escrows—such as how and when the receiving party can spend Bitcoin.

Our mechanism is especially useful for in-person exchanges. Users are able to release Bitcoin from escrow without access to the internet. It will be possible to trigger a release by sending an SMS with a unique code, or by showing a QR to the buyer. More on that later.

Generating and signing keys in bulk

The first thing you do when you create a LocalCryptos account is generate a lot of random keys and sign them. Of course, this happens in the background—ordinary users won’t notice it.

These ephemeral keys come in three categories, two of which are relevant to BTC escrows.

Generating end-to-end messaging keys

The unrelated category of random keys created at sign-up are end-to-end encryption keys. These keys allow people to begin encrypted conversations with you while you’re offline. LocalCryptos borrows this idea from popular forward-secret encrypted messaging apps such as Signal. You can learn more about it in LocalEthereum’s original white paper.

Generating wallet addresses

When you generate a Bitcoin address in your non-custodial LocalCryptos wallet, you also upload a signed version to LocalCryptos. When the web wallet is setting itself up, it creates and signs hundreds of Bitcoin addresses.

Encrypted copies of your private keys are also uploaded to allow you to log in from another device.

The same process occurs for our other web wallets, including Ethereum.

This allows others to open trades with you and fund escrows, even while you’re offline. Users fetch one of your addresses from us, and check the signature against your public key. Doing so helps avoid the risk of a complex man-in-the-middle attack.

Generating escrow keys

Escrow keys are unique to Bitcoin escrows, as Ethereum’s escrow system is different.

Escrow keys are 32-byte secret codes which you can reveal later. You generate a hash of the secret code (using hash160) and sign the hash. Then, you upload the hashed code, signature, and an encrypted secret code to LocalCryptos.

Funding escrow

To put BTC in escrow, the seller creates a Bitcoin transaction containing two outputs. One output is for the amount being escrowed, and the other is LocalCryptos’ refundable fee.

Before doing so, the seller needs to fetch some details from LocalCryptos:

  1. Hashed escrow key codes from the buyer, arbitrator, and seller (their own).
  2. A signature from the buyer they can use to authenticate the hashed escrow key code.
  3. A hashed public key from the buyer, as well as their own.
  4. A signature from the buyer to verify the public key belongs to the buyer.
  5. The arbitrator’s hashed public key, so they can send a small fee.

The seller will confirm each of the buyer’s signatures are valid before proceeding. An incorrect signature means that a hacker has attempted to tamper with the escrow.

Escrow output

The escrow output, which carries the amount for the buyer, is a P2SH-P2WSH address for the following Script:

  # Release by seller
    # Release by arbitrator
      # Return by buyer
      # Return by arbitrator

Fee output

The fee output carries an amount of Bitcoin approximately 1% of the trade’s size. LocalCryptos will claim the fee if the trade is successful. If there’s a cancellation, the seller can unlock the output to claim a full refund.

The fee output is a P2SH-P2WSH address for the folowing Script:

  # Return by buyer
    # Return by arbitrator
    # Spend by LocalCryptos

Waiting for confirmations

LocalCryptos requires most Bitcoin escrows to have at least one block confirmation. Larger trades need up to six confirmations depending on the size of the exchange.

Revealing a secret code 

The buyer already has one of the two inputs needed to unlock the escrow transaction: their own public key. The only missing piece is the seller’s secret “releaseBySeller” code.

Likewise, the seller is only one input away from unlocking the escrow. If they get their hands on the buyer’s secret “returnByBuyer” code, they can recall the amount in escrow.

The arbitrator holds the secret code to the “releaseByArbitrator” and “returnByArbitrator” escrow keys. If a payment dispute arises, the arbitrator can make a resolution by revealing one of the secrets.

This is the crux of the non-custodial escrow system. The hashed script includes the hash of each secret code, but not the real thing.

Using a secret code 

To spend an escrow output, the receiver needs to compile a signature with the following items:

  • Signature — A signature of a hash of the transaction. The signature is checked against the below public key to verify only the receiver can spend.
  • PubKey — The receiver’s public key, matching the hashed version in the code.
  • SecretCode — The revealed escrow code from another party.
  • Action — One byte to identify the final state of the escrow. 

The first two items are identical to spending a P2PKH input, and the next two are unique to LocalCryptos.

The “action” byte will tell the script which hashes to check. In a standard trade, a buyer will use a “release by seller” code (0x01). If the first item is 0x01, the script will expect the buyer’s public key and the seller’s release code.

There are four escrow actions, representing all the scenarios of an escrow:

Action Byte Expected code Expected PubKey
ReleaseBySeller 0x01 Seller’s Buyer’s
ReleaseByArbitrator 0x02 Arbitrator’s Buyer’s
ReturnByBuyer 0x03 Buyer’s Seller’s
ReturnByArbitrator 0x04 Arbitrator’s Seller’s

Recalling the fee output

In the case of an escrow cancellation, LocalCryptos doesn’t charge a a fee. We only take a fee if the Bitcoin goes to the buyer.

With a return escrow code, the above signature is also compatible with fee output. The seller can spend the fee output in the same way they spend a recalled escrow UTXO.

Settle when you spend

Clicking “Release” in a LocalCryptos Bitcoin escrow doesn’t change anything on the blockchain. It allows the receiving party to spend the coins from escrow (i.e. unlock the UTXO).

The receiving party can choose to spend these coins immediately, or they can wait. In the user-interface, an escrow UTXO will appear in the web wallet next to your regular addresses.

After spending the coins, the escrow’s completion is permanently etched into the blockchain.

Releasing without an internet connection 

All the buyer needs is the seller’s secret code to claim the escrow.

We can add a method for a seller to reveal their code without an internet connection. The code is too long to write on paper, but it’s a perfect length to store in your phone or a QR code.

LocalCryptos is going to add two new ways to reveal a code:

1. Reveal the code by sending it in an SMS to one of our phone numbers. Our servers can compute the code’s hash to identify which trade it belongs to, then forward the code to the buyer.

2. Show a QR code to the buyer. The buyer doesn’t need the internet to verify the code; they only need a piece of software that can calculate a hash. This method will enable in-person exchanges when neither has a stable internet connection. It will be useful in Venezuela, where national power outages are a common occurrence.

We’re working on the first option today. The second option will become available when we release the LocalCryptos mobile app.

How secure is this?

The best supercomputer can’t break a standard P2PKH address, which uses a single hash. The key space of a hash160 is 160 bits. We use two hash160s: one for the P2PKH part, and one for the hashed secret code. This brings the key space to 320 bits for external attackers. (The math is more nuanced than that, but you get the idea.)

LocalCryptos Bitcoin escrow addresses are—for all intents and purposes—impossible to crack.

How much does it cost?

We’ve reduced network fees by upgrading to Segregated Witness. However, the cost of a Bitcoin transaction is often higher than Ethereum.

The cost of a Bitcoin escrow depends on the network’s congestion. For small trades under $10, we recommend choosing another crypto.

Is this the final version?

This is likely not the final version of the LocalCryptos’ non-custodial Bitcoin escrow. In the future, we plan to optimise the system for cost and speed.