Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
val event: TextNoteEvent = ...
val subject = event.subject() // Extension from nip14Subjectval mentions = event.mentions() // List of p-tagsval quotedEvents = event.quotes() // List of q-tags
// Find tags
event.tags.tagValue("subject") // First subject tag value
event.tags.allTags("p") // All p-tags
event.tags.tagValues("e") // All e-tag values// Parse structured tags
event.tags.mapNotNull(ETag::parse) // Parse as ETag objects
For comprehensive tag patterns, see references/tag-patterns.md.
Pattern: reply and root markers establish thread hierarchy.
Cryptography
Signing (secp256k1)
interfaceISigner {
suspendfunsign(template: EventTemplate): Event
}
// Local key signingclassLocalSigner(privateval privateKey: ByteArray) : ISigner {
overridesuspendfunsign(template: EventTemplate): Event {
val id = template.generateId()
val sig = Secp256k1.sign(id, privateKey)
return Event(id, pubKey, createdAt, kind, tags, content, sig)
}
}
Pattern: Signers abstract key management. Can be local, remote (NIP-46), or hardware.
Encryption (NIP-44)
// Modern encryption (ChaCha20-Poly1305) via the Nip44 facade// (nip44Encryption/Nip44.kt — picks the current version, decrypts any)object Nip44 {
funencrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): Nip44v2.EncryptedInfo
fundecrypt(payload: String, privateKey: ByteArray, pubKey: ByteArray): String
}
// Usageval encrypted = Nip44.encrypt("Secret message", myPrivateKey, recipientPubKey)
val payload = encrypted.encodePayload() // base64 string for event contentval 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.
Note: Use NIP-44 (Nip44) for new implementations. NIP-04 has security issues.
Hex Encoding (HexKey ↔ ByteArray)
Pubkeys, event ids and signatures are lower-case hex. Quartz uses the HexKey
typealias (= String) plus extensions in nip01Core/core/HexKey.kt, backed by
the Hex object in utils/Hex.kt. Use these — never hand-roll a byte loop or
import a third-party hex codec.
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArrayOrNull
import com.vitorpamplona.quartz.nip01Core.core.isValid
import com.vitorpamplona.quartz.utils.Hex
val hex: HexKey = bytes.toHexKey() // ByteArray -> lower-case hexval back: ByteArray = hex.hexToByteArray() // hex -> ByteArray (throws on odd length)val safe: ByteArray? = input.hexToByteArrayOrNull() // null on invalid hex
Hex.isHex(input) // valid hex, any length
Hex.isHex64(input) // ~30% faster fast-path for a 32-byte key/id
hex.isValid() // 64 chars + valid hex (pubkey / event-id shape)
Hex.isEqual(hex, bytes) // compare hex to bytes without decoding
Constants PUBKEY_LENGTH / EVENT_ID_LENGTH (both 64) live in nip01Core.core.
Core Utilities (time, random, event id)
Reuse these instead of hand-rolling — each avoids a common mistake:
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.RandomInstance
import com.vitorpamplona.quartz.utils.sha256.sha256
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
TimeUtils.now() // Unix SECONDS for created_at — not currentTimeMillis()/1000
TimeUtils.oneHourAgo() // relative filter bounds (…Ago / …FromNow); all in seconds
RandomInstance.bytes(32) // secure random (SecureRandom) — for nonces/keys, not kotlin.random.Random
RandomInstance.randomChars() // 16-char subscription id
sha256(bytes) // raw hash primitive
EventHasher.hashId(pubKey, createdAt, kind, tags, content) // canonical event id
EventHasher.hashIdCheck(id, pubKey, createdAt, kind, tags, content) // verify untrusted events
EventHasher serializes [0, pubkey, created_at, kind, tags, content] in the
exact form NIP-01 requires — prefer it over calling sha256 on your own JSON.
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.
// Decode (also accepts nostr: URIs); entity types live in nip19Bech32.entitieswhen (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")
}
Resolving User Input to a Pubkey (NIP-05 + NIP-19)
Before writing any if (isHex) … else if (npub) … else if ("@" in s) fetchWellKnown() logic, stop — it already exists.resolveUserHexOrNull in quartz/nip05DnsIdentifiers/ accepts every identifier form a user might type and returns a 64-hex pubkey.
Tries the synchronous hex/bech32 path first (decodePublicKeyAsHexOrNull) — only NIP-05-shaped input hits the network.
suspend; re-throws only CancellationException. Pass nip05Client = null for offline contexts.
Build the client with Nip05Client(fetcher = OkHttpNip05Fetcher { _ -> okHttp }) (see cli/Context.kt). The OkHttp fetcher already runs on IO and disables redirects per the NIP-05 spec — don't re-implement the .well-known/nostr.json fetch or JSON parse.
Need only hex/bech32 (no network)? Use decodePublicKeyAsHexOrNull(input) directly.
Need to verify a claimed identifier maps back to a pubkey? nip05Client.verify(Nip05Id.parse(id)!!, pubkey).
See references/nip05-identifiers.md for the full API surface (Nip05Id, Nip05Client, Nip05Parser, KeyInfoSet, Namecoin .bit) and the hand-rolled anti-pattern to avoid.
Event Validation
fun Event.verify(): Boolean {
// 1. Verify ID matches content hashval computedId = generateId()
if (id != computedId) returnfalse// 2. Verify signaturereturn Secp256k1.verify(id, sig, pubKey)
}
fun Event.generateId(): HexKey {
val serialized = serializeForId() // JSON array formatreturn sha256(serialized)
}
Pattern: Always verify events from untrusted sources (relays).
references/nip05-identifiers.md - Resolving any identifier (hex/npub/nprofile/nsec/name@domain) to a pubkey via resolveUserHexOrNull; Nip05Client, Nip05Id, Nip05Parser, Namecoin .bit — and the hand-rolled anti-pattern to avoid
references/event-factory.md - EventFactory dispatch pattern and how to register a new kind