Plings Keygen Menu (plings_keygen.py) — Design Spec
Plings Keygen Menu (plings_keygen.py) — Design Spec
Created: Sat 13 Jun 2026 10:40:40 CEST Document Version: 1.0 - Initial design spec Security Classification: Internal Technical Documentation Target Audience: Backend Developers Author: Paul Wisén
Problem
Generating keys/tags today requires long, error-prone CLI invocations, e.g.:
poetry run python scripts/derive_path_keys.py \
--path "3.1.2.1" --decimal-path --quantity 100 \
--master-key keys/dev/dev_key.json --csv-output keys/dev/pauls_1-100st.csv
The author wants to stop typing commands. This spec defines an interactive, menu-driven front-end that wraps the existing derivation logic and the master-key scripts, plus a URL-validation helper.
Scope
In scope (this iteration):
- Generate keys/tags — friendly wrapper around
HDPathKeyDerivation(single + batch + batch-from-instance), CSV output. - Master-key management — create new / import existing, wrapping the existing scripts.
- Validate a URL — paste a complete
s.plings.ioURL and verify it against a selected master key.
Out of scope (YAGNI for now):
- Editing/deleting existing batches.
- Any database writes (no Supabase/Neo4j integration — this is pure local key derivation).
- Anything beyond wrapping the existing master-key scripts.
Architecture & file layout
- New entrypoint:
scripts/plings_keygen.py, run withpoetry run python scripts/plings_keygen.py. - Reuse, don’t duplicate: imports
HDPathKeyDerivationandload_master_keyfromscripts/derive_path_keys.py, andPlingsPathUtils/HDPathUtils/CryptoUtilsfromscripts/utils/. No cryptographic logic is reimplemented. - Shared CSV writer: the CSV-writing block currently inline in
derive_path_keys.main()is extracted intoscripts/utils/output_utils.py(write_keys_csv(keys, path)), so both the CLI and the menu use one implementation.derive_path_keys.pyis updated to call it (behaviour-preserving — same columns:shortcode, decimal_path, base58_path, url). - TUI library:
questionaryfor prompts (select / text / confirm),richfor summary tables and coloured output.
Dependencies
- Add
questionaryandrichto a dev-only group inpyproject.toml([tool.poetry.group.dev.dependencies]). - Do NOT add them to
requirements.txt— Vercel’s serverless build readsrequirements.txt, and this is a local-only tool that never runs on Vercel. Keeping the deploy manifest clean is a requirement.
Main menu
A questionary select loop:
1) Generera nycklar / taggar
2) Master-nycklar → (Skapa ny / Importera befintlig)
3) Validera en URL
4) Avsluta
Flow: Generate keys/tags
- Select master key — auto-discover
keys/**/*.json, present a pick-list showing relative path +wallet_version. (Master-key files are recognised by the presence ofprivate_key+chain_codekeys; the history file and CSVs are excluded.) - What to generate:
- From history — pick a remembered entry (label + decimal path); the menu pre-fills start instance =
last_instance + 1. - New — enter a label and a decimal path (e.g.
3.1.2.1). Path is validated viaPlingsPathUtils; on error, re-prompt.
- From history — pick a remembered entry (label + decimal path); the menu pre-fills start instance =
- Quantity and start instance (start defaults to the history-suggested value, else
1). - Class pointer — optional; default none (matches existing batches whose URLs have no
cp=). - Confirm summary — show selected master key, decimal + base58 path, instance range, and target file;
questionary.confirmbefore deriving. - Derive → validate → write CSV using
HDPathKeyDerivation+write_keys_csv. Output path:keys/<env>/<label>_<start>-<end>st.csv, where<env>is the directory family of the chosen master key (e.g.keys/dev/). Never overwrite an existing file without an explicit confirm (offer overwrite or auto-suffix). - Update history (
label → {path_decimal, last_instance=end, master_key, updated_at}). - Show summary —
richtable: count, output file, first few rows.
Flow: Master keys
Thin wrappers reusing the existing scripts’ classes directly (both are importable, not CLI-only):
- Create new — prompt
wallet_version, dev/production, output filename under the chosenkeys/<env>/; usesMasterKeyGenerator(generate_master_key.py):MasterKeyGenerator(production).generate_master_key(wallet_version)+save_master_key(). - Import existing — uses
MasterKeyImporter(import_master_key.py) to import a pasted base58/JSON key and save it underkeys/<env>/.
Flow: Validate a URL
- Paste a complete URL, e.g.
https://s.plings.io?t=q&i=<instance_key>&p=<base58_path>&cp=<cp>. - Parse query params:
i(instance_key),p(path),cp(optional class pointer),t(transport — informational only). - Select a master key.
- Re-derive the key for path
pviaHDPathKeyDerivation.derive_path_key. - Compare the derived
instance_keyto the URL’si=. Report:- ✅/❌ instance-key match
- decoded decimal path, wallet version, short_code
- whether
cpmatches the supplied class pointer (if any)
This verifies a physical tag’s URL truly derives from the selected master key (tamper / wrong-key detection).
History file
- Location:
keys/.keygen_history.json(added to.gitignore). - Format:
{ "pauls": { "path_decimal": "3.1.2.1", "last_instance": 100, "master_key": "keys/dev/dev_key.json", "updated_at": "2026-06-13T08:00:00+00:00" } } - Read at startup; written after each successful generation. Corrupt/missing file → treated as empty, never fatal.
Error handling & safety
- Invalid path / URL / master-key JSON → clear message + re-prompt; no stack-trace crash.
Ctrl-C/ EOF → graceful exit.- Output CSVs contain only public data (shortcode, path, URL,
instance_key= public Solana address) — no private keys — so default file perms are acceptable. Master-key files remainchmod 600(unchanged). - Generation is deterministic (BIP32): same master key + path + instance ⇒ identical output (already verified: regenerating
keys/dev/pauls_1-100st.csvreproduces it byte-for-byte).
Testing strategy
The interactive TUI layer is thin and verified manually. The non-interactive core is factored into testable functions and covered in tests/:
- Filename builder —
(label, start, end) → "<label>_<start>-<end>st.csv". - History round-trip — write then read returns the same structure; corrupt file → empty.
- URL validator — a known-good URL (from
pauls_1-100st.csv) validates as ✅; a tamperedi=validates as ❌. - Determinism regression — regenerating
pauls_1-100st.csvfromdev_key.jsonmatches the committed file.
Resolved details
- Reuse is confirmed:
HDPathKeyDerivation+load_master_key(derive_path_keys.py),MasterKeyGenerator(generate_master_key.py), andMasterKeyImporter(import_master_key.py) are all importable classes/functions — no subprocess wrapping needed. <env>inference: use the immediate parent directory of the chosen master-key file (e.g.keys/dev/dev_key.json→keys/dev/). Output CSVs and any new master keys are written into that same directory. If a master key sits directly underkeys/, fall back to writing alongside it.