Skip to content

Add tools/liquid.py, to spend a verifier on Liquid, Liquid testnet or regtest - #27

Merged
mjthatch merged 1 commit into
research/docs-reviewfrom
research/liquid-networks
Oct 27, 2026
Merged

mjthatch merged 1 commit into
research/docs-reviewfrom
research/liquid-networks

Conversation

@mjthatch

Copy link
Copy Markdown
Collaborator

Until now a verifier's output could be spent only by hand with deploy, or by the toy-circuit script tools/regtest.sh on a regtest node. tools/liquid.py is a developer tool for spending any verifier deploy compile produced, on Liquid, Liquid testnet or a regtest node. It reads the chain through an Esplora server (Blockstream's by default) or your own node (--node "elements-cli ..."), builds every transaction with deploy, and needs only the Python standard library.

Commands

command what it does
address PROGRAM the verifier's address, to fund with an explicit output
faucet ADDRESS Liquid testnet coins from liquidtestnet.com
utxos PROGRAM the outputs that pay the verifier
sighash PROGRAM TXID:VOUT DEST (--fee | --vsize) the sighash the proof must bind
spend PROGRAM WITNESS TXID:VOUT DEST (--fee | --vsize) the transaction, from the witness file
prove PROGRAM TXID:VOUT DEST --prove CMD --witness FILE runs the prover (CMD with {sighash}), builds, and proves again if the fee must rise
keyspend PROGRAM TXID:VOUT DEST --secret-file F the key path, when the output has one
broadcast TX.hex sends a transaction written earlier

Every command takes --network liquid|liquidtestnet|elementsregtest. --broadcast sends; without it, nothing is sent.

How the fee works

The proof binds the spending transaction, fee included, so the fee has to be known before proving. There are two ways to give it:

  • Two steps, for any prover. sighash prints the sighash for a fee; the prover runs; spend builds the transaction from the witness file.
  • prove. It runs the prover itself, measures the transaction, and proves again if the size needs a higher fee.

The fee is --fee in satoshis, or --fee-rate (default 0.1 sat/vB, Liquid's minimum relay feerate) times the size plus 10 vB of slack. The size is --vsize from an earlier spend, or the size prove measures.

What it refuses

  • an output that is spent, does not pay the verifier, is confidential, or holds an asset other than L-BTC (the fee is paid from it);
  • any transaction whose fee is below its minimum relay fee;
  • a prover that fails, has no {sighash}, or writes no witness file;
  • with --node: a node on another chain than --network, and a transaction that testmempoolaccept refuses (the tool runs it first).

Before it sends anything, deploy executes the transaction and refuses it if the cost exceeds the budget, if it needs more Bit Machine cells than consensus allows, or if it is above the standard weight.

Verification

  • tests/test_liquid.py (added to make test and the CI matrix) fakes the chain and deploy. It covers:

    • Esplora's and elements-cli's outputs, with amounts in satoshis exactly, and transactions passed to elements-cli on stdin;
    • every refusal above;
    • the prove loop: it proves again only while the fee must rise, including when each proof is a few WU larger;
    • the prover's output staying off stdout, which carries only the transaction or the txid;
    • argument validation.

    It passes on Python 3.10, 3.13 and 3.14.

  • Elements 23.3.1 regtest, through --node:

    • prove --broadcast for the Noir verifier (67.8 kWU, whose hex is too long for a command-line argument) and for Groth16;
    • the two-step sighash/spend path;
    • broadcast and keyspend.

    All were accepted and mined.

  • Liquid testnet. faucet funded each toy verifier's address, and prove --broadcast spent it, paying the faucet's return address:

verifier proof spend
Groth16, circom e2ef29c6…, 53,314 WU, fee 1,334 sat
Groth16, Noir 6224147f…, 67,812 WU, fee 1,697 sat
fflonk, SHA-256 dc5b3334…, 126,577 WU, fee 3,166 sat
fflonk, Keccak 5a64e6be…, 185,285 WU, fee 4,634 sat
  • Liquid. The tool was not used to broadcast there.
    • address and utxos were run against Blockstream's Liquid API.
    • deploy's built-in genesis hashes match both chains' block 0.
    • Elements' policy has no stack-item size limit for Simplicity leaves; the 80-byte limit applies to tapscript only.
    • libsimplicity differs between Elements 23.3.1 (tested) and 23.3.4 (latest) only by renamed lock jets, which the verifiers don't use.
  • Independent review. A separate review of the tool found ten issues, all fixed here:
    • fees below the minimum relay fee slipping through prove --fee and keyspend;
    • a needless re-proof caused by counting the slack twice;
    • transaction hex passed as a command-line argument;
    • the prover's output mixed into stdout;
    • assets other than L-BTC accepted;
    • unvalidated numbers;
    • mempool-spent outputs crashing utxos;
    • conflicting flags not refused;
    • deploy's notes hidden;
    • stale witness files going unnoticed.

Docs

  • README. A section "On Liquid testnet and Liquid":

    • the commands, how the fee works, and funding with an explicit output of L-BTC;
    • a warning that anyone can race a funded toy verifier, because its statement is public.

    The support table gets a "spent on" row.

  • docs/benchmarks.md. The testnet spends above, beside the regtest ones.

  • docs/fflonk.md and docs/noir.md. Pointers to the tool.

🤖 Generated with Claude Code

… regtest

A developer tool around deploy: address, faucet (Liquid testnet), utxos,
sighash, spend, prove, keyspend and broadcast. It reads the chain through
an Esplora server (Blockstream's by default) or a node (--node
"elements-cli ..."), and needs only the Python standard library and the
harness.

The proof binds the fee, so the fee is fixed before proving: in two
steps (sighash, then spend with the witness) for any prover, or with
`prove`, which runs the prover itself and proves again if the measured
size needs a higher fee. The output must pay the verifier, be explicit
and hold L-BTC; every fee must meet the minimum relay fee; nothing is
sent without --broadcast, and with a node testmempoolaccept checks the
transaction first.

tests/test_liquid.py checks it with the chain and deploy faked (in make
test and CI). It was also run on an Elements regtest node through
--node, and each toy verifier was spent on Liquid testnet with `prove`
(docs/benchmarks.md links the transactions). The README gets a section
on both networks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mjthatch
mjthatch merged commit 819fadb into research/docs-review Oct 27, 2026
20 checks passed
@mjthatch
mjthatch deleted the research/liquid-networks branch October 27, 2026 09:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants