module Test.Cardano.Crypto.Wallet.V2FormatSpec (tests) where

import qualified Codec.CBOR.Decoding as CBOR
import qualified Codec.CBOR.Read as CBOR
import qualified Codec.CBOR.Write as CBOR (toLazyByteString)
import Control.Monad.Trans.Fail.String (errorFail)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Test.HUnit.Base (assertFailure)
import Test.Hspec

import Cardano.Crypto.WalletHD.Encrypted

testSeed :: BS.ByteString
testSeed :: ByteString
testSeed = Int -> Word8 -> ByteString
BS.replicate Int
32 Word8
0x02

testCC :: BS.ByteString
testCC :: ByteString
testCC = Int -> Word8 -> ByteString
BS.replicate Int
32 Word8
0xAB

testPass :: BS.ByteString
testPass :: ByteString
testPass = Int -> Word8 -> ByteString
BS.replicate Int
32 Word8
0x42

wrongPass :: BS.ByteString
wrongPass :: ByteString
wrongPass = Int -> Word8 -> ByteString
BS.replicate Int
32 Word8
0x00

-- ---------------------------------------------------------------------------
-- Public-key golden vector
--
-- This is the ed25519 public key derived from testSeed via cardano_crypto_ed25519_extend
-- + cardano_crypto_ed25519_publickey.  It is fully deterministic (no randomness).
-- If this changes, the key derivation C code has silently changed.
-- ---------------------------------------------------------------------------

expectedPublicKey :: PublicKey
expectedPublicKey :: PublicKey
expectedPublicKey =
  Fail PublicKey -> PublicKey
forall a. HasCallStack => Fail a -> a
errorFail (Fail PublicKey -> PublicKey) -> Fail PublicKey -> PublicKey
forall a b. (a -> b) -> a -> b
$
    ByteString -> Fail PublicKey
forall (f :: * -> *). MonadFail f => ByteString -> f PublicKey
mkPublicKey (ByteString -> Fail PublicKey) -> ByteString -> Fail PublicKey
forall a b. (a -> b) -> a -> b
$
      [Word8] -> ByteString
BS.pack
        [ Word8
129
        , Word8
57
        , Word8
119
        , Word8
14
        , Word8
168
        , Word8
125
        , Word8
23
        , Word8
95
        , Word8
86
        , Word8
163
        , Word8
84
        , Word8
102
        , Word8
195
        , Word8
76
        , Word8
126
        , Word8
204
        , Word8
203
        , Word8
141
        , Word8
138
        , Word8
145
        , Word8
180
        , Word8
238
        , Word8
55
        , Word8
162
        , Word8
93
        , Word8
246
        , Word8
15
        , Word8
91
        , Word8
143
        , Word8
201
        , Word8
179
        , Word8
148
        ]

tests :: Spec
tests :: Spec
tests = String -> Spec -> Spec
forall a. HasCallStack => String -> SpecWith a -> SpecWith a
describe String
"V2Format" (Spec -> Spec) -> Spec -> Spec
forall a b. (a -> b) -> a -> b
$ do
  let
    createTestKey :: HasCallStack => IO EncryptedKey
    createTestKey :: HasCallStack => IO EncryptedKey
createTestKey = do
      Either XPrvError EncryptedKey
res <- ByteString
-> ByteString -> ByteString -> IO (Either XPrvError EncryptedKey)
forall passphrase secret cc.
(ByteArrayAccess passphrase, ByteArrayAccess secret,
 ByteArrayAccess cc) =>
secret -> passphrase -> cc -> IO (Either XPrvError EncryptedKey)
encryptedCreate ByteString
testSeed ByteString
testPass ByteString
testCC
      case Either XPrvError EncryptedKey
res of
        Left XPrvError
err -> String -> IO EncryptedKey
forall a. HasCallStack => String -> IO a
assertFailure (String -> IO EncryptedKey) -> String -> IO EncryptedKey
forall a b. (a -> b) -> a -> b
$ String
"encryptedCreate failed: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ XPrvError -> String
forall a. Show a => a -> String
show XPrvError
err
        Right EncryptedKey
key -> EncryptedKey -> IO EncryptedKey
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptedKey
key

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"encryptedCreate produces EnvelopeV2 format" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    EncryptedKey
key <- IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    EncryptedKey -> XPrvFormat
encryptedKeyFormat EncryptedKey
key XPrvFormat -> XPrvFormat -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` XPrvFormat
EnvelopeV2

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"v2 key validates with correct passphrase" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    EncryptedKey
key <- IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    EncryptedKey -> ByteString -> IO (Either XPrvError ())
forall passphrase.
ByteArrayAccess passphrase =>
EncryptedKey -> passphrase -> IO (Either XPrvError ())
encryptedValidatePassphrase EncryptedKey
key ByteString
testPass IO (Either XPrvError ()) -> Either XPrvError () -> IO ()
forall a. (HasCallStack, Show a, Eq a) => IO a -> a -> IO ()
`shouldReturn` () -> Either XPrvError ()
forall a b. b -> Either a b
Right ()

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"v2 key rejects wrong passphrase with XPrvAuthenticationFailed" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    EncryptedKey
key <- IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    EncryptedKey -> ByteString -> IO (Either XPrvError ())
forall passphrase.
ByteArrayAccess passphrase =>
EncryptedKey -> passphrase -> IO (Either XPrvError ())
encryptedValidatePassphrase EncryptedKey
key ByteString
wrongPass IO (Either XPrvError ()) -> Either XPrvError () -> IO ()
forall a. (HasCallStack, Show a, Eq a) => IO a -> a -> IO ()
`shouldReturn` XPrvError -> Either XPrvError ()
forall a b. a -> Either a b
Left XPrvError
XPrvAuthenticationFailed

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"v2 envelope is a CBOR 5-element array" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    ByteString
bs <- EncryptedKey -> ByteString
unEncryptedKey (EncryptedKey -> ByteString) -> IO EncryptedKey -> IO ByteString
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    case (forall s. Decoder s Int)
-> ByteString -> Either DeserialiseFailure (ByteString, Int)
forall a.
(forall s. Decoder s a)
-> ByteString -> Either DeserialiseFailure (ByteString, a)
CBOR.deserialiseFromBytes Decoder s Int
forall s. Decoder s Int
CBOR.decodeListLen (ByteString -> ByteString
BSL.fromStrict ByteString
bs) of
      Left DeserialiseFailure
e -> HasCallStack => String -> IO ()
String -> IO ()
expectationFailure (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"CBOR decode failed: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ DeserialiseFailure -> String
forall a. Show a => a -> String
show DeserialiseFailure
e
      Right (ByteString
_, Int
5) -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
      Right (ByteString
_, Int
n) ->
        HasCallStack => String -> IO ()
String -> IO ()
expectationFailure (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$
          String
"Expected 5-element CBOR array, got: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
n

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"v2 envelope roundtrips CBOR serialization" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    Either XPrvError Envelope
res <- EncryptedKey -> Either XPrvError Envelope
decodeEncryptedKey (EncryptedKey -> Either XPrvError Envelope)
-> IO EncryptedKey -> IO (Either XPrvError Envelope)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    case Either XPrvError Envelope
res of
      Left XPrvError
e -> HasCallStack => String -> IO ()
String -> IO ()
expectationFailure (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"decodeEncryptedKey: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ XPrvError -> String
forall a. Show a => a -> String
show XPrvError
e
      Right Envelope
envelope -> do
        let bs :: ByteString
bs = Encoding -> ByteString
CBOR.toLazyByteString (Encoding -> ByteString) -> Encoding -> ByteString
forall a b. (a -> b) -> a -> b
$ Envelope -> Encoding
encodeEnvelope Envelope
envelope
        case (forall s. Decoder s Envelope)
-> ByteString -> Either DeserialiseFailure (ByteString, Envelope)
forall a.
(forall s. Decoder s a)
-> ByteString -> Either DeserialiseFailure (ByteString, a)
CBOR.deserialiseFromBytes Decoder s Envelope
forall s. Decoder s Envelope
decodeEnvelope ByteString
bs of
          Left DeserialiseFailure
e -> HasCallStack => String -> IO ()
String -> IO ()
expectationFailure (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"CBOR decode failed: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ DeserialiseFailure -> String
forall a. Show a => a -> String
show DeserialiseFailure
e
          Right (ByteString
rest, Envelope
envelope') -> do
            ByteString -> Int64
BSL.length ByteString
rest Int64 -> Int64 -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` Int64
0
            Envelope
envelope' Envelope -> Envelope -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` Envelope
envelope

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"public key and chain code in envelope match accessors" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    EncryptedKey
key <- IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    let pub :: PublicKey
pub = HasCallStack => EncryptedKey -> PublicKey
EncryptedKey -> PublicKey
encryptedPublic EncryptedKey
key
        cc :: ChainCode
cc = HasCallStack => EncryptedKey -> ChainCode
EncryptedKey -> ChainCode
encryptedChainCode EncryptedKey
key
    case ByteString -> Either XPrvError EncryptedKey
mkEncryptedKey (EncryptedKey -> ByteString
unEncryptedKey EncryptedKey
key) of
      Left XPrvError
err -> HasCallStack => String -> IO ()
String -> IO ()
expectationFailure (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"re-parse failed: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ XPrvError -> String
forall a. Show a => a -> String
show XPrvError
err
      Right EncryptedKey
key' -> do
        HasCallStack => EncryptedKey -> PublicKey
EncryptedKey -> PublicKey
encryptedPublic EncryptedKey
key' PublicKey -> PublicKey -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` PublicKey
pub
        HasCallStack => EncryptedKey -> ChainCode
EncryptedKey -> ChainCode
encryptedChainCode EncryptedKey
key' ChainCode -> ChainCode -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` ChainCode
cc

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"presenting a v1 raw blob returns Left XPrvDecodeError" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    let v1blob :: ByteString
v1blob = Int -> Word8 -> ByteString
BS.replicate Int
128 Word8
0x00
    case ByteString -> Either XPrvError EncryptedKey
mkEncryptedKey ByteString
v1blob of
      Left XPrvError
err -> HasCallStack => String -> IO ()
String -> IO ()
expectationFailure (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"mkEncryptedKey rejected v1 blob: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ XPrvError -> String
forall a. Show a => a -> String
show XPrvError
err
      Right EncryptedKey
key -> do
        Either XPrvError ()
r <- EncryptedKey -> ByteString -> IO (Either XPrvError ())
forall passphrase.
ByteArrayAccess passphrase =>
EncryptedKey -> passphrase -> IO (Either XPrvError ())
encryptedValidatePassphrase EncryptedKey
key ByteString
testPass
        Either XPrvError ()
r Either XPrvError () -> Either XPrvError () -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` XPrvError -> Either XPrvError ()
forall a b. a -> Either a b
Left XPrvError
XPrvDecodeError

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"truncated CBOR bytes return Left XPrvDecodeError" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    EncryptedKey
key <- IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    ByteString -> Either XPrvError EncryptedKey
mkEncryptedKey (Int -> ByteString -> ByteString
BS.take Int
10 (EncryptedKey -> ByteString
unEncryptedKey EncryptedKey
key))
      Either XPrvError EncryptedKey
-> Either XPrvError EncryptedKey -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` XPrvError -> Either XPrvError EncryptedKey
forall a b. a -> Either a b
Left XPrvError
XPrvDecodeError

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"encryptedChangePassphrase re-randomizes envelope (different bytes, same public key)" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    EncryptedKey
key <- IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    Either XPrvError EncryptedKey
res <- ByteString
-> ByteString -> EncryptedKey -> IO (Either XPrvError EncryptedKey)
forall oldPassPhrase newPassPhrase.
(ByteArrayAccess oldPassPhrase, ByteArrayAccess newPassPhrase) =>
oldPassPhrase
-> newPassPhrase
-> EncryptedKey
-> IO (Either XPrvError EncryptedKey)
encryptedChangePassphrase ByteString
testPass ByteString
testPass EncryptedKey
key
    case Either XPrvError EncryptedKey
res of
      Left XPrvError
err -> HasCallStack => String -> IO ()
String -> IO ()
expectationFailure (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"changePass failed: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ XPrvError -> String
forall a. Show a => a -> String
show XPrvError
err
      Right EncryptedKey
key' -> do
        HasCallStack => EncryptedKey -> PublicKey
EncryptedKey -> PublicKey
encryptedPublic EncryptedKey
key PublicKey -> PublicKey -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` HasCallStack => EncryptedKey -> PublicKey
EncryptedKey -> PublicKey
encryptedPublic EncryptedKey
key'
        EncryptedKey -> ByteString
unEncryptedKey EncryptedKey
key ByteString -> ByteString -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldNotBe` EncryptedKey -> ByteString
unEncryptedKey EncryptedKey
key'

  String -> IO () -> SpecWith (Arg (IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"golden: public key matches deterministic ed25519 derivation from testSeed" (IO () -> SpecWith (Arg (IO ())))
-> IO () -> SpecWith (Arg (IO ()))
forall a b. (a -> b) -> a -> b
$ do
    EncryptedKey
key <- IO EncryptedKey
HasCallStack => IO EncryptedKey
createTestKey
    HasCallStack => EncryptedKey -> PublicKey
EncryptedKey -> PublicKey
encryptedPublic EncryptedKey
key PublicKey -> PublicKey -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` PublicKey
expectedPublicKey