untested: rsa pub binary format

This commit is contained in:
swrup 2025-10-13 00:02:35 +02:00
parent 33032a4fbf
commit 461d6291e6
4 changed files with 178 additions and 103 deletions

78
src/amount.ml Normal file
View file

@ -0,0 +1,78 @@
(* TODO
have safe amount arithmetic
make type private *)
(* Amounts of currency, serialized as `<Currency>:<IntegerPart>.<FractionalPart>`
Fixed-precision numbers with 8 decimal places.
- <Currency> must be at most 11 characters long
only consist of ASCII letters (a-zA-Z).
- integer part of <DecimalAmount> may be at most 2^52.
- fractional part of <DecimalAmount> 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

View file

@ -22,6 +22,61 @@
exchange and gana master branch are not in sync exchange and gana master branch are not in sync
and we should use a specific git tag instead *) 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 open Include
let int32_size = 4 let int32_size = 4

View file

@ -76,15 +76,17 @@ end
let amount_jsont = let amount_jsont =
Jsont.of_of_string ~kind:"Amount" Amount.of_string ~enc:Amount.to_string Jsont.of_of_string ~kind:"Amount" Amount.of_string ~enc:Amount.to_string
module Eddsa = struct module EddsaPublicKey = struct
open Eddsa open EddsaPublicKey
let pub_jsont = let jsont =
let dec = Jsont.Base.dec_result pub_of_string in let dec = Jsont.Base.dec_result of_string in
let enc = Jsont.Base.enc pub_to_string in let enc = Jsont.Base.enc to_string in
Jsont.Base.string (Jsont.Base.map ~kind:"EddsaPublicKey" ~dec ~enc ()) Jsont.Base.string (Jsont.Base.map ~kind:"EddsaPublicKey" ~dec ~enc ())
end
let signature_jsont = Jsont.string module EddsaSignature = struct
let jsont = Jsont.string
end end
module RsaDenominationKey = struct module RsaDenominationKey = struct
@ -148,10 +150,10 @@ module FutureSignKey = struct
let signkey_secmod_sig v = v.signkey_secmod_sig in let signkey_secmod_sig v = v.signkey_secmod_sig in
let open Jsont.Object in let open Jsont.Object in
map ~kind:"FutureSignKey" make 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_start" Timestamp.jsont ~enc:stamp_start
|> mem "stamp_expire" Timestamp.jsont ~enc:stamp_expire |> mem "stamp_expire" Timestamp.jsont ~enc:stamp_expire
|> mem "stamp_end" Timestamp.jsont ~enc:stamp_end |> 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 |> finish
end end

View file

@ -30,97 +30,16 @@ module RelativeTime = struct
| Forever | Forever
end end
module Amount = struct module Amount = Amount
(* TODO
have safe amount arithmetic
make type private *)
(* Amounts of currency, serialized as `<Currency>:<IntegerPart>.<FractionalPart>`
Fixed-precision numbers with 8 decimal places.
- <Currency> must be at most 11 characters long
only consist of ASCII letters (a-zA-Z).
- integer part of <DecimalAmount> may be at most 2^52.
- fractional part of <DecimalAmount> may contain at most 8 decimal digits.
Prefixed with '+' or '-' in certain contexts. (* TODO key format *)
When no sign is present, the amount is assumed to be positive. *) module EddsaPublicKey = struct
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 *)
(* EdDSA and ECDHE public keys always point on Curve25519 (* EdDSA and ECDHE public keys always point on Curve25519
and represented using the standard 256 bits Ed25519 compact format, and represented using the standard 256 bits Ed25519 compact format,
converted to Crockford Base32. *) converted to Crockford Base32. *)
(* EdDSA signatures are transmitted as 64-bytes `base32` type t = Mirage_crypto_ec.Ed25519.pub
binary-encoded objects with just the R and S values (base32_ binary-only). *)
type pub = Mirage_crypto_ec.Ed25519.pub
let pub_of_string s = let of_string s =
let open Mirage_crypto_ec in let open Mirage_crypto_ec in
match Base_32.decode s with match Base_32.decode s with
| Error _ as err -> err | Error _ as err -> err
@ -129,24 +48,44 @@ module Eddsa = struct
| Error e -> Fmt.error "%a" pp_error e | Error e -> Fmt.error "%a" pp_error e
| Ok pub -> Ok pub) | Ok pub -> Ok pub)
let pub_to_string pub = let to_string pub =
pub |> Mirage_crypto_ec.Ed25519.pub_to_octets |> Base_32.encode pub |> Mirage_crypto_ec.Ed25519.pub_to_octets |> Base_32.encode
end
module EddsaSignature = struct
(* EdDSA signatures are transmitted as 64-bytes base32 (* EdDSA signatures are transmitted as 64-bytes base32
binary-encoded objects with just the R and S values (base32_ binary-only). 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 *) 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 end
module Rsa = struct module RsaPublicKey = struct
(* TODO
GNUNET_CRYPTO custom encode/decode *)
(* RSA public key converted to Crockford Base32. *) (* RSA public key converted to Crockford Base32. *)
type pub = Mirage_crypto_pk.Rsa.pub type pub = Mirage_crypto_pk.Rsa.pub
let pub_of_string _s = assert false let pub_of_string s =
let pub_to_string _pub = assert false (* 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 end
module HashCode = struct module HashCode = struct
@ -161,6 +100,7 @@ module RsaDenominationKey = struct
} }
end end
(* not implemented *)
module CSDenominationKey = struct module CSDenominationKey = struct
(* Clause Schnorr *) (* Clause Schnorr *)
type t = { type t = {
@ -178,7 +118,7 @@ end
module FutureSignKey = struct module FutureSignKey = struct
type t = { type t = {
(* The actual exchange's EdDSA signing public key *) (* The actual exchange's EdDSA signing public key *)
key: Eddsa.pub; key: EddsaPublicKey.t;
(* Initial validity date for the signing key. *) (* Initial validity date for the signing key. *)
stamp_start: Timestamp.t; stamp_start: Timestamp.t;
(* Date when the exchange will stop using the signing key, allowed to overlap (* Date when the exchange will stop using the signing key, allowed to overlap
@ -190,6 +130,6 @@ module FutureSignKey = struct
(* Signature over TALER_SigningKeyAnnouncementPS (* Signature over TALER_SigningKeyAnnouncementPS
for this signing key by the signkey security for this signing key by the signkey security
module using purpose TALER_SIGNATURE_SM_SIGNING_KEY. *) module using purpose TALER_SIGNATURE_SM_SIGNING_KEY. *)
signkey_secmod_sig: Eddsa.signature; signkey_secmod_sig: EddsaSignature.t;
} }
end end