Skip to content

Latest commit

 

History

History
100 lines (73 loc) · 4.72 KB

serialization.md

File metadata and controls

100 lines (73 loc) · 4.72 KB

Serialization

The Bitcoin RPC API returns and expects transactions in some hex format. For example, if I use createrawtransaction to create some transaction with no inputs and one output::

./bin/bitcoin-cli -testnet -rpcport=18332 -rpcuser=root -rpcpassword=hellohello createrawtransaction "[]" "{\"tb1q6nkpv2j9lxrm6h3w4skrny3thswgdcca8cx9k6\":0.01}"

I get the following hex string as response:

02000000000140420f0000000000160014d4ec162a45f987bd5e2eac2c39922bbc1c86e31d00000000

The implementation of the encoding is here in bitcoind, which sets without_witness=false, to use the latest segwite update which, IIUC, moves some witness data (needed to validate the transaction) outside of the transaction.

It is also here in rust-bitcoin.

It seems like things get encoded in this order without segwit: version|inputs|outputs|lock_time. And it seems like things get encoded in this order with segwite: version|segwit_marker|segwit_flag|inputs|outputs|witnesses|lock_time.

This article has a similar explanation on the serialization.

Also this has a nice visualization: https://bc-2.jp/tools/txeditor2.html

If I pass it to decoderawtransaction or an online decoder like https://btc.com/tools/tx/decode I get:

{
    "txid": "078883f990a287d6f64576f17b869fe664204485909c929404d95a88caff965d",
    "hash": "078883f990a287d6f64576f17b869fe664204485909c929404d95a88caff965d",
    "version": 2,
    "size": 41,
    "vsize": 41,
    "weight": 164,
    "locktime": 0,
    "vin": [],
    "vout": [
        {
            "value": 0.01,
            "n": 0,
            "scriptPubKey": {
                "asm": "0 d4ec162a45f987bd5e2eac2c39922bbc1c86e31d",
                "desc": "addr(bc1q6nkpv2j9lxrm6h3w4skrny3thswgdccad7akdf)#fhmjzx3q",
                "hex": "0014d4ec162a45f987bd5e2eac2c39922bbc1c86e31d",
                "address": "bc1q6nkpv2j9lxrm6h3w4skrny3thswgdccad7akdf",
                "type": "witness_v0_keyhash"
            }
        }
    ]
}

There be dragons

Interestingly, the segwit serialization (done with Bitcoin rust) doesn't seem to work with what bitcoind expects.

The transaction serialized in hex without segwit:

020000000001e8030000000000004076a914000000000000000000000000000000000000000088ac6a200000000000000000000000000000000000000000000000000000000000000000040000000000000000

The default, serialized with segwit:

0200000000010001e8030000000000004076a914000000000000000000000000000000000000000088ac6a200000000000000000000000000000000000000000000000000000000000000000040000000000000000

It seems like the only difference is 2 bytes 0x0001 which represent the segwit_marker (0x00) and the segwit_flag (0x01).

If we sent the segwit serialized one, we get the following error:

{"result":null,"error":{"code":-22,"message":"TX decode failed"},"id":"whatevs"}

decoderawtransaction has the following comment:

iswitness: depends on heuristic tests, Whether the transaction hex is a serialized witness transaction.

If iswitness is not present, heuristic tests will be used in decoding. If true, only witness deserialization will be tried. If false, only non-witness deserialization will be tried. This boolean should reflect whether the transaction has inputs (e.g. fully valid, or on-chain transactions), if known by the caller."

The function logic calls DecodeHexTx which calls DecodeTx which has the following comment:

// General strategy:
// - Decode both with extended serialization (which interprets the 0x0001 tag as a marker for
//   the presence of witnesses) and with legacy serialization (which interprets the tag as a
//   0-input 1-output incomplete transaction).
//   - Restricted by try_no_witness (which disables legacy if false) and try_witness (which
//     disables extended if false).
//   - Ignore serializations that do not fully consume the hex string.
// - If neither succeeds, fail.

so if we're not passing anything it should detect the special serialization... so I'm not sure why it's failing.

Turns out I believe there's a bug in the lib we're using rust-bitcoin/rust-bitcoin#2239

Note: one recommendation seems to be using PSBT instead of raw transactions.