- Updates CLAUDE.md tech stack to current versions (Compose 1.10.3, Kotlin 2.3.20). - Reframes kotlin-multiplatform iOS as mature; adds secp256k1-kmp 0.23.0 references. - Updates desktop-expert Main.kt references (code grew from ~270 to 1341 lines and NavigationRail moved to ui/deck/SinglePaneLayout.kt); replaces obsolete "hardcoded ctrl = true" anti-pattern note with accurate isMacOS branching. - Removes compose-desktop.md (superseded by desktop-expert/). - Adds nostr-expert references: nip19-bech32, event-factory, crypto-and-encryption, large-cache. Adds kotlin-expert/common-utilities, compose-expert/rich-text-parsing, android-expert/image-loading. - New skills: account-state (Account + LocalCache), relay-client (subscriptions, filter assemblers, preloaders), feed-patterns (FeedFilter + FeedViewModel family), auth-signers (NostrSigner across internal / NIP-46 / NIP-55).
4.1 KiB
NIP-19: Bech32 Encoding & Parsing
Quartz implementation for npub, nsec, note, nevent, nprofile, naddr, nrelay, nembed — the user-facing encoded forms of Nostr identifiers.
Layout
All under quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/:
Nip19Parser.kt— the entry point. Parses any Bech32 ornostr:URI into a typedEntity.bech32/Bech32Util.kt— raw Bech32 encode/decode (bits ↔ 5-bit groups).tlv/Tlv.kt/tlv/TlvBuilder.kt— Type-Length-Value codec for composite entities (nevent,nprofile,naddr).TlvTypes.kt— TLV type constants (0 = special payload, 1 = relay, 2 = author, 3 = kind).entities/— one class per entity type (see below).ATagExt.kt,ByteArrayExt.kt,EventExt.kt,ListEntityExt.kt,TlvBuilderExt.kt— convenience extensions for encoding domain objects directly.
Entity Types
Each is a sealed class Entity subclass under entities/:
| Class | Prefix | Payload | Purpose |
|---|---|---|---|
NPub |
npub1... |
32-byte pubkey | Public key |
NSec |
nsec1... |
32-byte private key | Private key (never log/share) |
NNote |
note1... |
32-byte event id | Bare note reference (no hints) |
NEvent |
nevent1… |
TLV: event id + relays + author + kind | Rich note reference |
NProfile |
nprofile… |
TLV: pubkey + relays | User reference with relay hints |
NAddress |
naddr1… |
TLV: d-tag + relays + author + kind (addressable) | Parameterized replaceable event |
NRelay |
nrelay1… |
TLV: relay URL | Relay pointer |
NEmbed |
nembed1… |
Compressed event JSON | Full event embedded inline |
Parsing
// From anywhere (URI, Bech32, nostr: prefix, "nostr:" + data):
val entity: Entity? = Nip19Parser.uriToRoute(input)?.entity
// More forgiving — strips scheme, whitespace, surrounding chars:
val parsed = Nip19Parser.tryParseAndClean(dirtyInput)
when (entity) {
is NPub -> entity.hex // 32-byte pubkey hex
is NEvent -> entity.hex + entity.relay + entity.author + entity.kind
is NAddress -> entity.atag // kind:pubkey:d-tag
is NProfile -> entity.hex + entity.relay
// …
}
Encoding
The cleanest path is the entity's toNostrUri() / toBech32() methods (each entity class defines them). For composite entities (NEvent, NProfile, NAddress), internally the code builds a TLV buffer via TlvBuilder:
// TlvBuilder DSL (tlv/TlvBuilder.kt)
val bytes = TlvBuilder().apply {
addHex(TlvTypes.SPECIAL, eventIdHex)
addString(TlvTypes.RELAY, relayUrl)
addHex(TlvTypes.AUTHOR, authorHex)
addInt(TlvTypes.KIND, kind)
}.build()
Bech32Util.encode("nevent", bytes)
Kotlin-idiomatic extension helpers live in TlvBuilderExt.kt, EventExt.kt, and ATagExt.kt — prefer those over hand-building TLV.
When to Use
- Pasted input from users →
Nip19Parser.tryParseAndClean(handles prefixes, whitespace, leftovernostr:) - Internal routing / deep links →
Nip19Parser.uriToRoute - Outbound share links → call the entity's
toNostrUri()/toBech32()directly - Building a custom TLV entity →
TlvBuilderDSL +Bech32Util.encode
Gotchas
NSecshould never be logged or propagated. Parse and discard the string buffer.- Relay hints in
NEvent/NProfile/NAddressare hints, not guarantees. The Outbox model (NIP-65) overrides them. - TLV types are fixed (see
TlvTypes.kt); do not reorder or invent new types without NIP-19 support. NEmbedis an Amethyst-specific compressed-event extension, not part of NIP-19 proper.
Tests
See quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip19Bech32/ for round-trip tests covering every entity and Nip19Parser input cleaning.