packages feed

nkeys (empty) → 0.0.1.0

raw patch · 9 files changed

+865/−0 lines, 9 filesdep +HUnitdep +basedep +base32

Dependencies added: HUnit, base, base32, binary, bytestring, ed25519, nkeys, text, vector

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for nkeys++## 0.0.1.0 -- 2024-04-1++* First version with basic nkey support
+ LICENSE view
@@ -0,0 +1,202 @@++                                 Apache License+                           Version 2.0, January 2004+                        http://www.apache.org/licenses/++   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION++   1. Definitions.++      "License" shall mean the terms and conditions for use, reproduction,+      and distribution as defined by Sections 1 through 9 of this document.++      "Licensor" shall mean the copyright owner or entity authorized by+      the copyright owner that is granting the License.++      "Legal Entity" shall mean the union of the acting entity and all+      other entities that control, are controlled by, or are under common+      control with that entity. For the purposes of this definition,+      "control" means (i) the power, direct or indirect, to cause the+      direction or management of such entity, whether by contract or+      otherwise, or (ii) ownership of fifty percent (50%) or more of the+      outstanding shares, or (iii) beneficial ownership of such entity.++      "You" (or "Your") shall mean an individual or Legal Entity+      exercising permissions granted by this License.++      "Source" form shall mean the preferred form for making modifications,+      including but not limited to software source code, documentation+      source, and configuration files.++      "Object" form shall mean any form resulting from mechanical+      transformation or translation of a Source form, including but+      not limited to compiled object code, generated documentation,+      and conversions to other media types.++      "Work" shall mean the work of authorship, whether in Source or+      Object form, made available under the License, as indicated by a+      copyright notice that is included in or attached to the work+      (an example is provided in the Appendix below).++      "Derivative Works" shall mean any work, whether in Source or Object+      form, that is based on (or derived from) the Work and for which the+      editorial revisions, annotations, elaborations, or other modifications+      represent, as a whole, an original work of authorship. For the purposes+      of this License, Derivative Works shall not include works that remain+      separable from, or merely link (or bind by name) to the interfaces of,+      the Work and Derivative Works thereof.++      "Contribution" shall mean any work of authorship, including+      the original version of the Work and any modifications or additions+      to that Work or Derivative Works thereof, that is intentionally+      submitted to Licensor for inclusion in the Work by the copyright owner+      or by an individual or Legal Entity authorized to submit on behalf of+      the copyright owner. For the purposes of this definition, "submitted"+      means any form of electronic, verbal, or written communication sent+      to the Licensor or its representatives, including but not limited to+      communication on electronic mailing lists, source code control systems,+      and issue tracking systems that are managed by, or on behalf of, the+      Licensor for the purpose of discussing and improving the Work, but+      excluding communication that is conspicuously marked or otherwise+      designated in writing by the copyright owner as "Not a Contribution."++      "Contributor" shall mean Licensor and any individual or Legal Entity+      on behalf of whom a Contribution has been received by Licensor and+      subsequently incorporated within the Work.++   2. Grant of Copyright License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      copyright license to reproduce, prepare Derivative Works of,+      publicly display, publicly perform, sublicense, and distribute the+      Work and such Derivative Works in Source or Object form.++   3. Grant of Patent License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      (except as stated in this section) patent license to make, have made,+      use, offer to sell, sell, import, and otherwise transfer the Work,+      where such license applies only to those patent claims licensable+      by such Contributor that are necessarily infringed by their+      Contribution(s) alone or by combination of their Contribution(s)+      with the Work to which such Contribution(s) was submitted. If You+      institute patent litigation against any entity (including a+      cross-claim or counterclaim in a lawsuit) alleging that the Work+      or a Contribution incorporated within the Work constitutes direct+      or contributory patent infringement, then any patent licenses+      granted to You under this License for that Work shall terminate+      as of the date such litigation is filed.++   4. Redistribution. You may reproduce and distribute copies of the+      Work or Derivative Works thereof in any medium, with or without+      modifications, and in Source or Object form, provided that You+      meet the following conditions:++      (a) You must give any other recipients of the Work or+          Derivative Works a copy of this License; and++      (b) You must cause any modified files to carry prominent notices+          stating that You changed the files; and++      (c) You must retain, in the Source form of any Derivative Works+          that You distribute, all copyright, patent, trademark, and+          attribution notices from the Source form of the Work,+          excluding those notices that do not pertain to any part of+          the Derivative Works; and++      (d) If the Work includes a "NOTICE" text file as part of its+          distribution, then any Derivative Works that You distribute must+          include a readable copy of the attribution notices contained+          within such NOTICE file, excluding those notices that do not+          pertain to any part of the Derivative Works, in at least one+          of the following places: within a NOTICE text file distributed+          as part of the Derivative Works; within the Source form or+          documentation, if provided along with the Derivative Works; or,+          within a display generated by the Derivative Works, if and+          wherever such third-party notices normally appear. The contents+          of the NOTICE file are for informational purposes only and+          do not modify the License. You may add Your own attribution+          notices within Derivative Works that You distribute, alongside+          or as an addendum to the NOTICE text from the Work, provided+          that such additional attribution notices cannot be construed+          as modifying the License.++      You may add Your own copyright statement to Your modifications and+      may provide additional or different license terms and conditions+      for use, reproduction, or distribution of Your modifications, or+      for any such Derivative Works as a whole, provided Your use,+      reproduction, and distribution of the Work otherwise complies with+      the conditions stated in this License.++   5. Submission of Contributions. Unless You explicitly state otherwise,+      any Contribution intentionally submitted for inclusion in the Work+      by You to the Licensor shall be under the terms and conditions of+      this License, without any additional terms or conditions.+      Notwithstanding the above, nothing herein shall supersede or modify+      the terms of any separate license agreement you may have executed+      with Licensor regarding such Contributions.++   6. Trademarks. This License does not grant permission to use the trade+      names, trademarks, service marks, or product names of the Licensor,+      except as required for reasonable and customary use in describing the+      origin of the Work and reproducing the content of the NOTICE file.++   7. Disclaimer of Warranty. Unless required by applicable law or+      agreed to in writing, Licensor provides the Work (and each+      Contributor provides its Contributions) on an "AS IS" BASIS,+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or+      implied, including, without limitation, any warranties or conditions+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+      PARTICULAR PURPOSE. You are solely responsible for determining the+      appropriateness of using or redistributing the Work and assume any+      risks associated with Your exercise of permissions under this License.++   8. Limitation of Liability. In no event and under no legal theory,+      whether in tort (including negligence), contract, or otherwise,+      unless required by applicable law (such as deliberate and grossly+      negligent acts) or agreed to in writing, shall any Contributor be+      liable to You for damages, including any direct, indirect, special,+      incidental, or consequential damages of any character arising as a+      result of this License or out of the use or inability to use the+      Work (including but not limited to damages for loss of goodwill,+      work stoppage, computer failure or malfunction, or any and all+      other commercial damages or losses), even if such Contributor+      has been advised of the possibility of such damages.++   9. Accepting Warranty or Additional Liability. While redistributing+      the Work or Derivative Works thereof, You may choose to offer,+      and charge a fee for, acceptance of support, warranty, indemnity,+      or other liability obligations and/or rights consistent with this+      License. However, in accepting such obligations, You may act only+      on Your own behalf and on Your sole responsibility, not on behalf+      of any other Contributor, and only if You agree to indemnify,+      defend, and hold each Contributor harmless for any liability+      incurred by, or claims asserted against, such Contributor by reason+      of your accepting any such warranty or additional liability.++   END OF TERMS AND CONDITIONS++   APPENDIX: How to apply the Apache License to your work.++      To apply the Apache License to your work, attach the following+      boilerplate notice, with the fields enclosed by brackets "[]"+      replaced with your own identifying information. (Don't include+      the brackets!)  The text should be enclosed in the appropriate+      comment syntax for the file format. We also recommend that a+      file or class name and description of purpose be included on the+      same "printed page" as the copyright notice for easier+      identification within third-party archives.++   Copyright [yyyy] [name of copyright owner]++   Licensed under the Apache License, Version 2.0 (the "License");+   you may not use this file except in compliance with the License.+   You may obtain a copy of the License at++       http://www.apache.org/licenses/LICENSE-2.0++   Unless required by applicable law or agreed to in writing, software+   distributed under the License is distributed on an "AS IS" BASIS,+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+   See the License for the specific language governing permissions and+   limitations under the License.
+ app/Main.hs view
@@ -0,0 +1,7 @@+module Main where++import qualified Nats.Nkeys++main :: IO ()+main = do+  putStrLn "Hello, Haskell!"
+ nkeys.cabal view
@@ -0,0 +1,65 @@+cabal-version:      2.4+name:               nkeys+version:            0.0.1.0+synopsis:           Nkeys ed25519 encoding for use with NATS++description: This library exposes a custom encoding and decoding mechanism for ed25519 keys. This codec produces keys that are both URL safe and double-clickable +homepage:           https://github.com/autodidaddict/nkeys-hs++-- A URL where users can report bugs.+-- bug-reports:+license:            Apache-2.0+license-file:       LICENSE+author:             Kevin Hoffman+maintainer:         autodidaddict@users.noreply.github.com++-- A copyright notice.+-- copyright:+category:           Cryptography+extra-source-files: CHANGELOG.md++library nkeys-lib+    exposed-modules:  Nats.Nkeys++    -- Modules included in this library but not exported.+    other-modules: Nats.Nkeys.Pairs, Nats.Nkeys.Codec, Nats.Nkeys.Crc++    -- LANGUAGE extensions used by modules in this package.+    -- other-extensions:+    build-depends:    base == 4.19.0.0,+                      ed25519 >= 0.0.5 && < 0.1,+                      bytestring ^>=0.11,+                      text >= 2.1 && < 2.2,+                      base32 >= 0.4 && < 0.5,                      +                      vector >= 0.13.1 && < 0.14,+                      binary >= 0.8.9 && < 0.9+    hs-source-dirs:   src+    default-language: Haskell2010++executable nkeys-exe+    main-is:          Main.hs++    -- Modules included in this executable, other than Main.+    -- other-modules:++    -- LANGUAGE extensions used by modules in this package.+    -- other-extensions:+    build-depends:+        base == 4.19.0.0,+        nkeys-lib++    hs-source-dirs:   app+    default-language: Haskell2010++test-suite nkeys-test+    default-language: Haskell2010+    type:             exitcode-stdio-1.0+    hs-source-dirs:   test+    main-is:          NkeysTest.hs+    build-depends:    base == 4.19.0.0,+                      HUnit ^>=1.6,+                      base32 >= 0.4 && < 0.5,+                      text >= 2.1,  +                      ed25519 >= 0.0.5 && < 0.1,+                      nkeys-lib,+                      bytestring ^>=0.11
+ src/Nats/Nkeys.hs view
@@ -0,0 +1,46 @@+-- |+-- Module       : Nats.Nkeys+-- Copyright    : (c) 2023 Kevin Hoffman+-- License      : Apache-2.0+--+-- Maintainer   : Kevin Hoffman+--+-- Support for the NATS encoding of ed25519 key pairs. Internally these keys and seeds are standard+-- ed25519 bytes. This package provides an encoding/decoding layer on top that produces the long,+-- capital-letter keys that begin with well-known prefixes.+--+-- The main benefit to using the NATS encoding for ed25519 keys is that Nkeys are URL-safe, prefixed+-- with the purpose/role of the key, and are even double-clickable on most computers. Nkeys are also+-- an integral part of NATS's decentralized, JWT-based security.+--+-- =Usage+-- The following code shows some of the common ways of using this library+--+-- Creating a key pair from random bytes:+-- +-- >>> bob <- create User+-- >>> bob+-- UBXEJQE5OZ2Y7YAWGLRQQDTFFUVUQMRZG6W4BU3FW2XDNYBXMH72OR45+--+-- Create a key pair from an existing seed:+--+-- >>> Just alice = createFromSeed $ seed bob+-- >>> alice+-- UBXEJQE5OZ2Y7YAWGLRQQDTFFUVUQMRZG6W4BU3FW2XDNYBXMH72OR45+-- +-- Sign and verify messages using keys:+--+-- >>> let message = C.pack "hello there"+-- >>> let Just sig = sign bob message+-- >>> let verified = verify bob message sig+--++module Nats.Nkeys (+    module Nats.Nkeys.Pairs,+    module Nats.Nkeys.Codec,+    module Crypto.Sign.Ed25519    +) where++import Nats.Nkeys.Pairs (KeyPair, create, publicKey, seed, createFromSeed, sign, verify)+import Nats.Nkeys.Codec (KeyPrefix(..))+import Crypto.Sign.Ed25519 (Signature)
+ src/Nats/Nkeys/Codec.hs view
@@ -0,0 +1,128 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}++module Nats.Nkeys.Codec (KeyPrefix (..), Nats.Nkeys.Codec.encode, encodeSeed, fromByte, toByte, decode, extractSeedPrefix, extractCrc) where++import Data.Binary (encode, putWord8)+import Data.Bits+import Debug.Trace (trace)+import Text.Printf (printf)+import Data.ByteString as B+import Data.ByteString.Base32 (decodeBase32, decodeBase32Unpadded, encodeBase32Unpadded)+import Data.Data+import Data.Text (Text, pack, append)+import Data.Text.Encoding (encodeUtf8)+import Data.Word (Word16, Word8)+import Nats.Nkeys.Crc++-- | Represents the well-known prefixes available for NATS-encoded keys+data KeyPrefix = Seed -- ^ (__S__) Precedes all seed keys, followed by a type prefix+  | Private -- ^ (__P__) Used for private keys+  | Server -- ^ (__N__) Servers and their ilk (nodes, processes, etc)+  | Cluster -- ^ (__C__) Clusters+  | Operator -- ^ (__O__) Operators+  | Account -- ^ (__A__) Accounts+  | User -- ^ (__U__) Users+  | Curve  -- ^ (__X__) Curve keys used for encryption/decryption+  | Unknown -- ^ (__Z__) Catch-all for unknown prefixes+   deriving (Eq, Show, Data, Typeable)++toByte :: KeyPrefix -> Word8+toByte prefix = case prefix of+  Seed -> 18 `shiftL` 3+  Private -> 15 `shiftL` 3+  Server -> 13 `shiftL` 3+  Cluster -> 2 `shiftL` 3+  Operator -> 14 `shiftL` 3+  Account -> 0+  User -> 20 `shiftL` 3+  Curve -> 23 `shiftL` 3+  Unknown -> 25 `shiftL` 3++fromByte :: Word8 -> KeyPrefix+fromByte input = case input of+  144 -> Seed+  120 -> Private+  104 -> Server+  16 -> Cluster+  112 -> Operator+  0 -> Account+  160 -> User+  184 -> Curve+  200 -> Unknown+  _ -> Unknown++encode :: KeyPrefix -> ByteString -> ByteString+encode prefix input =+  let raw = B.cons (toByte prefix) input+   in encodeUtf8 . encodeBase32Unpadded $ appendCrc raw++encodeSeed :: KeyPrefix -> ByteString -> ByteString+encodeSeed publicPrefix input =+  let input' = B.take 32 input+      s = toByte Seed+      p = toByte publicPrefix+      raw = prefixBytes [s .|. p `shiftR` 5, fromIntegral $ (p .&. 31) `shiftL` 3] input'+   in encodeUtf8 . encodeBase32Unpadded $ appendCrc raw++decode :: ByteString -> Either Text ByteString+decode input =+  let decoded = decodeBase32Unpadded input+      trimmed = dropEnd 2 <$> decoded+      crc = crc16 <$> trimmed+      expectedCrc = extractCrc <$> decoded       +      crcValid = case (expectedCrc, crc) of+        (Left _, _) -> False+        (_, Left _) -> False+        (Right d, Right c) -> d == c  +  in+    if crcValid then+      trimmed+    else+      Left ("Invalid CRC " :: Text)++extractCrc :: ByteString -> Word16+extractCrc input =+  let input' = B.takeEnd 2 input+  in+  case B.unpack input' of+    [a,b] -> word16FromBytes (a, b)+    _ ->+      0++extractSeedPrefix :: ByteString -> KeyPrefix+extractSeedPrefix input =+  let [r0, r1] = B.unpack $ B.take 2 input +      b0 = r0 .&. 248+      b1 = ((r0 .&. 7) `shiftL` 5) .|. ((r1 .&. 248) `shiftR` 3)+      pb0 = fromByte b0+      pb1 = fromByte b1+  in+    if pb0 /= Seed+    then Unknown+    else pb1++prefixBytes :: [Word8] -> ByteString -> ByteString+prefixBytes bytes input =+  B.pack bytes <> input+  +appendBytes :: [Word8] -> ByteString -> ByteString+appendBytes bytes input =+  input <> B.pack bytes+  +appendCrc :: ByteString -> ByteString+appendCrc raw = +  raw <> B.pack (encodeWord16 $ crc16 raw)++encodeWord16 :: Word16 -> [Word8]+encodeWord16 x =+  let right_byte = x .&. 0xFF+      left_byte = ( x `shiftR` 8 ) .&. 0xFF+  in Prelude.map fromIntegral [right_byte, left_byte]++word16FromBytes :: (Word8, Word8) -> Word16 +word16FromBytes (a, b) =+  let a' = fromIntegral a+      b' = shift (fromIntegral b) 8+  in+    a' .|. b'
+ src/Nats/Nkeys/Crc.hs view
@@ -0,0 +1,284 @@+module Nats.Nkeys.Crc where++import Data.Bits (shiftR, shiftL, xor, (.&.))+import Data.Word (Word16, Word8)+import Data.ByteString++-- CRC-16 lookup table (CCITT XMODEM)+crcTable :: [Word16]+crcTable =+  [ 0x0000,+    0x1021,+    0x2042,+    0x3063,+    0x4084,+    0x50a5,+    0x60c6,+    0x70e7,+    0x8108,+    0x9129,+    0xa14a,+    0xb16b,+    0xc18c,+    0xd1ad,+    0xe1ce,+    0xf1ef,+    0x1231,+    0x0210,+    0x3273,+    0x2252,+    0x52b5,+    0x4294,+    0x72f7,+    0x62d6,+    0x9339,+    0x8318,+    0xb37b,+    0xa35a,+    0xd3bd,+    0xc39c,+    0xf3ff,+    0xe3de,+    0x2462,+    0x3443,+    0x0420,+    0x1401,+    0x64e6,+    0x74c7,+    0x44a4,+    0x5485,+    0xa56a,+    0xb54b,+    0x8528,+    0x9509,+    0xe5ee,+    0xf5cf,+    0xc5ac,+    0xd58d,+    0x3653,+    0x2672,+    0x1611,+    0x0630,+    0x76d7,+    0x66f6,+    0x5695,+    0x46b4,+    0xb75b,+    0xa77a,+    0x9719,+    0x8738,+    0xf7df,+    0xe7fe,+    0xd79d,+    0xc7bc,+    0x48c4,+    0x58e5,+    0x6886,+    0x78a7,+    0x0840,+    0x1861,+    0x2802,+    0x3823,+    0xc9cc,+    0xd9ed,+    0xe98e,+    0xf9af,+    0x8948,+    0x9969,+    0xa90a,+    0xb92b,+    0x5af5,+    0x4ad4,+    0x7ab7,+    0x6a96,+    0x1a71,+    0x0a50,+    0x3a33,+    0x2a12,+    0xdbfd,+    0xcbdc,+    0xfbbf,+    0xeb9e,+    0x9b79,+    0x8b58,+    0xbb3b,+    0xab1a,+    0x6ca6,+    0x7c87,+    0x4ce4,+    0x5cc5,+    0x2c22,+    0x3c03,+    0x0c60,+    0x1c41,+    0xedae,+    0xfd8f,+    0xcdec,+    0xddcd,+    0xad2a,+    0xbd0b,+    0x8d68,+    0x9d49,+    0x7e97,+    0x6eb6,+    0x5ed5,+    0x4ef4,+    0x3e13,+    0x2e32,+    0x1e51,+    0x0e70,+    0xff9f,+    0xefbe,+    0xdfdd,+    0xcffc,+    0xbf1b,+    0xaf3a,+    0x9f59,+    0x8f78,+    0x9188,+    0x81a9,+    0xb1ca,+    0xa1eb,+    0xd10c,+    0xc12d,+    0xf14e,+    0xe16f,+    0x1080,+    0x00a1,+    0x30c2,+    0x20e3,+    0x5004,+    0x4025,+    0x7046,+    0x6067,+    0x83b9,+    0x9398,+    0xa3fb,+    0xb3da,+    0xc33d,+    0xd31c,+    0xe37f,+    0xf35e,+    0x02b1,+    0x1290,+    0x22f3,+    0x32d2,+    0x4235,+    0x5214,+    0x6277,+    0x7256,+    0xb5ea,+    0xa5cb,+    0x95a8,+    0x8589,+    0xf56e,+    0xe54f,+    0xd52c,+    0xc50d,+    0x34e2,+    0x24c3,+    0x14a0,+    0x0481,+    0x7466,+    0x6447,+    0x5424,+    0x4405,+    0xa7db,+    0xb7fa,+    0x8799,+    0x97b8,+    0xe75f,+    0xf77e,+    0xc71d,+    0xd73c,+    0x26d3,+    0x36f2,+    0x0691,+    0x16b0,+    0x6657,+    0x7676,+    0x4615,+    0x5634,+    0xd94c,+    0xc96d,+    0xf90e,+    0xe92f,+    0x99c8,+    0x89e9,+    0xb98a,+    0xa9ab,+    0x5844,+    0x4865,+    0x7806,+    0x6827,+    0x18c0,+    0x08e1,+    0x3882,+    0x28a3,+    0xcb7d,+    0xdb5c,+    0xeb3f,+    0xfb1e,+    0x8bf9,+    0x9bd8,+    0xabbb,+    0xbb9a,+    0x4a75,+    0x5a54,+    0x6a37,+    0x7a16,+    0x0af1,+    0x1ad0,+    0x2ab3,+    0x3a92,+    0xfd2e,+    0xed0f,+    0xdd6c,+    0xcd4d,+    0xbdaa,+    0xad8b,+    0x9de8,+    0x8dc9,+    0x7c26,+    0x6c07,+    0x5c64,+    0x4c45,+    0x3ca2,+    0x2c83,+    0x1ce0,+    0x0cc1,+    0xef1f,+    0xff3e,+    0xcf5d,+    0xdf7c,+    0xaf9b,+    0xbfba,+    0x8fd9,+    0x9ff8,+    0x6e17,+    0x7e36,+    0x4e55,+    0x5e74,+    0x2e93,+    0x3eb2,+    0x0ed1,+    0x1ef0+  ]++-- Calculate CRC-16 of a string+crc16 :: ByteString -> Word16+crc16 str = foldl' crcUpdate 0 str++validate :: ByteString -> Word16 -> Bool+validate input expected =+  crc16 input == expected+  +-- Update CRC-16 value+-- from the go code:+-- crc = ((crc << 8) & 0xffff) ^ crc16tab[((crc>>8)^uint16(b))&0x00FF]+crcUpdate :: Word16 -> Word8 -> Word16+crcUpdate crc byte = +  let tableIndex = fromIntegral (((crc `shiftR` 8) `xor` fromIntegral byte) .&. 0xff)+      crcVal = crcTable !! tableIndex `xor` (crc `shiftL` 8)+  in crcVal+
+ src/Nats/Nkeys/Pairs.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RecordWildCards #-}++module Nats.Nkeys.Pairs where++import Crypto.Sign.Ed25519 (PublicKey (unPublicKey), SecretKey (unSecretKey), Signature, createKeypair, createKeypairFromSeed_, dsign, dverify)+import Data.ByteString (drop, index)+import Data.ByteString hiding (unpack)+import Data.Text (pack, unpack)+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Nats.Nkeys.Codec (KeyPrefix (..), decode, encode, encodeSeed, extractSeedPrefix, fromByte)++-- | Represents an ed25519 key pair with NATS string encoding+data KeyPair = KeyPair {prefixByte :: KeyPrefix, pk :: PublicKey, sk :: SecretKey} deriving (Eq)++instance Show KeyPair where+  show p = unpack . decodeUtf8 $ publicKey p++-- | Returns the public key of the pair as a prefixed byte string+publicKey :: KeyPair -> ByteString+publicKey (KeyPair {prefixByte, pk, ..}) = encode prefixByte $ unPublicKey pk++-- | Returns the seed (private) key of the pair as a prefixed string starting with S+seed :: KeyPair -> ByteString+seed (KeyPair {prefixByte, sk, ..}) = encodeSeed prefixByte $ unSecretKey sk++-- | Creates a new keypair from an encoded seed with an appropriate prefix. Do not+-- call this function with unencoded ed25519 seeds+createFromSeed :: ByteString -> Maybe KeyPair+createFromSeed input =+  let decoded = decode input+      prefix = case extractSeedPrefix <$> decoded of+        Left _ -> Unknown+        Right p -> p+      rawkp = createKeypairFromSeed_ . Data.ByteString.drop 2 <$> decoded+   in case rawkp of+        Left x -> Nothing+        Right Nothing -> Nothing+        Right (Just (p, s)) ->+          Just KeyPair {prefixByte = prefix, pk = p, sk = s}++-- | This IO action creates a new key pair from a randomly generated 32-byte seed+create :: KeyPrefix -> IO KeyPair+create prefix = do+  (pk, sk) <- createKeypair+  return KeyPair {prefixByte = prefix, pk = pk, sk = sk}++-- | Signs the given input bytes using the key pair's seed key+sign :: KeyPair -> ByteString -> Maybe Signature+sign (KeyPair {prefixByte = Curve, ..}) = const Nothing+sign (KeyPair {sk, ..}) = Just . Crypto.Sign.Ed25519.dsign sk++-- | Verifies a signature against the key pair's public key and the input bytes+verify :: KeyPair -> ByteString -> Signature -> Bool+verify (KeyPair {prefixByte = Curve, ..}) = \ _ _ -> False+verify (KeyPair {pk, ..}) = Crypto.Sign.Ed25519.dverify pk
+ test/NkeysTest.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}++module Main where++import Test.HUnit+import qualified System.Exit as Exit+import qualified Data.ByteString.Char8 as C+import Data.Text.Encoding+import Crypto.Sign.Ed25519 hiding (sign, verify)+import Data.ByteString.Base32 (decodeBase32, decodeBase32Unpadded, encodeBase32Unpadded)++import Data.ByteString as BS+import Nats.Nkeys++codecRoundTrip :: Test+codecRoundTrip =+    TestCase (do bob <- create User+                 let bobSeed = seed bob+                 case createFromSeed bobSeed of+                    Just roundTripUser -> +                        assertEqual "decoded encode should equal original key" bobSeed (seed roundTripUser)+                    Nothing ->+                        assertBool "failed round trip of generated pair" False)++verifyAndSignTest :: Test+verifyAndSignTest =+    TestCase (do bob <- create User                 +                 let message = "hello there" :: ByteString+                 let Just sig = sign bob message+                 let verified = verify bob message sig+                 assertEqual "signature should be verifiable" verified True)++officialGoRoundTrip :: Test+officialGoRoundTrip =+    TestCase (do let goPublic = "UAYTKBRMQCOPT7AGPZA3U5QYUNZIZYLL6TX4WY36GBEGZSVP4TIXUY3B"+                 let goSeed = "SUALB7CYOPEXH27JJHTWAR5JOLFRCVT2J2AJYBZ5GBP6I52HUW5JKLLJPU"+                 let Just roundTripUser = createFromSeed goSeed+                 assertEqual "decoded encode public key should match" goPublic (publicKey roundTripUser)+                 assertEqual "decoded encode should equal original key" goSeed (seed roundTripUser))++rejectBadCrc :: Test+rejectBadCrc = +    -- note the last character of the go seed has been tampered with+    TestCase (do let goSeed = "SUALB7CYOPEXH27JJHTWAR5JOLFRCVT2J2AJYBZ5GBP6I52HUW5JKLLJPA"+                 let m = createFromSeed goSeed+                 assertEqual "should not be able to create seed with bad CRC" m Nothing)++-- commented out because I don't want the crc function exposed in the public API++-- crc :: Test +-- crc = +--     TestCase (do let goSeed = "SUALB7CYOPEXH27JJHTWAR5JOLFRCVT2J2AJYBZ5GBP6I52HUW5JKLLJPU" :: ByteString+--                      input = dropEnd 2 <$> decodeBase32Unpadded goSeed+--                      crc = case input of+--                         Right i -> crc16 i+--                         _ -> 42+                 +--                  assertEqual "CRC" crc 32105) -- 32105 obtained by executing crc16 function in the Go lib++tests :: Test+tests = TestList [TestLabel "Codec Round Trip" codecRoundTrip,+                  TestLabel "Round Trip with Go-Generated Seed" officialGoRoundTrip,+--                  TestLabel "Verify CRC matches Go CRC" crc,+                  TestLabel "Cannot create a pair with a bad CRC" rejectBadCrc,+                  TestLabel "Verification Round Trip" verifyAndSignTest]++main :: IO ()+main = do+    result <- runTestTT tests    +    if failures result > 0 then Exit.exitFailure else Exit.exitSuccess+