From be2e590e936b12ed1556b21e27aad8029bd13a33 Mon Sep 17 00:00:00 2001 From: swrup Date: Mon, 13 Oct 2025 00:02:35 +0200 Subject: [PATCH] untested: rsa pub binary format --- src/amount.ml | 78 +++++++++++++++++++++++++ src/binary_formats.ml | 55 ++++++++++++++++++ src/json.ml | 18 +++--- src/types.ml | 130 ++++++++++++------------------------------ 4 files changed, 178 insertions(+), 103 deletions(-) create mode 100644 src/amount.ml diff --git a/src/amount.ml b/src/amount.ml new file mode 100644 index 00000000..228261be --- /dev/null +++ b/src/amount.ml @@ -0,0 +1,78 @@ +(* TODO + have safe amount arithmetic + make type private *) +(* Amounts of currency, serialized as `:.` + Fixed-precision numbers with 8 decimal places. + - must be at most 11 characters long + only consist of ASCII letters (a-zA-Z). + - integer part of may be at most 2^52. + - fractional part of may contain at most 8 decimal digits. + + Prefixed with '+' or '-' in certain contexts. + When no sign is present, the amount is assumed to be positive. *) +type t = { + sign: [ `Plus | `Minus ] option; + currency: [ `Eur ]; + value: Int64.t; + fraction: Int64.t; +} + +let make ~sign ~currency ~value ~fraction = + let open Syntax in + let value_upper_bound = Z.(pow (of_int 2) 52) |> Z.to_int64 in + let fraction_upper_bound = Int64.of_int 99_999_999 in + let* () = if value < Int64.zero then Error "value is negative" else Ok () in + let* () = + if fraction < Int64.zero then Error "fraction is negative" else Ok () + in + let* () = + if value > value_upper_bound then Error "value is greater than 2^52" + else Ok () + in + let* () = + if fraction > fraction_upper_bound then + Error "fraction have more than 8 decimal digits" + else Ok () + in + Ok { sign; currency; value; fraction } + +let to_string = + let pp = + let open Fmt in + let pp_sign ppf = function + | `Plus -> char ppf '+' + | `Minus -> char ppf '-' + in + let pp_currency ppf = function `Eur -> string ppf "EUR" in + fun ppf { sign; currency; value; fraction } -> + pf ppf "%a%a:%Ld.%Ld" (Fmt.option pp_sign) sign pp_currency currency value + fraction + in + Fmt.str "%a" pp + +let of_string = + let open Angstrom in + let parse_sign = + choice + [ + char '+' *> return (Some `Plus); char '-' *> return (Some `Minus); + return None; + ] + in + let parse_currency = string "EUR" *> return `Eur in + let parse_int = + take_while1 (function '0' .. '9' -> true | _ -> false) + >>| Int64.of_string_opt + >>= function + | None -> fail "invalid integer" + | Some n -> return n + in + let parse_t = + lift4 + (fun sign currency value fraction -> + make ~sign ~currency ~value ~fraction) + parse_sign parse_currency + (char ':' *> parse_int) + (char '.' *> parse_int) + in + fun s -> parse_string ~consume:Consume.All parse_t s |> Result.join diff --git a/src/binary_formats.ml b/src/binary_formats.ml index 2eb61c6b..84836609 100644 --- a/src/binary_formats.ml +++ b/src/binary_formats.ml @@ -22,6 +22,61 @@ exchange and gana master branch are not in sync and we should use a specific git tag instead *) +(* -- Crypto keys -- *) + +module RsaPublicKey = struct + (* libgnuutil format: + https://docs.gnunet.org/doxygen/d9/dbe/structGNUNET__CRYPTO__RsaPublicKeyHeaderP.html + https://docs.gnunet.org/doxygen/d6/d70/group__libgnunetutil.html#ga9c99a81e8cd649c1c925d23211a0738f + https://www.gnupg.org/documentation/manuals/gcrypt/MPI-formats.html + + format: + - rsa header + - modulus + - public_exponent + + integer in big-endian format (MSB first). + Leading zeroes are stripped unless they are required to keep a value positive. + *) + type header = { + n_len: int; + e_len: int; + } + + type t = { + header: header; + n: Z.t; + e: Z.t; + } + + (* need to strip leading zeros or something? *) + let z_to_bigendian_bits v = + let s = Z.to_bits v in + let len = String.length s in + let s = String.init len (fun i -> s.[len - 1 - i]) in + s + + let header_bin = + let open Bin in + record (fun n_len e_len -> { n_len; e_len }) + |+ field beint16 (fun t -> t.n_len) + |+ field beint16 (fun t -> t.e_len) + |> sealr + + let bin = + let open Bin in + record (fun header n e -> + let n = Z.of_bits n in + let e = Z.of_bits e in + { header; n; e }) + |+ field header_bin (fun t -> t.header) + (* TODO + Z.to_bits is in little endian but we need it in big endian *) + |+ field cstring (fun t -> z_to_bigendian_bits t.n) + |+ field cstring (fun t -> z_to_bigendian_bits t.e) + |> sealr +end + open Include let int32_size = 4 diff --git a/src/json.ml b/src/json.ml index f800b83a..f5f24e19 100644 --- a/src/json.ml +++ b/src/json.ml @@ -76,15 +76,17 @@ end let amount_jsont = Jsont.of_of_string ~kind:"Amount" Amount.of_string ~enc:Amount.to_string -module Eddsa = struct - open Eddsa +module EddsaPublicKey = struct + open EddsaPublicKey - let pub_jsont = - let dec = Jsont.Base.dec_result pub_of_string in - let enc = Jsont.Base.enc pub_to_string in + let jsont = + let dec = Jsont.Base.dec_result of_string in + let enc = Jsont.Base.enc to_string in Jsont.Base.string (Jsont.Base.map ~kind:"EddsaPublicKey" ~dec ~enc ()) +end - let signature_jsont = Jsont.string +module EddsaSignature = struct + let jsont = Jsont.string end module RsaDenominationKey = struct @@ -148,10 +150,10 @@ module FutureSignKey = struct let signkey_secmod_sig v = v.signkey_secmod_sig in let open Jsont.Object in map ~kind:"FutureSignKey" make - |> mem "key" Eddsa.pub_jsont ~enc:key + |> mem "key" EddsaPublicKey.jsont ~enc:key |> mem "stamp_start" Timestamp.jsont ~enc:stamp_start |> mem "stamp_expire" Timestamp.jsont ~enc:stamp_expire |> mem "stamp_end" Timestamp.jsont ~enc:stamp_end - |> mem "signkey_secmod_sig" Eddsa.signature_jsont ~enc:signkey_secmod_sig + |> mem "signkey_secmod_sig" EddsaSignature.jsont ~enc:signkey_secmod_sig |> finish end diff --git a/src/types.ml b/src/types.ml index 415b6dcb..28a7a7d7 100644 --- a/src/types.ml +++ b/src/types.ml @@ -30,97 +30,16 @@ module RelativeTime = struct | Forever end -module Amount = struct - (* TODO - have safe amount arithmetic - make type private *) - (* Amounts of currency, serialized as `:.` - Fixed-precision numbers with 8 decimal places. - - must be at most 11 characters long - only consist of ASCII letters (a-zA-Z). - - integer part of may be at most 2^52. - - fractional part of may contain at most 8 decimal digits. +module Amount = Amount - Prefixed with '+' or '-' in certain contexts. - When no sign is present, the amount is assumed to be positive. *) - type t = { - sign: [ `Plus | `Minus ] option; - currency: [ `Eur ]; - value: Int64.t; - fraction: Int64.t; - } - - let make ~sign ~currency ~value ~fraction = - let open Syntax in - let value_upper_bound = Z.(pow (of_int 2) 52) |> Z.to_int64 in - let fraction_upper_bound = Int64.of_int 99_999_999 in - let* () = if value < Int64.zero then Error "value is negative" else Ok () in - let* () = - if fraction < Int64.zero then Error "fraction is negative" else Ok () - in - let* () = - if value > value_upper_bound then Error "value is greater than 2^52" - else Ok () - in - let* () = - if fraction > fraction_upper_bound then - Error "fraction have more than 8 decimal digits" - else Ok () - in - Ok { sign; currency; value; fraction } - - let to_string = - let pp = - let open Fmt in - let pp_sign ppf = function - | `Plus -> char ppf '+' - | `Minus -> char ppf '-' - in - let pp_currency ppf = function `Eur -> string ppf "EUR" in - fun ppf { sign; currency; value; fraction } -> - pf ppf "%a%a:%Ld.%Ld" (Fmt.option pp_sign) sign pp_currency currency - value fraction - in - Fmt.str "%a" pp - - let of_string = - let open Angstrom in - let parse_sign = - choice - [ - char '+' *> return (Some `Plus); char '-' *> return (Some `Minus); - return None; - ] - in - let parse_currency = string "EUR" *> return `Eur in - let parse_int = - take_while1 (function '0' .. '9' -> true | _ -> false) - >>| Int64.of_string_opt - >>= function - | None -> fail "invalid integer" - | Some n -> return n - in - let parse_t = - lift4 - (fun sign currency value fraction -> - make ~sign ~currency ~value ~fraction) - parse_sign parse_currency - (char ':' *> parse_int) - (char '.' *> parse_int) - in - fun s -> parse_string ~consume:Consume.All parse_t s |> Result.join -end - -module Eddsa = struct - (* TODO test *) +(* TODO key format *) +module EddsaPublicKey = struct (* EdDSA and ECDHE public keys always point on Curve25519 and represented using the standard 256 bits Ed25519 compact format, converted to Crockford Base32. *) - (* EdDSA signatures are transmitted as 64-bytes `base32` - binary-encoded objects with just the R and S values (base32_ binary-only). *) - type pub = Mirage_crypto_ec.Ed25519.pub + type t = Mirage_crypto_ec.Ed25519.pub - let pub_of_string s = + let of_string s = let open Mirage_crypto_ec in match Base_32.decode s with | Error _ as err -> err @@ -129,24 +48,44 @@ module Eddsa = struct | Error e -> Fmt.error "%a" pp_error e | Ok pub -> Ok pub) - let pub_to_string pub = + let to_string pub = pub |> Mirage_crypto_ec.Ed25519.pub_to_octets |> Base_32.encode +end +module EddsaSignature = struct (* EdDSA signatures are transmitted as 64-bytes base32 binary-encoded objects with just the R and S values (base32_ binary-only). They are signature over a c-struct like `TALER_xxxPS` + with a purpose *) - type signature = string + type t = string + + (* TODO key format + is it exactly like in GNU_CRYPTO? + endianess issue? *) + let sign ~key s = + (* mirage_crypto: "The result is the concatenation of r and s, as specified in RFC 8032." *) + Mirage_crypto_ec.Ed25519.sign ~key s end -module Rsa = struct - (* TODO - GNUNET_CRYPTO custom encode/decode *) +module RsaPublicKey = struct (* RSA public key converted to Crockford Base32. *) type pub = Mirage_crypto_pk.Rsa.pub - let pub_of_string _s = assert false - let pub_to_string _pub = assert false + let pub_of_string s = + (* TODO bin + - no [Bin.of_string] ? + - what to do with the int ref? *) + let off = ref 0 in + let v = Bin.decode Binary_formats.RsaPublicKey.bin s off in + let res = Mirage_crypto_pk.Rsa.pub ~n:v.n ~e:v.e in + match res with Error (`Msg e) -> Error e | Ok pub -> Ok pub + + let pub_to_string ({ n; e } : Mirage_crypto_pk.Rsa.pub) = + let open Binary_formats.RsaPublicKey in + let header = { n_len= Z.size n; e_len= Z.size e } in + let v = { header; n; e } in + let s = Bin.to_string bin v in + s end module HashCode = struct @@ -161,6 +100,7 @@ module RsaDenominationKey = struct } end +(* not implemented *) module CSDenominationKey = struct (* Clause Schnorr *) type t = { @@ -178,7 +118,7 @@ end module FutureSignKey = struct type t = { (* The actual exchange's EdDSA signing public key *) - key: Eddsa.pub; + key: EddsaPublicKey.t; (* Initial validity date for the signing key. *) stamp_start: Timestamp.t; (* Date when the exchange will stop using the signing key, allowed to overlap @@ -190,6 +130,6 @@ module FutureSignKey = struct (* Signature over TALER_SigningKeyAnnouncementPS for this signing key by the signkey security module using purpose TALER_SIGNATURE_SM_SIGNING_KEY. *) - signkey_secmod_sig: Eddsa.signature; + signkey_secmod_sig: EddsaSignature.t; } end