Nostr values and cryptography
This family establishes the trust boundary for keys, identifiers, events, and encrypted payloads. Parse once, keep the validated value, and preserve exact raw tag arrays—including trailing empty fields.
NIP-19 identifiers
Nip19Identifier parses and encodes the key, event, profile, and relay-hint
forms used by Softchat. Mixed-case Bech32 is rejected. Unknown TLVs are retained
where the typed representation supports them, so a read/write round trip does
not silently discard extensions.
- Rust
- Kotlin
- Android
- Swift
- TypeScript
let identifier = Nip19Identifier::parse(encoded)?;
let canonical = identifier.encode()?;
val identifier = Nip19Identifier.parse(encoded)
val canonical = identifier.encode()
Use the same Nip19Identifier Kotlin API. Parse incoming intents or QR data on
a bounded worker before updating UI state.
let identifier = try Nip19Identifier(encoded: encoded)
let canonical = try identifier.encoded()
const identifier = Nip19Identifier.parse(encoded);
const canonical = Nip19Identifier.stringify(identifier);
Author a NIP-01 event
The caller supplies createdAt; the SDK never hides a clock in signing.
Signing computes the canonical event ID from [0, pubkey, created_at, kind, tags, content] and produces a verified immutable event.
- Rust
- Kotlin
- Android
- Swift
- TypeScript
let draft = NostrEventDraft::new(
1_700_000_000,
NostrEventKind::SHORT_TEXT_NOTE,
vec![NostrTag::new([
"p".to_owned(),
recipient.to_hex(),
String::new(),
String::new(),
])?],
"hello",
)?;
let event = identity.sign_event(draft)?;
let received = SignedNostrEvent::from_json(&event.to_json()?)?;
val draft = NostrEventDraft(
createdAt = 1_700_000_000,
kind = NostrEventKind.SHORT_TEXT_NOTE,
tags = listOf(NostrTag.of("p", recipient.hex, "", "")),
content = "hello",
)
val event = identity.sign(draft)
val received = SignedNostrEvent.parse(event.toJson())
Create and sign on Dispatchers.Default, persist event.toJson() or a complete
lossless schema, and validate any event received from storage or a relay with
SignedNostrEvent.parse.
let draft = try NostrEventDraft(
createdAt: 1_700_000_000,
kind: .shortTextNote,
tags: [.init("p", recipient.hex, "", "")],
content: "hello"
)
let event = try identity.sign(draft)
let received = try SignedNostrEvent(json: event.canonicalJSON())
const draft = NostrEventDraft.create({
createdAt: 1_700_000_000,
kind: NostrEventKind.shortTextNote,
tags: [["p", recipient, "", ""]],
content: "hello",
});
const event = identity.sign(draft);
const received = SignedNostrEvent.parse(SignedNostrEvent.stringify(event));
- Rust
- Kotlin
- Android
- Swift
- TypeScript
Verify before use
Parsing a signed event checks canonical lowercase hex, the 512-KiB bound, portable kind and timestamp bounds, the recomputed event ID, and its Schnorr signature. A parsed event is authenticated; raw JSON is not.
- Rust
- Kotlin
- Android
- Swift
- TypeScript
NIP-44 v2
Encrypt UTF-8 with the sender identity and the validated recipient public key. The immutable result includes the algorithm, sender public key, and NIP-44 payload. Decryption maps authentication, padding, and decoding failures to one redacted error.
- Rust
- Kotlin
- Android
- Swift
- TypeScript
let encrypted = alice.encrypt_utf8(&bob_public, plaintext)?;
let plaintext = bob.decrypt_utf8(&encrypted)?;
val encrypted = alice.encrypt(bob.publicKey, plaintext)
val plaintext = bob.decrypt(encrypted)
The Android facade automatically selects the narrow JNI byte-array path. Inputs and outputs stay identical to Kotlin/JVM; do not call this synchronously from the main thread.
let encrypted = try alice.encrypt(plaintext, for: bob.publicKey)
let plaintext = try bob.decrypt(encrypted)
const encrypted = alice.encrypt(bob.publicKey, plaintext);
const plaintext = bob.decrypt(encrypted);
The three accepted payload sizes show fixed crossing/crypto cost and scaling. Each card includes the frozen old-library comparison when an equivalent baseline exists.
- Rust
- Kotlin
- Android
- Swift
- TypeScript
- Rust
- Kotlin
- Android
- Swift
- TypeScript
- Rust
- Kotlin
- Android
- Swift
- TypeScript
- Rust
- Kotlin
- Android
- Swift
- TypeScript
- Rust
- Kotlin
- Android
- Swift
- TypeScript
- Rust
- Kotlin
- Android
- Swift
- TypeScript
Readers accept both legacy and current extended-length encodings. Writers remain compatible with released clients. Never expose distinct failures that could become a padding or authentication oracle.