Bitcoin Ordinals & Inscriptions are causing a rift in the Bitcoin community, between those who love them and those who hate them. Personally, I find them silly and anachronistic. But today, rather than attempting to argue one side or the other, I’d like to demonstrate why personal feelings, mine or yours, are irrelevant.
I would like to prove that inscriptions can be constructed in such a way that they are uncensorable, their content being revealed only after having already been included in a block. I hope this will demonstrate to the well-meaning but unimaginative supporters of Luke’s “bugfix” crusade that their efforts at enforcing “spam filtering” of inscription transactions are ultimately doomed.
Inscriptions are a relatively simple tool for those already familiar with the workings of Bitcoin transactions. At a high-level, they need only a few steps to execute:
- Construct an
envelopescript fragment, which contains a series of arbitrary data pushes which will not be executed.
- Create a script pubkey (a locking script) which includes the
envelopefragment. Since the
envelopeis a no-op, it is never executed and can’t have any effect on the script execution. For example:
Send some bitcoins to a taproot address which commits to the above script pubkey as one of its script leaves.
Spend those bitcoins, revealing this script (and thus the inscription data) in the process.
The bitcoins are reclaimed by the owner, minus fees, and the arbitrary data (the inscription) is recorded in the witness data of the block in which the TX is mined.
I see this approach as pointless and inefficient for all its most common use cases.
If the goal is to communicate and transfer ownership of a unique digital asset (a Non-Fungible Token), then one should instead defer to some authority or Oracle which certifies the unique ownership of that asset. That entity can publish an OpenTimestamped signature attesting to the ownership, or publish a signed transaction which includes a commitment to the new owner (e.g. using a Taproot-style commitment hash).
If the purpose of the inscription is to create a provenance anchor in time (e.g. to prove something existed before a certain time), then OpenTimestamps is a fast and zero-cost alternative - far simpler and with near zero on-chain footprint.
Yet, in spite of my subjective opinions, there have been over 47 million inscriptions made to date, storing over 13 gigabytes of data on the Bitcoin blockchain, costing the uploaders a cumulative 1908 BTC ($79 million USD as of 2023-12) in transaction fees. Jeez.
Okay, clearly people are very willing to throw away their money for no reason, and bitcoin miners are happy to pick it up. So what’s next?
This PR is, as far as I can tell, the spark which ignited the controversy.
The PR is quite a simple change. It works by detecting the particular format of the inscription
envelope inside the witness script block, and counting the size of the inscribed data pushes (the stuff inside the unreachable
OP_IF block). This byte count is tested against the 80 byte limit normally applied to
OP_RETURN outputs, called the “max data-carrier limit”. If the data-carrier limit is exceeded, Bitcoin core labels the transaction as ‘non-standard’, meaning it would not relay that transaction to other nodes, treating it as spam.
Notably this does not prevent the inscription transactions from being considered valid under consensus rules and mined in a block. Doing so would require a hard fork, and is not backwards compatible given that numerous inscription transactions have already been mined.
Instead, it is merely a soft-block to allow individual node operators to exclude transactions which they feel are not aligned with their vision for how Bitcoin transactions should be used. Any node operator today can modify their node to run such soft-blocking rules. For example, perhaps your node would exclude any transaction which pays to an OFAC sanctioned bitcoin address, or transactions which contain large multisig scripts.
Just like those other examples of soft-blocking though, this is a narrowly-focused fix. It addresses only the specific format of inscription which is currently popular.
I would like to demonstrate another kind of inscription which cannot be filtered using a simple analytical test. I hope this will prove Luke’s PR is pointless, by showing that the underlying functionality which enables inscriptions can be made highly fungible with normal Bitcoin transactions.
Theorem: One can hide arbitrary data in a Bitcoin transaction’s witness, such that the true plaintext data is only revealed after the transaction is mined.
Consequences: If my theorem is correct, it means that filtering, blocking, or otherwise censoring transactions based on the kind of data they contain is an unreachable goal. Bitcoin nodes which process and relay transactions would be hard-pressed to distinguish between an inscription transaction and a normal Bitcoin transaction. Any attempt to do so would likely result in numerous false positives and false negatives.
We define concept of a Script Cipher. A Script Cipher is a layer of encryption between arbitrary message data (e.g. an inscription), and valid Bitcoin script bytecode.
A Script Cipher has two methods:
encode(bytes, translator) -> fragments
encode method converts a finite sequence of bytes into a set of Bitcoin script fragments. Each of the
fragments looks like a genuine Bitcoin locking script. For example:
OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG
<2> <pubkey1> <pubkey2> <pubkey3> <3> <OP_CHECKMULTISIG>
OP_SHA256 <hash> OP_EQUALVERIFY <pubkey> OP_CHECKSIG
translator is a succinct but randomized seed which describes the mapping between bytes and scripts. It might be a seed for a cryptographically secure RNG, for example, or a key to a cipher.
A Script Cipher should have a very large number of possible
translators, so in practice a
translator should be thought of as a decryption key, and the
fragments collectively act as a ciphertext.
decode(fragments, translator) -> bytes
decode method decrypts the
fragments back into the arbitrary data bytes. Note that it requires the
translator. A key property of a Script Cipher is that without the
fragments produced by its
encode method cannot be distinguished from regular Bitcoin locking scripts.
I’ll pause for a second to prove that Script Ciphers exist, by describing a very simple instance of a Script Cipher.
translator be a randomly generated symmetric encryption key for an authenticated encryption scheme.
For the encoding procedure:
translator. This produces a
- Break the
ciphertextinto a stack of fixed-length
chunks, each 32 bytes long.
- If needed, pad the last chunk until its length is also 32.
- For each
chunk, construct a simple P2PKH script composed as follows:
OP_DUP OP_SHA256 <chunk> OP_EQUALVERIFY OP_CHECKSIG
This set of script pubkeys forms the
To decode, simply extract & collect all
chunks from the
fragments, then concatenate and decrypt them with
This is a rudimentary and inefficient Script Cipher, but is by no means the only way of achieving this kind of ciphered encoding. One could also combine other classes of locking script, such as hash-time locks, or even generate the locking script fragments dynamically based on the message content and
But before we can send any transactions, we must compose an
envelope script which contains all of the above
fragments, plus one extra locking script condition which will be used to actually claim the inscribed bitcoins.
Each of the
fragments is included in the
envelope as a mutually exclusive locking condition, forming a nested tree of
OP_ELSE branches which might look something like this:
Carrying on from the trivial example where
fragments is an array of P2PKH conditions, an
envelope containing 4
pk_hash is the hash of an actual valid pubkey, for which the inscription recipient user has the secret key.
In the above example, I placed the locking condition paying to
pk_hash in the first
OP_IF branch. But in principle the
pk_hash branch could be inserted anywhere among the other
fragments if desired. Even if the
pk_hash branch is nestled in between, though, the ciphertext chunks must maintain their order for decoding to be possible.
Since each of the
chunks is a piece of ciphertext, they appear indistinguishable from SHA256 hashes, and so appear to be valid alternative spending conditions. But only one P2PKH branch of the script can actually be used at spending time.
In a more sensible (read: efficient) scenario, unused branches could be hidden in the leaves of a TapScript merkle tree, to gain a script size reduction. However, script size reduction is the opposite of what our user wants here. In this approach, we are intentionally abstaining from decomposing this script from
OP_IF branches into TapScript leaves so that we can include all of our
chunks in the witness script.
We will now go about publishing our inviscription on-chain.
The next steps are very similar to standard inscriptions, except an inviscription needs at least three transactions:
- The commit transaction
- The ciphertext transaction
- The translator transaction
This is very similar to a regular inscription commit transaction. The
envelope script pubkey is converted into a Pay-to-TapRoot address, and the user sends bitcoins to this address.
The ciphertext transaction claims the bitcoins from the
envelope address. In the above example where the
pk_hash locking condition is the very first branch, one would use the following witness stack to unlock the coins.
<signature> <pk> <1>
These - along with the
envelope script itself - would be included in the witness data for one of the inputs to the ciphertext transaction.
chunk of ciphertext is indistinguishable from random data, a node observing this transaction would have no way of telling whether the
chunks encode an inviscription ciphertext, or if the chunks are actual pubkey hashes which could be used for spending coins.
Only after the
translator decryption key is revealed can the
chunks fulfill their true purpose, which leads us to the next stage.
The translator transaction must reveal the
translator, and also point to the appropriate ciphertext transaction input which contains the
envelope script. Using both of these, observers can look up and decrypt the
translator could be done by, for example, including it in a fixed-format
OP_RETURN output, or in a specifically formatted witness script in similar style to the ciphertext’s own
envelope script. Unlike the
envelope though, the translator transaction’s structure must not be intentionally obfuscated - quite the opposite, as its purpose is to enable on-chain discoverability of the plaintext data.
The pointer to the ciphertext transaction can either be explicit, or implicit (by convention).
- Implicit Example: The translator transaction spends from an output of the ciphertext transaction. Alongside the
translator, we also include the input index of the ciphertext transaction.
- Explicit Example: The translator transaction explicitly points to the TXID and input index of the inviscription’s ciphertext
Either way, once the
envelope script is found, it can be decoded into
chunks, e.g. by filtering out the
OP_IF branch used to claim the coins from the commit transaction, and then decrypting the extracted ciphertext. This is just an example though - The Script Cipher is free to specify how exactly encoding and decoding of the
envelope works under a given
However, the translator transaction’s structure should be recognizable and standardized, so that anyone scanning the mempool or blockchain can detect and decrypt the
envelope once they see the translator transaction.
But wait, if the translator transaction can be detected, couldn’t it be censored?
Technically yes, but there is very little incentive to do so.
The idea of inviscription is to use the ciphertext transaction’s fungibility properties to sneak it into a block under the radar of any mempool-level filtering/censorship, and only afterwards to publish the translator transaction, ousting it as an inviscription.
Bitcoin users opposed to inviscriptions could try to filter/censor translator transactions, but by that time, the damage is already done as far as block-space consumption and fee-market inflation. These are the primary motivators for the inscription filtering/censorship debate today. Thus, censoring translator transactions has no practical gain for the censoring parties beyond griefing the inscriber. This renders the practice far less appealing, and less likely to occur.
Take street art as an analogy. Inscriptions are like graffiti, except permanent. Currently, vandals are painting buildings in broad daylight: Their progress is easy to observe, and thus easy to interrupt if we wanted to.
However if we start to interrupt them too much, then the inscribers will just wait until dark, and paint in the shadows while we’re asleep. We wake up and find their works were completed in secret, and by then, nobody can remove them. The damage is already done.
Furthermore, even if one translator transaction can be identified and effectively excluded from the blockchain, the ciphertext is already on-chain, so censoring nodes would need to ensure that no other translator transactions for that inviscription ever make it onto the chain in perpetuity, which is no small feat.
And even then, the inscribing user can still publish their
translator off-chain to reveal the inscription data, and prove they inscribed bitcoins with said data. It just won’t be detectable by on-chain scanning alone.
To be meaningfully resistant to censorship, the ciphertext transaction cannot be easily distinguishable from a regular TapRoot script-spend transaction. While this holds for small transactions, nobody who transacts normal financial payments actually wants to publish TapRoot transactions which waste kilobytes of witness space in redundant nested
OP_IF trees, when they could use a much more efficient TapScript merkle tree instead.
Could nodes simply censor any transaction which includes a large number of mutually exclusive
OP_ELSEpaths in a witness script?
Yes, but envelope formats can be generalized beyond
OP_IF branches. Using similar approaches, one could embed the ciphertext almost anywhere.
- In the TapRoot control block
- In the pubkeys of an
- In the timestamps supplied to
- In pushdata blocks which are consumed, or dropped by
- In op-codes themselves
One could even set up the
envelope script such that the ciphertext data must be appended to the witness stack to unlock the output of the commit transaction. For example, consider this envelope script pubkey:
OP_HASH160 <chunk1_hash> OP_EQUALVERIFY
This simple script could only be unlocked by providing
<sig> ... <chunk3> <chunk2> <chunk1> in the witness stack, where each
chunk is a 520-byte chunk of the ciphertext. The ciphertext could be extracted by simply reading the chunks from the witness.
This is far less efficient than pushing the chunks in the
envelope script pubkey, but it also binds spending of the bitcoins to the knowledge of the chunks. It makes the ciphertext a giant preimage for a hash-lock spending condition, which by any argument is a valid way to encumber Bitcoins.
Couldn’t we filter transactions containing an input that has a very large witness stack? Those are probably inviscription ciphertext transactions.
Technically yes, but the inviscription ciphertext could be broken up across multiple inputs to lower the footprint of any single witness. A single ciphertext TX could be broken up into a set of multiple smaller and unrelated ciphertext transactions, their true cohesive purpose revealed only later by the translator transaction. Any arbitrarily low limit imposed on witness size or transaction size could be bypassed by breaking the inviscription ciphertext up into smaller pieces spread among more transactions.
Besides the methods I’ve discussed here, there are probably countless other ways of obfuscating and revealing arbitrary data on the Bitcoin blockchain. They all invariably appear to be vastly less efficient than what the current inscriptions standard uses, both in terms of computational workload and on-chain storage requirements. And ordinals themselves are infinitely less efficient than simply using OpenTimestamps to prove data provenance off-chain.
If Luke’s PR is merged, the most popular Bitcoin node implementation will no longer relay inscription transactions. Inscription users frustrated by this artificially-imposed barrier will, if pressed, seek out alternative inscription methods to bypass it. Inventive users will find the barrier to be a two-foot fence: easily bypassed by sacrificing efficiency.
Instead of preempting foolish on-chain behavior, it is best to let foolish behavior run itself into the ground while the agents behind it waste their money. Numerous similar hype cycles have occurred in this industry, and they all end the same way: A slow fizzle out as everyone slowly but inevitably migrates to faster, more efficient, lower cost alternatives. I believe same will happen to inscriptions.
We Bitcoiners just need to keep a cool head, avoid rushing into rash action, and let nature take its course.
In the meantime, join me on Github in my efforts to reduce the on-chain footprint of downstream apps and protocols which are causing this inscription debate in the first place.