pyrxd.btc_wallet — BTC-side wallet¶
Bitcoin wallet tooling for the Gravity Taker.
Public API¶
BtcKeypair — keypair with all 4 address formats BtcUtxo — UTXO descriptor BtcPaymentTx — signed transaction result generate_keypair — generate a fresh keypair from CSPRNG keypair_from_wif — load keypair from WIF (testing/recovery) build_payment_tx — build+sign a 1-input segwit-v0 payment tx validate_btc_address — validate a mainnet Bitcoin address string validate_satoshis — validate a satoshi amount
- class pyrxd.btc_wallet.BitcoinCoreBroadcaster[source]¶
Bases:
objectBtcBroadcasterbacked by a Bitcoin CoresendrawtransactionRPC.Intended for the regtest milestone (a local node). Reuses the injected
rpc(method, params)coroutine so it shares transport/auth with aBitcoinCoreRpcSourcerather than opening a second session. Idempotent: an “already known” node response is mapped to the tx’s own txid as success.
- class pyrxd.btc_wallet.BitcoinTaprootLeg[source]¶
Bases:
objectThe concrete BTC HTLC leg (the production
btc_leg).- Parameters:
network – BTC network prefix (“bcrt” regtest, “tb” testnet/signet, “bc” mainnet).
funding_utxo (taker_keypair /) – The taker’s wallet key + the single UTXO that funds the HTLC (one input is the covenant structural constraint of
build_payment_tx).funding_utxomust holdbtc_sats + fee_sats(plus dust slack for change).broadcaster – A
BtcBroadcaster(idempotent).funding_reader – A
BtcFundingReader— reads the funded amount from the chain.claim_to_scriptpubkey (refund_to_scriptpubkey /) – Where the refund (taker) and claim (maker) spends pay out.
fee_sats – Flat fee for the funding/claim/refund txs (regtest milestone; a fee estimator is a later refinement).
min_confirmations – Confirmations required before the on-chain funded amount is trusted.
audit_cleared – Explicit opt-in for a value-bearing
network(seerequire_audit_cleared()). Ignored for isolated test chains.
- __init__(*, network, taker_keypair, funding_utxo, maker_claim_pubkey_xonly, broadcaster, funding_reader, refund_to_scriptpubkey, claim_to_scriptpubkey, policy=None, maker_claim_privkey=None, audit_cleared=False, fee_sats=<object object>, min_confirmations=<object object>, funding_input_type=<object object>, fund_confirm_poll_s=<object object>, fund_confirm_timeout_s=<object object>)[source]¶
- Parameters:
network (str)
taker_keypair (BtcKeypair)
funding_utxo (BtcUtxo)
maker_claim_pubkey_xonly (bytes)
broadcaster (BtcBroadcaster)
funding_reader (BtcFundingReader)
refund_to_scriptpubkey (bytes)
claim_to_scriptpubkey (bytes)
policy (FundingPolicy | None)
maker_claim_privkey (bytes | None)
audit_cleared (bool)
- Return type:
None
- async claim(locator, preimage)[source]¶
Build + idempotently broadcast the maker’s claim tx (reveals
p).Only a MAKER-role leg (constructed with
maker_claim_privkey) can do this — the claim spend uses the maker’s claim-leaf key. A taker-role leg without that key fail-closes.build_claim_txre-verifiessha256(p)opens the leaf hashlock before signing.- Parameters:
locator (BtcHtlcLocator)
preimage (bytes)
- Return type:
- async confirmations_of_claim(claim_tx_bytes)[source]¶
Confirmation depth of the maker’s BTC claim tx (the reorg gate’s input).
The txid is resolved VIA THE NODE from the exact
claim_tx_bytespwas scraped from (never a local segwit parse) — so an attacker can’t revealpin a shallow tx while pointing the gate at a deep unrelated tx. Fail-closed: any read/derivation error propagates (the coordinator then refuses to claim).
- derive_funding_scriptpubkey(terms)[source]¶
The funding SPK the taker independently re-derives from the terms.
- Return type:
- async fund(terms)[source]¶
Fund the HTLC P2TR address from the taker’s UTXO; return the locator.
Build → idempotent-broadcast → read the funded amount back from the chain (D4: the amount is the ON-CHAIN value, never a self-report). The funding tx pays output 0 to the HTLC address; change (if any) returns to the taker.
- Return type:
- locked_amount(locator)[source]¶
The funded amount the coordinator binds to
terms.value_amount— sats for BTC (the chain-neutral seam; an ETH leg returns wei).- Return type:
- promised_funding_scriptpubkey(terms)[source]¶
The funding SPK the maker promised.
For the HTLC there is no separate maker-side derivation — the SPK is a pure function of the negotiated terms, so the promised SPK equals the re-derived one. (The pre-lock gate’s equality check still runs; a divergence here would signal a terms/derivation bug.)
- Return type:
- async refund(locator, timeout)[source]¶
Build + idempotently broadcast the taker’s CSV refund tx. Returns the txid.
The refund leaf spends via the taker’s refund key (held by this leg) after the relative timelock matures. Idempotent broadcast tolerates a retry.
- Parameters:
locator (BtcHtlcLocator)
timeout (Timelock)
- Return type:
- class pyrxd.btc_wallet.BtcBroadcaster[source]¶
Bases:
ProtocolSubmit a raw BTC tx to the network. Composed into the leg (not on the ABC).
broadcastMUST be idempotent: if the node already knows the tx, return its txid as success rather than raising — a crash-recovery retry re-broadcasts the same tx and must not be treated as a failure.- __init__(*args, **kwargs)¶
- class pyrxd.btc_wallet.BtcFundingReader[source]¶
Bases:
ProtocolRead BTC chain state the HTLC leg needs: funding amount, confirmation depth, and the canonical txid of a raw tx.
Duck-typed over a
BtcDataSource-like object.read_output_amount_satsreturns the value of(txid, vout)as committed on-chain (NOT a self-report), enforcingmin_confirmations(raise/fail-closed if shallower).confirmationsis the symmetric confirmation-depth reader (mirrorsRadiantChainIO.confirmations) the reorg gate consumes.txid_ofresolves a raw tx’s canonical txid VIA THE NODE — never a local segwit parse (see the reorg gate plan; the gated txid must be that of the exact bytespwas scraped from).- __init__(*args, **kwargs)¶
- async confirmations(txid)[source]¶
Return the confirmation depth of
txid(0 if unconfirmed/unknown).
- class pyrxd.btc_wallet.BtcHtlc[source]¶
Bases:
objectThe HTLC funding artifact, before a UTXO funds it.
Carries the script tree, control blocks for each leaf, the NUMS internal key, and the derived funding address/scriptPubKey.
with_fundingproduces aBtcHtlcLocatoronce the funding outpoint + amount are known.- __init__(script_tree, internal_key, control_block_claim, control_block_refund, network)¶
- Parameters:
script_tree (ScriptTree)
internal_key (bytes)
control_block_claim (bytes)
control_block_refund (bytes)
network (str)
- Return type:
None
- with_funding(outpoint, amount_sats)[source]¶
- Parameters:
outpoint (BtcOutpoint)
amount_sats (int)
- Return type:
- script_tree: ScriptTree¶
- class pyrxd.btc_wallet.BtcHtlcLocator[source]¶
Bases:
objectThe FULL durable retained state for a funded BTC HTLC.
This is NOT opaque — it is everything required to later claim or refund the output. Persisting a reduced form (e.g. only the privkey) strands the BTC, because the script-path spend needs the whole Tapscript tree + control block.
- __init__(funding_outpoint, script_tree, control_block_claim, control_block_refund, internal_key, amount_sats, network='bc')¶
- Parameters:
funding_outpoint (BtcOutpoint)
script_tree (ScriptTree)
control_block_claim (bytes)
control_block_refund (bytes)
internal_key (bytes)
amount_sats (int)
network (str)
- Return type:
None
- funding_outpoint: BtcOutpoint¶
- script_tree: ScriptTree¶
- class pyrxd.btc_wallet.BtcKeypair[source]¶
Bases:
objectA Bitcoin keypair with addresses in all 4 Gravity-supported formats.
Private key is stored as PrivateKeyMaterial (never logs/repr leaks).
- network¶
bech32 HRP (
"bc"mainnet,"tb"testnet/signet,"bcrt"regtest, or any custom HRP). Defaults to"bc".- Type:
- __init__(_privkey, pubkey_bytes, p2pkh_address, p2wpkh_address, p2sh_p2wpkh_address, p2tr_address, pkh, p2sh_hash, p2tr_output_key, network='bc')¶
- class pyrxd.btc_wallet.BtcOutpoint[source]¶
Bases:
objectA funding outpoint (txid big-endian hex as shown by explorers, + vout).
- class pyrxd.btc_wallet.BtcPaymentTx[source]¶
Bases:
objectResult of build_payment_tx.
- __init__(tx_hex, txid, fee_sats, change_sats, input_type, output_type)¶
- class pyrxd.btc_wallet.ScriptTree[source]¶
Bases:
objectA 2-leaf Tapscript tree (claim leaf + refund leaf).
Holds the leaf scripts and their (cached) leaf hashes + merkle root, so the durable swap state never has to re-derive (and risk mis-deriving) the tree.
- __init__(claim_script, refund_script, leaf_version=192)¶
- class pyrxd.btc_wallet.TimeUnit[source]¶
Bases:
EnumThe unit a
Timelockis measured in.The whole cross-chain safety invariant (
t_BTC - t_RXD >= margin) rides on comparing like units; mixing blocks and seconds without conversion is a fail-closed error, not a silent coercion.- BLOCKS = 'blocks'¶
- SECONDS = 'seconds'¶
- class pyrxd.btc_wallet.Timelock[source]¶
Bases:
objectA unit-tagged relative timelock (BIP68/112 CSV).
- csv_script_operand()[source]¶
Return the integer that the CSV leaf pushes (matches nSequence encoding).
The value compared by OP_CSV is the nSequence value masked to its relative-locktime bits, so the script operand equals
to_nsequence()for the same lock.- Return type:
- normalize_to(unit, *, block_interval_s)[source]¶
Return an equivalent
Timelockinunit.block_interval_sis the assumed seconds-per-block used for conversion (caller supplies a measured value for mainnet; estimates are test-only). Conversion is floor-based; the margin check must account for the rounding.
- pyrxd.btc_wallet.build_claim_tx(*, locator, preimage, claim_privkey, to_scriptpubkey, fee_sats, aux_rand)[source]¶
Build the maker’s claim tx (spends the claim leaf, reveals
p).Witness:
<sig> <preimage> <claim_script> <control_block>.
- pyrxd.btc_wallet.build_htlc(*, hashlock, claim_pubkey_xonly, refund_pubkey_xonly, timeout, internal_key_xonly=b'P\x92\x9bt\xc1\xa0IT\xb7\x8bK`5\xe9z^\x07\x8aZ\x0f(\xec\x96\xd5G\xbf\xee\x9a\xce\x80:\xc0', network='bc')[source]¶
Construct the BTC Taproot HTLC (funding address + control blocks).
The default internal key is the provable NUMS point — every spend is script-path, so a colluding maker cannot key-path-spend without revealing
p.
- pyrxd.btc_wallet.build_payment_tx(keypair, utxo, to_hash, to_type, amount_sats, fee_sats, input_type='p2wpkh', change_address=None)[source]¶
Build and sign a 1-input Bitcoin payment transaction for the Gravity Taker.
Exactly 1 input is required — this is a covenant structural constraint. input_type controls whether the input is native segwit (empty scriptSig) or wrapped segwit (23-byte scriptSig with P2WPKH redeem push).
- Parameters:
- Return type:
- pyrxd.btc_wallet.build_refund_tx(*, locator, refund_privkey, timeout, to_scriptpubkey, fee_sats, aux_rand)[source]¶
Build the taker’s pre-signed refund tx (spends the refund leaf via CSV).
v2 tx with nSequence encoding the relative timelock per BIP68; witness is
<sig> <refund_script> <control_block>(the refund leaf has no preimage).
- pyrxd.btc_wallet.generate_keypair(network='bc')[source]¶
Generate a fresh Bitcoin keypair using CSPRNG.
Uses secure_scalar_mod_n() for the private key — explicit range check, rejection sampling, never Math.random(). Matches JS btc_wallet.js::generateKeypair() audit-hardened version.
- Parameters:
network (str) – bech32 HRP for address serialization.
"bc"(default) for mainnet,"tb"for testnet/signet,"bcrt"for regtest, or any custom HRP.- Return type:
- pyrxd.btc_wallet.keypair_from_wif(wif, network='bc')[source]¶
Load keypair from WIF string (for testing/recovery).
- Parameters:
- Return type:
- pyrxd.btc_wallet.require_audit_cleared(network, *, audit_cleared)[source]¶
Retained for backward-compatibility; no longer blocks.
The cross-chain swap stack is unaudited — callers handling real value should verify it themselves. This matches the ecosystem norm (Radiant Core itself ships unaudited and does not hard-block mainnet use): the in-code audit gate is no longer a blocking control as of 0.9.0. The signature and
audit_clearedparameter are kept so existing callers continue to work.
- pyrxd.btc_wallet.scrape_secret(claim_tx_bytes, hashlock)[source]¶
Extract the preimage
pfrom a claim tx by matchingsha256(p)==H.Matches over EVERY witness push of EVERY input — never by positional offset (the C-PARSER lesson). Returns the 32-byte preimage. Raises
ValidationErrorif no witness push hashes toH(e.g. this is a refund tx, or the wrong tx).The hashlock disambiguates which swap this tx belongs to; the caller should pair it with the funding outpoint when multiple swaps share an
H.
- pyrxd.btc_wallet.validate_btc_address(address)[source]¶
Validate a mainnet Bitcoin address.
Rejects path traversal, query injection, and anything outside the two recognized mainnet address shapes (Base58Check P2PKH/P2SH and bech32/bech32m).
- Raises:
ValidationError – if the address is not a recognized mainnet format.
- Parameters:
address (str)
- Return type:
None
- pyrxd.btc_wallet.validate_satoshis(value, name='value')[source]¶
Validate a satoshi amount.
- Rules:
Must be a plain int (not bool, not float).
Must be > 0.
Must not exceed max BTC supply (21M BTC = 2.1e15 sats).
- Raises:
ValidationError – on any violation.
- Parameters:
- Return type:
None