diff --git a/data/eddsa_master_offline b/data/eddsa_master_offline
new file mode 100644
index 00000000..3e6eca77
--- /dev/null
+++ b/data/eddsa_master_offline
@@ -0,0 +1 @@
+mԯ9W��]w�L�y�	��[�����\㘊Ug(
\ No newline at end of file
diff --git a/src/amount.ml b/src/amount.ml
index 563fc982..d208cfde 100644
--- a/src/amount.ml
+++ b/src/amount.ml
@@ -88,3 +88,24 @@ let of_string =
       (char '.' *> parse_int32)
   in
   fun s -> parse_string ~consume:Consume.All parse_t s |> Result.join
+
+let jsont = Jsont.of_of_string ~kind:"Amount" of_string ~enc:to_string
+let currency_len = 12
+
+let bin =
+  let open Bin in
+  record (fun _value _fraction _currency ->
+      (* no need to decode amount? *)
+      assert false)
+  |+ field neint64 (fun t -> t.value)
+  |+ field neint32 (fun t -> t.fraction)
+  |+ field (bytes currency_len) (fun t -> t.currency)
+  |> sealr
+
+let bin_nbo =
+  let open Bin in
+  record (fun _value _fraction _currency -> assert false)
+  |+ field beint64 (fun t -> t.value)
+  |+ field beint32 (fun t -> t.fraction)
+  |+ field (bytes currency_len) (fun t -> t.currency)
+  |> sealr
diff --git a/src/amount.mli b/src/amount.mli
index 180d74dd..e2572401 100644
--- a/src/amount.mli
+++ b/src/amount.mli
@@ -19,3 +19,9 @@ val make :
 val pp : Format.formatter -> t -> unit
 val to_string : t -> string
 val of_string : string -> (t, string) result
+val currency_len : int
+val jsont : t Jsont.t
+
+(* only for encoding *)
+val bin : t Bin.t
+val bin_nbo : t Bin.t
diff --git a/src/api.ml b/src/api.ml
index 988c2e4d..eec54a6f 100644
--- a/src/api.ml
+++ b/src/api.ml
@@ -29,23 +29,33 @@ module ErrorDetail = struct
     |> Jsont.Object.finish
 end
 
-module HashCode = struct
+module HashCode : sig
+  type t
+
+  val hash : string -> t
+  val jsont : t Jsont.t
+end = struct
   type t = B32.t
 
+  let hash s =
+    let open Digestif.SHA256 in
+    s |> digest_string |> to_raw_string
+
   let jsont = Jsont.of_of_string ~kind:"HashCode" B32.decode ~enc:B32.encode
 end
 
-module Amount = struct
-  include Amount
-
-  let jsont = Jsont.of_of_string ~kind:"Amount" of_string ~enc:to_string
-end
-
 module Timestamp = struct
   type t =
     | Seconds of float
     | Never
 
+  (* TODO time
+     - time.ml
+     - check this
+     - what value for never?
+     - better way for conversion to bin_types? *)
+  let to_int64 t = match t with Never -> 0_L | Seconds v -> Int64.of_float v
+  let to_ptime t = Util.ptime_of_int64 @@ to_int64 t
   let of_ptime p = Seconds (Ptime.to_float_s p)
 
   let number_or_never_jsont =
diff --git a/src/bin_type.ml b/src/bin_type.ml
index c2a0ffc6..9660064f 100644
--- a/src/bin_type.ml
+++ b/src/bin_type.ml
@@ -65,13 +65,15 @@ module INT64 = struct
 
   let bin = Bin.neint64
   let of_ptime v = Util.ptime_to_int64 v
+  let of_int64 i = i
 end
 
 module INT64_NBO = struct
   type t = int64
 
   let bin = Bin.beint64
-  let of_ptime = Util.ptime_to_int64
+  let of_ptime v = Util.ptime_to_int64 v
+  let of_int64 i = i
 end
 
 (* -- Time -- *)
@@ -80,6 +82,7 @@ module type Time_S = sig
 
   val bin : t Bin.t
   val of_ptime : Ptime.t -> t
+  val of_int64 : int64 -> t
 end
 
 module TimeAbsolute : Time_S = INT64
@@ -232,67 +235,14 @@ module AgeMask = struct
     record (fun mask -> { mask }) |+ field beint32 (fun t -> t.mask) |> sealr
 end
 
-(* TODO
-   - 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.
-
-   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 missing doc
-   found in src/include/taler/taler_amount_lib.h *)
-module AmountP = struct
-  type t = {
-    value: int64;
-    fraction: int32;
-    currency: string;
-  }
-
-  (* 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;
-  }
-
-  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
-
 (* TODO keep this?
    some of those are actuall ecdhe, or union of eddsa|ecdhe *)
 module Aliases = struct
-  (* TODO add Amount.bin *)
-  module Amount = AmountP
+  module AmountNBO = struct
+    type t = Amount.t
+
+    let bin = Amount.bin_nbo
+  end
 
   (* - Keys - *)
   module PursePublicKey = EddsaPublicKey
diff --git a/src/crypto.ml b/src/crypto.ml
index b6e99c94..1e2f7e2c 100644
--- a/src/crypto.ml
+++ b/src/crypto.ml
@@ -32,6 +32,7 @@ module EddsaPrivateKey = struct
 
   type t = priv
 
+  let pub_of_priv = pub_of_priv
   let to_octets t = priv_to_octets t
   let of_octets t = priv_of_octets t |> Result.get_ok
   let bin = Bin.map (Bin.bytes 32) of_octets to_octets
diff --git a/tools/offline.ml b/tools/offline.ml
index c127e643..c76c3651 100644
--- a/tools/offline.ml
+++ b/tools/offline.ml
@@ -12,6 +12,66 @@ let download ~base_url ~output =
   let wget = Cmd.(v "wget" % "-O" % output % uri) in
   OS.Cmd.run wget
 
+let setup ~output =
+  let output = Fpath.v output in
+  let () = Mirage_crypto_rng_unix.use_default () in
+  let priv, _pub = Mirage_crypto_ec.Ed25519.generate () in
+  let priv_data = Mirage_crypto_ec.Ed25519.priv_to_octets priv in
+  Bos.OS.File.write output priv_data
+
+let _denom_signature master_priv master_pub
+    Api.FutureDenom.
+      {
+        section_name= _;
+        value;
+        stamp_start;
+        stamp_expire_withdraw;
+        stamp_expire_deposit;
+        stamp_expire_legal;
+        denom_pub;
+        fee_withdraw;
+        fee_deposit;
+        fee_refresh;
+        fee_refund= _;
+        (* TODO check sigs *)
+        denom_secmod_sig= _;
+      } =
+  let pub_octets =
+    let denom_pub =
+      match denom_pub with
+      | Rsa v -> v
+      | CS _ -> Fmt.failwith "CS key types are not supported."
+    in
+    let pub = denom_pub.Api.RsaDenominationKey.rsa_pub in
+    Crypto.RsaPublicKey.to_octets pub
+  in
+  let h_denom_pub = Api.HashCode.hash pub_octets in
+  let master_sig =
+    let open Bin_type in
+    let open Bin_signature.DenominationKeyValidityPS in
+    (* TODO time *)
+    let of_time ts : TimeAbsoluteNBO.t =
+      TimeAbsoluteNBO.of_int64 @@ Api.Timestamp.to_int64 ts
+    in
+    let denom_key_validity =
+      {
+        master= master_pub;
+        start= of_time stamp_start;
+        expire_withdraw= of_time stamp_expire_withdraw;
+        expire_spend= of_time stamp_expire_deposit;
+        expire_legal= of_time stamp_expire_legal;
+        value;
+        fee_withdraw;
+        fee_deposit;
+        fee_refresh;
+        denom_hash= DenominationHash.hash pub_octets;
+      }
+    in
+    let data = Bin.to_string bin denom_key_validity in
+    Crypto.EddsaSignature.sign ~key:master_priv data
+  in
+  Api.DenomSignature.{ h_denom_pub; master_sig }
+
 let sign ~input ~output ~master_key =
   let open Syntax in
   let open Bos.OS in
@@ -31,6 +91,24 @@ let sign ~input ~output ~master_key =
 open Cmdliner
 open Cmdliner.Term.Syntax
 
+let setup_cmd =
+  let doc = "Generate offline master keys" in
+  let output =
+    let doc = "Output file for the generated private Eddsa key." in
+    Arg.(
+      value
+      & opt filepath "data/eddsa_master_offline"
+      & info [ "o"; "output" ] ~doc)
+  in
+  Cmd.make (Cmd.info "setup" ~version:"%%VERSION%%" ~doc)
+  @@
+  let+ output = output in
+  match setup ~output with
+  | Error (`Msg err) ->
+      Fmt.epr "Setup failure: %s@." err;
+      exit 1
+  | Ok () -> ()
+
 let download_cmd =
   let doc = "Downloads /management/keys/" in
   let man =
@@ -82,6 +160,7 @@ let sign_cmd =
       exit 1
   | Ok () -> ()
 
-let main () = Cmd.eval download_cmd
-let _main () = Cmd.eval sign_cmd
+let main () = Cmd.eval setup_cmd
+let _main () = Cmd.eval download_cmd
+let __main () = Cmd.eval sign_cmd
 let () = if !Sys.interactive then () else exit (main ())
