Skip to content

Instantly share code, notes, and snippets.

@AdamISZ
Last active April 3, 2023 20:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AdamISZ/ba7794c801b339265fc6efeeed1684be to your computer and use it in GitHub Desktop.
Save AdamISZ/ba7794c801b339265fc6efeeed1684be to your computer and use it in GitHub Desktop.
Segwit support in Joinmarket

Current work is in this branch.

Reference information at: BIP141, BIP143 (note BIP142 is not in force)

Implementation uses P2SH-P2WPKH - the P2SH is for backwards-compatibility (old wallets can send to P2SH addresses), and P2WPKH not P2WSH for simplicity (no multisig involved).

Modifications made so far:

New ordertypes:

swrelorder, swabsorder can be announced by Maker bots.

Rationale:

See modifications to Joinmarket protocol below. Because of these necessary changes, non-updated bots will not understand some of the segwit enabled protocol messages.

Messaging protocol changes:

!sig message changes from base64(sig+pub) to base64(sig+pub+witnessScript) (see BIP141). The set of 3 items is serialized as a complete script (using bitcoin.serialize_script)

This is the only change; the presence of the third item in the list acts as a flag to trigger segwit-style verification by the Taker (see BIP143 for how transaction signing/verification is different).

Changes to bitcoin module

The most important change is explained in detail in BIP143: a new style of transaction signing involves constructing a transaction hash of a completely different type. This is implemented in bitcoin.segwit_signature_form (currently here. The important change for the interface is that the calling signer must provide the amount in satoshis to the signing process. This is wrapped with an optional amount argument to bitcoin.sign, allowing each txin to be signed with the appropriate transaction hash. Note that the functions bitcoin.txhash and bitcoin.ecdsa_tx_sign, bitcoin.ecdsa_tx_verify do not have to change (the first of those three only takes the hash, appends the hashcode and little-endian-ises it; this has not changed).

The transaction deserialization has to change to account for the existence of an additional dict key txinwitness for each transaction input. The name was chosen to agree with that found in Bitcoin rpc's own deserialization (see e.g. the output of decoderawtransaction). For inputs not derived from a segregated witness output (or not yet signed), this field is empty. The script key does not become empty, but is now replaced with that required for P2SH-P2WPKH, it has form 160014<hash160>, see BIP141 for details.

An important note on transaction serialization: the detailed serialization is explained in the BIPs, but note that whenever none of the inputs are from segregated witness scripts, then we revert to the original serialization scheme, i.e. not including the marker and flag and not appending witness data at the end. This check is made here.

Wallet changes:

Class SegwitWallet derives from class Wallet.

Currently: the use of the flag -s 1 to the wallet-tool, yield-generator and sendpayment bots triggers the creation of a SegwitWallet, which generates the BIP32 tree but with P2SH-P2WPKH addresses instead of plain P2PKH addresses.

An overlay of Wallet.sign replaces btc.sign to allow the Wallet instance to decide what kind of signing is needed (segwit/not). Similarly, address generation from seed and private keys is deferred to the type of Wallet instance.

Probably will change: instead of using a flag -s and creating a separate address tree from the same keys, create a separate set of branches (max_mix_depth extra branches) for which segwit-style addresses are generated. Then a bot can choose to use either type of address/transaction from the same Wallet instance (or possibly mixed).

Fee estimate changes

An extra transaction type is added to bitcoin.estimate_tx_size based on the assumption that all inputs are p2sh-p2wpkh: it calculates this estimate:

elif txtype == 'p2sh-p2wpkh':
        #return the estimate for the witness and non-witness
        #portions of the transaction, assuming that all the inputs
        #are of segwit type p2sh-p2wpkh
        #witness are roughly 3+~73+33 for each input
        #non-witness input fields are roughly 32+4+4+20+4=64, so total becomes
        #n_in * 64 + 4(ver) + 4(locktime) + n_out*34 + n_in * 109
        witness_estimate = ins*109
        non_witness_estimate = 4 + 4 + outs*34 + ins*64
        return (witness_estimate, non_witness_estimate)

Some of these numeric values might not be perfect; initial tests seem to agree reasonably well with real transactions.

Note that it returns two different numbers, the estimate for how much data is stored in the witness portion, and how much is consumed by the non-witness portion (which means, everything else, including script fields - which consume about 4 bytes ("17160014" in hex), plus 20 bytes for the hash160, for this transaction type. These two estimates are then fed into the fee estimation algorithm in wallet.estimate_tx_fee, which, as for ordinary p2pkh, calls bitcoin rcp estimatefee N and then applies the heuristic: fee estimated = (non-witness + 0.25*witness) * fee per byte.

@mecampbellsoup
Copy link

Is there a checklist or something that you guys are using to track 'SegWit implementation for JoinMarket'?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment