Files
amethyst/.claude/skills/nostr-expert/references/crypto-and-encryption.md
Vitor PamplonaandClaude Opus 5.5 395af4dc9c chore(skills): consolidate 33 skills into 14, verified against the tree
The 33 descriptions (~16k chars, ~19k with paths) overran Codex's skill-list
budget (2% of the context window, or 8,000 chars when unknown), and a review
found most bodies stale: paths from before the commons/commonsUI moves, the
deleted legacy desktop app, a callback signer API that no longer exists, and
example APIs that were never in the tree.

Kept, rewritten and checked against the code (every path, class and command):
account-state (+feeds), amy-expert, android-expert, auth-signers,
compose-expert (+API style, Navigation 3 back stacks), desktop-expert
(+run/package), event-store-semantics (+NIP-50 search), find-missing-
translations, kotlin-coroutines (+structured concurrency, flow modeling),
kotlin-multiplatform (+Gradle, composable extraction), ngit-pr,
nip85-trusted-assertions, nostr-expert (+NIP lookup, generated NIP catalog),
relay-client.

Dropped: textbook Compose/Kotlin technique skills, kotlin-expert,
kotlin-types-value-class, gradle-expert, quartz-integration (external-facing;
see the Quartz docs commit), find-non-lambda-logs (audit finished; the rule is
now in AGENTS.md Kotlin Style), and the /nip, /desktop-run, /extract commands.
The skill list is now ~4k chars. Also drops .claude/core-skills-plan.md
(stale history) and repoints SearchableEvent's KDoc to event-store-semantics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:21:01 -04:00

43 lines
2.3 KiB
Markdown

# Crypto and encryption in Quartz
## Signing and ids (`nip01Core/crypto/`)
| File | Holds |
|---|---|
| `EventHasher.kt` | `hashId(pubKey, createdAt, kind, tags, content)`, `hashIdCheck(id, …)` — canonical NIP-01 serialization + SHA-256 |
| `EventHasherSerializer.kt` | the exact byte layout the hash needs |
| `Nip01Crypto.kt` | `object Nip01Crypto`: `privKeyCreate()`, `pubKeyCreate(privKey)`, `sign(...)`, `signWithPubKey(...)`, `verify(...)` |
| `KeyPair.kt` | private/public key holder; `toString()` redacts the private key |
| `EventAssembler.kt` | `EventAssembler.hashAndSign(...)` — id + Schnorr signature into a typed event |
| `EventExt.kt` | `Event.generateId()`, `verifyId()`, `verifySignature()`, `verify()`, `checkSignature()` |
App code signs through a `NostrSigner` (`signer.sign(template)`), never `Nip01Crypto`
directly, so NIP-46 and NIP-55 signers keep working — see the `auth-signers` skill.
`utils/Secp256k1Instance.kt` is a plain common `object` over secp256k1-kmp
(`signSchnorr`, `verifySchnorr`, `compressedPubKeyFor`, tweaks); the platform binding
comes from the `secp256k1-kmp-jni-android` / `-jni-jvm` dependency.
## NIP-44 (`nip44Encryption/`)
`Nip44.kt` is the facade: `encrypt(msg, privateKey, pubKey): Nip44v2.EncryptedInfo`
(always the current version; call `.encodePayload()` for the base64 content) and
`decrypt(payload, privateKey, pubKey)` (any version, base64 or the legacy JSON form).
`Nip44v1.kt`/`Nip44v2.kt` are the versions; `crypto/` holds the ChaCha20/HKDF/HMAC
primitives; `SharedKeyCache` (one private instance per `Nip44v1`/`Nip44v2`/`Nip04`)
caches ECDH secrets, so don't compute conversation keys yourself.
As with signing, app code goes through `signer.nip44Encrypt(plaintext, toPublicKey)` /
`signer.nip44Decrypt(ciphertext, fromPublicKey)`, or `signer.decrypt(...)` which picks
NIP-04 vs NIP-44 from the payload. NIP-04 (`nip04Dm/crypto/Nip04.kt`) is legacy: decrypt
old DMs, don't encrypt new content with it.
## Gotchas
- Private keys stay `ByteArray`; never log keys, shared secrets or plaintext.
- NIP-44 pads plaintext — assert round-trips, not ciphertext lengths.
- Hashing your own JSON instead of `EventHasher` produces ids relays reject.
Tests: `quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/`
(`Nip44v2Test`, padding, `SharedKeyCacheTest`).