Bitcoin Forum
October 25, 2026, 10:43:06 PM *
News: Latest Bitcoin Core release: 31.1 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: Dual-use transactions for harmless, quasi-steganographic arbitrary data on-chain  (Read 77 times)
This is a self-moderated topic. If you do not want to be moderated by the person who started this topic, create a new topic.
nullius (OP)
Copper Member
Hero Member
*****
Offline

Activity: 644
Merit: 2703


AnonChange.com


View Profile WWW
October 24, 2026, 04:00:00 PM
Merited by athanred (1)
 #1

This discussion is transplanted from the off-topic forum:

It’s the world’s most expensive database, and it’s append-only.
You can always commit some data to r-value of a signature, for no additional cost. Then, it will be committed to your next transaction, whenever it will happen. Or it can be hidden in some unused TapScript branch, even behind OP_RETURN, so it will never be pushed on-chain, but it will still affect address derivation, so it can be later proven to be connected with it.
(Note:  OP_RETURN puts things on-chain, but not in the UTXO set.)
Another way is mining. You already produced a hash with at least 36 zero bits, so you know some bits can be grinded here and there. Then, you can count, how much space your transactions are taking now, and calculate, how long it would take, to grind things, and save them on-chain, without any additional cost.

Yet another thing is hashing. If you want to store encrypted messages, then you have to write a lot of bytes. However, if you want to just prove, that "you knew X key at Y timestamp", then just committing it to r-value is sufficient, and can be proved later. I think blockchain is more about timestamping, than about pushing actual data, otherwise, we wouldn't have so many pruned nodes.

The way that I ground txid 000000000fdf0c619cd8e0d512c7e2c0da5a5808e60f12f1e0d01522d2986a51 for the Lauda memorial puzzle was rudimentary.  I wrote my own vanity address generator in 2018; in March 2021, I did a one-off dirty hack on it to grind a change address that produced the desired zeroes in the txid.  As you can see on-chain, I later spent the change.

At the time, I did realize that I could use this method to store data in the txid!  I’ve considered writing a wallet which does exactly that, and thought about a protocol for it.  If the data are encrypted, this is even a covert channel; is it called “steganography” to hide pseudrandom data in pseudorandom data?

(Up to this point, I’m vaguely aware that although I thought of it independently, the idea is not really original.  I believe that much of the below is original.)

To avoid losing funds, the change address should be ground according to a well-defined system—say, incrementally tweak what the BIP 32 HD change key would be for that transaction.  Then, disaster recovery of unspent change would have the same computational cost as it took to create the transaction.

Quote from: callout alert box (not a quote)
Note:  I’ve also thought of how to integrate vanity addresses with HD wallets, such that recovering the vanity address private key from seed has the same computational cost as creating the vanity address.  If I have good forum experience now, I’ll update Segvan with this as a reference implementation, and draft a BIP.  All vanity address generators should use the same system for recoverable keys, and wallets should support easy import via m/x/y/z path plus one extra integer.

This terse note serves as prior art, in case some fucking moron later tries to patent my idea.

Thereupon, I just need to work out a protocol for organizing a wallet’s transactions, such that files can be reconstructed from a few bytes per txid by walking through the transactions created by one HD master key (or a branch of the derivation tree).

Grinding 2 bytes into a txid has a trivial cost.  For 4 bytes, it’s a relatively moderate cost.  (The 36 bits of zeroes for the puzzle took a long time, but it was done on four cores of a very old CPU.)  If someone makes a GPU implementation, at least another byte or two can be added without much inconvenience.

By thus means, if someone routinely makes Bitcoin transactions, they can slowly archive small, but non-negligible amounts of their most important data on the blockchain—with no spam and no added tx fees.

athanred, you now gave me the idea for also grinding data into the wtxid, using r-values.  (Or into the txid of a legacy tx, but I don’t want to advocate any continuing non-Segwit usage.)  If one starts with a pseudorandom r-value off /dev/urandom, then increments it as a 256-bit integer to grind the wtxid, I don’t think this should have any security impact; to be extra-sure, one could simply use a securely-seeded fast stream cipher (Chacha20) to generate pseudo-independent r-values, until one gets a hit.

For obvious reasons, the txid needs to be ground before the wtxid.

2–4 bytes per txid, 2–4 bytes per wtxid...  Every bit adds up!

This thread welcomes all further ideas for harmless, non-spam ways of etching arbitrary data into the blockchain with dual-use transactions.  Store arbitrary data on the blockchain, with the transactions that you would make anyway!



Speaking of spam, I see that I missed some awful controversy in the past few years.  I am now tempted to put an image file into an OP_RETURN.  It is the tragedy of the commons:  When not only the blockchain, but the UTXO set is bloated with bullshit that does not belong there, why shouldn’t I (ab)use OP_RETURN for some blockchain art that also makes a sociopolitical statement?

I think I’m actually going to do this, if/when I can spare fee money to carve a pretty picture into Bitcoin forever and forever.

That being said, spammish suggestions are off-topic, and I may delete them accordingly.  Moreover, condoning unspendable UTXO set bloat in this thread will get red trust from me.  If you want to store your cat photos on-chain without cleverly working them into financial data, OP_RETURN or GTFO!

▄███████████████████████▄
█████████████████████████
██████████████▀▀▀████████
██████████▀▀██████▀██████
████▀▀███▄▄███▄████▀█▀▀██
██▌█████▀▀███████████████
██▌█████▄▄▄████▀▀█████▐██
██████████████▄▄██████▐██
██▄▄█▄████▀███▀▀██▄▄█████
██████▄██████▄▄██████████
████████▄▄▄██████████████
█████████████████████████
▀███████████████████████▀
 
 ANONCHANGE 
████
██
██
██
██
██
██
██
██
██
██
██
████
████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
IIIINSTANT CRYPTO SWAPS
  XMR     BTC     ETH     LTC     SOL     USDT     USDC     DAI  


████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
████
██
██
██
██
██
██
██
██
██
██
██
████
████
██
██
██
██
██
██
██
██
██
██
██
████
████
██
██
██
██
██
██
██
██
██
██
██
████
athanred
Full Member
***
Offline

Activity: 207
Merit: 385


View Profile
October 24, 2026, 05:38:33 PM
Last edit: October 24, 2026, 06:38:25 PM by athanred
Merited by nullius (2)
 #2

Quote
to grind a change address that produced the desired zeroes in the txid
It is slow to do it in that way, if you can grind nSequence and nLockTime first.

https://en.bitcoin.it/wiki/NLockTime
Quote
If all inputs in a transaction have nSequence equal to UINT_MAX (0xFFFFFFFF), then nLockTime is ignored.
Which means, that you can just set nSequence to 0xffffffff, and then, you have 32 bits of nLockTime, that can be set with anything. Also, because it is the last field of the transaction, computing SHA-256 on top of it is faster, because you can start from some middle-state, and then grind the last 512-bit chunk of your transaction.

Also, even if you enable both nSequence and nLockTime, then still, there is a lot of freedom here:

https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
Quote
If bit (1 << 31) of the sequence number is set, then no consensus meaning is applied to the sequence number and can be included in any block under all currently possible circumstances.

Let's assume nLockTime is used, so it has to be valid. What we have here:
Code:
+------------+------------------------------+
| Unix Epoch | Time (UTC)                   |
+------------+------------------------------+
| 0x1dcd6500 | Tue Nov 05 00:53:20 1985 UTC |
| 0x20000000 | Mon Jan 05 18:48:32 1987 UTC |
| 0x60000000 | Thu Jan 14 08:25:36 2021 UTC |
| 0x6adcd600 | Sat Oct 24 16:00:00 2026 UTC |
+------------+------------------------------+
If we use numbers between 0x20000000 and 0x60000000, then all of them are valid, and will lead us to some spendable transaction. Then, if we use nSequence between 0x80000000 and 0xffffffff, it will also be ignored. From these two fields, we can use 61 bits. Which is more than enough, to grind some 36-bit target, and it should be faster, than grinding ECDSA public keys (of course, they can be also rotated, but there is no need to do it that often, because it is quite slow).

If some transaction has exactly 80 bytes, then it can be even potentially used by ASICs, because grinding nonce in a block header is quite similar to grinding nLockTime in a transaction. For Segwit data, it is harder, but GPUs should deal with it anyway, for example here is some Segwit message with 57-bit target:

Code:
MSG = 020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001280bc5682e0cbe78108912437ab583daa49a8fe66e995001a22a44aec2a3ab06000000288201369f69210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798acf0d20000000000003d74fbff045b0f32c7adb53ce93b424e7ba280a4328ef880b60058c771b3ef599f9e95840000000081000000
Z = ffffffffffffffedfe243987a5dfa917a1bba0978fd5ce30e6df1b23cf1d8fc4
S = 000000000000002403b78c79c2a4014f1ea49d7614fa4fc457cb59250f722a34
As you can see, OP_SIZE inside Script can even enforce a given target, which can be potentially used to make decentralized sidechains, because BIP-300 has no consensus, and is unlikely to be deployed in the nearest future on Bitcoin (but there may be some altcoins, trying to do it anyway).

https://github.com/bitcoin/bips/blob/master/bip-0300.mediawiki
Quote
BIP-300 enables a new type of L2, where "withdrawals" (the L2-to-L1 txns) are governed by proof-of-work -- instead of a federation or fixed set of pubkeys.
So, we are already there, because there are different approaches, which won't require BIP-300 at all. Without BIP-301, there is no Merged Mining, so you cannot hit a block, while solving such puzzles. But it seems to be good enough for second layers, to limit unwanted double-spending attempts, because each time, an attacker needs to grind a new Proof of Work solution entirely from scratch, because txid:vout is always committed, and unique, even with SIGHASH_NONE, combined with SIGHASH_ANYONECANPAY.

Edit:
Quote
you now gave me the idea for also grinding data into the wtxid, using r-values
It was already done by some people. This transaction is quite interesting: https://mempool.space/tx/b10c0000004da5a9d1d9b4ae32e09f0b3e62d21a5cce5428d4ad714fb444eb5d
Code:
+------+------------------------------------------------------------------+
| txid | b10c0000004da5a9d1d9b4ae32e09f0b3e62d21a5cce5428d4ad714fb444eb5d |
| hash | 0000000001d549a7df6bb3346ab8d390ea517bb284fc0c081647068f9b72e641 |
+------+------------------------------------------------------------------+
nullius (OP)
Copper Member
Hero Member
*****
Offline

Activity: 644
Merit: 2703


AnonChange.com


View Profile WWW
October 24, 2026, 07:49:37 PM
Merited by athanred (1)
 #3

to grind a change address that produced the desired zeroes in the txid
It is slow to do it in that way,

True.  According to my logs, for that 236 POW, I tied up an old CPU for more than two days.  However, that was a very old CPU—thus, it was not as slow as you may think.  I used this code by sipa:

You want this code:  https://github.com/bitcoin-core/secp256k1/pull/507  it will be astronomically faster than your current code.

I believe when I previously implemented the techniques in this code my result was faster than vanitygen on a GPU.

It could also be made faster still with some improvements. [...]

I’m sad to see that bitcoin-core/secp256k1#507 was closed on 2026-05-09 without merging.  Why?  I should dust off my Github login, and investigate!

you can grind nSequence and nLockTime first.

Good idea.  Thanks for the explanations.  I didn’t think of it at the time.

If I had, though, I still would have needed to grind the change address.  I used nLockTime to point to a block on the day Lauda vanished.

It is also a privacy problem for using the blockchain as a backup.  Blockchain analysis spies would love this as a fingerprint to link wallet addresses.  No transaction distinguishers are added by grinding a pseudorandom change address to store a few bytes of encrypted data in the txid.  If I actually build a wallet with this feature, I will try to make the wallet’s fingerprint match Bitcoin Core, Electrum, or another wallet with a big anonymity set.

For non-spam art where one doesn’t mind wallet addresses being openly linked (or even with address reuse), it’s an excellent idea.

From these two fields, we can use 61 bits. From these two fields, we can use 61 bits. Which is more than enough, to grind some 36-bit target, and it should be faster, than grinding ECDSA public keys (of course, they can be also rotated, but there is no need to do it that often, because it is quite slow).

Over seven years ago, I built (unpublished) a PGP keyid/fingerprint grinder that uses the key creation time field similarly.  I designed multiple modes, which could be adapted to this Bitcoin use:

  • Grind ctime decrementing backwards within the past 220 seconds.  That’s about the past 11 days.  After every run through this range, refresh the key, and also refresh the current time to move the 220-second window forward.  This gives a huge speedup, and it produces a recent, natural-looking ctime—not something from 1972 or whatever.  (However, it would still not make a Bitcoin transaction indistinguishable.)
  • Grind ctime decrementing backwards through all times from the present back to 0 (1970-01-01T00:00:00Z).  This gives maximal speedup, at the cost of probably getting a bizarrely old-looking ctime.
  • Some variations of the above strategies.

IIRC.  I haven’t looked at the code recently.  A PGP key with an obviously ground keyid should still be visible in the forum signature of an acknowledged alt of mine.

For blockchain art, where one wants to draw public attention to transactions, your idea is definitely the way to go.


Edit:
you now gave me the idea for also grinding data into the wtxid, using r-values
It was already done by some people. This transaction is quite interesting: https://mempool.space/tx/b10c0000004da5a9d1d9b4ae32e09f0b3e62d21a5cce5428d4ad714fb444eb5d
Code:
+------+------------------------------------------------------------------+
| txid | b10c0000004da5a9d1d9b4ae32e09f0b3e62d21a5cce5428d4ad714fb444eb5d |
| hash | 0000000001d549a7df6bb3346ab8d390ea517bb284fc0c081647068f9b72e641 |
+------+------------------------------------------------------------------+

Thanks.  Besides my liking for curios to examine, I do want to know proper priority when the same idea is invented multiple times independently.



An historical footnote on the puzzle:  I did not target 36 bits.  I wanted as many zeroes as I could get!  But although I’m patient, I didn’t want to delay the puzzle for a month to try to get another 0 nibble.  Then, I decided it was fitting for nine zero nibbles to represent the nine lives of a cat.

▄███████████████████████▄
█████████████████████████
██████████████▀▀▀████████
██████████▀▀██████▀██████
████▀▀███▄▄███▄████▀█▀▀██
██▌█████▀▀███████████████
██▌█████▄▄▄████▀▀█████▐██
██████████████▄▄██████▐██
██▄▄█▄████▀███▀▀██▄▄█████
██████▄██████▄▄██████████
████████▄▄▄██████████████
█████████████████████████
▀███████████████████████▀
 
 ANONCHANGE 
████
██
██
██
██
██
██
██
██
██
██
██
████
████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
IIIINSTANT CRYPTO SWAPS
  XMR     BTC     ETH     LTC     SOL     USDT     USDC     DAI  


████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
████
██
██
██
██
██
██
██
██
██
██
██
████
████
██
██
██
██
██
██
██
██
██
██
██
████
████
██
██
██
██
██
██
██
██
██
██
██
████
athanred
Full Member
***
Offline

Activity: 207
Merit: 385


View Profile
October 24, 2026, 09:14:41 PM
 #4

Quote
through all times from the present back to 0
There is a reason, why I started from 0x1dcd6500. Timestamps lower than 500,000,000 are treated as block numbers, if nLockTime is active. It would be unlucky to hit for example 0x12345678, which would lock the transaction up to the block number 305,419,896.

Of course, if nSequence is set to 0xffffffff, then anything can be used. But if any nSequence is different than that, then it is better to be careful, to not waste a lot of mining power for a transaction, locked for many decades, centuries, or worse.
nullius (OP)
Copper Member
Hero Member
*****
Offline

Activity: 644
Merit: 2703


AnonChange.com


View Profile WWW
October 24, 2026, 09:43:34 PM
 #5

through all times from the present back to 0
There is a reason, why I started from 0x1dcd6500.

Indeed, I understood this.  I was talking about grinding PGP keyids/fingerprints, for which purpose you can go all the way back to 0 (Unix epoch) in the key creation time field.  As I suggested, my method for PGP keyids could be adapted to Bitcoin with your idea.  I should have been clearer about that.

If nLockTime is not ignored, the adaptation would need to go back not earlier than 0x1dcd6500 (500,000,000 = 1985-11-05T00:53:20Z).

Of course, if nSequence is set to 0xffffffff, then anything can be used. But if any nSequence is different than that, then it is better to be careful, to not waste a lot of mining power for a transaction, locked for many decades, centuries, or worse.

When writing custom code for weird transactions, one must be careful anyway.  It’s easy to send money into a black hole, one way or another.  If one desires the greatest range for grinding, I suggest being careful to make sure that either (nSequence == 0xffffffff || nLockTime >= 0x1dcd6500).

▄███████████████████████▄
█████████████████████████
██████████████▀▀▀████████
██████████▀▀██████▀██████
████▀▀███▄▄███▄████▀█▀▀██
██▌█████▀▀███████████████
██▌█████▄▄▄████▀▀█████▐██
██████████████▄▄██████▐██
██▄▄█▄████▀███▀▀██▄▄█████
██████▄██████▄▄██████████
████████▄▄▄██████████████
█████████████████████████
▀███████████████████████▀
 
 ANONCHANGE 
████
██
██
██
██
██
██
██
██
██
██
██
████
████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
IIIINSTANT CRYPTO SWAPS
  XMR     BTC     ETH     LTC     SOL     USDT     USDC     DAI  


████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
████
██
██
██
██
██
██
██
██
██
██
██
████
████
██
██
██
██
██
██
██
██
██
██
██
████
████
██
██
██
██
██
██
██
██
██
██
██
████
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!