2026-02-27 17:49:54 +01:00
|
|
|
open Syntax
|
|
|
|
|
|
|
|
|
|
let keys content =
|
2026-02-27 18:15:09 +01:00
|
|
|
let open Api in
|
|
|
|
|
let open ExchangeKeysResponse in
|
2026-02-27 17:49:54 +01:00
|
|
|
let* v = Api.decode jsont content in
|
|
|
|
|
let* () =
|
|
|
|
|
if
|
|
|
|
|
Libtool_version.is_compatible
|
|
|
|
|
~implementation:Libtool_version.mte_protocol_version v.version
|
|
|
|
|
then Ok ()
|
|
|
|
|
else Fmt.error "version incompatible"
|
|
|
|
|
in
|
|
|
|
|
|
2026-02-27 18:15:09 +01:00
|
|
|
(* TODO need hash over json..
|
|
|
|
|
validate ExchangeWireAccount *)
|
|
|
|
|
let* () =
|
|
|
|
|
let open AggregateTransferFee in
|
|
|
|
|
v.wire_fees
|
|
|
|
|
|> String_map.bindings
|
|
|
|
|
|> List.concat_map (fun (wire_method, l) ->
|
|
|
|
|
List.map (fun item -> (wire_method, item)) l)
|
|
|
|
|
|> list_iter
|
|
|
|
|
(fun
|
|
|
|
|
(wire_method, { wire_fee; closing_fee; start_date; end_date; sig_ })
|
|
|
|
|
->
|
|
|
|
|
let open Signatures.MasterWireFee in
|
|
|
|
|
verify v.master_public_key sig_
|
|
|
|
|
{
|
|
|
|
|
h_wire_method= Hash.Cstring.H64.hash wire_method;
|
|
|
|
|
start_date;
|
|
|
|
|
end_date;
|
|
|
|
|
wire_fee;
|
|
|
|
|
closing_fee;
|
|
|
|
|
})
|
|
|
|
|
in
|
|
|
|
|
|
2026-02-27 18:30:01 +01:00
|
|
|
let* () =
|
|
|
|
|
let open SignKey in
|
|
|
|
|
v.signkeys
|
|
|
|
|
|> list_iter
|
|
|
|
|
(fun { key; stamp_start; stamp_expire; stamp_end; master_sig } ->
|
|
|
|
|
let open Signatures.ExchangeSigningKeyValidity in
|
|
|
|
|
verify v.master_public_key master_sig
|
|
|
|
|
{
|
|
|
|
|
R.start= stamp_start;
|
|
|
|
|
expire= stamp_expire;
|
|
|
|
|
end_= stamp_end;
|
|
|
|
|
signkey_pub= key;
|
|
|
|
|
})
|
|
|
|
|
in
|
|
|
|
|
|
2026-02-27 18:37:37 +01:00
|
|
|
let* () =
|
|
|
|
|
let opt =
|
|
|
|
|
List.find_opt (fun sk -> sk.SignKey.key = v.exchange_pub) v.signkeys
|
|
|
|
|
in
|
|
|
|
|
match opt with
|
|
|
|
|
| None -> Fmt.error "exchange_pub is not in signkeys list"
|
|
|
|
|
| Some sk ->
|
|
|
|
|
let sk = SignKey.to_signkey sk in
|
|
|
|
|
(* todo will need to fake time to validate expired data *)
|
|
|
|
|
let now = Timestamp.of_ptime (Ptime_clock.now ()) in
|
|
|
|
|
if Signkey.is_valid_at ~timestamp:now sk then Ok ()
|
|
|
|
|
else Fmt.error "exchange_pub is not valid at the current time"
|
|
|
|
|
in
|
2026-02-27 18:40:20 +01:00
|
|
|
let* () =
|
|
|
|
|
let open Signatures.ExchangeKeySet in
|
|
|
|
|
verify v.exchange_pub v.exchange_sig
|
|
|
|
|
R.
|
|
|
|
|
{
|
|
|
|
|
list_issue_date= v.list_issue_date;
|
|
|
|
|
hc= Denomination.hash_over_master_sigs v.denominations;
|
|
|
|
|
}
|
|
|
|
|
in
|
2026-02-27 18:30:01 +01:00
|
|
|
|
2026-02-27 18:55:31 +01:00
|
|
|
let last_issue_date = ref Timestamp.zero in
|
|
|
|
|
let* () =
|
|
|
|
|
(* TODO parse CS *)
|
|
|
|
|
v.denominations
|
|
|
|
|
|> list_iter
|
|
|
|
|
(fun
|
|
|
|
|
(DenomGroup.Rsa
|
|
|
|
|
RsaDenomGroup.
|
|
|
|
|
{
|
|
|
|
|
denoms;
|
|
|
|
|
value;
|
|
|
|
|
fee_withdraw;
|
|
|
|
|
fee_deposit;
|
|
|
|
|
fee_refresh;
|
|
|
|
|
fee_refund;
|
|
|
|
|
})
|
|
|
|
|
->
|
|
|
|
|
let open RsaDenom in
|
|
|
|
|
denoms
|
|
|
|
|
|> list_iter
|
|
|
|
|
(fun
|
|
|
|
|
{
|
|
|
|
|
rsa_pub;
|
|
|
|
|
master_sig;
|
|
|
|
|
stamp_start;
|
|
|
|
|
stamp_expire_withdraw;
|
|
|
|
|
stamp_expire_deposit;
|
|
|
|
|
stamp_expire_legal;
|
|
|
|
|
lost= _;
|
|
|
|
|
}
|
|
|
|
|
->
|
|
|
|
|
last_issue_date := Timestamp.max !last_issue_date stamp_start;
|
|
|
|
|
let denom_hash =
|
|
|
|
|
DenominationHash.hash
|
|
|
|
|
(Crypto.RsaPublicKey.to_octets rsa_pub)
|
|
|
|
|
in
|
|
|
|
|
let open Signatures.DenominationKeyValidity in
|
|
|
|
|
verify v.master_public_key master_sig
|
|
|
|
|
{
|
|
|
|
|
R.master= v.master_public_key;
|
|
|
|
|
start= stamp_start;
|
|
|
|
|
expire_withdraw= stamp_expire_withdraw;
|
|
|
|
|
expire_spend= stamp_expire_deposit;
|
|
|
|
|
expire_legal= stamp_expire_legal;
|
|
|
|
|
value;
|
|
|
|
|
fee_withdraw;
|
|
|
|
|
fee_deposit;
|
|
|
|
|
fee_refresh;
|
|
|
|
|
fee_refund;
|
|
|
|
|
denom_hash;
|
|
|
|
|
}))
|
|
|
|
|
in
|
|
|
|
|
let* () =
|
|
|
|
|
if !last_issue_date = v.list_issue_date then Ok ()
|
|
|
|
|
else Fmt.error "list_issue_date is wrong"
|
|
|
|
|
in
|
|
|
|
|
|
2026-02-27 17:49:54 +01:00
|
|
|
(* validate global-fees *)
|
2026-02-27 18:55:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-27 17:49:54 +01:00
|
|
|
(* validate list_issue_date *)
|
|
|
|
|
(* validate auditors *)
|
|
|
|
|
Ok ()
|
|
|
|
|
|
|
|
|
|
(* --- *)
|
|
|
|
|
open Cmdliner
|
|
|
|
|
open Cmdliner.Term.Syntax
|
|
|
|
|
|
|
|
|
|
let keys_cmd =
|
|
|
|
|
let input =
|
|
|
|
|
let doc = "input file" in
|
|
|
|
|
Arg.(required & opt (some filepath) None & info [ "i"; "input" ] ~doc)
|
|
|
|
|
in
|
|
|
|
|
let doc = "validate a /keys JSON response" in
|
|
|
|
|
Cmd.make (Cmd.info "keys" ~doc)
|
|
|
|
|
@@
|
|
|
|
|
let+ input = input in
|
|
|
|
|
let* content =
|
|
|
|
|
Result.bind (Fpath.of_string input) Bos.OS.File.read |> unwrap_err_msg
|
|
|
|
|
in
|
|
|
|
|
keys content
|
|
|
|
|
|
|
|
|
|
let cli =
|
|
|
|
|
let info =
|
|
|
|
|
let doc = "Testing tool to validate exchange responses" in
|
|
|
|
|
Cmd.info "validate" ~doc
|
|
|
|
|
in
|
|
|
|
|
Cmd.group info [ keys_cmd ]
|
|
|
|
|
|
|
|
|
|
let main () = Cmd.eval_result cli
|
|
|
|
|
let () = if !Sys.interactive then () else exit (main ())
|