Integration Guide¶
General Principles¶
ps2ocaml
is the tool you use to convert ProScript into OCaml. It was designed with the following tenets:
When there is tension between performance, conciseness, idiomatic expression and statement-by-statement equivalence, we always choose statement-by-statement equivalence because equivalence makes it relatively easy for any security audit team to vet the translated code
When there is tension between knowingly producing correct code all the time and knowingly producing correct code 99.9% of the time, we always choose 100% of the time but we leave an escape hatch that comes with a comprehensive AUDIT NOTICE easily searchable (
grep -R "AUDIT NOTICE" .
) in the source code.
The tenets reflect the primary focus of both ProScript and dirsp
on secure code.
Statement by statement equivalence means each ProScript statement is translated to one OCaml expression. Here are some examples:
ProScript Statement |
OCaml Expression |
Notes |
---|---|---|
var a = 1;
/* ... */
|
let a = 1 in
(* ... *)
|
|
const Type_key = {
construct: function(a) {
return []
}
}
|
module Type_key = struct
let construct a = []
end
|
|
a.field =
Type_key.somefunc(a.field);
|
a.field <-
Type_key.somefunc a.field;
|
|
a.field === true;
|
if (a.field = true) then
()
else
raise (
Invalid_argument
"not (a.field = true)"
)
|
a descriptive assert |
a.field === true;
|
let (_ : bool) =
(* no-op
statement-by-statement
equivalence *)
a.field in () ;
|
if you disable assertions |
ps2ocaml Usage¶
Assuming you ran opam install dirsp-ps2ocaml
and eval $(opam env)
, you can run dirsp-ps2ocaml
from your shell.
Otherwise:
Build the translator with
dune build src-proscript/dirsp_ps2ocaml.exe
. That will produce a standalone executable at<dirsp-exchange>/_build/default/src-proscript/dirsp-ps2ocaml.exe
which you can run directly or use the shorthanddune exec src-proscript/dirsp_ps2ocaml.exe
.In everything that follows, use
dune exec src-proscript/dirsp_ps2ocaml.exe --
wherever you seedirsp-ps2ocaml
.
The command line options are:
dirsp-ps2ocaml <proscript_file>
[-i <ocaml_interface_module>]
[-s <ocaml_shims_module>]
(-p [<module_name>.]<parameter_name>:<parameter_type>)*
[-disable_strictequals_assertions]
[-apachev2license <YYYY Owner>]
(-whitelist_module_for_letin <module_name>)*
-o <ocaml_output_file>
When you are first starting you should just use the mandatory option (-o
) and then add additional options
one at a time until your translated OCaml code compiles. Your first command will look something like:
dirsp-ps2ocaml -- src-proscript/proscript-messaging/ps/sp.js \
-o src-proscript/kobeissi_bhargavan_blanchet.ml
For KBB2017 this is done automatically as part of the build, but if we were to invoke it by hand we could run:
dirsp-ps2ocaml -- src-proscript/proscript-messaging/ps/sp.js \
-p "them:t record_them" \
-p "msg:t record_msg" \
-p "Type_sendoutput.a:t record_sendoutput" \
-whitelist_module_for_letin TOPLEVEL \
-whitelist_module_for_letin HANDLE \
-disable_strictequals_assertions \
-o src-proscript/kobeissi_bhargavan_blanchet.ml
Let’s go through that complete command invocation from top to bottom:
ps2ocaml will take the ProScript file
src-proscript/proscript-messaging/ps/sp.js
and translate it into an OCaml implementation atsrc-proscript/kobeissi_bhargavan_blanchet.ml
.The generated implementation file will rely on other files you create manually. Each of those manually created files has its own section in the documentation. They are:
The Interface signature file
kobeissi_bhargavan_blanchet.mli
The Interface module file
kobeissi_bhargavan_blanchet_intf.ml
(the naming is automatically derived from your-o
option unless you use the-t
option)The Shims module file
kobeissi_bhargavan_blanchet_shims.ml
(the naming is automatically derived from your-o
option unless you use the-s
option)
The
-p
option lets us add type hints. OCaml is very good at inferring the types without you having to write type hints, but occasionally it needs your help. With the options-p "them:t record_them" -p "msg:t record_msg" -p "Type_sendoutput.a:t record_sendoutput"
:Any ProScript function parameter with the name
them
will be annotated with thet record_them
type defined in the OCaml interface module described later.Any
msg
parameter will be annotated witht record_msg
.And any
a
parameter in a moduleType_sendoutput
will be annotated witht record_sendoutput
.
For example, the ProScript function
tryDecrypt: function(myIdentityKey, myEphemeralKey, them, msg) { /* ... */ }
becomes
let tryDecrypt myIdentityKey myEphemeralKey (them : t record_them) (msg : t record_msg) = begin (* ... *) end
and
const Type_sendoutput = { /* ... */ assert: function(a) { /* ... */ } }; const Type_recvoutput = { /* ... */ assert: function(a) { /* ... */ } };
becomes
module Type_sendoutput = struct (* ... *) let xassert (a : t record_sendoutput) = (* ... *) end module Type_recvoutput = struct (* ... *) let xassert a = (* ... *) end
The
-whitelist_module_for_letin
option is an escape hatch to add when ps2ocaml cannot safely convert the ProScript into equivalent OCaml. ps2ocaml will generate an implementation that won’t compile, and when you look at the error in the generated source code it will tell you detailed instructions about how to fix it, including the specific-whitelist_module_for_letin
option you must use to enable the escape hatch.The
disable_strictequals_assertions
option is an esoteric option you probably will not have to use. It is documented in Question 4. How do I disable assertions?.
Interface Module¶
For KBB2017 the interface module file was src-proscript/kobeissi_bhargavan_blanchet_intf.ml.
The interface module has all the record types for any records your ProScript algorithm implicitly uses.
It also has any undefined parameter types that you use as annotations with the [(-p <parameter_name:parameter_type>)*] option.
- For example, with ps2ocaml -p "msg:t record_msg"
you must have a type `a record_msg
defined.
- For example, with ps2ocaml -p msg:string
nothing is needed since string
is built-in.
- For example, with ps2ocaml -p msg:Bigarray.int16_unsigned_elt
nothing is needed since int16_unsigned_elt
was specified with a named module Bigarray
.
An example you would include in the interface module that accounts for implicit record used in the following ProScript KBB2017 algorithm code
construct: function() {
return {
valid: false,
ephemeralKey: Type_key.construct(),
initEphemeralKey: Type_key.construct(),
ciphertext: '',
iv: Type_iv.construct(),
tag: '',
preKeyId: 0
};
}
is:
type 'a record_msg =
{ valid : bool
; mutable ephemeralKey : 'a
; mutable initEphemeralKey : 'a
; ciphertext : 'a
; mutable iv : 'a
; tag : 'a
; preKeyId : int
}
In addition you must define a PROTOCOL
type that declares the signatures for any ProScript functions where OCaml can’t infer their types, and
a PROTOCOLFUNCTOR
and PROTOCOLMODULE
that can instantiate it as follows:
module type PROTOCOL = sig
(** The type that will be used to represent contiguous bytes in the protocol; typically Bytes.t or Cstruct.t *)
type t
(** An internal type representing a decrypted AES message *)
type t_aes_decrypted
(* Everything else below is optional, unless OCaml can't compile because it needs a signature for a function *)
module Type_key : sig
val construct : 'a -> t
end
end
module type PROTOCOLFUNCTOR = functor (ProScript : Dirsp_proscript.S) ->
PROTOCOL with type t = ProScript.t
module type PROTOCOLMODULE = sig
module Make: PROTOCOL
end
The PROTOCOL
is also a great place to put in documentation!
Tip: You can instantiate your machine translated module in utop
and then #show
it to generate a complete signature of your ProScript code. Use
that signature as your Protocol, replace any references to ProScript.t
with t
, and then document each signature.
Interface Signature¶
For KBB2017 the interface signature file was src-proscript/kobeissi_bhargavan_blanchet.mli.
You simply need to include
the PROTOCOLMODULE
of your interface module. Here is an example:
(* filename: kobeissi_bhargavan_blanchet.mli *)
(* Use whichever _intf module you defined earlier *)
(** @inline *)
include Kobeissi_bhargavan_blanchet_intf.PROTOCOLMODULE
Shims Module¶
For KBB2017 the shims module file was src-proscript/kobeissi_bhargavan_blanchet_shims.ml.
The shims module needs to contain a Make
functor that defines all functions in your ProScript algorithm
that can’t be machine translated into OCaml. Even if all functions can be machine translated, the Make
functor
must still exist.
Here is an example:
(* filename: kobeissi_bhargavan_blanchet_shims.ml *)
(* Use whichever _intf module you defined earlier *)
include kobeissi_bhargavan_blanchet_intf
module Make (ProScript : Proscript.S) : Protocol = struct
(**
In KBB2017, we had the following ProScript algorithm code that did not convert:
{v
const UTIL = {
newKeyPair: function(id) {
const priv = ProScript.Crypto.random32Bytes('aPK' + id);
return {
priv: priv,
pub: ProScript.Crypto.DH25519(priv, [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09
])
};
}
}
v}
That Javascript function will be replaced by your hand-written [shim_UTIL_newKeyPair]
in this module.
*)
let shim_UTIL_newKeyPair id =
let priv = ProScript.Crypto.random32Bytes (ProScript.concat [ ProScript.of_string "aID"; id ]) in
let byte = ProScript.elem_of_char in
{
priv = priv;
pub = ProScript.crypto.xDH25519 priv (ProScript.of_elem_list [
byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00';
byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00';
byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00';
byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x00'; byte '\x09'
])
}