Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping Secrets in Haskell

Suppose I have a secret which I keep in a type

data Secret a = Secret a deriving Functor, Show
sec :: Secret String

and I want to allow computation on the secret, and some way to view results e.g.

getSecretHash :: Show a => Secret a -> String

or

askQuestion :: (a->Bool) -> Secret a -> Bool

but I don't want to allow the secret to be directly extracted (I know you could bruteforce one of the above ways, but assume the secret is large so this is unfeasible).

Of course someone could just write

reveal :: Secret a -> a
reveal (Secret x) = x

and I know I can prevent this by putting secret in a module and not exporting the constructor but instead giving a makeSecret :: a->Secret a, but I want to know if there's a way to do it using the type system.

Without hiding the constructor, how can I make a type that can't have its value arbitrarily extracted?

like image 361
Sean D Avatar asked Oct 12 '17 17:10

Sean D


3 Answers

This

askQuestion :: (a -> Bool) -> Secret a -> Bool

looks a bit like a flipped version of runCont

λ import Control.Monad.Trans.Cont
λ :t runCont
runCont :: forall r a. Cont r a -> (a -> r) -> r
λ :set -XTypeApplications
λ :t runCont @Bool
runCont @Bool :: forall a. Cont Bool a -> (a -> Bool) -> Bool
λ :t flip (runCont @Bool)
flip (runCont @Bool) :: forall a. (a -> Bool) -> Cont Bool a -> Bool

So perhaps in that respect your Secret type is Cont Bool, and you can create values with cont:

 cont :: forall a. ((a -> Bool) -> Bool) -> Cont Bool a

 makeSecret :: forall a. a -> Cont Bool a
 makeSecret a = cont $ \f -> f a

The actual value is hidden behind a function.

like image 110
danidiaz Avatar answered Oct 17 '22 16:10

danidiaz


Without hiding the constructor, how can I make a type that can't have its value arbitrarily extracted?

No. Hiding the constructor is precisely the right tool for that, and the only reasonable method I can think of.

like image 31
Joachim Breitner Avatar answered Oct 17 '22 18:10

Joachim Breitner


Well, if using fancy Haskell types isn't a requirement, then you can use the old function closure trick. Just define the data type as the query function:

data Secret a = Secret { query :: (a -> Bool) -> Bool }

Exporting a helper function to construct secrets may be helpful (though it's entirely optional, as the constructor is public and anyone can make their own makeSecret function):

makeSecret :: a -> Secret a
makeSecret x = Secret (\f -> f x)   -- or Secret ($x) if you're feeling clever

The definition of askQuestion is straightforward:

askQuestion :: (a -> Bool) -> Secret a -> Bool
askQuestion = flip query

I guess this is ultimately similar to danidiaz's answer, but the monad machinery isn't really necessary just to store a secret in a function.

Note that, if you need a functor instance for this Secret, Haskell has no problem deriving one, and it works as expected:

> askQuestion (=="Stack Overflow") $ fmap (++" Overflow") $ makeSecret "Stack"
True

I guess technically you can get it back out by cheating, like so, but I'm not sure that any of the other methods can avoid this:

> askQuestion (\x -> unsafePerformIO (putStrLn (show x) 
     >> return False)) $ makeSecret "secret"
"secret"
False
>
like image 5
K. A. Buhr Avatar answered Oct 17 '22 18:10

K. A. Buhr