Skip to content

Latest commit

 

History

History
941 lines (767 loc) · 42.3 KB

File metadata and controls

941 lines (767 loc) · 42.3 KB

Changelog

All notable changes to pyrxd are documented here. Format based on Keep a Changelog; this project follows Semantic Versioning.

[Unreleased]

Added

  • RxinDexerClient discovery wrappers (glyph_get_recent, glyph_get_tokens_by_type) — global newest-first asset lists over the RXinDexer v4 discovery indexes (Glyph DB schema 4, live on electrumx.radiantcore.org since 2026-07-18). Cursor-paginated ({"tokens", "next_cursor"}); order="recent" on the by-type call is newest-deployed-first, default "ref" keeps the legacy order. Enables incremental watermark sync: page newest-first, stop when deploy_height drops below the last run's watermark.

Fixed

  • dMint ASERT difficulty adjustment rebuilt as fractional fixed-point (ASERT-v2). The previous on-chain "ASERT" was an integer power-of-2 stepper (drift = trunc(excess / halfLife) clamped to [-4, +4], target *= 2^drift) with three structural defects: a dead zone (no adjustment while |excess| < halfLife), a one-sided ratchet (it could never raise difficulty when halfLife >= targetTime), and ≥2× lurches off a single nLockTime sample. ASERT-v2 replaces it with a fractional, symmetric, damped step (RADIX = 2^16; driftFp = (excess·RADIX)/halfLife clamped to ±RADIX/4 ⇒ the target moves at most ±25% per mint; difficulty floor 4 via the LWMA-style MAX/4 cap). New daa_mode=asert deploys emit the v2 bytecode, which is byte-identical to canonical Photonic buildAsertDaaBytecode and overflow-safe in int64 by construction (divide-first). Validated by golden byte-match against upstream and a 3 000-case differential that executes the actual bytecode under a faithful CScriptNum/OP_MUL-abort evaluator; a regtest consensus test (-m integration) is included.
  • No brick for pre-upgrade ASERT contracts. Contracts deployed before this change keep mining under the legacy formula: the miner detects the on-chain format by codescript signature (<RADIX push> OP_MUL vs <halfLife push> OP_DIV) and dispatches to the matching off-chain target computation. The legacy bytecode is retained and frozen by test.
  • Low-level fee default raised to the min-relay floor. TRANSACTION_FEE_RATE (the default for the no-argument Transaction.fee()) was 5 photons/KB (0.005 photons/byte) — ~2,000,000× under Radiant's 10,000 photons/byte post-V2 floor, so a transaction built via that default was non-relayable. It is now 10_000_000 photons/KB (= 10,000 photons/byte). All high-level paths (RxdWallet, HdWallet, glyph builders, CLI) already used 10,000 photons/byte; this aligns the low-level default with them. Override via RXD_PY_SDK_TRANSACTION_FEE_RATE is unchanged.

Changed

  • DmintCborPayload now mirrors Photonic DmintPayload for all DAA modes. The token-metadata CBOR gained the optional daa sub-keys asymptote (ASERT), epochLength + maxAdjustment (EPOCH), and schedule (SCHEDULE, as [{height, difficulty}]), so EPOCH/SCHEDULE V2 tokens emit and round-trip complete indexer-display metadata. Backward-compatible: FIXED / ASERT / LWMA payloads are byte-unchanged (the new keys are emitted only when set). max_adjustment is the multiplier (2/4/8/16) and schedule uses difficulty, both CBOR-native — convert from DmintDeployParams at the call site.

  • Default ASERT half_life is now 240 seconds (was 3600), ≈4× the default 60 s target_time, matching canonical Photonic DEFAULT_ASERT_HALFLIFE. Affects DmintDeployParams, build_dmint_mint_tx, and the pyrxd glyph deploy-dmint / claim-dmint CLI defaults. Explicit half_life values are unaffected; a contract must be re-mined with the same half_life it was deployed with (the mint builder byte-verifies this against the baked bytecode before grinding PoW).

[0.9.0] — 2026-06-18

Posture + documentation release. pyrxd's maturity framing is now consistent with the rest of the Radiant ecosystem: it is open-source software provided as-is, without warranty (Apache 2.0), like Radiant Core itself — rather than singling itself out as uniquely pre-audit. No new features; no breaking API changes.

Changed

  • Audit gates are now advisory, not blocking. The code-enforced gates that previously raised on value-bearing networks no longer hard-block mainnet / real-value use — pyrxd does what you tell it, consistent with running a Radiant node. require_audit_cleared and require_spv_sole_authority_cleared are retained as no-ops for backward compatibility (callers passing audit_cleared= are unaffected), and dMint V2 deploy no longer requires the allow_v2_deploy opt-in. The cross-chain swap stack remains unaudited — verify it yourself before moving real value.
  • Maturity language aligned to the standard open-source posture across the README and docs (the Apache-2.0 "as-is, no warranty" disclaimer is the operative one).

Documentation

  • Tutorials refreshed and verified end-to-end on regtest. Fixes: a wallet-load crash in "your first Radiant transaction" (strPath); the GlyphMedia import path; the FT token ref is the commit outpoint (not the reveal txid); the bundled parallel miner ships and is the default for claim-dmint (was documented as "bring your own"); stale 0.5.0 version pins → 0.8.0; and the new pyrxd wallet send CLI is surfaced. The "inspect a transaction" supply math and output-badge counts corrected.

[0.8.0] — 2026-06-18

Feature release on top of 0.7.0 — 56 commits. The headline is full, mainnet-proven dMint V2 (PoW distributed mint with adaptive difficulty). Alongside, the experimental, pre-audit cross-chain swap + watchtower stack advances (an autonomous asset-claim executor, more counter-chains, a CLI signing agent) and a CRITICAL HTLC preimage fix lands.

No breaking changes to the stable public API — everything is additive and existing import paths + CLI commands are unchanged. The cross-chain swap + watchtower stack and dMint V2 real-value use remain pre-external-audit and gated; this code is not for production until externally audited.

Added

  • dMint V2 (PoW distributed mint) — full support, mainnet-proven. pyrxd glyph deploy-dmint --v2 + claim-dmint deploy and PoW-mine V2 contracts with all five difficulty-adjustment modes (FIXED, ASERT, LWMA, EPOCH, SCHEDULE), byte-matched to canonical Photonic dMintScript. A first V2 deploy + PoW mint and an adaptive-difficulty retarget were confirmed on Radiant mainnet. V2 deploy is gated behind an explicit opt-in (pre-external-audit). (#206, #228, #232–#238)
  • Autonomous asset-claim executor for the swap watchtower — dormant-by-construction, capped, value-vs-reorg-gated; arms a keyed RXD covenant claim only on an audit-cleared network. (#239)
  • More counter-chains: Base and Litecoin, plus the Optimism / Arbitrum / Linea EVM registry. (#198, #200, #216)
  • CLI signing agent (sign-on-behalf) with transient account-key derivation (no long-lived key residency). (#190, #191, #201)
  • RXD multi-source quorum for the watchtower, clearing the single-source low_corroboration blocker. (#187)
  • Consensus-enforced soulbound covenants + credential-bound swap gating. (#186)
  • High-level partial-transaction swap API for same-chain trades. (#177)
  • CappedFeeWalletSource — a structural spend ceiling for autonomous RXD fees. (#211)
  • Tier-1 developer on-ramp: SDK swap primitive, regtest tooling, quickstart, a flagship RXD↔ETH cross-chain-swap tutorial, and expanded API reference. (#177, #185, #195, #196, #214, #215, #220, #226, #227)

Fixed

  • HTLC preimage-length theft (CRITICAL). The BTC claim leaf and all three Gravity HTLC covenants now consensus-pin the revealed preimage to exactly 32 bytes (OP_SIZE); without it a non-32-byte p' could defeat the keyless secret-scrape and let a maker keep both legs. (#239)
  • dMint EPOCH/LWMA int64-overflow. Found via differential testing (the on-chain retarget could exceed int64 and brick the contract), fixed upstream in canonical Photonic, and re-enabled here byte-matched to the merged fix. (#234, #238)
  • dMint V1 + FIXED V2 validated on real Radiant consensus (radiant-core v3.1.1). (#195, #228)
  • Cross-chain swap hardening across several audit rounds — value-scaled claim burial (fail-closed, dust opt-out), reorg-value guard, maker-stall → mutual_refund routing, durable seen-store default. (#189, #192, #193, #194, #210)
  • CLI: a malformed path option now returns a clean usage error. (#188)

[0.7.0] — 2026-06-07

Feature release on top of 0.6.x — 15 commits. The user-facing headline is a wallet sweep command and setup --coin-type. Alongside, the experimental, pre-audit cross-chain swap + watchtower stack advances: ETH counter-leg watching (alert-only), FT/NFT↔ETH swap coverage, and a dormant, capped, keyless autonomous BTC refund with a Go-gated dust-run harness — see the dedicated section below; this code is still not for production use until externally audited.

No breaking changes — everything is additive; existing public import paths and CLI commands are unchanged.

Added

  • pyrxd wallet sweep — move the full balance from any single derived path to a destination address, for consolidating funds a recovery scan turned up on a non-default derivation (#161).
  • pyrxd setup --coin-type — choose the HD derivation coin type at wallet init (e.g. 0 for Photonic/Chainbow compatibility) instead of the default; and pyrxd inspect now classifies rarer Glyph types (#174).

Changed

  • Internal: split the monolithic glyph_cmds.py (extracted inspect and shared helpers, de-duplicated _load_wallet). Public CLI unchanged (#167).

Fixed

  • wallet sweep now reports a clear, actionable error when the balance is dust (below the fee) instead of failing opaquely (#163).

Tests

  • Added a fuzz suite over the user-facing CLI surface, hardening argument parsing and command dispatch against malformed input (#175).

Experimental — pre-audit, NOT for production

These components ship for testing and integration only. The cross-chain atomic-swap and watchtower code has not cleared an external security audit. Do not use it to move real value beyond throwaway amounts.

  • Watchtower — ETH counter-leg watching (alert-only v3) — the watchtower now also watches RXD/Glyph↔ETH swaps via a production keyless RpcEthChainSource over a read-only ETH RPC, with a regtest end-to-end harness. It holds no keys and broadcasts nothing (#168, #170).
  • ETH↔RXD swap coverage — FT (fungible-token)↔ETH and Glyph(NFT)↔ETH atomic-swap harnesses, with a mainnet REST REF-authenticity gate (#166, #169).
  • Watchtower v2 — dormant, capped, keyless autonomous BTC refund — the first autonomous watchtower action: it broadcasts an operator-pre-signed BTC CSV refund when one is due and the operator is offline. Keyless (the daemon never holds a key — it re-sends pre-signed bytes), refund-only, and dormant-by-construction on a value-bearing network (no autonomy without an explicit, dust-capped opt-in). Adds a signet/testnet-capable runner and a Go-gated dust-run harness whose setup refuses to emit a funding address unless the refund provably reconstructs from on-disk state. Exercised on regtest and a mainnet dust run (#171, #172, #173).

Docs

  • Stuck-RXD recovery guide: pipx/venv install guidance (#162), an Electron-Wallet move option (#164), and a note that recover --scan takes a couple of minutes rather than hanging (#165).

[0.6.1] — 2026-06-04

Patch release. Fixes the package version reported by the CLI and the pyrxd.__version__ attribute.

Fixed

  • pyrxd --version and pyrxd.__version__ now report the actual installed version. __version__ was a hardcoded string in pyrxd/__init__.py that was separate from pyproject.toml; it went stale and the 0.6.0 wheel shipped reporting 0.5.1. __version__ is now derived from the installed package metadata (importlib.metadata.version), so it tracks pyproject.toml automatically and cannot drift again.

[0.6.0] — 2026-06-04

First release since 0.5.1 — 108 commits. The headline is multi-path HD wallet recovery; alongside it ship WAVE + RXinDexer support, a Photonic-compatible TIMELOCK protocol, a dmint subpackage refactor, and broad SPV/parser hardening. This release also includes experimental, pre-audit cross-chain HTLC atomic-swap engines (BTC↔RXD, ETH↔RXD) and a swap watchtower — see the dedicated section below; these are not for production use until externally audited.

No breaking changes to the stable public API: everything is additive, and the dmint subpackage split preserved existing pyrxd.glyph / pyrxd.glyph.dmint import paths.

Added

HD wallet — multi-path recovery / account discovery

  • pyrxd wallet recover --scan — read a BIP39 mnemonic and scan every coin_type × account pair across both BIP44 chains, reporting which derived addresses actually hold on-chain history and balance. Solves the "balance shows on the explorer but my wallet says empty" problem that arises because different wallets derive different addresses from the same seed — coin type 0 (legacy / Photonic ≤ v2 / Chainbow), 512 (SLIP-0044), 236 (older pyrxd). Read-only by design: it never signs or broadcasts; the mnemonic is prompted with hidden input and is never echoed, stored, or transmitted — only derived addresses (as scripthashes) reach the network. --coin-types and --accounts widen or narrow the search; --json for machine output.
  • pyrxd.hd.discovery — public discover(), DiscoveryReport, DiscoveryHit, and coin_type_label, with DEFAULT_COIN_TYPES = (0, 512, 236) and DEFAULT_ACCOUNTS = (0, 1, 2) as the single source of truth for the default search space.

Glyph — WAVE + RXinDexer client

  • pyrxd.glyph.wave — Photonic-compatible WAVE CBOR encoding plus RxinDexerClient for indexer-backed queries (#102).

Glyph — Photonic-compatible TIMELOCK protocol (REP-3009)

  • TIMELOCK token-protocol support (pyrxd.glyph.timelock, timelock_reveal_tx) compatible with Photonic's REP-3009 (#106).

Glyph — mainnet golden vectors

  • Wire-format builders pinned to real on-chain bytes: CBOR payload (#125), commit-script FT + NFT (#126), NFT locking script (#127).

Changed

  • pyrxd.glyph.dmint is now a four-submodule subpackage, split from the former monolithic dmint.py (#109). Public import paths are unchanged.
  • Glyph parsing: extracted a shared input-ref opcode walker and added PolicyRejection (#107); de-duplicated hashOutputHashes and hardened output parsing (#124).

Fixed

  • Miner: parallel-miner workers are now terminated on every exit path, fixing orphaned multiprocessing workers left behind on early exit (#116).

Security

  • SPV primitives: red-team hardening plus swap-coordinator / data-source follow-ups (#138); secure-by-default and further hardening covering findings F-02/F-09/F-12/F-16/F-17/F-26 (#142); live-regtest consensus validation of deployed covenant semantics across the V/NB/M/S matrix (#143).
  • Fixed a transitive idna DoS via a batched dependency update (#144).
  • Defense-in-depth secret scanning (trufflehog, #118) and tightened CI token scopes + CodeQL security-extended (#157).

Performance

  • ~52× faster SPV-related tests by pre-mining synthetic-block PoW headers (#108); halved the CI test job via header-grind memoization and dependency caching (#140).

Experimental — pre-audit, NOT for production

These components ship for testing and integration only. The cross-chain atomic-swap and watchtower code has not cleared an external security audit. Do not use it to move real value beyond throwaway amounts.

  • Gravity BTC↔RXD HTLC atomic-swap engine — async coordinator, BTC and Radiant legs, a five-binding REF gate, and a reorg-finality gate; exercised on regtest and a dust mainnet run (#137).
  • ETH↔RXD HTLC atomic swap — ETH leg and coordinator integration; red-team-fixed but pre-audit (#155).
  • HTLC swap watchtower v1 — alert-only, BTC-first; holds no keys and never broadcasts (#156).

Dependencies & tooling

  • Batched Dependabot updates across the cycle (#144, #146, #150, #153, and others); dev tooling moved to mypy ^2.1.0 (#150) and sphinx ^8 (#115). Added a manual Combine-PRs workflow (#154). Python support is unchanged: >=3.10,<4.0.

Docs

  • New tutorials (your first transaction #87, mint from a V1 dMint contract #85, inspect a transaction in the browser #82), how-to guides (broadcast a transaction #83, scan an address for Glyphs #84, BIP143 sighash quirks #86, verify an SPV proof #88, SPV verification pitfalls #139), and concept explainers (Glyph structures #121, V1 dMint mechanics #77, external miner protocol #78).

[0.5.1] — 2026-05-13

Audit follow-ups + new SDK primitives. No breaking changes; everything new is additive. Closes the four golden-vector recommendations from the 0.5.0 pattern-recognition audit and ships the time-lock builders

  • parallel miner that were deferred at 0.5.0 cut.

Added

Time-lock script primitives

  • pyrxd.script.timelock — new module with canonical Bitcoin/Radiant time-lock locking scripts:
    • build_p2pkh_with_cltv_script(pkh, locktime) — P2PKH gated by absolute time-lock (BIP-65 OP_CHECKLOCKTIMEVERIFY).
    • build_p2pkh_with_csv_script(pkh, sequence) — P2PKH gated by relative time-lock (BIP-112 OP_CHECKSEQUENCEVERIFY).
    • build_csv_sequence(units, kind) — encodes a (blocks | 512-second intervals) pair into the BIP-112 stack/nSequence integer form.
    • CsvKind.BLOCKS / CsvKind.TIME_512_SECONDS — kind enum.
    • LOCKTIME_THRESHOLD = 500_000_000 — height-vs-Unix-time boundary.
  • Validation rejects out-of-range locktime/sequence, the BIP-112 disable bit (would silently make the lock a no-op), and wrong-size PKH. 20 tests cover minimal-int push behaviour, sign-pad edge cases, bit-22 encoding, and the cross-invariant that CLTV and CSV shapes differ only at the verify opcode.
  • Scope: locking scripts only. Threading nLockTime / nSequence through transaction construction (and the unlocking ScriptTemplate that consumes signatures) is deferred until a concrete pyrxd consumer needs it.

Unified miner entrypoint

  • mine_solution_dispatch(preimage, target, *, miner_argv=None, ...) — single entrypoint that routes to in-process mine_solution when miner_argv is None, otherwise to mine_solution_external for a subprocess miner. Callers no longer have to branch on miner availability themselves. Exported from pyrxd.glyph.

Parallel pure-Python miner

  • pyrxd.contrib.miner — multi-process parallel miner that scales the existing pure-Python mining loop across CPU cores using the JSON-over-stdio external-miner protocol. No compiled extensions; useful when a faster C/Rust miner isn't available.

Mainnet golden-vector test infrastructure

  • Four new test classes pin every wire-format builder against real on-chain bytes — the strongest interop assertion an SDK can carry:
    • TestFtLockingScriptMainnetGolden (tests/test_dmint_module.py) — 75-byte FT script vs RBG transfer ac7f1f70…0ae4.
    • TestNftLockingScriptMainnetGolden (tests/test_glyph.py) — 63-byte NFT singleton vs Glyph NFT 27390efa…be7e.
    • TestCommitLockingScriptMainnetGolden (tests/test_glyph_dmint.py) — 75-byte commit script, both FT and NFT branches, vs the GLYPH deploy a443d9df…878b.
    • TestCborPayloadMainnetGolden (tests/test_glyph.py) — full 65,569-byte CBOR reveal payload incl. embedded PNG, vs GLYPH reveal b965b32d…9dd6 (fixture: tests/fixtures/glyph_reveal_cbor.bin). Pins sha256d(payload) == commit_hash linkage, decoder shape, and OP_PUSHDATA4 framing for payloads >65,535 bytes.

V2 quarantine markers

  • V2UnvalidatedWarning — new UserWarning subclass; emitted by every V2 dMint entry point. Silenceable with warnings.simplefilter("ignore", V2UnvalidatedWarning); escalable to error with warnings.simplefilter("error", V2UnvalidatedWarning) in CI. No V2 dMint contract has been deployed to Radiant mainnet as of 0.5.1; the V2 code paths are byte-equivalent-by-construction to V1 where they share bytecode but have never been exercised against live consensus.
  • build_dmint_v2_mint_preimage(...) — V2 PoW preimage helper that mirrors the V1 helper. Carries V2UnvalidatedWarning.

Documentation

  • Migration guide: docs/how-to/migrate-0.4-to-0.5.md.
  • New tutorials: mint a Glyph NFT, mint a Glyph FT, mint from a V1 dMint contract on Radiant mainnet, inspect any Radiant transaction in the browser, your first Radiant transaction.
  • New how-tos: scan an address for Glyphs, broadcast a Radiant transaction, handle Radiant's BIP143 sighash quirks, verify an SPV proof.
  • New concepts pages: V1 dMint mint mechanics, V1 dMint deploy, external miner protocol (JSON-over-stdio subprocess contract), Glyph inspect tool (structural match, not semantic correctness).
  • Design decision: docs/solutions/design-decisions/wave-protocol-deferred-until-consumer.md — WAVE name-claim protocol deferred until a concrete pyrxd consumer needs it.

Changed

  • CI: cancel in-flight runs on push to the same branch / PR. Roughly halves per-PR Actions minute spend.
  • CI: hash-pin poetry==2.3.4 and ruff==0.15.12 via pip-compile --generate-hashes lock files under ci/. Each workflow that previously ran pip install poetry==2.3.4 now runs pip install -r ci/poetry-pin.txt --require-hashes. Closes 3 of the 5 open OpenSSF Scorecard / CodeQL PinnedDependenciesID alerts (the remaining 2 are on the docs build's editable pip install -e ., which physically cannot be hash-pinned). See ci/README.md for the bump workflow.

Fixed

  • CodeQL note-severity alerts cleared: replaced the bare import availability probe in docs/inspect_static/inspect/glue.py with importlib.util.find_spec (closes py/unused-import); switched internal re-exports in src/pyrxd/glyph/builder.py and src/pyrxd/cli/glyph_cmds.py to PEP 484 explicit X as X form (CodeQL doesn't honour # noqa: F401 on those — the explicit re-export form does close the alert); added unreachable raise AssertionError("unreachable: pytest.skip raises") after pytest.skip in tests/test_ripemd160_fallback.py (closes py/mixed-returns).

Security

  • The 0.5.0 pre-release multi-reviewer audit's deferred items (V2 quarantine, mainnet golden vectors for FT/NFT/commit/CBOR, CodeQL note cleanup) are closed by this release. No new formal audit was run for 0.5.1; changes are additive on top of audited 0.5.0 code and each touches a single area (script primitives, test fixtures, CI config) with high in-PR review coverage.

[0.5.0] — 2026-05-11

Added

dMint V1 deploy (M2)

  • prepare_dmint_deploy_v1 — full V1 dMint deploy support. Builds the commit + reveal scripts for the mainnet-canonical "one reveal, many parallel contract UTXOs" shape that every live dMint token (RBG, snk, etc.) uses. Pinned against Photonic-Wallet's reference layout (docs/DMINT_RESEARCH.md §2/§3).
  • DmintV1DeployParams / DmintV1DeployResult / DmintV1ContractInitialState — typed inputs/outputs for the V1 deploy flow.
  • find_dmint_contract_utxos — chain helper that walks ElectrumX to enumerate the N unspent contract UTXOs from a V1 deploy reveal txid.
  • examples/dmint_v1_deploy_demo.py — end-to-end runnable V1 deploy demo with resume support, commit/reveal atomic signing, and param-drift defense on the resume file.
  • Live-validation: deploy reveal at 8eeb333943771991c2752abc78038365ecd76b1a24426f7a3212eea71b6a6564 (2026-05-11) produced 4 unspent contracts and was classified correctly by the inspect tool.

dMint V1 mint scriptSig — golden-vector pinning

  • PowPreimageResult dataclass — frozen record of (preimage, input_hash, output_hash) returned by build_pow_preimage. Forces miners + scriptSig assembly to feed from a single byte source, structurally preventing the recurring builder-vs-covenant divergence pattern.
  • TestCovenantShape regression suite — pins the V1 mint scriptSig convention against the mainnet snk token mint 146a4d688ba3fc1ea9588e406cc6104be2c9321738ea093d6db8e1b83581af3c AND pyrxd's own first successful mint c9fdcd3488f3e396bec3ce0b766bb8070963e7e75bb513b8820b6663e469e530. Two independent mainnet golden vectors.
  • TestFtLockingScriptBuilderCrossEquality — byte-equality test between the two FT-output builders in pyrxd.glyph.script and pyrxd.glyph.dmint (red-team finding R2 from the 0.5.0 pre-release audit).

CBOR reveal scriptSig

  • build_reveal_scriptsig_suffix now supports OP_PUSHDATA4 for payloads above 65,535 bytes (up to a 256 KB hard cap). The mainnet GLYPH reveal at b965b32d…9dd6 used 65,569 bytes via PUSHDATA4 — pyrxd would have refused to build that shape under the previous PUSHDATA2-only cap. Red-team finding R3.

Inspect tool

  • V1 mint scriptSig parsing — decode + display the 4 canonical pushes (nonce, inputHash, outputHash, OP_0). V1 vs V2 distinguished by nonce width (4 vs 8 bytes).
  • V1 deploy commit/reveal shape detection in the browser inspector.

Public-API exports (pyrxd.glyph)

  • PowPreimageResult, build_dmint_v1_mint_preimage, build_dmint_v1_ft_output_script now exported from pyrxd.glyph for direct import and type annotation.

Changed (breaking)

  • build_pow_preimage(...) now returns PowPreimageResult(preimage, input_hash, output_hash) instead of the raw 64-byte preimage. Migration:
    # before (0.4.0):
    preimage = build_pow_preimage(txid_le, ref, in_script, out_script)
    # after (0.5.0):
    result = build_pow_preimage(txid_le, ref, in_script, out_script)
    preimage = result.preimage  # if you only need the bytes
  • build_mint_scriptsig(nonce, preimage, *, nonce_width) is now build_mint_scriptsig(nonce, input_hash, output_hash, *, nonce_width). The two 32-byte hashes MUST come from the same build_pow_preimage call that produced the mined preimage. Splitting the sources caused the M1 covenant-rejection incident — see docs/solutions/logic-errors/dmint-v1-mint-scriptsig-divergence.md.
  • build_dmint_v1_mint_preimage(...) now returns PowPreimageResult instead of bytes. Callers feed .preimage to the miner and .input_hash/.output_hash to build_mint_scriptsig.
  • No backward-compatible shim — the old signature could silently produce on-chain-rejected transactions, so a hard break with loud TypeError / ValidationError is safer than a deprecation path.

Fixed

  • CRITICAL (latent): V2 mint reward output was emitting a 25-byte plain P2PKH; the V2 covenant requires a 75-byte FT-wrapped reward. Every V2 mint would have been rejected by the network with mandatory-script-verify-flag-failed once a V2 contract existed on chain. No V2 contracts exist yet, so the bug was caught pre-mainnet during the 0.5.0 red-team audit (finding R1). V2 reward now uses the same build_dmint_v1_ft_output_script as V1 — the FT-conservation fingerprint dec0e9aa76e378e4a269e69d is shared via _PART_C between V1 and V2.
  • CRITICAL (M1 follow-up): V1 mint scriptSig pushed the wrong values. The original M1 (shipped in 0.4.0 via PR #65) had build_mint_scriptsig pushing the PoW preimage halves into the scriptSig instead of the raw SHA256d(funding_script) and SHA256d(OP_RETURN_script) the covenant expects. Every successful mine was rejected by the on-chain covenant; M1 had never successfully spent a contract. Fix verified on Radiant mainnet at txid c9fdcd3488f3e396bec3ce0b766bb8070963e7e75bb513b8820b6663e469e530. See docs/solutions/logic-errors/dmint-v1-mint-scriptsig-divergence.md.

Security

  • The 0.5.0 pre-release audit was run by 8 independent specialised reviewers (security, red-team chain-conformance, data-integrity, Python code-quality, simplicity, architecture, performance, pattern-recognition). Two CRITICAL findings (R1 + the M1 scriptSig bug) and two HIGH findings (R2 cross-builder drift risk, R3 PUSHDATA4 capability gap) were addressed pre-tag. Medium/low findings tracked for 0.5.1.

Migration notes

Public API consumers must update three call sites:

  1. build_pow_preimage(...) — use .preimage attribute on the returned PowPreimageResult if you only need the 64 bytes.
  2. build_mint_scriptsig(nonce, preimage, ...)build_mint_scriptsig(nonce, result.input_hash, result.output_hash, ...).
  3. build_dmint_v1_mint_preimage(...) — same .preimage pattern.

The library raises loud TypeError / ValidationError immediately on old-style calls — there is no silent-failure migration path.

[0.4.0] — 2026-05-07

Added

Glyph inspect — CLI

  • pyrxd glyph inspect — offline classifier for any Glyph input (script hex, txid, outpoint, contract id). Always emits a "structural pattern match" qualifier so users understand the tool classifies on-chain shapes, not protocol-level semantic correctness.
  • pyrxd glyph inspect --fetch — txid lookup via the configured ElectrumX server; full transaction structure with per-output classification.
  • V1 dMint contract parsing — the actual mainnet format observed during RBG live testing. V2 also supported for future-compat.
  • Locked against a real RBG transfer fixture so the classifier is pinned to mainnet behaviour, not synthetic vectors.

Glyph inspect — browser-hosted (GitHub Pages)

  • New static tool at docs/inspect_static/inspect/ (live at the Pages site under /inspect/). Loads pyrxd via Pyodide and runs the inspect classifier entirely in-browser — no server, no key material, no transaction broadcast.
  • Inputs: raw script hex, txid (auto-fetches via ElectrumX WebSocket), outpoint, contract id.
  • Tx-shape banner explaining what kind of transaction the user is looking at: FT deploy, NFT deploy, dMint contract deploy, dMint claim (with height / max_height), Glyph burn, mutable contract update. Plain RXD sends and ordinary transfers render with no banner.
  • Per-output structural-match qualifier on every classified script type (ft, nft, mut, dmint, commit-ft, commit-nft, op_return) spelling out exactly what the pattern match does not verify — never claims semantic correctness.
  • OP_RETURN data carriers classified explicitly with data_hex split out from the leading opcode.

Glyph protocol

  • GlyphRef.from_contract_hex — parse explorer-style contract ids in the standard hex form.
  • is_dmint_script / extract_*_from_dmint_script — first-class dMint contract recognition alongside the existing FT/NFT/MUT helpers.
  • TR39 confusables / homoglyph detector (pyrxd.glyph.confusables) — flags Latin-spoofed token names and symbols against the Unicode TR39 confusables data. Skeleton + is_latin_lookalike helpers for inspecting hostile glyph metadata.

Hash

  • Pure-Python RIPEMD160 fallback. OpenSSL 3 distros (and Pyodide) ship without a built-in RIPEMD160 provider; the fallback keeps pyrxd working out of the box on those environments. Selected at import time; OpenSSL is preferred when available.

Security

  • All browser-hosted inspect install artifacts (Pyodide loader, pyrxd wheel, micropip wheels, vendored cbor2 wheel) verified by SHA-256 before micropip.install. Loader uses Subresource Integrity. Mismatch aborts install loudly rather than falling through.
  • Vendored cbor2==5.4.6 wheel served same-origin. cbor2 6.x ships C-only; pinning to the last pure-Python release closes a Pyodide install path that depended on PyPI staying reachable and unchanged.
  • micropip.install(..., deps=False) for pyrxd to avoid transitive metadata fetches during browser bootstrap.
  • pyrxd/__init__.py, pyrxd/glyph/__init__.py, and pyrxd/curve.py rewritten to use lazy PEP 562 __getattr__ re-exports. Importing pyrxd.glyph.inspect no longer drags in coincurve, aiohttp, or websockets — both a Pyodide enabler and a startup-cost win for narrow callers.
  • Manifest filename validated as a bare basename (rejects path traversal, dot-only names, and URL-encoded separators) before use. CSP no longer allows PyPI as a script source. CLI outpoint rendering sanitized against terminal control-character injection. Manifest emit hardened against shell heredoc injection.
  • CBOR mime_type field capped at 256 chars at parse time — bounds an otherwise-unbounded user-controlled string before it reaches metadata renderers.

Fixed

  • pyrxd glyph inspect transfer-ft previously passed bytes where hex string was expected; corrected.
  • Python 3.10 compatibility for the CLI: tomli fallback for tomllib (3.11+).
  • _select_ripemd160 exception handling widened so OpenSSL variants raising ValueError (not just the documented UnsupportedDigestmodError) fall through to the pure-Python implementation cleanly.

Documentation

  • docs/solutions/runtime-errors/dmint-v1-classifier-gap.md written from the live RBG test that surfaced the V1/V2 split.
  • docs/research/glyphs-on-radiant.md — explains why Radiant FTs are on-chain (not just metadata), with fuzzing strategy.

Tooling

  • Poetry version pinned in CI workflows.
  • OSSF Scorecard residual-risk decisions documented.
  • PyPI publishing automated via Trusted Publishing (no long-lived tokens).

0.3.0 — 2026-05-04

Breaking changes

  • Default BIP44 coin type is now 512 (Radiant per SLIP-0044), not 236 (Bitcoin SV). Wallets created with 0.2.0 derive at m/44'/236'/0'/...; the same mnemonic in 0.3.0 derives at m/44'/512'/0'/... and produces different addresses. To recover funds from a 0.2.0 install, set RXD_PY_SDK_BIP44_DERIVATION_PATH="m/44'/236'/0'", or pass coin_type=236 to HdWallet (see new per-instance kwarg below). See docs/research/wallet-derivation-paths.md for the full migration story.

Added

CLI

  • New pyrxd console script (pip install pyrxd registers it on PATH).
  • pyrxd wallet new | load | info | export-xpub — create, validate, and inspect HD wallets; account-level xpub export for watch-only use.
  • pyrxd address / pyrxd balance / pyrxd utxos — bare query commands for address derivation, balance, and UTXO listing.
  • pyrxd glyph subcommand group — Glyph protocol operations.
  • pyrxd setup — onboarding walkthrough; probes node + ElectrumX reachability and wallet presence, writes default config.
  • Global flags: --network, --electrumx, --wallet, --config, --json, --quiet, --no-color, --yes, --debug.
  • Typed CLI errors (UserError, NetworkBoundaryError, WalletDecryptError) with stable exit codes and a static decrypt message that never echoes user input.

HD wallet

  • HdWallet(coin_type=...) per-instance kwarg overrides the default derivation path without touching env state.
  • HdWallet.send / HdWallet.send_max — key-aware UTXO collection and signed-transaction construction.
  • Load-time path validation against the wallet record's stored derivation path.

⚠️ Downgrade hazard introduced here. Once 0.3.0 writes a wallet file with a coin_type annotation, downgrading to a pre-0.3.0 version and re-saving the wallet corrupts the stored coin_type while leaving derived keys unchanged. A subsequent upgrade will fail load() validation. Funds are recoverable from the mnemonic but require manual re-creation of the wallet file. Pin all machines to the same pyrxd version. See the README "Upgrading" section for details.

Documentation

  • docs/research/wallet-derivation-paths.md — public research doc on the five-way derivation path fragmentation across the Radiant wallet ecosystem with verified source links.
  • docs/solutions/ convention established for searchable problem/solution documentation.
  • README user-risk disclaimer above Status section.
  • Documentation moved from Read the Docs to GitHub Pages (https://mudwoodlabs.github.io/pyrxd/).

Fixed

  • HdWallet previously ignored the RXD_PY_SDK_BIP44_DERIVATION_PATH env override. Now respected.
  • Cyclic imports between cli.main and the four CLI subcommand modules resolved by registering subcommands explicitly via cli.add_command().
  • pyrxd glyph broadcast summary now surfaces metadata fields.
  • BIP39 empty-passphrase defaults annotated to silence false-positive bandit findings.

Security

  • All 16 GitHub Actions pinned to commit SHAs (no floating tags).
  • Explicit minimum permissions declared in CI and lint workflows.
  • OSSF Scorecard and OSV Scanner workflows added.
  • CodeQL static analysis workflow added.
  • Threat model + red-team checklist documented.
  • --json mnemonic exposure warning documented.
  • bandit added to task lint so security findings fail locally before CI.

Tooling

  • task ci aggregate task + versioned pre-push git hook (scripts/git-hooks/pre-push) + installer for local CI parity.
  • scripts/check-no-private-links.py — link checker that prevents tracked docs from referencing gitignored design docs.
  • ruff replaces flake8 + black for lint and format.
  • Dependabot version updates landed: actions/checkout → 6.0.2, actions/deploy-pages → 5.0.0, actions/upload-pages-artifact → 5.0.0, github/codeql-action → 4.35.3, click → ^8.3, bandit → ^1.9.4, pre-commit → 4.6.0, myst-parser constraint refresh.
  • websockets constraint widened to >=15.0.1, <17.0.0 (was ^16.0.0). pyrxd uses only stable websockets API (connect/send/recv/close/WebSocketException) common to versions 13 through 16, so the upper-bound floor was unnecessarily strict and locked out coexistence with libraries pinned to websockets <=15.0.1 (e.g., solana-py 0.36.x).

0.2.0 — 2026-04-29

Initial public release.

Features

Core

  • Typed primitives at all SDK boundaries: Hex32, Hex20, Txid, Satoshis, SecretBytes, RawTx. Strings and untyped bytes are rejected at the constructor.
  • pyrxd.curve — secp256k1 with coincurve, RFC 6979 deterministic signing, low-s normalization, DER encoding.
  • pyrxd.security — typed errors, RNG, secret-bytes (libsodium-backed SecretBytes for memory hygiene).
  • pyrxd.crypto — symmetric primitives.

Keys and HD wallets

  • PrivateKey / PublicKey with WIF encoding/decoding and address derivation (P2PKH mainnet).
  • BIP32 extended keys (Xprv / Xpub) with hardened/non-hardened derivation.
  • BIP39 mnemonic generation and seed derivation.
  • BIP44 derivation paths (m/44'/236'/0'/... for Radiant).
  • HdWallet with persistent encrypted save/load (AES-CBC keyed by hash of the BIP39 seed) and BIP44 gap-limit address scanning.

Transactions and scripts

  • Transaction / TransactionInput / TransactionOutput — Radiant tx construction, serialization, and txid computation.
  • BIP143-style sighash with Radiant's additional hashOutputHashes field; literal-zero zero-refs in the refsHash component.
  • P2PKH script template + unlock(private_key) for standard signing.
  • Script primitives in pyrxd.script for custom locking/unlocking patterns.
  • SatoshisPerKilobyte fee model.

Glyph protocol

  • GlyphBuilder with prepare_commit, prepare_reveal, prepare_ft_deploy_reveal, prepare_dmint_deploy, prepare_mutable_reveal, prepare_container_reveal, prepare_wave_reveal.
  • GlyphMetadata with V1 and V2 sub-objects (creator, royalty, policy, rights, image+image_ipfs+image_sha256). Canonical CBOR encoding.
  • GlyphInspector — parse Glyph tokens from a transaction's outputs.
  • GlyphScanner — query an address's UTXOs and return Glyph tokens with metadata.
  • FtUtxoSet + build_transfer_tx — conservation-enforcing FT transfers; refuses to build a tx that would create or destroy fungible units.
  • DmintState.from_script — parse a live dMint contract UTXO into a typed state object.
  • verify_sha256d_solution — off-chain PoW verifier matching on-chain semantics.

Network

  • ElectrumXClient — async WebSocket client for ElectrumX servers. Multi-URL failover, transparent reconnect, per-request id correlation.
  • get_balance, get_utxos, get_history, get_transaction, broadcast, get_merkle_proof.
  • script_hash_for_address — derive the ElectrumX script hash from a Radiant address.
  • BtcDataSource — Bitcoin chain reader for cross-chain Gravity flows.

Gravity (cross-chain BTC↔RXD atomic swaps)

  • GravityMakerSession — maker side of a sentinel-artifact-shaped atomic swap.
  • Covenant artifacts in pyrxd.gravity.artifacts (sentinel and legacy variants).
  • SPV-anchored claim and forfeit flows.

Status: mainnet-proven for the sentinel-artifact path. Other covenant variants in this module are experimental.

SPV

  • Block-header verification, merkle-proof verification, partial-merkle parsing.
  • Header chain tip tracking.

Examples

  • examples/glyph_mint_demo.py — end-to-end Glyph NFT mint.
  • examples/ft_deploy_premine.py — FT deploy with full premine to one address.
  • examples/gravity_*.py — Gravity Protocol cross-chain demos.

Quality

  • 2,000+ tests across unit, property-based (hypothesis), and integration suites.
  • CBOR cross-decoder tests against an independent reference decoder (RXinDexer).
  • Frozen golden vectors for CBOR encoding determinism and ECDSA RFC 6979 signing.
  • mypy --strict clean on src/.
  • ruff clean on the codebase.

Documentation

  • docs/DMINT_RESEARCH.md — premine vs PoW dMint scope.
  • docs/DMINT_RESEARCH.md — Photonic Wallet TS reverse engineering.
  • docs/DMINT_RESEARCH.md — decoded live mainnet dMint contracts.

Known limitations

  • dMint PoW-based distributed FT mint not implemented. Premine-at-deploy works via prepare_ft_deploy_reveal. PoW commit/reveal + ASERT/LWMA difficulty adjustment is documented as future work in docs/DMINT_RESEARCH.md. Premine-only consumers do not need it.
  • Gravity covenant variants beyond sentinel-artifact are experimental and have not been audited.
  • No third-party security audit yet. Use at your own risk in production.