Extends the Block 2 spike to attack the highest-risk unbuilt piece (private messaging) and to prove the Q2 architecture recommendation with running code: - NostrConnection: one shared socket+key+sign+REQ/EOSE lifecycle. Refactored NostrOfferTransport onto it; added NostrMessageTransport — two thin contracts on ONE connection (the 'one connection, three interfaces' shape). - NIP-44 v2 encryption (secp256k1 ECDH -> ChaCha20 + HMAC, length-hiding pad). - NIP-17/NIP-59 gift-wrap: rumor(14) -> seal(13) -> wrap(1059, ephemeral key). Alice->Bob DM round-trips through the relay: encrypted, sender authenticated, and metadata-private (relay/eavesdropper sees only ciphertext, an ephemeral author and the recipient p-tag — Alice stays hidden). Findings updated: NIP-17 risk drops Medium-High -> Medium (hardening, not feasibility); next de-risking target is the web of trust. NIP-44 not yet vector-verified (noted). 20 tests green, analyzer clean, Block 1 untouched.
28 lines
973 B
Dart
28 lines
973 B
Dart
/// One decrypted direct message handed to the app.
|
|
class DirectMessage {
|
|
const DirectMessage({
|
|
required this.fromPubkey,
|
|
required this.text,
|
|
required this.at,
|
|
});
|
|
|
|
/// The authenticated sender (from the inner rumor). NOT the gift-wrap author,
|
|
/// which is a throwaway ephemeral key — that's the metadata-privacy point.
|
|
final String fromPubkey;
|
|
final String text;
|
|
final DateTime at;
|
|
}
|
|
|
|
/// The second interface over the shared `NostrConnection` (Q2): private 1:1
|
|
/// messaging. Deliberately SEPARATE from `OfferTransport` — its verbs (send a
|
|
/// private DM / read an inbox) don't fit publish/discover. Backed by NIP-17
|
|
/// gift-wrapped, NIP-44-encrypted events.
|
|
abstract interface class MessageTransport {
|
|
/// Sends [text] privately to [toPubkey].
|
|
Future<void> send({required String toPubkey, required String text});
|
|
|
|
/// Streams decrypted messages addressed to this identity.
|
|
Stream<DirectMessage> inbox();
|
|
|
|
Future<void> close();
|
|
}
|