From cfdb079339333c268189fe1c00226a49400ea181 Mon Sep 17 00:00:00 2001 From: swrup Date: Fri, 17 Oct 2025 20:05:48 +0200 Subject: [PATCH] --- src/binary_formats.ml | 474 ++++++++++++++++++++---------------------- src/management.ml | 12 +- test/test.ml | 2 +- 3 files changed, 228 insertions(+), 260 deletions(-) diff --git a/src/binary_formats.ml b/src/binary_formats.ml index b77126d4..7179914c 100644 --- a/src/binary_formats.ml +++ b/src/binary_formats.ml @@ -1,14 +1,14 @@ (* https://docs.taler.net/core/api-common.html#binary-formats - - numeric values are in network byte order (big endian) *) + numeric values are in network byte order (big endian) *) -(* structs that are ‘packed’ and do not contain pointers and are +(* structs that are 'packed' and do not contain pointers and are thus suitable for hashing or similar operations are distinguished - by adding a “P” at the end of the name. (NEW) Note that this convention - does not hold for the GNUnet-structs (yet). + by adding a 'P' at the end of the name. + (NEW) Note that this convention does not hold for the GNUnet-structs (yet). structs that are used with a purpose for signatures, - additionally get an “S” at the end of the name. + additionally get an 'S' at the end of the name. (from https://docs.taler.net/taler-developer-manual.html) *) @@ -18,19 +18,170 @@ - we don't need to worry about struct having "P" suffix remove them - test them + - union: not sure what to do of them + not needed or relevant i think - some purpose (`TALER_SIGNATURE_XXX`) are missing - exchange and gana master branch are not in sync - and we should use a specific git tag instead + - exchange and gana master branch are not in sync + and we should use a specific git tag instead + - outdated doc(?) + - some missing struct documentation *) - - clean up functor mess - don't have not hash thing be HashCode like - - have a make function for wrapped HashCode structs - make it take the relevant type *) +module UTIL = struct + let int32_size = 4 + let int64_size = 8 + (* microseconds since the UNIX Epoch + UINT64_MAX represents "never" *) + module MK_TIME () = struct + type t = { v: int64 } + + (* not BE (?) *) + let bin = + let open Bin in + record (fun v -> { v }) |+ field neint64 (fun t -> t.v) |> sealr + end + + module MK_TIME_NBO () = struct + type t = { v: int64 } + + let bin = + let open Bin in + record (fun v -> { v }) |+ field beint64 (fun t -> t.v) |> sealr + end + + (* MK_XX functor for structs like: + struct Foo { uint8t thing[XX]; } + MK_HASH_XX for hashed value + taler doc: + - Taler uses 512-bit hash codes (64 bytes). + - usually SHA-512 *) + module MK_32 () = struct + type t = { v: string } + + let bin = + let open Bin in + record (fun v -> { v }) |+ field (bytes 32) (fun t -> t.v) |> sealr + end + + module MK_64 () = struct + type t = { v: string } + + let bin = + let open Bin in + record (fun v -> { v }) |+ field (bytes 64) (fun t -> t.v) |> sealr + end + + module MK_HASH_32 () = struct + type t = { hash: string } + + let bin = + let open Bin in + record (fun hash -> { hash }) + |+ field (bytes 32) (fun t -> t.hash) + |> sealr + end + + module MK_HASH_64 () = struct + type t = { hash: string } + + let bin = + let open Bin in + record (fun hash -> { hash }) + |+ field (bytes 64) (fun t -> t.hash) + |> sealr + end +end + +open UTIL module Taler_signatures = Include.Taler_signatures -(* -- Crypto keys -- *) +(* -- Time -- *) +module TimeAbsolute = MK_TIME () +module TimeAbsoluteNBO = MK_TIME_NBO () +module TimeRelative = MK_TIME () +module TimeRelativeNBO = MK_TIME_NBO () +module Timestamp = MK_TIME () +module TimestampNBO = MK_TIME_NBO () + +(* -- Cryptographic primitives -- *) + +(* --- Hashes --- *) + +module ShortHashCode = MK_HASH_32 () +module HashCode = MK_HASH_64 () + +(* Hash over a full payto://-URI, including receiver-name + (and possibly BIC and other optional fields). *) +module FullPaytoHash = MK_HASH_32 () + +(* Hash over a normalized payto://-URI, including all optional + fields and also with account-part canonicalized (so no BIC). *) +module NormalizedPaytoHash = MK_HASH_32 () +module DenominationHash = MK_HASH_64 () +module PrivateContractHash = MK_HASH_64 () +module ExtensionsPolicyHash = MK_HASH_64 () +module MerchantWireHash = MK_HASH_64 () + +(* TODO missing doc *) +module AgeCommitmentHash = MK_HASH_64 () + +(* Hash over: + a) the hash of the denomination's public key, + b) an enum value identifying the cipher, and + c) cipher-dependant blinded information. + See implementation of `TALER_CoinEvHash` + in libtalerexchange for details. *) +module BlindedCoinHash = MK_HASH_64 () +module CoinPubHash = MK_HASH_64 () +module OutputCommitmentHash = MK_HASH_64 () + +(* This is the running SHA512-hash over all + `TALER_BlindedCoinHashP` values of an array of coins. + Note that each `TALER_BlindedCoinHashP` itself + captures the hash of the corresponding denomination's + public key. *) +module HashPlanchetsP = MK_HASH_64 () + +(* --- Keys --- *) +module PursePublicKey = MK_32 () (* missing doc *) +module AuditorPublicKeyP = MK_32 () (* missing doc *) +module BlindingMasterSeed = MK_32 () +module BlindingMasterSecret = MK_32 () +module ReservePublicKeyP = MK_32 () +module ReservePrivateKeyP = MK_32 () +module MerchantPublicKeyP = MK_32 () +module MerchantPrivateKeyP = MK_32 () +module TransferPublicKeyP = MK_32 () +module TransferPrivateKeyP = MK_32 () +module AmlOfficerPublicKeyP = MK_32 () +module AmlOfficerPrivateKeyP = MK_32 () +module ExchangePublicKeyP = MK_32 () +module ExchangePrivateKeyP = MK_32 () +module MasterPublicKeyP = MK_32 () +module MasterPrivateKeyP = MK_32 () +module WireTransferIdentifierRawP = MK_32 () +module CoinSpendPublicKeyP = MK_32 () (* union *) +module CoinSpendPrivateKeyP = MK_32 () (* union *) +module TokenPublicKeyP = MK_32 () (* union *) +module PublicRefreshCoinNonceP = MK_64 () (* missing doc *) +module ReserveSignatureP = MK_64 () +module ExchangeSignatureP = MK_64 () +module MasterSignatureP = MK_64 () +module CoinSpendSignatureP = MK_64 () +module TransferSecretP = MK_64 () +module LinkSecretP = MK_64 () +module EncryptedLinkSecretP = MK_64 () + +(* TODO ? need to use/save a specific nonce for cryptographic blinding *) +(* Secret for blinding/unblinding. + An RSA blinding secret, which is basically + a 256-bit nonce, converted to Crockford `Base32`. + + type DenominationBlindingKeyP = string; *) +module DenominationBlindingKeyP = MK_32 () + +(* GNUNET_CRYPTO format *) module RsaPublicKey = struct (* libgnuutil format: https://docs.gnunet.org/doxygen/d9/dbe/structGNUNET__CRYPTO__RsaPublicKeyHeaderP.html @@ -70,6 +221,7 @@ module RsaPublicKey = struct |+ field beint16 (fun t -> t.e_len) |> sealr + (* note: this one has a dynamic sizeof *) let bin = let open Bin in record (fun header n e -> @@ -84,152 +236,9 @@ module RsaPublicKey = struct |> sealr end -let int32_size = 4 -let int64_size = 8 +(* --- Various --- *) -(* -- Time -- *) - -(* microseconds since the UNIX Epoch. UINT64_MAX represents "never" *) -module MK_TIME () = struct - type t = { v: int64 } - - (* not BE (?) *) - let bin = - let open Bin in - record (fun v -> { v }) |+ field neint64 (fun t -> t.v) |> sealr -end - -module MK_TIME_NBO () = struct - type t = { v: int64 } - - let bin = - let open Bin in - record (fun v -> { v }) |+ field beint64 (fun t -> t.v) |> sealr -end - -module TimeAbsolute = MK_TIME () -module TimeAbsoluteNBO = MK_TIME_NBO () -module TimeRelative = MK_TIME () -module TimeRelativeNBO = MK_TIME_NBO () -module Timestamp = MK_TIME () -module TimestampNBO = MK_TIME_NBO () - -(* -- Cryptographic primitives -- *) - -(* MK_BASIC_XX functor for structs like: - struct Foo { uint8t bar[XX]; } *) -module MKMK_BASIC (S : sig - val v : int -end) = -struct - type t = { v: string } - - let bin = - let open Bin in - record (fun v -> { v }) |+ field (bytes S.v) (fun t -> t.v) |> sealr -end - -module SIZE_32 = struct - let v = 32 -end - -module SIZE_64 = struct - let v = 64 -end - -module MK_BASIC_32 () = MKMK_BASIC (SIZE_32) -module MK_BASIC_64 () = MKMK_BASIC (SIZE_64) - -(* MK_XX functor for structs like: - struct FooWrap { struct Foo { uint8t bar[XX]; } } *) -module MKMK (S : sig - val v : int -end) = -struct - module H = MKMK_BASIC (S) - - type t = { v: H.t } - - let bin = - let open Bin in - record (fun v -> { v }) |+ field H.bin (fun t -> t.v) |> sealr -end - -module MK_32 () = MKMK (SIZE_32) -module MK_64 () = MKMK (SIZE_64) -(* - *) - -module ShortHashCode = MK_BASIC_32 () -module HashCode = MK_BASIC_64 () -module DenominationHash = MK_64 () -module PrivateContractHash = MK_64 () -module ExtensionsPolicyHash = MK_64 () -module MerchantWireHash = MK_64 () - -(* Hash over a full payto://-URI, including receiver-name - (and possibly BIC and other optional fields). *) -module FullPaytoHash = MK_32 () - -(* Hash over a normalized payto://-URI, including all optional - fields and also with account-part canonicalized (so no BIC). *) -module NormalizedPaytoHash = MK_32 () - -(* TODO Taler doc: missing *) -module AgeCommitmentHash = MK_64 () - -(* TODO Taler doc: missing *) -module PublicRefreshCoinNonceP = MK_64 () - -(* TODO Taler doc: missing *) -module PursePublicKey = MK_32 () - -(* TODO Taler doc: missing *) -module AuditorPublicKeyP = MK_32 () - -(* TODO - // Secret for blinding/unblinding. - // An RSA blinding secret, which is basically - // a 256-bit nonce, converted to Crockford `Base32`. - type DenominationBlindingKeyP = string; -*) -module DenominationBlindingKeyP = MK_64 () - -(* Hash over: - a) the hash of the denomination's public key, - b) an enum value identifying the cipher, and - c) cipher-dependant blinded information. - See implementation of `TALER_CoinEvHash` - in libtalerexchange for details. *) -module BlindedCoinHash = MK_64 () - -(* -- *) -module CoinPubHash = MK_64 () -module OutputCommitmentHash = MK_64 () -module ReservePublicKeyP = MK_32 () -module ReservePrivateKeyP = MK_32 () -module ReserveSignatureP = MK_64 () -module MerchantPublicKeyP = MK_32 () -module MerchantPrivateKeyP = MK_32 () -(*module MerchantSignatureP = MK_64 () *) - -module TransferPublicKeyP = MK_32 () -module TransferPrivateKeyP = MK_32 () - -(* -enum TALER_AmlDecisionState { - NORMAL, PENDING, FROZEN -}; -*) - -module AmlOfficerPublicKeyP = MK_32 () -module AmlOfficerPrivateKeyP = MK_32 () -module ExchangePublicKeyP = MK_32 () -module ExchangePrivateKeyP = MK_32 () -module ExchangeSignatureP = MK_64 () -module MasterPublicKeyP = MK_32 () -module MasterPrivateKeyP = MK_32 () -module MasterSignatureP = MK_64 () -module WireTransferIdentifierRawP = MK_BASIC_32 () +module RefreshCommitmentP = MK_64 () module UUID = struct (* uint32t value[4]; *) @@ -255,90 +264,6 @@ module WadId = struct record (fun raw -> { raw }) |+ field (bytes size) (fun t -> t.raw) |> sealr end -(* TODO not sure what to do of union, probably not needed *) -(* -union TALER_CoinSpendPublicKeyP { - uint8t eddsaPub[32]; - uint8t ecdhePub[32]; -}; -union TALER_CoinSpendPrivateKeyP { - uint8t eddsaPriv[32]; - uint8t ecdhePriv[32]; -}; -*) -module CoinSpendPublicKeyP = MK_32 () -module CoinSpendPrivateKeyP = MK_32 () -module CoinSpendSignatureP = MK_64 () - -(* TODO padding: sizeof used here (assume no padding for now) *) -(* -struct TALER_TransferSecretP { - uint8t key[sizeof (struct HashCode)]; -}; - uint8t key[sizeof (struct HashCode)]; -}; -struct TALER_EncryptedLinkSecretP { - uint8t enc[sizeof (struct TALER_LinkSecretP)]; -}; -*) -module TransferSecretP = MK_64 () -module LinkSecretP = MK_64 () -module EncryptedLinkSecretP = MK_64 () - -(* -union TALER_TokenPublicKeyP { - uint8t eddsaPub[32]; - uint8t ecdhePub[32]; -}; -*) -module TokenPublicKeyP = MK_32 () - -(* -- Signatures -- *) - -(* EccSignaturePurpose *) -module Purpose = struct - type t = { - (* This field equals the number of bytes being signed, - namely 'sizeof (struct Data)'. *) - size: int32; - (* This field is used to express the context in - which the signature is made, ensuring that a - signature cannot be lifted from one part of the protocol - to another. *) - purpose: int32; - } - - let bin = - let open Bin in - record (fun size purpose -> { size; purpose }) - |+ field beint32 (fun t -> t.size) - |+ field beint32 (fun t -> t.purpose) - |> sealr - - let make ~size purpose = { size= Int32.of_int size; purpose } - let dummy = make ~size:0 0_l - - (* helper function to make ['signature Bin.t] *) - let make_bin = - let get_size f = - let open Bin in - match Size.of_value (Size.size_of (f dummy)) with - | Dynamic _ | Unknown -> - Fmt.failwith "size_of failure: size is not Static" - | Static n -> n - in - fun code f -> make ~size:(get_size f) code |> f - - let field purpose = Bin.field bin (fun _t -> purpose) -end - -(* This is the running SHA512-hash over all - `TALER_BlindedCoinHashP` values of an array of coins. - Note that each `TALER_BlindedCoinHashP` itself - captures the hash of the corresponding denomination's - public key. *) -module HashPlanchetsP = MK_64 () - module AgeMask = struct type t = { mask: int32 } @@ -348,17 +273,27 @@ module AgeMask = struct end (* TODO - - why is the non-NBO version only used in TALER_WithdrawRequestPS? *) + - why is the non-NBO version only used in TALER_WithdrawRequestPS? + - correctly do the padding and 0-termination + - handle "invalid" values *) +(* documentation: *) (* Number of characters (plus 1 for 0-termination) for currency names. typically an ISO 4217 currency code when an alphanumeric 3-digit code is used. For regional currencies, the first character should be a "*" followed by a region-specific name (i.e. "*BRETAGNEFR"). + Currency codes are compared case-insensitively. - Currency string, left adjusted and padded with zeros. All zeros - for "invalid" values. *) + Currency string, left adjusted and padded with zeros. + All zeros for "invalid" values. + + Name of the currency, using either a three-character ISO 4217 currency + code, or a regional currency identifier between 4 and 11 characters, + consisting of ASCII alphabetic characters ("a-zA-Z"). + Should be padded to 12 bytes with 0-characters. + Currency codes are compared case-insensitively. *) let currency_len = 12 -(* TODO Taler doc: missing +(* TODO missing doc found in src/include/taler/taler_amount_lib.h *) module Amount = struct type t = { @@ -393,7 +328,46 @@ module AmountNBO = struct |> sealr end -module BlindingMasterSeed = MK_BASIC_32 () +(* -- Signatures -- *) +(* PS: Packed Signature *) + +(* EccSignaturePurpose *) +module Purpose = struct + type t = { + (* This field equals the number of bytes being signed, + namely 'sizeof (struct Data)'. *) + size: int32; + (* This field is used to express the context in + which the signature is made, ensuring that a + signature cannot be lifted from one part of the protocol + to another. *) + purpose: int32; + } + + let bin = + let open Bin in + record (fun size purpose -> { size; purpose }) + |+ field beint32 (fun t -> t.size) + |+ field beint32 (fun t -> t.purpose) + |> sealr + + let make ~size purpose = { size= Int32.of_int size; purpose } + let dummy = make ~size:0 0_l + + (* helper function to make ['signature Bin.t] + to compute [t.size], we first build a ['signature Bin.t] with a dummy purpose *) + let make_bin = + let get_size f = + let open Bin in + match Size.of_value (Size.size_of (f dummy)) with + | Dynamic _ | Unknown -> + Fmt.failwith "size_of failure: size is not Static" + | Static n -> n + in + fun code f -> make ~size:(get_size f) code |> f + + let field purpose = Bin.field bin (fun _t -> purpose) +end module WithdrawRequestPS = struct (* Purpose is #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW *) @@ -401,9 +375,7 @@ module WithdrawRequestPS = struct amount: Amount.t; fee: Amount.t; h_planchets: HashPlanchetsP.t; - (* TODO TALER doc - `TALER_BlindingMasterSecretP` in doc, but probably TALER_BlindingMasterSeed *) - blinding_seed: BlindingMasterSeed.t; + blinding_seed: BlindingMasterSecret.t; max_age_group: int32; mask: AgeMask.t; } @@ -418,7 +390,7 @@ module WithdrawRequestPS = struct |+ field Amount.bin (fun t -> t.amount) |+ field Amount.bin (fun t -> t.fee) |+ field HashPlanchetsP.bin (fun t -> t.h_planchets) - |+ field BlindingMasterSeed.bin (fun t -> t.blinding_seed) + |+ field BlindingMasterSecret.bin (fun t -> t.blinding_seed) |+ field beint32 (fun t -> t.max_age_group) |+ field AgeMask.bin (fun t -> t.mask) |> sealr @@ -533,8 +505,6 @@ module DepositConfirmationPS = struct } end -module RefreshCommitmentP = MK_64 () - module RefreshMeltCoinAffirmationPS = struct (* purpose.purpose = TALER_SIGNATURE_WALLET_COIN_MELT *) type t = { diff --git a/src/management.ml b/src/management.ml index 18118aea..6fbfc9ee 100644 --- a/src/management.ml +++ b/src/management.ml @@ -30,13 +30,13 @@ let mk_future_denom denom_secmod_sign_f let open Binary_formats in let h_denom_pub = (* TODO hash *) - let v = pub |> Types.RsaPublicKey.to_b32 in - DenominationHash.{ v= { v } } + let hash = pub |> Types.RsaPublicKey.to_b32 in + DenominationHash.{ hash } in let h_section_name = (* TODO hash *) - let v = section_name in - HashCode.{ v } + let hash = section_name in + HashCode.{ hash } in let anchor_time = TimeAbsoluteNBO.{ v= Util.ptime_to_int64_us stamp_start } @@ -75,9 +75,7 @@ let mk_future_sign_key signkey_secmod_sign_f ({ pub; sign= _; stamp_start; stamp_expire; stamp_end } : Secmod_keys.t) = let signkey_secmod_sig = let open Binary_formats in - let exchange_pub = - ExchangePublicKeyP.{ v= { v= EddsaPublicKey.to_octets pub } } - in + let exchange_pub = ExchangePublicKeyP.{ v= EddsaPublicKey.to_octets pub } in let anchor_time = TimeAbsoluteNBO.{ v= Util.ptime_to_int64_us stamp_start } in diff --git a/test/test.ml b/test/test.ml index 017c677d..8d4bd866 100644 --- a/test/test.ml +++ b/test/test.ml @@ -78,7 +78,7 @@ let () = let () = let open Binary_formats.WithdrawConfirmationPS in let str64 = String.init 64 (fun i -> Char.unsafe_chr (i + 1)) in - let dummy_t = { h_planchets= { v= { v= str64 } }; noreveal_index= 0_l } in + let dummy_t = { h_planchets= { hash= str64 }; noreveal_index= 0_l } in let size = Bin.size_of_value bin dummy_t |> Option.get in assert (size = 76);