Files
amethyst/.claude/skills/nostr-expert/references/crypto-and-encryption.md
Claude 8bd5063e10 refactor: consume Quartz from its own repository
Quartz now lives in github.com/vitorpamplona/quartz, split into
quartz-core, quartz-social, quartz-relay-client and the other quartz-*
libraries, with history kept. Amethyst depends on the `quartz` umbrella
artifact at a released version (1.20.0) instead of the :quartz project.

- Remove the quartz/ module, jitpack.yml and the Quartz CI, release and
  pre-push steps; tools/unicode-nfkc moved with it.
- Point every module at libs.quartz.
- Move the device-test fixtures the benchmark and relayBench still read
  to testdata/.
- NostrConnectLoginUseCase parses bunker messages with BunkerJson, which
  now owns NIP-46 JSON.
- Repoint docs and skills at the Quartz repository; the nostr-expert
  scripts read a Quartz checkout (QUARTZ_REPO, default ../quartz).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ZR5ZBfhFzRiXubJc5Evyv
2026-10-15 02:03:53 +00:00

43 lines
2.4 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: [core/src/commonTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/](https://github.com/vitorpamplona/quartz/tree/main/core/src/commonTest/kotlin/com/vitorpamplona/quartz/nip44Encryption)
(`Nip44v2Test`, padding, `SharedKeyCacheTest`).