package shared-secret
Install
Dune Dependency
Authors
Maintainers
Sources
md5=cbdacb2c2ae8d888af027e8464f1b234
sha512=cdc11da593f35c82312754a793067728adbd6fac068717dea7baa530c0fb6c466dd5c0e092d4f70e1ff0bf04d7e0d66f44c8ef22606e4060483ee5a453acfc2a
Description
Abstract (encapsulated) messages or hidden (semi-deterministic) exceptions using OCaml's module system.
Published: 04 Nov 2019
README
shared-secret
Abstract (encapsulated) messages or hidden (semi-deterministic) exceptions using OCaml's module system.
A package inspired by this post: https://existentialtype.wordpress.com/2012/12/03/exceptions-are-shared-secrets/
Installation
If available on OPAM:
$ opam install shared-secret
Otherwise, through this project directory (changes should be committed):
$ opam install .
The library is linked by Dune/Findlib as shared-secret
as well.
API for version 0.1
This package actually provides only one module file called Shared_secret
. This module contains 2 functors, which are needed for different purposes. They are Message
and Exception
, parameterized by:
sig type t end
but the Message functor is generative, so it also takes ( )
.
The first is to encode and decode existentially typed values. The use becomes obvious when we declare an exception of an existential type. For instance:
module StringMessage = Message (String) ( );;
exception AbstractString of StringMessage.t;;
In the above example, StringMessage will contain 2 submodules with these signatures:
module Encoder : sig
val encode : String.t -> StringMessage.t
end;;
module Decoder : sig
val decode : StringMessage.t -> String.t
end;;
So the AbstractString
exception can be caught, but not decoded without Decoder.decode
:
let open StringMessage in
try raise (AbstractString (Encoder.encode "Hello, OCaml!")) with
| AbstractString msg -> Decoder.decode msg;; (* = "Hello, OCaml!" *)
This encoding/decoding process imposes no additional runtime costs, internally it is just the identity function.
On other hand, the Exception functor is used when we don't like to expose the exception itself, thus is impossible to catch it by explicit binding (pattern matching), it can only be intercepted, but not revealed without the proper handler.
module StringException = Exception (String);;
let unsafe ( ) = StringException.Raiser.raise "Hello, World!";;
try unsafe ( ) with
| _ -> ( );; (* // discards the exception, don't use it on production code *)
let open StringException in
Handler.handle unsafe (fun str -> str);; (* = "Hello, World!" *)
By the above example, we can deduce the following signature of the submodules:
module Raiser : sig
val raise : String.t -> 'a
end;;
module Handler : sig
val handle : (unit -> 'a) -> (String.t -> 'a) -> 'a
end;;
API for version 0.2
There is no break of compatibility with the previous API. This current API provides 2 additional modules in the namespace of Shared_secret
, called Token
and Box
. They are inspired on the Sealing Pairs mechanisms of the Object Capability Model. The historical origins of these pairs can be found at Morris73, though.
The Token.t
value is an unforgeable and revocable reference. This unique identifier is used by the Box
module to seal any 'value
as 'value Box.t
. Despite the content type being known (different of the module types for version 0.1, this type is parametric rather than abstract), the sealed value can only be known by the proper Token.t
which sealed it beforehand.
The following module interfaces can help you how to understand their use:
module type IToken = sig
type t
type revoker
exception AlreadyRevoked
exception RevokedToken
val create : unit -> t * revoker
val revoke : revoker -> unit
val revoked : t -> bool
val (=) : t -> t -> bool
end;;
module Token : IToken;;
module type IBox = sig
type 'value t
exception InvalidToken
module Sealer : sig val seal : Token.t -> 'value -> 'value t end;;
module Unsealer : sig val unseal : Token.t -> 'value t -> 'value end;;
end;;
module Box : IBox;;
The list below also shows where exceptions may be thrown:
Token.AlreadyRevoked
, prone to occur onToken.revoke
if theToken.t
was already revoked previouslyToken.RevokedToken
, occurs on eitherBox.Sealer.seal
orBox.Unsealer.unseal
ifToken.revoke
was called at least onceBox.InvalidToken
, always raised onBox.Unsealer.unseal
if the'value Box.t
was sealed with a differentToken.t
API for version 0.3
In this version 2 new things were added. One thing is a module called Pair
while the other is a generative functor called Revocable
. This current API provides two functions in the Pair
module, which are the Exception
and Box
operations being projected, somehow. Value restriction appears on both functions, though. The first function makes a pair of exception "handlers", the first element is the "raiser" while the second is the respective "catcher". The second function, on the other hand, generates a associated pair of projection and injection for a generic Box
type. So, the needed Token
value is hidden and shared among these operations. The whole purpose of this module is to provide shortcuts/aliases for other modules. Fair enough of cheap talking, the entire signature of Pair
is:
module Pair : sig
val exceptional : unit -> ('value -> 'failure) * ((unit -> 'result) -> ('value -> 'result) -> 'result)
val sealing : unit -> ('a -> 'a Box.t) * ('a Box.t -> 'a)
end;;
The semantics of raised exceptions from the module level counterparts are still preserved and untouched.
'Cause the Token
value is hidden on sealing
result operations, we need some way to revoke these operations without even touch the token itself. Here comes the generative Revocable
functor. Being a generative functor, it will take an ( )
as argument, and so, yield a fresh module. This fresh module revokes generic lambda proxies, while keeping the original lambda expressions intact. That said, this module must be equipped with a revoke
operation, once this operation is called, the fresh module itself becomes useless together with its respective generated lambda proxies. On any attempt to use these lambda proxies once the revocation was triggered, an exception RevokedReference
(from the fresh module) is raised. When we call revoke
again, another exception called AlreadyRevoked
(also from the fresh module) is also raised. The signature below describes with some detail this generative functor:
module Revocable : functor ( ) -> sig
exception RevokedReference
exception AlreadyRevoked
val revocable : ('a -> 'b) -> ('a -> 'b)
val revoke : unit -> unit
end;;