test(v2): re-wire bitspire cross-test fixture for v1.1 positions-keyed shape (#29 v1.1)

Replaces the v1 fixture (denominations-keyed, 2026-05-30T13:15Z) with
bitspire's v1.1 fixture (positions-keyed, 2026-05-30T19:00Z log entry).
Drops the class-level @pytest.mark.skip from commit 1cebefc.

The v1.1 fixture intentionally includes two positions (1 + 2) both
holding denomination=20 — exercises the multi-same-denomination round-
trip end-to-end through encrypt → wire → decrypt → payload-validate.
Pinned explicitly in test_decrypts_bitspire_sample_event:

    assert payload["positions"]["1"]["denomination"] == 20
    assert payload["positions"]["2"]["denomination"] == 20
    assert payload["positions"]["1"]["count"] != payload["positions"]["2"]["count"]

So a future "fix" that re-introduces denom-uniqueness validation
surfaces at this test instead of as a runtime rejection on real
machines (the v1.1 operational case from coord-log 18:45Z).

Other two cross-tests (test_signature_verifies_via_lnbits_helper,
test_encrypt_round_trip_via_our_impl_decrypts_with_their_keys) carry
over from v1 unchanged — same shape, just the new fixture's keys +
event flow through them.

Total: 151 passed, 0 skipped, 1 pre-existing async-plugin failure
unchanged. PR #30 is now byte-compat-verified end-to-end with the
v1.1 wire shape; bitspire side is typecheck-green + about to cache-
push (per coord-log 20:55Z). Joint re-smoke on Sintra is the
remaining v1.1 step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-30 22:43:12 +02:00
commit 4b128ca53c

View file

@ -243,81 +243,77 @@ class TestPaddingFormula:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Bitspire-side fixture, posted to ~/dev/coordination/log.md at 2026-05-30T13:15Z. # Bitspire-side v1.1 fixture, posted to ~/dev/coordination/log.md at
# Throwaway keypairs (one-shot, never sign anything else) — safe to embed verbatim. # 2026-05-30T19:00Z. Positions-keyed wire shape per the v1.1 redesign
# Generated by apps/machine/src/services/operator-config.ts-shape code path using # (18:30Z + 18:45Z); intentionally includes two positions sharing
# the @bitSpire/nostr-client encryptContentV2 + createSignedEvent helpers (same # denomination=20 to exercise the multi-same-denom round-trip on our
# code the production bootstrap publish uses). Round-tripped on bitspire side # decrypt + payload-validate path. Throwaway keypairs (one-shot, never
# before posting. # sign anything else) — safe to embed verbatim.
# Generated by apps/machine/src/services/operator-config.ts-shape code
# path using the @bitSpire/nostr-client encryptContentV2 +
# createSignedEvent helpers (same code the production bootstrap publish
# uses). Round-tripped on bitspire side via decryptContentV2 before posting.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
_BITSPIRE_FIXTURE = { _BITSPIRE_FIXTURE = {
"atm_keypair": { "atm_keypair": {
"privkey_hex": ( "privkey_hex": (
"a1601b05967cb421056f197008eca1dfa61f0eb5b505c277a0d4ca6b053e91f2" "814e6188d017102bbf301ba5b38fba95b2556dc79a60df4cd50605c4593578e6"
), ),
"pubkey_hex": ( "pubkey_hex": (
"8db588b6431edbbc0c4f7517bc90447cec34c866b7110e63c88e20a4cccd0e5c" "217bdc9a65b571c4d9b59da6227a7aa6ca5bbfd5280af791417c57a79d92852b"
), ),
}, },
"operator_keypair": { "operator_keypair": {
"privkey_hex": ( "privkey_hex": (
"216030bdda5aa47c37b74117bc29612bfc18d8122f70e80cb7a6d875c8699108" "cca7dd9fe4874f6b9f3f3fae21648da686b7e714bfd4786e8fa8745933fd3185"
), ),
"pubkey_hex": ( "pubkey_hex": (
"052f27837c3c46b5086825805b8d061ed64346e61cd0c3013725e544aa2a0b49" "49bd8e615769f8b6a5aa8ce9617b919996abecf234599ba196789461cf239146"
), ),
}, },
"expected_plaintext": { "expected_plaintext": {
"denominations": { "positions": {
"20": {"position": 1, "count": 49}, "1": {"denomination": 20, "count": 49},
"50": {"position": 2, "count": 100}, "2": {"denomination": 20, "count": 38},
"3": {"denomination": 50, "count": 100},
}, },
}, },
"event": { "event": {
"kind": 30078, "kind": 30078,
"content": ( "content": (
"AgUSQOlYyF7JomOKqJSyAOF/O7yR1d2DYgXvXUS7sBMqRbKPM+ACmkT/R6owFd22nRf2" "AqOHsCcjN2W8L/Cx0uH+n++VA13W+wy7z1EcuuNX49sSagelX2lI0HEKyd+ActOc"
"k+KEibEi+WcK6+acBwy1ThWP2NHUlrMp8qjUYrV1XXJXwRLOlLBe0LHmioFi6jTyJxSE" "iaPsHrp9ecJTkEZOD86ioldbLbEVColJwK4g1uVZSbpDeqRe+97woxVDqPnzj507"
"/Z+z79o7wki60CKDoNZqSRiScRN0lT7tzEgsFXo2vFzPdzEQwy/jk154DgBoCiRIRjtX" "tFaVLF/dRmda+oKHUzkVPhE4PHQJzp9Fqji38J3nU6N68qo7KOt3qg1nSy5eDfAu"
"kBNGGlN9ABPPfw==" "zt7djRBx63+/veub0rWTMMQLBgci8+Ms6Y+Zb1mki3L6NWuIR0Or+8DhcD+ZJiOu"
"WTcx"
), ),
"tags": [ "tags": [
[ [
"d", "d",
"bitspire-cassettes-state:" "bitspire-cassettes-state:"
"8db588b6431edbbc0c4f7517bc90447cec34c866b7110e63c88e20a4cccd0e5c", "217bdc9a65b571c4d9b59da6227a7aa6ca5bbfd5280af791417c57a79d92852b",
], ],
[ [
"p", "p",
"052f27837c3c46b5086825805b8d061ed64346e61cd0c3013725e544aa2a0b49", "49bd8e615769f8b6a5aa8ce9617b919996abecf234599ba196789461cf239146",
], ],
], ],
"created_at": 1780156459, "created_at": 1780173222,
"pubkey": ( "pubkey": (
"8db588b6431edbbc0c4f7517bc90447cec34c866b7110e63c88e20a4cccd0e5c" "217bdc9a65b571c4d9b59da6227a7aa6ca5bbfd5280af791417c57a79d92852b"
), ),
"id": ( "id": (
"28e2bd428bca5b522c037d06e962f5c2ed2e40c398f7ecf84ed5f6272ab77ae4" "72c09f333386dd4ad6125f8c69823824eea50d8091b694458bcd60701517eece"
), ),
"sig": ( "sig": (
"8bbde91fb39cfe7026384ca89843b3f9aaf5b9a9a90ddc20e09bc056721438b2" "07ecafacf0169f074e564a999ee1c31446930b43391d007c4a1f9ef7ad890d6c"
"9d032435e71bb16a5ac211c951de02d8e2f5422d9ee110653f6e3df72238f6dd" "2aa6e3ecc5318edeb5748fbd64c7ca33407099a97154e2ff7e0c626e48d71925"
), ),
}, },
} }
@pytest.mark.skip(
reason=(
"v1.1 wire-shape pivot (coord-log 2026-05-30T18:30Z + 18:45Z): the "
"13:15Z fixture above is encoded with the old `{denominations: ...}` "
"wire shape; the v1.1 wire shape is `{positions: {<pos>: "
"{denomination, count}}}`. Bitspire will post a fresh fixture against "
"the v1.1 shape; once posted, swap `_BITSPIRE_FIXTURE` for the new "
"JSON and drop this skip (v1.1 commit g on the PR)."
)
)
class TestBitspireCrossTest: class TestBitspireCrossTest:
"""Byte-compat cross-test between our hand-rolled NIP-44 v2 (`nip44.py`) """Byte-compat cross-test between our hand-rolled NIP-44 v2 (`nip44.py`)
and the nostr-tools NIP-44 v2 impl that bitspire uses on the ATM side and the nostr-tools NIP-44 v2 impl that bitspire uses on the ATM side
@ -328,8 +324,9 @@ class TestBitspireCrossTest:
def test_decrypts_bitspire_sample_event(self): def test_decrypts_bitspire_sample_event(self):
"""The load-bearing assertion: our `decrypt_from` recovers the """The load-bearing assertion: our `decrypt_from` recovers the
expected `{"denominations": {...}}` plaintext from bitspire's expected `{"positions": {...}}` plaintext from bitspire's encrypted
encrypted event content.""" event content. v1.1 fixture intentionally exercises the multi-same-
denomination round-trip (positions 1 + 2 both hold $20)."""
import json import json
event = _BITSPIRE_FIXTURE["event"] event = _BITSPIRE_FIXTURE["event"]
@ -342,7 +339,16 @@ class TestBitspireCrossTest:
operator_privkey, operator_privkey,
event["pubkey"], event["pubkey"],
) )
assert json.loads(plaintext) == _BITSPIRE_FIXTURE["expected_plaintext"] payload = json.loads(plaintext)
assert payload == _BITSPIRE_FIXTURE["expected_plaintext"]
# v1.1 invariant: two positions can carry the same denomination.
# Pin it explicitly so a future "fix" that re-introduces denom-
# uniqueness validation surfaces here instead of as a runtime
# rejection on real machines.
assert payload["positions"]["1"]["denomination"] == 20
assert payload["positions"]["2"]["denomination"] == 20
assert payload["positions"]["1"]["count"] != payload["positions"]["2"]["count"]
def test_signature_verifies_via_lnbits_helper(self): def test_signature_verifies_via_lnbits_helper(self):
"""Optional extra per bitspire's 13:15Z note (3). The consumer """Optional extra per bitspire's 13:15Z note (3). The consumer