ntpstats.roughtime¶
Roughtime client (draft-ietf-ntp-roughtime-19).
Roughtime gives a signed, coarse (about 1 s) time from several servers and makes a lying server provable: every request nonce is derived from the previous response, so a chain of responses shows the order in which they were received. If two responses cannot both be right, the chain is a malfeasance report anyone can verify.
For ntpstats this is an independent, authenticated sanity bound: an NTP or PTP clock that disagrees with Roughtime by more than the Roughtime radius is wrong, whatever its own statistics say.
Signature checks need cryptography (pip install 'ntpstats[nts]').
RoughtimeError
¶
Bases: Exception
Malformed, unverifiable or missing response.
Response
dataclass
¶
Server
dataclass
¶
parse(spec, known=None)
classmethod
¶
NAME (from the list), or host:port=BASE64KEY.
Measurement
dataclass
¶
H(data, size=32)
¶
SHA-512 truncated to size bytes (32 in the IETF drafts).
encode(msg)
¶
Roughtime message: N, N-1 offsets, N tags (sorted as uint32 LE), values.
build_request(nonce, public_key=None, versions=DEFAULT_VERSIONS, min_size=MIN_REQUEST)
¶
Request packet: VER, NONC, TYPE=0, SRV (when the key is known), ZZZZ padding.
build_google_request(nonce, min_size=MIN_REQUEST)
¶
Google-Roughtime request: unframed message with a 64-byte NONC and PAD\xff padding to min_size.
merkle_root(leaf, path, index, size=32)
¶
Root from a leaf hash, PATH and INDX (draft-19 section 5.3.1); None if INDX has extra bits.
verify(request, response, public_key, server='', t_send=0.0, t_recv=0.0)
¶
Check a response against its request and the server's long-term key (draft-19 section 5.4).
Raises :class:RoughtimeError on any failure. Older drafts that sign the
nonce instead of the request, and microsecond timestamps, are accepted.
load_servers(source=None)
¶
Servers from a draft-19 server list (JSON, section 8.3), or the bundled list.
backoff(n, base=1.0)
¶
Minimum wait before retry n (1-based): min(base * 1.5**(n-1), 86400) s (draft-19 section 5).
query(server, nonce=None, timeout=3.0, tcp=False, retries=0, versions=DEFAULT_VERSIONS, family=0, sleep=time.sleep, google_fallback=True, nonce64=None)
¶
One verified exchange. UDP by default; tcp=True for paths that drop large datagrams.
If the IETF request gets no answer, a Google-Roughtime request (the
pre-IETF protocol that some servers still answer, with nonce64) is
tried once over UDP; :attr:Response.protocol says which one answered.
causal_violations(responses)
¶
Pairs (i, j), i received before j, with MIDP_i - RADI_i > MIDP_j + RADI_j.
measure(servers, rounds=2, timeout=3.0, tcp=False, spacing=0.0, rand=os.urandom, query_fn=None, **kw)
¶
Query the servers in order, rounds times (two per draft-19 section 8.2), chaining nonces.
Each nonce after the first is H(previous response || rand), so the responses prove their order (for a Google-Roughtime fallback the full 64-byte SHA-512 of the same input). Failed servers are recorded and skipped.
verify_report(report)
¶
Re-check a malfeasance report: every response valid, and each nonce chained from the previous one.