This message is meant to inform participants of attempts of double spending an unconfirmed transaction by providing cryptographic provable evidence that one UTXO entry was spent twice by the owner(s) of the funds.
A double spend attack can be used, for instance, to redirect payments sent to a specific merchant to a different target and thus defraud the merchant.
The basic concept of a double spend is that (at least) one unspent output is spent twice in different transactions which forces miners to pick one of them to mine.
At its most basic level, this can be detected by finding two signed inputs both spending the same output.
In the case of pay-to-public-key-hash (P2PKH) this means two signatures from the same public key.
Cryptographic signatures in Bitcoin Cash follow the “fork-id” algorithm described here.
Since a hash of the transaction is signed, the protocol sends only the intermediate components used to build the preimage for the hash, while still allowing receivers to validate both signatures of the same public key, and therefore proving that a double spend has taken place.
A node that finds itself in possession of a correct double-spend-proof
shall notify its peers using the inventory message, using a “type” field with the value 0x94A0
.
This inventory type value will be changed to another number once double-spend proofs move out of beta and is finalized.
The hash-ID for the double-spend-proof is a double sha256 over the entire serialized content of the proof, as defined next.
In response to an inventory message, any peer can issue a getdata message which will cause a reply with the following message.
This type of message is dsproof-beta
but will be changed to another identifier once double-spend proofs move out of beta and is finalized.
Field | Length | Format | Description |
---|---|---|---|
previous transaction output’s transaction hash | 32 | sha256(LE) | The transaction hash of the output being spent |
previous transaction output’s index | 4 | unsigned integer(LE) | The index of the transaction output being spent |
first spender | variable | spender(BE) | The preimage data structure needed to validate a transaction’s signature. |
second spender | variable | spender(BE) | The preimage data structure needed to validate a transaction’s signature. |
A double-spend-proof describes two inputs, both spending the same output.
As such the previous transaction output’s hash and previous transaction output’s index point to the output and the spenders each describe inputs.
The spender
format is as follows.
Each field in the (below) table’s description
column loosely corresponds to the name of the preimage component used when generating transaction signatures per the transaction signing algorithm.
Field | Length | Format | Signature Preimage Component | Description |
---|---|---|---|---|
tx version | 4 | unsigned integer(LE) | #1 | Copy of the transactions version field |
sequence | 4 | unsigned integer(LE) | #7 | Copy of the sequence field of the input |
locktime | 4 | unsigned integer(LE) | #9 | Copy of the transactions locktime field |
hash prevoutputs | 32 | sha256 | #2 | Transaction hash of prevoutputs |
hash sequence | 32 | sha256 | #3 | Transaction hash of sequences |
hash outputs | 32 | sha256 | #8 | Transaction hash of outputs |
push-data count | variable | variable length integer | Number of push-data objects within in the push-data list | |
push-data | variable | bytes | #10 | List of push-data objects |
The push data
format is as follows.
Each item is a value pushed by the unlocking script.
Field | Length | Format | Description |
---|---|---|---|
byte count | variable | variable length integer | The number of bytes in this push-data object |
bytes | variable | bytes (BE) | The resulting bytes pushed by an unlocking script’s push operation (ex. signature data). |
It is required that nodes validate the proof before using it or forwarding it to other nodes.
Double spend proofs only apply to transactions within the memory pool.
Validated double spend proofs should be advertised (via inventory message) to all connected peers.
If a peer has a bloom filter set, nodes should only relay double spend proofs that the bloom filter.
Validation includes a short list of requirements;
spenders
must be different.spender
s are sorted via the following algorithm:3a. sort via the hash-outputs
field of the spender
.
3b. if hash-outputs
are equal, then compare on hash-prevoutputs
.
The sorting order is in numerically ascending order of the hash, interpreted as 256-bit little endian integers.
The double spent output is still available in the UTXO database, implying no spending transaction has been mined.
The push data elements of the two signers are different.
No other valid proof is known.
Further validation can be done by validating the signature that
was copied from the inputs of both transactions against the (soon to be spent) previous transaction output.
To validate a spender of the proof, a node is required to have;
As the forkid specification details, the digest algorithm hashes 10 items in order to receive a sha256 hash, which is then signed.
These 10 items are;
The double spend message includes items: 1, 2, 3, 4, 7, 8, 9 and 10.
Items 5 & 6 can be obtained from the output being spent.
The transaction that first spent the output within the node’s memory pool, can be used to get the public key required to validate that the signature within the push-data
field of the double spend proof is correct.
When all rules are followed, the proof is valid.
Not all types and all combinations of transactions are supported.
Wallets and point-of-sale applications are suggested to give a rating of how secure an unconfirmed transaction is based on various factors.
Transactions that spend all, confirmed, P2PKH outputs with all inputs signed SIGHASH_ALL
without ANYONECANPAY
, are double-spend-proof’s “protected transactions”.
Bitcoin Verde supports an extended form of the dsproof-beta
message.
For P2PKH outputs, the format is as described above for compatibility with other nodes.
For all other script types, the following changes are made to the existing data:
hash prevoutputs
is always the non-zero version of hash (e.g. for SIGHASH_ALL
, not ANYONECANPAY
hash types).hash sequence
is always the non-zero version of the hash (e.g. for SIGHASH_ALL
, not ANYONECANPAY
hash types).hash outputs
is always all zero (0x00) bytes.push data
is defined to be the values push by every (push) operation in the unlocking scripts, except for P2SH, where the last value (the redeem script) is left off.The following extra data is also appended after the second spender:
Field | Length | Format | Description |
---|---|---|---|
hash type count | 1 byte | variable length integer | The number of hashes to follow (always 2 with current signature rules). |
hash type indicator 0 | 1 byte | byte | The hash type of the following hash (always 0x01 for SIGHASH_ALL ). |
hash outputs 0 | 32 bytes | sha256 | The hash outputs, as used in the signature preimage for a SIGHASH_ALL signature. |
hash type indicator 1 | 1 byte | byte | The hash type of the following hash (always 0x03 for SIGHASH_SINGLE ). |
hash outputs 1 | 32 bytes | sha256 | The hash outputs, as used in the signature preimage for a SIGHASH_SINGLE signature. |
This format corresponds with Bitcoin Verde’s proposal for an alternate double spend proof message that supports all script types.