mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
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:
@@ -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>`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user