From c503af0f5a67f20207e6b81eae0903544740ebe8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 23:21:30 +0000 Subject: [PATCH] docs: fix stale signer, NIP-19, NIP-44, and EventStore claims in skills Second audit pass over the remaining skills (amy-expert, auth-signers, find-*, nostr-expert, quartz-integration, vendored technique skills), verifying every concrete claim against the code: - auth-signers: bunker login goes through NostrSignerRemote.fromBunkerUri + connect(), not the nonexistent RemoteSignerManager.connect(url) - nostr-expert: NIP count 57 -> 80+; replace invented Nip44v2/Nip19 static APIs with the real Nip44 facade, ByteArray bech32 extensions, entity create() helpers, and Nip19Parser.uriToRoute()?.entity - nip-catalog: heading counts corrected to 87 standard + 23 experimental packages with a ground-truth pointer - quartz-integration: NIP-19 example rewritten for ParseReturn.entity; Event Store is commonMain (all platforms), not Android-only, with the real store.sqlite.EventStore import and suspend query API amy-expert, find-missing-translations, find-non-lambda-logs, the rest of auth-signers, and the vendored technique skills audited clean. https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn --- .claude/core-skills-plan.md | 19 +++++ .claude/skills/auth-signers/SKILL.md | 2 +- .claude/skills/nostr-expert/SKILL.md | 76 ++++++++----------- .../nostr-expert/references/nip-catalog.md | 6 +- .claude/skills/quartz-integration/SKILL.md | 48 +++++++----- 5 files changed, 84 insertions(+), 67 deletions(-) diff --git a/.claude/core-skills-plan.md b/.claude/core-skills-plan.md index 65b10c8047..1af746f6dd 100644 --- a/.claude/core-skills-plan.md +++ b/.claude/core-skills-plan.md @@ -66,3 +66,22 @@ handles them natively; stale references fixed: `quartz-integration` and `nostr-expert` cover its pointers). - Stop hook moved to `.claude/hooks/stop-spotless.sh` and gated on modified Kotlin files, so Q&A-only turns no longer pay a Gradle invocation. + +Second audit pass (every concrete claim checked against the code; `amy-expert`, +`find-missing-translations`, `find-non-lambda-logs`, and the vendored technique +skills verified clean): + +- `auth-signers`: bunker login entry point corrected — `NostrSignerRemote.fromBunkerUri(...)` + + `connect()`, not the nonexistent `RemoteSignerManager.connect(url)`. +- `nostr-expert`: NIP count 57 → 80+ packages; `Nip44v2.encrypt/decrypt` + static-object snippet replaced with the real `Nip44` facade + (returns `EncryptedInfo`, `encodePayload()` for event content); invented + `Nip19.npubEncode`/`Nip19Result` API replaced with the real `ByteArray` + extensions (`toNpub()`, …), entity `create()` helpers, and + `Nip19Parser.uriToRoute()?.entity`. +- `nostr-expert/references/nip-catalog.md`: heading count (60+8) replaced with + actual package counts (87 + 23 experimental) and a ground-truth pointer. +- `quartz-integration`: NIP-19 decode example rewritten for + `ParseReturn.entity` (the `Nip19Parser.Return.*` sealed class never existed); + Event Store section corrected from "Android only" to commonMain/all platforms + with the real `store.sqlite.EventStore` import and suspend generic `query`. diff --git a/.claude/skills/auth-signers/SKILL.md b/.claude/skills/auth-signers/SKILL.md index 26b72b1b79..8b11a52d1b 100644 --- a/.claude/skills/auth-signers/SKILL.md +++ b/.claude/skills/auth-signers/SKILL.md @@ -75,7 +75,7 @@ Most feature code should go through `Account`'s mutation methods (`account.sendR Entry points: - **Existing private key** (`nsec`, 32-byte hex, file) → `NostrSignerInternal`. -- **Bunker URL** (`bunker://...`) → `RemoteSignerManager.connect(url)` in `nip46RemoteSigner/signer/RemoteSignerManager.kt` returns a `NostrSignerRemote`. +- **Bunker URL** (`bunker://...`) → `NostrSignerRemote.fromBunkerUri(bunkerUri, localSigner, client)` in `nip46RemoteSigner/signer/NostrSignerRemote.kt` parses the URI and returns a `NostrSignerRemote`; then call its `suspend fun connect()` to perform the NIP-46 handshake. - **Installed external signer app** (Amber, nos2x, etc. on Android) → `ExternalSignerLogin.launch(...)` opens the signer app; approval yields a `NostrSignerExternal`. The UI hosts both flows via `amethyst/.../ui/screen/loggedOff/login/` — look there for `ExternalSignerButton.kt` and the bunker-URL paste screen. diff --git a/.claude/skills/nostr-expert/SKILL.md b/.claude/skills/nostr-expert/SKILL.md index 373b14eb2b..c8c87c0cd7 100644 --- a/.claude/skills/nostr-expert/SKILL.md +++ b/.claude/skills/nostr-expert/SKILL.md @@ -1,6 +1,6 @@ --- name: nostr-expert -description: Nostr protocol implementation patterns in Quartz (AmethystMultiplatform's KMP Nostr library). Use when working with: (1) Nostr events (creating, parsing, signing), (2) Event kinds and tags, (3) NIP implementations (57 NIPs in quartz/), (4) Event builders and TagArrayBuilder DSL, (5) Nostr cryptography (secp256k1, NIP-44 encryption), (6) Relay communication patterns, (7) Bech32 encoding (npub, nsec, note, nevent). Complements nostr-protocol agent (NIP specs) - this skill provides Quartz codebase patterns and implementation details. +description: Nostr protocol implementation patterns in Quartz (AmethystMultiplatform's KMP Nostr library). Use when working with: (1) Nostr events (creating, parsing, signing), (2) Event kinds and tags, (3) NIP implementations (80+ NIP packages in quartz/), (4) Event builders and TagArrayBuilder DSL, (5) Nostr cryptography (secp256k1, NIP-44 encryption), (6) Relay communication patterns, (7) Bech32 encoding (npub, nsec, note, nevent). Complements nostr-protocol agent (NIP specs) - this skill provides Quartz codebase patterns and implementation details. --- # Nostr Protocol Expert (Quartz Implementation) @@ -313,26 +313,24 @@ class LocalSigner(private val privateKey: ByteArray) : ISigner { ### Encryption (NIP-44) ```kotlin -// Modern encryption (ChaCha20-Poly1305) -object Nip44v2 { - fun encrypt(plaintext: String, privateKey: ByteArray, pubKey: HexKey): String - fun decrypt(ciphertext: String, privateKey: ByteArray, pubKey: HexKey): String +// Modern encryption (ChaCha20-Poly1305) via the Nip44 facade +// (nip44Encryption/Nip44.kt — picks the current version, decrypts any) +object Nip44 { + fun encrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): Nip44v2.EncryptedInfo + fun decrypt(payload: String, privateKey: ByteArray, pubKey: ByteArray): String } // Usage -val encrypted = Nip44v2.encrypt( - plaintext = "Secret message", - privateKey = myPrivateKey, - pubKey = recipientPubKey -) +val encrypted = Nip44.encrypt("Secret message", myPrivateKey, recipientPubKey) +val payload = encrypted.encodePayload() // base64 string for event content -val decrypted = Nip44v2.decrypt( - ciphertext = encrypted, - privateKey = myPrivateKey, - pubKey = senderPubKey -) +val decrypted = Nip44.decrypt(payload, myPrivateKey, senderPubKey) ``` +Most code should not call `Nip44` directly — go through +`signer.nip44Encrypt(plaintext, toPublicKey)` / `signer.nip44Decrypt(ciphertext, fromPublicKey)` +so remote/external signers keep working. + **Pattern**: Elliptic curve Diffie-Hellman + ChaCha20-Poly1305 AEAD. ### NIP-04 (Deprecated) @@ -345,44 +343,34 @@ object Nip04 { } ``` -**Note**: Use NIP-44 (Nip44v2) for new implementations. NIP-04 has security issues. +**Note**: Use NIP-44 (`Nip44`) for new implementations. NIP-04 has security issues. ## Bech32 Encoding (NIP-19) +Encoding uses extension functions on `ByteArray` (`nip19Bech32/ByteArrayExt.kt`); +TLV entities carry relay hints via `create()` helpers on the entity classes in +`nip19Bech32/entities/`. Decoding goes through `Nip19Parser`, whose +`uriToRoute()` returns a `ParseReturn?` wrapping the parsed `Entity`. + ```kotlin -object Nip19 { - // Encode - fun npubEncode(pubkey: HexKey): String // npub1... - fun nsecEncode(privateKey: ByteArray): String // nsec1... - fun noteEncode(eventId: HexKey): String // note1... - fun neventEncode(eventId: HexKey, relays: List = emptyList()): String - fun nprofileEncode(pubkey: HexKey, relays: List = emptyList()): String - fun naddrEncode(kind: Int, pubkey: HexKey, dTag: String, relays: List = emptyList()): String +// Encode simple entities: ByteArray extensions +val npub = pubkeyBytes.toNpub() // "npub1..." +val nsec = privKeyBytes.toNsec() // "nsec1..." +val note = eventIdBytes.toNote() // "note1..." - // Decode - fun decode(bech32: String): Nip19Result -} - -sealed class Nip19Result { - data class NPub(val hex: HexKey) : Nip19Result() - data class NSec(val hex: HexKey) : Nip19Result() - data class Note(val hex: HexKey) : Nip19Result() - data class NEvent(val hex: HexKey, val relays: List) : Nip19Result() - data class NProfile(val hex: HexKey, val relays: List) : Nip19Result() - data class NAddr(val kind: Int, val pubkey: HexKey, val dTag: String, val relays: List) : Nip19Result() -} +// Encode TLV entities with relay hints (relays: List) +val nevent = NEvent.create(eventIdHex, authorHex, kind, relays) +val nprofile = NProfile.create(pubkeyHex, relays) ``` **Usage**: ```kotlin -// Encode -val npub = Nip19.npubEncode(pubkeyHex) -// Output: "npub1..." - -// Decode -when (val result = Nip19.decode(npub)) { - is Nip19Result.NPub -> println("Pubkey: ${result.hex}") - is Nip19Result.NEvent -> println("Event: ${result.hex}, relays: ${result.relays}") +// Decode (also accepts nostr: URIs); entity types live in nip19Bech32.entities +when (val entity = Nip19Parser.uriToRoute(input)?.entity) { + is NPub -> println("Pubkey: ${entity.hex}") + is NEvent -> println("Event: ${entity.hex}, relays: ${entity.relay}") + is NAddress -> println("Address: ${entity.aTag()}") + null -> println("not a valid bech32 entity") else -> println("Other type") } ``` diff --git a/.claude/skills/nostr-expert/references/nip-catalog.md b/.claude/skills/nostr-expert/references/nip-catalog.md index 950f2cb1a1..a18731696f 100644 --- a/.claude/skills/nostr-expert/references/nip-catalog.md +++ b/.claude/skills/nostr-expert/references/nip-catalog.md @@ -1,4 +1,8 @@ -# NIP Catalog: 60 Standard + 8 Experimental NIPs in Quartz +# NIP Catalog: Quartz NIP Packages + +As of 2026-06 Quartz has **87 standard `nip*` packages** plus **23 packages +under `experimental/`**. The categorized list below may lag behind — +`ls quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/` is ground truth. ## Standard NIPs by Category diff --git a/.claude/skills/quartz-integration/SKILL.md b/.claude/skills/quartz-integration/SKILL.md index ebc53886f7..3c1293679f 100644 --- a/.claude/skills/quartz-integration/SKILL.md +++ b/.claude/skills/quartz-integration/SKILL.md @@ -447,23 +447,28 @@ val textNote = Event.fromJson(json) as? TextNoteEvent ```kotlin import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser +import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress +import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent +import com.vitorpamplona.quartz.nip19Bech32.entities.NNote +import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile +import com.vitorpamplona.quartz.nip19Bech32.entities.NPub -// Decode any bech32 entity -val result = Nip19Parser.uriToRoute("npub1abc...") -// Returns: NPub | NSec | Note | NEvent | NProfile | NAddr | null - -when (val r = Nip19Parser.uriToRoute(input)) { - is Nip19Parser.Return.NPub -> println("pubkey: ${r.hex}") - is Nip19Parser.Return.Note -> println("event id: ${r.hex}") - is Nip19Parser.Return.NEvent -> println("event: ${r.hex}, relays: ${r.relays}") - is Nip19Parser.Return.NProfile -> println("profile: ${r.hex}") - is Nip19Parser.Return.NAddr -> println("address: ${r.kind}:${r.pubKey}:${r.dTag}") - null -> println("not a valid bech32 entity") - else -> {} +// Decode any bech32 entity (plain or nostr:-prefixed). +// uriToRoute() returns Nip19Parser.ParseReturn? — the parsed Entity is in .entity +when (val entity = Nip19Parser.uriToRoute(input)?.entity) { + is NPub -> println("pubkey: ${entity.hex}") + is NNote -> println("event id: ${entity.hex}") + is NEvent -> println("event: ${entity.hex}, relays: ${entity.relay}") + is NProfile -> println("profile: ${entity.hex}") + is NAddress -> println("address: ${entity.aTag()}") + null -> println("not a valid bech32 entity") + else -> {} } -// The parser also handles nostr: URI scheme -val result = Nip19Parser.uriToRoute("nostr:npub1abc...") +// Encode: ByteArray extensions from nip19Bech32/ByteArrayExt.kt +val npub = pubkeyBytes.toNpub() // also toNsec(), toNote(), ... +// TLV entities with relay hints (relays: List) +val nevent = NEvent.create(eventIdHex, authorHex, kind, relays) ``` --- @@ -601,21 +606,22 @@ In Xcode: drag & drop the `.xcframework` into your project, then use from Swift --- -## 14. Event Store (Android only) +## 14. Event Store (SQLite, all platforms) -SQLite-based storage with full NIP support (NIP-09, NIP-40, NIP-45, NIP-50, NIP-62): +SQLite-backed storage in `commonMain` (JVM, Android, iOS — uses the bundled +androidx.sqlite driver) with full NIP support (NIP-09, NIP-40, NIP-45, NIP-50, +NIP-62). All operations are `suspend`: ```kotlin -import com.vitorpamplona.quartz.nip01Core.store.EventStore -import android.content.Context +import com.vitorpamplona.quartz.nip01Core.store.sqlite.EventStore -val store = EventStore() +val store = EventStore() // default DB file "events.db" // Insert store.insert(event) // Query -val events = store.query( +val events = store.query( Filter(authors = listOf(pubKey), kinds = listOf(1), limit = 50) ) @@ -623,7 +629,7 @@ val events = store.query( val count = store.count(Filter(kinds = listOf(1))) // Full-text search (NIP-50) -val results = store.query(Filter(search = "bitcoin")) +val results = store.query(Filter(search = "bitcoin")) ``` ---