Skip to main content

Noise and synchronization

The SDK implements the exact deployed Noise_NK_25519_ChaChaPoly_SHA256 profile and bounded NIP-77 Negentropy state. The standalone primitives never open a socket. Native consumers can enable managed-transport; Android starts it through SoftchatAccount.startManagedTransport, including bounded SQLite fingerprint pages, fetched-event transactions, and the durable checkpoint. For Noise endpoints the managed transport advertises and requires the deployed WebSocket subprotocol nostr-noise-nk.v1.25519-chachapoly-sha256 before the binary handshake. Custom socket implementations must negotiate the same token.

Noise handshake and transport

Operation contractnoise.transport

Authenticate the deployed Noise NK relay profile and transport frames.

Inputs and exact bounds
  • exact 32-byte relay static key
  • frame plaintext at most 65,519 bytes and ciphertext at most 65,535 bytes
  • chunked logical message at most 512 KiB
Output
A single-use handshake then authenticated transport.
Trust and authentication
No transport plaintext is exposed before frame authentication succeeds.
Stable errors
invalid_noise_handshake, noise_handshake_consumed, noise_session_closed, noise_transport_failed · handling and retry rules
Ownership, lifecycle, and execution rules
Ownership
Rust owns protocol semantics, Android account SQLite/use-case state, and optional native relay I/O; the host owns account lifecycle, HTTP/media/background effects, files, and UI.
Retry
Retry only host transport or explicitly retryable session work; validation and authentication failures are permanent for the same input.
Lifecycle
Language ownership is automatic; explicitly cancel or erase only at the operation-specific authority boundary.
Concurrency
Serialize mutable session capabilities; immutable values follow the language type system.
Cancellation
The host owns async cancellation; use cancel, shutdown, close, or erase only where exposed.
Exact public symbols
NoiseClientHandshakeNoiseTransport

Create one handshake with the relay’s authenticated static public key. Send messageOne as one binary WebSocket frame, pass the relay’s complete message two to complete, then use the returned transport for ordered binary frames.

let handshake = NoiseClientHandshake::new(relay_static_key, true)?;
socket.send_binary(handshake.message_one());
let transport = handshake.complete(message_two)?;

for frame in transport.encrypt(application_bytes)? {
socket.send_binary(frame);
}
if let Some(message) = transport.decrypt(received_frame)? {
handle_complete_message(message);
}
transport.shutdown();

Authentication failure closes this session. Never retry with the same handshake state, reveal detailed cryptographic errors, or concatenate transport frames outside the negotiated chunking rules.

Negentropy

Operation contractsync.negentropy

Reconcile bounded immutable event snapshots with NIP-77.

Inputs and exact bounds
  • at most 100,000 items
  • frame size from 4,096 through 512 KiB
Output
Bounded outgoing frames and have/need event ids.
Trust and authentication
The standalone client owns only reconciliation state; Android account transport reads and commits through the Rust-owned database.
Stable errors
invalid_negentropy · handling and retry rules
Ownership, lifecycle, and execution rules
Ownership
Rust owns protocol semantics, Android account SQLite/use-case state, and optional native relay I/O; the host owns account lifecycle, HTTP/media/background effects, files, and UI.
Retry
Retry only host transport or explicitly retryable session work; validation and authentication failures are permanent for the same input.
Lifecycle
Language ownership is automatic; explicitly cancel or erase only at the operation-specific authority boundary.
Concurrency
Serialize mutable session capabilities; immutable values follow the language type system.
Cancellation
The host owns async cancellation; use cancel, shutdown, close, or erase only where exposed.
Exact public symbols
NegentropyClient

The standalone API takes one bounded, sorted snapshot of (createdAt, eventId) pairs. Android uses the account managed transport instead: Rust pages the account fingerprints, drives Negentropy, authenticates and commits fetched events, saves the checkpoint, and executes its correlated WebSocket actions.

let client = NegentropyClient::new(items, 60_000)?;
send(client.initiate()?);
let step = client.reconcile(response)?;

The snapshot and frame limits are denial-of-service boundaries. Split the application’s sync scope instead of raising them without a measured, interoperable protocol decision.