packages feed

signet (empty) → 0.2025.5.5

raw patch · 74 files changed

+2125/−0 lines, 74 filesdep +basedep +bytestringdep +case-insensitive

Dependencies added: base, bytestring, case-insensitive, crypton, exceptions, http-types, memory, signet, tasty, tasty-hunit, text, time, transformers

Files

+ CHANGELOG.md view
@@ -0,0 +1,4 @@+# Change log++Signet follows the [Package Versioning Policy](https://pvp.haskell.org). You can+find release notes [on GitHub](https://github.com/MercuryTechnologies/signet/releases).
+ LICENSE.txt view
@@ -0,0 +1,14 @@+BSD Zero Clause License++Copyright (c) 2025 Mercury Technologies, Inc.++Permission to use, copy, modify, and/or distribute this software for any+purpose with or without fee is hereby granted.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR+PERFORMANCE OF THIS SOFTWARE.
+ README.md view
@@ -0,0 +1,4 @@+# Signet++Signet is a Haskell library for working with+[Standard Webhooks](https://www.standardwebhooks.com).
+ signet.cabal view
@@ -0,0 +1,165 @@+cabal-version: 3.0+name: signet+version: 0.2025.5.5+synopsis: Standard Webhooks+description:+  Signet is a Haskell library for working with Standard Webhooks.++category: Web+license: 0BSD+license-file: LICENSE.txt+maintainer: Mercury Technologies, Inc.+extra-doc-files:+  CHANGELOG.md+  README.md++source-repository head+  location: https://github.com/MercuryTechnologies/signet+  type: git++flag pedantic+  default: False+  manual: True++common library+  build-depends: base ^>=4.19 || ^>=4.20 || ^>=4.21+  default-language: Haskell2010+  ghc-options:+    -Weverything+    -Wno-all-missed-specialisations+    -Wno-implicit-prelude+    -Wno-missed-specialisations+    -Wno-missing-deriving-strategies+    -Wno-missing-export-lists+    -Wno-missing-kind-signatures+    -Wno-missing-safe-haskell-mode+    -Wno-prepositive-qualified-module+    -Wno-safe+    -Wno-unsafe++  if flag(pedantic)+    ghc-options: -Werror++common executable+  import: library+  ghc-options:+    -rtsopts+    -threaded++common dependencies+  build-depends:+    bytestring ^>=0.12,+    case-insensitive ^>=1.2.1.0,+    crypton ^>=0.34 || ^>=1.0.4,+    exceptions ^>=0.10.7,+    memory ^>=0.18.0,+    time ^>=1.12.2,++library unstable+  import: library, dependencies+  build-depends:+    http-types ^>=0.12.4,+    text ^>=2.0 || ^>=2.1,++  -- cabal-gild: discover source/libraries/unstable+  exposed-modules:+    Signet.Unstable+    Signet.Unstable.Exception.InvalidAsymmetricSignature+    Signet.Unstable.Exception.InvalidId+    Signet.Unstable.Exception.InvalidMessage+    Signet.Unstable.Exception.InvalidPublicKey+    Signet.Unstable.Exception.InvalidSecret+    Signet.Unstable.Exception.InvalidSecretKey+    Signet.Unstable.Exception.InvalidSignature+    Signet.Unstable.Exception.InvalidSigner+    Signet.Unstable.Exception.InvalidSymmetricSignature+    Signet.Unstable.Exception.InvalidTimestamp+    Signet.Unstable.Exception.InvalidVerifier+    Signet.Unstable.Exception.SignetException+    Signet.Unstable.Exception.ToleranceException+    Signet.Unstable.Exception.VerificationException+    Signet.Unstable.Extra.Either+    Signet.Unstable.Extra.Http+    Signet.Unstable.Extra.Maybe+    Signet.Unstable.Type.AsymmetricSignature+    Signet.Unstable.Type.Id+    Signet.Unstable.Type.Message+    Signet.Unstable.Type.Payload+    Signet.Unstable.Type.PublicKey+    Signet.Unstable.Type.Secret+    Signet.Unstable.Type.SecretKey+    Signet.Unstable.Type.Signature+    Signet.Unstable.Type.Signatures+    Signet.Unstable.Type.Signer+    Signet.Unstable.Type.SymmetricSignature+    Signet.Unstable.Type.Timestamp+    Signet.Unstable.Type.Tolerance+    Signet.Unstable.Type.UnknownSignature+    Signet.Unstable.Type.Verifier++  hs-source-dirs: source/libraries/unstable++library+  import: library+  build-depends: signet:unstable+  -- cabal-gild: discover source/libraries/signet+  exposed-modules: Signet+  hs-source-dirs: source/libraries/signet++library test+  import: library, dependencies+  build-depends:+    signet:unstable,+    tasty ^>=1.4 || ^>=1.5.3,+    tasty-hunit ^>=0.10.2,+    transformers ^>=0.6.1.0,++  -- cabal-gild: discover source/libraries/test+  exposed-modules:+    Signet.Unstable.Exception.InvalidAsymmetricSignatureTest+    Signet.Unstable.Exception.InvalidIdTest+    Signet.Unstable.Exception.InvalidMessageTest+    Signet.Unstable.Exception.InvalidPublicKeyTest+    Signet.Unstable.Exception.InvalidSecretKeyTest+    Signet.Unstable.Exception.InvalidSecretTest+    Signet.Unstable.Exception.InvalidSignatureTest+    Signet.Unstable.Exception.InvalidSignerTest+    Signet.Unstable.Exception.InvalidSymmetricSignatureTest+    Signet.Unstable.Exception.InvalidTimestampTest+    Signet.Unstable.Exception.InvalidVerifierTest+    Signet.Unstable.Exception.SignetExceptionTest+    Signet.Unstable.Exception.ToleranceExceptionTest+    Signet.Unstable.Exception.VerificationExceptionTest+    Signet.Unstable.Extra.EitherTest+    Signet.Unstable.Extra.HttpTest+    Signet.Unstable.Extra.MaybeTest+    Signet.Unstable.Extra.Tasty+    Signet.Unstable.Type.AsymmetricSignatureTest+    Signet.Unstable.Type.IdTest+    Signet.Unstable.Type.MessageTest+    Signet.Unstable.Type.PayloadTest+    Signet.Unstable.Type.PublicKeyTest+    Signet.Unstable.Type.SecretKeyTest+    Signet.Unstable.Type.SecretTest+    Signet.Unstable.Type.SignatureTest+    Signet.Unstable.Type.SignaturesTest+    Signet.Unstable.Type.SignerTest+    Signet.Unstable.Type.SymmetricSignatureTest+    Signet.Unstable.Type.TimestampTest+    Signet.Unstable.Type.ToleranceTest+    Signet.Unstable.Type.UnknownSignatureTest+    Signet.Unstable.Type.VerifierTest+    Signet.UnstableTest+    SignetTest++  hs-source-dirs: source/libraries/test++test-suite signet-test-suite+  import: executable+  build-depends:+    signet:test,+    tasty,++  hs-source-dirs: source/test-suite+  main-is: Main.hs+  type: exitcode-stdio-1.0
+ source/libraries/signet/Signet.hs view
@@ -0,0 +1,88 @@+module Signet+  ( -- * Verification+    Signet.Unstable.verifyWebhookText,+    Signet.Unstable.verifyWebhookByteString,+    Signet.Unstable.verifyWebhook,+    Signet.Unstable.verifyWebhookWith,++    -- * Signing+    Signet.Unstable.signWebhook,++    -- * Headers+    Signet.Unstable.Extra.Http.hWebhookId,+    Signet.Unstable.Extra.Http.hWebhookTimestamp,+    Signet.Unstable.Extra.Http.hWebhookSignature,++    -- * Types+    Signet.Unstable.Type.AsymmetricSignature.AsymmetricSignature (..),+    Signet.Unstable.Type.Id.Id (..),+    Signet.Unstable.parseId,+    Signet.Unstable.Type.Message.Message (..),+    Signet.Unstable.parseMessage,+    Signet.Unstable.Type.Payload.Payload (..),+    Signet.Unstable.Type.PublicKey.PublicKey (..),+    Signet.Unstable.Type.Secret.Secret (..),+    Signet.Unstable.Type.SecretKey.SecretKey (..),+    Signet.Unstable.Type.Signature.Signature (Signet.Unstable.AsymmetricSignature, Signet.Unstable.SymmetricSignature),+    Signet.Unstable.Type.Signatures.Signatures (..),+    Signet.Unstable.parseSignatures,+    Signet.Unstable.Type.Signer.Signer (Signet.Unstable.AsymmetricSigner, Signet.Unstable.SymmetricSigner),+    Signet.Unstable.parseSigner,+    Signet.Unstable.Type.SymmetricSignature.SymmetricSignature (..),+    Signet.Unstable.Type.Timestamp.Timestamp (..),+    Signet.Unstable.parseTimestamp,+    Signet.Unstable.Type.Tolerance.Tolerance (..),+    Signet.Unstable.typicalTolerance,+    Signet.Unstable.Type.UnknownSignature.UnknownSignature (..),+    Signet.Unstable.Type.Verifier.Verifier (Signet.Unstable.AsymmetricVerifier, Signet.Unstable.SymmetricVerifier),+    Signet.Unstable.parseVerifier,++    -- * Exceptions+    Signet.Unstable.Exception.InvalidAsymmetricSignature.InvalidAsymmetricSignature (..),+    Signet.Unstable.Exception.InvalidId.InvalidId (..),+    Signet.Unstable.Exception.InvalidMessage.InvalidMessage (..),+    Signet.Unstable.Exception.InvalidPublicKey.InvalidPublicKey (..),+    Signet.Unstable.Exception.InvalidSecret.InvalidSecret (..),+    Signet.Unstable.Exception.InvalidSecretKey.InvalidSecretKey (..),+    Signet.Unstable.Exception.InvalidSignature.InvalidSignature (..),+    Signet.Unstable.Exception.InvalidSigner.InvalidSigner (..),+    Signet.Unstable.Exception.InvalidSymmetricSignature.InvalidSymmetricSignature (..),+    Signet.Unstable.Exception.InvalidTimestamp.InvalidTimestamp (..),+    Signet.Unstable.Exception.InvalidVerifier.InvalidVerifier (..),+    Signet.Unstable.Exception.SignetException.SignetException (..),+    Signet.Unstable.Exception.ToleranceException.ToleranceException (..),+    Signet.Unstable.Exception.VerificationException.VerificationException (..),+  )+where++import qualified Signet.Unstable+import qualified Signet.Unstable.Exception.InvalidAsymmetricSignature+import qualified Signet.Unstable.Exception.InvalidId+import qualified Signet.Unstable.Exception.InvalidMessage+import qualified Signet.Unstable.Exception.InvalidPublicKey+import qualified Signet.Unstable.Exception.InvalidSecret+import qualified Signet.Unstable.Exception.InvalidSecretKey+import qualified Signet.Unstable.Exception.InvalidSignature+import qualified Signet.Unstable.Exception.InvalidSigner+import qualified Signet.Unstable.Exception.InvalidSymmetricSignature+import qualified Signet.Unstable.Exception.InvalidTimestamp+import qualified Signet.Unstable.Exception.InvalidVerifier+import qualified Signet.Unstable.Exception.SignetException+import qualified Signet.Unstable.Exception.ToleranceException+import qualified Signet.Unstable.Exception.VerificationException+import qualified Signet.Unstable.Extra.Http+import qualified Signet.Unstable.Type.AsymmetricSignature+import qualified Signet.Unstable.Type.Id+import qualified Signet.Unstable.Type.Message+import qualified Signet.Unstable.Type.Payload+import qualified Signet.Unstable.Type.PublicKey+import qualified Signet.Unstable.Type.Secret+import qualified Signet.Unstable.Type.SecretKey+import qualified Signet.Unstable.Type.Signature+import qualified Signet.Unstable.Type.Signatures+import qualified Signet.Unstable.Type.Signer+import qualified Signet.Unstable.Type.SymmetricSignature+import qualified Signet.Unstable.Type.Timestamp+import qualified Signet.Unstable.Type.Tolerance+import qualified Signet.Unstable.Type.UnknownSignature+import qualified Signet.Unstable.Type.Verifier
+ source/libraries/test/Signet/Unstable/Exception/InvalidAsymmetricSignatureTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidAsymmetricSignatureTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidAsymmetricSignature" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidIdTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidIdTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidId" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidMessageTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidMessageTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidMessage" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidPublicKeyTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidPublicKeyTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidPublicKey" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidSecretKeyTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidSecretKeyTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidSecretKey" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidSecretTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidSecretTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidSecret" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidSignatureTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidSignatureTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidSignature" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidSignerTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidSignerTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidSigner" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidSymmetricSignatureTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidSymmetricSignatureTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidSymmetricSignature" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidTimestampTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidTimestampTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidTimestamp" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/InvalidVerifierTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.InvalidVerifierTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.InvalidVerifier" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/SignetExceptionTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.SignetExceptionTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.SignetException" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/ToleranceExceptionTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.ToleranceExceptionTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.ToleranceException" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Exception/VerificationExceptionTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Exception.VerificationExceptionTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Exception.VerificationException" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Extra/EitherTest.hs view
@@ -0,0 +1,31 @@+module Signet.Unstable.Extra.EitherTest where++import qualified Control.Monad.Catch as Exception+import qualified Data.Void as Void+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Tasty as Tasty+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Extra.Either" $ do+  Tasty.describe "hush" $ do+    Tasty.it "works with Left" $ do+      Either.hush (Left () :: Either () Void.Void) @?= Nothing++    Tasty.it "works with Right" $ do+      Either.hush (Right () :: Either Void.Void ()) @?= Just ()++  Tasty.describe "throw" $ do+    Tasty.it "throws an exception for Left" $ do+      result <- Exception.try . Either.throw $ Left MkTestException+      result @?= (Left MkTestException :: Either TestException Void.Void)++    Tasty.it "returns the value for Right" $ do+      value <- Either.throw (Right () :: Either Void.Void ())+      value @?= ()++data TestException+  = MkTestException+  deriving (Eq, Show)++instance Exception.Exception TestException
+ source/libraries/test/Signet/Unstable/Extra/HttpTest.hs view
@@ -0,0 +1,21 @@+module Signet.Unstable.Extra.HttpTest where++import qualified Data.ByteString.Char8 as Ascii+import qualified Data.CaseInsensitive as CI+import qualified Signet.Unstable.Extra.Http as Http+import qualified Signet.Unstable.Extra.Tasty as Tasty+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Extra.Http" $ do+  Tasty.describe "hWebhookId" $ do+    Tasty.it "creates the correct header name" $ do+      Http.hWebhookId @?= CI.mk (Ascii.pack "webhook-id")++  Tasty.describe "hWebhookSignature" $ do+    Tasty.it "creates the correct header name" $ do+      Http.hWebhookSignature @?= CI.mk (Ascii.pack "webhook-signature")++  Tasty.describe "hWebhookTimestamp" $ do+    Tasty.it "creates the correct header name" $ do+      Http.hWebhookTimestamp @?= CI.mk (Ascii.pack "webhook-timestamp")
+ source/libraries/test/Signet/Unstable/Extra/MaybeTest.hs view
@@ -0,0 +1,15 @@+module Signet.Unstable.Extra.MaybeTest where++import qualified Data.Void as Void+import qualified Signet.Unstable.Extra.Maybe as Maybe+import qualified Signet.Unstable.Extra.Tasty as Tasty+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Extra.Maybe" $ do+  Tasty.describe "note" $ do+    Tasty.it "works with nothing" $ do+      Maybe.note () (Nothing :: Maybe Void.Void) @?= Left ()++    Tasty.it "works with just" $ do+      Maybe.note () (Just True) @?= Right True
+ source/libraries/test/Signet/Unstable/Extra/Tasty.hs view
@@ -0,0 +1,13 @@+module Signet.Unstable.Extra.Tasty where++import qualified Control.Monad.Trans.Writer as Writer+import qualified Test.Tasty as Tasty+import qualified Test.Tasty.HUnit as Tasty++type Spec = Writer.Writer [Tasty.TestTree] ()++describe :: Tasty.TestName -> Spec -> Spec+describe testName = Writer.tell . pure . Tasty.testGroup testName . Writer.execWriter++it :: Tasty.TestName -> Tasty.Assertion -> Spec+it testName = Writer.tell . pure . Tasty.testCase testName
+ source/libraries/test/Signet/Unstable/Type/AsymmetricSignatureTest.hs view
@@ -0,0 +1,27 @@+module Signet.Unstable.Type.AsymmetricSignatureTest where++import qualified Crypto.Error as Error+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidAsymmetricSignature as InvalidAsymmetricSignature+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.AsymmetricSignature" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with invalid input" $ do+      let byteString = Ascii.pack "invalid"+      let result = AsymmetricSignature.parse byteString+      result @?= Left (InvalidAsymmetricSignature.MkInvalidAsymmetricSignature byteString)++    Tasty.it "succeeds with valid input" $ do+      let result = AsymmetricSignature.parse $ Ascii.pack "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVotMDEyMzQ1Njc4OS1hYmNkZWZnaGlqa2xtbm9wcXJzdHV2cXh5eg=="+      signature <- Error.throwCryptoErrorIO . Ed25519.signature $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789-abcdefghijklmnopqrstuvqxyz"+      result @?= Right (AsymmetricSignature.MkAsymmetricSignature signature)++  Tasty.describe "render" $ do+    Tasty.it "works" $ do+      asymmetricSignature <- fmap AsymmetricSignature.MkAsymmetricSignature . Error.throwCryptoErrorIO . Ed25519.signature $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789-abcdefghijklmnopqrstuvqxyz"+      AsymmetricSignature.render asymmetricSignature @?= Ascii.pack "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVotMDEyMzQ1Njc4OS1hYmNkZWZnaGlqa2xtbm9wcXJzdHV2cXh5eg=="
+ source/libraries/test/Signet/Unstable/Type/IdTest.hs view
@@ -0,0 +1,26 @@+module Signet.Unstable.Type.IdTest where++import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidId as InvalidId+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.Id as Id+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Id" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with input containing separator" $ do+      let byteString = Ascii.pack "invalid.id"+      let result = Id.parse byteString+      result @?= Left (InvalidId.MkInvalidId byteString)++    Tasty.it "succeeds with valid input" $ do+      let byteString = Ascii.pack "valid-id"+      let result = Id.parse byteString+      result @?= Right (Id.MkId byteString)++  Tasty.describe "render" $ do+    Tasty.it "returns the original ByteString" $ do+      let byteString = Ascii.pack "valid-id"+      let id_ = Id.MkId byteString+      Id.render id_ @?= byteString
+ source/libraries/test/Signet/Unstable/Type/MessageTest.hs view
@@ -0,0 +1,47 @@+module Signet.Unstable.Type.MessageTest where++import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidMessage as InvalidMessage+import qualified Signet.Unstable.Exception.InvalidTimestamp as InvalidTimestamp+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.Id as Id+import qualified Signet.Unstable.Type.Message as Message+import qualified Signet.Unstable.Type.Payload as Payload+import qualified Signet.Unstable.Type.Timestamp as Timestamp+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Message" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with invalid timestamp" $ do+      let timestamp = Ascii.pack "invalid"+      let byteString = Ascii.pack "i." <> timestamp <> Ascii.pack ".p"+      let result = Message.parse byteString+      result @?= Left (InvalidMessage.InvalidTimestamp $ InvalidTimestamp.MkInvalidTimestamp timestamp)++    Tasty.it "succeeds with valid input" $ do+      let result = Message.parse $ Ascii.pack "i.0.p"+      id_ <- Either.throw . Id.parse $ Ascii.pack "i"+      timestamp <- Either.throw . Timestamp.parse $ Ascii.pack "0"+      let payload = Payload.MkPayload $ Ascii.pack "p"+      result+        @?= Right+          Message.MkMessage+            { Message.id_ = id_,+              Message.timestamp = timestamp,+              Message.payload = payload+            }++  Tasty.describe "render" $ do+    Tasty.it "returns the correct ByteString representation" $ do+      id_ <- Either.throw . Id.parse $ Ascii.pack "i"+      timestamp <- Either.throw . Timestamp.parse $ Ascii.pack "0"+      let payload = Payload.MkPayload $ Ascii.pack "p"+      let message =+            Message.MkMessage+              { Message.id_ = id_,+                Message.timestamp = timestamp,+                Message.payload = payload+              }+      Message.render message @?= Ascii.pack "i.0.p"
+ source/libraries/test/Signet/Unstable/Type/PayloadTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Type.PayloadTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Payload" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Type/PublicKeyTest.hs view
@@ -0,0 +1,32 @@+module Signet.Unstable.Type.PublicKeyTest where++import qualified Crypto.Error as Error+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidPublicKey as InvalidPublicKey+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.PublicKey as PublicKey+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.PublicKey" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with invalid prefix" $ do+      let byteString = Ascii.pack "invalid"+      let result = PublicKey.parse byteString+      result @?= Left (InvalidPublicKey.MkInvalidPublicKey byteString)++    Tasty.it "fails with invalid input" $ do+      let byteString = Ascii.pack "whpk_invalid"+      let result = PublicKey.parse byteString+      result @?= Left (InvalidPublicKey.MkInvalidPublicKey byteString)++    Tasty.it "succeeds with valid input" $ do+      let result = PublicKey.parse $ Ascii.pack "whpk_QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU="+      publicKey <- fmap PublicKey.MkPublicKey . Error.throwCryptoErrorIO . Ed25519.publicKey $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"+      result @?= Right publicKey++  Tasty.describe "render" $ do+    Tasty.it "works" $ do+      publicKey <- fmap PublicKey.MkPublicKey . Error.throwCryptoErrorIO . Ed25519.publicKey $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"+      PublicKey.render publicKey @?= Ascii.pack "whpk_QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU="
+ source/libraries/test/Signet/Unstable/Type/SecretKeyTest.hs view
@@ -0,0 +1,32 @@+module Signet.Unstable.Type.SecretKeyTest where++import qualified Crypto.Error as Error+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidSecretKey as InvalidSecretKey+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.SecretKey as SecretKey+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.SecretKey" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with invalid prefix" $ do+      let byteString = Ascii.pack "invalid"+      let result = SecretKey.parse byteString+      result @?= Left (InvalidSecretKey.MkInvalidSecretKey byteString)++    Tasty.it "fails with invalid input" $ do+      let byteString = Ascii.pack "whsk_invalid"+      let result = SecretKey.parse byteString+      result @?= Left (InvalidSecretKey.MkInvalidSecretKey byteString)++    Tasty.it "succeeds with valid input" $ do+      let result = SecretKey.parse $ Ascii.pack "whsk_QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU="+      secretKey <- fmap SecretKey.MkSecretKey . Error.throwCryptoErrorIO . Ed25519.secretKey $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"+      result @?= Right secretKey++  Tasty.describe "render" $ do+    Tasty.it "works" $ do+      secretKey <- fmap SecretKey.MkSecretKey . Error.throwCryptoErrorIO . Ed25519.secretKey $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"+      SecretKey.render secretKey @?= Ascii.pack "whsk_QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU="
+ source/libraries/test/Signet/Unstable/Type/SecretTest.hs view
@@ -0,0 +1,31 @@+module Signet.Unstable.Type.SecretTest where++import qualified Data.ByteArray as ByteArray+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidSecret as InvalidSecret+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.Secret as Secret+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Secret" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with invalid prefix" $ do+      let byteString = Ascii.pack "invalid"+      let result = Secret.parse byteString+      result @?= Left (InvalidSecret.MkInvalidSecret byteString)++    Tasty.it "fails with invalid input" $ do+      let byteString = Ascii.pack "whsec_invalid"+      let result = Secret.parse byteString+      result @?= Left (InvalidSecret.MkInvalidSecret byteString)++    Tasty.it "succeeds with valid input" $ do+      let result = Secret.parse $ Ascii.pack "whsec_MDEyMzQ1Njc4OQ=="+      let secret = Secret.MkSecret . ByteArray.convert $ Ascii.pack "0123456789"+      result @?= Right secret++  Tasty.describe "render" $ do+    Tasty.it "works" $ do+      let secret = Secret.MkSecret . ByteArray.convert $ Ascii.pack "0123456789"+      Secret.render secret @?= Ascii.pack "whsec_MDEyMzQ1Njc4OQ=="
+ source/libraries/test/Signet/Unstable/Type/SignatureTest.hs view
@@ -0,0 +1,53 @@+module Signet.Unstable.Type.SignatureTest where++import qualified Crypto.Error as Error+import qualified Crypto.Hash as Hash+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidAsymmetricSignature as InvalidAsymmetricSignature+import qualified Signet.Unstable.Exception.InvalidSignature as InvalidSignature+import qualified Signet.Unstable.Exception.InvalidSymmetricSignature as InvalidSymmetricSignature+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature+import qualified Signet.Unstable.Type.UnknownSignature as UnknownSignature+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Signature" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with invalid asymmetric signature" $ do+      let byteString = Ascii.pack "invalid"+      let result = Signature.parse $ Ascii.pack "v1a," <> byteString+      result @?= Left (InvalidSignature.InvalidAsymmetricSignature $ InvalidAsymmetricSignature.MkInvalidAsymmetricSignature byteString)++    Tasty.it "fails with invalid symmetric signature" $ do+      let byteString = Ascii.pack "invalid"+      let result = Signature.parse $ Ascii.pack "v1," <> byteString+      result @?= Left (InvalidSignature.InvalidSymmetricSignature $ InvalidSymmetricSignature.MkInvalidSymmetricSignature byteString)++    Tasty.it "returns unknown signature with unrecognized prefix" $ do+      let byteString = Ascii.pack "unknown"+      let result = Signature.parse byteString+      result @?= Right (Left $ UnknownSignature.MkUnknownSignature byteString)++    Tasty.it "succeeds with valid asymmetric signature" $ do+      let byteString = Ascii.pack "v1a,QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVotMDEyMzQ1Njc4OS1hYmNkZWZnaGlqa2xtbm9wcXJzdHV2cXh5eg=="+      signature <- fmap (Signature.Asymmetric . AsymmetricSignature.MkAsymmetricSignature) . Error.throwCryptoErrorIO . Ed25519.signature $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789-abcdefghijklmnopqrstuvqxyz"+      Signature.parse byteString @?= Right (Right signature)++    Tasty.it "succeeds with valid symmetric signature" $ do+      let byteString = Ascii.pack "v1,47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="+      let signature = Signature.Symmetric . SymmetricSignature.MkSymmetricSignature $ Hash.hash ByteString.empty+      Signature.parse byteString @?= Right (Right signature)++  Tasty.describe "render" $ do+    Tasty.it "renders asymmetric signature correctly" $ do+      signature <- fmap (Signature.Asymmetric . AsymmetricSignature.MkAsymmetricSignature) . Error.throwCryptoErrorIO . Ed25519.signature $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789-abcdefghijklmnopqrstuvqxyz"+      Signature.render signature @?= Ascii.pack "v1a,QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVotMDEyMzQ1Njc4OS1hYmNkZWZnaGlqa2xtbm9wcXJzdHV2cXh5eg=="++    Tasty.it "renders symmetric signature correctly" $ do+      let signature = Signature.Symmetric . SymmetricSignature.MkSymmetricSignature $ Hash.hash ByteString.empty+      Signature.render signature @?= Ascii.pack "v1,47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
+ source/libraries/test/Signet/Unstable/Type/SignaturesTest.hs view
@@ -0,0 +1,72 @@+module Signet.Unstable.Type.SignaturesTest where++import qualified Crypto.Error as Error+import qualified Crypto.Hash as Hash+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidAsymmetricSignature as InvalidAsymmetricSignature+import qualified Signet.Unstable.Exception.InvalidSignature as InvalidSignature+import qualified Signet.Unstable.Exception.InvalidSymmetricSignature as InvalidSymmetricSignature+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.Signatures as Signatures+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature+import qualified Signet.Unstable.Type.UnknownSignature as UnknownSignature+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Signatures" $ do+  Tasty.describe "parse" $ do+    Tasty.it "succeeds with no signatures" $ do+      Signatures.parse ByteString.empty @?= Right ([], Signatures.MkSignatures [])++    Tasty.it "succeeds with one signature" $ do+      let signature = Signature.Symmetric . SymmetricSignature.MkSymmetricSignature $ Hash.hash ByteString.empty+      let byteString = Ascii.pack "v1,47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="+      Signatures.parse byteString @?= Right ([], Signatures.MkSignatures [signature])++    Tasty.it "succeeds with many signatures" $ do+      let symmetricSignature = SymmetricSignature.MkSymmetricSignature $ Hash.hash ByteString.empty+      asymmetricSignature <- fmap AsymmetricSignature.MkAsymmetricSignature . Error.throwCryptoErrorIO . Ed25519.signature $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789-abcdefghijklmnopqrstuvqxyz"+      let byteString = Ascii.pack "v1,47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= v1a,QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVotMDEyMzQ1Njc4OS1hYmNkZWZnaGlqa2xtbm9wcXJzdHV2cXh5eg=="+      let signatures =+            Signatures.MkSignatures+              [ Signature.Symmetric symmetricSignature,+                Signature.Asymmetric asymmetricSignature+              ]+      Signatures.parse byteString @?= Right ([], signatures)++    Tasty.it "fails with an invalid symmetric signature" $ do+      let x = Ascii.pack "invalid"+      let byteString = Ascii.pack "v1," <> x+      Signatures.parse byteString @?= Left (InvalidSignature.InvalidSymmetricSignature $ InvalidSymmetricSignature.MkInvalidSymmetricSignature x)++    Tasty.it "fails with an invalid asymmetric signature" $ do+      let x = Ascii.pack "invalid"+      let byteString = Ascii.pack "v1a," <> x+      Signatures.parse byteString @?= Left (InvalidSignature.InvalidAsymmetricSignature $ InvalidAsymmetricSignature.MkInvalidAsymmetricSignature x)++    Tasty.it "succeeds with an unknown signature" $ do+      let byteString = Ascii.pack "unknown"+      Signatures.parse byteString @?= Right ([UnknownSignature.MkUnknownSignature byteString], Signatures.MkSignatures [])++  Tasty.describe "render" $ do+    Tasty.it "renders no signatures" $ do+      let signatures = Signatures.MkSignatures []+      Signatures.render signatures @?= ByteString.empty++    Tasty.it "renders one signature" $ do+      let signatures = Signatures.MkSignatures . pure . Signature.Symmetric . SymmetricSignature.MkSymmetricSignature $ Hash.hash ByteString.empty+      Signatures.render signatures @?= Ascii.pack "v1,47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="++    Tasty.it "renders many signatures" $ do+      let symmetricSignature = SymmetricSignature.MkSymmetricSignature $ Hash.hash ByteString.empty+      asymmetricSignature <- fmap AsymmetricSignature.MkAsymmetricSignature . Error.throwCryptoErrorIO . Ed25519.signature $ Ascii.pack "ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789-abcdefghijklmnopqrstuvqxyz"+      let signatures =+            Signatures.MkSignatures+              [ Signature.Symmetric symmetricSignature,+                Signature.Asymmetric asymmetricSignature+              ]+      Signatures.render signatures @?= Ascii.pack "v1,47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= v1a,QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVotMDEyMzQ1Njc4OS1hYmNkZWZnaGlqa2xtbm9wcXJzdHV2cXh5eg=="
+ source/libraries/test/Signet/Unstable/Type/SignerTest.hs view
@@ -0,0 +1,76 @@+module Signet.Unstable.Type.SignerTest where++import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidSigner as InvalidSigner+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import qualified Signet.Unstable.Type.Message as Message+import qualified Signet.Unstable.Type.Secret as Secret+import qualified Signet.Unstable.Type.SecretKey as SecretKey+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.Signer as Signer+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Signer" $ do+  Tasty.describe "parse" $ do+    Tasty.it "succeeds with a valid secret key" $ do+      let byteString = Ascii.pack "whsk_QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU="+      let actual = Signer.parse byteString+      secretKey <- Either.throw $ SecretKey.parse byteString+      actual @?= Right (Signer.Asymmetric secretKey)++    Tasty.it "succeeds with a valid secret" $ do+      let byteString = Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      let actual = Signer.parse byteString+      secret <- Either.throw $ Secret.parse byteString+      actual @?= Right (Signer.Symmetric secret)++    Tasty.it "fails with invalid input" $ do+      let byteString = Ascii.pack "invalid"+      let actual = Signer.parse byteString+      actual @?= Left (InvalidSigner.MkInvalidSigner byteString)++  Tasty.describe "render" $ do+    Tasty.it "works with a secret key" $ do+      let byteString = Ascii.pack "whsk_QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU="+      secretKey <- Either.throw . SecretKey.parse $ byteString+      Signer.render (Signer.Asymmetric secretKey) @?= byteString++    Tasty.it "works with a secret" $ do+      let byteString = Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      secret <- Either.throw . Secret.parse $ byteString+      Signer.render (Signer.Symmetric secret) @?= byteString++  Tasty.describe "sign" $ do+    Tasty.it "works with asymmetric" $ do+      signer <- Either.throw . Signer.parse $ Ascii.pack "whsk_QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      let actual = Signer.sign signer message+      Right expected <- Either.throw . Signature.parse $ Ascii.pack "v1a,CV1O+PvrwXM42OMUX+tmm6bA3cS0tgLp0qo3YKuu0MGmBrsUhA0MHXF11HsEUJtPfTKs80WE7WUKVt9TueLDCQ=="+      actual @?= expected++    Tasty.it "works with symmetric" $ do+      signer <- Either.throw . Signer.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      let actual = Signer.sign signer message+      Right expected <- Either.throw . Signature.parse $ Ascii.pack "v1,IywpE5NXy+JdAScgR7j5Pt59GjmazD7iJuVsQoRZFyw="+      actual @?= expected++  Tasty.describe "asymmetric" $ do+    Tasty.it "creates correct asymmetric signature" $ do+      secretKey <- Either.throw . SecretKey.parse $ Ascii.pack "whsk_QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      let actual = Signer.asymmetric secretKey message+      expected <- Either.throw . AsymmetricSignature.parse $ Ascii.pack "CV1O+PvrwXM42OMUX+tmm6bA3cS0tgLp0qo3YKuu0MGmBrsUhA0MHXF11HsEUJtPfTKs80WE7WUKVt9TueLDCQ=="+      actual @?= expected++  Tasty.describe "symmetric" $ do+    Tasty.it "creates correct symmetric signature" $ do+      secret <- Either.throw . Secret.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      let actual = Signer.symmetric secret message+      expected <- Either.throw . SymmetricSignature.parse $ Ascii.pack "IywpE5NXy+JdAScgR7j5Pt59GjmazD7iJuVsQoRZFyw="+      actual @?= expected
+ source/libraries/test/Signet/Unstable/Type/SymmetricSignatureTest.hs view
@@ -0,0 +1,27 @@+module Signet.Unstable.Type.SymmetricSignatureTest where++import qualified Crypto.Hash as Hash+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidSymmetricSignature as InvalidSymmetricSignature+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.SymmetricSignature" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with invalid symmetric signature" $ do+      let byteString = Ascii.pack "invalid"+      let result = SymmetricSignature.parse byteString+      result @?= Left (InvalidSymmetricSignature.MkInvalidSymmetricSignature byteString)++    Tasty.it "succeeds with valid symmetric signature" $ do+      let byteString = Ascii.pack "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="+      let signature = SymmetricSignature.MkSymmetricSignature $ Hash.hash ByteString.empty+      SymmetricSignature.parse byteString @?= Right signature++  Tasty.describe "render" $ do+    Tasty.it "renders symmetric signature correctly" $ do+      let signature = SymmetricSignature.MkSymmetricSignature $ Hash.hash ByteString.empty+      SymmetricSignature.render signature @?= Ascii.pack "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
+ source/libraries/test/Signet/Unstable/Type/TimestampTest.hs view
@@ -0,0 +1,33 @@+module Signet.Unstable.Type.TimestampTest where++import qualified Data.ByteString.Char8 as Ascii+import qualified Data.Time as Time+import qualified Signet.Unstable.Exception.InvalidTimestamp as InvalidTimestamp+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.Timestamp as Timestamp+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Timestamp" $ do+  Tasty.describe "parse" $ do+    Tasty.it "fails with invalid timestamp format" $ do+      let byteString = Ascii.pack "invalid-timestamp"+      let result = Timestamp.parse byteString+      result @?= Left (InvalidTimestamp.MkInvalidTimestamp byteString)++    Tasty.it "succeeds with valid timestamp format" $ do+      let byteString = Ascii.pack "1617235200"+      let result = Timestamp.parse byteString+      case result of+        Right timestamp -> do+          let utcTime = Timestamp.unwrap timestamp+          let expectedTime = Time.UTCTime (Time.fromGregorian 2021 4 1) 0+          utcTime @?= expectedTime+        Left _ -> fail "Expected Right but got Left"++  Tasty.describe "render" $ do+    Tasty.it "returns the correct ByteString representation" $ do+      let utcTime = Time.UTCTime (Time.fromGregorian 2021 4 1) 0+      let timestamp = Timestamp.MkTimestamp utcTime+      let expected = Ascii.pack "1617235200"+      Timestamp.render timestamp @?= expected
+ source/libraries/test/Signet/Unstable/Type/ToleranceTest.hs view
@@ -0,0 +1,34 @@+module Signet.Unstable.Type.ToleranceTest where++import qualified Data.Time as Time+import qualified Signet.Unstable.Exception.ToleranceException as ToleranceException+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.Timestamp as Timestamp+import qualified Signet.Unstable.Type.Tolerance as Tolerance+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Tolerance" $ do+  Tasty.describe "check" $ do+    Tasty.it "succeeds when timestamp is within tolerance" $ do+      let tolerance = Tolerance.MkTolerance 1+      let utcTime = Time.UTCTime (Time.fromGregorian 2001 1 1) 60+      let timestamp = Timestamp.MkTimestamp utcTime+      let result = Tolerance.check tolerance utcTime timestamp+      result @?= Right ()++    Tasty.it "fails when timestamp is too old" $ do+      let tolerance = Tolerance.MkTolerance 1+      let utcTime = Time.UTCTime (Time.fromGregorian 2001 1 1) 60+      let timestamp = Timestamp.MkTimestamp utcTime+      let now = Time.addUTCTime 2 utcTime+      let result = Tolerance.check tolerance now timestamp+      result @?= Left (ToleranceException.MkToleranceException timestamp)++    Tasty.it "fails when timestamp is in the future" $ do+      let tolerance = Tolerance.MkTolerance 1+      let utcTime = Time.UTCTime (Time.fromGregorian 2001 1 1) 60+      let timestamp = Timestamp.MkTimestamp utcTime+      let now = Time.addUTCTime (-2) utcTime+      let result = Tolerance.check tolerance now timestamp+      result @?= Left (ToleranceException.MkToleranceException timestamp)
+ source/libraries/test/Signet/Unstable/Type/UnknownSignatureTest.hs view
@@ -0,0 +1,7 @@+module Signet.Unstable.Type.UnknownSignatureTest where++import qualified Signet.Unstable.Extra.Tasty as Tasty++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.UnknownSignature" $ do+  pure ()
+ source/libraries/test/Signet/Unstable/Type/VerifierTest.hs view
@@ -0,0 +1,110 @@+module Signet.Unstable.Type.VerifierTest where++import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidVerifier as InvalidVerifier+import qualified Signet.Unstable.Exception.VerificationException as VerificationException+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import qualified Signet.Unstable.Type.Message as Message+import qualified Signet.Unstable.Type.PublicKey as PublicKey+import qualified Signet.Unstable.Type.Secret as Secret+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.Signatures as Signatures+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature+import qualified Signet.Unstable.Type.Verifier as Verifier+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable.Type.Verifier" $ do+  Tasty.describe "parse" $ do+    Tasty.it "succeeds with a valid public key" $ do+      let byteString = Ascii.pack "whpk_wuzPrKxPfWpJSsXgyg/MEoMGvjs5SjDO4ad6X4ZYqqg="+      let actual = Verifier.parse byteString+      publicKey <- Either.throw $ PublicKey.parse byteString+      actual @?= Right (Verifier.Asymmetric publicKey)++    Tasty.it "succeeds with a valid secret" $ do+      let byteString = Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      let actual = Verifier.parse byteString+      secret <- Either.throw $ Secret.parse byteString+      actual @?= Right (Verifier.Symmetric secret)++    Tasty.it "fails with invalid input" $ do+      let byteString = Ascii.pack "invalid"+      let actual = Verifier.parse byteString+      actual @?= Left (InvalidVerifier.MkInvalidVerifier byteString)++  Tasty.describe "render" $ do+    Tasty.it "works with a public key" $ do+      let byteString = Ascii.pack "whpk_wuzPrKxPfWpJSsXgyg/MEoMGvjs5SjDO4ad6X4ZYqqg="+      publicKey <- Either.throw . PublicKey.parse $ byteString+      Verifier.render (Verifier.Asymmetric publicKey) @?= byteString++    Tasty.it "works with a secret" $ do+      let byteString = Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      secret <- Either.throw . Secret.parse $ byteString+      Verifier.render (Verifier.Symmetric secret) @?= byteString++  Tasty.describe "verify" $ do+    Tasty.it "succeeds with a valid asymmetric signature" $ do+      verifier <- Either.throw . Verifier.parse $ Ascii.pack "whpk_wuzPrKxPfWpJSsXgyg/MEoMGvjs5SjDO4ad6X4ZYqqg="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1a,CV1O+PvrwXM42OMUX+tmm6bA3cS0tgLp0qo3YKuu0MGmBrsUhA0MHXF11HsEUJtPfTKs80WE7WUKVt9TueLDCQ=="+      let actual = Verifier.verify verifier message signatures+      Right signature <- Either.throw . Signature.parse $ Ascii.pack "v1a,CV1O+PvrwXM42OMUX+tmm6bA3cS0tgLp0qo3YKuu0MGmBrsUhA0MHXF11HsEUJtPfTKs80WE7WUKVt9TueLDCQ=="+      actual @?= Right signature++    Tasty.it "fails with an invalid asymmetric signature" $ do+      verifier <- Either.throw . Verifier.parse $ Ascii.pack "whpk_wuzPrKxPfWpJSsXgyg/MEoMGvjs5SjDO4ad6X4ZYqqg="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1a,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000=="+      let actual = Verifier.verify verifier message signatures+      actual @?= Left (VerificationException.MkVerificationException $ Message.id_ message)++    Tasty.it "succeeds with a valid symmetric signature" $ do+      verifier <- Either.throw . Verifier.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1,IywpE5NXy+JdAScgR7j5Pt59GjmazD7iJuVsQoRZFyw="+      let actual = Verifier.verify verifier message signatures+      Right signature <- Either.throw . Signature.parse $ Ascii.pack "v1,IywpE5NXy+JdAScgR7j5Pt59GjmazD7iJuVsQoRZFyw="+      actual @?= Right signature++    Tasty.it "fails with an invalid symmetric signature" $ do+      verifier <- Either.throw . Verifier.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1,0000000000000000000000000000000000000000000="+      let actual = Verifier.verify verifier message signatures+      actual @?= Left (VerificationException.MkVerificationException $ Message.id_ message)++  Tasty.describe "asymmetric" $ do+    Tasty.it "succeeds with a valid signature" $ do+      publicKey <- Either.throw . PublicKey.parse $ Ascii.pack "whpk_wuzPrKxPfWpJSsXgyg/MEoMGvjs5SjDO4ad6X4ZYqqg="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1a,CV1O+PvrwXM42OMUX+tmm6bA3cS0tgLp0qo3YKuu0MGmBrsUhA0MHXF11HsEUJtPfTKs80WE7WUKVt9TueLDCQ=="+      let actual = Verifier.asymmetric publicKey message signatures+      asymmetricSignature <- Either.throw . AsymmetricSignature.parse $ Ascii.pack "CV1O+PvrwXM42OMUX+tmm6bA3cS0tgLp0qo3YKuu0MGmBrsUhA0MHXF11HsEUJtPfTKs80WE7WUKVt9TueLDCQ=="+      actual @?= Right asymmetricSignature++    Tasty.it "fails with an invalid signature" $ do+      publicKey <- Either.throw . PublicKey.parse $ Ascii.pack "whpk_wuzPrKxPfWpJSsXgyg/MEoMGvjs5SjDO4ad6X4ZYqqg="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1a,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000=="+      let actual = Verifier.asymmetric publicKey message signatures+      actual @?= Left (VerificationException.MkVerificationException $ Message.id_ message)++  Tasty.describe "symmetric" $ do+    Tasty.it "succeeds with a valid signature" $ do+      secret <- Either.throw . Secret.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1,IywpE5NXy+JdAScgR7j5Pt59GjmazD7iJuVsQoRZFyw="+      let actual = Verifier.symmetric secret message signatures+      symmetricSignature <- Either.throw . SymmetricSignature.parse $ Ascii.pack "IywpE5NXy+JdAScgR7j5Pt59GjmazD7iJuVsQoRZFyw="+      actual @?= Right symmetricSignature++    Tasty.it "fails with an invalid signature" $ do+      secret <- Either.throw . Secret.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1,0000000000000000000000000000000000000000000="+      let actual = Verifier.symmetric secret message signatures+      actual @?= Left (VerificationException.MkVerificationException $ Message.id_ message)
+ source/libraries/test/Signet/UnstableTest.hs view
@@ -0,0 +1,57 @@+module Signet.UnstableTest where++import qualified Data.ByteString.Char8 as Ascii+import qualified Data.Time as Time+import qualified Signet.Unstable as Signet+import qualified Signet.Unstable.Exception.SignetException as SignetException+import qualified Signet.Unstable.Exception.ToleranceException as ToleranceException+import qualified Signet.Unstable.Exception.VerificationException as VerificationException+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Tasty as Tasty+import qualified Signet.Unstable.Type.Message as Message+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.Signatures as Signatures+import qualified Signet.Unstable.Type.Tolerance as Tolerance+import qualified Signet.Unstable.Type.Verifier as Verifier+import Test.Tasty.HUnit ((@?=))++spec :: Tasty.Spec+spec = Tasty.describe "Signet.Unstable" $ do+  Tasty.describe "verifyWebhookText" $ do+    pure ()++  Tasty.describe "verifyWebhookByteString" $ do+    pure ()++  Tasty.describe "verifyWebhook" $ do+    pure ()++  Tasty.describe "verifyWebhookWith" $ do+    Tasty.it "succeeds with a valid symmetric signature" $ do+      let tolerance = Tolerance.typical+      let now = Time.UTCTime (Time.fromGregorian 1970 1 1) 0+      verifier <- Either.throw . Verifier.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      let byteString = Ascii.pack "v1,IywpE5NXy+JdAScgR7j5Pt59GjmazD7iJuVsQoRZFyw="+      (_, signatures) <- Either.throw $ Signatures.parse byteString+      let result = Signet.verifyWebhookWith tolerance now verifier message signatures+      Right signature <- Either.throw $ Signature.parse byteString+      result @?= Right signature++    Tasty.it "fails with an invalid timestamp" $ do+      let tolerance = Tolerance.typical+      let now = Time.UTCTime (Time.fromGregorian 1970 1 1) 0+      verifier <- Either.throw . Verifier.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.301.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1,IywpE5NXy+JdAScgR7j5Pt59GjmazD7iJuVsQoRZFyw="+      let result = Signet.verifyWebhookWith tolerance now verifier message signatures+      result @?= Left (SignetException.ToleranceException . ToleranceException.MkToleranceException $ Message.timestamp message)++    Tasty.it "fails with an invalid symmetric signature" $ do+      let tolerance = Tolerance.typical+      let now = Time.UTCTime (Time.fromGregorian 1970 1 1) 0+      verifier <- Either.throw . Verifier.parse $ Ascii.pack "whsec_bXlzZWNyZXRrZXkxMjM0NQ=="+      message <- Either.throw . Message.parse $ Ascii.pack "i.0.Hello, world!"+      (_, signatures) <- Either.throw . Signatures.parse $ Ascii.pack "v1,0000000000000000000000000000000000000000000="+      let result = Signet.verifyWebhookWith tolerance now verifier message signatures+      result @?= Left (SignetException.VerificationException . VerificationException.MkVerificationException $ Message.id_ message)
+ source/libraries/test/SignetTest.hs view
@@ -0,0 +1,73 @@+module SignetTest where++import qualified Control.Monad.Trans.Writer as Writer+import qualified Signet.Unstable.Exception.InvalidAsymmetricSignatureTest+import qualified Signet.Unstable.Exception.InvalidIdTest+import qualified Signet.Unstable.Exception.InvalidMessageTest+import qualified Signet.Unstable.Exception.InvalidPublicKeyTest+import qualified Signet.Unstable.Exception.InvalidSecretKeyTest+import qualified Signet.Unstable.Exception.InvalidSecretTest+import qualified Signet.Unstable.Exception.InvalidSignatureTest+import qualified Signet.Unstable.Exception.InvalidSignerTest+import qualified Signet.Unstable.Exception.InvalidSymmetricSignatureTest+import qualified Signet.Unstable.Exception.InvalidTimestampTest+import qualified Signet.Unstable.Exception.InvalidVerifierTest+import qualified Signet.Unstable.Exception.SignetExceptionTest+import qualified Signet.Unstable.Exception.ToleranceExceptionTest+import qualified Signet.Unstable.Exception.VerificationExceptionTest+import qualified Signet.Unstable.Extra.EitherTest+import qualified Signet.Unstable.Extra.HttpTest+import qualified Signet.Unstable.Extra.MaybeTest+import qualified Signet.Unstable.Type.AsymmetricSignatureTest+import qualified Signet.Unstable.Type.IdTest+import qualified Signet.Unstable.Type.MessageTest+import qualified Signet.Unstable.Type.PayloadTest+import qualified Signet.Unstable.Type.PublicKeyTest+import qualified Signet.Unstable.Type.SecretKeyTest+import qualified Signet.Unstable.Type.SecretTest+import qualified Signet.Unstable.Type.SignatureTest+import qualified Signet.Unstable.Type.SignaturesTest+import qualified Signet.Unstable.Type.SignerTest+import qualified Signet.Unstable.Type.SymmetricSignatureTest+import qualified Signet.Unstable.Type.TimestampTest+import qualified Signet.Unstable.Type.ToleranceTest+import qualified Signet.Unstable.Type.UnknownSignatureTest+import qualified Signet.Unstable.Type.VerifierTest+import qualified Signet.UnstableTest+import qualified Test.Tasty as Tasty++testTree :: Tasty.TestTree+testTree = Tasty.testGroup "signet" . Writer.execWriter $ do+  Signet.Unstable.Exception.InvalidAsymmetricSignatureTest.spec+  Signet.Unstable.Exception.InvalidIdTest.spec+  Signet.Unstable.Exception.InvalidMessageTest.spec+  Signet.Unstable.Exception.InvalidPublicKeyTest.spec+  Signet.Unstable.Exception.InvalidSecretKeyTest.spec+  Signet.Unstable.Exception.InvalidSecretTest.spec+  Signet.Unstable.Exception.InvalidSignatureTest.spec+  Signet.Unstable.Exception.InvalidSignerTest.spec+  Signet.Unstable.Exception.InvalidSymmetricSignatureTest.spec+  Signet.Unstable.Exception.InvalidTimestampTest.spec+  Signet.Unstable.Exception.InvalidVerifierTest.spec+  Signet.Unstable.Exception.SignetExceptionTest.spec+  Signet.Unstable.Exception.ToleranceExceptionTest.spec+  Signet.Unstable.Exception.VerificationExceptionTest.spec+  Signet.Unstable.Extra.EitherTest.spec+  Signet.Unstable.Extra.HttpTest.spec+  Signet.Unstable.Extra.MaybeTest.spec+  Signet.Unstable.Type.AsymmetricSignatureTest.spec+  Signet.Unstable.Type.IdTest.spec+  Signet.Unstable.Type.MessageTest.spec+  Signet.Unstable.Type.PayloadTest.spec+  Signet.Unstable.Type.PublicKeyTest.spec+  Signet.Unstable.Type.SecretKeyTest.spec+  Signet.Unstable.Type.SecretTest.spec+  Signet.Unstable.Type.SignaturesTest.spec+  Signet.Unstable.Type.SignatureTest.spec+  Signet.Unstable.Type.SignerTest.spec+  Signet.Unstable.Type.SymmetricSignatureTest.spec+  Signet.Unstable.Type.TimestampTest.spec+  Signet.Unstable.Type.ToleranceTest.spec+  Signet.Unstable.Type.UnknownSignatureTest.spec+  Signet.Unstable.Type.VerifierTest.spec+  Signet.UnstableTest.spec
+ source/libraries/unstable/Signet/Unstable.hs view
@@ -0,0 +1,196 @@+{-# LANGUAGE PatternSynonyms #-}++module Signet.Unstable where++import qualified Control.Monad.Catch as Exception+import qualified Control.Monad.IO.Class as MonadIO+import qualified Data.Bifunctor as Bifunctor+import qualified Data.ByteString as ByteString+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Time as Time+import qualified Signet.Unstable.Exception.InvalidId as InvalidId+import qualified Signet.Unstable.Exception.InvalidMessage as InvalidMessage+import qualified Signet.Unstable.Exception.InvalidSignature as InvalidSignature+import qualified Signet.Unstable.Exception.InvalidSigner as InvalidSigner+import qualified Signet.Unstable.Exception.InvalidTimestamp as InvalidTimestamp+import qualified Signet.Unstable.Exception.InvalidVerifier as InvalidVerifier+import qualified Signet.Unstable.Exception.SignetException as SignetException+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import qualified Signet.Unstable.Type.Id as Id+import qualified Signet.Unstable.Type.Message as Message+import qualified Signet.Unstable.Type.Payload as Payload+import qualified Signet.Unstable.Type.PublicKey as PublicKey+import qualified Signet.Unstable.Type.Secret as Secret+import qualified Signet.Unstable.Type.SecretKey as SecretKey+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.Signatures as Signatures+import qualified Signet.Unstable.Type.Signer as Signer+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature+import qualified Signet.Unstable.Type.Timestamp as Timestamp+import qualified Signet.Unstable.Type.Tolerance as Tolerance+import qualified Signet.Unstable.Type.UnknownSignature as UnknownSignature+import qualified Signet.Unstable.Type.Verifier as Verifier++-- | Verifies a webhook with 'Text.Text' values. This is a wrapper around+-- 'verifyWebhookByteString' that assumes all values are encoded as UTF-8.+verifyWebhookText ::+  (MonadIO.MonadIO m, Exception.MonadThrow m) =>+  -- | A 'Verifier.Verifier' for the webhook. Typically this will be+  -- @"whsec_..."@ or @"whpk_..."@.+  Text.Text ->+  -- | The webhook's unique 'Id.Id'. Typically this will come from the+  -- 'Signet.Unstable.Extra.Http.hWebhookId' header and look like @"msg_..."@.+  Text.Text ->+  -- | The webhook's 'Timestamp.Timestamp'. This is an integer number of+  -- seconds since the Unix epoch. Typically this will come from the+  -- 'Signet.Unstable.Extra.Http.hWebhookTimestamp' header. For example+  -- @"981173106"@ for @2001-02-03T04:05:06Z@.+  Text.Text ->+  -- | The webhook's raw 'Payload.Payload'. Typically this will be a JSON+  -- object like @{"event_type":"ping",...}@.+  Text.Text ->+  -- | The webhook's 'Signatures.Signatures'. Typically this will come from the+  -- 'Signet.Unstable.Extra.Http.hWebhookSignature' header and look like+  -- @"v1_..."@ or @"v1a_..."@.+  Text.Text ->+  m Signature.Signature+verifyWebhookText v i t p s =+  verifyWebhookByteString+    (Text.encodeUtf8 v)+    (Text.encodeUtf8 i)+    (Text.encodeUtf8 t)+    (Text.encodeUtf8 p)+    (Text.encodeUtf8 s)++-- | Verifies a webhook with 'ByteString.ByteString' values. This is a+-- wrapper around 'verifyWebhook'. See 'verifyWebhookText' for a description of+-- the arguments.+verifyWebhookByteString ::+  (MonadIO.MonadIO m, Exception.MonadThrow m) =>+  -- | 'Verifier.Verifier'+  ByteString.ByteString ->+  -- | 'Id.Id'+  ByteString.ByteString ->+  -- | 'Timestamp.Timestamp'+  ByteString.ByteString ->+  -- | 'Payload.Payload'+  ByteString.ByteString ->+  -- | 'Signatures.Signatures'+  ByteString.ByteString ->+  m Signature.Signature+verifyWebhookByteString v i t p s = do+  verifier <- Either.throw $ parseVerifier v+  id_ <- Either.throw $ parseId i+  timestamp <- Either.throw $ parseTimestamp t+  let payload = Payload.MkPayload p+  let message =+        Message.MkMessage+          { Message.id_ = id_,+            Message.timestamp = timestamp,+            Message.payload = payload+          }+  (_, signatures) <- Either.throw $ parseSignatures s+  verifyWebhook verifier message signatures++-- | Verifies a webhook. This is a wrapper around 'verifyWebhookWith' that uses+-- 'typicalTolerance' and the current time.+--+-- Throws an exception if the webhook is invalid.+verifyWebhook ::+  (MonadIO.MonadIO m, Exception.MonadThrow m) =>+  Verifier.Verifier ->+  Message.Message ->+  Signatures.Signatures ->+  m Signature.Signature+verifyWebhook verifier message signatures = do+  now <- MonadIO.liftIO Time.getCurrentTime+  Either.throw $ verifyWebhookWith typicalTolerance now verifier message signatures++-- | Verifies a webhook. This is the lowest-level function that gives you the+-- most control. If you're looking for something that's easier to use and+-- assumes some reasonable defaults, consider 'verifyWebhook'.+verifyWebhookWith ::+  -- | Often 'typicalTolerance'.+  Tolerance.Tolerance ->+  -- | Usually 'Time.getCurrentTime'.+  Time.UTCTime ->+  -- | See 'parseVerifier'.+  Verifier.Verifier ->+  -- | See 'parseMessage'. Or 'Message.MkMessage' along with 'parseId',+  -- 'parseTimestamp', and 'Payload.MkPayload'.+  Message.Message ->+  -- | See 'parseSignatures'.+  Signatures.Signatures ->+  Either SignetException.SignetException Signature.Signature+verifyWebhookWith tolerance now verifier message signatures = do+  Bifunctor.first SignetException.ToleranceException+    . Tolerance.check tolerance now+    $ Message.timestamp message+  Bifunctor.first SignetException.VerificationException $+    Verifier.verify verifier message signatures++-- | Alias for 'Tolerance.typical'.+typicalTolerance :: Tolerance.Tolerance+typicalTolerance = Tolerance.typical++-- | Alias for 'Verifier.parse'.+parseVerifier :: ByteString.ByteString -> Either InvalidVerifier.InvalidVerifier Verifier.Verifier+parseVerifier = Verifier.parse++-- | Alias for 'Id.parse'.+parseId :: ByteString.ByteString -> Either InvalidId.InvalidId Id.Id+parseId = Id.parse++-- | Alias for 'Timestamp.parse'.+parseTimestamp :: ByteString.ByteString -> Either InvalidTimestamp.InvalidTimestamp Timestamp.Timestamp+parseTimestamp = Timestamp.parse++-- | Alias for 'Message.parse'.+parseMessage :: ByteString.ByteString -> Either InvalidMessage.InvalidMessage Message.Message+parseMessage = Message.parse++-- | Alias for 'Signatures.parse'.+parseSignatures ::+  ByteString.ByteString ->+  Either InvalidSignature.InvalidSignature ([UnknownSignature.UnknownSignature], Signatures.Signatures)+parseSignatures = Signatures.parse++-- | Alias for 'Verifier.Asymmetric'.+pattern AsymmetricVerifier :: PublicKey.PublicKey -> Verifier.Verifier+pattern AsymmetricVerifier publicKey = Verifier.Asymmetric publicKey++-- | Alias for 'Verifier.Symmetric'.+pattern SymmetricVerifier :: Secret.Secret -> Verifier.Verifier+pattern SymmetricVerifier secret = Verifier.Symmetric secret++{-# COMPLETE AsymmetricVerifier, SymmetricVerifier #-}++-- | Alias for 'Signer.sign'.+signWebhook :: Signer.Signer -> Message.Message -> Signature.Signature+signWebhook = Signer.sign++-- | Alias for 'Signer.parse'.+parseSigner :: ByteString.ByteString -> Either InvalidSigner.InvalidSigner Signer.Signer+parseSigner = Signer.parse++-- | Alias for 'Signer.Asymmetric'.+pattern AsymmetricSigner :: SecretKey.SecretKey -> Signer.Signer+pattern AsymmetricSigner secretKey = Signer.Asymmetric secretKey++-- | Alias for 'Signer.Symmetric'.+pattern SymmetricSigner :: Secret.Secret -> Signer.Signer+pattern SymmetricSigner secret = Signer.Symmetric secret++{-# COMPLETE AsymmetricSigner, SymmetricSigner #-}++-- | Alias for 'Signature.Asymmetric'.+pattern AsymmetricSignature :: AsymmetricSignature.AsymmetricSignature -> Signature.Signature+pattern AsymmetricSignature asymmetricSignature = Signature.Asymmetric asymmetricSignature++-- | Alias for 'Signature.Symmetric'.+pattern SymmetricSignature :: SymmetricSignature.SymmetricSignature -> Signature.Signature+pattern SymmetricSignature symmetricSignature = Signature.Symmetric symmetricSignature++{-# COMPLETE AsymmetricSignature, SymmetricSignature #-}
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidAsymmetricSignature.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidAsymmetricSignature where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidAsymmetricSignature+  = MkInvalidAsymmetricSignature ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidAsymmetricSignature where+  displayException = mappend "invalid asymmetric signature: " . show . unwrap++unwrap :: InvalidAsymmetricSignature -> ByteString.ByteString+unwrap (MkInvalidAsymmetricSignature byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidId.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidId where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidId+  = MkInvalidId ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidId where+  displayException = mappend "invalid ID: " . show . unwrap++unwrap :: InvalidId -> ByteString.ByteString+unwrap (MkInvalidId byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidMessage.hs view
@@ -0,0 +1,18 @@+module Signet.Unstable.Exception.InvalidMessage where++import qualified Control.Monad.Catch as Exception+import qualified Signet.Unstable.Exception.InvalidId as InvalidId+import qualified Signet.Unstable.Exception.InvalidTimestamp as InvalidTimestamp++data InvalidMessage+  = InvalidId InvalidId.InvalidId+  | InvalidTimestamp InvalidTimestamp.InvalidTimestamp+  deriving (Eq, Show)++instance Exception.Exception InvalidMessage where+  displayException invalidMessage =+    "invalid message: " <> case invalidMessage of+      InvalidId invalidId ->+        Exception.displayException invalidId+      InvalidTimestamp invalidTimestamp ->+        Exception.displayException invalidTimestamp
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidPublicKey.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidPublicKey where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidPublicKey+  = MkInvalidPublicKey ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidPublicKey where+  displayException = mappend "invalid public key: " . show . unwrap++unwrap :: InvalidPublicKey -> ByteString.ByteString+unwrap (MkInvalidPublicKey byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidSecret.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidSecret where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidSecret+  = MkInvalidSecret ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidSecret where+  displayException = mappend "invalid secret: " . show . unwrap++unwrap :: InvalidSecret -> ByteString.ByteString+unwrap (MkInvalidSecret byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidSecretKey.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidSecretKey where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidSecretKey+  = MkInvalidSecretKey ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidSecretKey where+  displayException = mappend "invalid secret key: " . show . unwrap++unwrap :: InvalidSecretKey -> ByteString.ByteString+unwrap (MkInvalidSecretKey byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidSignature.hs view
@@ -0,0 +1,18 @@+module Signet.Unstable.Exception.InvalidSignature where++import qualified Control.Monad.Catch as Exception+import qualified Signet.Unstable.Exception.InvalidAsymmetricSignature as InvalidAsymmetricSignature+import qualified Signet.Unstable.Exception.InvalidSymmetricSignature as InvalidSymmetricSignature++data InvalidSignature+  = InvalidAsymmetricSignature InvalidAsymmetricSignature.InvalidAsymmetricSignature+  | InvalidSymmetricSignature InvalidSymmetricSignature.InvalidSymmetricSignature+  deriving (Eq, Show)++instance Exception.Exception InvalidSignature where+  displayException invalidSignature =+    "invalid signature: " <> case invalidSignature of+      InvalidAsymmetricSignature invalidAsymmetricSignature ->+        Exception.displayException invalidAsymmetricSignature+      InvalidSymmetricSignature invalidSymmetricSignature ->+        Exception.displayException invalidSymmetricSignature
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidSigner.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidSigner where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidSigner+  = MkInvalidSigner ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidSigner where+  displayException = mappend "invalid signer: " . show . unwrap++unwrap :: InvalidSigner -> ByteString.ByteString+unwrap (MkInvalidSigner byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidSymmetricSignature.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidSymmetricSignature where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidSymmetricSignature+  = MkInvalidSymmetricSignature ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidSymmetricSignature where+  displayException = mappend "invalid symmetric signature: " . show . unwrap++unwrap :: InvalidSymmetricSignature -> ByteString.ByteString+unwrap (MkInvalidSymmetricSignature byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidTimestamp.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidTimestamp where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidTimestamp+  = MkInvalidTimestamp ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidTimestamp where+  displayException = mappend "invalid timestamp: " . show . unwrap++unwrap :: InvalidTimestamp -> ByteString.ByteString+unwrap (MkInvalidTimestamp byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/InvalidVerifier.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.InvalidVerifier where++import qualified Control.Monad.Catch as Exception+import qualified Data.ByteString as ByteString++newtype InvalidVerifier+  = MkInvalidVerifier ByteString.ByteString+  deriving (Eq, Show)++instance Exception.Exception InvalidVerifier where+  displayException = mappend "invalid verifier: " . show . unwrap++unwrap :: InvalidVerifier -> ByteString.ByteString+unwrap (MkInvalidVerifier byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Exception/SignetException.hs view
@@ -0,0 +1,18 @@+module Signet.Unstable.Exception.SignetException where++import qualified Control.Monad.Catch as Exception+import qualified Signet.Unstable.Exception.ToleranceException as ToleranceException+import qualified Signet.Unstable.Exception.VerificationException as VerificationException++data SignetException+  = ToleranceException ToleranceException.ToleranceException+  | VerificationException VerificationException.VerificationException+  deriving (Eq, Show)++instance Exception.Exception SignetException where+  displayException signetException =+    "failed to verify webhook: " <> case signetException of+      ToleranceException toleranceException ->+        Exception.displayException toleranceException+      VerificationException verificationException ->+        Exception.displayException verificationException
+ source/libraries/unstable/Signet/Unstable/Exception/ToleranceException.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.ToleranceException where++import qualified Control.Monad.Catch as Exception+import qualified Signet.Unstable.Type.Timestamp as Timestamp++newtype ToleranceException+  = MkToleranceException Timestamp.Timestamp+  deriving (Eq, Show)++instance Exception.Exception ToleranceException where+  displayException = mappend "timestamp out of tolerance: " . show . unwrap++unwrap :: ToleranceException -> Timestamp.Timestamp+unwrap (MkToleranceException timestamp) = timestamp
+ source/libraries/unstable/Signet/Unstable/Exception/VerificationException.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Exception.VerificationException where++import qualified Control.Monad.Catch as Exception+import qualified Signet.Unstable.Type.Id as Id++newtype VerificationException+  = MkVerificationException Id.Id+  deriving (Eq, Show)++instance Exception.Exception VerificationException where+  displayException = mappend "verification failed: " . show . unwrap++unwrap :: VerificationException -> Id.Id+unwrap (MkVerificationException id_) = id_
+ source/libraries/unstable/Signet/Unstable/Extra/Either.hs view
@@ -0,0 +1,9 @@+module Signet.Unstable.Extra.Either where++import qualified Control.Monad.Catch as Exception++hush :: Either x a -> Maybe a+hush = either (const Nothing) Just++throw :: (Exception.Exception e, Exception.MonadThrow m) => Either e a -> m a+throw = either Exception.throwM pure
+ source/libraries/unstable/Signet/Unstable/Extra/Http.hs view
@@ -0,0 +1,14 @@+module Signet.Unstable.Extra.Http where++import qualified Data.ByteString.Char8 as Ascii+import qualified Data.CaseInsensitive as CI+import qualified Network.HTTP.Types as Http++hWebhookId :: Http.HeaderName+hWebhookId = CI.mk $ Ascii.pack "webhook-id"++hWebhookSignature :: Http.HeaderName+hWebhookSignature = CI.mk $ Ascii.pack "webhook-signature"++hWebhookTimestamp :: Http.HeaderName+hWebhookTimestamp = CI.mk $ Ascii.pack "webhook-timestamp"
+ source/libraries/unstable/Signet/Unstable/Extra/Maybe.hs view
@@ -0,0 +1,4 @@+module Signet.Unstable.Extra.Maybe where++note :: e -> Maybe a -> Either e a+note = flip maybe Right . Left
+ source/libraries/unstable/Signet/Unstable/Type/AsymmetricSignature.hs view
@@ -0,0 +1,28 @@+module Signet.Unstable.Type.AsymmetricSignature where++import qualified Crypto.Error as Error+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteArray.Encoding as Encoding+import qualified Data.ByteString as ByteString+import qualified Signet.Unstable.Exception.InvalidAsymmetricSignature as InvalidAsymmetricSignature+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Maybe as Maybe++newtype AsymmetricSignature+  = MkAsymmetricSignature Ed25519.Signature+  deriving (Eq, Show)++unwrap :: AsymmetricSignature -> Ed25519.Signature+unwrap (MkAsymmetricSignature signature) = signature++parse ::+  ByteString.ByteString ->+  Either InvalidAsymmetricSignature.InvalidAsymmetricSignature AsymmetricSignature+parse encoded = Maybe.note (InvalidAsymmetricSignature.MkInvalidAsymmetricSignature encoded) $ do+  byteString <- Either.hush $ Encoding.convertFromBase Encoding.Base64 encoded+  fmap MkAsymmetricSignature+    . Error.maybeCryptoError+    $ Ed25519.signature (byteString :: ByteString.ByteString)++render :: AsymmetricSignature -> ByteString.ByteString+render = Encoding.convertToBase Encoding.Base64 . unwrap
+ source/libraries/unstable/Signet/Unstable/Type/Id.hs view
@@ -0,0 +1,26 @@+module Signet.Unstable.Type.Id where++import qualified Control.Monad as Monad+import qualified Data.ByteString as ByteString+import qualified Data.Word as Word+import qualified Signet.Unstable.Exception.InvalidId as InvalidId++newtype Id+  = MkId ByteString.ByteString+  deriving (Eq, Show)++unwrap :: Id -> ByteString.ByteString+unwrap (MkId byteString) = byteString++separator :: Word.Word8+separator = 0x2e++parse :: ByteString.ByteString -> Either InvalidId.InvalidId Id+parse byteString = do+  Monad.when (ByteString.any (== separator) byteString)+    . Left+    $ InvalidId.MkInvalidId byteString+  Right $ MkId byteString++render :: Id -> ByteString.ByteString+render = unwrap
+ source/libraries/unstable/Signet/Unstable/Type/Message.hs view
@@ -0,0 +1,36 @@+module Signet.Unstable.Type.Message where++import qualified Data.Bifunctor as Bifunctor+import qualified Data.ByteString as ByteString+import qualified Signet.Unstable.Exception.InvalidMessage as InvalidMessage+import qualified Signet.Unstable.Type.Id as Id+import qualified Signet.Unstable.Type.Payload as Payload+import qualified Signet.Unstable.Type.Timestamp as Timestamp++data Message = MkMessage+  { id_ :: Id.Id,+    timestamp :: Timestamp.Timestamp,+    payload :: Payload.Payload+  }+  deriving (Eq, Show)++parse :: ByteString.ByteString -> Either InvalidMessage.InvalidMessage Message+parse x = do+  let (rawId, y) = ByteString.break (== Id.separator) x+  theId <- Bifunctor.first InvalidMessage.InvalidId $ Id.parse rawId+  let (rawTimestamp, z) =+        ByteString.break (== Id.separator) $+          ByteString.drop 1 y+  theTimestamp <-+    Bifunctor.first InvalidMessage.InvalidTimestamp $+      Timestamp.parse rawTimestamp+  let thePayload = Payload.MkPayload $ ByteString.drop 1 z+  pure MkMessage {id_ = theId, timestamp = theTimestamp, payload = thePayload}++render :: Message -> ByteString.ByteString+render message =+  Id.render (id_ message)+    <> ByteString.singleton Id.separator+    <> Timestamp.render (timestamp message)+    <> ByteString.singleton Id.separator+    <> Payload.unwrap (payload message)
+ source/libraries/unstable/Signet/Unstable/Type/Payload.hs view
@@ -0,0 +1,10 @@+module Signet.Unstable.Type.Payload where++import qualified Data.ByteString as ByteString++newtype Payload+  = MkPayload ByteString.ByteString+  deriving (Eq, Show)++unwrap :: Payload -> ByteString.ByteString+unwrap (MkPayload byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Type/PublicKey.hs view
@@ -0,0 +1,31 @@+module Signet.Unstable.Type.PublicKey where++import qualified Crypto.Error as Error+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteArray.Encoding as Encoding+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidPublicKey as InvalidPublicKey+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Maybe as Maybe++newtype PublicKey+  = MkPublicKey Ed25519.PublicKey+  deriving (Eq, Show)++unwrap :: PublicKey -> Ed25519.PublicKey+unwrap (MkPublicKey publicKey) = publicKey++prefix :: ByteString.ByteString+prefix = Ascii.pack "whpk_"++parse :: ByteString.ByteString -> Either InvalidPublicKey.InvalidPublicKey PublicKey+parse prefixed = Maybe.note (InvalidPublicKey.MkInvalidPublicKey prefixed) $ do+  encoded <- ByteString.stripPrefix prefix prefixed+  byteString <- Either.hush $ Encoding.convertFromBase Encoding.Base64 encoded+  fmap MkPublicKey+    . Error.maybeCryptoError+    $ Ed25519.publicKey (byteString :: ByteString.ByteString)++render :: PublicKey -> ByteString.ByteString+render = mappend prefix . Encoding.convertToBase Encoding.Base64 . unwrap
+ source/libraries/unstable/Signet/Unstable/Type/Secret.hs view
@@ -0,0 +1,27 @@+module Signet.Unstable.Type.Secret where++import qualified Data.ByteArray as ByteArray+import qualified Data.ByteArray.Encoding as Encoding+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidSecret as InvalidSecret+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Maybe as Maybe++newtype Secret+  = MkSecret ByteArray.ScrubbedBytes+  deriving (Eq, Show)++unwrap :: Secret -> ByteArray.ScrubbedBytes+unwrap (MkSecret scrubbedBytes) = scrubbedBytes++prefix :: ByteString.ByteString+prefix = Ascii.pack "whsec_"++parse :: ByteString.ByteString -> Either InvalidSecret.InvalidSecret Secret+parse prefixed = Maybe.note (InvalidSecret.MkInvalidSecret prefixed) $ do+  encoded <- ByteString.stripPrefix prefix prefixed+  fmap MkSecret . Either.hush $ Encoding.convertFromBase Encoding.Base64 encoded++render :: Secret -> ByteString.ByteString+render = mappend prefix . Encoding.convertToBase Encoding.Base64 . unwrap
+ source/libraries/unstable/Signet/Unstable/Type/SecretKey.hs view
@@ -0,0 +1,31 @@+module Signet.Unstable.Type.SecretKey where++import qualified Crypto.Error as Error+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteArray.Encoding as Encoding+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Ascii+import qualified Signet.Unstable.Exception.InvalidSecretKey as InvalidSecretKey+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Maybe as Maybe++newtype SecretKey+  = MkSecretKey Ed25519.SecretKey+  deriving (Eq, Show)++unwrap :: SecretKey -> Ed25519.SecretKey+unwrap (MkSecretKey secretKey) = secretKey++prefix :: ByteString.ByteString+prefix = Ascii.pack "whsk_"++parse :: ByteString.ByteString -> Either InvalidSecretKey.InvalidSecretKey SecretKey+parse prefixed = Maybe.note (InvalidSecretKey.MkInvalidSecretKey prefixed) $ do+  encoded <- ByteString.stripPrefix prefix prefixed+  byteString <- Either.hush $ Encoding.convertFromBase Encoding.Base64 encoded+  fmap MkSecretKey+    . Error.maybeCryptoError+    $ Ed25519.secretKey (byteString :: ByteString.ByteString)++render :: SecretKey -> ByteString.ByteString+render = mappend prefix . Encoding.convertToBase Encoding.Base64 . unwrap
+ source/libraries/unstable/Signet/Unstable/Type/Signature.hs view
@@ -0,0 +1,49 @@+module Signet.Unstable.Type.Signature where++import qualified Data.Bifunctor as Bifunctor+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Ascii+import qualified Data.Word as Word+import qualified Signet.Unstable.Exception.InvalidSignature as InvalidSignature+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature+import qualified Signet.Unstable.Type.UnknownSignature as UnknownSignature++data Signature+  = Asymmetric AsymmetricSignature.AsymmetricSignature+  | Symmetric SymmetricSignature.SymmetricSignature+  deriving (Eq, Show)++separator :: Word.Word8+separator = 0x2c++asymmetricPrefix :: ByteString.ByteString+asymmetricPrefix = Ascii.pack "v1a"++symmetricPrefix :: ByteString.ByteString+symmetricPrefix = Ascii.pack "v1"++parse :: ByteString.ByteString -> Either InvalidSignature.InvalidSignature (Either UnknownSignature.UnknownSignature Signature)+parse prefixed = do+  let (prefix, rest) = ByteString.break (== separator) prefixed+  case ByteString.drop 1 rest of+    byteString+      | prefix == asymmetricPrefix ->+          Bifunctor.bimap InvalidSignature.InvalidAsymmetricSignature (Right . Asymmetric) $+            AsymmetricSignature.parse byteString+      | prefix == symmetricPrefix ->+          Bifunctor.bimap InvalidSignature.InvalidSymmetricSignature (Right . Symmetric) $+            SymmetricSignature.parse byteString+    _ -> Right . Left $ UnknownSignature.MkUnknownSignature prefixed++render :: Signature -> ByteString.ByteString+render signature =+  case signature of+    Asymmetric asymmetricSignature ->+      asymmetricPrefix+        <> ByteString.singleton separator+        <> AsymmetricSignature.render asymmetricSignature+    Symmetric symmetricSignature ->+      symmetricPrefix+        <> ByteString.singleton separator+        <> SymmetricSignature.render symmetricSignature
+ source/libraries/unstable/Signet/Unstable/Type/Signatures.hs view
@@ -0,0 +1,30 @@+module Signet.Unstable.Type.Signatures where++import qualified Data.ByteString as ByteString+import qualified Data.Either as Either+import qualified Data.Word as Word+import qualified Signet.Unstable.Exception.InvalidSignature as InvalidSignature+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.UnknownSignature as UnknownSignature++newtype Signatures+  = MkSignatures [Signature.Signature]+  deriving (Eq, Show)++unwrap :: Signatures -> [Signature.Signature]+unwrap (MkSignatures signatures) = signatures++separator :: Word.Word8+separator = 0x20++parse :: ByteString.ByteString -> Either InvalidSignature.InvalidSignature ([UnknownSignature.UnknownSignature], Signatures)+parse =+  fmap (fmap MkSignatures . Either.partitionEithers)+    . traverse Signature.parse+    . ByteString.split separator++render :: Signatures -> ByteString.ByteString+render =+  ByteString.intercalate (ByteString.singleton separator)+    . fmap Signature.render+    . unwrap
+ source/libraries/unstable/Signet/Unstable/Type/Signer.hs view
@@ -0,0 +1,49 @@+module Signet.Unstable.Type.Signer where++import qualified Crypto.MAC.HMAC as Hmac+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteString as ByteString+import qualified Signet.Unstable.Exception.InvalidSigner as InvalidSigner+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import qualified Signet.Unstable.Type.Message as Message+import qualified Signet.Unstable.Type.Secret as Secret+import qualified Signet.Unstable.Type.SecretKey as SecretKey+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature++data Signer+  = Asymmetric SecretKey.SecretKey+  | Symmetric Secret.Secret+  deriving (Eq, Show)++parse :: ByteString.ByteString -> Either InvalidSigner.InvalidSigner Signer+parse byteString = case SecretKey.parse byteString of+  Right secretKey -> Right $ Asymmetric secretKey+  Left _ -> case Secret.parse byteString of+    Right secret -> Right $ Symmetric secret+    Left _ -> Left $ InvalidSigner.MkInvalidSigner byteString++render :: Signer -> ByteString.ByteString+render signer = case signer of+  Asymmetric secretKey -> SecretKey.render secretKey+  Symmetric secret -> Secret.render secret++sign :: Signer -> Message.Message -> Signature.Signature+sign signer = case signer of+  Asymmetric secretKey -> Signature.Asymmetric . asymmetric secretKey+  Symmetric secret -> Signature.Symmetric . symmetric secret++asymmetric :: SecretKey.SecretKey -> Message.Message -> AsymmetricSignature.AsymmetricSignature+asymmetric secretKey =+  let sk = SecretKey.unwrap secretKey+      pk = Ed25519.toPublic sk+   in AsymmetricSignature.MkAsymmetricSignature+        . Ed25519.sign sk pk+        . Message.render++symmetric :: Secret.Secret -> Message.Message -> SymmetricSignature.SymmetricSignature+symmetric secret =+  SymmetricSignature.MkSymmetricSignature+    . Hmac.hmacGetDigest+    . Hmac.hmac (Secret.unwrap secret)+    . Message.render
+ source/libraries/unstable/Signet/Unstable/Type/SymmetricSignature.hs view
@@ -0,0 +1,28 @@+module Signet.Unstable.Type.SymmetricSignature where++import qualified Crypto.Hash as Hash+import qualified Data.ByteArray as ByteArray+import qualified Data.ByteArray.Encoding as Encoding+import qualified Data.ByteString as ByteString+import qualified Data.Function as Function+import qualified Signet.Unstable.Exception.InvalidSymmetricSignature as InvalidSymmetricSignature+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Maybe as Maybe++newtype SymmetricSignature+  = MkSymmetricSignature (Hash.Digest Hash.SHA256)+  deriving (Show)++instance Eq SymmetricSignature where+  (==) = Function.on ByteArray.constEq unwrap++unwrap :: SymmetricSignature -> Hash.Digest Hash.SHA256+unwrap (MkSymmetricSignature digest) = digest++parse :: ByteString.ByteString -> Either InvalidSymmetricSignature.InvalidSymmetricSignature SymmetricSignature+parse encoded = Maybe.note (InvalidSymmetricSignature.MkInvalidSymmetricSignature encoded) $ do+  byteString <- Either.hush $ Encoding.convertFromBase Encoding.Base64 encoded+  MkSymmetricSignature <$> Hash.digestFromByteString (byteString :: ByteString.ByteString)++render :: SymmetricSignature -> ByteString.ByteString+render = Encoding.convertToBase Encoding.Base64 . unwrap
+ source/libraries/unstable/Signet/Unstable/Type/Timestamp.hs view
@@ -0,0 +1,33 @@+module Signet.Unstable.Type.Timestamp where++import qualified Data.ByteString as ByteString+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Time as Time+import qualified Signet.Unstable.Exception.InvalidTimestamp as InvalidTimestamp+import qualified Signet.Unstable.Extra.Either as Either+import qualified Signet.Unstable.Extra.Maybe as Maybe++newtype Timestamp+  = MkTimestamp Time.UTCTime+  deriving (Eq, Show)++unwrap :: Timestamp -> Time.UTCTime+unwrap (MkTimestamp utcTime) = utcTime++format :: String+format = "%s"++parse :: ByteString.ByteString -> Either InvalidTimestamp.InvalidTimestamp Timestamp+parse byteString = Maybe.note (InvalidTimestamp.MkInvalidTimestamp byteString) $ do+  text <- Either.hush $ Text.decodeUtf8' byteString+  fmap MkTimestamp+    . Time.parseTimeM False Time.defaultTimeLocale format+    $ Text.unpack text++render :: Timestamp -> ByteString.ByteString+render =+  Text.encodeUtf8+    . Text.pack+    . Time.formatTime Time.defaultTimeLocale format+    . unwrap
+ source/libraries/unstable/Signet/Unstable/Type/Tolerance.hs view
@@ -0,0 +1,29 @@+module Signet.Unstable.Type.Tolerance where++import qualified Control.Monad as Monad+import qualified Data.Time as Time+import qualified Signet.Unstable.Exception.ToleranceException as ToleranceException+import qualified Signet.Unstable.Type.Timestamp as Timestamp++newtype Tolerance+  = MkTolerance Time.NominalDiffTime+  deriving (Eq, Show)++unwrap :: Tolerance -> Time.NominalDiffTime+unwrap (MkTolerance nominalDiffTime) = nominalDiffTime++typical :: Tolerance+typical = MkTolerance 300++check ::+  Tolerance ->+  Time.UTCTime ->+  Timestamp.Timestamp ->+  Either ToleranceException.ToleranceException ()+check tolerance utcTime timestamp = do+  let diff = Time.diffUTCTime utcTime $ Timestamp.unwrap timestamp+  let hi = unwrap tolerance+  let lo = negate hi+  Monad.when (lo > diff || diff > hi)+    . Left+    $ ToleranceException.MkToleranceException timestamp
+ source/libraries/unstable/Signet/Unstable/Type/UnknownSignature.hs view
@@ -0,0 +1,10 @@+module Signet.Unstable.Type.UnknownSignature where++import qualified Data.ByteString as ByteString++newtype UnknownSignature+  = MkUnknownSignature ByteString.ByteString+  deriving (Eq, Show)++unwrap :: UnknownSignature -> ByteString.ByteString+unwrap (MkUnknownSignature byteString) = byteString
+ source/libraries/unstable/Signet/Unstable/Type/Verifier.hs view
@@ -0,0 +1,75 @@+module Signet.Unstable.Type.Verifier where++import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Data.ByteString as ByteString+import qualified Data.List as List+import qualified Data.Maybe as Maybe+import qualified Signet.Unstable.Exception.InvalidVerifier as InvalidVerifier+import qualified Signet.Unstable.Exception.VerificationException as VerificationException+import qualified Signet.Unstable.Extra.Maybe as Maybe+import qualified Signet.Unstable.Type.AsymmetricSignature as AsymmetricSignature+import qualified Signet.Unstable.Type.Message as Message+import qualified Signet.Unstable.Type.PublicKey as PublicKey+import qualified Signet.Unstable.Type.Secret as Secret+import qualified Signet.Unstable.Type.Signature as Signature+import qualified Signet.Unstable.Type.Signatures as Signatures+import qualified Signet.Unstable.Type.Signer as Signer+import qualified Signet.Unstable.Type.SymmetricSignature as SymmetricSignature++data Verifier+  = Asymmetric PublicKey.PublicKey+  | Symmetric Secret.Secret+  deriving (Eq, Show)++parse :: ByteString.ByteString -> Either InvalidVerifier.InvalidVerifier Verifier+parse byteString = case PublicKey.parse byteString of+  Right publicKey -> Right $ Asymmetric publicKey+  Left _ -> case Secret.parse byteString of+    Right secret -> Right $ Symmetric secret+    Left _ -> Left $ InvalidVerifier.MkInvalidVerifier byteString++render :: Verifier -> ByteString.ByteString+render verifier = case verifier of+  Asymmetric publicKey -> PublicKey.render publicKey+  Symmetric secret -> Secret.render secret++verify ::+  Verifier ->+  Message.Message ->+  Signatures.Signatures ->+  Either VerificationException.VerificationException Signature.Signature+verify verifier message =+  case verifier of+    Asymmetric publicKey -> fmap Signature.Asymmetric . asymmetric publicKey message+    Symmetric secret -> fmap Signature.Symmetric . symmetric secret message++asymmetric ::+  PublicKey.PublicKey ->+  Message.Message ->+  Signatures.Signatures ->+  Either VerificationException.VerificationException AsymmetricSignature.AsymmetricSignature+asymmetric publicKey message =+  let toAsymmetric signature = case signature of+        Signature.Asymmetric asymmetricSignature -> Just asymmetricSignature+        Signature.Symmetric _ -> Nothing+      pk = PublicKey.unwrap publicKey+      bs = Message.render message+   in Maybe.note (VerificationException.MkVerificationException $ Message.id_ message)+        . List.find (Ed25519.verify pk bs . AsymmetricSignature.unwrap)+        . Maybe.mapMaybe toAsymmetric+        . Signatures.unwrap++symmetric ::+  Secret.Secret ->+  Message.Message ->+  Signatures.Signatures ->+  Either VerificationException.VerificationException SymmetricSignature.SymmetricSignature+symmetric secret message =+  let toSymmetric signature = case signature of+        Signature.Asymmetric _ -> Nothing+        Signature.Symmetric symmetricSignature -> Just symmetricSignature+      expected = Signer.symmetric secret message+   in Maybe.note (VerificationException.MkVerificationException $ Message.id_ message)+        . List.find (expected ==)+        . Maybe.mapMaybe toSymmetric+        . Signatures.unwrap
+ source/test-suite/Main.hs view
@@ -0,0 +1,5 @@+import qualified SignetTest+import qualified Test.Tasty as Tasty++main :: IO ()+main = Tasty.defaultMain SignetTest.testTree