Raw File
aggregate_signature.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2021 Nomadic Labs <contact@nomadic-labs.com>                *)
(*                                                                           *)
(* Permission is hereby granted, free of charge, to any person obtaining a   *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense,  *)
(* and/or sell copies of the Software, and to permit persons to whom the     *)
(* Software is furnished to do so, subject to the following conditions:      *)
(*                                                                           *)
(* The above copyright notice and this permission notice shall be included   *)
(* in all copies or substantial portions of the Software.                    *)
(*                                                                           *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL   *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING   *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER       *)
(* DEALINGS IN THE SOFTWARE.                                                 *)
(*                                                                           *)
(*****************************************************************************)

open Error_monad

type public_key_hash = Bls12_381 of Bls.Public_key_hash.t

type public_key = Bls12_381 of Bls.Public_key.t

type secret_key = Bls12_381 of Bls.Secret_key.t

module Public_key_hash = struct
  type t = public_key_hash = Bls12_381 of Bls.Public_key_hash.t

  let name = "Aggregate_signature.Public_key_hash"

  let title = "A Bls12_381 public key hash"

  type Base58.data += Data of t (* unused *)

  let b58check_encoding =
    (* unused *)
    Base58.register_encoding
      ~prefix:"\255\255"
      ~length:2
      ~to_raw:(fun _ -> assert false)
      ~of_raw:(fun _ -> assert false)
      ~wrap:(fun x -> Data x)

  let raw_encoding =
    let open Data_encoding in
    def "aggregate_public_key_hash" ~description:title
    @@ union
         [
           case
             (Tag 0)
             Bls.Public_key_hash.encoding
             ~title:"Bls12_381"
             (function Bls12_381 x -> Some x)
             (function x -> Bls12_381 x);
         ]

  let to_bytes s = Data_encoding.Binary.to_bytes_exn raw_encoding s

  let of_bytes_opt s = Data_encoding.Binary.of_bytes_opt raw_encoding s

  let to_string s = Bytes.to_string (to_bytes s)

  let of_string_opt s = of_bytes_opt (Bytes.of_string s)

  let size = 1 + Bls.Public_key_hash.size

  let zero = Bls12_381 Bls.Public_key_hash.zero

  include Helpers.MakeRaw (struct
    type nonrec t = t

    let name = name

    let of_bytes_opt = of_bytes_opt

    let of_string_opt = of_string_opt

    let to_string = to_string
  end)

  let of_b58check_opt s =
    match Base58.decode s with
    | Some (Bls.Public_key_hash.Data pkh) -> Some (Bls12_381 pkh)
    | _ -> None

  let of_b58check_exn s =
    match of_b58check_opt s with
    | Some x -> x
    | None -> Format.kasprintf Stdlib.failwith "Unexpected data (%s)" name

  let of_b58check s =
    match of_b58check_opt s with
    | Some x -> Ok x
    | None ->
        error_with "Failed to read a b58check_encoding data (%s): %S" name s

  let to_b58check = function
    | Bls12_381 pkh -> Bls.Public_key_hash.to_b58check pkh

  let to_short_b58check = function
    | Bls12_381 pkh -> Bls.Public_key_hash.to_short_b58check pkh

  let to_path key l =
    match key with
    | Bls12_381 h -> "bls12_381" :: Bls.Public_key_hash.to_path h l

  let of_path = function
    | "bls12_381" :: q ->
        Bls.Public_key_hash.of_path q |> Option.map (fun pkh -> Bls12_381 pkh)
    | _ -> assert false

  let of_path_exn = function
    | "bls12_381" :: q -> Bls12_381 (Bls.Public_key_hash.of_path_exn q)
    | _ -> assert false

  let path_length = 1 + Bls.Public_key_hash.path_length

  let prefix_path _ = assert false (* unused *)

  let seeded_hash = Stdlib.Hashtbl.seeded_hash

  let hash = Stdlib.Hashtbl.hash

  include Compare.Make (struct
    type nonrec t = t

    let compare a b =
      match (a, b) with
      | Bls12_381 x, Bls12_381 y -> Bls.Public_key_hash.compare x y
  end)

  include Helpers.MakeEncoder (struct
    type nonrec t = t

    let name = name

    let title = title

    let raw_encoding = raw_encoding

    let of_b58check = of_b58check

    let of_b58check_opt = of_b58check_opt

    let of_b58check_exn = of_b58check_exn

    let to_b58check = to_b58check

    let to_short_b58check = to_short_b58check
  end)

  include Helpers.MakeIterator (struct
    type nonrec t = t

    let hash = hash

    let seeded_hash = seeded_hash

    let compare = compare

    let equal = equal

    let encoding = encoding
  end)

  let rpc_arg =
    Tezos_rpc.Arg.like
      rpc_arg
      ~descr:"An aggregate public key hash (Base58Check-encoded)"
      "pkh"

  module Logging = struct
    let tag = Tag.def ~doc:title name pp
  end
end

module Public_key = struct
  type t = public_key = Bls12_381 of Bls.Public_key.t

  let name = "Aggregate_signature.Public_key"

  let title = "A bls12_381 public key"

  let hash pk =
    match pk with
    | Bls12_381 pk -> Public_key_hash.Bls12_381 (Bls.Public_key.hash pk)

  include Compare.Make (struct
    type nonrec t = t

    let compare a b =
      match (a, b) with Bls12_381 x, Bls12_381 y -> Bls.Public_key.compare x y
  end)

  type Base58.data += Data of t (* unused *)

  let b58check_encoding =
    (* unused *)
    Base58.register_encoding
      ~prefix:"\255\255"
      ~length:2
      ~to_raw:(fun _ -> assert false)
      ~of_raw:(fun _ -> assert false)
      ~wrap:(fun x -> Data x)

  let of_b58check_opt s =
    match Base58.decode s with
    | Some (Bls.Public_key.Data public_key) -> Some (Bls12_381 public_key)
    | _ -> None

  let of_b58check_exn s =
    match of_b58check_opt s with
    | Some x -> x
    | None -> Format.kasprintf Stdlib.failwith "Unexpected data (%s)" name

  let of_b58check s =
    match of_b58check_opt s with
    | Some x -> Ok x
    | None ->
        error_with "Failed to read a b58check_encoding data (%s): %S" name s

  let to_b58check = function Bls12_381 pk -> Bls.Public_key.to_b58check pk

  let to_short_b58check = function
    | Bls12_381 pk -> Bls.Public_key.to_short_b58check pk

  let of_bytes_without_validation b =
    let tag = Bytes.(get_int8 b 0) in
    let b = Bytes.(sub b 1 (length b - 1)) in
    match tag with
    | 0 ->
        Option.bind (Bls.Public_key.of_bytes_without_validation b) (fun pk ->
            Some (Bls12_381 pk))
    | _ -> None

  include Helpers.MakeEncoder (struct
    type nonrec t = t

    let name = name

    let title = title

    let raw_encoding =
      let open Data_encoding in
      def "aggregate_public_key" ~description:title
      @@ union
           [
             case
               (Tag 0)
               Bls.Public_key.encoding
               ~title:"Bls12_381"
               (function Bls12_381 x -> Some x)
               (function x -> Bls12_381 x);
           ]

    let of_b58check = of_b58check

    let of_b58check_opt = of_b58check_opt

    let of_b58check_exn = of_b58check_exn

    let to_b58check = to_b58check

    let to_short_b58check = to_short_b58check
  end)

  let size pk = Data_encoding.Binary.length encoding pk

  let pp ppf t = Format.fprintf ppf "%s" (to_b58check t)
end

module Secret_key = struct
  type t = secret_key = Bls12_381 of Bls.Secret_key.t

  let name = "Aggregate_signature.Secret_key"

  let title = "A Bls12_381 secret key"

  let to_public_key = function
    | Bls12_381 sk -> Public_key.Bls12_381 (Bls.Secret_key.to_public_key sk)

  include Compare.Make (struct
    type nonrec t = t

    let compare a b =
      match (a, b) with Bls12_381 x, Bls12_381 y -> Bls.Secret_key.compare x y
  end)

  type Base58.data += Data of t (* unused *)

  let b58check_encoding =
    (* unused *)
    Base58.register_encoding
      ~prefix:"\255\255"
      ~length:2
      ~to_raw:(fun _ -> assert false)
      ~of_raw:(fun _ -> assert false)
      ~wrap:(fun x -> Data x)

  let of_b58check_opt b =
    match Base58.decode b with
    | Some (Bls.Secret_key.Data sk) -> Some (Bls12_381 sk)
    | _ -> None

  let of_b58check_exn s =
    match of_b58check_opt s with
    | Some x -> x
    | None -> Format.kasprintf Stdlib.failwith "Unexpected data (%s)" name

  let of_b58check s =
    match of_b58check_opt s with
    | Some x -> Ok x
    | None ->
        error_with "Failed to read a b58check_encoding data (%s): %S" name s

  let to_b58check = function Bls12_381 sk -> Bls.Secret_key.to_b58check sk

  let to_short_b58check = function
    | Bls12_381 sk -> Bls.Secret_key.to_short_b58check sk

  include Helpers.MakeEncoder (struct
    type nonrec t = t

    let name = name

    let title = title

    let raw_encoding =
      let open Data_encoding in
      def "aggregate_secret_key" ~description:title
      @@ union
           [
             case
               (Tag 0)
               Bls.Secret_key.encoding
               ~title:"Bls12_381"
               (function Bls12_381 x -> Some x)
               (function x -> Bls12_381 x);
           ]

    let of_b58check = of_b58check

    let of_b58check_opt = of_b58check_opt

    let of_b58check_exn = of_b58check_exn

    let to_b58check = to_b58check

    let to_short_b58check = to_short_b58check
  end)

  let pp ppf t = Format.fprintf ppf "%s" (to_b58check t)
end

type signature = Bls12_381 of Bls.t | Unknown of Bytes.t

type t = signature

type watermark = Bytes.t

let name = "Aggregate signature"

let title = "A Bls12_381 signature"

let size = Bls.size

let to_bytes = function Bls12_381 b -> Bls.to_bytes b | Unknown b -> b

let of_bytes_opt s = if Bytes.length s = size then Some (Unknown s) else None

let to_string s = Bytes.to_string (to_bytes s)

let of_string_opt s = of_bytes_opt (Bytes.of_string s)

type Base58.data += Data of t

let b58check_encoding =
  Base58.register_encoding
    ~prefix:Base58.Prefix.generic_aggregate_signature
    ~length:size
    ~to_raw:to_string
    ~of_raw:of_string_opt
    ~wrap:(fun x -> Data x)

let () = Base58.check_encoded_prefix b58check_encoding "asig" 141

include Helpers.MakeRaw (struct
  type nonrec t = t

  let name = name

  let of_bytes_opt = of_bytes_opt

  let of_string_opt = of_string_opt

  let to_string = to_string
end)

include Compare.Make (struct
  type nonrec t = t

  let compare a b =
    let a = to_bytes a and b = to_bytes b in
    Bytes.compare a b
end)

let of_b58check_opt s =
  if TzString.has_prefix ~prefix:Bls.b58check_encoding.encoded_prefix s then
    Option.map (fun x -> Bls12_381 x) (Bls.of_b58check_opt s)
  else Base58.simple_decode b58check_encoding s

let of_b58check_exn s =
  match of_b58check_opt s with
  | Some x -> x
  | None -> Format.kasprintf Stdlib.failwith "Unexpected data (%s)" name

let of_b58check s =
  match of_b58check_opt s with
  | Some x -> Ok x
  | None -> error_with "Failed to read a b58check_encoding data (%s): %S" name s

let to_b58check = function
  | Bls12_381 b -> Bls.to_b58check b
  | Unknown b -> Base58.simple_encode b58check_encoding (Unknown b)

let to_short_b58check = function
  | Bls12_381 b -> Bls.to_short_b58check b
  | Unknown b -> Base58.simple_encode b58check_encoding (Unknown b)

include Helpers.MakeEncoder (struct
  type nonrec t = t

  let name = name

  let title = title

  let raw_encoding =
    Data_encoding.conv to_bytes of_bytes_exn (Data_encoding.Fixed.bytes size)

  let of_b58check = of_b58check

  let of_b58check_opt = of_b58check_opt

  let of_b58check_exn = of_b58check_exn

  let to_b58check = to_b58check

  let to_short_b58check = to_short_b58check
end)

let pp ppf t = Format.fprintf ppf "%s" (to_b58check t)

let zero = Bls12_381 Bls.zero

let sign ?watermark (Secret_key.Bls12_381 sk) bytes =
  Bls12_381 (Bls.sign ?watermark sk bytes)

let check ?watermark pk signature message =
  match (pk, signature) with
  | Public_key.Bls12_381 pk, Unknown signature ->
      Bls.of_bytes_opt signature
      |> Option.map (fun signature -> Bls.check ?watermark pk signature message)
      |> Option.value ~default:false
  | Public_key.Bls12_381 pk, Bls12_381 signature ->
      Bls.check ?watermark pk signature message

let generate_key ?seed () =
  let pkh, pk, sk = Bls.generate_key ?seed () in
  ( Public_key_hash.Bls12_381 pkh,
    Public_key.Bls12_381 pk,
    Secret_key.Bls12_381 sk )

let deterministic_nonce (Secret_key.Bls12_381 sk) msg =
  Bls.deterministic_nonce sk msg

let deterministic_nonce_hash (Secret_key.Bls12_381 sk) msg =
  Bls.deterministic_nonce_hash sk msg

let aggregate_check pks signature =
  let pks =
    List.map
      (fun (Public_key.Bls12_381 pk, watermark, bytes) ->
        (pk, watermark, bytes))
      pks
  in
  match signature with
  | Bls12_381 signature -> Bls.aggregate_check pks signature
  | Unknown signature ->
      Bls.of_bytes_opt signature
      |> Option.map (Bls.aggregate_check pks)
      |> Option.value ~default:false

let aggregate_signature_opt signatures =
  let open Result_syntax in
  let aux acc s =
    match s with
    | Bls12_381 s -> return @@ (s :: acc)
    | Unknown s ->
        let* s = Bls.of_bytes s in
        return (s :: acc)
  in
  match List.fold_left_e aux [] signatures with
  | Ok signatures ->
      Bls.aggregate_signature_opt signatures
      |> Option.map (fun s -> Bls12_381 s)
  | Error _ -> None
back to top