diff --git a/Nostra.Client/Program.fs b/Nostra.Client/Program.fs index 48d4faf..8c6d9ce 100644 --- a/Nostra.Client/Program.fs +++ b/Nostra.Client/Program.fs @@ -41,7 +41,7 @@ let displayResponse (contacts : Map) (addContact: ContactKey -> EventId.toBytes eventId else - XOnlyPubKey.toBytes event.PubKey + Author.toBytes event.PubKey let maybeContact = contacts |> Map.tryFind contactKey let author = maybeContact |> Option.map (fun c -> c.metadata.displayName |> Option.defaultValue c.metadata.name) @@ -154,7 +154,7 @@ let Main args = | Some [c] -> c, StdIn.read "Message" | Some (c::msgs) -> c, msgs |> List.head - let channel = Shareable.decodeNpub channel' |> Option.map (fun pubkey -> EventId (XOnlyPubKey.toBytes pubkey) ) |> Option.get + let channel = Shareable.decodeNpub channel' |> Option.map (fun pubkey -> EventId (Author.toBytes pubkey) ) |> Option.get let user = User.load userFilePath let event = Event.createChannelMessage channel message |> Event.sign user.secret publish event user.relays @@ -207,7 +207,7 @@ let Main args = let unknownAuthors = user.subscribedAuthors - |> List.notInBy XOnlyPubKey.equals knownAuthors + |> List.notInBy Author.equals knownAuthors let filterMetadata = match unknownAuthors with @@ -223,7 +223,7 @@ let Main args = |> List.map (fun c -> (match c.key with | Channel e -> EventId.toBytes e - | Author p -> XOnlyPubKey.toBytes p) , c ) + | Author p -> Author.toBytes p) , c ) |> Map.ofList let addContact contactKey metadata = diff --git a/Nostra.Client/User.fs b/Nostra.Client/User.fs index d46b0e0..f5894ba 100644 --- a/Nostra.Client/User.fs +++ b/Nostra.Client/User.fs @@ -13,7 +13,6 @@ module List = |> List.filter (fun i1 -> not (List.exists (predicate i1) list2)) -type Author = XOnlyPubKey type Channel = EventId type Metadata = { @@ -28,7 +27,7 @@ type Relay = { proxy : Uri Option } type ContactKey = - | Author of XOnlyPubKey + | Author of AuthorId | Channel of Channel type Contact = { @@ -36,37 +35,37 @@ type Contact = { metadata : Metadata } type User = { - secret : ECPrivKey + secret : SecretKey metadata : Metadata relays : Relay list contacts : Contact list - subscribedAuthors : Author list + subscribedAuthors : AuthorId list subscribedChannels : Channel list } module Author = module Decode = - let author : Decoder = + let shareableAuthor : Decoder = Decode.string |> Decode.map Shareable.decodeNpub |> Decode.andThen (Decode.ofOption "Not a valid bech32 author") module Encode = - let author pubkey = - pubkey |> Shareable.encodeNpub |> Encode.string + let shareableAuthor author = + author |> Shareable.encodeNpub |> Encode.string module ContactKey = open Author module Encode = let contactKey = function - | Author author -> Encode.author author + | Author author -> Encode.shareableAuthor author | Channel channel -> Encode.eventId channel module Decode = let contactKey : Decoder = Decode.oneOf [ Decode.eventId |> Decode.map ContactKey.Channel - Decode.author |> Decode.map ContactKey.Author + Decode.shareableAuthor |> Decode.map ContactKey.Author ] module Metadata = @@ -97,7 +96,6 @@ module Metadata = Encode.object (mandatoryFields @ picture @ about @ displayName @ nip05) module Contact = - open Author open Metadata open ContactKey @@ -119,7 +117,7 @@ module User = open Metadata let createUser name displayName about picture nip05 = - let secret = Key.createNewRandom () + let secret = SecretKey.createNewRandom () { secret = secret metadata = { @@ -145,7 +143,7 @@ module User = proxy = get.Optional.Field "proxy" Decode.uri }) - let secret : Decoder = + let secret : Decoder = Decode.string |> Decode.map Shareable.decodeNsec |> Decode.andThen (Decode.ofOption "Not a valid bech32 secret") @@ -159,7 +157,7 @@ module User = metadata = get.Required.Field "metadata" Decode.metadata relays = get.Required.Field "relays" (Decode.list relay) contacts = get.Required.Field "contacts" (Decode.list Decode.contact) - subscribedAuthors = get.Required.Field "subscribed_authors" (Decode.list Decode.author) + subscribedAuthors = get.Required.Field "subscribed_authors" (Decode.list Decode.shareableAuthor) subscribedChannels = get.Required.Field "subscribed_channels" (Decode.list channel) }) @@ -184,7 +182,7 @@ module User = "metadata", Encode.metadata user.metadata "relays", Encode.list (List.map relay user.relays) "contacts", Encode.list (List.map Encode.contact user.contacts) - "subscribed_authors", Encode.list (List.map Encode.author user.subscribedAuthors) + "subscribed_authors", Encode.list (List.map Encode.shareableAuthor user.subscribedAuthors) "subscribed_channels", Encode.list (List.map channel user.subscribedChannels) ] @@ -211,13 +209,13 @@ module User = let contacts = { key = key; metadata = metadata }:: user.contacts { user with contacts = List.distinctBy (fun c -> c.key) contacts } - let subscribeAuthors (authors : Author list) user = - let authors' = List.distinctBy XOnlyPubKey.toBytes (user.subscribedAuthors @ authors) + let subscribeAuthors (authors : AuthorId list) user = + let authors' = List.distinctBy Author.toBytes (user.subscribedAuthors @ authors) { user with subscribedAuthors = authors' } - let unsubscribeAuthors (authors : Author list) user = + let unsubscribeAuthors (authors : AuthorId list) user = let authors' = user.subscribedAuthors - |> List.notInBy (fun a1 a2 -> XOnlyPubKey.toBytes a1 = XOnlyPubKey.toBytes a2) authors + |> List.notInBy (fun a1 a2 -> Author.toBytes a1 = Author.toBytes a2) authors { user with subscribedAuthors = authors' } let subscribeChannels (channels : Channel list) user = diff --git a/Nostra.Relay/Database.fs b/Nostra.Relay/Database.fs index 4a183a6..c8a7ecf 100644 --- a/Nostra.Relay/Database.fs +++ b/Nostra.Relay/Database.fs @@ -166,8 +166,8 @@ let handleParameterizedReplacement connection author kind createdAt dtag= let saveEvent connection (preprocessedEvent: StoredEvent) = asyncResult { let event = preprocessedEvent.Event let (EventId eventId) = event.Id - let (XOnlyPubKey xOnlyPubkey) = event.PubKey - let author = xOnlyPubkey.ToBytes() + let (AuthorId author) = event.PubKey + let author = author.ToBytes() let kind = int event.Kind let createdAt = event.CreatedAt let dtag = Tag.findByKey "d" preprocessedEvent.Event.Tags |> List.tryHead |> Option.defaultValue "" @@ -180,7 +180,7 @@ let saveEvent connection (preprocessedEvent: StoredEvent) = asyncResult { return! save connection eventId author preprocessedEvent |> AsyncResult.ignore } -let deleteEvents connection (XOnlyPubKey author) eventIds = +let deleteEvents connection (AuthorId author) eventIds = let eventIdsParameters = String.Join(",", eventIds |> List.mapi (fun i _ -> $"@event_hash{i}")) connection |> Sql.executeTransactionAsync [ diff --git a/Nostra.Relay/EventStore.fs b/Nostra.Relay/EventStore.fs index 2a8f5e7..af78737 100644 --- a/Nostra.Relay/EventStore.fs +++ b/Nostra.Relay/EventStore.fs @@ -4,7 +4,7 @@ open Nostra open Nostra.Relay type EventSaver = StoredEvent -> Async> -type EventsDeleter = XOnlyPubKey -> string list -> Async> +type EventsDeleter = AuthorId -> string list -> Async> type EventsFetcher = Request.Filter list -> System.DateTime -> Async> type EventStore = { diff --git a/Nostra.Relay/MessageProcessing.fs b/Nostra.Relay/MessageProcessing.fs index 9fe283a..d30de5e 100644 --- a/Nostra.Relay/MessageProcessing.fs +++ b/Nostra.Relay/MessageProcessing.fs @@ -25,8 +25,8 @@ type EventProcessingError = let preprocessEvent (event : Event) serializedEvent = let (EventId eventId) = event.Id - let (XOnlyPubKey xOnlyPubkey) = event.PubKey - let pubkey = xOnlyPubkey.ToBytes() + let (AuthorId author) = event.PubKey + let pubkey = author.ToBytes() { Event = event diff --git a/Nostra.Tests/Bech32.fs b/Nostra.Tests/Bech32.fs index fd842de..cdb0fa1 100644 --- a/Nostra.Tests/Bech32.fs +++ b/Nostra.Tests/Bech32.fs @@ -20,7 +20,7 @@ type ``Nip19 Bech32-Shareable entities``(output:ITestOutputHelper) = [] let ``Encode/Decode nsec`` () = - let secKey = Key.createNewRandom() + let secKey = SecretKey.createNewRandom() NSec secKey |> encodeDecode |> function @@ -30,13 +30,13 @@ type ``Nip19 Bech32-Shareable entities``(output:ITestOutputHelper) = [] let ``Encode/Decode npub`` () = - let secKey = Key.createNewRandom() - let pubkey = XOnlyPubKey (Key.getPubKey secKey) - NPub pubkey + let secKey = SecretKey.createNewRandom() + let author = SecretKey.getPubKey secKey + NPub author |> encodeDecode |> function | NPub decodedPubKey -> - should equal (XOnlyPubKey.toBytes decodedPubKey) (XOnlyPubKey.toBytes pubkey) + should equal (Author.toBytes decodedPubKey) (Author.toBytes author) | _ -> failwith "The entity is not a npub" [] @@ -52,8 +52,8 @@ type ``Nip19 Bech32-Shareable entities``(output:ITestOutputHelper) = [] let ``Encode/Decode nprofile`` () = let nprofile = - let pubkey = XOnlyPubKey.parse "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" |> Result.requiresOk - NProfile(pubkey, [ + let author = Author.parse "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" |> Result.requiresOk + NProfile(author, [ "wss://r.x.com" "wss://djbas.sadkb.com" ]) @@ -73,7 +73,7 @@ type ``Nip19 Bech32-Shareable entities``(output:ITestOutputHelper) = NEvent( EventId (Utils.fromHex "08a193492c7fb27ab1d95f258461e4a0dfc7f52bccd5e022746cb28418ef4905"), ["wss://nostr.mom"], - Some (XOnlyPubKey.parse "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" |> Result.requiresOk), + Some (Author.parse "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" |> Result.requiresOk), Some Kind.Text ) |> encodeDecode diff --git a/Nostra.Tests/ClientTests.fs b/Nostra.Tests/ClientTests.fs index 9387036..239c840 100644 --- a/Nostra.Tests/ClientTests.fs +++ b/Nostra.Tests/ClientTests.fs @@ -33,9 +33,9 @@ type ``Client tests``(output:ITestOutputHelper, fixture: EchoServerFixture) = [] let ``Can receive encrypted messages`` () = async { let! send, receive = Client.createClient fixture.Port - let secret = Key.createNewRandom () - let pubkey = Key.getPubKey secret |> XOnlyPubKey - let event = Event.createEncryptedDirectMessage pubkey secret "hello" |> Event.sign secret + let secret = SecretKey.createNewRandom () + let author = SecretKey.getPubKey secret + let event = Event.createEncryptedDirectMessage author secret "hello" |> Event.sign secret do! send (createRelayMessage "sid" (Event.serialize event)) event.Content |> should not' (equal "hello", "The note is not encrypted") @@ -50,7 +50,7 @@ type ``Client tests``(output:ITestOutputHelper, fixture: EchoServerFixture) = [] let ``Can receive a valid event from relay`` () = async { let! send, receive = Client.createClient fixture.Port - let event = Event.createNote "Welcome" |> Event.sign (Key.createNewRandom()) + let event = Event.createNote "Welcome" |> Event.sign (SecretKey.createNewRandom()) do! send (createRelayMessage "sid" (Event.serialize event)) let! msg = receive @@ -62,7 +62,7 @@ type ``Client tests``(output:ITestOutputHelper, fixture: EchoServerFixture) = [] let ``Can detect invalid (non-authentic) events`` () = async { let! send, receive = Client.createClient fixture.Port - let event = Event.createNote "Welcome" |> Event.sign (Key.createNewRandom()) + let event = Event.createNote "Welcome" |> Event.sign (SecretKey.createNewRandom()) let modifiedEvent = { event with Content = event.Content.Replace("Welcome","Bienvenido") } let serializedModifiedEvent = modifiedEvent |> Event.serialize do! send (createRelayMessage "sid" serializedModifiedEvent) diff --git a/Nostra.Tests/PayloadEncryptionTests.fs b/Nostra.Tests/PayloadEncryptionTests.fs index ead4e66..6f4af47 100644 --- a/Nostra.Tests/PayloadEncryptionTests.fs +++ b/Nostra.Tests/PayloadEncryptionTests.fs @@ -13,9 +13,9 @@ type ``Nip44 Payload encryption``(output:ITestOutputHelper) = [] let ``Basic example`` () = - let me = Key.createNewRandom () - let you = Key.createNewRandom () - let yourPk = you |> Key.getPubKey |> XOnlyPubKey + let me = SecretKey.createNewRandom () + let you = SecretKey.createNewRandom () + let yourPk = you |> SecretKey.getPubKey let sharedKey = Event.sharedKey yourPk me let conversationKey = EncryptedPayload.conversationKey sharedKey let nonce = RandomNumberGenerator.GetBytes 32 @@ -36,8 +36,8 @@ type ``Nip44 Payload encryption``(output:ITestOutputHelper) = |> fun x -> x["get_conversation_key"] |> Seq.map (fun x -> [| - x |> readHex "sec1" |> ECPrivKey.Create |> box - x |> readHex "pub2" |> ECXOnlyPubKey.Create |> XOnlyPubKey |> box + x |> readHex "sec1" |> ECPrivKey.Create |> SecretKey |> box + x |> readHex "pub2" |> ECXOnlyPubKey.Create |> AuthorId |> box x |> readHex "conversation_key" |> box |]) @@ -67,8 +67,8 @@ type ``Nip44 Payload encryption``(output:ITestOutputHelper) = |> fun x -> x["encrypt_decrypt"] |> Seq.map (fun x -> [| - x |> readHex "sec1" |> ECPrivKey.Create |> box - x |> readHex "sec2" |> ECPrivKey.Create |> box + x |> readHex "sec1" |> ECPrivKey.Create |> SecretKey |> box + x |> readHex "sec2" |> ECPrivKey.Create |> SecretKey |> box x |> readHex "conversation_key" |> box x |> readHex "nonce" |> box x.Value("plaintext") |> box @@ -77,7 +77,7 @@ type ``Nip44 Payload encryption``(output:ITestOutputHelper) = [] [] - let ``Conversation Key (valid)`` (sec1: ECPrivKey) (pub2 : XOnlyPubKey) (expectedConversationKey : byte[]) = + let ``Conversation Key (valid)`` (sec1: SecretKey) (pub2 : AuthorId) (expectedConversationKey : byte[]) = let sharedKey = Event.sharedKey pub2 sec1 let conversationKey = EncryptedPayload.conversationKey sharedKey should equal expectedConversationKey conversationKey @@ -95,8 +95,8 @@ type ``Nip44 Payload encryption``(output:ITestOutputHelper) = [] [] - let ``Encryption and decryption (valid)`` (sec1 : ECPrivKey) (sec2 : ECPrivKey) (expectedConversationKey : byte[]) (nonce : byte[]) (expectedPlainText : string) (expectedPayload : string) = - let sharedKey = Event.sharedKey (sec2 |> Key.getPubKey |> XOnlyPubKey) sec1 + let ``Encryption and decryption (valid)`` (sec1 : SecretKey) (sec2 : SecretKey) (expectedConversationKey : byte[]) (nonce : byte[]) (expectedPlainText : string) (expectedPayload : string) = + let sharedKey = Event.sharedKey (sec2 |> SecretKey.getPubKey) sec1 let conversationKey = EncryptedPayload.conversationKey sharedKey should equal expectedConversationKey conversationKey let payload = EncryptedPayload.encrypt conversationKey expectedPlainText nonce diff --git a/Nostra.Tests/RelaySimpleTests.fs b/Nostra.Tests/RelaySimpleTests.fs index c2dc153..198d1f4 100644 --- a/Nostra.Tests/RelaySimpleTests.fs +++ b/Nostra.Tests/RelaySimpleTests.fs @@ -33,7 +33,7 @@ type ``Relay Accept Events``(output:ITestOutputHelper, fixture:RelayFixture) = [] let ``Can receive a valid event from relay`` () = async { let! send, receive = Client.createClient fixture.Port - let event = Event.createNote "Welcome" |> Event.sign (Key.createNewRandom()) + let event = Event.createNote "Welcome" |> Event.sign (SecretKey.createNewRandom()) do! send $"""["EVENT",{Event.serialize event}]""" let! msg = receive @@ -45,7 +45,7 @@ type ``Relay Accept Events``(output:ITestOutputHelper, fixture:RelayFixture) = [] let ``Can detect invalid (non-authentic) events`` () = async { let! send, receive = Client.createClient fixture.Port - let event = Event.createNote "Welcome" |> Event.sign (Key.createNewRandom()) + let event = Event.createNote "Welcome" |> Event.sign (SecretKey.createNewRandom()) let modifiedEvent = { event with Content = event.Content.Replace("Welcome","Bienvenido") } let serializedModifiedEvent = modifiedEvent |> Event.serialize do! send ("""["EVENT",""" + serializedModifiedEvent + "]") diff --git a/Nostra.Tests/TestingFramework.fs b/Nostra.Tests/TestingFramework.fs index 7807047..e1de848 100644 --- a/Nostra.Tests/TestingFramework.fs +++ b/Nostra.Tests/TestingFramework.fs @@ -17,7 +17,7 @@ type Connection = { type User = { SentEvents : Event ResizeArray ReceivedEvents : Event ResizeArray - Secret : ECPrivKey + Secret : SecretKey Connection : Connection option } @@ -46,7 +46,7 @@ let ``given`` user : TestStep = fun ctx -> let alreadyExists, knownUser = ctx.Users.TryGetValue(user) if not alreadyExists then - let secret = Key.createNewRandom () + let secret = SecretKey.createNewRandom () ctx.Users.Add (user, { Secret = secret; SentEvents = ResizeArray(); ReceivedEvents = ResizeArray(); Connection = None }) async { return { ctx with CurrentUser = user } } @@ -120,8 +120,8 @@ let latest n : FilterFactory = let eventsFrom who : FilterFactory = fun ctx -> let user = ctx.Users[who] - let pubkey = user.Secret |> Key.getPubKey |> fun x -> x.ToBytes() |> Utils.toHex - $"""{{"authors": ["{pubkey}"]}}""" + let author = user.Secret |> SecretKey.getPubKey |> fun x -> Author.toBytes x |> Utils.toHex + $"""{{"authors": ["{author}"]}}""" let ``send event`` eventFactory : TestStep = fun ctx -> async { diff --git a/Nostra/Bech32.fs b/Nostra/Bech32.fs index af5e5c0..c2410b3 100644 --- a/Nostra/Bech32.fs +++ b/Nostra/Bech32.fs @@ -120,30 +120,29 @@ module Shareable = open NBitcoin.Secp256k1 type Relay = string - type Author = XOnlyPubKey type ShareableEntity = - | NSec of ECPrivKey - | NPub of XOnlyPubKey + | NSec of SecretKey + | NPub of AuthorId | Note of EventId - | NProfile of XOnlyPubKey * Relay list - | NEvent of EventId * Relay list * Author option * Kind option + | NProfile of AuthorId * Relay list + | NEvent of EventId * Relay list * AuthorId option * Kind option | NRelay of Relay let private _encode hrp bytesArr = Bech32.encode hrp (bytesArr |> Array.toList) let encode = function - | NSec ecPrivKey -> + | NSec (SecretKey ecPrivKey) -> let bytes = Array.create 32 0uy ecPrivKey.WriteToSpan bytes bytes |> _encode "nsec" - | NPub xOnlyPubKey -> - XOnlyPubKey.toBytes xOnlyPubKey |> _encode "npub" + | NPub author -> + Author.toBytes author |> _encode "npub" | Note(EventId eventId) -> eventId |> _encode "note" - | NProfile(xOnlyPubKey, relays) -> - let pubkey = XOnlyPubKey.toBytes xOnlyPubKey |> Array.toList - let encodedPubKey = 0uy :: 32uy :: pubkey + | NProfile(author, relays) -> + let author = Author.toBytes author |> Array.toList + let encodedPubKey = 0uy :: 32uy :: author let encodedRelays = relays |> List.map (Encoding.ASCII.GetBytes >> Array.toList) @@ -159,7 +158,7 @@ module Shareable = |> List.concat let encodedAuthor = author - |> Option.map (fun author -> 2uy :: 32uy :: List.ofArray (XOnlyPubKey.toBytes author)) + |> Option.map (fun author -> 2uy :: 32uy :: List.ofArray (Author.toBytes author)) |> Option.defaultValue [] let encodedKind = kind @@ -197,11 +196,12 @@ module Shareable = | "nsec" -> ECPrivKey.TryCreate byteArray |> Option.ofTuple + |> Option.map SecretKey |> Option.map NSec | "npub" -> ECXOnlyPubKey.TryCreate byteArray |> Option.ofTuple - |> Option.map XOnlyPubKey + |> Option.map AuthorId |> Option.map NPub | "note" -> Some (Note (EventId byteArray)) @@ -210,7 +210,7 @@ module Shareable = | [[secKey]; relays; _; _] -> ECXOnlyPubKey.TryCreate (secKey |> List.toArray) |> Option.ofTuple - |> Option.map XOnlyPubKey + |> Option.map AuthorId |> Option.map (fun key -> NProfile(key, relays |> List.map (List.toArray >> Encoding.ASCII.GetString))) | _ -> None | "nevent" -> @@ -224,7 +224,7 @@ module Shareable = |> Option.bind (fun author -> ECXOnlyPubKey.TryCreate (author |> List.toArray) |> Option.ofTuple - |> Option.map XOnlyPubKey), + |> Option.map AuthorId), kinds |> List.tryHead |> Option.map (List.toArray >> Utils.fromBE >> LanguagePrimitives.EnumOfValue) @@ -238,8 +238,8 @@ module Shareable = | _ -> None ) - let encodeNpub xonlypubkey = - encode (NPub xonlypubkey) + let encodeNpub author = + encode (NPub author) let decodeNpub str = decode str diff --git a/Nostra/Client.fs b/Nostra/Client.fs index a13d507..24c8779 100644 --- a/Nostra/Client.fs +++ b/Nostra/Client.fs @@ -16,12 +16,12 @@ module Client = type SubscriptionFilter = { Ids: EventId list Kinds: Kind list - Authors: XOnlyPubKey list + Authors: AuthorId list Limit: int option Since: DateTime option Until: DateTime option Events: EventId list - PubKeys: XOnlyPubKey list } + PubKeys: AuthorId list } module Request = [] @@ -42,9 +42,9 @@ module Client = [] |> encodeList "ids" filter.Ids Encode.eventId |> encodeList "kinds" filter.Kinds Encode.Enum.int - |> encodeList "authors" filter.Authors Encode.xOnlyPubkey + |> encodeList "authors" filter.Authors Encode.author |> encodeList "#e" filter.Events Encode.eventId - |> encodeList "#p" filter.PubKeys Encode.xOnlyPubkey + |> encodeList "#p" filter.PubKeys Encode.author |> encodeOption "limit" filter.Limit Encode.int |> encodeOption "since" filter.Since Encode.unixDateTime |> encodeOption "until" filter.Until Encode.unixDateTime diff --git a/Nostra/Event.fs b/Nostra/Event.fs index 5827b8c..dd14d38 100644 --- a/Nostra/Event.fs +++ b/Nostra/Event.fs @@ -23,6 +23,7 @@ type Kind = | HideMessage = 43 | MuteUser = 44 | ReplaceableStart = 10_000 + | RelayList = 10_002 | ReplaceableEnd = 20_000 | EphemeralStart = 20_000 | EphemeralEnd = 30_000 @@ -31,7 +32,7 @@ type Kind = type Event = { Id: EventId - PubKey: XOnlyPubKey + PubKey: AuthorId CreatedAt: DateTime Kind: Kind Tags: Tag list @@ -42,7 +43,6 @@ type Event = module Event = open Utils - type UnsignedEvent = { CreatedAt: DateTime Kind: Kind @@ -54,7 +54,7 @@ module Event = let event: Decoder = Decode.object (fun get -> { Id = get.Required.Field "id" Decode.eventId - PubKey = get.Required.Field "pubkey" Decode.xOnlyPubkey + PubKey = get.Required.Field "pubkey" Decode.author CreatedAt = get.Required.Field "created_at" Decode.unixDateTime Kind = get.Required.Field "kind" Decode.Enum.int Tags = get.Required.Field "tags" Tag.Decode.tagList @@ -65,18 +65,18 @@ module Event = let event (event: Event) = Encode.object [ "id", Encode.eventId event.Id - "pubkey", Encode.xOnlyPubkey event.PubKey + "pubkey", Encode.author event.PubKey "created_at", Encode.unixDateTime event.CreatedAt "kind", Encode.Enum.int event.Kind "tags", Tag.Encode.tagList event.Tags "content", Encode.string event.Content "sig", Encode.schnorrSignature event.Signature ] - let serializeForEventId (pubkey: XOnlyPubKey) (event: UnsignedEvent) = + let serializeForEventId (author: AuthorId) (event: UnsignedEvent) = Encode.toCanonicalForm ( Encode.list [ Encode.int 0 - Encode.xOnlyPubkey pubkey + Encode.author author Encode.unixDateTime event.CreatedAt Encode.Enum.int event.Kind Tag.Encode.tagList event.Tags @@ -94,6 +94,9 @@ module Event = let createNote content = create Kind.Text [] content + let createContactsEvent contacts = + create Kind.Contacts (contacts |> List.map Tag.authorTag) "" + let createReplyEvent (replyTo: EventId) content = create Kind.Text [ Tag.replyTag replyTo "" ] content @@ -106,13 +109,16 @@ module Event = let createProfileEvent (profile : Profile) = create Kind.Metadata [] (profile |> Profile.Encode.profile |> Encode.toCanonicalForm) - let sharedKey (XOnlyPubKey he) (mySecret: ECPrivKey) = + let createRelayListEvent relays = + create Kind.RelayList (relays |> List.map Tag.relayTag) + + let sharedKey (AuthorId he) (SecretKey mySecret) = let ecPubKey = ReadOnlySpan(Array.insertAt 0 2uy (he.ToBytes())) let hisPubKey = ECPubKey.Create ecPubKey let sharedPubKey = hisPubKey.GetSharedPubkey(mySecret).ToBytes() sharedPubKey[1..] - let createEncryptedDirectMessage (recipient: XOnlyPubKey) (secret: ECPrivKey) content = + let createEncryptedDirectMessage (recipient: AuthorId) (secret: SecretKey) content = let sharedPubKey = sharedKey recipient secret let iv, encryptedContent = Encryption.encrypt sharedPubKey content @@ -121,7 +127,7 @@ module Event = [ Tag.encryptedTo recipient ] $"{Convert.ToBase64String(encryptedContent)}?iv={Convert.ToBase64String(iv)}" - let decryptDirectMessage (secret: ECPrivKey) (event: Event) = + let decryptDirectMessage (secret: SecretKey) (event: Event) = let message = event.Content let parts = message.Split "?iv=" |> Array.map Convert.FromBase64String let sharedPubKey = sharedKey event.PubKey secret @@ -133,27 +139,27 @@ module Event = Tags = event.Tags Content = event.Content } - let getEventId (pubkey: XOnlyPubKey) (event: UnsignedEvent) = - event |> serializeForEventId pubkey |> Encoding.UTF8.GetBytes |> SHA256.HashData + let getEventId (author: AuthorId) (event: UnsignedEvent) = + event |> serializeForEventId author |> Encoding.UTF8.GetBytes |> SHA256.HashData - let sign (secret: ECPrivKey) (event: UnsignedEvent) : Event = - let pubkey = secret |> Key.getPubKey |> XOnlyPubKey - let eventId = event |> getEventId pubkey + let sign (secret: SecretKey) (event: UnsignedEvent) : Event = + let author = secret |> SecretKey.getPubKey + let eventId = event |> getEventId author { Id = EventId eventId - PubKey = pubkey + PubKey = author CreatedAt = event.CreatedAt Kind = event.Kind Tags = event.Tags Content = event.Content - Signature = secret.SignBIP340 eventId |> SchnorrSignature } + Signature = SecretKey.sign eventId secret |> SchnorrSignature } let verify (event: Event) = - let EventId id, XOnlyPubKey pubkey, SchnorrSignature signature = + let EventId id, AuthorId author, SchnorrSignature signature = event.Id, event.PubKey, event.Signature let computedId = event |> toUnsignedEvent |> getEventId event.PubKey - computedId = id && pubkey.SigVerifyBIP340(signature, id) + computedId = id && author.SigVerifyBIP340(signature, id) let isEphemeral (event: Event) = event.Kind >= Kind.EphemeralStart && @@ -171,9 +177,9 @@ module Event = let expirationUnixDateTime (event: Event) = event.Tags - |> List.ungroup - |> List.tryFind (fun (tagName, _) -> tagName = "expiration") - |> Option.bind (fun (_, expirationTagValue) -> expirationTagValue |> Int32.TryParse |> Option.ofTuple) + |> Tag.findByKey "expiration" + |> List.tryHead + |> Option.bind (Int32.TryParse >> Option.ofTuple) let isExpired (event: Event) (datetime: DateTime) = event diff --git a/Nostra/Nostra.fsproj b/Nostra/Nostra.fsproj index 426e63e..e855ca9 100644 --- a/Nostra/Nostra.fsproj +++ b/Nostra/Nostra.fsproj @@ -7,7 +7,7 @@ - + diff --git a/Nostra/Key.fs b/Nostra/SecretKey.fs similarity index 61% rename from Nostra/Key.fs rename to Nostra/SecretKey.fs index a253ffc..48eae6d 100644 --- a/Nostra/Key.fs +++ b/Nostra/SecretKey.fs @@ -4,13 +4,18 @@ open System open System.Security.Cryptography open NBitcoin.Secp256k1 +type SecretKey = SecretKey of ECPrivKey + [] -module Key = +module SecretKey = let createNewRandom () = fun _ -> ECPrivKey.TryCreate(ReadOnlySpan(RandomNumberGenerator.GetBytes(32))) |> Seq.initInfinite |> Seq.skipWhile (fun (succeed, _) -> not succeed) |> Seq.map snd |> Seq.head + |> SecretKey + + let getPubKey (SecretKey secret) = secret.CreateXOnlyPubKey() |> AuthorId - let getPubKey (secret: ECPrivKey) = secret.CreateXOnlyPubKey() + let sign content (SecretKey secret) = secret.SignBIP340 content \ No newline at end of file diff --git a/Nostra/Tag.fs b/Nostra/Tag.fs index e47aa5c..ab7383d 100644 --- a/Nostra/Tag.fs +++ b/Nostra/Tag.fs @@ -19,11 +19,17 @@ module Tag = let replyTag (EventId replyTo) uri = Tag("p", [ toHex replyTo; uri ]) - let encryptedTo (XOnlyPubKey pubkey) = Tag("p", [ toHex (pubkey.ToBytes()) ]) + let authorTag (AuthorId pubkey) = Tag("p", [ toHex (pubkey.ToBytes()) ]) + + let encryptedTo = authorTag let eventRefTag (EventId eventId) = Tag("e", [ toHex eventId ]) let rootEventRefTag (EventId eventId) = Tag("e", [ toHex eventId; ""; "root" ]) + let relayTag relay = Tag("r", relay) + let relayReadTag relayUri = relayTag [ relayUri; "read" ] + let relayWriteTag relayUri = relayTag [ relayUri; "write" ] + open Thoth.Json.Net module Decode = diff --git a/Nostra/Types.fs b/Nostra/Types.fs index aaa237c..59ad558 100644 --- a/Nostra/Types.fs +++ b/Nostra/Types.fs @@ -8,24 +8,23 @@ open System.IO open Newtonsoft.Json type EventId = EventId of byte[] -type XOnlyPubKey = XOnlyPubKey of ECXOnlyPubKey +type AuthorId = AuthorId of ECXOnlyPubKey type ProfileName = string type SubscriptionId = string -type Uri_ = string type SchnorrSignature = SchnorrSignature of SecpSchnorrSignature type SerializedEvent = string -module XOnlyPubKey = +module Author = let parse = function | Base64 64 byteArray -> match (ECXOnlyPubKey.TryCreate byteArray) with - | true, pubkey -> Ok (XOnlyPubKey pubkey) + | true, author -> Ok (AuthorId author) | _ -> Error "The byte array is not a valid xonly publick key." | invalid -> - Error $"XOnlyPubKey is invalid. The byte array is not 32 length but %i{invalid.Length / 2}" + Error $"Author is invalid. The byte array is not 32 length but %i{invalid.Length / 2}" - let toBytes (XOnlyPubKey ecpk) = + let toBytes (AuthorId ecpk) = ecpk.ToBytes() let equals pk1 pk2 = @@ -79,9 +78,9 @@ module Decode = |> Decode.map (fun x -> EventId.parse x) |> Decode.andThen ofResult - let xOnlyPubkey: Decoder = + let author: Decoder = Decode.string - |> Decode.map XOnlyPubKey.parse + |> Decode.map Author.parse |> Decode.andThen ofResult let schnorrSignature: Decoder = @@ -98,8 +97,8 @@ module Encode = let eventId (EventId id) = Encode.string (toHex id) - let xOnlyPubkey (XOnlyPubKey pubkey) = - Encode.string (toHex (pubkey.ToBytes())) + let author (AuthorId author) = + Encode.string (toHex (author.ToBytes())) let schnorrSignature (SchnorrSignature signature) = Encode.string (toHex (signature.ToBytes()))