packages feed

nkeys-0.0.1.0: src/Nats/Nkeys/Codec.hs

{-# 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'