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<T> 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
This commit is contained in:
Claude
2026-06-10 23:21:30 +00:00
parent 8209f4416a
commit c503af0f5a
5 changed files with 84 additions and 67 deletions
+19
View File
@@ -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<T>`.
+1 -1
View File
@@ -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.
+32 -44
View File
@@ -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<String> = emptyList()): String
fun nprofileEncode(pubkey: HexKey, relays: List<String> = emptyList()): String
fun naddrEncode(kind: Int, pubkey: HexKey, dTag: String, relays: List<String> = 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<String>) : Nip19Result()
data class NProfile(val hex: HexKey, val relays: List<String>) : Nip19Result()
data class NAddr(val kind: Int, val pubkey: HexKey, val dTag: String, val relays: List<String>) : Nip19Result()
}
// Encode TLV entities with relay hints (relays: List<NormalizedRelayUrl>)
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")
}
```
@@ -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
+27 -21
View File
@@ -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<NormalizedRelayUrl>)
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<Event>(
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<Event>(Filter(search = "bitcoin"))
```
---