open StdLabels module BA = struct include Bigarray.Array1 let length = size_in_bytes let rec compare_rec a b i len_a len_b = if i=len_a && i=len_b then 0 else if i=len_a then -1 else if i=len_b then 1 else match Char.compare (get a i) (get b i) with | 0 -> compare_rec a b (i+1) len_a len_b | n -> n let compare a b = compare_rec a b 0 (length a) (length b) let equal a b = compare a b = 0 let create len = Bigarray.(create char c_layout len) end type buffer = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t module Context = struct type flag = | Verify | Sign type t external flags : buffer -> int = "context_flags" external create : int -> t = "context_create" external clone : t -> t = "context_clone" external randomize : t -> buffer -> bool = "context_randomize" [@@noalloc] external get_16 : buffer -> int -> int = "%caml_bigstring_get16" [@@noalloc] let flags = let buf = BA.create (3 * 2) in let _ = flags buf in buf let int_of_flag = function | Verify -> get_16 flags 2 | Sign -> get_16 flags 4 let create a = List.fold_left a ~init:(get_16 flags 0) ~f:(fun a f -> a lor (int_of_flag f)) |> create let randomize ctx buf = if BA.length buf <> 32 then invalid_arg "Context.randomize: input must be 32 bytes long" ; randomize ctx buf end module Key = struct type secret type public type _ t = | Sk : buffer -> secret t | Pk : buffer -> public t let to_buffer : type a. a t -> buffer = function | Sk k -> k | Pk k -> k let secret_bytes = 32 let public_bytes = 64 let length : type a. a t -> int = function | Sk _ -> 32 | Pk _ -> 64 let equal : type a. a t -> a t -> bool = fun a b -> match a, b with | Sk a, Sk b -> BA.equal a b | Pk a, Pk b -> BA.equal a b let copy : type a. a t -> a t = function | Sk k -> let k' = BA.create secret_bytes in BA.blit k k' ; Sk k' | Pk k -> let k' = BA.create public_bytes in BA.blit k k' ; Pk k' external sk_negate_inplace : Context.t -> buffer -> unit = "ec_privkey_negate" [@@noalloc] external sk_add_tweak_inplace : Context.t -> buffer -> buffer -> bool = "ec_privkey_tweak_add" [@@noalloc] external sk_mul_tweak_inplace : Context.t -> buffer -> buffer -> bool = "ec_privkey_tweak_mul" [@@noalloc] external pk_negate_inplace : Context.t -> buffer -> unit = "ec_pubkey_negate" [@@noalloc] external pk_add_tweak_inplace : Context.t -> buffer -> buffer -> bool = "ec_pubkey_tweak_add" [@@noalloc] external pk_mul_tweak_inplace : Context.t -> buffer -> buffer -> bool = "ec_pubkey_tweak_mul" [@@noalloc] external pk_combine : Context.t -> buffer -> buffer list -> bool = "ec_pubkey_combine" [@@noalloc] let negate_inplace : type a. Context.t -> a t -> unit = fun ctx -> function | Sk k -> sk_negate_inplace ctx k | Pk k -> pk_negate_inplace ctx k let negate ctx k = let k' = copy k in negate_inplace ctx k' ; k' let op_tweak : type a. string -> (Context.t -> buffer -> buffer -> bool) -> Context.t -> a t -> ?pos:int -> buffer -> buffer = fun name f ctx k ?(pos=0) buf -> let buflen = BA.length buf in if pos < 0 || pos > buflen - 32 then invalid_arg (Printf.sprintf "Key.%s: pos < 0 or pos > buflen - 32" name) ; let buf = BA.sub buf pos 32 in let k' = copy k |> to_buffer in if not (f ctx k' buf) then failwith (Printf.sprintf "Key.%s: operation failed" name) ; k' let add_tweak : type a. Context.t -> a t -> ?pos:int -> buffer -> a t = fun ctx k ?pos buf -> match k with | Sk _ -> Sk (op_tweak "add_tweak" sk_add_tweak_inplace ctx k ?pos buf) | Pk _ -> Pk (op_tweak "add_tweak" pk_add_tweak_inplace ctx k ?pos buf) let mul_tweak : type a. Context.t -> a t -> ?pos:int -> buffer -> a t = fun ctx k ?pos buf -> match k with | Sk _ -> Sk (op_tweak "mul_tweak" sk_mul_tweak_inplace ctx k ?pos buf) | Pk _ -> Pk (op_tweak "mul_tweak" pk_mul_tweak_inplace ctx k ?pos buf) external pk_parse : Context.t -> buffer -> buffer -> bool = "ec_pubkey_parse" [@@noalloc] external pk_serialize : Context.t -> buffer -> buffer -> int = "ec_pubkey_serialize" [@@noalloc] external pk_create : Context.t -> buffer -> buffer -> bool = "ec_pubkey_create" [@@noalloc] let neuterize : type a. Context.t -> a t -> public t option = fun ctx -> function | Pk pk -> Some (Pk pk) | Sk sk -> let pk = BA.create public_bytes in if pk_create ctx pk sk then Some (Pk pk) else None let neuterize_exn ctx k = match neuterize ctx k with | None -> invalid_arg "Key.neuterize_exn: invalid secret key" | Some pk -> pk let list_map_filter_opt ~f l = List.fold_left ~init:[] ~f:begin fun a e -> match f e with | None -> a | Some r -> r :: a end l let combine ctx pks = let nb_pks = List.length pks in if nb_pks = 0 || nb_pks > 1024 then None else let pk = BA.create public_bytes in let pks = list_map_filter_opt ~f:begin fun k -> match neuterize ctx k with | None -> None | Some (Pk k) -> Some k end pks in if pk_combine ctx pk pks then Some (Pk pk) else None let combine_exn ctx pks = match combine ctx pks with | None -> invalid_arg "Key.combine_exn: sum of pks is invalid" | Some pk -> pk external verify_sk : Context.t -> buffer -> bool = "ec_seckey_verify" [@@noalloc] let read_sk_exn ctx ?(pos=0) buf = let buflen = BA.length buf in if pos < 0 || pos > buflen - secret_bytes then invalid_arg "Key.read_sk: pos < 0 or pos + 32 > buflen" ; let buf = BA.sub buf pos secret_bytes in match verify_sk ctx buf with | true -> let t = BA.create secret_bytes in BA.blit buf t ; Sk buf | false -> invalid_arg "Key.read_sk_exn: secret key is invalid" let read_sk ctx ?pos buf = try Ok (read_sk_exn ctx ?pos buf) with | Invalid_argument msg -> Error msg let read_pk_exn ctx ?(pos=0) inbuf = let pklen = BA.length inbuf in if pos < 0 || pos > pklen - 33 then invalid_arg "Key.read_pk: pos < 0 or pos > buflen - 33" ; let inbuf = BA.(sub inbuf pos (length inbuf)) in if BA.(length inbuf < 33) then invalid_arg "Key.read_pk: input must be at least 33 bytes long" ; let outbuf = BA.create public_bytes in if (pk_parse ctx outbuf inbuf) then Pk outbuf else invalid_arg "Key.read_pk_exn: public key is invalid" let read_pk ctx ?pos buf = try Ok (read_pk_exn ctx ?pos buf) with | Invalid_argument msg -> Error msg let write : type a. ?compress:bool -> Context.t -> ?pos:int -> buffer -> a t -> int = fun ?(compress=true) ctx ?(pos=0) buf -> function | Sk sk -> let buflen = BA.length buf in if pos < 0 || pos > buflen - secret_bytes then invalid_arg "Key.write (secret): pos < 0 or pos + 32 > buflen" ; let buf = BA.sub buf pos secret_bytes in BA.blit sk buf ; secret_bytes | Pk pk -> let buflen = BA.length buf in if pos < 0 || (compress && pos > buflen - 33) || (not compress && pos > buflen - 65) then invalid_arg (Printf.sprintf "Key.write (public): pos=%d, buflen=%d" pos buflen) ; let len = if compress then 33 else 65 in let buf = BA.sub buf pos len in pk_serialize ctx buf pk let to_bytes : type a. ?compress:bool -> Context.t -> a t -> buffer = fun ?(compress=true) ctx -> function | Sk _ as sk -> let buf = BA.create secret_bytes in let _ = write ~compress ctx buf sk in buf | Pk _ as pk -> let buf = BA.create (1 + (if compress then secret_bytes else public_bytes)) in let _ = write ~compress ctx buf pk in buf end module Sign = struct type plain type recoverable type _ t = | P : buffer -> plain t | R : buffer -> recoverable t let plain_bytes = 64 let recoverable_bytes = 65 let msg_bytes = 32 type msg = buffer let msg_of_bytes ?(pos=0) buf = try Some (BA.sub buf pos msg_bytes) with _ -> None let msg_of_bytes_exn ?pos buf = match msg_of_bytes ?pos buf with | None -> invalid_arg "msg_of_bytes_exn" | Some msg -> msg let write_msg_exn ?(pos=0) buf msg = let buflen = BA.length buf in if pos < 0 || pos > buflen - msg_bytes then invalid_arg "Sign.read_exn: pos < 0 or pos > buflen - 64" ; BA.blit (BA.sub msg 0 msg_bytes) (BA.sub buf pos msg_bytes) ; msg_bytes let write_msg ?pos buf msg = try Ok (write_msg_exn ?pos buf msg) with | Invalid_argument msg -> Error msg let msg_to_bytes msg = msg let equal : type a. a t -> a t -> bool = fun a b -> match a, b with | P a, P b -> BA.equal a b | R a, R b -> BA.equal a b external parse_compact : Context.t -> buffer -> buffer -> bool = "ecdsa_signature_parse_compact" [@@noalloc] external parse_der : Context.t -> buffer -> buffer -> bool = "ecdsa_signature_parse_der" [@@noalloc] external serialize_compact : Context.t -> buffer -> buffer -> unit = "ecdsa_signature_serialize_compact" [@@noalloc] external serialize_der : Context.t -> buffer -> buffer -> int = "ecdsa_signature_serialize_der" [@@noalloc] external parse_recoverable : Context.t -> buffer -> buffer -> int -> bool = "ecdsa_recoverable_signature_parse_compact" [@@noalloc] external serialize_recoverable : Context.t -> buffer -> buffer -> int = "ecdsa_recoverable_signature_serialize_compact" [@@noalloc] let read_exn ctx ?(pos=0) buf = let buflen = BA.length buf in if pos < 0 || pos > buflen - plain_bytes then invalid_arg "Sign.read_exn: pos < 0 or pos > buflen - 64" ; let signature = BA.create plain_bytes in if parse_compact ctx signature (BA.sub buf pos plain_bytes) then P signature else invalid_arg "Sign.read_exn: signature could not be parsed" let read ctx ?pos buf = try Ok (read_exn ctx ?pos buf) with | Invalid_argument msg -> Error msg let read_der_exn ctx ?(pos=0) buf = let buflen = BA.length buf in if pos < 0 || pos > buflen - plain_bytes then invalid_arg "Sign.read_der: pos < 0 or pos > buflen - 72" ; let signature = BA.create plain_bytes in if parse_der ctx signature BA.(sub buf pos (length buf)) then P signature else invalid_arg "Sign.read_der_exn: signature could not be parsed" let read_der ctx ?pos buf = try Ok (read_der_exn ctx ?pos buf) with | Invalid_argument msg -> Error msg let read_recoverable_exn ctx ~recid ?(pos=0) buf = let buflen = BA.length buf in if pos < 0 || pos > buflen - plain_bytes then invalid_arg "Sign.read_recoverable_exn: pos < 0 or pos > buflen - 64" ; let signature = BA.create recoverable_bytes in if parse_recoverable ctx signature (BA.sub buf pos plain_bytes) recid then (R signature) else invalid_arg "Sign.read_recoverable_exn: signature could not be parsed" let read_recoverable ctx ~recid ?pos buf = try Ok (read_recoverable_exn ctx ~recid ?pos buf) with | Invalid_argument msg -> Error msg let write_exn : type a. ?der:bool -> Context.t -> ?pos:int -> buffer -> a t -> int = fun ?(der=false) ctx ?(pos=0) buf -> function | P signature -> let buf = BA.(sub buf pos (length buf)) in if der then serialize_der ctx buf signature else (serialize_compact ctx buf signature ; plain_bytes) | R signature -> let buflen = BA.length buf in if pos < 0 || pos > buflen - plain_bytes then invalid_arg "write: pos < 0 or pos > buflen - 64" ; ignore (serialize_recoverable ctx (BA.sub buf pos plain_bytes) signature) ; plain_bytes let write ?der ctx ?pos buf signature = try Ok (write_exn ?der ctx ?pos buf signature) with | Invalid_argument msg -> Error msg let to_bytes ?der ctx signature = let buf = BA.create 72 in let nb_written = write_exn ?der ctx buf signature in BA.sub buf 0 nb_written let to_bytes_recid ctx (R signature) = let buf = BA.create plain_bytes in let recid = serialize_recoverable ctx buf signature in buf, recid external sign : Context.t -> buffer -> buffer -> buffer -> bool = "ecdsa_sign" [@@noalloc] external verify : Context.t -> buffer -> buffer -> buffer -> bool = "ecdsa_verify" [@@noalloc] let write_sign_exn ctx ~sk ~msg ?(pos=0) buf = let buflen = BA.length buf in if pos < 0 || pos > buflen - plain_bytes then invalid_arg "Sign.write_sign: outpos < 0 or outpos > outbuf - 64" ; if sign ctx (BA.sub buf pos plain_bytes) (Key.to_buffer sk) msg then plain_bytes else invalid_arg "Sign.write_sign: the nonce generation function failed, or the private key was invalid" let write_sign ctx ~sk ~msg ?pos buf = try Ok (write_sign_exn ctx ~sk ~msg ?pos buf) with | Invalid_argument msg -> Error msg let sign ctx ~sk ~msg = let signature = BA.create plain_bytes in match write_sign ctx ~sk ~msg signature with | Error msg -> Error msg | Ok _nb_written -> Ok (P signature) let sign_exn ctx ~sk ~msg = match sign ctx ~sk ~msg with | Error msg -> invalid_arg msg | Ok signature -> signature external sign_recoverable : Context.t -> buffer -> buffer -> buffer -> bool = "ecdsa_sign_recoverable" [@@noalloc] let write_sign_recoverable_exn ctx ~sk ~msg ?(pos=0) buf = let buflen = BA.length buf in if pos < 0 || pos > buflen - recoverable_bytes then invalid_arg "Sign.write_sign_recoverable_exn: \ outpos < 0 or outpos > outbuflen - 65" ; if sign_recoverable ctx (BA.sub buf pos recoverable_bytes) (Key.to_buffer sk) msg then recoverable_bytes else invalid_arg "Sign.write_sign_recoverable_exn: \ the nonce generation function failed, or the private key was invalid" let write_sign_recoverable ctx ~sk ~msg ?pos buf = try Ok (write_sign_recoverable_exn ctx ~sk ~msg ?pos buf) with | Invalid_argument msg -> Error msg let sign_recoverable ctx ~sk msg = let signature = BA.create recoverable_bytes in match write_sign_recoverable ctx ~sk ~msg signature with | Error error -> Error error | Ok _nb_written -> Ok (R signature) let sign_recoverable_exn ctx ~sk msg = match sign_recoverable ctx ~sk msg with | Error msg -> invalid_arg msg | Ok signature -> signature external to_plain : Context.t -> buffer -> buffer -> unit = "ecdsa_recoverable_signature_convert" [@@noalloc] let to_plain ctx (R recoverable) = let plain = BA.create plain_bytes in to_plain ctx plain recoverable ; P plain let verify_plain_exn ctx ~pk ?(pos=0) msg signature = let msglen = BA.length msg in if pos < 0 || pos > msglen - 32 then invalid_arg "Sign.verify: msg must be at least 32 bytes long" ; verify ctx (Key.to_buffer pk) (BA.sub msg pos 32) signature let verify_exn : type a. Context.t -> pk:Key.public Key.t -> msg:msg -> signature:a t -> bool = fun ctx ~pk ~msg ~signature -> match signature with | P signature -> verify_plain_exn ctx ~pk msg signature | R _ as r -> let P signature = to_plain ctx r in verify_plain_exn ctx ~pk msg signature let verify ctx ~pk ~msg ~signature = try Ok (verify_exn ctx ~pk ~msg ~signature) with | Invalid_argument msg -> Error msg external recover : Context.t -> buffer -> buffer -> buffer -> bool = "ecdsa_recover" [@@noalloc] let recover_exn ctx ~signature:(R signature) ~msg = let pk = BA.create Key.public_bytes in if recover ctx pk signature msg then Key.Pk pk else invalid_arg "Sign.recover: pk could not be recovered" let recover ctx ~signature ~msg = try Ok (recover_exn ctx ~signature ~msg) with | Invalid_argument msg -> Error msg end