Manual Proof Submission (Fallback Path)
What to do if the Writz relayer is unavailable and you need to submit an SPV proof yourself.bitcoin-spv::verify_transaction (see SPV Verification) takes raw_tx as the non-witness serialization of a Bitcoin transaction - the txid is SHA256d(raw_tx), and Bitcoin’s block Merkle tree is built from non-witness txids. The Writz relayer strips SegWit witness data automatically before submitting a proof. This document explains that step so a technical user can reproduce it manually if the relayer is down.
This is a liveness concern only: if the raw transaction bytes are stripped incorrectly, verify_transaction simply fails with MerkleProofInvalid and your deposit is rejected - it can be retried with correct bytes. No funds are ever at risk from a stripping mistake.
Why stripping is needed
A SegWit transaction (P2WPKH, P2WSH, P2TR) has two serializations:- Full serialization (
wtxidform) - includes a 2-byte marker+flag (0x00 0x01) right after the version field, and a witness section (signatures/scripts) after the outputs. - Non-witness serialization (
txidform) - the marker+flag and witness section are removed entirely.
txids, not wtxids. If you pass bitcoin-spv the full serialization, it computes SHA256d of the wrong bytes, gets the wrong txid, and the Merkle proof will not reconstruct the block’s Merkle root.
Legacy (pre-SegWit) transactions have no witness data - the two serializations are identical, and no stripping is needed.
Algorithm
This mirrorsrelayer/src/bitcoin/tx.ts’s stripWitness exactly:
- Check for a SegWit marker: is
rawTx[4] == 0x00andrawTx[5] == 0x01? If not, the transaction is legacy - use it unchanged. - Copy the 4-byte version field.
- Skip the 2-byte marker+flag.
- Copy the input count (varint) and every input unchanged (each input is
prevout (36 bytes) + scriptSig (varint-prefixed) + sequence (4 bytes)- none of this is witness data). - Copy the output count (varint) and every output unchanged (
value (8 bytes) + scriptPubKey (varint-prefixed)). - Skip the witness section: for each input (in order), read one varint (stack item count), then for each stack item read a varint (item length) and skip that many bytes.
- Copy the final 4-byte locktime.
raw_tx to verify_transaction.
Reference implementation
CompactSize encoding (single byte if < 0xfd; 0xfd + 2-byte LE if it fits in u16; 0xfe + 4-byte LE for u32; 0xff + 8-byte LE for larger).
Verifying you stripped correctly
ComputeSHA256d(nonWitnessHex) and compare it against the txid your Bitcoin node reports for the transaction (e.g. bitcoin-cli getrawtransaction <txid> 1, or a block explorer’s txid field - not its “wtxid” field, if shown separately). They must match exactly.