From 50dc467e22f56440f819c8953959a5d39980fd8e Mon Sep 17 00:00:00 2001 From: Lucas Ontivero Date: Sun, 21 Jan 2024 21:23:57 -0300 Subject: [PATCH] C# interop example (#29) --- Nostra.CSharp/GlobalUsings.cs | 10 +++++ Nostra.CSharp/Nostra.CSharp.csproj | 14 +++++++ Nostra.CSharp/Program.cs | 59 ++++++++++++++++++++++++++++++ Nostra.CSharp/README.md | 43 ++++++++++++++++++++++ Nostra.Client/Program.fs | 8 ++-- Nostra.Client/User.fs | 4 +- Nostra.Tests/Bech32.fs | 6 +-- Nostra.Tests/TestingFramework.fs | 2 +- Nostra.sln | 6 +++ Nostra/Bech32.fs | 30 +++++++++++++-- Nostra/Client.fs | 42 +++++++++++++++++++-- Nostra/Event.fs | 22 ++++++++--- Nostra/Profile.fs | 1 + Nostra/SecretKey.fs | 2 + Nostra/Tag.fs | 11 ++++++ Nostra/Types.fs | 15 ++++++-- 16 files changed, 248 insertions(+), 27 deletions(-) create mode 100644 Nostra.CSharp/GlobalUsings.cs create mode 100644 Nostra.CSharp/Nostra.CSharp.csproj create mode 100644 Nostra.CSharp/Program.cs create mode 100644 Nostra.CSharp/README.md diff --git a/Nostra.CSharp/GlobalUsings.cs b/Nostra.CSharp/GlobalUsings.cs new file mode 100644 index 0000000..c15df02 --- /dev/null +++ b/Nostra.CSharp/GlobalUsings.cs @@ -0,0 +1,10 @@ +global using Microsoft.FSharp.Collections; +global using Microsoft.FSharp.Core; +global using static Microsoft.FSharp.Core.FSharpOption; +global using static Microsoft.FSharp.Core.FSharpOption; +global using static Nostra.Client.Request; +global using static Nostra.Client.Response; +global using RelayConnection = Nostra.Client.RelayConnection; +global using EventId = Nostra.EventIdModule; +global using Event = Nostra.EventModule; +global using SecretKey = Nostra.SecretKeyModule; diff --git a/Nostra.CSharp/Nostra.CSharp.csproj b/Nostra.CSharp/Nostra.CSharp.csproj new file mode 100644 index 0000000..1206a35 --- /dev/null +++ b/Nostra.CSharp/Nostra.CSharp.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + Exe + enable + enable + + + + + + + diff --git a/Nostra.CSharp/Program.cs b/Nostra.CSharp/Program.cs new file mode 100644 index 0000000..76697c4 --- /dev/null +++ b/Nostra.CSharp/Program.cs @@ -0,0 +1,59 @@ +namespace Nostra.CSharp; +using NostrListenerCallback = Action>; + +public static class Program +{ + public static async Task Main(string[] args) + { + var relay = await Client.ConnectToRelayAsync(new Uri("wss://relay.damus.io")); + if (args.Length > 1) + { + var textToPublish = args[1]; + PublishNote(relay, textToPublish); + } + await ListenEverything(relay); + } + + private static void PublishNote(RelayConnection relay, string noteText) + { + // Creates a note with the given text, sign it using a new randomly-generated + // secret key and send it to the connected relay. + var unsignedEvent = Event.CreateNote(noteText); + var signedEvent = Event.Sign(SecretKey.CreateRandom(), unsignedEvent); + Client.Publish(signedEvent, relay); + + // Encode the event as a shareable bech32 nevent event and prints it + // in the console. + Console.WriteLine(Shareable.ToNEvent( + signedEvent.Id, + ToFSharpList(["wss://relay.damus.io"]), + Some(signedEvent.PubKey), + Some(signedEvent.Kind))); + + // Serialized the event as SJON and prints it in the console. + var signedEventAsJson = Event.Serialize(signedEvent); + Console.WriteLine(signedEventAsJson); + } + + private static async Task ListenEverything(RelayConnection relay) + { + // Subscribes to all the new events. + var filter = Filter.since(DateTime.UtcNow, Filter.all); + var filters = ToFSharpList([filter]); + Client.Subscribe("all", filters, relay); + + // Start listeniong for all the events. + await Client.StartListening(FuncConvert.ToFSharpFunc((NostrListenerCallback) (mresult => + { + var relayMessage = Result.requiresOk(mresult); + if (relayMessage.IsRMEvent) + { + var (_, relayEvent) = GetEvent(relayMessage); + Console.WriteLine(Event.Serialize(relayEvent)); + } + })), relay); + } + + private static FSharpList ToFSharpList(this IEnumerable seq) => + seq.Aggregate(FSharpList.Empty, (state, e) => new FSharpList(e, state)); +} diff --git a/Nostra.CSharp/README.md b/Nostra.CSharp/README.md new file mode 100644 index 0000000..03d6708 --- /dev/null +++ b/Nostra.CSharp/README.md @@ -0,0 +1,43 @@ +# C# Interop demo + +This is a simple working example of how to consume Nostra from C# projects. + +### Create a note, sign it and publish it +Creates a note with a text, signs it using a new randomly-generated secret key and sends it to the connected relay. +```c# +// Connect to the relay +var relay = await Client.ConnectToRelayAsync(new Uri("wss://relay.damus.io")); + +// Create a note +var unsignedEvent = Event.CreateNote("Hello everybody!"); + +// Sign the note +var signedEvent = Event.Sign(SecretKey.CreateRandom(), unsignedEvent); + +// Publish the note in the relay +Client.Publish(signedEvent, relay); +``` + +#### Subscribe to events +Creates a filter to match all events created from now and start listening for them. +Display the raw json for those messages received from the relay that are events. +```c# +// Connect to the relay +var relay = await Client.ConnectToRelayAsync(new Uri("wss://relay.damus.io")); + +// Subscribes to all the new events. +var filter = Filter.since(DateTime.UtcNow, Filter.all); +var filters = ToFSharpList([filter]); +Client.Subscribe("all", filters, relay); + +// Start listeniong for all the events. +await Client.StartListening(FuncConvert.ToFSharpFunc((NostrListenerCallback) (mresult => +{ + var relayMessage = Result.requiresOk(mresult); + if (relayMessage.IsRMEvent) + { + var (_, relayEvent) = GetEvent(relayMessage); + Console.WriteLine(Event.Serialize(relayEvent)); + } +})), relay); +``` \ No newline at end of file diff --git a/Nostra.Client/Program.fs b/Nostra.Client/Program.fs index 8c6d9ce..e9ca11d 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 - Author.toBytes event.PubKey + AuthorId.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 (Author.toBytes pubkey) ) |> Option.get + let channel = Shareable.decodeNpub channel' |> Option.map (fun pubkey -> EventId (AuthorId.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 Author.equals knownAuthors + |> List.notInBy AuthorId.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 -> Author.toBytes p) , c ) + | Author p -> AuthorId.toBytes p) , c ) |> Map.ofList let addContact contactKey metadata = diff --git a/Nostra.Client/User.fs b/Nostra.Client/User.fs index f5894ba..b0f48fa 100644 --- a/Nostra.Client/User.fs +++ b/Nostra.Client/User.fs @@ -210,12 +210,12 @@ module User = { user with contacts = List.distinctBy (fun c -> c.key) contacts } let subscribeAuthors (authors : AuthorId list) user = - let authors' = List.distinctBy Author.toBytes (user.subscribedAuthors @ authors) + let authors' = List.distinctBy AuthorId.toBytes (user.subscribedAuthors @ authors) { user with subscribedAuthors = authors' } let unsubscribeAuthors (authors : AuthorId list) user = let authors' = user.subscribedAuthors - |> List.notInBy (fun a1 a2 -> Author.toBytes a1 = Author.toBytes a2) authors + |> List.notInBy (fun a1 a2 -> AuthorId.toBytes a1 = AuthorId.toBytes a2) authors { user with subscribedAuthors = authors' } let subscribeChannels (channels : Channel list) user = diff --git a/Nostra.Tests/Bech32.fs b/Nostra.Tests/Bech32.fs index cdb0fa1..bbb3b1d 100644 --- a/Nostra.Tests/Bech32.fs +++ b/Nostra.Tests/Bech32.fs @@ -36,7 +36,7 @@ type ``Nip19 Bech32-Shareable entities``(output:ITestOutputHelper) = |> encodeDecode |> function | NPub decodedPubKey -> - should equal (Author.toBytes decodedPubKey) (Author.toBytes author) + should equal (AuthorId.toBytes decodedPubKey) (AuthorId.toBytes author) | _ -> failwith "The entity is not a npub" [] @@ -52,7 +52,7 @@ type ``Nip19 Bech32-Shareable entities``(output:ITestOutputHelper) = [] let ``Encode/Decode nprofile`` () = let nprofile = - let author = Author.parse "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" |> Result.requiresOk + let author = AuthorId.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 (Author.parse "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" |> Result.requiresOk), + Some (AuthorId.parse "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" |> Result.requiresOk), Some Kind.Text ) |> encodeDecode diff --git a/Nostra.Tests/TestingFramework.fs b/Nostra.Tests/TestingFramework.fs index e1de848..ed8008d 100644 --- a/Nostra.Tests/TestingFramework.fs +++ b/Nostra.Tests/TestingFramework.fs @@ -120,7 +120,7 @@ let latest n : FilterFactory = let eventsFrom who : FilterFactory = fun ctx -> let user = ctx.Users[who] - let author = user.Secret |> SecretKey.getPubKey |> fun x -> Author.toBytes x |> Utils.toHex + let author = user.Secret |> SecretKey.getPubKey |> fun x -> AuthorId.toBytes x |> Utils.toHex $"""{{"authors": ["{author}"]}}""" let ``send event`` eventFactory : TestStep = diff --git a/Nostra.sln b/Nostra.sln index c9dc01f..c2ceb80 100644 --- a/Nostra.sln +++ b/Nostra.sln @@ -8,6 +8,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Nostra.Relay", "Nostra.Rela EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Nostra.Tests", "Nostra.Tests\Nostra.Tests.fsproj", "{36AE25AA-6314-4D66-A758-4442FE616BEE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nostra.CSharp", "Nostra.CSharp\Nostra.CSharp.csproj", "{24396D29-6F1E-46A0-83DA-A3299C5F3470}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,5 +32,9 @@ Global {36AE25AA-6314-4D66-A758-4442FE616BEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {36AE25AA-6314-4D66-A758-4442FE616BEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {36AE25AA-6314-4D66-A758-4442FE616BEE}.Release|Any CPU.Build.0 = Release|Any CPU + {24396D29-6F1E-46A0-83DA-A3299C5F3470}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24396D29-6F1E-46A0-83DA-A3299C5F3470}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24396D29-6F1E-46A0-83DA-A3299C5F3470}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24396D29-6F1E-46A0-83DA-A3299C5F3470}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Nostra/Bech32.fs b/Nostra/Bech32.fs index c2410b3..78b4c0c 100644 --- a/Nostra/Bech32.fs +++ b/Nostra/Bech32.fs @@ -137,11 +137,11 @@ module Shareable = ecPrivKey.WriteToSpan bytes bytes |> _encode "nsec" | NPub author -> - Author.toBytes author |> _encode "npub" + AuthorId.toBytes author |> _encode "npub" | Note(EventId eventId) -> eventId |> _encode "note" | NProfile(author, relays) -> - let author = Author.toBytes author |> Array.toList + let author = AuthorId.toBytes author |> Array.toList let encodedPubKey = 0uy :: 32uy :: author let encodedRelays = relays @@ -158,7 +158,7 @@ module Shareable = |> List.concat let encodedAuthor = author - |> Option.map (fun author -> 2uy :: 32uy :: List.ofArray (Author.toBytes author)) + |> Option.map (fun author -> 2uy :: 32uy :: List.ofArray (AuthorId.toBytes author)) |> Option.defaultValue [] let encodedKind = kind @@ -238,6 +238,7 @@ module Shareable = | _ -> None ) + [] let encodeNpub author = encode (NPub author) @@ -247,6 +248,10 @@ module Shareable = | NPub pk -> Some pk | _ -> None) + [] + let _decodeNpub = decodeNpub >> Option.get + + [] let encodeNsec secret = encode (NSec secret) @@ -256,6 +261,10 @@ module Shareable = | NSec sk -> Some sk | _ -> None) + [] + let _decodeNsec = decodeNsec >> Option.get + + [] let encodeNote note = encode (Note note) @@ -265,6 +274,10 @@ module Shareable = | Note eventId -> Some eventId | _ -> None) + [] + let _decodeNote = decodeNote >> Option.get + + [] let encodeNprofile profile = encode (NProfile profile) @@ -274,6 +287,10 @@ module Shareable = | NProfile (pk, relays) -> Some (pk, relays) | _ -> None) + [] + let _decodeNprofile = decodeNprofile >> Option.get + + [] let encodeNevent event = encode (NEvent event) @@ -283,6 +300,10 @@ module Shareable = | NEvent (eventId, relays, author, kind) -> Some (eventId, relays, author, kind) | _ -> None) + [] + let _decodeNevent = decodeNevent >> Option.get + + [] let encodeNrelay relay = encode (NRelay relay) @@ -291,3 +312,6 @@ module Shareable = |> Option.bind (function | NRelay relays -> Some relays | _ -> None) + + [] + let _decodeNrelay = decodeNrelay >> Option.get \ No newline at end of file diff --git a/Nostra/Client.fs b/Nostra/Client.fs index 24c8779..26bf749 100644 --- a/Nostra/Client.fs +++ b/Nostra/Client.fs @@ -42,9 +42,9 @@ module Client = [] |> encodeList "ids" filter.Ids Encode.eventId |> encodeList "kinds" filter.Kinds Encode.Enum.int - |> encodeList "authors" filter.Authors Encode.author + |> encodeList "authors" filter.Authors Encode.authorId |> encodeList "#e" filter.Events Encode.eventId - |> encodeList "#p" filter.PubKeys Encode.author + |> encodeList "#p" filter.PubKeys Encode.authorId |> encodeOption "limit" filter.Limit Encode.int |> encodeOption "since" filter.Since Encode.unixDateTime |> encodeOption "until" filter.Until Encode.unixDateTime @@ -123,6 +123,27 @@ module Client = | RMACK of EventId * bool * string | RMEOSE of string + // Functions for interoperability with C# + [] + let getEvent = function + | RMEvent(sid, evnt) -> sid, evnt + | _ -> failwith "Not an event relay message" + + [] + let getNotice = function + | RMNotice str -> str + | _ -> failwith "Not a notice relay message" + + [] + let getAck = function + | RMACK(eventId, success, str) -> eventId, success, str + | _ -> failwith "Not an acknowledge relay message" + + [] + let getEOSE = function + | RMEOSE str -> str + | _ -> failwith "Not an end-of-stream relay message" + module Decode = let relayMessage: Decoder = Decode.index 0 Decode.string @@ -228,6 +249,19 @@ module Client = } } + type RelayConnection = { + publish: Event -> unit + subscribe: SubscriptionId -> SubscriptionFilter list -> unit + startListening: (Result -> unit) -> Async + } + + [] + let publish event relay = relay.publish event + [] + let subscribe subscriptionId filterList relay = relay.subscribe subscriptionId filterList + [] + let startListening callback relay = relay.startListening callback |> Async.StartAsTask + let connectToRelay uri = let ws = new ClientWebSocket() let ctx = Communication.buildContext ws Console.Out @@ -241,11 +275,11 @@ module Client = let subscribe sid filters = pushToRelay (ClientMessage.CMSubscribe (sid, filters)) let publish event = pushToRelay (ClientMessage.CMEvent event) - return {| + return { publish = publish subscribe = subscribe startListening = receiveLoop - |} + } } diff --git a/Nostra/Event.fs b/Nostra/Event.fs index dd14d38..968b402 100644 --- a/Nostra/Event.fs +++ b/Nostra/Event.fs @@ -7,7 +7,7 @@ open Microsoft.FSharp.Core open NBitcoin.Secp256k1 open Thoth.Json.Net - +[] type Kind = | Metadata = 0 | Text = 1 @@ -30,6 +30,7 @@ type Kind = | ParameterizableReplaceableStart = 30_000 | ParameterizableReplaceableEnd = 40_000 +[] type Event = { Id: EventId PubKey: AuthorId @@ -39,6 +40,7 @@ type Event = Content: string Signature: SchnorrSignature } +[] [] module Event = open Utils @@ -54,7 +56,7 @@ module Event = let event: Decoder = Decode.object (fun get -> { Id = get.Required.Field "id" Decode.eventId - PubKey = get.Required.Field "pubkey" Decode.author + PubKey = get.Required.Field "pubkey" Decode.authorId 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,7 +67,7 @@ module Event = let event (event: Event) = Encode.object [ "id", Encode.eventId event.Id - "pubkey", Encode.author event.PubKey + "pubkey", Encode.authorId event.PubKey "created_at", Encode.unixDateTime event.CreatedAt "kind", Encode.Enum.int event.Kind "tags", Tag.Encode.tagList event.Tags @@ -76,36 +78,43 @@ module Event = Encode.toCanonicalForm ( Encode.list [ Encode.int 0 - Encode.author author + Encode.authorId author Encode.unixDateTime event.CreatedAt Encode.Enum.int event.Kind Tag.Encode.tagList event.Tags Encode.string event.Content ] ) + [] let serialize event = event |> Encode.event |> Encode.toCanonicalForm + [] let create kind tags content = { CreatedAt = DateTime.UtcNow Kind = kind - Tags = tags + Tags = tags |> Seq.toList Content = content } + [] 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 + [] let createChannelMessage (replyTo: EventId) content = create Kind.ChannelMessage [ Tag.rootEventRefTag replyTo ] content + [] let createDeleteEvent (ids: EventId list) content = create Kind.Delete (ids |> List.map Tag.eventRefTag) content + [] let createProfileEvent (profile : Profile) = create Kind.Metadata [] (profile |> Profile.Encode.profile |> Encode.toCanonicalForm) @@ -142,10 +151,10 @@ module Event = let getEventId (author: AuthorId) (event: UnsignedEvent) = event |> serializeForEventId author |> Encoding.UTF8.GetBytes |> SHA256.HashData + [] let sign (secret: SecretKey) (event: UnsignedEvent) : Event = let author = secret |> SecretKey.getPubKey let eventId = event |> getEventId author - { Id = EventId eventId PubKey = author CreatedAt = event.CreatedAt @@ -154,6 +163,7 @@ module Event = Content = event.Content Signature = SecretKey.sign eventId secret |> SchnorrSignature } + [] let verify (event: Event) = let EventId id, AuthorId author, SchnorrSignature signature = event.Id, event.PubKey, event.Signature diff --git a/Nostra/Profile.fs b/Nostra/Profile.fs index ed8446b..3a95933 100644 --- a/Nostra/Profile.fs +++ b/Nostra/Profile.fs @@ -2,6 +2,7 @@ namespace Nostra open Thoth.Json.Net +[] type Profile = { Name : string About : string diff --git a/Nostra/SecretKey.fs b/Nostra/SecretKey.fs index 48eae6d..1933f8c 100644 --- a/Nostra/SecretKey.fs +++ b/Nostra/SecretKey.fs @@ -4,10 +4,12 @@ open System open System.Security.Cryptography open NBitcoin.Secp256k1 +[] type SecretKey = SecretKey of ECPrivKey [] module SecretKey = + [] let createNewRandom () = fun _ -> ECPrivKey.TryCreate(ReadOnlySpan(RandomNumberGenerator.GetBytes(32))) |> Seq.initInfinite diff --git a/Nostra/Tag.fs b/Nostra/Tag.fs index ab7383d..6068089 100644 --- a/Nostra/Tag.fs +++ b/Nostra/Tag.fs @@ -1,5 +1,6 @@ namespace Nostra +[] type Tag = string * (string list) type SingleTag = string * string @@ -17,13 +18,23 @@ module Tag = |> List.filter (fun (k,_) -> k = key) |> List.map snd + [] + let create (tag : string) (values: string list) = + Tag (tag, values) + + [] let replyTag (EventId replyTo) uri = Tag("p", [ toHex replyTo; uri ]) + [] 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) diff --git a/Nostra/Types.fs b/Nostra/Types.fs index 59ad558..ecbdb3c 100644 --- a/Nostra/Types.fs +++ b/Nostra/Types.fs @@ -7,7 +7,9 @@ open Thoth.Json.Net open System.IO open Newtonsoft.Json +[] type EventId = EventId of byte[] +[] type AuthorId = AuthorId of ECXOnlyPubKey type ProfileName = string type SubscriptionId = string @@ -15,7 +17,8 @@ type SubscriptionId = string type SchnorrSignature = SchnorrSignature of SecpSchnorrSignature type SerializedEvent = string -module Author = +module AuthorId = + [] let parse = function | Base64 64 byteArray -> match (ECXOnlyPubKey.TryCreate byteArray) with @@ -24,6 +27,7 @@ module Author = | invalid -> Error $"Author is invalid. The byte array is not 32 length but %i{invalid.Length / 2}" + [] let toBytes (AuthorId ecpk) = ecpk.ToBytes() @@ -31,6 +35,7 @@ module Author = toBytes pk1 = toBytes pk2 module SchnorrSignature = + [] let parse = function | Base64 128 byteArray -> match (SecpSchnorrSignature.TryCreate byteArray) with @@ -40,10 +45,12 @@ module SchnorrSignature = Error $"SchnorrSignature is invalid. The byte array is not 64 length but %i{invalid.Length / 2}" module EventId = + [] let parse = function | Base64 64 byteArray -> Ok (EventId byteArray) | invalid -> Error $"EventId is invalid. The byte array is not 32 length but %i{invalid.Length / 2}" + [] let toBytes (EventId eid) = eid @@ -78,9 +85,9 @@ module Decode = |> Decode.map (fun x -> EventId.parse x) |> Decode.andThen ofResult - let author: Decoder = + let authorId: Decoder = Decode.string - |> Decode.map Author.parse + |> Decode.map AuthorId.parse |> Decode.andThen ofResult let schnorrSignature: Decoder = @@ -97,7 +104,7 @@ module Encode = let eventId (EventId id) = Encode.string (toHex id) - let author (AuthorId author) = + let authorId (AuthorId author) = Encode.string (toHex (author.ToBytes())) let schnorrSignature (SchnorrSignature signature) =