pyrxd.security — Typed primitives + errors¶
Security primitives for pyrxd.
This package contains the security foundation the rest of the SDK is built on: exception hierarchy, key-material wrappers, secure RNG helpers, and typed newtypes enforcing trust-boundary invariants.
Nothing in this package should ever log, print, or format raw key material.
- class pyrxd.security.BlockHeight[source]¶
Bases:
intNon-negative block height with a generous sanity ceiling.
- exception pyrxd.security.ConfirmationTimeoutError[source]¶
Bases:
InsufficientConfirmationsErrorA confirmation wait gave up before the tx reached the required depth.
A subclass of
InsufficientConfirmationsError(and therefore ofNetworkError) — which is exactly the distinction that class exists to make: “the tx is just shallow, retry / check the explorer” is a fundamentally different operator response from “the transport is broken”. A confirmation timeout is the former, so raising a bareNetworkErrorwould tell the caller the wrong thing.Carries
txidandwaited_sso a caller can render a resume hint. The txid is public chain data and is intentionally kept verbatim — it is the only thing that makes the failure actionable.- Parameters:
txid – the transaction that failed to reach
requiredconfirmations.have – the last observed depth (0 if the tx was never seen).
required – the caller-supplied
min_confirmationsthreshold.waited_s – elapsed seconds on the injected clock when the wait gave up.
reason – why the wait stopped (
"timeout","max_iterations", …).
- exception pyrxd.security.CovenantError[source]¶
Bases:
RxdSdkErrorRaised for covenant construction or verification failures.
- class pyrxd.security.Hex20[source]¶
Bases:
_FixedBytesExactly 20 raw bytes (e.g. a hash160 public-key hash).
- exception pyrxd.security.InsufficientConfirmationsError[source]¶
Bases:
NetworkErrorA tx exists but has fewer confirmations than the caller required.
Subclass of
NetworkErrorso existing handlers still catch it, but catchable as a distinct class forwait-for-confretry loops that need to discriminate “tx is just shallow, retry later” from “real transport error, fail fast”. The legacy substring match ("confirmations, required" in str(exc)) was fragile across reader implementations — this class is the typed replacement.- Parameters:
have – observed confirmation depth at read time (0 if unconfirmed).
required – the caller-supplied
min_confirmationsthreshold.detail – optional extra context appended in parentheses (e.g. why the wait gave up). Static description only — never key material.
- exception pyrxd.security.InsufficientFundsError[source]¶
Bases:
ValidationErrorA pre-flight value check found less funding than the operation provably needs.
Deliberately a subclass of
ValidationError: the SDK already raises a bareValidationError("Insufficient funds…")from ~16 sites inwallet.py,hd/wallet.py,agent/watch_only.pyandbtc_wallet/payment.py, and every existingexcept ValidationErrorhandler must keep catching this. The subclass only adds the machine-readableavailable/required/shortfalltriple so a caller can say how much more is needed instead of substring-matching a message.Not to be confused with
pyrxd.transaction.transaction.InsufficientFunds, which is a bareValueErrorraised by the low-level transaction builder and is not part of theRxdSdkErrorfamily. That one means “these inputs do not cover these outputs” at serialisation time; this one means “we checked before spending anything and the operation cannot succeed”. The two are not interchangeable and neither catches the other; new library code should raise this one.- Parameters:
message – static description — must not embed key material.
available – value the caller actually has, in the operation’s own units.
required – value the operation needs, same units.
- exception pyrxd.security.KeyMaterialError[source]¶
Bases:
RxdSdkErrorRaised for errors touching private keys, mnemonics, or WIFs.
Constructors raising this error MUST NOT include the offending key material in the message — pass a static description only.
- class pyrxd.security.Nbits[source]¶
Bases:
bytesThe compact difficulty target (nBits) from a block header.
Wire format¶
- nBits is a 4-byte little-endian encoding of a uint32. When decoded:
exponent = nBits_uint32 >> 24(high byte, little-endian: byte[3])mantissa = nBits_uint32 & 0x007fffff(low 3 bytes)target = mantissa * 256^(exponent-3)
This type accepts the raw 4 wire bytes and validates the three conditions Bitcoin Core enforces on target-word parsing. A malformed nBits can be used to forge PoW (e.g. a negative target evaluates the comparison weirdly, a zero target is trivially satisfied, an over-large exponent shifts out of range). Rejecting these at the trust boundary protects every SPV check downstream.
- exception pyrxd.security.NetworkError[source]¶
Bases:
RxdSdkErrorRaised for transport / RPC / network failures.
- class pyrxd.security.Photons[source]¶
Bases:
intNon-negative integer amount in photons (RXD smallest unit), capped at Radiant max supply.
The cap is
RADIANT_MAX_PHOTONS, so every value a Radiant node can put in an output constructs. It exists to catch the shapes a hostile or broken server can inject that an unboundedintwould carry into coin selection — a value large enough to swamp any subtraction, or one parsed out of a field that was never a number.
- exception pyrxd.security.PolicyRejection[source]¶
Bases:
CovenantError,NetworkErrorRaised when a node rejects a transaction on a consensus/policy rule (e.g.
mandatory-script-verify-flag-failed, dust, min-relay-fee, an ElectrumXcode 1).Surface this distinctly rather than letting a node rejection be reclassified as a plain
NetworkError— that masking hid a critical dMint covenant-rejection bug for weeks (see docs/solutions/logic-errors/dmint-v1-mint-scriptsig-divergence.md). The masking harm was that the node’s reason was discarded, so a script failure was indistinguishable from a dropped socket.Parentage: it inherits from both
CovenantError(its original parent — keeps everyexcept CovenantErrorhandler working) andNetworkError(so the ~30except NetworkErrorhandlers that already wrap broadcast calls do not silently stop catching rejections now that this class is actually raised). A node rejection is not covenant-specific — a dust or min-relay-fee rejection has nothing to do with covenants — so the covenant-only parentage it shipped with was too narrow. Widening it here is the compatible fix; re-rooting it under a dedicatedNodeRejectionbase would be the cleaner shape but is a breaking change for existing handlers.- Parameters:
message – sanitized, caller-safe description. Node messages are attacker-influencable text — run them through
redact()and strip control characters before constructing this.code – the RPC error code, when the server supplied one.
reason – the sanitized server reason on its own, for programmatic matching.
- class pyrxd.security.PrivateKeyMaterial[source]¶
Bases:
SecretBytesA
SecretByteswhose contents are a valid secp256k1 private key.- Invariants enforced at construction:
length is exactly 32 bytes
integer value is in the valid scalar range
[1, N-1]
- classmethod from_wif(wif)[source]¶
Decode a WIF-encoded private key.
On failure raises
KeyMaterialErrorWITHOUT embedding the inputwifin the message — an attacker watching logs must not learn any part of the candidate key.Implementation is self-contained (no heavy coincurve dependency) so the security module can be imported before the rest of the SDK.
- Parameters:
wif (str)
- Return type:
- class pyrxd.security.RawTx[source]¶
Bases:
bytesRaw transaction bytes.
Enforces the 64-byte Merkle-forgery defense: any candidate transaction must be strictly greater than 64 bytes. A 64-byte “transaction” can be forged from an internal Merkle-tree node, letting an attacker prove inclusion of bogus data. See audit finding 02-F-1 and Bitcoin BIP-141’s segwit commitment for the historical context (and the CVE-2017-12842 family for concrete exploits).
- exception pyrxd.security.RxdSdkError[source]¶
Bases:
ExceptionBase class for every exception raised by pyrxd.
Applying
redactto each positional arg on construction defends against accidental key-material leakage when callers pass user-supplied values straight into the exception.
- class pyrxd.security.Satoshis[source]¶
Bases:
intNon-negative integer amount in Bitcoin satoshis, capped at Bitcoin max supply.
Not for Radiant values — see
RADIANT_MAX_PHOTONSandPhotons.
- class pyrxd.security.SecretBytes[source]¶
Bases:
objectA wrapper around
bytesthat will not leak its contents on repr/str.The internal storage is a
bytearraysozeroize()can mutate it in place.unsafe_raw_bytesreturns an immutablebytescopy.- classmethod from_hex(h)[source]¶
Construct from a hex string.
The hex string itself is not embedded in any error message — an attacker who can see the exception must not learn the invalid input was “0x…” with specific bytes.
- Parameters:
h (str)
- Return type:
- class pyrxd.security.SighashFlag[source]¶
Bases:
intA valid Radiant sighash flag byte.
- exception pyrxd.security.SpvVerificationError[source]¶
Bases:
RxdSdkErrorRaised when an SPV proof (Merkle path, header chain) fails to verify.
- exception pyrxd.security.ValidationError[source]¶
Bases:
RxdSdkErrorRaised when input fails a trust-boundary validation check.
- pyrxd.security.redact(value)[source]¶
Return a redacted representation of
valueif it looks sensitive.strlonger than 8 chars that looks like key material ->"<redacted>"byteslonger than 8 bytes ->"<redacted:Nb>"other types -> returned unchanged
Warning
This matches the value as a whole.
redact(wif)redacts;redact(f"bad wif {wif}")does not — the interpolated string is neither all-hex nor all-base58, so the heuristic declines and the secret passes through verbatim. So the idiom at the top of this module,raise KeyMaterialError(redact(bad_wif)), is the only defended shape, andraise ValidationError(f"bad wif {wif}")is undefended by construction.Making this per-token instead is not the fix: nearly every English word longer than 8 characters is also a valid base58 string (
"transaction"is), so per-token redaction would replace ordinary prose with<redacted>, and it would also swallow the public txids that errors likeConfirmationTimeoutErrordeliberately keep verbatim because they are the only thing that makes the failure actionable. The defence for the embedded shape is therefore the call-site discipline this module documents, enforced bytests/security/test_key_material_never_echoed.py— not a wider heuristic here.
- pyrxd.security.secure_random_bytes(n)[source]¶
Return
ncryptographically secure random bytes.Thin wrapper over
secrets.token_bytes(). Exists so callers have a single chokepoint to audit/mock and so then <= 0guard lives in one place.
- pyrxd.security.secure_scalar_mod_n()[source]¶
Draw a uniform random scalar in
[1, N-1]and return it wrapped.Convenience wrapper combining
pyrxd.security.rng.secure_scalar_bytes_mod_n()withPrivateKeyMaterial. Lives in this module (notrng) sornghas no dependency onPrivateKeyMaterial— that keeps the import graph acyclic.- Return type: