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 +5/−0
- LICENSE +202/−0
- app/Main.hs +7/−0
- nkeys.cabal +65/−0
- src/Nats/Nkeys.hs +46/−0
- src/Nats/Nkeys/Codec.hs +128/−0
- src/Nats/Nkeys/Crc.hs +284/−0
- src/Nats/Nkeys/Pairs.hs +56/−0
- test/NkeysTest.hs +72/−0
+ 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+