pyrxd.agent — sign-on-behalf signing daemon¶
The optional pyrxd agent daemon holds an unlocked wallet for a bounded window and signs
transactions on request — with a per-spend confirmation on its own controlling terminal and
prevout-authenticity checks — so the key is removed from the short-lived CLI process and a
same-uid caller can only request a signature, never take the key.
pyrxd.agent — local sign-on-behalf signing agent (issue #8, Path A’).
A daemon holds the unlocked wallet and signs transactions the CLI builds watch-only; the key never leaves the agent. Reaching the agent lets a caller request a signature (gated by per-spend confirmation), never take the key.
This package is the in-process signing brain (signer)
plus its wire types (protocol). The Unix-socket
daemon and the CLI client are layered on top (later phases). It is NOT
the generalized Signer seam (that is the deferred Path B); the agent
simply wraps HdWallet.
Security model: see docs/plans/2026-06-08-feat-cli-signing-agent-a-prime-plan.md § “Load-bearing safety properties”. The signer independently verifies each input’s prevout (never trusts caller-claimed values), attributes outputs (change re-derived and verified, the rest shown as external), and requires confirmation before signing.
- class pyrxd.agent.AgentClient[source]¶
Bases:
objectTalks to a running
AgentDaemon.- is_live()[source]¶
True iff an agent answers a status query on the socket (never raises).
- Return type:
- lock()[source]¶
Ask the agent to lock (zeroize + shut down). No-op if already down.
- Return type:
None
- sign(request)[source]¶
Send a signing request; return the signed tx or raise the typed error.
- Parameters:
request (SigningRequest)
- Return type:
- class pyrxd.agent.AgentDaemon[source]¶
Bases:
objectHolds an unlocked wallet and signs requests arriving on a Unix socket.
- class pyrxd.agent.AgentSigner[source]¶
Bases:
objectSigns transactions on behalf of an unlocked wallet, without ever exposing the wallet’s keys.
- sign(request, *, confirm)[source]¶
- Parameters:
request (SigningRequest)
confirm (Callable[[SpendSummary], bool])
- Return type:
- class pyrxd.agent.ChangeClaim[source]¶
Bases:
objectA claim that output
output_indexis change to the wallet’s ownchange/indexkey. The agent VERIFIES it by re-deriving that address — a false claim (hiding an external payee as “change”) fails verification and the spend is rejected.- __init__(output_index, change, index)¶
- class pyrxd.agent.ExternalOutput[source]¶
Bases:
objectA non-change output (a real payee), shown to the user before signing.
- __init__(output_index, dest, amount)¶
- class pyrxd.agent.InputToSign[source]¶
Bases:
objectOne input the agent must sign.
source_tx_hexis the FULL previous transaction; the agent verifies it hashes to the input’s outpoint and reads the real prevout value/script from it — it never trusts values embedded in the unsigned tx (prevout-authenticity, C1).change/indexare the BIP44 chain/index the agent derives the signing key from.- __init__(input_index, change, index, source_tx_hex, sighash=65)¶
- class pyrxd.agent.SignedResult[source]¶
Bases:
objectThe agent’s response: a fully-signed, broadcast-ready transaction.
Carries a transaction only — never key material (enforced invariant).
- exception pyrxd.agent.SignerDeclined[source]¶
Bases:
SignerErrorThe spend was declined at the confirmation gate (user said no).
- exception pyrxd.agent.SignerError[source]¶
Bases:
ValidationErrorA signing request was malformed, unauthorized, or failed validation.
Bases:
NetworkErrorThe agent is not reachable (socket absent, locked, or down).
A
NetworkErrorsubclass so callers can fall back to the mnemonic prompt without conflating it with a validation failure.
- class pyrxd.agent.SigningRequest[source]¶
Bases:
objectAn unsigned tx plus everything the agent needs to verify and sign it.
- __init__(unsigned_tx_hex, inputs, change_claims=())¶
- Parameters:
unsigned_tx_hex (str)
inputs (tuple[InputToSign, ...])
change_claims (tuple[ChangeClaim, ...])
- Return type:
None
- change_claims: tuple[ChangeClaim, ...] = ()¶
- inputs: tuple[InputToSign, ...]¶
- class pyrxd.agent.SpendSummary[source]¶
Bases:
objectWhat the agent is about to sign — handed to the confirmation gate.
Derived entirely from the verified tx (prevouts checked, change claims re-derived), never from caller-asserted free-form values.
- __init__(external_outputs, total_external, change_total, input_total, fee, sighash_flags=<factory>)¶
- external_outputs: tuple[ExternalOutput, ...]¶
- class pyrxd.agent.TtyConfirmer[source]¶
Bases:
objectConfirms spends on the daemon’s controlling terminal (
/dev/tty).auto_confirm_underlets small spends (total external ≤ threshold) skip the prompt — documented as outside the trust boundary. With no tty available the call fails closed (returnsFalse).
- class pyrxd.agent.UnsignedSend[source]¶
Bases:
objectThe watch-only build result: an unsigned tx + the request to sign it.
input_totalis the summed value of the SELECTED inputs (the builder picks a subset), so the caller can show an accurate fee = input_total − Σ outputs.- __init__(transaction, request, input_total)¶
- Parameters:
transaction (Transaction)
request (SigningRequest)
input_total (int)
- Return type:
None
- transaction: Transaction¶
- request: SigningRequest¶
- class pyrxd.agent.WatchOnlyScan[source]¶
Bases:
objectResult of a watch-only scan: spendable UTXOs + the next unused change index.
- __init__(utxos, next_change_index)¶
- Parameters:
utxos (list[WatchOnlyUtxo])
next_change_index (int)
- Return type:
None
- utxos: list[WatchOnlyUtxo]¶
- class pyrxd.agent.WatchOnlyTxBuilder[source]¶
Bases:
objectBuilds unsigned P2PKH sends + signing requests from an account
xpub.Holds only public material; structurally cannot derive a private key. The account
xpubis the one the agent vends on unlock (m/44'/<coin>'/<acct>'), so addresses derived here match exactly what the agent re-derives when it verifies ownership and change claims.- build_send(utxos, to_address, photons, *, change_index, change_chain=1, fee_rate=10000)[source]¶
Build an unsigned send of
photonstoto_addresswith change tochange_chain/change_index. Returns the unsigned tx + a SigningRequest.Mirrors
HdWallet.build_send_tx()’s greedy selection and dust-burn rule, but key-free and with an estimated (not signature-measured) fee.- Parameters:
- Return type:
- class pyrxd.agent.WatchOnlyUtxo[source]¶
Bases:
objectA spendable UTXO described by PUBLIC data only.
change/indexare the BIP44 coords of the owning address — the agent re-derives the signing key from them and checks it owns the prevout.source_tx_hexis the FULL previous transaction; the agent verifies it hashes to(txid, vout)and reads the real value/script from it (C1).- __init__(txid, vout, value, change, index, source_tx_hex)¶
- pyrxd.agent.agent_socket_path(wallet_path)[source]¶
The agent socket co-located with the wallet file:
<wallet dir>/agent.sock.Single definition so the CLI’s
agentandwallet sendcommands cannot drift to different (possibly looser) socket locations (security-panel M6).
- async pyrxd.agent.collect_watch_only_utxos(account_xpub, client, *, gap_limit=20)[source]¶
Gap-limit scan both BIP44 chains from the public xpub and collect spendable UTXOs.
get_historymarks a derived address used;get_utxosenumerates its spendable outputs;get_transactionfetches each output’s source tx (for the agent’s C1 prevout check). Also reports the next unused internal index, so the caller can place change on a fresh change address. No private key is touched.- Parameters:
account_xpub (Xpub)
client (ElectrumXClient)
gap_limit (int)
- Return type:
- pyrxd.agent.format_spend_summary(summary)[source]¶
Render the verified spend for human review (pure; no I/O).
Shows every external payee + amount, the change total, the input total, and the fee — all derived from the verified tx (prevouts checked, change re-derived), so what the user sees is what gets signed.
- Parameters:
summary (SpendSummary)
- Return type: