mte/src/binary_formats.ml

466 lines
13 KiB
OCaml
Raw Normal View History

2025-10-06 23:56:10 +02:00
(* https://docs.taler.net/core/api-common.html#binary-formats
- numeric values are in network byte order (big endian) *)
2025-10-07 12:02:16 +02:00
open Include
2025-10-07 14:20:32 +02:00
let int32_size = 4
let int64_size = 8
2025-10-07 01:13:08 +02:00
2025-10-06 23:56:10 +02:00
(* -- Time -- *)
module Time = struct
module Absolute = struct
type t = { timestamp_us: int64 }
type t_nbo = { abs_value_us__: int64 }
2025-10-07 14:20:32 +02:00
let size = int64_size
2025-10-07 11:10:40 +02:00
(* not BE here? never used? *)
2025-10-06 23:56:10 +02:00
let bin =
let open Bin in
record (fun timestamp_us -> { timestamp_us })
2025-10-07 11:10:40 +02:00
|+ field neint64 (fun t -> t.timestamp_us)
2025-10-06 23:56:10 +02:00
|> sealr
2025-10-07 14:59:58 +02:00
let nboBin =
2025-10-06 23:56:10 +02:00
let open Bin in
record (fun abs_value_us__ -> { abs_value_us__ })
|+ field beint64 (fun t -> t.abs_value_us__)
|> sealr
end
module Relative = struct
type t = { timestamp_us: int64 }
type t_nbo = { rel_value_us__: int64 }
2025-10-07 14:20:32 +02:00
let size = int64_size
2025-10-06 23:56:10 +02:00
let bin =
let open Bin in
record (fun timestamp_us -> { timestamp_us })
|+ field neint64 (fun t -> t.timestamp_us)
|> sealr
2025-10-07 14:59:58 +02:00
let nboBin =
2025-10-06 23:56:10 +02:00
let open Bin in
record (fun rel_value_us__ -> { rel_value_us__ })
|+ field beint64 (fun t -> t.rel_value_us__)
|> sealr
end
end
(* -- Cryptographic primitives -- *)
2025-10-07 14:20:32 +02:00
(* MK_BASIC_XX functor for structs like:
2025-10-07 14:59:58 +02:00
struct Foo { uint8t bar[XX]; } *)
2025-10-07 14:20:32 +02:00
module MKMK_BASIC (Size : sig
val v : int
end) =
struct
type t = { v: string }
2025-10-06 23:56:10 +02:00
2025-10-07 14:20:32 +02:00
let size = Size.v
2025-10-07 12:02:16 +02:00
2025-10-06 23:56:10 +02:00
let bin =
let open Bin in
2025-10-07 14:20:32 +02:00
record (fun v -> { v }) |+ field (bytes size) (fun t -> t.v) |> sealr
2025-10-06 23:56:10 +02:00
end
2025-10-07 14:20:32 +02:00
module SIZE_32 = struct
let v = 32
end
2025-10-07 12:02:16 +02:00
2025-10-07 14:20:32 +02:00
module SIZE_64 = struct
let v = 64
2025-10-06 23:56:10 +02:00
end
2025-10-07 14:20:32 +02:00
module MK_BASIC_32 () = MKMK_BASIC (SIZE_32)
module MK_BASIC_64 () = MKMK_BASIC (SIZE_64)
2025-10-06 23:56:10 +02:00
2025-10-07 14:20:32 +02:00
(* MK_XX functor for structs like:
2025-10-07 14:59:58 +02:00
struct FooWrap { struct Foo { uint8t bar[XX]; } } *)
2025-10-07 14:20:32 +02:00
module MKMK (Size : sig
val v : int
2025-10-06 23:56:10 +02:00
end) =
struct
2025-10-07 14:20:32 +02:00
module H = MKMK_BASIC (Size)
type t = { v: H.t }
2025-10-06 23:56:10 +02:00
2025-10-07 12:02:16 +02:00
let size = H.size
2025-10-06 23:56:10 +02:00
let bin =
let open Bin in
2025-10-07 14:20:32 +02:00
record (fun v -> { v }) |+ field H.bin (fun t -> t.v) |> sealr
2025-10-06 23:56:10 +02:00
end
2025-10-07 14:20:32 +02:00
module MK_32 () = MKMK (SIZE_32)
module MK_64 () = MKMK (SIZE_64)
(* - *)
module ShortHashCode = MK_BASIC_32 ()
module HashCode = MK_BASIC_64 ()
2025-10-07 14:59:58 +02:00
module DenominationHash = MK_64 ()
module PrivateContractHash = MK_64 ()
module ExtensionsPolicyHash = MK_64 ()
module MerchantWireHash = MK_64 ()
2025-10-06 23:56:10 +02:00
(* Hash over a full payto://-URI, including receiver-name
(and possibly BIC and other optional fields). *)
2025-10-07 14:59:58 +02:00
module FullPaytoHash = MK_32 ()
2025-10-06 23:56:10 +02:00
(* Hash over a normalized payto://-URI, including all optional
fields and also with account-part canonicalized (so no BIC). *)
2025-10-07 14:59:58 +02:00
module NormalizedPaytoHash = MK_32 ()
2025-10-06 23:56:10 +02:00
(* Hash over:
a) the hash of the denomination's public key,
b) an enum value identifying the cipher, and
c) cipher-dependant blinded information.
2025-10-07 14:59:58 +02:00
See implementation of `TALER_CoinEvHash`
2025-10-06 23:56:10 +02:00
in libtalerexchange for details. *)
2025-10-07 14:59:58 +02:00
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 TransfertPublicKeyP = MK_32 ()
module TransfertPrivateKeyP = MK_32 ()
2025-10-07 01:13:08 +02:00
(*
enum TALER_AmlDecisionState {
NORMAL, PENDING, FROZEN
};
*)
2025-10-07 14:59:58 +02:00
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 WireTransfertIdentifierRawP = MK_BASIC_32 ()
2025-10-07 01:13:08 +02:00
module UUID = struct
2025-10-07 14:59:58 +02:00
(* uint32t value[4]; *)
2025-10-07 01:13:08 +02:00
type t = { value: string }
2025-10-07 14:20:32 +02:00
let size = 4 * int32_size
2025-10-07 01:13:08 +02:00
let bin =
let open Bin in
record (fun value -> { value })
2025-10-07 14:20:32 +02:00
|+ field (bytes size) (fun t -> t.value)
2025-10-07 01:13:08 +02:00
|> sealr
end
2025-10-07 14:59:58 +02:00
module WadId = struct
(* uint32t value[6]; *)
2025-10-07 01:13:08 +02:00
type t = { raw: string }
2025-10-07 14:20:32 +02:00
let size = 6 * int32_size
2025-10-07 01:13:08 +02:00
let bin =
let open Bin in
2025-10-07 14:20:32 +02:00
record (fun raw -> { raw }) |+ field (bytes size) (fun t -> t.raw) |> sealr
2025-10-07 01:13:08 +02:00
end
2025-10-07 11:10:40 +02:00
(* TODO not sure what to do of union, probably not needed *)
2025-10-07 01:13:08 +02:00
(*
union TALER_CoinSpendPublicKeyP {
2025-10-07 14:59:58 +02:00
uint8t eddsaPub[32];
uint8t ecdhePub[32];
2025-10-07 01:13:08 +02:00
};
union TALER_CoinSpendPrivateKeyP {
2025-10-07 14:59:58 +02:00
uint8t eddsaPriv[32];
uint8t ecdhePriv[32];
2025-10-07 01:13:08 +02:00
};
*)
2025-10-07 14:59:58 +02:00
module CoinSpendPublicKeyP = MK_32 ()
module CoinSpendPrivateKeyP = MK_32 ()
module CoinSpendSignatureP = MK_64 ()
2025-10-07 01:13:08 +02:00
(* TODO padding: sizeof used here (assume no padding for now) *)
(*
struct TALER_TransferSecretP {
2025-10-07 14:59:58 +02:00
uint8t key[sizeof (struct GNUNET_HashCode)];
2025-10-07 01:13:08 +02:00
};
2025-10-07 14:59:58 +02:00
uint8t key[sizeof (struct GNUNET_HashCode)];
2025-10-07 01:13:08 +02:00
};
struct TALER_EncryptedLinkSecretP {
2025-10-07 14:59:58 +02:00
uint8t enc[sizeof (struct TALER_LinkSecretP)];
2025-10-07 01:13:08 +02:00
};
*)
2025-10-07 14:59:58 +02:00
module TransfertSecretP = MK_64 ()
module LinkSecretP = MK_64 ()
module EncryptedLinkSecretP = MK_64 ()
2025-10-07 01:13:08 +02:00
(*
union TALER_TokenPublicKeyP {
2025-10-07 14:59:58 +02:00
uint8t eddsaPub[32];
uint8t ecdhePub[32];
2025-10-07 01:13:08 +02:00
};
*)
2025-10-07 14:59:58 +02:00
module TokenPublicKeyP = MK_32 ()
2025-10-07 01:13:08 +02:00
2025-10-06 23:56:10 +02:00
(* -- Signatures -- *)
2025-10-07 12:02:16 +02:00
(* EccSignaturePurpose *)
module Purpose = struct
2025-10-06 23:56:10 +02:00
type t = {
(* This field equals the number of bytes being signed,
namely 'sizeof (struct Data)'. *)
2025-10-07 01:13:08 +02:00
size: int32;
2025-10-06 23:56:10 +02:00
(* 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. *)
2025-10-07 01:13:08 +02:00
purpose: int32;
2025-10-06 23:56:10 +02:00
}
2025-10-07 14:20:32 +02:00
let size = 2 * int32_size
2025-10-07 12:02:16 +02:00
2025-10-07 01:13:08 +02:00
let bin =
2025-10-06 23:56:10 +02:00
let open Bin in
record (fun size purpose -> { size; purpose })
|+ field beint32 (fun t -> t.size)
|+ field beint32 (fun t -> t.purpose)
|> sealr
2025-10-07 12:02:16 +02:00
2025-10-07 14:20:32 +02:00
let make ~size purpose = { size= Int32.of_int size; purpose }
2025-10-06 23:56:10 +02:00
end
2025-10-07 11:10:40 +02:00
(* 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. *)
2025-10-07 14:59:58 +02:00
module HashPlanchetsP = MK_64 ()
2025-10-07 11:10:40 +02:00
(* Binary representation of the age groups.
The bits set in the mask mark the edges at the beginning of a next age
group. F.e. for the age groups
0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-*
the following bits are set:
31 24 16 8 0
| | | | |
oooooooo oo1oo1o1 o1o1o1o1 ooooooo1
A value of 0 means that the exchange does not support the extension for
age-restriction. *)
2025-10-07 14:59:58 +02:00
module AgeMask = struct
2025-10-07 11:10:40 +02:00
type t = { mask: int32 }
2025-10-07 14:20:32 +02:00
let size = int32_size
2025-10-07 11:10:40 +02:00
let bin =
let open Bin in
record (fun mask -> { mask }) |+ field beint32 (fun t -> t.mask) |> sealr
end
2025-10-07 11:45:15 +02:00
(* TODO TALER doc
should be in doc
2025-10-07 14:59:58 +02:00
found in src/include/taler/talerAmountLib.h
2025-10-07 11:10:40 +02:00
2025-10-07 11:45:15 +02:00
why is the non-NBO version only used in TALER_WithdrawRequestPS?
GNUNET_PACKED?
can be set to "invalid" if currency = all 0 *)
(*
/**
* @brief Number of characters (plus 1 for 0-termination) we use to
* represent currency names (i.e. EUR, USD, etc.). We use 8+4 for
* alignment in the `struct TALER_Amount`. The amount is 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").
*/
struct TALER_AmountNBO
{
/**
* Value in the main currency, in NBO.
*/
2025-10-07 14:59:58 +02:00
uint64t value GNUNET_PACKED;
2025-10-07 11:45:15 +02:00
/**
* Fraction (integer multiples of #TALER_AMOUNT_FRAC_BASE), in NBO.
*/
2025-10-07 14:59:58 +02:00
uint32t fraction GNUNET_PACKED;
2025-10-07 11:45:15 +02:00
/**
* Type of the currency being represented.
*/
char currency[TALER_CURRENCY_LEN];
};
struct TALER_Amount
{
/**
* Value (numerator of fraction)
*/
2025-10-07 14:59:58 +02:00
uint64t value;
2025-10-07 11:45:15 +02:00
/**
* Fraction (integer multiples of #TALER_AMOUNT_FRAC_BASE).
*/
2025-10-07 14:59:58 +02:00
uint32t fraction;
2025-10-07 11:45:15 +02:00
/**
* Currency string, left adjusted and padded with zeros. All zeros
* for "invalid" values.
*/
char currency[TALER_CURRENCY_LEN];
};
*)
let currency_len = 12
module Amount = struct
type t = {
value: int64;
fraction: int32;
currency: string;
}
2025-10-07 14:20:32 +02:00
let size = int64_size + int32_size + currency_len
2025-10-07 11:45:15 +02:00
(* TODO BE here? *)
let bin =
let open Bin in
record (fun value fraction currency -> { value; fraction; currency })
|+ field beint64 (fun t -> t.value)
|+ field beint32 (fun t -> t.fraction)
|+ field (bytes currency_len) (fun t -> t.currency)
|> sealr
end
module AmountNBO = struct
type t = {
value: int64;
fraction: int32;
currency: string;
}
2025-10-07 14:20:32 +02:00
let size = int64_size + int32_size + currency_len
2025-10-07 11:45:15 +02:00
let bin =
let open Bin in
record (fun value fraction currency -> { value; fraction; currency })
|+ field beint64 (fun t -> t.value)
|+ field beint32 (fun t -> t.fraction)
|+ field (bytes currency_len) (fun t -> t.currency)
|> sealr
end
2025-10-07 11:10:40 +02:00
2025-10-07 14:59:58 +02:00
module BlindingMasterSeed = MK_BASIC_32 ()
2025-10-07 14:20:32 +02:00
2025-10-07 11:10:40 +02:00
(* Format used for to generate the signature on a request to withdraw
coins from a reserve. *)
2025-10-07 14:59:58 +02:00
module WithdrawRequestPs = struct
2025-10-07 14:20:32 +02:00
(* Purpose is #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW *)
2025-10-07 11:10:40 +02:00
type t = {
(* Amount to withdraw, excluding fees, i.e.
the total sum of the denominations of the coins.
Note that the reserve must have a value of at least amount+fee. *)
amount: Amount.t;
(* Total fee for the withdrawal.
Note that the reserve must have a value of at least amount+fee. *)
fee: Amount.t;
(* This is the running SHA512-hash over all
`TALER_BlindedCoinHashP` values of the coins.
Note that each `TALER_BlindedCoinHashP` itself
captures the hash of the corresponding denomination's
public key.
2025-10-07 14:59:58 +02:00
If maxAge was set in the withdraw request, there will be
2025-10-07 11:10:40 +02:00
n*κ many such values. The iteration MUST be first over
all coins belonging to κ index=0, then all coins
to κ index=1 etc:
h[0][0]h[0][n-1]h[1][0]h[1][n-1] h[κ-1][0]h[κ-1][n-1]
Note also that this value is required for /recoup and
-- in case of a withdraw request with required age proof --
in the subsequent call to /reveal-withdraw *)
2025-10-07 14:59:58 +02:00
h_planchets: HashPlanchetsP.t;
2025-10-07 11:10:40 +02:00
(* The master seed that was used in the call to /blinding-prepare blinding,
or all zeros, if no denomination of cipher type Clause-Schnorr is used. *)
2025-10-07 11:45:15 +02:00
(* TODO TALER doc
`TALER_BlindingMasterSecretP` in doc, but probably TALER_BlindingMasterSeed *)
2025-10-07 14:59:58 +02:00
blinding_seed: BlindingMasterSeed.t;
(* If age restriction proof is required, the maximum age Group_
2025-10-07 11:10:40 +02:00
to commit to, 0 otherwise. Note that in this case, all
denominations for all coins MUST support age restriction.
Also note that this is not an age (in years), but the age group
(an index) according to list of age groups in the configuration
2025-10-07 14:59:58 +02:00
of the exchange. See TALER_GetMaxGroup() how to calculate
2025-10-07 11:10:40 +02:00
the age group to a given age (in years). *)
max_age_group: int32;
(* The age groups as configured for the exchange, represented as a mask.
If max_age_group is > 0, the mask MUST be non-zero, too. *)
2025-10-07 14:59:58 +02:00
mask: AgeMask.t;
2025-10-07 11:10:40 +02:00
}
2025-10-07 14:20:32 +02:00
let size =
Purpose.size
+ (2 * Amount.size)
2025-10-07 14:59:58 +02:00
+ HashPlanchetsP.size
+ BlindingMasterSeed.size
2025-10-07 14:20:32 +02:00
+ int32_size
2025-10-07 14:59:58 +02:00
+ AgeMask.size
2025-10-07 14:20:32 +02:00
let purpose = Purpose.make ~size Taler_signatures.wallet_reserve_withdraw
2025-10-07 11:10:40 +02:00
let bin =
let open Bin in
record
2025-10-07 14:59:58 +02:00
(fun _urpose amount fee h_planchets blinding_seed max_age_group mask ->
2025-10-07 14:20:32 +02:00
{ amount; fee; h_planchets; blinding_seed; max_age_group; mask })
|+ field Purpose.bin (Fun.const purpose)
2025-10-07 11:10:40 +02:00
|+ field Amount.bin (fun t -> t.amount)
|+ field Amount.bin (fun t -> t.fee)
2025-10-07 14:59:58 +02:00
|+ field HashPlanchetsP.bin (fun t -> t.h_planchets)
|+ field BlindingMasterSeed.bin (fun t -> t.blinding_seed)
2025-10-07 11:10:40 +02:00
|+ field beint32 (fun t -> t.max_age_group)
2025-10-07 14:59:58 +02:00
|+ field AgeMask.bin (fun t -> t.mask)
2025-10-07 11:10:40 +02:00
|> sealr
end
2025-10-07 11:45:15 +02:00
2025-10-07 14:59:58 +02:00
module WithdrawConfirmationPs = struct
2025-10-07 12:02:16 +02:00
(* Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_WITHDRAW.
Signed by a `struct TALER_ExchangePrivateKeyP` using EdDSA. *)
2025-10-07 11:45:15 +02:00
type t = {
(* Commitment made in the /withdraw request.
Also needed for the /reveal-withdraw endpoint (in case
of required proof of age restriction) and for /recoup *)
(* TODO TALER doc
missing TALER_HashBlindedPlanchetsP*)
2025-10-07 14:59:58 +02:00
h_planchets: HashPlanchetsP.t;
2025-10-07 11:45:15 +02:00
(* If proof of age restriction is not required for to this
2025-10-07 14:59:58 +02:00
withdrawal, (i.e. maxAge was not set during the request)
2025-10-07 11:45:15 +02:00
MUST be 0xFFFFFFFF.
Otherwise (i.e. proof of age restriction required):
index that the client will not have to reveal, in NBO,
MUST be smaller than #TALER_CNC_KAPPA. *)
noreveal_index: int32;
}
2025-10-07 14:59:58 +02:00
let size = Purpose.size + HashPlanchetsP.size + int32_size
2025-10-07 14:20:32 +02:00
let purpose = Purpose.make ~size Taler_signatures.exchange_confirm_withdraw
2025-10-07 12:02:16 +02:00
2025-10-07 11:45:15 +02:00
let bin =
let open Bin in
2025-10-07 14:59:58 +02:00
record (fun _urpose h_planchets noreveal_index ->
2025-10-07 12:02:16 +02:00
{ h_planchets; noreveal_index })
|+ field Purpose.bin (Fun.const purpose)
2025-10-07 14:59:58 +02:00
|+ field HashPlanchetsP.bin (fun t -> t.h_planchets)
2025-10-07 11:45:15 +02:00
|+ field beint32 (fun t -> t.noreveal_index)
|> sealr
end