P2P, Blockchain monitoring, and SPV

Overview

The way SPV was interpreted for the original Electrum wallet was that a user would broadcast a transaction to the blockchain, get notified it was present in the nodes, get notified it was mined in a block and then fetch a merkle proof and verify it against the given block. ElectrumSV still has to do this to interoperate with incoming payments still made in this way, but it will stop doing it in all other cases where possible.

Current approach

Script hash events

The API ElectrumSV has at its disposal is the ability to register script hashes with the ElectrumX server it is connected to, it then gets the current state and after that receives any notifications relating to those script hashes being used. It gets told about what transactions use those script hashes, and what height in the blockchain they are. This indicates their presence in the mempool, a block or their movement from the mempool into a block as they get mined.

Detecting new blockchain payments

Examining the script hash history obtained from the server, ElectrumSV can reconcile which transactions it already has and which it does not. Those that it does not have, are new payments and it can then obtain and import them.

Merkle proofs and transaction verification

A transaction in the script hash history that has a height indicates it has been mined, and is not just in a mempool. ElectrumSV can then request the merkle proof and verify it against the block header.

Dispensing payment destinations

A user can give out payment destinations from their account and be sure that if a payment is made to it their wallet will pick it up from the blockchain. They might do this by going to the Receiving tab and copying the destination (most often in the form of an address) shown there. They might go to the keys tab and copy a range of destinations available there. Or they might go to their account menu, and generate any number on request via the menu option provided for that purpose.

Copying an address in the existing UI.

Payment requests

The receive tab features the ability to save an expected payment with optional description, amount or expiry date. These are internally known as payment requests. This is mostly for the user’s records, although we have hooked it up to the processing of incoming payments and expiration, either possibly marking an entry as paid automatically. But for the most part, it is mostly useless and I expect it is also mostly unused.

Saving a payment request in the existing UI

Scanning the blockchain

All accounts with deterministic key sequences have a gap limit for both change and receiving payment destinations. This is a maintained space of unused keys at the end of the current sequence of enumerated payment destinations. As all payment destinations are monitored on the blockchain, transactions are discovered, imported and keys become used. In turn the gap of unused addresses shifts further out in the sequence of enumerated payment destinations — with more being enumerated/created as the need arises.

P2P focused approach

ElectrumSV still needs to use an indexer for a P2P focused approach, and the only available indexer is ElectrumX which indexes script hashes. Every key that is active has a number of different script types that it can be used in, and each of these produces an output script which is also known as a payment destination.

Addresses and blockchain payments

What about addresses? There were only ever two types of addresses, P2PKH (historically for single recipient payments) and P2SH (historically for multi-signature payments). The classic P2SH output script is now rejected by nodes, so that leaves P2PKH. But there are many more types of output script than those two, including P2PK and bare multi-signature. In fact, there is no reason why your application or wallet cannot dynamically construct custom output scripts on a case by case basis.

  • Receiving payments from exchanges and other businesses rather than via other secure P2P mechanisms which do not exist yet.
  • All ElectrumSV users until hosting services are available to provide either Paymail hosting, or further evolved replacements for it.

Detecting new blockchain payments

As part of normal wallet management, the only time we will ever pay attention to unknown transaction ids in the script hash history is if the script is a payment destination the user has created a payment request for in the Receiving tab, and that payment request is still unpaid. As transactions are processed and the payment criteria are met, namely that any designated amount is paid or that any designated expiry date has passed, any remaining transactions are ignored.

  1. A payment request can be created without an amount. All discovered transactions paying to the given payment destination will be processed regardless of how many.
  2. A payment request can be created without an expiration date. The payment destination will be monitored until the specified amount is paid, or indefinitely if there is no specified amount.
  3. The user can go to the Keys tab and set a key as “user active”. This will process all discovered transactions making payments to payment destinations that use the given key.

Merkle proofs and transaction verification

For every transaction that we detect, obtain and import into the wallet, it is known that it is related to at least one of the accounts in the wallet. The point of SPV is that the user obtains the merkle proof of any transaction that they plan to spend coins from, so that they can provide those transactions and their merkle proofs with any child transaction that spends them. The recipient can then have some proof given they can verify the spent coins against the blockchain headers, that the spent coins are legitimate.

  • When there is a payment request that uses that key that is still unpaid.
  • When the user has forcibly set that key as active.
  • When we have a transaction that uses that key and want to know when it is mined, and the merkle proof is available. This may be just after we receive the transaction, after we learn of it on the blockchain or just after a reorg that affects the given transaction.
  1. We could monitor the script hash of the first output of our transaction, and this would detect expected spending, malleation but not necessarily double spending.
  2. In order to monitor a script hash to know whether our transaction’s spent outputs are spent in the mempool or mined, we would need to fetch all the parent transactions and identify the script hashes of the outputs that are spent from them in our transaction.

REFERENCES

  1. The BIP276 standard for encoding payment destinations in a form users can copy and paste.
    https://github.com/moneybutton/bips/blob/master/bip-0276.mediawiki
  2. The Merchant API v1.2.0 specification.
    https://github.com/bitcoin-sv-specs/brfc-merchantapi
  3. The ElectrumX get_merkle RPC method.
    https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle
  4. The Bitcoin SV node getrawtransaction RPC method.
    https://github.com/bitcoin-sv/bitcoin-sv/blob/9e144729c7a22507603386bb2fe3dd88b0e35537/src/rpc/rawtransaction.cpp#L55
  5. The Bitcoin Core ElectrumX spent output notification pending pull request.
    https://github.com/spesmilo/electrumx/pull/80
  6. The Bitcoin SV wiki SIGHASH page reference for SIGHASH_NONE.
    https://wiki.bitcoinsv.io/index.php/SIGHASH_flags#SIGHASH_NONE.7CANYONECANPAY

Changes

2021–02–02

This document was modified with malleation taken into account and not naively looking for transaction state by whatever hash we may have for it, based on advice from Kyuupichan developer of ElectrumX.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store