From 22678dfb4fd7c25644dbf996ae81e7feb1d8d81e Mon Sep 17 00:00:00 2001 From: Padreug Date: Thu, 18 Jun 2026 19:27:29 +0200 Subject: [PATCH] feat(pairing): authorize kind-22242 (NIP-42 AUTH) in spire policy (#52) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bitspire#52 consumer review (2026-06-18) enumerated the kinds the spire signs as its OWN identity and found NIP-42 relay AUTH (kind 22242) missing from SPIRE_POLICY_RULES — a silent bunker reject the moment a relay challenges with AUTH. It must be bunker-signed (AUTH proves control of spire_pubkey, which only the bunker holds; can't use the local client_nsec). Adds 22242. Records the confirmed set in the policy comment: live = 21000 + 30078 + 22242; CLINK 21001-21003 dormant but kept; nip04 unused (v1 path is dead code). New test locks the required-kinds contract so 22242 can't silently regress. Co-Authored-By: Claude Opus 4.8 (1M context) --- pairing.py | 17 +++++++++++------ tests/test_pairing.py | 11 +++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pairing.py b/pairing.py index b65b760..8629de9 100644 --- a/pairing.py +++ b/pairing.py @@ -57,20 +57,25 @@ SEED_URL_SCHEME = "spire-seed:v1:" # Policy granted to every spire's connect token. Scoped to exactly what a # bitSpire signs as itself: # - 21000 nostr-transport cash RPC envelope to lnbits -# - 21001-21003 CLINK Offer / Debit / Manage (payment flow) +# - 22242 NIP-42 relay AUTH — the spire authenticates to its relays +# (must be bunker-signed: AUTH proves control of spire_pubkey, +# which only the bunker holds; can't be done with client_nsec) +# - 21001-21003 CLINK Offer / Debit / Manage (dormant on dev; kept) # - 30078 NIP-78 beacon + bitspire-cassettes-state hello-event # Kind-scoped rules go in create_new_policy; kind-less methods (nip44, for # encrypting cassette-state to the operator) are added via add_policy_rule # because nsecbunkerd's create_new_policy chokes on null `kind` -# (rule.kind.toString()). Mirrors lnbits' DEFAULT_POLICY_* split. +# (rule.kind.toString()). Mirrors lnbits' DEFAULT_POLICY_* split. nip04 is +# deliberately absent — the v1/nip04 path is dead code (bitspire#52). # -# NOTE (reconcile when bitspire#52 lands): confirm this kind set against the -# spire's actual createSignedEvent / finalizeEvent call sites. Over-granting -# here only widens what a spire may sign *as its own key* — low blast radius — -# but under-granting makes the bunker reject the spire's events. +# Kind set confirmed against the spire's signing sites in bitspire#52 +# (2026-06-18): live = 21000 + 30078 + 22242; CLINK 21001-21003 dormant but +# kept; nip04 unused. Under-granting = silent bunker reject, so err toward +# inclusion (low blast radius — only widens what a spire signs as its OWN key). SPIRE_POLICY_NAME = "spirekeeper-spire" SPIRE_POLICY_RULES = [ {"method": "sign_event", "kind": 21000}, + {"method": "sign_event", "kind": 22242}, # NIP-42 relay AUTH (bitspire#52) {"method": "sign_event", "kind": 21001}, {"method": "sign_event", "kind": 21002}, {"method": "sign_event", "kind": 21003}, diff --git a/tests/test_pairing.py b/tests/test_pairing.py index 4deb5a0..54f00ca 100644 --- a/tests/test_pairing.py +++ b/tests/test_pairing.py @@ -305,3 +305,14 @@ def test_revoke_spire_maps_bunker_error(): bunker.revoke_key_user = _boom with pytest.raises(PairingError, match="revoke"): asyncio.run(revoke_spire(_machine(), admin_client=bunker)) + + +def test_policy_authorizes_required_signing_kinds(): + # Kinds the spire signs as its OWN identity, confirmed against the + # consumer signing sites in bitspire#52 (2026-06-18). A missing kind is a + # silent bunker reject. 22242 = NIP-42 relay AUTH (must be bunker-signed — + # it proves control of spire_pubkey). nip04 stays out (v1 path is dead). + kinds = {r["kind"] for r in SPIRE_POLICY_RULES if r["method"] == "sign_event"} + assert {21000, 30078, 22242} <= kinds + assert "nip04_encrypt" not in SPIRE_POLICY_METHODS_NO_KIND + assert "nip04_decrypt" not in SPIRE_POLICY_METHODS_NO_KIND