Skip to content

fix(docs): run the Python quickstart offline and make it match the SDK - #200

Merged
truthixify merged 2 commits into
wraith-protocol:developfrom
aratass:docs/188-run-python-quickstart
Oct 18, 2026
Merged

truthixify merged 2 commits into
wraith-protocol:developfrom
aratass:docs/188-run-python-quickstart

Conversation

@aratass

@aratass aratass commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Closes #188.

Summary

Running the quickstart showed that its crypto and RPC code could not work, so this PR fixes the guide and then executes it in CI.

What was broken (each confirmed against vectors from the pinned SDK):

  • _ed25519_point_add computed scalar * G with Ed25519PrivateKey.from_private_bytes(scalar), which hashes and clamps its input. Stealth addresses never matched the SDK, the scanner found 0 of 2 SDK payments, and sign_with_scalar produced signatures that fail ed25519 verification.
  • The view tag hashed the shared secret under the v2 prefix. The v2 tag is SHA-256("wraith:stellar:view-tag:v2:" || R || V)[0] (architecture/view-tag-scanner.mdx, SDK computeAnnouncementViewTag), with no ECDH.
  • fetch_announcements filtered on two-topic events and read the value as a map with an .xdr field. The v2 announcer emits ("announce", scheme_id, view_tag_bucket, metadata_kind) with a (stealth_address, R, metadata) value, and the RPC returns base64 strings. It now pages through getEvents with the cursor.
  • announce() was called with scheme 1. The deployed v2 announcer (scripts/contract-registry.json) asserts scheme_id == 2.
  • Step 6b read simulate_transaction(...).transaction (no such attribute) instead of calling prepare_transaction, and imported SorobanServer from the wrong module. Step 7 called a nonexistent add_signature() and paid out 9 of 10 XLM, below the base reserve. It now appends a DecoratedSignature and merges the stealth account.
  • The page said futurenet, but the contracts and the Step 6 URLs are on testnet. It now targets testnet throughout.

The fixture (scripts/python-quickstart/):

  • run_quickstart.py builds wraith_crypto.py from the # wraith_crypto.py fence (plus # Add to wraith_crypto.py) and runs every other Python fence in page order in one session, so the published code is the code under test. Tracebacks point at MDX lines.
  • Offline: httpx.post answers getLatestLedger and serves recorded v2 announcer events two per getEvents page, so the cursor loop runs. Fakes for stellar_sdk.Server and SorobanServer check the network passphrase, sequence numbers and prepare_transaction before signing, and verify every signature, including the scalar-signed merge in Step 7.
  • vectors.json is generated from the pinned @wraith-protocol/sdk 1.4.5 primitives by generate-vectors.ts (--check in CI): keys, meta-address, stealth addresses, five events (two payments to the reader, two to someone else, one decoy that passes the view tag and must fail the address check), matches with private scalars, and a signWithScalar signature. I also checked the vectors against the SDK's current develop (generateStealthAddress, scanAnnouncements, signWithScalar): identical.
  • Step 6a's random stealth address is checked against an independent libsodium derivation (PyNaCl, already a stellar-sdk dependency).
  • requirements.txt pins the full dependency tree, frozen from a clean Python 3.11 environment. The runner refuses other versions, and --bootstrap builds a venv with the pins under node_modules/.cache.
  • check:snippets-extended, the CI job with Python 3.11, now runs check:python-quickstart. All 9 Python fences run and no no-check remains on the page. A fence can only be skipped if it is marked python Pseudocode no-check.

Verification

  • pnpm check:snippets-extended, check:snippets, check:placeholders, check:nav-coverage, check:contract-registry, check:compatibility-matrix, check:redirects-and-anchors, test:redirects-and-anchors and node localization-qa.cjs pass.
  • The old guide fails the runner at the first module check (stealth address mismatch). Mutations it catches: swapped view tag inputs, scheme 1 in announce, a single-page fetch_announcements, a broken Step 7 import, and no-check on a Python fence.

Summary of the run:

wraith_crypto.py matches the TypeScript SDK on 18 checks
  Step 3 keys and meta-address match the SDK
  Step 4 parsed 5 v2 events over 4 getEvents pages
  Step 5 found the 2 payments the SDK finds, and skipped the decoy
  Step 6a stealth address and view tag match an independent libsodium derivation
  Step 6b createAccount and announce(2, stealth, R, [view tag]) are signed for testnet
  Step 7 merge is signed by sign_with_scalar and verifies against the stealth key

@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@aratass Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@truthixify truthixify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documented Python flow runs fully offline and matches the pinned TypeScript vectors on all 18 checks. Approved.

Running the quickstart against vectors from the SDK showed it could not work:

- scalar * G came from Ed25519PrivateKey.from_private_bytes(), which hashes
  and clamps the input, so stealth addresses never matched the SDK and
  sign_with_scalar produced signatures that do not verify.
- The view tag hashed the shared secret under the v2 prefix. The v2 tag is
  SHA-256("wraith:stellar:view-tag:v2:" || R || V)[0], with no ECDH.
- fetch_announcements filtered on two-topic events and read a map value, but
  the v2 announcer emits four topics and a (stealth, R, metadata) tuple.
  It now pages through getEvents with the cursor.
- announce() used scheme 1; the deployed v2 announcer asserts scheme 2.
- Step 6b used simulate_transaction(...).transaction instead of
  prepare_transaction(). Step 7 called a nonexistent add_signature() and
  left too little XLM for the base reserve; it now merges the account.

The guide now targets testnet, where the contracts live, pins stellar-sdk
16.1.0, cryptography 50.0.2 and httpx 0.28.1, and drops every no-check fence.
scripts/python-quickstart/run_quickstart.py extracts wraith_crypto.py and the
steps from guides/quickstarts/python.mdx and runs them in page order. The RPC,
Horizon and Soroban calls go to fakes that serve recorded v2 announcer events
and verify each transaction's signatures. Results are checked against
vectors.json, generated from the pinned SDK by generate-vectors.ts (--check in
CI), and Step 6a against an independent libsodium derivation.

requirements.txt pins the full dependency tree; --bootstrap builds a venv with
it. check:snippets-extended now runs check:python-quickstart.

Closes wraith-protocol#188
@aratass
aratass force-pushed the docs/188-run-python-quickstart branch from cf4f9bc to 978ba13 Compare October 17, 2026 18:22
@aratass

aratass commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on develop after #199 merged; the package.json scripts and CONTRIBUTING sections from both PRs are kept. check:snippets (with check:api-examples), check:python-quickstart, nav-coverage, contract-registry and redirects-and-anchors pass locally.

@truthixify truthixify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rebased branch remains correct. The pinned offline quickstart ran all six published steps, matched 18 SDK vector checks, paged all five v2 events, rejected the decoy, and verified both transaction signatures.

@truthixify
truthixify merged commit bf2789a into wraith-protocol:develop Oct 18, 2026
7 checks passed
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.

[Wave 10] Execute the Python quickstart instead of skipping its core examples

3 participants