I was following https://bitcointalk.org/index.php?topic=453086.0 for creating a message in OP_RETURN. I am able to decode my raw transaction, but on signrawtransaction it gives

error: {"code":-22,"message":"TX decode failed"}

I validated the address, is there something that is missing in that tutorial.

  • Best way would be to post the resulting tx. Is the data you're embedding less than 40 bytes? – Matthieu May 9 '14 at 18:09
  • Thanks Matthieu, you were right about the size. But I still had to do more steps. So was looking for a complete Step by Step. – wizgot May 10 '14 at 4:31
  • I got this message when I'd forgotten to replace the original script length with the new one for my OP_RETURN hex string. – Echelon May 22 '15 at 11:28

I wrote a little demo program which puts a snippet of data into an OP_RETURN script. It requires a bitcoin instance that accepts RPC connections, though it could be implemented without that. You can find it on github here. It's been tested, but only on testnet. I'm going to go through the code and explain what it's doing.



This makes logging more verbose. It's handy because it shows what RPC calls are being made.

Connect to bitcoind

rpc_user = "bitcoinrpc"
rpc_password = "87Y9A2gs25E9HDPGc9axqSqzxMR2MyTtrMkYc5KiZk2Z"

rpc = AuthServiceProxy("http://%s:%s@" % (rpc_user, rpc_password))

Note that your password will be different, and that you use port 8332 for mainnet instead of port 18332.

List unspent outputs

first_unspent = rpc.listunspent()[0]
txid = first_unspent['txid']
vout = first_unspent['vout']
input_amount = first_unspent['amount']
SATOSHI = Decimal("0.00000001")
change_amount = input_amount - Decimal("0.005") - SATOSHI

The - Decimal("0.005") part is so that we pay a transaction fee.

Create transaction

# Marker address we're going to replace
# Produces a pattern that's easy to search for
mainnet = 0
if mainnet:
    dummy_address = "1111111111111111111114oLvT2"
    dummy_address = "mfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8"

These are two different encodings of a Pay to Public Key Hash of all zeros. The top one is the mainnet representation, and the bottom is the testnet representation.

# My change address
change_address = "mhZuYnuMCZLjZKeDMnY48xsR5qkjq7bAr9"

Remember, this is my change address. If you don't change it, you'll be sending money to me.

tx = rpc.createrawtransaction([{"txid": txid, "vout": vout}], \
                              {change_address: change_amount, \
                               dummy_address: SATOSHI})

And now we have an actual transaction. It doesn't contain any of our own data though, so we'll have to fix that.

Replace dummy output with our own output

# Pattern to replace
# Represents length of script, then OP_DUP OP_HASH160,
# then length of hash, then 20 bytes of zeros, OP_EQUALVERIFY OP_CHECKSIG
oldScriptPubKey = "1976a914000000000000000000000000000000000000000088ac"

This is a bit of a hack. Instead of creating our own output, we create a dummy output, then search for the pattern it makes and replace it. There's probably a better way to do this, but this seems easiest.

# Data to insert
data = "Melons."
if len(data) > 75:
    raise Exception("Can't contain this much data-use OP_PUSHDATA1")

newScriptPubKey = "6a" + hexlify(chr(len(data))) + hexlify(data)

Next we create the data we want to put into the blockchain. I'm using the string Melons., but you could use anything. (Above 40 bytes is nonstandard, though.) I covered most of this in my other answer.

This code will break if the data is longer than 75 bytes. If you need it for more than that, you could use OP_PUSHDATA1 instead of the single-byte pushdata that I'm using here.

#Append int of length to start
newScriptPubKey = hexlify(chr(len(unhexlify(newScriptPubKey)))) + newScriptPubKey

This part is a little different than my other answer, because we need to include the length of the scriptPubKey also. This code will break with data longer than 251 bytes. If you want it to work on data longer than that, encode a varint correctly.

if oldScriptPubKey not in tx:
    raise Exception("Something broke!")

Error checking for this very rickety method.

tx = tx.replace(oldScriptPubKey, newScriptPubKey)

Finally, a string replace swaps the new script for the old one.

Sign it

tx = rpc.signrawtransaction(tx)['hex']

Bitcoin handles the heaving lifting here.

Broadcast it to the network.


Done! Now just wait for your transaction to get into a block.

I ran the code (output), and I produced a transaction, which you can see in Block Explorer here. If you copy the string next to OP_RETURN, and paste it into a Hex to ASCII converter, you get...



Other resources

I found this webpage helpful while writing this.

  • Nice, as it's an OP_RETURN scriptsig (If I understands correctly), does that mean the tx will be removed / invalidated by the network? – Valmond Mar 13 '15 at 13:28
  • 1
    @Valmond scriptsig scriptPubKey, actually. does that mean the tx will be removed / invalidated by the network? Not... really. It will be removed from the unspent transaction output set in some clients, but many clients will still remember it. (They need to, because they need to prove to new clients that it's just an OP_RETURN transaction.) – Nick ODell Mar 13 '15 at 14:24
  • 1
    Note, the transaction you produced would not be a standard transaction on mainnet because the op_return output has a value > 0. It gets relayed/mined-by-default on testnet because testnet accepts almost all transactions as standard. – David A. Harding Mar 17 '15 at 22:31
  • 1
    @DavidA.Harding I did that because createrawtransaction wouldn't accept a value of 0. Can you provide a source to that claim? I can't find any check like that in Solver or IsStandard. – Nick ODell Mar 18 '15 at 1:17
  • @NickODell oh, bugger---you seem to be correct; I've been giving people bad advice for a year now. Sorry. – David A. Harding Mar 18 '15 at 12:17

You can make life easy by using one of our OP_RETURN libraries:

The code will also show you exactly how it's done.



Academic paper describing the different methods of data insertion for Bitcoin's blockchain.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.