Skip to content

Instantly share code, notes, and snippets.

@markblundeberg
Last active January 7, 2019 17:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markblundeberg/34d128ddcd95829070e5d8686d44b47a to your computer and use it in GitHub Desktop.
Save markblundeberg/34d128ddcd95829070e5d8686d44b47a to your computer and use it in GitHub Desktop.
BCH 24-bit public notification addresses

This is a brief specification proposing the use of short anyone-can-spend P2SH addresses. This protocol is already in use with BCHMessage as used in OpenSwap, for the 'public bulletin boards' feature, but may be of use in other protocols.

Rationale

Various protocols like on-chain messaging, payment codes, and so on need SPV-wallet-friendly notifications.

  • In BIP47, notifications are sent to an address owned by the recipient, which means that notifications create UTXO dust. If the UTXO dust is cleaned up, this causes a privacy concern as its destination can be traced. If not cleaned, then the UTXO set will grow and grow over time. BIP47 v2 tried to solve this by using a 1-of-2 multisig so that the sender can also clean up the dust, however this creates SPV unfriendliness for the recipient.
  • In stealth addresses, payments are tagged with an OP_RETURN including a few-byte prefix that can be used to alert the recipient. Unfortunately, such OP_RETURNs are not SPV friendly in all cases (ElectrumX for example does not index OP_RETURNs nor does it provide prefix-matching facilities).
  • In OpenSwap, we needed an on-chain 'public bulletin board' (for advertising offers), where all SPV wallets need to be able to easily discover the same set of op_return transactions.

By using anyone-can-spend addresses as described in this document, the result is perfectly SPV friendly, and also means that the notification UTXO dust can be left alone, to be cleaned up by uninvolved third parties. The proposed 16 million notification addresses are sufficiently many to prevent efficient spam attacks, and yet sufficiently few that they can be enumerated, monitored, and cleaned up by third party services.

Specification

Notifications addresses are P2SH scripts that simply push a nonzero value onto the stack.

redeemScript = 0x03 <number>, where **number** is a 24-bit (three byte) integer.
scriptPubkey = OP_HASH160 <redeemscripthash> OP_EQUAL

The notification number may have any three byte value except for either of the values that evaluate to FALSE in bitcoin script: the byte arrays [0, 0, 0] and [0, 0, 128]. With those two values, the result is an unspendable script, hence the utxos cannot be cleaned up. This leaves 2^24-2 = 16777214 different possible notification addresses.

Coins sent to the address can be spent by anyone, using a five-byte scriptSig that simply pushes the redeemscript:

scriptSig = 0x04 0x03 <number>

Note that redeem transactions that simply donate the funds to a miner (with a single 0-value output with 1-byte script OP_RETURN) are currently too small (66 bytes), below the 100 byte limit that currently exists. Such small redeems should be either combined (multiple inputs) or padded (put some arbitrary data in the OP_RETURN) so as to exceed 99 bytes in size.

Generation

The notification address should be deterministically derived from public data known to both the notifier and recipient. The derived value should be uniformly distributed 24-bit values excluding the two unspendable FALSE values. Here is one possible pseudocode (used in BCHMessage):

entropy = hash("Myprotocol" + public_data)
value = int(hash) % 0xfffffe
if value < 0x7fffff:
  value += 1  # exclude 0x000000
else:
  value += 2  # exclude 0x800000
bytes = unsigned_int_to_3_byte_little_endian(value)
redeemscript = [0x03] + bytes
redeemscripthash = ripemd160(sha256(redeemscript))

Another approach would be to include a counter in the hash, and truncate the last 3 bytes of the entropy: if the resulting bytes are FALSE then retry after incrementing the counter. It is up to the protocol designer to choose their method of payload derivation.

Common practices (deniability + privacy)

All users of the notification addresses should enforce the following behaviour for notification transactions, so that all protocols appear the same.

  • Output 0 should hold OP_RETURN data in a single push of 220 bytes, whose content is indistinguishable from random data from the viewpoint of a third party.
  • Output 1 should go to the notification address (scriptpubkey = OP_HASH160 OP_EQUAL) with an amount of 540 satoshis (the minimum that is allowed to be sent to P2SH).
  • Output 2 should receive the change from the notification, if any.
  • Transactions should be version 1, have final sequence numbers, and locktime 0.
  • Senders and recipients should not clean up their notification utxos, but rather leave this for someone else.

As many different protocols (or attacks) may land on the same notification address, protocols should include a checksum (say, at least 128 bits) into the OP_RETURN data that can be easily checked by recipients, to see whether the protocol is correct. The checksum should be produced by HMAC using some secret data that is only known to the sender and intended recipient(s), so that for all other parties will see something indistinguishable from noise.

Recipes

Common secret key (symmetric)

For notification addresses that involve a common shared secret encryption key:

  • public_data = key
  • op_return_payload = <iv> <ciphertext> <hmac>

It is suggested that encrypt-then-MAC scheme is used, i.e., the hmac is calculated as HMAC(key, iv|ciphertext)

Shared notification (asymmetric)

For asymmetric notifications, the sender knows the recipient's elliptic curve public key.

For static Diffie Hellman the sender's public key can be obtained from the P2PKH scriptSig in the notification transaction. This gives an implicit shared Diffie Hellman secret. The advantage here is that the sender can later recover the message that they sent, by simply using their wallet's private key. The resulting message is also uniformly random.

  • public_data = recipient public key
  • encryption_key = hash(diffie hellman shared secret)
  • op_return_payload = <iv> <ciphertext> <hmac>

Ephemeral notification (asymmetric)

For ephemeral diffie hellman, the sender should include an ad-hoc EC point in their message for encryption purposes. Unfortunately, there is a problem: EC points are not indistinguishable from random data. This is especially so for uncompressed/compressed points, but even just encoding the x coordinate leaves some traces as only about half of the possible x coordinates are valid (see Bernstein 2013).

For secp256k1, some large contortions need to be done to yield a covert diffie-hellman scheme, and I don't expect anyone to design their protocol in this way. Other elliptic curve systems make it easier to have covert diffie hellman ("Elligator" -- Bernstein 2013), but it is also unlikely that wallet developers will want to support non-secp256k1 curves. (However, see also Elligator Squared.)

For secp256k1 ephemeral Diffie Hellman I suggest the following common recipe, so that at least all such protocols resemble each other:

  • public_data = recipient public key
  • encryption_key = hash(diffie hellman shared secret using ephemeral key)
  • op_return_payload = <compressed ephemeral public key> <ciphertext> <hmac>

Thus, there will likely always be two sets of notification protocols: A) those that use secp256k1 ephemeral Diffie Hellman, and B) those that use symmetric crypto or static Diffie Hellman (above). If it turns out that ephemeral Diffie Hellman protocols are the most popular, then I suggest that the other protocols should be upgraded to steganographically hide themselves by using an ephemeral point as the encryption IV.

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