feat(pairing): slim the spire seed + carry lnbits_npub (bitspire-#70)
Some checks failed
ci.yml / feat(pairing): slim the spire seed + carry lnbits_npub (bitspire-#70) (pull_request) Failing after 0s
Some checks failed
ci.yml / feat(pairing): slim the spire seed + carry lnbits_npub (bitspire-#70) (pull_request) Failing after 0s
Mint the new-shape seed the bitspire consumer now expects: the pubkey rides
once as spire_npub (consumer derives the hex + reconstructs bunker_url from
bunker_secret + bunker_relay|relays[0]), and lnbits_npub is embedded so a paired
machine reaches this lnbits' nostr-transport with nothing else provisioned.
- build_seed_url emits {spire_npub, lnbits_npub, bunker_secret, relays} and
bunker_relay only when it differs from relays[0] (omitted in the common case).
Drops spire_pubkey + the full bunker_url from the payload.
- pair_spire reads settings.nostr_transport_public_key, hex_to_npub's it, and
raises PairingError when it's empty (transport not running → can't mint a
self-sufficient seed). bunker_url is still returned in PairResult for operator
display / audit; only the seed stops embedding it.
Consumer side: bitspire packages/nostr-client/src/seed.ts + the #70 machine
wiring. Kept as v: 1 (redefined in place; no shipped seed to preserve).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b93e3fb698
commit
9dc4d09973
2 changed files with 105 additions and 21 deletions
|
|
@ -17,6 +17,7 @@ from datetime import datetime, timezone
|
|||
|
||||
import pytest
|
||||
from lnbits.core.services.nsec_bunker import NsecBunkerError
|
||||
from lnbits.settings import settings
|
||||
from lnbits.utils.nostr import hex_to_npub
|
||||
|
||||
from ..models import Machine
|
||||
|
|
@ -35,11 +36,23 @@ from ..pairing import (
|
|||
_NOW = datetime(2026, 6, 16, tzinfo=timezone.utc)
|
||||
_SPIRE_HEX = "522a4538f1df96508d9ee8b14072344dd4a566acfe03c25a92a39179c6fca891"
|
||||
_SPIRE_NPUB = hex_to_npub(_SPIRE_HEX)
|
||||
_LNBITS_HEX = "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
|
||||
_LNBITS_NPUB = hex_to_npub(_LNBITS_HEX)
|
||||
_RELAYS = ["wss://lnbits.demo.aiolabs.dev/nostrrelay/demo"]
|
||||
_BUNKER_RELAY = "wss://bunker.internal/relay"
|
||||
_PASSPHRASE = "keystore-pass" # pragma: allowlist secret
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _set_transport_pubkey():
|
||||
# pair_spire reads this lnbits' nostr-transport server pubkey to embed
|
||||
# lnbits_npub in the seed (bitspire#70). Set it for every test; restore after.
|
||||
prev = settings.nostr_transport_public_key
|
||||
settings.nostr_transport_public_key = _LNBITS_HEX
|
||||
yield
|
||||
settings.nostr_transport_public_key = prev
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear_policy_cache():
|
||||
# lnbits' ensure_policy caches resolved policy ids on
|
||||
|
|
@ -157,18 +170,28 @@ def test_bunker_url_carries_pubkey_relay_secret():
|
|||
assert "secret=topsecret" in result.bunker_url
|
||||
|
||||
|
||||
def _decode_seed(seed_url: str) -> dict:
|
||||
assert seed_url.startswith(SEED_URL_SCHEME)
|
||||
blob = seed_url[len(SEED_URL_SCHEME) :]
|
||||
return json.loads(base64.urlsafe_b64decode(blob + "=" * (-len(blob) % 4)))
|
||||
|
||||
|
||||
def test_seed_url_decodes_to_contract():
|
||||
# _pair passes an explicit bunker_relay distinct from relays[0], so it's
|
||||
# carried; the pubkey rides once as spire_npub, lnbits_npub is embedded,
|
||||
# and neither spire_pubkey nor bunker_url appears (bitspire#70).
|
||||
result = _pair(FakeBunker(token_secret="zzz")) # pragma: allowlist secret
|
||||
assert result.seed_url.startswith(SEED_URL_SCHEME)
|
||||
blob = result.seed_url[len(SEED_URL_SCHEME) :]
|
||||
payload = json.loads(base64.urlsafe_b64decode(blob + "=" * (-len(blob) % 4)))
|
||||
payload = _decode_seed(result.seed_url)
|
||||
assert payload == {
|
||||
"v": 1,
|
||||
"spire_npub": _SPIRE_NPUB,
|
||||
"spire_pubkey": _SPIRE_HEX,
|
||||
"bunker_url": result.bunker_url,
|
||||
"lnbits_npub": _LNBITS_NPUB,
|
||||
"bunker_secret": "zzz",
|
||||
"relays": _RELAYS,
|
||||
"bunker_relay": _BUNKER_RELAY,
|
||||
}
|
||||
assert "spire_pubkey" not in payload
|
||||
assert "bunker_url" not in payload
|
||||
|
||||
|
||||
def test_fresh_policy_adds_kindless_nip44_rules():
|
||||
|
|
@ -266,14 +289,40 @@ def test_missing_relay_or_passphrase_raises():
|
|||
def test_build_seed_url_roundtrip():
|
||||
url = build_seed_url(
|
||||
spire_npub=_SPIRE_NPUB,
|
||||
spire_pubkey_hex=_SPIRE_HEX,
|
||||
bunker_url="bunker://x?relay=r&secret=s",
|
||||
lnbits_npub=_LNBITS_NPUB,
|
||||
bunker_secret="s", # pragma: allowlist secret
|
||||
relays=_RELAYS,
|
||||
bunker_relay=_BUNKER_RELAY,
|
||||
)
|
||||
blob = url[len(SEED_URL_SCHEME) :]
|
||||
payload = json.loads(base64.urlsafe_b64decode(blob + "=" * (-len(blob) % 4)))
|
||||
assert payload["spire_pubkey"] == _SPIRE_HEX
|
||||
payload = _decode_seed(url)
|
||||
assert payload["spire_npub"] == _SPIRE_NPUB
|
||||
assert payload["lnbits_npub"] == _LNBITS_NPUB
|
||||
assert payload["bunker_secret"] == "s"
|
||||
assert payload["relays"] == _RELAYS
|
||||
assert payload["bunker_relay"] == _BUNKER_RELAY
|
||||
|
||||
|
||||
def test_build_seed_url_omits_default_bunker_relay():
|
||||
# bunker_relay omitted when it equals relays[0] (or is None) — the consumer
|
||||
# defaults it to relays[0], so carrying it would be redundant bytes.
|
||||
for same in (_RELAYS[0], None):
|
||||
payload = _decode_seed(
|
||||
build_seed_url(
|
||||
spire_npub=_SPIRE_NPUB,
|
||||
lnbits_npub=_LNBITS_NPUB,
|
||||
bunker_secret="s", # pragma: allowlist secret
|
||||
relays=_RELAYS,
|
||||
bunker_relay=same,
|
||||
)
|
||||
)
|
||||
assert "bunker_relay" not in payload
|
||||
|
||||
|
||||
def test_pair_missing_transport_pubkey_raises():
|
||||
# No transport server pubkey → can't mint a self-sufficient seed.
|
||||
settings.nostr_transport_public_key = ""
|
||||
with pytest.raises(PairingError, match="transport"):
|
||||
_pair(FakeBunker())
|
||||
|
||||
|
||||
def test_pair_threads_duration_hours():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue