Skip to content

Instantly share code, notes, and snippets.

@awemany
Last active January 10, 2023 15:00
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save awemany/619a5722d129dec25abf5de211d971bd to your computer and use it in GitHub Desktop.
Save awemany/619a5722d129dec25abf5de211d971bd to your computer and use it in GitHub Desktop.
Solving the 0-conf problem using forfeits

Solving the 0-conf problem using forfeits

by /u/awemany

Overview

The problem of ensuring good security for unconfirmed transactions in Bitcoin is repeatedly discussed in the community. Right now these so-called 0-conf transactions are usually expected to be of low risk in the case of small amounts and face-to-face interaction of merchant and customer.

These face-to-face interactions usually mean that there exist extra-blockchain means of enforcing good manners and discouraging theft (merchant and customer knowing each other, justice system, ...). However, there is no reason to refrain from trying to make this better through technical means.

In principle, transactions that are not yet included in blocks can be replaced by a loose duo of a scammer and a miner willing to help the scammer out, which creates a certain risk for a merchant to accept an unconfirmed Bitcoin transaction.

Apart from preconsensus approaches discussed elsewhere, which will cut down the time window in which a scammer might be successfully double-spending, there is also the option to use payment channels to have secure and fast transactions.

However, this necessitates set up of a payment channel before the transaction takes place, which creates a burden for the customer as well as the merchant. This is the approach taken by the Core variant (BTC) of Bitcoin with their Lightning Network implementation.

A third alternative, proposed here rather for the Cash variant (BCH) of Bitcoin (henceforth simply called Bitcoin) is to use the usual zero-confirmation payment approach, but add a special feature to the transaction that will act as a forfeit, should the scammer attempt a double spend of the money that he sent to the merchant.

In about two months from now, the OP_CHECKDATASIG and OP_CHECKDATASIGVERIFY opcodes are planned to activate for Bitcoin. Using these opcodes, it is possible to implement such a forfeit in an automated way. This scheme is hereby named Zero Confirmation Forfeits, or ZCF for short.

Prior art

There is an old idea by Peter Todd for a merchant to go forward with a scorched earth policy in case the customer turns out to be a scammer and double-spends.

The idea is described (and criticized) in an older post here on Medium by Mike Hearn here: https://medium.com/@octskyward/replace-by-fee-43edd9a1dd6d.

Then, there is a paper on a scheme that is overall quite close to the one described here: https://eprint.iacr.org/2017/394.pdf

In this paper, the authors describe a way to use specially prepared outputs with specially prepared signatures so that double-spending a transaction will reveal the private key.

In contrast, the scheme that is discussed herein, using the to-be-activated CHECKDATASIG/-VERIFY opcodes can also be implemented in a backwards-compatible manner and does not need the creation of any special outputs before paying a merchant, unlike the scheme by Solà et al.

All that is needed is upgraded software and customers and merchants can use this scheme right away, without further preparation. This seems highly desirable from a user interface perspective.

Current status of the proposed scheme

This post is merely exploring a proof-of-concept of this scheme. Much work is necessary to make this a real world feature of Bitcoin!

Brief overview of the new opcodes

OP_CHECKDATASIG and OP_CHECKDATASIGVERIFY, which are tweaked variants of Andrew Stone's OP_DATASIGVERIFY opcode, allow to check the validity of a Bitcoin ECDSA signature in the Bitcoin Script Forth-like predicate language. OP_CHECKDATASIG will return true (1) on the stack when the triple

[signature] [message] [public key]

turns out to be validly signed data resp. false (0) for an mismatching signature.

The [message] part is hashed once with SHA256 before it is used for checking the signature, a fact which will become important later in this document. Similarly, OP_CHECKDATASIGVERIFY works like OP_CHECKDATASIG, but will not leave a result on the stack but rather abort the script as failing, should the signature turn out not to be valid. A very similar (or even functionally identical) opcode reportedly exists in Blockstream's Elements project as well.

Creating a Zero Conf Forfeit transaction

The key insight for this scheme is that double-spending a transaction means double-spending one of its inputs. And double-spending one of its inputs means creating two distinct signatures that are valid for the same public key.

A merchant who requires a zero-confirmation transaction with forfeit (a ZCF transaction) will therefore require from the customer a transaction that has the following structure:

  • Inputs: [P2PKH inputs 1] ... [P2PKH input I]
  • Outputs: [any-type-output 1] ... [any-type-output O] [Forfeit Output]

With the [Forfeit Output] being the key requirement, though it is also important that all of the inputs are of the P2PKH type and also from distinct addresses (otherwise, the customer will lose his forfeit by default). Only then should a transaction deemed to be of the Zero Conf Forfeit (ZCF) type.

The forfeit output is a P2SH output that pays to a specially prepared forfeit script. This forfeit script will allow to spend the output using either a scriptSig that contains just

[signature] [public key] [P2SH script]

or by supplying two distinct messages and signatures for the same public key (the same for the two messages, not the same as above), like this:

[signature1] [message1] [signature2] [message2] [public key] [P2SH script]

The first one is the regular spending case. In this case, the well-behaved customer simply spends his forfeit output like any other P2PKH output and everyone is happy.

In the second case, a miner has seen an attempted double-spend by the customer and uses the signed data from the double-spending as well as the regular (paying to the merchant) transaction to spend the forfeit output to himself. Important to note here is that CHECKDATASIG/-VERIFY allows to check the signatures of Bitcoin transactions.

An example of creating a ZCF-enabled transaction

Assume that 08454094ee44d4fcb7f92c90d57a10d57ad4dc4d7ccafe08a582fff42b0e08ba:0 is an output that has received 9999.9 testBCH to address bchreg:qrcj0e448csqtyc8rp5p6fzluk3tytetnu6eyusazv, like this:

$ ./bitcoin-cli -regtest decoderawtransaction 0200000001c093ebf9c7a22bafbc831b1dcbc075657c82636459c9cd1339926fe6a51959d8000000006b483045022100c7b546f4c2c3bf57f756fc175416a496abda199839ab6588b3eae8bff3f6d0d8022048ba06d2258e4997619ac7644347fe0e1faa78ea8500783e8f37a1633fc4721a412103cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c1feffffff0280790cd4e80000001976a914f127e6b53e2005930718681d245fe5a2b22f2b9f88acd8849800000000001976a914f288e5578c011f98eb4c759e0b0ea9169c1d335e88ac93010000
{
  "txid": "08454094ee44d4fcb7f92c90d57a10d57ad4dc4d7ccafe08a582fff42b0e08ba",
  "hash": "08454094ee44d4fcb7f92c90d57a10d57ad4dc4d7ccafe08a582fff42b0e08ba",
  "size": 226,
  "version": 2,
  "locktime": 403,
  "vin": [
    {
      "txid": "d85919a5e66f923913cdc9596463827c6575c0cb1d1b83bcaf2ba2c7f9eb93c0",
      "vout": 0,
      "scriptSig": {
        "asm": "3045022100c7b546f4c2c3bf57f756fc175416a496abda199839ab6588b3eae8bff3f6d0d8022048ba06d2258e4997619ac7644347fe0e1faa78ea8500783e8f37a1633fc4721a[ALL|FORKID] 03cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c1",
        "hex": "483045022100c7b546f4c2c3bf57f756fc175416a496abda199839ab6588b3eae8bff3f6d0d8022048ba06d2258e4997619ac7644347fe0e1faa78ea8500783e8f37a1633fc4721a412103cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c1"
      },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 9999.90000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 f127e6b53e2005930718681d245fe5a2b22f2b9f OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914f127e6b53e2005930718681d245fe5a2b22f2b9f88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "bchreg:qrcj0e448csqtyc8rp5p6fzluk3tytetnu6eyusazv"
        ]
      }
    },
    {
      "value": 0.09995480,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 f288e5578c011f98eb4c759e0b0ea9169c1d335e OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914f288e5578c011f98eb4c759e0b0ea9169c1d335e88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "bchreg:qreg3e2h3sq3lx8tf36euzcw4ytfc8fntc3a3jpsej"
        ]
      }
    }
  ]
}

The customer now creates the following ZCF-enabled transaction to pay the merchant 4400.0 testBCH at address bchreg:qp7a5v7t3ejacvznnpf8n6w5k3z9jtjanug7u447fj and put up a forfeit of 5599.8 testBCH, that he can spend forward (should he not double-spend) from his address bchreg:qz938xjjwnxgtcknd48e0y32zkh96lmg4utd5jc66s:

$ ./bitcoin-cli -regtest decoderawtransaction 0100000001ba080e2bf4ff82a508feca7c4ddcd47ad5107ad5902cf9b7fcd444ee94404508000000006a4730440220665817cdf25beb8505fee4ecc8239e63d973183f5741d9bee68f883df1c8c04402204e5a08daee4a65c68eaf90e7f60ed0b436de16f49b5aca48724c70d9f32a41cb412103cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c1000000000200300b72660000001976a9147dda33cb8e65dc3053985279e9d4b444592e5d9f88ac00b368618200000017a914fcc909008e148ac6dc5258d8884fb167da4904c48700000000
{
  "txid": "b25d2fff5b0e3f106b6fa31d7ed6a7a1da72f923128316352397aae0da1eec79",
  "hash": "b25d2fff5b0e3f106b6fa31d7ed6a7a1da72f923128316352397aae0da1eec79",
  "size": 223,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "08454094ee44d4fcb7f92c90d57a10d57ad4dc4d7ccafe08a582fff42b0e08ba",
      "vout": 0,
      "scriptSig": {
        "asm": "30440220665817cdf25beb8505fee4ecc8239e63d973183f5741d9bee68f883df1c8c04402204e5a08daee4a65c68eaf90e7f60ed0b436de16f49b5aca48724c70d9f32a41cb[ALL|FORKID] 03cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c1",
        "hex": "4730440220665817cdf25beb8505fee4ecc8239e63d973183f5741d9bee68f883df1c8c04402204e5a08daee4a65c68eaf90e7f60ed0b436de16f49b5aca48724c70d9f32a41cb412103cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c1"
      },
      "sequence": 0
    }
  ],
  "vout": [
    {
      "value": 4400.00000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 7dda33cb8e65dc3053985279e9d4b444592e5d9f OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9147dda33cb8e65dc3053985279e9d4b444592e5d9f88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "bchreg:qp7a5v7t3ejacvznnpf8n6w5k3z9jtjanug7u447fj"
        ]
      }
    },
    {
      "value": 5599.80000000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_HASH160 fcc909008e148ac6dc5258d8884fb167da4904c4 OP_EQUAL",
        "hex": "a914fcc909008e148ac6dc5258d8884fb167da4904c487",
        "reqSigs": 1,
        "type": "scripthash",
        "addresses": [
          "bchreg:pr7vjzgq3c2g43ku2fvd3zz0k9na5jgycslg3ae66v"
        ]
      }
    }
  ]
}

The first output is the regular payment to the merchant. The second one is the P2SH forfeit output. The script that creates the above P2SH output is:

76a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af8763ac6776a914f127e6b53e2005930718681d245fe5a2b22f2b9f8763785479879169766bbb6cba676a6868,

which decodes to:

OP_DUP OP_HASH160 8b139a5274cc85e2d36d4f97922a15ae5d7f68af OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    OP_DUP OP_HASH160 f127e6b53e2005930718681d245fe5a2b22f2b9f OP_EQUAL
    OP_IF
        OP_OVER 4 OP_PICK OP_EQUAL OP_NOT OP_VERIFY
        OP_DUP OP_TOALTSTACK OP_CHECKDATASIGVERIFY
        OP_FROMALTSTACK OP_CHECKDATASIG
    OP_ELSE
        OP_RETURN
    OP_ENDIF
OP_ENDIF

The above idea is implemented here with a couple of nested OP_IF and OP_ELSE statements. Let's go into it in detail:

OP_DUP OP_HASH160 8b139a5274cc85e2d36d4f97922a15ae5d7f68af OP_EQUAL
OP_IF
    OP_CHECKSIG

This part looks superficially similar to the scriptPubKey of a P2PKH output and indeed it is. Given [sig] [public key] on the stack, this will hash the public key, compare it to the given hash / address, check for equality, and if it is equal, do a regular CHECKSIG operation. Note that the 1 that is put on the stack by the OP_EQUAL will be consumed by the OP_IF. A successful OP_CHECKSIG will then lead to a succesful script execution with a 1 on the stack from this signature checking. This is the code path that allows the customer to regularly spend his forfeit output forward, in case he decides to not double-spend any of the inputs. Next, the else path that starts with this:

OP_DUP OP_HASH160 f127e6b53e2005930718681d245fe5a2b22f2b9f OP_EQUAL
    OP_IF

Again, this looks superficially similar to a standard output. Here, the public key of a stack that contains [signature1] [message1] [signature2] [message2] [public-key] is checked not for being a valid key for spending the output, but is rather checked to be one of the input public keys. This is to test for double spending any input. Again the OP_EQUAL and OP_IF combination will leave the stack as it was upon entry into the script. As noted above, the signatures and messages are expected to be extracts of Bitcoin transactions, more specifically the transaction to the merchant with the forfeit output itself, plus the attempted double-spend by the customer turned scammer. So, to continue in this code path:

OP_OVER 4 OP_PICK OP_EQUAL OP_NOT OP_VERIFY

This checks for the messages being not equal and will fail the script otherwise. First, the message2 is picked and put onto the stack once more with OP_OVER, and then 4 OP_PICK picks the message1. They are compared for inequality then with OP_EQUAL OP_NOT OP_VERIFY, with terminal failure if they are equal. Then,

        OP_DUP OP_TOALTSTACK OP_CHECKDATASIGVERIFY
        OP_FROMALTSTACK OP_CHECKDATASIG

Checks the two message, pubkey, signature combinations using CHECKDATASIG resp. CHECKDATASIGVERIFY. The pubkey is needed for both checks and is saved for convenience onto the altstack with OP_DUP OP_TOALTSTACK. The triple (signature2, message2, public key) is first checked with OP_CHECKDATASIGVERIFY and then the triple (signature1, message1, public key) is checked with OP_FROMALTSTACK OP_CHECKDATASIG, leaving the final 1 for success on the stack.

What follows is

OP_ELSE
        OP_RETURN
    OP_ENDIF
OP_ENDIF

which is the else path ending in an invalidating OP_RETURN in case other data is supplied to the script. In the following, an example of each valid code path and spending situation is now presented.

Example of regular spend of the forfeit output

In the first case of a regular spend, the supplied pubkey is hashed to yield address 8b139.. (qz938xjj..) and a regular OP_CHECKSIG is executed. Such a regular spending transaction could for example be the following, which just spends the above output forward to the same address:

$ ./bitcoin-cli -regtest decoderawtransaction 010000000179ec1edae0aa97233516831223f972daa1a7d67e1da36f6b103f0e5bff2f5db201000000af4830450221009800712a9042fc2bcf7cce2be6d907f064ddf61b55183d4988d9cd1128fb422a0220070cf7db4e1cea03e3cf0d902bfff44071f93e7a1446b8724fb7845c1d1d6e0641210320db2c52e5d23f2730770ca136c717359c73c1d1b9a4bf288bf3606bf116353f4376a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af8763ac6776a914f127e6b53e2005930718681d245fe5a2b22f2b9f8763785479879169766bbb6cba676a6868000000000118af6861820000001976a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af88ac00000000
{
  "txid": "7f6c4b6e21d80b6e0ce36cefe85c010603332d9d981da7231a667fed2b9053f7",
  "hash": "7f6c4b6e21d80b6e0ce36cefe85c010603332d9d981da7231a667fed2b9053f7",
  "size": 260,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "b25d2fff5b0e3f106b6fa31d7ed6a7a1da72f923128316352397aae0da1eec79",
      "vout": 1,
      "scriptSig": {
        "asm": "30450221009800712a9042fc2bcf7cce2be6d907f064ddf61b55183d4988d9cd1128fb422a0220070cf7db4e1cea03e3cf0d902bfff44071f93e7a1446b8724fb7845c1d1d6e06[ALL|FORKID] 0320db2c52e5d23f2730770ca136c717359c73c1d1b9a4bf288bf3606bf116353f 76a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af8763ac6776a914f127e6b53e2005930718681d245fe5a2b22f2b9f8763785479879169766bbb6cba676a6868",
        "hex": "4830450221009800712a9042fc2bcf7cce2be6d907f064ddf61b55183d4988d9cd1128fb422a0220070cf7db4e1cea03e3cf0d902bfff44071f93e7a1446b8724fb7845c1d1d6e0641210320db2c52e5d23f2730770ca136c717359c73c1d1b9a4bf288bf3606bf116353f4376a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af8763ac6776a914f127e6b53e2005930718681d245fe5a2b22f2b9f8763785479879169766bbb6cba676a6868"
      },
      "sequence": 0
    }
  ],
  "vout": [
    {
      "value": 5599.79999000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 8b139a5274cc85e2d36d4f97922a15ae5d7f68af OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "bchreg:qz938xjjwnxgtcknd48e0y32zkh96lmg4utd5jc66s"
        ]
      }
    }
  ]
}}

Note the scriptSig that pushes a signature and pubkey just like for a regular P2PKH output, plus the P2SH script itself, as needed for P2SH outputs.

Example of forfeit spend to the miner in case of an input double spend

Now, if the customer is a scammer and double-spent the transaction, two different signature and message combinations will exist for the public pair. Spending the forfeit might then look like this:

$ ./bitcoin-cli -regtest decoderawtransaction 010000000179ec1edae0aa97233516831223f972daa1a7d67e1da36f6b103f0e5bff2f5db201000000fd3701473045022100cf7e027d3423845c8db9ea81654c739bb009454728cee307cda2963f672c7cc2022012a2ed81a9544ce8eaf47cfbb466a6267684127fd06cc423c4e2a3c702173d212093f66ecca97e91ab69cf8e62838e92992d0a25a4432d6e75da9ba7b90a82d6bf4630440220665817cdf25beb8505fee4ecc8239e63d973183f5741d9bee68f883df1c8c04402204e5a08daee4a65c68eaf90e7f60ed0b436de16f49b5aca48724c70d9f32a41cb20a51b7e2846c3dbbe54fd830461db15f3d18f63d1f596baafb2a8c8b4ef90e6582103cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c14376a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af8763ac6776a914f127e6b53e2005930718681d245fe5a2b22f2b9f8763785479879169766bbb6cba676a6868000000000100b36861820000001976a914b0db3d9021a1066ed5be4c2954162a6f4e757c8c88ac00000000
{
  "txid": "4d351c6b0eb615bdd8b826aa2af4bf9f3f1b8d2f7aa95cd2f4c0c19ecd9cec76",
  "hash": "4d351c6b0eb615bdd8b826aa2af4bf9f3f1b8d2f7aa95cd2f4c0c19ecd9cec76",
  "size": 398,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "b25d2fff5b0e3f106b6fa31d7ed6a7a1da72f923128316352397aae0da1eec79",
      "vout": 1,
      "scriptSig": {
        "asm": "3045022100cf7e027d3423845c8db9ea81654c739bb009454728cee307cda2963f672c7cc2022012a2ed81a9544ce8eaf47cfbb466a6267684127fd06cc423c4e2a3c702173d21 93f66ecca97e91ab69cf8e62838e92992d0a25a4432d6e75da9ba7b90a82d6bf 30440220665817cdf25beb8505fee4ecc8239e63d973183f5741d9bee68f883df1c8c04402204e5a08daee4a65c68eaf90e7f60ed0b436de16f49b5aca48724c70d9f32a41cb a51b7e2846c3dbbe54fd830461db15f3d18f63d1f596baafb2a8c8b4ef90e658 03cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c1 76a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af8763ac6776a914f127e6b53e2005930718681d245fe5a2b22f2b9f8763785479879169766bbb6cba676a6868",
        "hex": "473045022100cf7e027d3423845c8db9ea81654c739bb009454728cee307cda2963f672c7cc2022012a2ed81a9544ce8eaf47cfbb466a6267684127fd06cc423c4e2a3c702173d212093f66ecca97e91ab69cf8e62838e92992d0a25a4432d6e75da9ba7b90a82d6bf4630440220665817cdf25beb8505fee4ecc8239e63d973183f5741d9bee68f883df1c8c04402204e5a08daee4a65c68eaf90e7f60ed0b436de16f49b5aca48724c70d9f32a41cb20a51b7e2846c3dbbe54fd830461db15f3d18f63d1f596baafb2a8c8b4ef90e6582103cdb375ff72e4e42898d80193c4026a91d8acea44a42ddf1c30b72d759e7d40c14376a9148b139a5274cc85e2d36d4f97922a15ae5d7f68af8763ac6776a914f127e6b53e2005930718681d245fe5a2b22f2b9f8763785479879169766bbb6cba676a6868"
      },
      "sequence": 0
    }
  ],
  "vout": [
    {
      "value": 5599.80000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 b0db3d9021a1066ed5be4c2954162a6f4e757c8c OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914b0db3d9021a1066ed5be4c2954162a6f4e757c8c88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "bchreg:qzcdk0vsyxssvmk4hexzj4qk9fh5uatu3s29fz5p5t"
        ]
      }
    }
  ]
}

The scriptSig here is a lot more verbose, containing the two signatures, the two messages (hashes) as well as the public key (that hashes to f127...) and finally the P2SH script.

In this example, the miner would spend the forfeit to a miner-owned address. This could be simplified, of course, to be a transaction without any outputs, therefore spending everything as fees to himself. For testing this, it was a lot simpler to make this a regular-looking transaction, however.

Any other input should end up in the OP_RETURN and thus fail to spend the output.

To be noted here is that CHECKDATASIG will hash the input given to it once. Very luckily, any transaction input of the P2PKH type is hashed twice before its signature is checked in bitcoind (for the required SIGHASH_FORKID signature type). This allows to supply the single hashing intermediate result from the SignatureHash(..) function in bitcoind to this script to enable the forfeit logic.

In case of more inputs, the above script would be adapted with more input public key hashes in an OP_OR clause or similar.

Status, outlook and proposed changes

The whole "implementation" right now consists of a bunch of very messy, still unpublished ad-hoc scripts based on the primal100 variant of Vitalik' Buterins pybitcointools(https://github.com/primal100/pybitcointools) plus an abuse of the functional test suite in the Bitcoin Unlimited software stack. Furthermore, as Bitcoin Unlimited has not yet been updated to support the CHECKDATASIG/-VERIFY opcodes, the above example has been created and tested using the BitcoinABC implementation.

For reference, to allow playing with CHECKDATASIG, it should be started like this:

./bitcoind -regtest -debug -magneticanomalyactivationtime=0 -relaypriority=0

In short, all this needs to be cleaned up, as well as documented, as well as implemented in consumer wallets such as Copay to be of real use. Furthermore, mining software likely needs to be adapted to allow an exemption for double-spending the discussed forfeit outputs described here in. A standard needs to be written - and so forth ...

If this is deemed to be an acceptable way to deal with the 0-conf problem, it might make sense to change the mining code in leading implementations to allow spending forfeits easy as a discouragement of scammers.

It should finally be noted that the nature of this change is one that "in the expected case, it will never be used". Just having this feature around might be enough deterrent for scammers to not try scamming - and using ZCF transactions should deter potential scammers from trying.

However, it is also imaginable that this feature will - if implemented ecosystem-wide - rarely be used, as 0-conf is currently quite usable for low risk anyways. This might end up with a minor Volterra-Lotka-like dynamic with a low amount of scams happening and merchants reacting by requiring forfeits.

I propose that wallets and point-of-sale system display a transaction protected with a sufficient (TBD) forfeit with a green 'F' in their user interfaces.

Conclusions

The above presents a scheme to create forfeit outputs that would discourage people from double-spending unconfirmed payments to merchants. It includes an example set of regtest transactions to demonstrate the general idea.

The author is happy for any input on this, including bug reports, pointers to related work and similar. Finally, I like to thank Andrew Stone and one anonymous reviewer for having a detailed look at the draft of this post.

I also further like to thank Andrew Stone and imaginary_username, because AFAIR I developed this idea when throwing ideas back and forth in a discussion with them on the Bitcoin Unlimited slack.

(Final editing note: I planned to publish this on Medium, but it screwed up the formatting of all the code. So I am leaving it here on github then.)

Addendum

From the discussion on reddit: One worry that was mentioned by user TorusJKL and others is that there might be collusion between miner and scammer. In this case, one way to address this is to increase the forfeit, assuming the double spend is sent into the network. And if reaches only a particular miner, adding an extra time lock on the forfeit until it can be spend forward (but only in the regular spending case) would seem to further discourage this kind of collusion.

@fivepiece
Copy link

Hi @awemany
I'd appreciate if you could have a look at https://gist.github.com/fivepiece/b2f87b5e4bda40452cb081a58b9471f5 and let me know your thoughts.

Cheers

@DrSammyD
Copy link

About the addendum. Something like this would work for your Finney Attack. https://gist.github.com/DrSammyD/097f84b49e02fda4ab434515346f6039#finney-attack-example

@TierNolan
Copy link

You could make it so that the forfeit goes to the merchant rather than the miners, so it works as actual insurance.

OP_IF
    "1 day" OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 <customer_pub_key_hash> OP_EQUALVERIFY OP_CHECKSIG
OP_ELSE
    -- DOUBLE SPEND PROOF --
    OP_HASH160 <merchant_pub_key_hash> OP_EQUALVERIFY OP_CHECKSIG
OP_ENDIF

The top branch is a standard output but with a 1 day delay to allow the merchant to claim the forfeit.

The amount asked by the merchant could depend on how often zero-conf fails.

Assume a double spend is successful 1% of the time and the merchant requests a 2% fee. 99% of the time, the merchant gets 1.02X of the payment and 1% of the time, they get nothing. That works out at an average payment of 1.0098X to the merchant.

As long as double spends are hard to achieve, it makes zero-conf safe (on average) for merchants, rather than just about deterrence.

Even if double spends worked 20% of the time, a 30% deposit wouldn't necessarily be unreasonable.

@bitjson
Copy link

bitjson commented Aug 19, 2021

Hey @awemany, thank you for your work on this idea! We figured out some more details and put it all together as a CHIP: https://github.com/bitjson/bch-zce

If you're interested, we'd love to know what you think!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment