Technical Whitepaper. No, we’re not minting a useless ICO‐token.

LocalCryptos is a peer‐to‐peer custody‐less encrypted marketplace for buying and selling ether over‐the‐counter. This document was written when LocalCryptos was named LocalEthereum. In late 2019, the platform became LocalCryptos to support more cryptocurrencies including Bitcoin. To learn about how our non‐custodial Bitcoin escrow works, see here. LocalEthereum is the most private, secure and intuitive way to swap ether with others for local currency.
This document describes the technical specifications of LocalEthereum and the unique design decisions behind the platform. If you only want to use the platform, click here.



People use LocalEthereum to exchange ether for fiat money without counter‐party risk by using a trust‐less escrow concept. The peer‐to‐peer platform allows traders to skip the middle‐man and trade on their own terms, whether that be via bank transfer, any online payment service, cash in person, or any other mutual agreement between two people.

Trades executed using the LocalEthereum marketplace are confidential via end‐to‐end encryption, and, unlike traditional over‐the‐counter platforms, the escrow mechanism is decentralised. The only time the escrow arbiter can step in and transfer ether is if it receives explicit digital permission to resolve a dispute — but even then the smart contract code only allows the arbiter to transfer escrowed ether to one of the parties.


Centralised Exchanges: Not Your Keys. Not Your Coins. While cryptocurrency has succeeded in enabling people to conduct peer‐to‐peer transactions in a trust‐less way, there has been no such solution for entering or exiting the crypto‐economy. Because of the inherently insecure properties of centralised exchanges, billions of dollars have been stolen, lost, and destroyed in the hands of even the most popular services.

Although the number of cryptocurrency exchanges has grown, there has been little progress in eliminating trust from cryptocurrency on‐boarding. Instead, blockchain financial innovators are replicating the vulnerable systems of Wall Street stock exchanges and banks — the same systems that provoked the global financial crisis, and the same systems that motivated the genesis of Bitcoin.

1.2.1Secrets are meant to be kept

The prime advantage of cryptocurrency over fiat is the elimination of the need for an intermediary. Instead of relying on an institution to keep a record of your balance (i.e. a bank), blockchains use a distributed ledger of transactions verified by a large network of computers.

The result is that it’s impossible for anybody to access your digital wallet without possession of your secret key, regardless of other circumstances. As long as your wallet’s private key is kept secret, your cryptocurrency is secure — the idea is similar to keeping an ounce of gold in a home safe. Instead of trusting somebody to hold your cryptocurrency for you, you only need to have trust in the laws of mathematics and the thousands of computers auditing the ledger around the clock.

But when you deposit or buy cryptocurrency on a centralised exchange, you don’t hold the private key to it. Instead, you need to trust the exchange, in the same way that you trust a bank, to hold on to your money and keep an accurate record of your balance.

When you keep Bitcoin or Ether on a centralised exchange, you miss out the advantages of cryptocurrency. Instead, your pseudo‐cryptocurrency now faces a big chance of being lost or stolen because of the compounding risks of centralised exchanges:

  1. Centralised exchanges are often the subject of heists. Because of the irreversible nature of cryptocurrency, it’s very attractive to cyber‐criminals. Billions of dollars worth of cryptocurrencies have been stolen from centralised exchanges.
  2. Centralised exchanges often cause serious accidents. There is a constant inflow of inexperienced entrepreneurs attempting to cash in on the new technology. Millions have been lost due to fatal, simple mistakes.
  3. Centralised exchanges can rarely insure deposits. Most governments insure bank account deposits, however that isn’t the case for cryptocurrency. More, insurers stay afar from the industry because of the previous points.

While any of these threats alone should be enough to make users think twice before trusting an exchange, these risks together are a recipe for disaster. Despite this, most of the risk people face still stems from trusting financial institutions, nearly defeating the purpose of decentralisation. Headlines about centralised exchanges making multi‐million‐dollar mistakes appear almost every month.

In the very first paragraph of the original Bitcoin whitepaper, Satoshi Nakamoto pondered that cryptocurrency would mean people wouldn’t need to trust a financial intermediary anymore:

A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution.

Similarly, Vitalik Buterin, co‐founder of Ethereum, made an observation before the public launch of the decentralised computing platform:

I was acutely aware that many of the major problems still plaguing the Bitcoin ecosystem, including fraudulent services, unreliable exchanges, and an often surprising lack of security, were not caused by Bitcoin’s unique property of decentralization; rather, these issues are a result of the fact that there was still great centralization left, in places where it could potentially quite easily be removed.

LocalEthereum is a non‐custodial platform, meaning it never asks for your private key(s). We don’t ask you to trust us to hold on to your balance, because we know central reserves have a bad track record, plus the burden of safeguarding a large store of ether poses many other hurdles better avoided. Your LocalEthereum wallet is kept encrypted in your web browser.

1.2.2Over-the-counter crypto

2008 Since the invention of cryptocurrency, early adopters have been seeking alternatives to on‐exchange trading. Having a central authority like an exchange keep record of customer balances and identities in a database, and facilitate transfers of local currency to cryptocurrency and vice versa, has always been viewed as counter‐intuitive to the peer‐to‐peer nature of Bitcoin.

Over the counter trading, which is where two parties exchange with one another directly without the supervision of an exchange, has been an popular in cryptocurrency since Bitcoin’s beginnings. OTC trading grants flexibility, as there are no restrictions on what you can swap for cryptocurrency, and person‐to‐person transactions don’t involve a financial intermediary.

However, exchanging cryptocurrency with strangers online and in person carries a major risk: how do we ensure that both parties are going to hold up their end of the bargain, and not vanish as soon as money or cryptocurrency hits their account? This exposure is known as counter‐party risk.

2010 One of the first innovations in early Bitcoin OTC trading was an online “web of trust” platform. The platform known as #bitcoin‐otc is a simple system where traders can leave other traders a public rating, and a website will calculate the cumulative trust received for each user. If a user has a higher rating, they’re more reputable and less likely to run away. This system worked most of the time, but it wasn’t flawless: people with high ratings would “cash in” on their reputation and pull off bigger heists, and scammers would create bogus accounts to manipulate the ratings.

2012 The next evolution was an escrow system by LocalBitcoins in 2012. Instead of exchanging directly with each other, sellers of Bitcoin could now transfer BTC to LocalBitcoins first, then release the Bitcoin from escrow once confirming payment from the buyer. If the buyer didn’t send money, the seller could ask LocalBitcoins to intervene and return the coins. Similarly, if the buyer claimed to have sent money but the seller won’t release the escrow, they could ask LocalBitcoins to review their proof of payment and force the seller to release the Bitcoins. The escrow system combined with a robust reputation system worked well.

But there was a huge problem: by offering an escrow service, LocalBitcoins made itself the new intermediary. If LocalBitcoins were hacked, went offline, or its founders decided to run away, all of the Bitcoins in escrow could be lost. There is a tremendous amount of trust placed in this new centralised escrow system. With the escrow system, users no longer held the keys to their Bitcoin; instead, they’re trusting LocalBitcoins to hold Bitcoins for them, like a bank or an exchange. The escrow system didn’t fix counter‐party risk—it only moved it somewhere else.

2017 The launch of Ethereum introduced programmable money, which meant that many ideas not possible on the Bitcoin blockchain could now be achieved with Ethereum smart contracts. Building on the ideas of a web of trust and an escrow system, LocalEthereum has created a peer‐to‐peer OTC trading platform that keeps users in control of their cryptocurrency at all times. With the use of an Ethereum smart contract, LocalEthereum has invented an escrow system that never takes custody of the ether in escrow. The platform’s unusual escrow mechanism doesn’t involve an intermediary, as financial services involving trusted middlemen are antithetical to cryptocurrency.

1.2.3Sheltering from mass intrusion

Revelations about mass communications surveillance have made consumers more aware of their online privacy. In 2013, it was revealed that the U.S. government had been secretly collecting data from internet companies. The NSA’s PRISM program siphoned data from a broad range of companies including Google, Microsoft, Yahoo, Facebook, YouTube, Skype, and numerous consumer upstream providers. In addition to wiretapping, prominent online services have been the subject of intrusions by anonymous hackers. Troves of sensitive information have been stolen from Equifax, Yahoo, eBay, Adobe, Ashley Madison and others in some of the largest data breaches in recent history.

Early instant messaging apps provided hardly any protection from these types of breaches. Although messages were sent over encrypted channels, the channel was usually between the client and a service provider. The service provider, such as Facebook, Skype, or Google, was able to read the contents of all messages delivered through the service. If the provider was breached, histories of plain‐text messages could be extracted in mass.

In response to the NSA leaks, cypherpunks, developers, and scientists accelerated research into new techniques to provide security in a modern digital world, where internet upstream providers nor service providers can be trusted. Many widespread instant messaging apps now employ advanced cryptographic techniques to achieve end‐to‐end confidentiality, including OTR, Apple’s iMessage, WhatsApp, Signal, and Telegram.

LocalEthereum applies the techniques of end‐to‐end encryption and post‐compromise secrecy to create a new type of online marketplace where its users’ financial details and personal information are kept confidential. Messages between users are end‐to‐end encrypted, meaning that even in the event of a compromised host, no sensitive information could be extracted.

1.3How trading works

The experience of buying or selling on the LocalEthereum platform differs from an exchange or an ordinary escrow provider. Unlike an exchange, LocalEthereum doesn’t accept deposits or process withdrawals and there is no automatic matching algorithm. Unlike a normal escrow arrangement, LocalEthereum doesn’t hold any ether or fiat in trust; the ether portion of the trade is put in trust through a decentralised escrow mechanism.

Using LocalEthereum.com, an ordinary trade works like this:

Posting an offer 0. Alice posts a public offer to buy or sell ether. Offers can be posted publicly for anyone else to find. An offer is an advertisement stating a trader’s intentions to swap a range of ether for local currency (for example, “I am willing to buy up to $800 worth of ETH with GBP.”).

Opening a trade 1. Bob responds to the offer to exchange a specific amount of ether. When a trader responds to an offer, a trade is opened between the two parties. The person responding to the offer enters the exact amount of ether they want to buy or sell, and the rate is locked into the trade.

Now, Alice and Bob can communicate with each other via an encrypted conversation.

Encrypted chat 2. Alice and Bob confirm and agree on the terms of the trade. The two parties introduce each other and chat about the exchange. The seller will let the buyer know if they have any non‐standard terms (for example, a maximum limit on the buyer’s first trade or a request to verify their identity), and both parties will come to a mutual agreement on how the exchange is going to take place.

Placing ether in escrow 3. The seller places the funds in escrow. Once the parties are ready to continue, the seller will place the ether in escrow. This takes place with a transaction to an Ethereum smart contract (with one click). The escrow provides proof‐of‐funds to the buyer and allows for a much safer trade.

As soon as the ether is escrowed, a payment window countdown begins. The buyer is expected to initiate payment within this window, or else the seller will have the ability to cancel the escrow. This prevents the seller’s funds from being locked due to an unresponsive buyer.

Payments happen outside LocalEthereum 4. The buyer makes payment directly to the seller. Payments happen outside of the LocalEthereum platform. Alice and Bob have a direct line of encrypted communication, so they can discuss where the buyer is supposed to send money to. Unless Alice or Bob have a grievance later, the payment will be invisible to LocalEthereum staff.

Knowing that the ether is in escrow, the buyer can safely send money to the seller — if the seller doesn’t hold their end of the bargain, a dispute resolution process can assist them. Without the protection of escrow, the seller could simply vanish after the buyer sends money.

Once the buyer has initiated payment, they can cancel the payment window by clicking “Mark as paid”. The seller no longer has an opportunity to cancel the escrow.

After payment, release the escrow 5 – Scenario A. The seller successfully confirms the payment and releases the escrow. Trade complete! The ether is released from the escrow to the buyer and both parties are happy.

Now each party can give each other feedback to contribute to a web of trust, which makes the platform safer.

Raising a dispute 5 – Scenario B. Somebody raises a dispute. Either party can raise a dispute to call in a third‐party arbitrator (currently LocalEthereum staff) to make a resolution. The arbitrator is given two keys: one to decrypt the messages, and another to resolve the escrow.

The arbitrator may ask for evidence, such as proof of payment from the buyer, to help determine which party is the rightful owner of the ether. It might take minutes, hours, days or even weeks for them to reach a decision depending on the complexity of the dispute.

Once the arbitrator is satisfied one way or another, they can direct the ether in escrow to be released to one of the parties. The smart contract code doesn’t allow them to send the ether anywhere else.


  1. LocalEthereum is easy for anybody to use, no matter their background. The platform is built to work on all web browsers without any extra software, and it’s not necessary to read this document to understand how to use the platform.
  2. LocalEthereum is immune to surveillance. The LocalEthereum platform leverages an end‐to‐end encryption scheme to protect messages in transit from eavesdropping by hackers and oppressive regimes.
  3. LocalEthereum doesn’t involve counter‐party risk. While billions of dollars have been stolen from centralised exchanges, users are in safe hands on LocalEthereum because the platform never holds any ether.
  4. LocalEthereum enables super‐fast exchanges. Enabling person‐to‐person payment methods allows super‐fast settlements — often less than three minutes.
  5. LocalEthereum lets people trade on their own terms. Fiat currency transfers are external and invisible to LocalEthereum — you can exchange using your bank account, physical cash, PayPal or something else.
  6. LocalEthereum connects the unbanked population. The LocalEthereum platform allows people to exchange with cash, giving improvised communities access to cryptocurrency as a financial alternative.
  7. LocalEthereum earns people a living. LocalEthereum allows you to become your own exchange2. Many of the top traders earn a daily income on the difference between the buying and selling price.
  8. LocalEthereum has no painstaking approval process. Users can exchange immediately after signing up. We’re not an exchange and we have no legal obligation to collect the identities of users.1
  9. LocalEthereum is where everyone else is. Since the platform launched in late 2017, it’s already seen more than 100,000 people in more than 100 countries. There is no other peer‐to‐peer Ethereum marketplace with more users than LocalEthereum.
  10. LocalEthereum accelerates worldwide adoption of cryptocurrency, for the aforementioned reasons.

1. But you might have an obligation, primarily depending on where you live.2

2. Different countries and states have different legislation: for example, you may have a requirement to keep specific records, apply for a particular license, or collect and verify the identities of people to help prevent money laundering. These rules vary by country, so be sure to do your research.

1.5Design requirements

Before stepping into the technical specification, we should understand why some decisions were made over others. As will be better explained later, LocalEthereum isn’t completely decentralised, however, this fact doesn’t mean the platform isn’t safe from hacking, surveillance, and censorship.

Long before the development of LocalEthereum began, its creators laid out a set of rules that the platform must follow. These are the principles we decided must be characteristics of the finished product to ensure a frictionless experience. Each design requirement below has been met:

Extreme portability.

LocalEthereum must work in all modern web browsers without extra software. Needing to install a browser extension is a big hurdle for users who aren’t tech‐savvy, or who don’t trust third‐party software on their device.

Also, relying on external software creates device‐compatibility issues and could pose security threats outside of LocalEthereum’s control.

Multi‐device synchronicity.

LocalEthereum should work seamlessly across many devices. People trade on the go, especially when dealing with physical cash. The platform needs to work on desktops, laptops, tablets, and phones at the same time.


Users must not need to be online to accept a new trade or receive an encrypted message. This means that secure information must be able to be exchanged entirely asynchronously. To accomplish this, it’s necessary to have somewhere to reliably store encrypted data and allow users to retrieve it later.

Expectancy of crypto‐bankruptcy.

Users must not need any ether in their wallet to use any part of LocalEthereum. This requires some trickery because every interaction with the blockchain costs ether as payment for computational work.

The platform will be many people’s first interaction with cryptocurrency, so we expect many buyers to lack any ether at all, which means they won’t be able to interact with smart contracts at their own expense. Because of this, LocalEthereum has a system allowing users to interact with the blockchain at no up‐front cost via a proxy mechanism.

Self‐explanatory UX.

Users shouldn’t need to read this document to understand how to use LocalEthereum. While every decentralised service has a duty to educate users on the basics of distributed ledger technology, we recognise that the majority of the general public is probably not going to understand every detail, and that shouldn’t matter.

Lots of effort is going towards making sure the user‐interface is friendly for all users, no matter their background.


We don’t need or want a LocalEthereum ERC20 token. The LocalEthereum platform is designed using only Ethereum’s intrinsic currency (ether). There are no plans to create an ERC20 token.

Our decision to not have a token sale was published on our blog. Think twice if you see an offer to invest in LocalEthereum: it’s definitely a scam.

1.6(De)centralisation hybrid

LocalEthereum is not a completely decentralised system. Instead, it’s a blend of centralised and decentralised components to construct a cocktail of security and usability. LocalEthereum is centralised in the same way that end‐to‐end encrypted instant messaging apps Signal, Telegram, Wickr, Viber and WhatsApp each rely on centralised servers.

The decision to avoid a full decentralised system is to be able to meet all of the important design constraints described above. Many of these constraints will become less of an issue in the future as the world transitions to decentralisation; the long‐term goal is to decentralise more components over time as the Ethereum ecosystem matures.

For the purposes of explaining the whole project, our design is split into three components. You’ll notice that this document has been divided by each system fits into these categories:

In more detail…

1.6.1Centralised layer

The Centralised layer (a.k.a. “the API”) is mainly used to store and transport encrypted payloads and metadata. The centralised layer is made up of our API, servers and databases. It’s important to note that LocalEthereum is carefully designed so that no sensitive information is disclosed to the centralised layer.

The LocalEthereum system keeps in mind that centralised systems can be compromised or replaced. For example, the front‐end won’t display any messages unless the sender signature is valid, and no funds can be stolen in the event of a hack.

What to expect:

  • E‐mails & SMS notifications
  • Offers
  • Bans & anti‐spam
  • Storage and transport of encrypted blobs

1.6.2Cryptography layer

The Cryptography layer is used to secure and verify all kinds of sensitive information.

This includes, for example, signing and encrypting messages between users, creating digital signatures for various purposes, unlocking encrypted wallets and signing Ethereum transactions. These computations happen off‐chain in the web browser using the Web Crypto API.

What to expect:

  • Diffie–Hellman key exchanges
  • Encrypted messages
  • In‐browser ether wallet

1.6.3Blockchain layer

The Blockchain layer involves anything to do with the blockchain. This includes any interaction with our smart contract(s). In the first version, LocalEthereum only uses the public Ethereum blockchain.

What to expect:

  • Escrows via a smart contract

Q: Why don’t we use the blockchain for everything?

A: The term “blockchain” has become a mostly‐meaningless buzzword in 2018. To answer this question, it’s worth recounting what blockchain solves and where it might be inappropriate.

A blockchain is an immutable public ledger. Blockchains let us agree on the order of a set of records without trusting a central authority. The introduction of blockchain technology solved the double‐spending problem of digital cash and paved the way for the cryptocurrency boom we know today.

Blockchain technology has proven to be a tremendous invention, but it was never meant to be the be‐all‐end‐all to security. The underlying mechanisms that Ethereum and Bitcoin use to verify and secure information (known as Elliptic‐curve cryptography) were invented decades earlier.

When there is an advantage to having a public immutable record of something, blockchain is the answer. When there no benefit, it doesn’t make much sense to use a blockchain. When this is the case, stand‐alone cryptography is the solution to your problem. In fact, adding blockchain into the mix only creates new problems:

  • Erosion of privacy (everything on the blockchain is public)
  • Cost (every change to the ledger involves a transaction fee)
  • Untimeliness (transactions can take minutes or longer to propagate)

Hence, LocalEthereum currently only uses blockchain technology where its application is helpful and the pros outweigh the cons.

2Centralised layer

2.1Introduction to the API

The Australian company behind LocalEthereum maintains the API. It operates over a global content delivery network to ensure quick speeds around the world.

The API is currently available at:


Q: Why is the API on a separate domain name?

A: Most of the API infrastructure is with Amazon Web Services. Unfortunately, Amazon doesn’t currently support DNSSEC on its hosted DNS zones. Because of the risk of a DNS hijacking attack (as seen with MyEtherWallet in April 2018), we decided to put the back‐end on a slightly less secure domain name. The front‐end is far more important to protect than the API, and it’s important that we protect its integrity with every security measure available.

Q: Why isn’t the API documented?

A: The API isn’t documented because it’s still undergoing rapid development as we add new features. When breaking changes slow down, we’ll begin to document the API endpoints.


To use the LocalEthereum platform, you must first create an account. Your account is known by a public username that can’t be changed.

Accounts involve client‐side cryptography rather than a password to protect data. They also have an associated e‐mail address and optional phone number to receive notifications.


An offer is a public advertisement to buy or sell ether. Any user can submit an offer to the platform for others to find.

Offers have the following properties:

To begin a trade, a user must respond to an offer. It is not possible to enter into a trade with another user if they haven’t posted an offer.


The difference between a trade and an offer is that an offer is a public advertisement to buy or sell ether that anyone can respond to, whereas a trade is created when you respond to an offer. Also:

Trade messages and transfers involve cryptography, but there are metadata properties that do not. For example, the local currency code is not encrypted. The API can see who you are trading with and at what exchange rate, but it can’t see the contents of your messages.

The complete list of unencrypted metadata on trades includes:


Every user has a reputation attached to its public profile. This includes:


The LocalEthereum team has a responsibility to remove suspicious users from the platform. Users can be suspended from trading if they’re found harassing, spamming or attempting to defraud others.

Good users can report bad users to staff with a simple “Report user” button, which will volunteer encrypted messages for review. Staff will also suspend a user who engages in activity that seems to be of fraudulent nature.

2.7Encrypted payloads

The API stores and transports encrypted payloads between users. This data is not decipherable without the necessary keys, which the user keeps on their device.

Because the data is end‐to‐end encrypted, it’s impossible for LocalEthereum staff or any intruder to decipher any encrypted information hosted on its servers.

3Cryptography layer


The ability to do cryptography in the browser opened a new universe of web applications. All modern web browsers in 2018 include the Web Crypto API, a standard providing JavaScript interfaces to perform cryptographic operations like hashing, symmetric and asymmetric encryption, and verifying digital signatures.

These cryptographic operations are used in various ways by the LocalEthereum platform to secure information, and to enable end‐to‐end secret messaging. The goal is to allow the user to retain complete control over the most important aspects of their account, while only revealing a minimal metadata to LocalEthereum servers (or anybody else who might be listening).

Each of the techniques used by LocalEthereum were inspired by renowned systems such as the Off‐the‐Record Messaging protocol, Open Whisper System’s Signal Protocol, and Blockchain.info’s encrypted web wallet. These systems are already in widespread use and have been reviewed by prominent cryptographers and developers.

The primary attack vector we need to consider when managing keys in the browser is the possibility of malicious code being injected into the website. This could be executed by two methods: the first being a hijacking or man‐in‐the‐middle attack against our web server, and the second being an attack of any third‐party JavaScript libraries included on the site. To limit the attack surface, we’ve eliminated the second method by choosing to not use any third‐party‐hosted scripts such as Google Analytics or Google Maps APIs on the website, as we can’t guarantee they’ll be safeguarded. The other attack vector is mitigated by utilising all the HTTP and BGP security mechanisms available, such as x509 public key pinning, same‐origin policies, and DNSSEC. In the future, we’ll offer open source and downloadable releases of our front‐end so users can open the website locally instead of relying on our web server.


Users can log in to their account with a simple username and password. However, while the log‐in form might look and feel like an ordinary website, it actually involves complex cryptography. Under the hood, the password used to log in is actually a sort of private key that never leaves the web browser.

The password selected by a user is transformed into a secure key using a key stretching process, and then another key is encrypted with the stretched password‐key. This solution is inspired by the Blockchain.info web wallet, which uses a similar PBKDF2‐based mechanism.

Public identity keys

Every account has a permanent public identity key pair, which is used to authenticate ownership of various other things (e.g. ephemeral Ethereum addresses and key pairs).

Currently, the LocalEthereum website displays a multi‐coloured identicon next to usernames to represent their public identity key, making any change clearly noticeable. Despite this, users are encouraged to keep a memory of user identity keys of their trade partners to ensure their public keys haven’t changed since. While the API won’t let a user swap their public key, there is a possibility of a man‐in‐the‐middle or server hijacking attack to alter the centralised public key infrastructure. Soon, LocalEthereum plans to use local and external public key management systems to minimise this attack vector (including possibly using a blockchain‐based PKI or the Ethereum Name Service to map usernames to identity keys).

3.2.1Creating an account

Creating a new account involves generating a fresh public and private key pair. To prove ownership of the new public key, the API requires the user to sign a specific message while signing up.

To sign up, the first step is to fetch the message needed to be signed from the API. This is called an ephemeral registration “token” and it contains two parts:

  • TokenId — A unique identifier to the registration token.
  • TokenNonce — Some message we need to sign with the key we’re going to create.

Once the token is grabbed from the API, the user can generate a new account key pair. The first step is to generate a random AccountKeyRoot. This random 32‐byte key is the seed to everything in the account; it’s crucial to keep this secure.

Once the AccountKeyRoot is created, two more keys can be derived from it:

  1. AccountKeyIdentityPrivate is the function of SHA256(AccountKeyRoot, "identity"). This will be used for signing messages using secp256k1 cryptography.
  2. AccountKeyEnc is the function of SHA256(AccountKeyRoot, "end"). This will be used for AES‐256 encryption.

The AccountKeyRoot key must be encrypted before being stored on LocalEthereum’s server. However, a user’s password will not do: we need to turn a password into something more secure to make brute‐force attacks near‐impossible.

To transform the user’s simple password (Passphrase) into something suitable for cryptography, the key derivation function PBKDF2 is used. This process, known as “key stretching”, adds complex computational work to make password cracking very difficult. Passphrase is stretched using PBKDF2 with the salt PassphraseSalt and hashed PassphrasePBKDF2Iterations times to create SecretKey.

Once SecretKey is created, it’s used to encrypt AccountKeyRoot using AES‐256. The random initialisation vector (16 random bytes) is kept as SecretIv.

Finally, a digital signature of the API’s token is created using the AccountKeyIdentityPrivate key. TokenSignature is an ECDSA signature of the keccak‐256 hash of TokenNonce.

The non‐sensitive information is then submitted to the API (everything marked “secret” below is not uploaded):

  • Passphrase (secret) — User‐inputted plain password.
  • SecretKey (secret) — A more secure 32‐byte key derived from stretching Passphrase using PBKDF2 with the salt PassphraseSalt using PassphrasePBKDF2Iterations iterations.
  • AccountKeyRoot (secret) — A randomly generated 32‐byte account root key. This is the seed to everything in your account; it’s crucial to keep this secure.
  • AccountKeyIdentityPrivate (secret) — A key that is the SHA‐256 hash of the concatenation of AccountKeyRoot and “identity”. Used for signing and authentication.
  • AccountKeyEnc (secret) — A key that is the SHA‐256 hash of the concatenation of AccountKeyRoot and “enc”. Used for encryption.
  • AccountKeyIdentityPublic — Using the SECP‐256k1 curve, an ECDSA public key that corresponds to AccountKeyIdentityPrivate. Uniqueness is enforced here; no two users can have the same AccountKeyIdentityPublic.
  • PassphraseSalt — A random 16‐byte salt used for the PBKDF2 process.
  • PassphrasePBKDF2Iterations — The number of PBKDF2 iterations to use to stretch the Passphrase to more secure key material. A higher number results in a slower key stretch process, weakening brute‐force attempts. Any number above 50,000 is permitted.
  • SecretIv — A randomly generated 16‐byte initialisation vector.
  • SecretCiphertext — The ciphertext of AccountKeyRoot encrypted to SecretKey using AES‐256.
  • TokenSignature — An ECDSA signature of the keccak‐256 hash of TokenNonce by AccountKeyIdentityPrivate, to verify ownership of AccountKeyIdentityPublic.
  • Any other non‐privy account information (e.g. username and e‐mail address).

3.2.2Logging into an account

Logging into an account requires two‐factor authentication. E‐mail is the default two‐factor authentication method and there is optional support for time‐synchronized OTP (e.g. apps like Google Authenticator). SMS is not supported as a two‐factor method because it’s unsafe.

Two‐factor authentication is necessary to protect the encrypted version of AccountKeyRoot (i.e. SecretCiphertext and SecretIv). Although the decryption process has been slowed by the key stretching process, passwords are still theoretically susceptible to brute‐force attacks (especially dictionary‐based attacks on weak passwords). Hiding the key behind a secure two‐factor method eliminates this hypothetical attack vector.

The two‐factor authentication process is executed by the API, meaning that there is a small element of trust here. If the database were to be hacked, an attacker could get all the encrypted private keys. Still, these passwords are salted and stretched so rainbow‐table attacks aren’t possible and other brute‐force attacks are very hard. In this hypothetical hack scenario, it would be difficult for the hacker to obtain passwords except those that are very weak (e.g. “password”, or a re‐used password from a separate compromised database).

This is the log‐in process for two‐factor authentication by e‐mail:

  1. The user enters their username and password.
  2. The username is delivered to the API in a request for the associated encrypted private key. The password is ignored for now.
  3. An e‐mail is sent to the e‐mail address associated with the username, containing a secure link to progress the login request to the next stage.
  4. Once the link in the e‐mail is clicked, the original log‐in window receives SecretCiphertext, SecretIv, PassphraseSalt, and PassphrasePBKDF2Iterations.
  5. The user‐entered password is salted and stretched as required to derive SecretKey. The stretching process must be exactly the same as the process used when signing up.
  6. If the SecretKey can be used to calculate AccountKeyRoot correctly and hence AccountKeyIdentityPublic, then the login attempt was successful. If not, the user is prompted to try another password and step 5 is repeated.
  7. A nonce provided by the API is signed by AccountKeyIdentityPrivate to prove ownership of the key pair, and a new session is issued.
  8. AccountKeyRoot is stored in the browser’s Web Storage API for the duration of the session.

3.2.3Changing a password

Changing an account password involves re‐encrypting AccountKeyRoot with a new SecretKey.

The AccountKeyRoot key can never be changed, hence neither can the identity key or account encryption key be changed.


There are some known limitations of our implementation to keep in mind.

  • Brute‐force attacks — Brute‐force attacks are very difficult because of the key stretching process, but the threat shouldn’t be ignored. No matter how large the warnings are, some users are still going to use weak or reused passwords. As further protection, two‐factor authentication is mandatory before the log‐in process.
  • Two‐factor confusion — The fact that second‐factor authentication comes before attempting the password is opposite to nearly every other website out there. This will cause some confusion to users.
  • Lost passwords — If you lose your password, there is no way for staff to manually recover access to an account. It’s the same as losing the private key to your cryptocurrency wallet.
  • No recovery from break‐ins — Changing the account password after a break‐in has essentially no effect; the account is forever compromised as the root key cannot change. If an account is stolen, the victim must make a new account and start over.


There are two components to any trade: the conversation, and the escrow. Although they’re both linked to the same trade, the two are cryptographically unrelated. Each component is independent of the other:

When referring to aspects of trades, the following terminology is used:

3.3.1Maker keys

Every LocalEthereum user will preemptively generate hundreds of key pairs, sign them, and publish them to the LocalEthereum API. These are called “maker keys” and they allow people to initiate trades with and send encrypted messages to accounts that are offline while maintaining post‐compromise secrecy.

These maker keys:

  • Are similar to “pre‐keys” described in the Signal Protocol.
  • Are ephemeral (used only once per trade).
  • Are bulk‐generated and signed in advance.
  • Allow asynchronous key agreements.

This solution is inspired by Open Whisper System’s Signal Protocol, which is a trusted open source standard endorsed by Edward Snowden and others. To learn more about the Signal implementation, read Open Whisper Systems’ Forward Secrecy for Asynchronous Messages.

Creating a maker key is simple. The first step is to generate a private key and name it MakerKeyPrivate. Next, calculate the corresponding ECDSA public key MakerKeyPublic. Finally, sign the keccak‐256 hash of the public key using AccountKeyIdentityPrivate and encrypt the private key using AccountKeyEnc. Everything except the unencrypted private key can be uploaded to the API for storage.

Maker key illustration

Each account has a queue of maker keys maintained by the API. When somebody else wants to begin a new encrypted session with you, they’ll retrieve a maker key from your queue and use it for encryption.

Example code to generate and sign a single maker key:

Example № 1const ethereumjsUtil = require('ethereumjs-util');

// Generate a new private key (make sure this is a valid ECC private key)
const MakerKeyPrivate = randomBytes(36);
// Calculate the corresponding public key
const MakerKeyPublic = ethereumjsUtil.privateToPublic(preKeyPrivate);
// Generate a random IV for encryption (any 16 bytes)
const MakerKeyPrivateIv = randomBytes(16);
// Encrypt with AES-256 using the account's encryption key
const MakerKeyPrivateCiphertext = encryptAES({
  payload: MakerKeyPrivate,
  key: AccountKeyEnc,
  iv: MakerKeyPrivateIv,
// Calculate a keccak-256 hash of the public key
const MakerKeyPublicHash = ethereumjsUtil.sha3(
// Sign that hash using the account's public key
// This is so that people can verify that this key indeed belongs to you
const MakerKeySignature = ethereumjsUtil.ecsign(
// Turn that signature into the RPC format, as the API prefers
const MakerKeySignatureRPC = ethereumjsUtil.toRpcSig(
); the queue

When the queue of maker keys begins to run low, it will automatically be refilled with new keys when the user returns to LocalEthereum. This process of generating maker keys is usually invisible to the end‐user.

If the queue becomes empty (for example, if the user hasn’t logged in to check hundreds of new trades), then their offers will be hidden from the platform. After they return and replenish their queue of maker keys, their offers will resume. It’s not possible to open a trade with a user who doesn’t have any maker keys left.

A few hundred maker keys are typically more than enough at any time. key properties
  • MakerKeyPrivate (secret) — A 32‐byte private key used for elliptic‐curve cryptography.
  • MakerKeyPublic — The ECDSA public key corresponding to MakerKeyPrivate.
  • MakerKeyPrivateIv — A random 16‐byte initialisation vector used for encryption.
  • MakerKeyPrivateCiphertextMakerKeyPrivate encrypted using AES‐256 with the secret key AccountKeyEnc and IV MakerKeyPrivateIv.
  • MakerKeySignature — An ECDSA signature of the keccak‐256 hash of MakerKeyPublic by the account’s public identity key (AccountKeyIdentityPrivate).

3.3.2Creating a trade

To initiate a trade, the taker first needs to fetch:

  1. The maker’s AccountKeyIdentityPublic
  2. A maker key from the maker’s queue (MakerKey). Once a maker key has been exposed to somebody, it’ll never be used again — even if the trade doesn’t go ahead.

The first thing the taker needs to do is verify the signature attached to the maker key. The maker key should be correctly signed by the maker’s account identity key. Next, the taker needs his own:

  1. Brand new one‐time ECDSA key pair (TakerKey)
  2. Ethereum wallet address (TakerAddress)

Taker keys are identical to maker keys except that they’re created by the user responding to an offer and are signed in a different manner. Unlike maker keys, a taker key is not generated in advance; it’s created on‐the‐spot by the user opening a trade.

To authenticate the new TakerKey, while at the same time proving the intention to begin a trade with the maker, the taker will create a signature of the keccak‐256 hash of the concatenation of:

  1. The public key of the maker key (MakerKeyPublic)
  2. The public key of the taker key (TakerKeyPublic)
  3. The taker’s address (TakerAddress)

These three properties are tightly concatenated, hashed using keccak‐256 and signed using the taker’s AccountKeyIdentityPrivate ECDSA key. This new signature, known as the taker signature (TakerSignature), is a key component to the asynchronous key agreement process: it allows the maker to authenticate the new key and address in the same way that the taker validated the maker’s key.

Taker key illustration

The private key for the TakerKey needs to be encrypted with AccountKeyEnc using AES256‐CBC (TakerKeyPrivateCiphertext and TakerKeyPrivateIv).

Now that the taker has everything needed to open a secure session with the maker, they’ll post something like this to the API:

Example № 2{
  "offer_id": "...",
  "maker_key_public": "MakerKeyPublic",
  "taker_address": "TakerAddress",
  "taker_key": {
    "public": "TakerKeyPublic",
    "encryptedPrivate": {
      "ciphertext": "TakerKeyPrivateCiphertext",
      "iv": "TakerKeyPrivateIv"
  "taker_signature": "TakerSignature",
  "... non-privvy trade params (e.g. pricing)"
} properties

To recap, the properties of a taker key and signature include:

  • TakerKeyPrivate (secret) — A 32‐byte private key used for elliptic‐curve cryptography.
  • TakerAddress — The Ethereum address that the taker is going to use for this trade.
  • TakerKeyPublic — The ECDSA public key corresponding to TakerKeyPrivate.
  • TakerKeyPrivateIv — A random 16‐byte initialisation vector used for encryption.
  • TakerKeyPrivateCiphertextTakerKeyPrivate encrypted using AES‐256 with the secret key AccountKeyEnc and IV TakerKeyPrivateIv.
  • TakerSignature — An ECDSA signature of the keccak‐256 hash of the concatenation of MakerKeyPublicKey, TakerKeyPublicKey, and TakerAddress by the account’s public identity key (AccountKeyIdentityPrivate).

3.3.3Maker addresses

When a trade has just been created, it is only assigned an Ethereum address for the taker. In order to create and fund the escrow between parties, an address for the maker is needed too. As touched on earlier, when a user first creates an account they mass‐generate and sign hundreds of maker key pairs. The same is done for Ethereum wallet addresses, explained in better detail later.

As per design of the smart contract, the seller is always the one who initiates the escrow. In order to initiate the escrow on the blockchain, the seller needs to know an address for themselves and one for the buyer.

When the seller is the maker party, they already have the taker’s address from when they opened the trade. When initiating the escrow, they’ll pick an Ethereum address at their discretion, sign it with their account key and attach it to the trade.

When the seller is the taker party (i.e. the maker is the buyer), the maker address is chosen in a similar way to the maker key being chosen. The API picks an unused Ethereum address that has been signed in advance by the seller and assigns it to this particular trade.

In both scenarios, the taker party can always verify the maker’s Ethereum address is signed by the maker’s identity key.

3.3.4Encrypted messages

Using a key agreement protocol, the parties can compute a shared secret by knowing each others’ ECDSA public keys. Shared secrets are used to encrypt messages between the users, protecting LocalEthereum and other potential eavesdroppers from reading the contents of messages. secrets

For secure messaging, the parties select a shared secret using an anonymous agreement. The way this works is through an asynchronous key exchange protocol called Elliptic curve Diffie–Hellman (ECDH) which allows two people to derive the same shared secret using one party’s private key and the other’s public key.

The charm of ECDH is that the function produces the same output given reversed public and private key inputs. For any two private keys pk and p’k and their corresponding ECDSA public keys pu and p’u:

ECDH equation with ECDSA public keys

The root shared secret (SharedSecretRoot) is the ECDH function of one party’s private key and the other party’s public key. For maker key pair mk and mu and taker key pair tk and tu, the shared secret root sr is:

Shared secret equation with ECDH

The SharedSecretRoot is used as the seed to calculate more secure keys using SHA‐256 as a key derivation function. As it’s considered a poor practice to use one key for multiple purposes because of potential unwanted interactions in the different schemes, different keys are derived from the root for different purposes:

  • SharedSecretEnc — For message encryption using AES‐256. This is the result of SHA256(SharedSecretRoot || "enc").
  • SharedSecretMac — For message authentication using HMAC‐SHA256. This is the result of SHA256(SharedSecretRoot || "mac").

Q: Why no Double Ratchet?

A: While some implementations take key renewals a step further with a Diffie–Hellman agreement after each message roundtrip, that practice doesn’t benefit LocalEthereum and is actually counterproductive since it complicates the dispute resolution process.

Message‐level key renewals are an overkill for trades since the sessions don’t last for long. It makes sense to keep exchanging new keys for long‐lived conversations (e.g. WhatsApp or Facebook conversations that can go on for years), but most trades are over quickly. payloads

When a user composes a message, it’s compiled into a formatted payload before encryption. There are two types of payloads: message payloads and attachment payloads. Messages are simple plain‐text messages and attachments are for larger file uploads (e.g. documents and photos).

A message payload is a simple JSON structure. There are three properties that must be included in every payload regardless of the type:

  • type — This can be “message” or “attachment”. Support for other types may be added in the future.
  • trade_id — The unique identifier of this trade. This is to prevent complex replay attacks (i.e. copying messages from other trades and pretending they were meant for this one.)
  • client_timestamp — The current timestamp on the sender’s computer (ISO 8601). This is also to detect complex replay attacks and it allows the arbitrator to call out a bad actor if they claim to have sent a message later or earlier than reality.
Example № 3{
  "type": "message",
  "client_timestamp": "2017-01-01T00:00:00Z",
  "trade_id": "xxxx-xx-xxxx-xxx"

Standard messages are simple plain‐text messages in any language. There is only one extra field in the payload for plain‐text messages: the composed message (UTF‐8 encoded).

Example № 4{
  "type": "message",
  "client_timestamp": "2017-01-01T00:00:00Z",
  "trade_id": "xxxx-xx-xxxx-xxx",
  "message": "Hello! This is my encrypted message."
} messages

Uploading an attachment is more complex. Instead of including the uploaded file in the message, the payload contains a download link and another key to decrypt the file. This is much friendlier because it offers the recipient a choice to download the attachment or not.

The process of uploading an attachment is as follows:

  1. The sender generates a new ephemeral 256‐bit key (AttachmentKey).
  2. The sender generates an initialisation vector for encrypting the attachment (AttachmentIv).
  3. The sender encrypts the file data (AttachmentCiphertext) to the key AttachmentKey using AES256‐CBC with IV AttachmentIv.
  4. An SHA‐256 hash of AttachmentCiphertext is kept as AttachmentHash to protect the integrity of the document.
  5. The sender uploads AttachmentCiphertext to the API alone and is given a unique identifier (AttachmentBlobKey).

Once the identifier is created, the user constructs a payload containing a link to the attachment. It also contains the original file name, extension, and size.

Example № 5{
  "type": "message",
  "client_timestamp": "2017-01-01T00:00:00Z",
  "trade_id": "xxxx-xx-xxxx-xxx",
  "attachment_blob_key": "AttachmentBlobKey",
  "attachment_sha256": "AttachmentHash",
  "attachment_iv": "AttachmentIv",
  "attachment_key": "AttachmentKey",
  "filename": "UploadedFile.jpg",
  "filesize": 1000000
} payloads

Payloads are encrypted using the conversation’s shared secret, signed using the sender’s account identity key and hashed using the shared MAC key. This allows the other use to verify that the message really was sent by the sender and it hasn’t been tampered with along the way.

  1. The sender creates an ECDSA signature (MessageSignature) of the keccak‐256 hash of MessagePayload..
  2. The sender generates an initialisation vector for encryption (MessageIv).
  3. A JSON‐encoded package (MessagePackage) containing MessagePayload and MessageSignature is created following the format of { payload: "...", signature: "..." }.
  4. MessagePackage is encrypted using AES256‐CBC to SharedSecretEnc with IV MessageIv and padding (MessageCiphertext).
  5. To protect the integrity of the message, the sender produces a message authentication code (MessageMac) as HMAC‐SHA256(SharedSecretMac, MessageCiphertext).
  6. The sender submits MessageCiphertext, MessageIv, and MessageMac to the API. receipts

Read receipts are not encrypted. The recipient simply tells the API that they read a particular encrypted message and the API will treat that message, and every message before it, as opened.

3.3.5Dispute resolution

In the event of a dispute or when a conversation is reported to moderation, either party can volunteer SharedSecretRoot to LocalEthereum staff via the API. This will allow a moderator to review the conversation, including any attachments, and take action against foul play.

With access to the shared secret, staff can now:

  • Decipher the encrypted messages in this particular conversation.
  • Verify the messages in this particular conversation.

However, volunteering a trade’s SharedSecretRoot does not let staff:

  • Decipher any other trade conversation, even if it’s between the same users.
  • Send a message pretending to be one of the parties.
  • Resolve any ether in escrow, without access to another separate key.
  • Access any of the party’s wallets.


LocalEthereum has a complete in‐browser Ethereum wallet. No private keys are held by LocalEthereum; the platform never takes custody of your ether.

The LocalEthereum wallet uses a deterministic system to generate addresses. This means that each wallet includes a virtually infinite number of Ethereum addresses.

With LocalEthereum’s deterministic system, an export of your wallet gives you the ability to calculate to all current and future address private keys, but not the ability to recover information about deleted addresses. We call this “financial forward secrecy” because it allows you to continuously erase old addresses as new ones are created. This is made possible with a cryptographic “ratcheting” algorithm.

Q: Is this a HD (hierarchical deterministic) wallet?

A: Not quite. The web wallet uses a simple deterministic method as opposed to a hierarchical deterministic method. The difference between a hierarchical deterministic wallet (known as an “HD wallet”) and a non‐hierarchical deterministic wallet is that hierarchical wallets use a master public key system. A master public key allows observers to calculate the wallet’s other addresses by looking at a single public key. In a simple deterministic wallet, it’s not possible to calculate other addresses from one public key as there’s no relationship between public keys.

While hierarchical wallets enable nifty abilities and are more common than simple deterministic wallets, their application on LocalEthereum wouldn’t provide advantages.

3.4.1Ratcheting addresses

Each address in a LocalEthereum wallet contains an assigned index number beginning from zero. This index number is known colloquially as n. There can’t be any skipped indices.

Each LocalEthereum wallet begins with a random 32‐byte seed known as the zero‐index chain key (chainKey0). The seed can be used to determine the private key for the wallet’s first address (address0) and any subsequent addresses, making the system a deterministic wallet.

The private key for addressn can be determined from chainKeyn, and chainKeyn can be determined from chainKeyn‐1. Hence, if you know chainKeyn then you can determine addressm, where m is any number between n and infinity.

For this simple ratcheting algorithm, we use a common one‐way hashing function called HMAC‐SHA256. The calculations are as follows for every n-index chain key:

  • The n-index address private key (addressn) is HMAC‐SHA256(chainKeyn, 0x0001).
  • The next chain key (chainKeyn+1) is HMAC‐SHA256(chainKeyn, 0x02).

This simple ratcheting system is illustrated in the diagram below. Remember that each arrow represents a one‐way hashing function; it is impossible to go backward along any arrow.

Ratcheting illustration

As you can see from the illustration, accidentally divulging one address’s private key won’t harm the rest of your wallet. And if somebody gets their hands on a chain key, they can only calculate future addresses; they can’t gather any information about previous addresses.

We felt that these security benefits were necessary while designing the wallet. Here are some hypothetical scenarios we tested while writing the algorithm:

  1. Evan accidentally sends an ERC20 token to one of his LocalEthereum wallet addresses. Since the web wallet doesn’t have an ERC20 interface, Evan decides to export the private key and import it into MyEtherWallet. Unfortunately, he imports the address into a phishing site and his tokens are stolen. Thankfully, it won’t harm the rest of his wallet.
  2. David enjoys his privacy and is worried about his nosy roommate going through his transaction history. While David is at work, the roommate uses David’s laptop to export his LocalEthereum wallet (where he is already logged in). Thankfully, his old Ethereum addresses are deleted and unrecoverable.

Here is example Javascript code to generate one thousand addresses from a random seed:

Example № 6const crypto = require('crypto');
const ethereumjsUtil = require('ethereumjs-util');

function HMACSHA256(data, key) {
  return crypto.createHmac('sha256', key)

function chainKeyRatchet(previousChainKey) {
  return HMACSHA256(
    Buffer.from('02', 'hex')

function chainKeyToAddressPrivateKey(chainKey) {
  return HMACSHA256(
    Buffer.from('0001', 'hex')

function privateKeyToAddress(privateKey) {
  const publicKey = ethereumjsUtil.privateToPublic(privateKey);
  const address = ethereumjsUtil.publicToAddress(publicKey);
  return `0x${address.toString('hex')}`;

// The first chain private key is random; this key is the seed
// to all future addresses.
const firstChainPrivateKey = crypto.randomBytes(36);
let chainPrivateKey = firstChainPrivateKey;
// Go through and generate 1000 addresses from this seed
for (let i = 0; i < 1000; i++) {
  // The address private key is the HMAC-SHA256 result of the
  // current chain key + 0x0001.
  const privateKey = chainKeyToAddressPrivateKey(chainPrivateKey);
  // Calculate the address from the private key
  const address = privateKeyToAddress(privateKey);
  // Ratchet the chain key forward one step to calculate the next
  // address. It can only move forward; not backwards!
  chainPrivateKey = chainKeyRatchet(chainPrivateKey);
  // Print the address and private key
  console.log(`#${i + 1}\t${address}\t${privateKey.toString('hex')}`);

3.4.2Storing chain keys

Chain keys are encrypted using AES‐256 to AccountKeyEnc with a random initialisation vector. Once encrypted, the ciphertext is submitted to the API to be saved.

Only the first chain key (chainKey0) needs to be encrypted and saved when a wallet is first created, however, users are encouraged to save more so that older chain keys can be safely erased in the future.

3.4.3Deleting addresses

LocalEthereum wallet addresses are intended to be ephemeral. Although the addresses can be re‐used if the owner prefers, it’s not recommended as re‐using addresses can erode financial privacy.

When a wallet address becomes empty and no longer needed, a countdown timer will begin before the address is deleted. However, there is a significant limitation to this: it is not possible to delete an address unless every address to the left of it is also deleted.

For example, if you want to delete your 4th address, you will need to first delete your 1st, 2nd and 3rd addresses. If there is ether remaining in your 3rd address, you will need to spend it first (or burn it). This is because it’s always possible to generate the private key of the 4th address from any chain private key preceding it.

The LocalEthereum wallet keeps track of the lowest available n chain key. It begins at zero when the wallet is created, and it’s increased as wallets are deleted. Any addresses less than n are unreachable.

3.4.4Backing up

Backing up your LocalEthereum wallet means to simply export the lowest chain key available. An example backup file might look like this:

Example № 7{
  "export": {
    "version": "1.0",
    "created_at": "2018-01-01"
  "wallet": {
    "id": "08ff9422-552e-4803-a808-8ebb054950f6",
    "version": "1.0"
  "first_address": {
    "address": "0x153eac21fc4e66ede5fcae1c763e094dd8e96dc6",
    "wallet_address_n": 0
  "chain_private_key": "08ff942248034f6019cc63550f053300871b1f4ea18ef48035c3611cb9b25b4f"

The important part is chain_private_key, which you can us to calculate every address in the wallet. To test that you’re ratcheting keys and calculating addresses correctly, the first address in the back up file should be first_address → address. The first_address → wallet_address_n number is the earliest index available at the time of the backup; everything to the left of that has been deleted.

3.4.5Pre-signing addresses

When somebody opens a trade with you, they’ll need one of your wallet addresses. Without an address, they’d have to wait for you to come online before opening an escrow.

Your potential trade partners will need to be able to fetch an unused address of yours, verify it actually belongs to you, and, in some cases, send ether to it — all while you’re offline. To make this possible, addresses are signed and published in advance with your public account identity key (AccountKeyIdentityPrivate). When you sign up, hundreds of addresses are automatically signed and submitted to the API to allow asynchronous exchanges.

When a wallet address is shown to another user, the API marks it as “exposed” and it’ll never be shown to anyone else (for stronger privacy). When the number of unused and unexposed addresses begins to run low, the user will automatically and unknowingly sign more addresses in the background when they return to the platform.

This process of signing addresses in advance is similar to the signing of maker keys.


Using the LocalEthereum ratcheting wallet has strong advantages related to privacy and security:

  • Using many addresses makes it difficult to link transactions to unique identities.
  • Exposing any address will not expose any of the wallet’s other addresses.
  • Exposing any private key will not expose any of the wallet’s other private keys, as there is no mathematical relationship.
  • It is not possible to calculate previous addresses from a chain key, enabling financial forward secrecy.

4Blockchain layer

LocalEthereum currently only uses the Ethereum blockchain for:

LocalEthereum has a smart contract to make trades between users trust‐less and secure. Each exchange involves an escrow of ether from the seller to the buyer. Smart contract code can never be changed and their state can only be modified in ways initially defined.

The current version of the smart contract is located at the address:


You can find the complete source code for the contract on Etherscan, and view its past and pending transactions.

4.1Optimising for gas

It’s important that the LocalEthereum smart contract is cheap to use. Therefore, we’ve employed some unique tactics to lower the amount of computation and storage required.

Of all of the operations available in Ethereum smart contracts, writing to unallocated storage is the most expensive operation. Hence, the smart contract has been designed to use keep a tiny storage footprint to save on costs.

Further optimisations may be made in the future, at which point we will deploy a new contract and migrate the platform to it.

4.2Configuration variables

There are some global variables in the smart contract which can be changed by the owner address from time to time. The usage of these variables will be put in context in sections below.

4.3Creating an escrow

Technically, escrows are not directly linked to trades on LocalEthereum.com. When a trade is first created and messages are exchanged between parties, the smart contract isn’t involved at all and no ether is in escrow.

Once the parties of a trade have agreed to the terms and are ready to proceed, the escrow can be created. Escrows are initiated and funded in a single transaction by the selling party when ready.

4.3.1Properties of an escrow

The static properties of an escrow can never change once created. They are sent once in the first transaction to initialise the escrow and must be sent in all subsequent calls because they also make up the identifier, which will be explained below.

These unchangeable properties include:

  1. Trade ID (16 bytes) — Although currently always the related UUID identifier of a trade on LocalEthereum.com, any unique nonce would work just fine here. This is to make sure that no two escrows will collide.
  2. Seller address (24 bytes) — The address of the seller, used to interact with the escrow and receive funds in case of a cancellation.
  3. Buyer address (24 bytes) — The address of the buyer, used to interact with the escrow and receive funds upon a successful exchange.
  4. Value (256 bytes) — The amount of the escrow.
  5. Fee (16 bytes) — LocalEthereum’s commission represented in 1/10,000ths.

4.3.2Escrow identifiers

Each escrow has an identifier, which is the key to access it in a mapping. Escrow identifiers are more than simple unique identifiers. Each time an identifier is checked, the above properties of an escrow are verified. This is because the identifier of an escrow is actually a hash of its static properties.

This mechanism achieves a tiny storage footprint because storage is very costly while hashing is very cheap in comparison. By packing these properties into a blob, hashing that blob and using the hash as a key, we avoid the need for allocating extra space for every property. This way, each escrow in the contract only needs 64 bytes of storage.

To convert the properties into a hash, the first step is to tightly concatenate them using Solidity’s abi.encodePacked function:

Once we have all of the static properties of the trade, we can determine the hash with a simple one‐way keccak‐256 hashing function:

The resulting hash is the identifier to the particular escrow in the escrows mapping.

Identifiers also serve to validate the properties of an escrow. When a user wants to access an escrow later to make a change, they pass all of these properties again as arguments to a function. The smart contract code will calculate the hash from the properties, and if the hash exists in the mapping then all of the properties must be correct.

4.3.3Escrow struct

Information about each escrow is stored in an Escrow struct, which is what the public mapping points to. Each instance of the Escrow struct fits an allocation of one word in storage (256 bits).

struct Escrow {
  bool exists;
  uint32 sellerCanCancelAfter;
  uint128 totalGasFeesSpentByRelayer;
mapping (bytes32 => Escrow) public escrows;

Unlike the static properties encoded in the hash identifiers, these variables can change. The struct includes:

  1. exists — This is simply a boolean to indicate that the escrow exists since there is no way to differentiate unused storage from zero. This is always true until the escrow has completed.
  2. sellerCanCancelAfter — This is initially an epoch timestamp containing the date after which a seller is permitted to cancel the escrow and return the funds to themselves. There are two other special values:
    • A value of 0 indicates that the seller is not allowed to cancel at any time. The value can be set to 0 at any time at the buyer’s request to lock the funds in the escrow until the seller releases the funds or the arbitrator steps in to resolve the trade. This is typically done when buyer indicates he has made payment and expects the funds to be released.
    • For specific payment types where payment windows don’t make as much sense (e.g. cash), this is set to 1, which is a special value to mean infinity. When sellerCanCancelAfter equals 1, the seller can’t cancel but can initiate a request to cancel. When the seller indicates that he’d like to cancel the trade, the buyer is given adequate time to dispute the request before the seller‐cancellation is permitted (currently set to two hours).
  3. totalGasFeesSpentByRelayer — This is a counter of the gas costs incurred by relay operations (explained shortly). It will be deducted from the escrow to cover network fees once it has completed.

4.3.4Requesting an invitation

Before creating an escrow, there is one small step required. Each escrow requires a signed invitation from the API to use the smart contract, with the purpose of keeping the escrow clean and preventing complicated user mistakes. The seller can request a signature from the API whenever they’re ready to place ether in escrow.

The invitation is simply an ECDSA signature of three tightly‐concatenated properties:

  1. The escrow identifier — The keccak‐256 hash of all the static properties of the trade.
  2. Payment window — The duration of the payment window in seconds, except for payment methods where there is no payment window (e.g. cash). The seller is allowed to cancel after the payment window expires unless the buyer has locked the escrow (i.e. signalled that payment has been made).
  3. Expiration date — An expiration date for the escrow invitation. The invitation cannot be used in a transaction after this date, else it will be reverted.

These properties are tightly concatenated, hashed, and signed using the private key to the relayer address. The invitation is used when the seller creates the escrow with the createEscrow function.

4.3.5Making the first transaction

The createEscrow function is called by the seller to register the new escrow with the smart contract. In the same transaction, the seller funds the escrow with the full balance.

The seller uses the static properties of the trade, the properties of the invitation, and the signature of the invitation as arguments to the function. Note that this transaction doesn’t need to be broadcast by the seller’s address as per the escrow; it can be sent from any address.

The createEscrow function works as follows:

  1. A _tradeHash is created by tightly‐concatenating and hashing properties of the trade. These include _tradeID, _seller, _buyer, _value and _fee. This hash becomes the identifier of the escrow, and hence all these variables must be supplied on future contract calls.
  2. The escrows mapping is checked to see that the _tradeHash does not already exist.
  3. _tradeHash, _paymentWindowInSeconds, _expiry are concatenated and hashed to create _invitationHash. _v, _r, _s must result in a valid signature of _invitationHash by the relayer.
  4. If the current block timestamp is later than _expiry, the transaction is reverted.
  5. The transaction value is checked to ensure the correct amount of ether was sent.
  6. The escrow is added to the escrows mapping, referenced by its _tradeHash. This includes a _sellerCanCancelAfter variable, which is set to the current block timestamp plus _paymentWindowInSeconds. For exchanges without a payment window (such as cash), _paymentWindowInSeconds is set to the special value of 1.
  7. A Created event is emitted.

4.4Interacting via relay

LocalEthereum has a system which allows buyers and sellers to interact with escrows in progress without making a transaction themselves.

This relay mechanism eliminates two major issues with most decentralised applications:

1. Transactions aren’t free. The platform might be many people’s first interaction with Ethereum and cryptocurrencies in general, and that makes things complicated. It costs ether to interact with the blockchain in terms of gas, and somebody who doesn’t have any ether is unable to interact with smart contracts directly.

2. Transactions can be difficult to make. Sometimes, making a transaction and ensuring it goes through quickly can be difficult, especially if you have limited knowledge about Ethereum. Picking an adequate gas price and replacing stuck transactions with higher gas prices can be daunting tasks, and they require you to remain at your device and pay close attention. This is a side effect of using brand new technology.

With these issues in mind, we’ve designed a system in which traders can interact with our smart contract for “free” using us as a proxy. The cost of gas is paid up‐front by us to relay digital signatures which authorise instructions on a user’s behalf, with the expectation that we’ll be reimbursed at the end of the escrow.

4.4.1Signing an instruction

During the course of an escrow, there are a few instructions each party can make. For example, a seller can create an instruction to release the ether to the buyer, and a buyer can create an instruction to cancel and refund the escrow to the seller.

In most other decentralised applications, each time a user wants to do something with a smart contract, they’ll need to broadcast an Ethereum transaction to execute code in the smart contract. The code will check the transaction’s sender address to determine if they are allowed to do what they’re trying to do.

The LocalEthereum relay mechanism allows the user to instead write a message (e.g. “I want to release the ether from escrow ABC”) and sign it with the private key of their Ethereum address. Instead of the user broadcasting the message in a transaction themselves, they’ll give the signature data to the relayer (i.e. the API) and the relayer will execute the instruction on the instructor’s behalf. The smart contract code will verify that the instruction has been correctly signed before executing it.

Each instruction contains just three properties:

  1. Trade ID (16 bytes) — The identifier of the trade used when creating the escrow.
  2. Instruction byte (1 byte) — A single byte indicating the type of instruction.
  3. Maximum gas price (16 bytes) — The maximum gas price that the relayer is allowed to spend for the action. This gas price cap prevents an attack vector whereby LocalEthereum could hypothetically overpay for gas and cause the parties to burn more than a reasonable amount on network fees.

To invoke an instruction via the relay, the caller simply needs to sign a keccak‐256 hash of the above properties. The signature and relay properties should be submitted to the API to be added to a queue of pending relayed instructions. Depending on the urgency of the instruction, it may be sent immediately, sometime later, or never, depending on the circumstances of the escrow.

Each time an instruction is executed, the approximate cost of gas is added to the escrow’s totalGasFeesSpentByRelayer variable.

4.4.2Deducting gas and fees

When an escrow completes, whether by a release from a seller or cancellation from either party, the following occurs:

  1. Except in the event of a cancellation, LocalEthereum’s commission is added to the global variable feesAvailableForWithdraw, which contains the total amount in uncollected fees that LocalEthereum staff can withdraw later.
  2. The total of ether spent on executing gas by relaying instructions (i.e. totalGasFeesSpentByRelayer) is added to feesAvailableForWithdraw.
  3. The remaining ether is transferred to the winning party.

4.4.3Instruction types

Each instruction byte is a single‐byte identifier, all of which are defined as constants near the beginning of the smart contract’s source code. The available instructions types are, in no particular order:

Prevent seller from cancelling

The instruction SELLER_CANNOT_CANCEL (0x01) is used by the buyer to mark the external payment as sent. After this instruction is executed, the payment window ends, preventing the seller from being able to cancel (by setting sellerCanCancelAfter to zero). It corresponds with the “Mark as paid” button in the LocalEthereum UI.

This locks the seller’s ether in escrow until either:

  1. The seller releases the escrow the buyer.
  2. The buyer refunds the ether to the seller.
  3. A dispute is raised, and the arbitrator steps in to perform (1) or (2) on the party’s behalf.

Cancel, as a buyer

The instruction BUYER_CANCEL (0x02) is called by the buyer to cancel the escrow and refund the ether to the seller.

Cancel, as a seller

The instruction SELLER_CANCEL (0x03) is called by the seller to refund the escrow to themselves. This option is unavailable unless the payment window (i.e. sellerCanCancelAfter) has expired, and it becomes permanently unavailable after the buyer invokes SELLER_CANNOT_CANCEL.

Request to cancel, as a seller

The instruction SELLER_REQUEST_CANCEL (0x04) is called by the seller to begin a “request to cancel”, which the buyer can object to within a defined time period. This option is only available for exchanges that don’t involve a payment window, such as cash‐in‐person trades.

This changes the escrow’s sellerCanCancelAfter to the current block timestamp plus the predefined global variable requestCancellationMinimumTime. Now, like an ordinary payment window, the buyer can invoke the SELLER_CANNOT_CANCEL instruction to veto the seller’s request to cancel (and likely initiate the dispute resolution process at the same time).

Release funds, as a seller

The instruction RELEASE (0x05) is called by the seller to release funds to the buyer. This is used once the seller confirms payment.

Resolve dispute, as either party

The instruction RESOLVE (0x06) is a unique instruction because, unlike the rest, it doesn’t contain a maximum gas price and it’s sent by the arbitrator address. If the arbitrator is volunteered this signed instruction from either party, they can use it to direct the ether escrow to either party.

4.4.4Broadcasting instructions

The API maintains a queue of signed instructions ready to be broadcast, and it has the discretion to pick which instructions to broadcast at what time. Some instructions end up not being broadcast at all.

The smart contract method batchRelay is used to relay escrow instructions. This method allows the API to broadcast instructions many at a time, which saves on transaction fees as the “overhead” on any transaction is fixed at 21,000.

The API prioritises pending instructions in a way so that:

  1. Release instructions are considered the most important and broadcast nearly immediately.
  2. Cancellation instructions are considered less important and are usually not broadcast alone. Instead, they remain in the queue until either:
    • A threshold of cancellation instructions is reached (e.g. a minimum of three); or
    • A release instruction is queued, which the cancellation will be bundled with; or
    • The instruction has been pending for some time (e.g. longer than three minutes).
  3. Instructions to prevent a seller from cancelling (a.k.a. “Marking as paid”) are not sent until the payment window is nearing completion (e.g. thirty minutes prior). If a release instruction for the escrow appears beforehand, the redundant instruction will be removed from the queue.

The prioritising and redundancy mechanism saves on transaction costs for both LocalEthereum and its users. At the time of writing, more than half of all escrows have a “prevent seller from cancelling” instruction that is never broadcast, which reduces gas costs for the buyer significantly.

4.4.5Considering abuse

From a distance, the relay system may sound exploitable. What’s to stop users from racking up debt with no intention to pay the balance later? There are a few reasons why fronting gas costs is not abusable:

  1. There’s a strong financial disincentive to an attack of this nature because it would involve the attacker burning lots of ether on gas and fees. Essentially, the amount in escrow acts as a security deposit to ensure the gas balance will be paid.
  2. We choose the gas price, and we’ll keep it reasonable.
  3. We only relay what we validate to be legitimate and necessary.
  4. Since we’ve managed to cut our gas costs down significantly by minimising transactions and storage space, the amount that we loan for each trade is tiny.
  5. The API is rate‐limited and protected by CAPTCHAs plus other bot‐deterrents, and you need to have signed permission from the API to create an escrow.
  6. The overhead on relaying actions is reduced by sending many at a time, modifying many escrows in a single transaction. As escrow activity increases, the transaction overhead costs will continue to shrink in proportion.

4.5Interacting without relay

It is not necessary to use the relay system; it’s only there for convenience. If a user prefers, they can interact with the smart contract directly like any other decentralised application.

For each instruction type, there is a corresponding external function in the smart contract. These functions can be called by a party of an escrow and the code will execute exactly the same as if an instruction were relayed, except that the costs of gas will be paid by the calling user.

If the relaying mechanism were to ever go offline for any reason, users can always interact with the contract directly to get their funds. It is because of this, and the fact that LocalEthereum never holds any ether itself, that the platform doesn’t involve counter‐party risk.

4.6Dispute resolution

When the parties cannot come to an agreement about the outcome of the escrow, either party can initiate a dispute. By providing the arbitrator with a special key, the arbiter will have the ability to resolve the trade to one of the parties. It’s expected that the disgruntled party will also volunteer the shared key to decrypt the encrypted conversation between the pair (see above).

The special key is exactly the same as a signed instruction except that it doesn’t include a maximum gas price. With the signature, the arbiter can call the resolveDispute function to indicate the percentage of escrowed ether that should be delivered to either party. In most cases, the _buyerPercent argument will either be zero (in cases where the buyer is the winning party) or 100 (in cases where the seller wins); an unusual split is possible but very rarely warranted.


Smart contract events allow the convenient usage of EVM logging facilitates, which can be used to trigger callbacks in applications which listen for these events. The LocalEthereum escrow smart contract emits the following events:


5.1Launch and growth

The platform launched on October 23, 2017. In its first two weeks, LocalEthereum helped people exchange 800 ETH — the equivalent of ~US$228,000 at the time. As the first trading platform of its kind, it quickly became popular among cryptocurrency miners, traders, and remittance dealers, especially in countries with strong economic controls such as Venezuela.

Shortly after launch, the platform was translated to Spanish, Russian, and Chinese. The vast majority of bugs that occurred during early days were isolated to the centralised layer, and to date there have been no security breaches.

As of August 2018, the platform has a cumulative trading volume of $40 million USD. This volume is verifiable on the blockchain, and, unlike centralised exchanges, it’s free of wash trading and high frequency trading bots.

5.2Future development

This whitepaper is a first draft and will be updated in the future. While maintaining the current version, the LocalEthereum team is continuously working on improving the platform. Especially as the ecosystem surrounding Ethereum dApps improves, components of LocalEthereum where there still remains some elements of trust will be replaced in the near‐future.

Some of the features we’re currently investigating include:


The peer‐to‐peer trading platform couldn’t exist without the ideas and initiatives of others. In particular, the LocalEthereum project owes a thank‐you to:

Lastly, thank you for reading the whitepaper. As thanks, enjoy a free trade on us by clicking this link.

Published August 31, 2018.
Updated September 1st, 2018.
  1. 1Concept
    1. 1.1Abstract
    2. 1.2Introduction
      1. 1.2.1Secrets are meant to be kept
      2. 1.2.2Over-the-counter crypto
      3. 1.2.3Sheltering from mass intrusion
    3. 1.3How trading works
    4. 1.4Advantages
    5. 1.5Design requirements
    6. 1.6(De)centralisation hybrid
      1. 1.6.1Centralised layer
      2. 1.6.2Cryptography layer
      3. 1.6.3Blockchain layer
  2. 2Centralised layer
    1. 2.1Introduction to the API
    2. 2.2Accounts
    3. 2.3Offers
    4. 2.4Trades
    5. 2.5Reputation
    6. 2.6Moderation
    7. 2.7Encrypted payloads
  3. 3Cryptography layer
    1. 3.1Introduction
    2. 3.2Accounts
      1. 3.2.1Creating an account
      2. 3.2.2Logging into an account
      3. 3.2.3Changing a password
      4. 3.2.4Caveats
    3. 3.3Trades
      1. 3.3.1Maker keys
        1. the queue
        2. key properties
      2. 3.3.2Creating a trade
        1. properties
      3. 3.3.3Maker addresses
      4. 3.3.4Encrypted messages
        1. secrets
        2. payloads
        3. messages
        4. payloads
        5. receipts
      5. 3.3.5Dispute resolution
    4. 3.4Wallets
      1. 3.4.1Ratcheting addresses
      2. 3.4.2Storing chain keys
      3. 3.4.3Deleting addresses
      4. 3.4.4Backing up
      5. 3.4.5Pre-signing addresses
      6. 3.4.6Advantages
  4. 4Blockchain layer
    1. 4.1Optimising for gas
    2. 4.2Configuration variables
    3. 4.3Creating an escrow
      1. 4.3.1Properties of an escrow
      2. 4.3.2Escrow identifiers
      3. 4.3.3Escrow struct
      4. 4.3.4Requesting an invitation
      5. 4.3.5Making the first transaction
    4. 4.4Interacting via relay
      1. 4.4.1Signing an instruction
      2. 4.4.2Deducting gas and fees
      3. 4.4.3Instruction types
      4. 4.4.4Broadcasting instructions
      5. 4.4.5Considering abuse
    5. 4.5Interacting without relay
    6. 4.6Dispute resolution
    7. 4.7Events
  5. 5Organisation
    1. 5.1Launch and growth
    2. 5.2Future development
    3. 5.3Acknowledgements