[proxy] github.com← back | site home | direct (HTTPS) ↗ | proxy home | ◑ dark◐ light

GitHub - Sturdy-Statistics/roughtime-client: CLI client for roughtime protocol

Sturdy-Statistics

roughtime-client (Clojure)

A tiny Clojure CLI client for Roughtime servers. It crafts a request, verifies the response, and prints the verified time and skew.

While this may be used as a CLI client, its primary purpose is to demonstrate how to make Roughtime requests in client applications.

Quick start

Requires the Clojure CLI (clj / clojure).

# Query a named server from the built-in ecosystem:
clj -M:run :server-name SturdyStatistics

# Verbose (pretty-print request/response frames):
clj -M:run :server-name int08h :print-request :print-response

# Chain a request through known servers
clj -M:run :chain

Example output:

{:skew 0,
 :online-key-expires-in "∞",
 :maxt 18446744073709551615,
 :midp 1761408075,
 :local-time 1761408075}

You can also pass explicit server parameters (no ecosystem lookup):

clj -M:run :address "roughtime.int08h.com:2002" :public-key "AW5uAoTSTDfG5NfY1bTh08GUnOqlRb+HVhbJ3ODJvsE=" :version-no "0x8000000c" :protocol "udp"

CLI options

These options are parsed/validated via babashka.cli. Defaults:

{:protocol "udp"
 :msg-size 1024
 :print-request false
 :print-response false}
Option Alias Type Default Description
:server-name :s string Name from the built-in ecosystem (see below).
:address :a string host:port (IPv4/IPv6 or DNS), e.g. roughtime.int08h.com:2002.
:protocol :p string udp Transport protocol.
:public-key :k base64 Server’s Ed25519 public key (base64).
:version-no :V string Roughtime version (integer or hex), e.g. "0x8000000c".
:msg-size :n long 1024 Pad messages to this size (bytes).
:print-request boolean false Pretty print the serialized request map/frame.
:print-response boolean false Pretty print the server response (CERT, SREP, PATH, etc.).

Selection logic

  • If :server-name is provided, the client loads connection parameters from the ecosystem (below) and applies CLI overrides (e.g., :protocol, :msg-size).
  • Otherwise, it expects at least :address, :public-key, and :version-no.

Built-in ecosystem

A small curated set of known servers:

{
 "Cloudflare"
 {:name "Cloudflare-Roughtime-2"
  :version "IETF-Roughtime"
  :version-no "0b000080"
  :msg-size 1012
  :public-key "0GD7c3yP8xEc4Zl2zeuN2SlLvDVVocjsPSL8/Rl/7zg="
  :addresses [{:protocol "udp" :address "roughtime.cloudflare.com:2003"}]}

 "int08h"
 {:name "int08h-Roughtime"
  :version "IETF-Roughtime"
  :version-no "0c000080"
  :msg-size 1024
  :public-key "AW5uAoTSTDfG5NfY1bTh08GUnOqlRb+HVhbJ3ODJvsE="
  :addresses [{:protocol "udp" :address "roughtime.int08h.com:2002"}]}

 "roughtime.se"
 {:name "roughtime.se"
  :version "IETF-Roughtime"
  :version-no "0c000080"
  :msg-size 1024
  :public-key "S3AzfZJ5CjSdkJ21ZJGbxqdYP/SoE8fXKY0+aicsehI="
  :addresses [{:protocol "udp" :address "roughtime.se:2002"}]}

 "TXRyan"
 {
  :name "time.txryan.com"
  :version "Google-Roughtime"
  :version-no 0x00
  :supported-versions nil
  :msg-size 1024
  :public-key-type "ed25519"
  :public-key "iBVjxg/1j7y1+kQUTBYdTabxCppesU/07D4PMDJk2WA="
  :addresses [ {:protocol "udp" :address "time.txryan.com:2002"}]}

 "SturdyStatistics"
 {
  :name "Sturdy-Statistics"
  :version "IETF-Roughtime"
  :version-no 0x8000000c
  :supported-versions nil
  :msg-size 1024
  :public-key-type "ed25519"
  :public-key "NqIjwLopQn6yQChtE21Mb97dAbAPe5UOuTa0tOakgD8="
  :addresses [{:protocol "udp" :address "roughtime.sturdystatistics.com:2002"}]}
}

Request Chaining

You can also chain requests. The command

clj -M:run :chain

Will chain a request through all servers in the ecosystem and report a bad actor if one is found.

Example output:

Running chain script; ignoring all other options.

All servers are consistent.

|                  :name |                                  :public-key | :lower-limit | :upper-limit | :expires-in |
|------------------------+----------------------------------------------+--------------+--------------+-------------|
|       int08h-Roughtime | AW5uAoTSTDfG5NfY1bTh08GUnOqlRb+HVhbJ3ODJvsE= |           -5 |            5 |           ∞ |
| Cloudflare-Roughtime-2 | 0GD7c3yP8xEc4Zl2zeuN2SlLvDVVocjsPSL8/Rl/7zg= |           -1 |            1 |     13h 12m |
|      Sturdy-Statistics | NqIjwLopQn6yQChtE21Mb97dAbAPe5UOuTa0tOakgD8= |          -10 |           10 |      1d 11h |
|           roughtime.se | S3AzfZJ5CjSdkJ21ZJGbxqdYP/SoE8fXKY0+aicsehI= |           -1 |            1 |      14d 2h |
|        time.txryan.com | iBVjxg/1j7y1+kQUTBYdTabxCppesU/07D4PMDJk2WA= |           -1 |            1 |      2y 5mo |

Examples

Query a named server:

clj -M:run :server-name SturdyStatistics
{:skew 0,
 :online-key-expires-in "∞",
 :maxt 18446744073709551615,
 :midp 1768457585,
 :local-time 1768457585}

Verbose request/response:

clj -M:run :server-name int08h :print-request :print-response
==== REQUEST ====
{"VER" "0x8000000c",
 "SRV"
 ["4a4344b8 ed36a263 cae105a2 92fe2661"
  "d964691d be07548f 43b5b523 2671f3ab"],
 "NONC"
 ["5e74bbfb 6902f802 9c464d0c e0a94bf5"
  "a4f2f803 fff56505 13d319c8 824dcfff"],
 "TYPE" 0,
 "ZZZZ" "0{912}"}

==== RESPONSE ====
{"SIG"
 ["2359bfd7 f3c4fa29 6847c016 40938256"
  "344c5c91 cdede673 372fc79f 4332a482"
  "7f2ffc4d 478c2295 413d57d5 505170ff"
  "aa11f3e2 c609453d be14192c 2edb060e"],
 "NONC"
 ["5e74bbfb 6902f802 9c464d0c e0a94bf5"
  "a4f2f803 fff56505 13d319c8 824dcfff"],
 "TYPE" 1,
 "PATH" [],
 "SREP"
 {"VER" "0x8000000c",
  "RADI" 10,
  "MIDP" 1768457531,
  "VERS"
  ("0"
   "0x80000001"
   "0x80000002"
   "0x80000003"
   "0x80000004"
   "0x80000006"
   "0x80000008"
   "0x80000009"
   "0x8000000a"
   "0x8000000b"
   "0x8000000c"),
  "ROOT"
  ["f5681bf4 c46ee065 3f21988c 23ed8708"
   "9461c0c8 b2267f4e f2720269 0105d74f"]},
 "CERT"
 {"SIG"
  ["c8735ec3 502f2b25 c4855de8 91e8ce24"
   "cf6b09c9 c954a958 ef7915ad f47c1097"
   "7f74063c e4b5c528 e3075c6f 0ca93ef3"
   "074892f6 4cb2608a 10a6ea36 0132c406"],
  "DELE"
  {"PUBK"
   ["c51da875 891887e1 9557784c d8e3b163"
    "990dd688 851633f6 dcced7de fcd9fcc2"],
   "MINT" 1768450687,
   "MAXT" 1768623487}},
 "INDX" 0}

==== RESULT ====
{:skew 0,
 :online-key-expires-in "1d 22h",
 :maxt 1768623487,
 :midp 1768457531,
 :local-time 1768457531}

Direct parameters (no ecosystem):

# Loopback example (adjust to your server):
clj -M:run :address "127.0.0.1:2002" :protocol "udp" :public-key "dWPfGcRTe9nbILRO3cyJsWFHUnSF2AEKQ6g2530UNHA=" :version-no "0x8000000c"

# Public server (int08h):
clj -M:run :version-no "0x8000000c" :public-key "AW5uAoTSTDfG5NfY1bTh08GUnOqlRb+HVhbJ3ODJvsE=" :address "roughtime.int08h.com:2002"

License

Apache License 2.0

Copyright © Sturdy Statistics