module Tezos.Crypto
(
PublicKey (..)
, SecretKey
, Signature (..)
, KeyHashTag (..)
, KeyHash (..)
, detSecretKey
, toPublic
, signatureToBytes
, mkSignature
, signatureLengthBytes
, checkSignature
, CryptoParseError (..)
, formatPublicKey
, mformatPublicKey
, parsePublicKey
, formatSignature
, mformatSignature
, parseSignature
, formatKeyHash
, mformatKeyHash
, parseKeyHash
, keyHashLengthBytes
, hashKey
, blake2b
, blake2b160
, sha256
, sha512
, encodeBase58Check
, decodeBase58Check
, B58CheckWithPrefixError (..)
, decodeBase58CheckWithPrefix
) where
import Data.Aeson (FromJSON(..), ToJSON(..))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Encoding as Aeson
import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS
import Fmt (Buildable, build, pretty)
import Test.QuickCheck (Arbitrary(..), elements, oneof)
import Michelson.Text
import qualified Tezos.Crypto.Ed25519 as Ed25519
import Tezos.Crypto.Hash
import qualified Tezos.Crypto.P256 as P256
import qualified Tezos.Crypto.Secp256k1 as Secp256k1
import Tezos.Crypto.Util
data PublicKey
= PublicKeyEd25519 Ed25519.PublicKey
| PublicKeySecp256k1 Secp256k1.PublicKey
| PublicKeyP256 P256.PublicKey
deriving stock (Show, Eq)
instance Arbitrary PublicKey where
arbitrary = toPublic <$> arbitrary
data SecretKey
= SecretKeyEd25519 Ed25519.SecretKey
| SecretKeySecp256k1 Secp256k1.SecretKey
| SecretKeyP256 P256.SecretKey
deriving stock (Show, Eq)
detSecretKey :: HasCallStack => ByteString -> SecretKey
detSecretKey seed = seed & case (length seed + 2) `mod` 3 of
0 -> SecretKeyEd25519 . Ed25519.detSecretKey
1 -> SecretKeySecp256k1 . Secp256k1.detSecretKey
2 -> SecretKeyP256 . P256.detSecretKey
_ -> error "detSecretKey: unexpected happened"
instance Arbitrary SecretKey where
arbitrary = oneof
[ SecretKeyEd25519 <$> arbitrary
, SecretKeySecp256k1 <$> arbitrary
, SecretKeyP256 <$> arbitrary
]
toPublic :: SecretKey -> PublicKey
toPublic = \case
SecretKeyEd25519 sk -> PublicKeyEd25519 . Ed25519.toPublic $ sk
SecretKeySecp256k1 sk -> PublicKeySecp256k1 . Secp256k1.toPublic $ sk
SecretKeyP256 sk -> PublicKeyP256 . P256.toPublic $ sk
data Signature
= SignatureEd25519 Ed25519.Signature
| SignatureSecp256k1 Secp256k1.Signature
| SignatureP256 P256.Signature
| SignatureGeneric ByteString
deriving stock (Show)
instance Eq Signature where
sig1 == sig2 = case (sig1, sig2) of
(SignatureGeneric bytes1, SignatureGeneric bytes2) -> bytes1 == bytes2
(SignatureGeneric bytes1, SignatureEd25519 (Ed25519.signatureToBytes -> bytes2)) ->
bytes1 == bytes2
(SignatureGeneric bytes1, SignatureSecp256k1 (Secp256k1.signatureToBytes -> bytes2)) ->
bytes1 == bytes2
(SignatureGeneric bytes1, SignatureP256 (P256.signatureToBytes -> bytes2)) ->
bytes1 == bytes2
(_, SignatureGeneric {}) -> sig2 == sig1
(SignatureEd25519 s1, SignatureEd25519 s2) -> s1 == s2
(SignatureEd25519 {}, _) -> False
(SignatureSecp256k1 s1, SignatureSecp256k1 s2) -> s1 == s2
(SignatureSecp256k1 {}, _) -> False
(SignatureP256 s1, SignatureP256 s2) -> s1 == s2
(SignatureP256 {}, _) -> False
instance Arbitrary Signature where
arbitrary = oneof
[ SignatureEd25519 <$> arbitrary
, SignatureSecp256k1 <$> arbitrary
, SignatureP256 <$> arbitrary
, SignatureGeneric . BS.replicate signatureLengthBytes <$> arbitrary
]
signatureToBytes :: BA.ByteArray ba => Signature -> ba
signatureToBytes = \case
SignatureEd25519 sig -> Ed25519.signatureToBytes sig
SignatureSecp256k1 sig -> Secp256k1.signatureToBytes sig
SignatureP256 sig -> P256.signatureToBytes sig
SignatureGeneric bytes -> BA.convert bytes
mkSignature :: BA.ByteArray ba => ba -> Maybe Signature
mkSignature ba =
SignatureGeneric (BA.convert ba) <$ guard (l == signatureLengthBytes)
where
l = BA.length ba
signatureLengthBytes :: HasCallStack => Integral n => n
signatureLengthBytes
| all is64
[ Ed25519.signatureLengthBytes
, P256.signatureLengthBytes
, Secp256k1.signatureLengthBytes
] = 64
| otherwise =
error "Apparently our understanding of signatures in Tezos is broken"
where
is64 :: Int -> Bool
is64 = (== 64)
genericSignatureTag :: ByteString
genericSignatureTag = "\004\130\043"
checkSignature :: PublicKey -> Signature -> ByteString -> Bool
checkSignature pk0 sig0 bytes =
case (pk0, sig0) of
(PublicKeyEd25519 pk, SignatureEd25519 sig) ->
Ed25519.checkSignature pk sig bytes
(PublicKeySecp256k1 pk, SignatureSecp256k1 sig) ->
Secp256k1.checkSignature pk sig bytes
(PublicKeyP256 pk, SignatureP256 sig) ->
P256.checkSignature pk sig bytes
_ -> False
formatPublicKey :: PublicKey -> Text
formatPublicKey = \case
PublicKeyEd25519 pk -> Ed25519.formatPublicKey pk
PublicKeySecp256k1 pk -> Secp256k1.formatPublicKey pk
PublicKeyP256 pk -> P256.formatPublicKey pk
mformatPublicKey :: PublicKey -> MText
mformatPublicKey = mkMTextUnsafe . formatPublicKey
instance Buildable PublicKey where
build = build . formatPublicKey
parsePublicKey :: Text -> Either CryptoParseError PublicKey
parsePublicKey txt =
firstRight $ map ($ txt)
( fmap PublicKeyEd25519 . Ed25519.parsePublicKey :|
[ fmap PublicKeySecp256k1 . Secp256k1.parsePublicKey
, fmap PublicKeyP256 . P256.parsePublicKey
])
formatSignature :: Signature -> Text
formatSignature = \case
SignatureEd25519 sig -> Ed25519.formatSignature sig
SignatureSecp256k1 sig -> Secp256k1.formatSignature sig
SignatureP256 sig -> P256.formatSignature sig
SignatureGeneric sig -> formatImpl genericSignatureTag sig
mformatSignature :: Signature -> MText
mformatSignature = mkMTextUnsafe . formatSignature
instance Buildable Signature where
build = build . formatSignature
parseSignature :: Text -> Either CryptoParseError Signature
parseSignature txt =
firstRight $ map ($ txt)
( fmap SignatureEd25519 . Ed25519.parseSignature :|
[ fmap SignatureSecp256k1 . Secp256k1.parseSignature
, fmap SignatureP256 . P256.parseSignature
, parseImpl genericSignatureTag (pure . SignatureGeneric)
])
instance ToJSON PublicKey where
toJSON = Aeson.String . formatPublicKey
toEncoding = Aeson.text . formatPublicKey
instance FromJSON PublicKey where
parseJSON =
Aeson.withText "PublicKey" $
either (fail . pretty) pure . parsePublicKey
instance ToJSON Signature where
toJSON = Aeson.String . formatSignature
toEncoding = Aeson.text . formatSignature
instance FromJSON Signature where
parseJSON =
Aeson.withText "Signature" $
either (fail . pretty) pure . parseSignature
instance ToJSON KeyHash where
toJSON = Aeson.String . formatKeyHash
toEncoding = Aeson.text . formatKeyHash
instance FromJSON KeyHash where
parseJSON =
Aeson.withText "KeyHash" $
either (fail . pretty) pure . parseKeyHash
data KeyHashTag
= KeyHashEd25519
| KeyHashSecp256k1
| KeyHashP256
deriving stock (Show, Eq, Ord, Bounded, Enum)
instance Arbitrary KeyHashTag where
arbitrary = elements [minBound .. ]
data KeyHash = KeyHash
{ khTag :: KeyHashTag
, khBytes :: ByteString
} deriving stock (Show, Eq, Ord)
keyHashLengthBytes :: Integral n => n
keyHashLengthBytes = 20
instance Arbitrary KeyHash where
arbitrary = hashKey <$> arbitrary
hashKey :: PublicKey -> KeyHash
hashKey =
\case
PublicKeyEd25519 pk ->
KeyHash KeyHashEd25519 (blake2b160 $ Ed25519.publicKeyToBytes pk)
PublicKeySecp256k1 pk ->
KeyHash KeyHashSecp256k1 (blake2b160 $ Secp256k1.publicKeyToBytes pk)
PublicKeyP256 pk ->
KeyHash KeyHashP256 (blake2b160 $ P256.publicKeyToBytes pk)
formatKeyHash :: KeyHash -> Text
formatKeyHash (KeyHash tag bytes) = formatImpl (keyHashTagBytes tag) bytes
mformatKeyHash :: KeyHash -> MText
mformatKeyHash = mkMTextUnsafe . formatKeyHash
instance Buildable KeyHash where
build = build . formatKeyHash
parseKeyHash :: Text -> Either CryptoParseError KeyHash
parseKeyHash txt =
let
parse :: KeyHashTag -> Either CryptoParseError KeyHash
parse tag = KeyHash tag <$> parseImpl (keyHashTagBytes tag) pure txt
in firstRight $ map parse $ minBound :| [succ minBound ..]
keyHashTagBytes :: KeyHashTag -> ByteString
keyHashTagBytes =
\case
KeyHashEd25519 -> "\006\161\159"
KeyHashSecp256k1 -> "\006\161\161"
KeyHashP256 -> "\006\161\164"