nostr: Nostr library

[ library, mit, network, program ] [ Propose Tags ] [ Report a vulnerability ]

A Haskell library for the Nostr protocol, providing client and relay communication, event handling, and support for various NIPs including authentication, threading, and URI schemes.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 1.3.0.0, 1.3.1.0
Change log CHANGELOG.md
Dependencies aeson (<3), async (<2.3), base (>=4.18.0.0 && <4.20), base16-bytestring (<1.1), base64-bytestring (<1.3), bech32 (<1.2), bytestring (<0.13), containers (<0.8), cryptonite (<0.31), entropy (<0.5), http-conduit (<2.4), memory (<0.19), mtl (<2.4), nonempty-wrapper-text (<0.2), nostr, ppad-fixed (<0.2), ppad-secp256k1 (>=0.4.0 && <0.5), stm (<2.6), text (<3), time (<1.13), unix-time (<0.5), vector (<0.14), websockets (<0.14), wuss (<2.1) [details]
License MIT
Author Emre YILMAZ
Maintainer z@emre.xyz
Uploaded by delirehberi at 2026-06-07T15:06:05Z
Category Network
Home page https://nostrhs.emre.xyz
Bug tracker https://github.com/delirehberi/nostr.hs/issues
Source repo head: git clone https://github.com/delirehberi/nostr.hs.git
Distributions
Executables nostr-example, nostr
Downloads 0 total (0 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for nostr-1.3.1.0

[back to package description]

nostr.hs

A NIP-01 compliant Nostr library and client implementation in Haskell.

Features

  • NIP-01 Compliant: Full implementation of event structure, rules, and tags.
  • Schnorr Signatures: Pure Haskell BIP-340 Schnorr signatures using ppad-secp256k1.
  • Relay Communication: WebSocket-based communication with Nostr relays.
  • Type Safety: Strong types for Event IDs, Public Keys, and Signatures.

Implemented NIPs

  • NIP-01: Basic protocol flow

  • NIP-02: Contact Lists

  • NIP-05: DNS-based verification

  • NIP-09: Event Deletion Request

  • NIP-10: Text Notes and Threads

  • NIP-19: bech32-encoded entities

  • NIP-21: nostr: URI scheme

  • NIP-42: Relay Authentication

Installation

This project uses Nix for reproducible builds.

  1. Clone the repository:

    git clone https://github.com/delirehberi/nostr.hs.git
    cd nostr.hs
    
  2. Enter the development shell:

    nix develop
    
  3. Build the project:

    cabal build all
    

Development

Building and Testing

make build    # Build the project
make test     # Run tests
make clean    # Clean build artifacts
make docs     # Generate documentation
make sdist    # Generate source distribution tar.gz

All make commands automatically use the Nix development environment, so you don't need to run nix develop first.

Uploading to Hackage

To package and upload the library to Hackage:

make upload-hackage

This will:

  1. Generate a source distribution tar.gz file
  2. Generate Haddock documentation for Hackage
  3. Upload the package source to Hackage
  4. Upload the documentation to Hackage

You'll need to be authenticated with Hackage and have upload permissions. The script assumes you have cabal configured with your Hackage credentials.

Usage Examples

The Nostr.Client module provides a monadic interface for managing connections, signing, and publishing. It handles robust reconnections (exponential backoff) and TLS usage automatically.

{-# LANGUAGE OverloadedStrings #-}

import Nostr.Client
import Nostr.Event
import Nostr.Crypto
import Data.Function ((&))
import Control.Monad.IO.Class (liftIO)

main :: IO ()
main = do
  -- 1. Generate Keys
  -- Note: In a real app, you'd load these from safe storage
  (secKey, pubKey) <- generateKeyPair
  let keys = Keys secKey pubKey Nothing

  -- 2. Connect to Relays
  -- connectRelays establishes background threads for each relay and manages reconnection
  env <- connectRelays ["wss://relay.damus.io", "wss://nos.lol"]

  runNostrApp env $ do
    -- A. Publish a simple short text note (Kind 1)
    publishShortNote keys "Hello Nostr from Haskell!"

    -- B. Publish a complex event using the Builder Pattern
    -- Use the (&) operator to chain combinators for tags and kinds
    liftIO $ putStrLn "Publishing a reply..."
    publish keys $ shortNote "Replying to an event..."
      & withKind 1
      & withReply "event-id-hex"
      & withMention "pubkey-hex"
      & withTag ["t", "haskell"]
      
    -- C. Query Events
    -- Queries all connected relays concurrently and aggregates unique results
    let filter = defaultFilter
          { filterKinds = Just [1]
          , filterAuthors = Just [pubKey]
          , filterLimit = Just 10
          }
    
    events <- queryEvents filter
    liftIO $ print events

    -- D. Follow Users (NIP-02)
    -- This fetches your existing contact list (Kind 3), adds the user, and republishes.
    liftIO $ putStrLn "Following Jack..."
    follow keys "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbf71d22" (Just "wss://relay.damus.io") (Just "jack")

    -- E. Fetch Contacts
    contacts <- getContacts keys
    liftIO $ print contacts

    -- F. Delete Events (NIP-09)
    -- Deletes a list of event IDs with an optional reason.
    -- let eventIds = [EventId "hex_id_1...", EventId "hex_id_2..."]
    -- deleteEvents keys eventIds (Just "mistake")

  -- 3. Disconnect
  disconnect env

Low-Level Event Creation

If you need manual control over event creation without the NostrApp environment:

import Nostr.Event
import Nostr.Crypto
import Data.Time.Clock.POSIX (getPOSIXTime)

main :: IO ()
main = do
  (secKey, pubKey) <- generateKeyPair
  now <- round <$> getPOSIXTime
  
  -- Create unsigned event
  let unsigned = createUnsignedEvent pubKey now 1 [] "Manual event content"
  
  -- Sign event
  signed <- signEvent secKey unsigned
  case signed of
    Right event -> putStrLn $ "Event ID: " ++ show (eventId event)
    Left err    -> putStrLn $ "Error: " ++ show err

### Encrypted Direct Messages (NIP-04)

Use the `Nostr.Nip04` module to securely encrypt and decrypt direct messages using AES-256-CBC and ECDH:

```haskell
import Nostr.Crypto
import Nostr.Nip04
import qualified Data.Text as T

main :: IO ()
main = do
  (senderSec, senderPub) <- generateKeyPair
  (receiverSec, receiverPub) <- generateKeyPair
  
  let msg = "Super secret message"
  
  -- Encrypt (Sender -> Receiver)
  Just encrypted <- encryptNip04 senderSec receiverPub msg
  putStrLn $ "Encrypted: " ++ T.unpack encrypted
  
  -- Decrypt (Receiver)
  let decrypted = decryptNip04 receiverSec senderPub encrypted
  print decrypted -- Just "Super secret message"

Fetching Relay Information (NIP-11)

Query a relay's metadata document using the Nostr.Nip11 module:

import Nostr.Nip11

main :: IO ()
main = do
  info <- fetchRelayInfo "wss://relay.damus.io"
  case info of
    Right i  -> print (riSoftware i, riSupportedNips i)
    Left err -> putStrLn $ "Failed to fetch info: " ++ err

Parsing Lightning Zaps (NIP-57)

Easily parse Zap Requests (Kind 9734) and Zap Receipts (Kind 9735):

import Nostr.Nip57
import Nostr.Event

-- Assuming `event` is a parsed Event of kind 9734
handleEvent :: Event -> IO ()
handleEvent event = do
  case parseZapRequest event of
    Just zr -> putStrLn $ "Zap Request for " ++ T.unpack (zrP zr)
    Nothing -> return ()

## Contributing

Contributions are welcome! Please ensure all tests pass before submitting a PR.